@photostructure/fs-metadata 0.5.0 → 0.5.1

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 (130) hide show
  1. package/C++_REVIEW_TODO.md +1 -1
  2. package/CHANGELOG.md +11 -2
  3. package/CLAUDE.md +269 -92
  4. package/CONTRIBUTING.md +41 -0
  5. package/README.md +20 -1
  6. package/WINDOWS_DEBUG_GUIDE.md +89 -0
  7. package/binding.gyp +3 -2
  8. package/dist/index.cjs +1440 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.cts +360 -0
  11. package/dist/index.d.mts +360 -0
  12. package/dist/index.d.ts +360 -0
  13. package/dist/index.mjs +1398 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/jest.config.cjs +1 -0
  16. package/package.json +25 -27
  17. package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  18. package/prebuilds/linux-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  19. package/prebuilds/linux-arm64/@photostructure+fs-metadata.musl.node +0 -0
  20. package/prebuilds/linux-x64/@photostructure+fs-metadata.glibc.node +0 -0
  21. package/prebuilds/linux-x64/@photostructure+fs-metadata.musl.node +0 -0
  22. package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
  23. package/scripts/check-memory.mjs +53 -173
  24. package/scripts/clang-tidy.ts +241 -0
  25. package/scripts/prebuild-linux-glibc.sh +108 -0
  26. package/scripts/precommit.ts +45 -0
  27. package/scripts/sanitizers-test.sh +157 -0
  28. package/scripts/{configure.mjs → setup-native.mjs} +4 -1
  29. package/scripts/valgrind-test.mjs +1 -1
  30. package/scripts/{valgrind.sh → valgrind-test.sh} +15 -0
  31. package/src/binding.cpp +1 -1
  32. package/src/common/error_utils.h +17 -0
  33. package/src/common/metadata_worker.h +4 -1
  34. package/src/darwin/hidden.cpp +13 -6
  35. package/src/darwin/volume_metadata.cpp +2 -2
  36. package/src/darwin/volume_mount_points.cpp +8 -1
  37. package/src/linux/blkid_cache.cpp +8 -2
  38. package/src/linux/volume_metadata.cpp +1 -1
  39. package/src/platform.ts +25 -0
  40. package/src/test-utils/benchmark-harness.ts +192 -0
  41. package/src/test-utils/debuglog-child.ts +30 -2
  42. package/src/test-utils/debuglog-enabled-child.ts +38 -8
  43. package/src/test-utils/jest-setup.ts +14 -0
  44. package/src/test-utils/worker-thread-helper.cjs +92 -0
  45. package/src/windows/hidden.cpp +20 -11
  46. package/coverage/base.css +0 -224
  47. package/coverage/block-navigation.js +0 -87
  48. package/coverage/favicon.png +0 -0
  49. package/coverage/index.html +0 -131
  50. package/coverage/lcov-report/base.css +0 -224
  51. package/coverage/lcov-report/block-navigation.js +0 -87
  52. package/coverage/lcov-report/favicon.png +0 -0
  53. package/coverage/lcov-report/index.html +0 -131
  54. package/coverage/lcov-report/prettify.css +0 -1
  55. package/coverage/lcov-report/prettify.js +0 -2
  56. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  57. package/coverage/lcov-report/sorter.js +0 -196
  58. package/coverage/lcov-report/src/array.ts.html +0 -217
  59. package/coverage/lcov-report/src/async.ts.html +0 -547
  60. package/coverage/lcov-report/src/debuglog.ts.html +0 -187
  61. package/coverage/lcov-report/src/defer.ts.html +0 -175
  62. package/coverage/lcov-report/src/dirname.ts.html +0 -124
  63. package/coverage/lcov-report/src/error.ts.html +0 -322
  64. package/coverage/lcov-report/src/fs.ts.html +0 -316
  65. package/coverage/lcov-report/src/glob.ts.html +0 -472
  66. package/coverage/lcov-report/src/hidden.ts.html +0 -724
  67. package/coverage/lcov-report/src/index.html +0 -521
  68. package/coverage/lcov-report/src/index.ts.html +0 -676
  69. package/coverage/lcov-report/src/linux/dev_disk.ts.html +0 -316
  70. package/coverage/lcov-report/src/linux/index.html +0 -146
  71. package/coverage/lcov-report/src/linux/mount_points.ts.html +0 -364
  72. package/coverage/lcov-report/src/linux/mtab.ts.html +0 -493
  73. package/coverage/lcov-report/src/mount_point.ts.html +0 -106
  74. package/coverage/lcov-report/src/number.ts.html +0 -148
  75. package/coverage/lcov-report/src/object.ts.html +0 -265
  76. package/coverage/lcov-report/src/options.ts.html +0 -475
  77. package/coverage/lcov-report/src/path.ts.html +0 -268
  78. package/coverage/lcov-report/src/platform.ts.html +0 -112
  79. package/coverage/lcov-report/src/random.ts.html +0 -205
  80. package/coverage/lcov-report/src/remote_info.ts.html +0 -553
  81. package/coverage/lcov-report/src/stack_path.ts.html +0 -298
  82. package/coverage/lcov-report/src/string.ts.html +0 -382
  83. package/coverage/lcov-report/src/string_enum.ts.html +0 -208
  84. package/coverage/lcov-report/src/system_volume.ts.html +0 -301
  85. package/coverage/lcov-report/src/unc.ts.html +0 -274
  86. package/coverage/lcov-report/src/units.ts.html +0 -274
  87. package/coverage/lcov-report/src/uuid.ts.html +0 -157
  88. package/coverage/lcov-report/src/volume_health_status.ts.html +0 -259
  89. package/coverage/lcov-report/src/volume_metadata.ts.html +0 -787
  90. package/coverage/lcov-report/src/volume_mount_points.ts.html +0 -388
  91. package/coverage/lcov.info +0 -3581
  92. package/coverage/prettify.css +0 -1
  93. package/coverage/prettify.js +0 -2
  94. package/coverage/sort-arrow-sprite.png +0 -0
  95. package/coverage/sorter.js +0 -196
  96. package/coverage/src/array.ts.html +0 -217
  97. package/coverage/src/async.ts.html +0 -547
  98. package/coverage/src/debuglog.ts.html +0 -187
  99. package/coverage/src/defer.ts.html +0 -175
  100. package/coverage/src/dirname.ts.html +0 -124
  101. package/coverage/src/error.ts.html +0 -322
  102. package/coverage/src/fs.ts.html +0 -316
  103. package/coverage/src/glob.ts.html +0 -472
  104. package/coverage/src/hidden.ts.html +0 -724
  105. package/coverage/src/index.html +0 -521
  106. package/coverage/src/index.ts.html +0 -676
  107. package/coverage/src/linux/dev_disk.ts.html +0 -316
  108. package/coverage/src/linux/index.html +0 -146
  109. package/coverage/src/linux/mount_points.ts.html +0 -364
  110. package/coverage/src/linux/mtab.ts.html +0 -493
  111. package/coverage/src/mount_point.ts.html +0 -106
  112. package/coverage/src/number.ts.html +0 -148
  113. package/coverage/src/object.ts.html +0 -265
  114. package/coverage/src/options.ts.html +0 -475
  115. package/coverage/src/path.ts.html +0 -268
  116. package/coverage/src/platform.ts.html +0 -112
  117. package/coverage/src/random.ts.html +0 -205
  118. package/coverage/src/remote_info.ts.html +0 -553
  119. package/coverage/src/stack_path.ts.html +0 -298
  120. package/coverage/src/string.ts.html +0 -382
  121. package/coverage/src/string_enum.ts.html +0 -208
  122. package/coverage/src/system_volume.ts.html +0 -301
  123. package/coverage/src/unc.ts.html +0 -274
  124. package/coverage/src/units.ts.html +0 -274
  125. package/coverage/src/uuid.ts.html +0 -157
  126. package/coverage/src/volume_health_status.ts.html +0 -259
  127. package/coverage/src/volume_metadata.ts.html +0 -787
  128. package/coverage/src/volume_mount_points.ts.html +0 -388
  129. package/scripts/clang-tidy.mjs +0 -73
  130. package/scripts/run-asan.sh +0 -92
