@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.
- package/C++_REVIEW_TODO.md +1 -1
- package/CHANGELOG.md +11 -2
- package/CLAUDE.md +269 -92
- package/CONTRIBUTING.md +41 -0
- package/README.md +20 -1
- package/WINDOWS_DEBUG_GUIDE.md +89 -0
- package/binding.gyp +3 -2
- package/dist/index.cjs +1440 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +360 -0
- package/dist/index.d.mts +360 -0
- package/dist/index.d.ts +360 -0
- package/dist/index.mjs +1398 -0
- package/dist/index.mjs.map +1 -0
- package/jest.config.cjs +1 -0
- package/package.json +25 -27
- package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-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.glibc.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
- package/scripts/check-memory.mjs +53 -173
- package/scripts/clang-tidy.ts +241 -0
- package/scripts/prebuild-linux-glibc.sh +108 -0
- package/scripts/precommit.ts +45 -0
- package/scripts/sanitizers-test.sh +157 -0
- package/scripts/{configure.mjs → setup-native.mjs} +4 -1
- package/scripts/valgrind-test.mjs +1 -1
- package/scripts/{valgrind.sh → valgrind-test.sh} +15 -0
- package/src/binding.cpp +1 -1
- package/src/common/error_utils.h +17 -0
- package/src/common/metadata_worker.h +4 -1
- package/src/darwin/hidden.cpp +13 -6
- package/src/darwin/volume_metadata.cpp +2 -2
- package/src/darwin/volume_mount_points.cpp +8 -1
- package/src/linux/blkid_cache.cpp +8 -2
- package/src/linux/volume_metadata.cpp +1 -1
- package/src/platform.ts +25 -0
- package/src/test-utils/benchmark-harness.ts +192 -0
- package/src/test-utils/debuglog-child.ts +30 -2
- package/src/test-utils/debuglog-enabled-child.ts +38 -8
- package/src/test-utils/jest-setup.ts +14 -0
- package/src/test-utils/worker-thread-helper.cjs +92 -0
- package/src/windows/hidden.cpp +20 -11
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -131
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -131
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -196
- package/coverage/lcov-report/src/array.ts.html +0 -217
- package/coverage/lcov-report/src/async.ts.html +0 -547
- package/coverage/lcov-report/src/debuglog.ts.html +0 -187
- package/coverage/lcov-report/src/defer.ts.html +0 -175
- package/coverage/lcov-report/src/dirname.ts.html +0 -124
- package/coverage/lcov-report/src/error.ts.html +0 -322
- package/coverage/lcov-report/src/fs.ts.html +0 -316
- package/coverage/lcov-report/src/glob.ts.html +0 -472
- package/coverage/lcov-report/src/hidden.ts.html +0 -724
- package/coverage/lcov-report/src/index.html +0 -521
- package/coverage/lcov-report/src/index.ts.html +0 -676
- package/coverage/lcov-report/src/linux/dev_disk.ts.html +0 -316
- package/coverage/lcov-report/src/linux/index.html +0 -146
- package/coverage/lcov-report/src/linux/mount_points.ts.html +0 -364
- package/coverage/lcov-report/src/linux/mtab.ts.html +0 -493
- package/coverage/lcov-report/src/mount_point.ts.html +0 -106
- package/coverage/lcov-report/src/number.ts.html +0 -148
- package/coverage/lcov-report/src/object.ts.html +0 -265
- package/coverage/lcov-report/src/options.ts.html +0 -475
- package/coverage/lcov-report/src/path.ts.html +0 -268
- package/coverage/lcov-report/src/platform.ts.html +0 -112
- package/coverage/lcov-report/src/random.ts.html +0 -205
- package/coverage/lcov-report/src/remote_info.ts.html +0 -553
- package/coverage/lcov-report/src/stack_path.ts.html +0 -298
- package/coverage/lcov-report/src/string.ts.html +0 -382
- package/coverage/lcov-report/src/string_enum.ts.html +0 -208
- package/coverage/lcov-report/src/system_volume.ts.html +0 -301
- package/coverage/lcov-report/src/unc.ts.html +0 -274
- package/coverage/lcov-report/src/units.ts.html +0 -274
- package/coverage/lcov-report/src/uuid.ts.html +0 -157
- package/coverage/lcov-report/src/volume_health_status.ts.html +0 -259
- package/coverage/lcov-report/src/volume_metadata.ts.html +0 -787
- package/coverage/lcov-report/src/volume_mount_points.ts.html +0 -388
- package/coverage/lcov.info +0 -3581
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -196
- package/coverage/src/array.ts.html +0 -217
- package/coverage/src/async.ts.html +0 -547
- package/coverage/src/debuglog.ts.html +0 -187
- package/coverage/src/defer.ts.html +0 -175
- package/coverage/src/dirname.ts.html +0 -124
- package/coverage/src/error.ts.html +0 -322
- package/coverage/src/fs.ts.html +0 -316
- package/coverage/src/glob.ts.html +0 -472
- package/coverage/src/hidden.ts.html +0 -724
- package/coverage/src/index.html +0 -521
- package/coverage/src/index.ts.html +0 -676
- package/coverage/src/linux/dev_disk.ts.html +0 -316
- package/coverage/src/linux/index.html +0 -146
- package/coverage/src/linux/mount_points.ts.html +0 -364
- package/coverage/src/linux/mtab.ts.html +0 -493
- package/coverage/src/mount_point.ts.html +0 -106
- package/coverage/src/number.ts.html +0 -148
- package/coverage/src/object.ts.html +0 -265
- package/coverage/src/options.ts.html +0 -475
- package/coverage/src/path.ts.html +0 -268
- package/coverage/src/platform.ts.html +0 -112
- package/coverage/src/random.ts.html +0 -205
- package/coverage/src/remote_info.ts.html +0 -553
- package/coverage/src/stack_path.ts.html +0 -298
- package/coverage/src/string.ts.html +0 -382
- package/coverage/src/string_enum.ts.html +0 -208
- package/coverage/src/system_volume.ts.html +0 -301
- package/coverage/src/unc.ts.html +0 -274
- package/coverage/src/units.ts.html +0 -274
- package/coverage/src/uuid.ts.html +0 -157
- package/coverage/src/volume_health_status.ts.html +0 -259
- package/coverage/src/volume_metadata.ts.html +0 -787
- package/coverage/src/volume_mount_points.ts.html +0 -388
- package/scripts/clang-tidy.mjs +0 -73
- package/scripts/run-asan.sh +0 -92
package/C++_REVIEW_TODO.md
CHANGED
|
@@ -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/
|
|
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.
|
|
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
|
-
-
|
|
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
|
-
###
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
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
|
|
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": "
|
|
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
|
[
|