@@ -243,7 +243,7 @@ This document outlines a comprehensive review of all C++ files in the fs-metadat
243
243
  3. **Memory Testing Infrastructure**
244
244
  - Created comprehensive memory testing documentation (`docs/MEMORY_TESTING.md`)
245
245
  - Improved ASan configuration with proper suppressions
246
- - Created standalone test runner (`scripts/run-asan.sh`)
246
+ - Created standalone test runner (`scripts/sanitizers-test.sh`)
247
247
  - Updated `scripts/check-memory.mjs` with better ASan support
248
248
  - All memory tests integrated into CI/CD
249
249
 
package/CHANGELOG.md CHANGED
@@ -14,7 +14,7 @@ Fixed for any bug fixes.
14
14
  Security in case of vulnerabilities.
15
15
  -->
16
16
 
17
- ## [0.5.0] - 2025-06-01
17
+ ## [0.6.0] - 2025-06-09
18
18
 
19
19
  ### Added
20
20
 
@@ -22,6 +22,10 @@ Security in case of vulnerabilities.
22
22
  - Thread safety tests for Windows platform
23
23
  - Platform-specific build scripts with automatic OS detection
24
24
  - Clang-tidy integration for C++ code analysis
25
+ - Worker thread helper for volume metadata operations
26
+ - NAPI_VERSION=9 definition for improved compatibility
27
+ - Dynamic test timeout configuration based on environment (CI, platform, architecture)
28
+ - Prebuild script for Linux GLIBC compatibility in Docker environments
25
29
 
26
30
  ### Breaking
27
31
 
@@ -32,6 +36,9 @@ Security in case of vulnerabilities.
32
36
  - Simplified ESM/CJS dual module support with unified build configuration
33
37
  - Enhanced test coverage with additional error handling and edge case tests
34
38
  - Updated all imports to use `node:` prefix for built-in modules
39
+ - Reorganized build and test scripts for better clarity
40
+ - Improved memory test workflow for cross-platform compatibility
41
+ - Renamed native module target from `node_fs_meta` to `fs_metadata` for consistency
35
42
 
36
43
  ### Fixed
37
44
 
@@ -40,7 +47,9 @@ Security in case of vulnerabilities.
40
47
  - Fixed buffer allocation issues in Windows networking and volume operations
41
48
  - Enhanced resource management with better validation for empty mount points
42
49
  - Made `SystemPathPatternsDefault` values visible in TypeScript typings
43
- - Improved test reliability across different CI environments
50
+ - Added Napi::HandleScope to OnOK and OnError methods for proper scope management
51
+ - Removed unnecessary std::move operations in worker implementations
52
+ - Resolved CI test reliability issues across different environments, particularly Alpine ARM64 emulation timeouts
44
53
 
45
54
  ## [0.4.0] - 2025-01-09
46
55
 
package/CLAUDE.md CHANGED
@@ -15,92 +15,13 @@ This is @photostructure/fs-metadata - a cross-platform native Node.js module for
15
15
  - Timeout handling for unresponsive network volumes
16
16
  - ESM and CJS module support
17
17
  - Full TypeScript type definitions
18
+ - Worker threads support with proper context isolation
18
19
 
19
20
  ### Platform-Specific Notes
20
21
  - **Linux**: Optional GIO/GVfs mount support via Gnome libraries
21
22
  - **Windows**: Uses separate threads per mountpoint for health checks to handle blocked system calls
22
23
  - **System Volumes**: Each platform handles system volumes differently; the library uses heuristics to identify them
23
24
 
24
- ## Common Commands
25
-
26
- ### Build and Development
27
- ```bash
28
- # Install dependencies and build native modules
29
- npm install
30
-
31
- # Configure platform-specific build settings
32
- npm run configure
33
-
34
- # Build native bindings
35
- npm run node-gyp-rebuild
36
-
37
- # Create prebuilds for distribution
38
- npm run prebuild
39
-
40
- # Bundle TypeScript to dist/
41
- npm run bundle
42
- ```
43
-
44
- ### Testing
45
- ```bash
46
- # Run all tests with coverage (includes memory tests on Linux)
47
- npm run tests
48
-
49
- # Run CommonJS tests
50
- npm test cjs
51
-
52
- # Run ESM tests
53
- npm test esm
54
-
55
- # Test memory leaks (JavaScript)
56
- npm run test:memory
57
-
58
- # Run valgrind memory analysis (Linux only)
59
- npm run test:valgrind
60
-
61
- # Run comprehensive memory tests (JavaScript + valgrind on Linux)
62
- npm run tests:memory
63
-
64
- # Run AddressSanitizer tests (Linux only)
65
- npm run asan
66
-
67
- # Run a specific test file (no coverage)
68
- npm test volume_metadata
69
- ```
70
-
71
- ### Code Quality
72
- ```bash
73
- # Run ESLint
74
- npm run lint
75
-
76
- # Fix ESLint issues
77
- npm run lint:fix
78
-
79
- # Format code (all formats)
80
- npm run fmt
81
-
82
- # Format C++ code only
83
- npm run fmt:cpp
84
-
85
- # Format TypeScript only
86
- npm run fmt:ts
87
-
88
- # Type checking
89
- npm run compile
90
- ```
91
-
92
- ### Pre-commit
93
- ```bash
94
- # Full precommit check (fmt, clean, prebuild, tests)
95
- npm run precommit
96
- ```
97
-
98
- ### Documentation
99
- ```bash
100
- # Generate API documentation
101
- npm run docs
102
- ```
103
-
104
25
  ## Architecture Overview
105
26
 
106
27
  ### Core Structure
@@ -122,23 +43,29 @@ npm run docs
122
43
  - Provides prebuilt binaries via `prebuildify` for common platforms
123
44
  - Supports both ESM and CJS module formats
124
45
 
46
+ ### Cross-Module Compatibility
47
+ - **Directory Path Resolution**: Use `_dirname()` from `./dirname` instead of `__dirname`
48
+ - Works in both CommonJS and ESM contexts
49
+ - Handles Jest test environments correctly
50
+ - Example: `const dir = _dirname()` instead of `const dir = __dirname`
51
+
125
52
  ### Testing Strategy
126
53
  - Jest for both CJS and ESM test suites
127
54
  - Platform-specific test expectations handled via `isWindows`, `isMacOS`, `isLinux` helpers
128
55
  - Memory leak testing with garbage collection monitoring
129
56
  - Coverage thresholds: 80% for all metrics
130
57
 
131
- ### Memory Testing
132
- - JavaScript memory tests: `npm run test:memory` - uses GC and heap monitoring
133
- - Valgrind integration: `npm run test:valgrind` - runs on Linux only, checks for memory leaks
134
- - AddressSanitizer: `npm run asan` - runs on Linux only, detects memory errors and leaks (~2x faster than Valgrind)
135
- - Comprehensive memory tests: `npm run tests:memory` - runs all memory tests appropriate for the platform
136
- - Automated test runners: `scripts/valgrind-test.mjs` and `scripts/run-asan.sh`
137
- - CI/CD includes both valgrind and ASAN tests via `.github/workflows/memory-tests.yml`
138
- - Cross-platform memory check script: `scripts/check-memory.mjs` handles platform differences
139
- - Suppression files: `.valgrind.supp` (Valgrind), `.lsan-suppressions.txt` (LeakSanitizer)
140
- - Memory tests are integrated into `npm run tests` pipeline on Linux
141
- - See `docs/MEMORY_TESTING.md` for detailed memory testing guide
58
+ ### Testing File System Metadata
59
+ - **Important**: File system metadata like `available` space, `used` space, and other dynamic properties change continuously as other processes run on the machine
60
+ - Tests should **never** expect exact equality for these values between multiple calls
61
+ - **Do not** make range assertions (e.g., `available > 0` or `used < size`) because:
62
+ - Files can be created or deleted between calls (potentially gigabytes)
63
+ - The changes can be dramatic and unpredictable
64
+ - Instead, for dynamic metadata:
65
+ - Only verify the value exists and has the correct type
66
+ - Use `typeof result.available === 'number'` rather than range checks
67
+ - Focus on testing static properties (e.g., `size`, `mountFrom`, `fstype`) for exact equality
68
+ - Consider using snapshot testing only for stable properties
142
69
 
143
70
  ### Timeout Handling
144
71
  - Default timeout for volume operations to handle unresponsive network mounts
@@ -166,4 +93,254 @@ console.dir({ volumeMetadata });
166
93
  // Check if a file is hidden
167
94
  import { isHidden } from "@photostructure/fs-metadata";
168
95
  const hidden = await isHidden("/path/to/file");
169
- ```
96
+ ```
97
+
98
+ ## CI/CD Test Reliability Guidelines
99
+
100
+ Based on analysis of recent test failures, here are critical patterns to avoid flaky tests:
101
+
102
+ ### 1. Benchmark and Performance Tests
103
+ - **Problem**: "Cannot log after tests are done" errors in worker_threads.test.ts
104
+ - **Solution**:
105
+ - Always await all async operations before test completion
106
+ - Use proper test lifecycle hooks (afterEach/afterAll) for cleanup
107
+ - Avoid console.log in async contexts without proper synchronization
108
+ - Consider using test.concurrent with explicit done() callbacks
109
+
110
+ ### 2. Alpine Linux ARM64 Issues
111
+ - **Problem**: Tests timeout on emulated Alpine ARM64 environments
112
+ - **Solution**:
113
+ - Skip process-spawning tests on Alpine ARM64 (`if (isAlpine && isARM64)`)
114
+ - Use increased timeout multipliers (20x) for emulated environments
115
+ - Detect emulation via `/proc/cpuinfo` or environment checks
116
+ - Consider separate test suites for native vs emulated environments
117
+
118
+ ### 3. Worker Thread Management
119
+ - **Problem**: Race conditions in concurrent worker operations
120
+ - **Solution**:
121
+ - Implement proper worker pool management with size limits
122
+ - Use Promise.allSettled() instead of Promise.all() for parallel operations
123
+ - Add explicit cleanup in test teardown to terminate all workers
124
+ - Set reasonable concurrency limits based on environment (CPU cores)
125
+
126
+ ### 4. Timeout Test Reliability
127
+ - **Problem**: Timeout tests fail due to timing precision issues
128
+ - **Solution**:
129
+ - Never use exact timing assertions (e.g., expect 100ms)
130
+ - Use ranges with adequate margins (e.g., 90-110ms)
131
+ - Account for CI environment variability (slower machines)
132
+ - Consider mocking timers for deterministic behavior
133
+
134
+ ### 5. File System Operations
135
+ - **Problem**: ENOENT errors for test directories, permission issues
136
+ - **Solution**:
137
+ - Always use unique temporary directories per test
138
+ - Clean up test artifacts in afterEach hooks
139
+ - Check directory existence before operations
140
+ - Handle platform-specific path separators
141
+
142
+ ### 6. Memory and Resource Leaks
143
+ - **Problem**: Tests don't properly clean up resources
144
+ - **Solution**:
145
+ - Explicitly close all file handles, network connections
146
+ - Use try-finally blocks for resource cleanup
147
+ - Monitor memory usage in long-running tests
148
+ - Implement proper garbage collection triggers
149
+
150
+ ### 7. Platform-Specific Failures
151
+ - **Problem**: Different behavior across Windows/macOS/Linux
152
+ - **Solution**:
153
+ - Use platform detection helpers consistently
154
+ - Skip platform-specific tests appropriately
155
+ - Account for filesystem differences (case sensitivity, path formats)
156
+ - Test with platform-specific CI matrices
157
+
158
+ ### 8. Jest Configuration
159
+ - **Problem**: Tests interfere with each other
160
+ - **Solution**:
161
+ - Use `--runInBand` for tests with shared resources
162
+ - Clear module cache between tests when needed
163
+ - Isolate tests that spawn processes
164
+ - Configure proper test timeouts per environment
165
+
166
+ ### Async Cleanup Anti-Patterns
167
+
168
+ **IMPORTANT**: The following approaches are NOT valid solutions for async cleanup issues:
169
+
170
+ ```javascript
171
+ // BAD: Arbitrary timeouts in tests
172
+ await new Promise((resolve) => setTimeout(resolve, 100));
173
+
174
+ // BAD: Forcing garbage collection
175
+ if (global.gc) {
176
+ global.gc();
177
+ }
178
+
179
+ // BAD: Adding setImmediate in afterAll to "fix" hanging tests
180
+ afterAll(async () => {
181
+ await new Promise((resolve) => setImmediate(resolve));
182
+ });
183
+ ```
184
+
185
+ **Why these are problematic:**
186
+
187
+ 1. **Arbitrary timeouts** are race conditions waiting to happen. They might work on fast machines but fail on slower CI runners.
188
+ 2. **Forcing GC** should never be required for correct behavior. If your code depends on GC for correctness, it has a fundamental design flaw.
189
+ 3. **setImmediate/nextTick delays** in cleanup hooks don't fix the root cause - they just paper over the real issue.
190
+ 4. These approaches mask the real problem instead of fixing it.
191
+
192
+ **Note**: This is different from legitimate uses of timeouts, such as:
193
+
194
+ - Waiting for time to pass to test timestamp changes
195
+ - Rate limiting or throttling tests
196
+ - Testing timeout behavior itself
197
+
198
+ The anti-pattern is using timeouts or GC to "fix" async cleanup issues.
199
+
200
+ **What to do instead:**
201
+
202
+ 1. Find the actual resource that's keeping the process alive (use `--detectOpenHandles`)
203
+ 2. Ensure all database connections are properly closed
204
+ 3. Ensure all file handles are closed
205
+ 4. Cancel or await all pending async operations
206
+ 5. Use proper resource management patterns (RAII, try-finally, using statements)
207
+
208
+ ### Windows-Compatible Directory Cleanup
209
+
210
+ **IMPORTANT**: Never use `fs.rmSync()` or `fs.rm()` without proper Windows retry logic for directory cleanup in tests.
211
+
212
+ **Problem**: On Windows, file handles and directory locks can remain active longer than on Unix systems, causing `EBUSY` errors during cleanup.
213
+
214
+ **Proper Solution**: Use `fsp.rm()` (async) with retry options:
215
+
216
+ ```typescript
217
+ await fsp.rm(tempDir, {
218
+ recursive: true,
219
+ force: true,
220
+ maxRetries: process.platform === "win32" ? 3 : 1,
221
+ retryDelay: process.platform === "win32" ? 100 : 0,
222
+ });
223
+ ```
224
+
225
+ **Best Practice**: Use existing test utilities that handle Windows-compatible cleanup patterns. Don't manually clean up temp directories - let the test framework handle it with proper retry logic.
226
+
227
+ ### Adaptive Timeout Testing
228
+
229
+ **Problem**: Fixed timeouts don't account for varying CI environment performance.
230
+
231
+ **Root Causes**:
232
+
233
+ - Alpine Linux (musl libc) is 2x slower than glibc
234
+ - ARM64 emulation on x64 runners is 5x slower
235
+ - Windows process operations are 4x slower
236
+ - macOS VMs are 4x slower
237
+ - CI environments have resource constraints
238
+
239
+ **Solutions**:
240
+
241
+ ```typescript
242
+ // DON'T: Use fixed timeouts
243
+ test("my test", async () => {
244
+ // Test code
245
+ }, 10000);
246
+
247
+ // DO: Use adaptive timeouts based on environment
248
+ import { getTestTimeout } from "./test-utils/test-timeout-config";
249
+
250
+ test(
251
+ "my test",
252
+ async () => {
253
+ // Test code
254
+ },
255
+ getTestTimeout(10000),
256
+ );
257
+
258
+ // DO: Account for platform timing differences
259
+ const timingMultiplier = process.platform === "win32" ? 4 :
260
+ process.platform === "darwin" ? 4 :
261
+ process.env.CI ? 2 : 1;
262
+ ```
263
+
264
+ ### Multi-Process Test Synchronization
265
+
266
+ **Problem**: Multi-process tests failing due to race conditions between process startup and test assertions.
267
+
268
+ **Root Cause**: The timing between process startup and resource acquisition varies significantly by platform.
269
+
270
+ **Solutions**:
271
+
272
+ ```typescript
273
+ // DON'T: Assume immediate process readiness
274
+ const proc = spawn(nodeCmd, [script]);
275
+ const result = await waitForProcessResult(proc);
276
+ expect(result).toBe("expected_outcome"); // May fail due to timing
277
+
278
+ // DO: Use explicit synchronization signals
279
+ const script = `
280
+ // Setup code
281
+ console.log("READY"); // Signal readiness
282
+ // Main test logic
283
+ console.log("RESULT:" + outcome);
284
+ `;
285
+
286
+ const proc = spawn(process.execPath, ["-e", script]);
287
+ await waitForOutput(proc, "READY"); // Wait for process to be ready
288
+ const result = await waitForOutput(proc, "RESULT:");
289
+ expect(result.split(":")[1]).toBe("expected_outcome");
290
+ ```
291
+
292
+ ### Wait-for-Condition Pattern
293
+
294
+ **Problem**: Tests failing because they don't wait for asynchronous conditions to be met.
295
+
296
+ **Solution**: Implement robust condition waiting with platform-aware timing:
297
+
298
+ ```typescript
299
+ async function waitForCondition(
300
+ check: () => boolean | Promise<boolean>,
301
+ options: {
302
+ maxAttempts?: number;
303
+ delay?: number;
304
+ timeoutMs?: number;
305
+ } = {}
306
+ ) {
307
+ const {
308
+ maxAttempts = 50,
309
+ delay = 100,
310
+ timeoutMs = 30000
311
+ } = options;
312
+
313
+ const startTime = Date.now();
314
+
315
+ for (let i = 0; i < maxAttempts; i++) {
316
+ if (Date.now() - startTime > timeoutMs) {
317
+ throw new Error(`Condition not met within ${timeoutMs}ms`);
318
+ }
319
+
320
+ if (await check()) return true;
321
+ await new Promise((resolve) => setTimeout(resolve, delay));
322
+ }
323
+
324
+ return false;
325
+ }
326
+
327
+ // Usage example
328
+ await waitForCondition(
329
+ () => fs.existsSync(expectedFile),
330
+ { timeoutMs: getTestTimeout(10000) }
331
+ );
332
+ ```
333
+
334
+ ### Best Practices Summary
335
+ 1. **Always clean up**: Resources, timers, workers, file handles
336
+ 2. **Never assume timing**: Use ranges and adaptive timeouts, not exact values
337
+ 3. **Isolate tests**: Each test should be independent
338
+ 4. **Platform awareness**: Skip tests that can't work on certain platforms
339
+ 5. **Proper async handling**: Always await or return promises
340
+ 6. **Resource limits**: Don't spawn unlimited workers/processes
341
+ 7. **Environment detection**: Adjust behavior for CI vs local
342
+ 8. **Deterministic tests**: Mock external dependencies when possible
343
+ 9. **Explicit synchronization**: Use signals for multi-process coordination
344
+ 10. **Robust waiting**: Use condition-based waiting instead of arbitrary timeouts
345
+ 11. **Windows compatibility**: Use retry logic for file operations on Windows
346
+ 12. **Anti-pattern awareness**: Avoid masking problems with timeouts or forced GC
package/CONTRIBUTING.md CHANGED
@@ -54,18 +54,59 @@ into account both Windows and POSIX systems.
54
54
  **Why**: Windows Command Prompt/PowerShell parses the entire command line before execution, including the Unix-specific parts that would never run on Windows. Even though constructs like `node scripts/is-platform.mjs win32 || <unix-command>` would exit early on Windows, the shell still tries to parse the syntax after `||`.
55
55
 
56
56
  **Solution**: For platform-specific npm scripts that use shell operators:
57
+
57
58
  - Create a wrapper Node.js script that handles platform detection internally (see `scripts/clang-tidy.mjs`)
58
59
  - The wrapper can use `process.platform` or `os.platform()` to detect Windows and exit early
59
60
  - Unix-specific commands can then be spawned using `child_process.spawn()` with `sh -c`
60
61
 
61
62
  **Example**: The `clang-tidy` npm script was moved from:
63
+
62
64
  ```json
63
65
  "clang-tidy": "node scripts/is-platform.mjs win32 || (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)"
64
66
  ```
65
67
 
66
68
  To:
69
+
67
70
  ```json
68
71
  "clang-tidy": "node scripts/clang-tidy.mjs"
69
72
  ```
70
73
 
71
74
  Where the script handles platform detection and command execution internally.
75
+
76
+ ## npm Script Naming Conventions
77
+
78
+ This project follows consistent naming patterns for npm scripts to improve discoverability and maintainability:
79
+
80
+ ### Action:Target Format
81
+
82
+ Scripts follow an `action:target` pattern where:
83
+
84
+ - **action**: The operation being performed (`build`, `clean`, `lint`, `test`)
85
+ - **target**: What the action operates on (`native`, `ts`, `dist`)
86
+
87
+ Examples:
88
+
89
+ - `build:native` - Build native C++ code
90
+ - `lint:ts` - Lint TypeScript code
91
+ - `clean:dist` - Clean distribution files
92
+
93
+ ### Parallel Execution
94
+
95
+ Actions that have multiple targets can be run in parallel using wildcards:
96
+
97
+ - `npm run clean` runs all `clean:*` scripts
98
+ - `npm run lint` runs all `lint:*` scripts
99
+ - Uses `run-p` from npm-run-all for parallel execution
100
+
101
+ ### Special Namespaces
102
+
103
+ - **memory:\*** - Memory testing scripts that should not run automatically with `test:*`
104
+ - `memory:test` - Comprehensive memory testing suite
105
+ - Run with `ENABLE_ASAN=1` to include sanitizer tests
106
+
107
+ ### Naming Guidelines
108
+
109
+ - Use explicit names to avoid ambiguity (e.g., `setup:native` instead of just `setup`)
110
+ - Group related scripts by action prefix for easy wildcard execution
111
+ - Avoid names that could cause npm lifecycle conflicts (e.g., `prebuild` vs `build`)
112
+ - Use descriptive suffixes that clearly indicate the target or purpose
package/README.md CHANGED
@@ -16,7 +16,8 @@ Built and supported by [PhotoStructure](https://photostructure.com).
16
16
  - Cross-platform support:
17
17
  - Windows 10+ (x64)
18
18
  - macOS 14+
19
- - Ubuntu 22+ (x64, arm64) (with Gnome GIO/`GVfs` mount support where available)
19
+ - Debian 11/Ubuntu 20.04+ (x64, arm64) (with Gnome GIO/`GVfs` mount support where available)
20
+ - Alpine 3.21+ (x64, arm64)
20
21
 
21
22
  - [List all mounted volumes/drives](https://photostructure.github.io/fs-metadata/functions/getVolumeMountPoints.html)
22
23
 
@@ -41,6 +42,24 @@ Built and supported by [PhotoStructure](https://photostructure.com).
41
42
 
42
43
  - Comprehensive test coverage
43
44
 
45
+ ## Supported Platforms
46
+
47
+ Prebuilt binaries are provided for the following platforms:
48
+
49
+ | Platform | Architecture | Node.js | Minimum OS Version |
50
+ |----------|--------------|---------|-------------------|
51
+ | Windows | x64 | 20+ | Windows 10 |
52
+ | macOS | x64 | 20+ | macOS 14 (Sonoma) |
53
+ | macOS | arm64 | 20+ | macOS 14 (Sonoma) |
54
+ | Linux (glibc) | x64 | 20+ | Debian 11 (Bullseye), Ubuntu 20.04, GLIBC 2.31+ |
55
+ | Linux (glibc) | arm64 | 20+ | Debian 11 (Bullseye), Ubuntu 20.04, GLIBC 2.31+ |
56
+ | Linux (musl) | x64 | 20+ | Alpine 3.21 |
57
+ | Linux (musl) | arm64 | 20+ | Alpine 3.21 |
58
+
59
+ Notes:
60
+ - Linux binaries require GLIBC 2.31+ (Debian 11 Bullseye or newer). **Note**: this means the `node:20` docker image is **not** supported, due to the last several major versions of `node-gyp-build` requiring both a newer python and newer C++ version.
61
+ - Electron is supported via [Node-API](https://nodejs.org/api/n-api.html) compatibility
62
+
44
63
  ## Installation
45
64
 
46
65
  ```bash
@@ -0,0 +1,89 @@
1
+ # Windows and Alpine Linux Debugging Guide for debuglog.test.ts
2
+
3
+ ## Issue Summary
4
+ The debuglog.test.ts file was failing on:
5
+ 1. **Windows CI**: "Converting circular structure to JSON" error in Jest's message passing system when `execFileSync` threw errors with circular references
6
+ 2. **Alpine Linux**: `ETIMEDOUT` errors when spawning child processes with `npx`
7
+
8
+ ## Changes Made
9
+
10
+ 1. **Replaced execFileSync with spawnSync**
11
+ - `spawnSync` provides better process control and doesn't throw errors with circular references
12
+ - Added `windowsHide: true` to prevent console windows from appearing on Windows
13
+ - Added `shell: true` on Windows only for proper npx execution
14
+ - Increased timeout to 30 seconds for slower environments like Alpine Linux containers
15
+
16
+ 2. **Enhanced Error Handling**
17
+ - Extract only serializable properties from errors (message, code, status, stderr, stdout)
18
+ - Added detailed logging for Windows-specific debugging
19
+ - Prevent circular reference errors from reaching Jest's worker communication
20
+
21
+ 3. **Improved Child Process Scripts**
22
+ - Added uncaughtException and unhandledRejection handlers
23
+ - Use `process.stdout.write` instead of `console.log` for cleaner output
24
+ - Added detailed error logging with stack traces to stderr
25
+
26
+ ## Manual Testing on Windows
27
+
28
+ To test these changes on your Windows test box:
29
+
30
+ 1. **Run the specific test file:**
31
+ ```powershell
32
+ npm test -- src/debuglog.test.ts
33
+ ```
34
+
35
+ 2. **Run with verbose output to see debugging info:**
36
+ ```powershell
37
+ npm test -- src/debuglog.test.ts --verbose
38
+ ```
39
+
40
+ 3. **If tests fail, check for:**
41
+ - Console output showing "Windows child process error" with detailed info
42
+ - Error messages, status codes, and stderr output
43
+ - Whether `npx tsx` is available and working correctly
44
+
45
+ 4. **To test individual child scripts directly:**
46
+ ```powershell
47
+ # Test debuglog-child.ts
48
+ $env:NODE_DEBUG="fs-metadata"
49
+ npx tsx src/test-utils/debuglog-child.ts
50
+
51
+ # Test debuglog-enabled-child.ts
52
+ $env:NODE_DEBUG="fs-metadata"
53
+ npx tsx src/test-utils/debuglog-enabled-child.ts
54
+ ```
55
+
56
+ 5. **Check for hanging processes:**
57
+ - The tests now have a 5-second timeout
58
+ - If a test hangs, it will fail with a timeout error
59
+ - Check Task Manager for any orphaned node.exe processes
60
+
61
+ ## Debugging Tips
62
+
63
+ 1. **Enable Node.js debugging:**
64
+ ```powershell
65
+ $env:NODE_OPTIONS="--trace-warnings"
66
+ npm test -- src/debuglog.test.ts
67
+ ```
68
+
69
+ 2. **Check npx/tsx availability:**
70
+ ```powershell
71
+ npx --version
72
+ npx tsx --version
73
+ ```
74
+
75
+ 3. **If npx tsx fails, try direct execution:**
76
+ ```powershell
77
+ node_modules/.bin/tsx src/test-utils/debuglog-child.ts
78
+ ```
79
+
80
+ ## Expected Behavior
81
+
82
+ - All 10 tests in debuglog.test.ts should pass
83
+ - No "Converting circular structure to JSON" errors
84
+ - Child processes should exit cleanly without hanging
85
+ - Detailed error information should be logged on failures
86
+
87
+ ## Rollback Plan
88
+
89
+ If these changes cause new issues, the previous approach using execFileSync can be restored by reverting this commit. The key difference is the switch from execFileSync to spawnSync and the enhanced error handling.
package/binding.gyp CHANGED
@@ -4,7 +4,7 @@
4
4
  },
5
5
  "targets": [
6
6
  {
7
- "target_name": "node_fs_meta",
7
+ "target_name": "fs_metadata",
8
8
  "sources": [
9
9
  "src/binding.cpp"
10
10
  ],
@@ -16,7 +16,8 @@
16
16
  "<!(node -p \"require('node-addon-api').gyp\")"
17
17
  ],
18
18
  "defines": [
19
- "NAPI_CPP_EXCEPTIONS"
19
+ "NAPI_CPP_EXCEPTIONS",
20
+ "NAPI_VERSION=9"
20
21
  ],
21
22
  "conditions": [
22
23
  [