@photostructure/fs-metadata 0.6.0 → 0.7.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/CHANGELOG.md +11 -6
- package/CLAUDE.md +160 -136
- package/CODE_OF_CONDUCT.md +11 -11
- package/CONTRIBUTING.md +2 -2
- package/README.md +34 -84
- package/binding.gyp +98 -23
- package/claude.sh +23 -0
- package/dist/index.cjs +53 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +52 -21
- package/dist/index.mjs.map +1 -1
- package/{C++_REVIEW_TODO.md → doc/C++_REVIEW_TODO.md} +97 -25
- package/doc/GPG_RELEASE_HOWTO.md +505 -0
- package/doc/MACOS_API_REFERENCE.md +469 -0
- package/doc/SECURITY_AUDIT_2025.md +809 -0
- package/doc/SSH_RELEASE_HOWTO.md +207 -0
- package/doc/WINDOWS_API_REFERENCE.md +422 -0
- package/doc/WINDOWS_ARM64_SECURITY.md +161 -0
- package/doc/WINDOWS_DEBUG_GUIDE.md +96 -0
- package/doc/examples.md +267 -0
- package/doc/gotchas.md +297 -0
- package/doc/logo.png +0 -0
- package/doc/logo.svg +85 -0
- package/doc/macos-asan-sip-issue.md +71 -0
- package/doc/social.png +0 -0
- package/doc/social.svg +125 -0
- package/doc/windows-build.md +226 -0
- package/doc/windows-clang-tidy.md +72 -0
- package/doc/windows-memory-testing.md +108 -0
- package/doc/windows-prebuildify-arm64.md +232 -0
- package/jest.config.cjs +24 -0
- package/package.json +68 -44
- package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/darwin-x64/@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-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/scripts/check-memory.ts +186 -0
- package/scripts/clang-tidy.ts +832 -0
- package/scripts/install.cjs +42 -0
- package/scripts/is-platform.mjs +1 -1
- package/scripts/macos-asan.sh +155 -0
- package/scripts/post-build.mjs +3 -3
- package/scripts/prebuild-linux-glibc.sh +119 -0
- package/scripts/prebuildify-wrapper.ts +77 -0
- package/scripts/precommit.ts +70 -0
- package/scripts/sanitizers-test.sh +7 -1
- package/scripts/{configure.mjs → setup-native.mjs} +4 -1
- package/src/binding.cpp +1 -1
- package/src/common/error_utils.h +0 -6
- package/src/common/volume_metadata.h +6 -0
- package/src/darwin/hidden.cpp +73 -25
- package/src/darwin/path_security.h +149 -0
- package/src/darwin/raii_utils.h +104 -4
- package/src/darwin/volume_metadata.cpp +132 -58
- package/src/darwin/volume_mount_points.cpp +80 -47
- package/src/hidden.ts +36 -13
- package/src/linux/gio_mount_points.cpp +17 -18
- package/src/linux/gio_utils.cpp +92 -37
- package/src/linux/gio_utils.h +11 -5
- package/src/linux/gio_volume_metadata.cpp +111 -48
- package/src/linux/volume_metadata.cpp +67 -4
- package/src/object.ts +1 -0
- package/src/options.ts +6 -0
- package/src/path.ts +11 -0
- package/src/platform.ts +25 -0
- package/src/remote_info.ts +5 -3
- package/src/stack_path.ts +8 -6
- package/src/string_enum.ts +1 -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/memory-test-core.ts +336 -0
- package/src/test-utils/memory-test-runner.ts +108 -0
- package/src/test-utils/platform.ts +46 -1
- package/src/test-utils/worker-thread-helper.cjs +157 -26
- package/src/types/native_bindings.ts +1 -1
- package/src/types/options.ts +6 -0
- package/src/windows/drive_status.h +133 -163
- package/src/windows/error_utils.h +54 -3
- package/src/windows/fs_meta.h +1 -1
- package/src/windows/hidden.cpp +60 -43
- package/src/windows/security_utils.h +250 -0
- package/src/windows/string.h +68 -11
- package/src/windows/system_volume.h +1 -1
- package/src/windows/thread_pool.h +206 -0
- package/src/windows/volume_metadata.cpp +11 -6
- package/src/windows/volume_mount_points.cpp +8 -7
- package/src/windows/windows_arch.h +39 -0
- package/scripts/check-memory.mjs +0 -123
- package/scripts/clang-tidy.mjs +0 -73
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Windows Build Issues and Solutions
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document summarizes the Windows-specific build issues encountered with fs-metadata, particularly the "No Target Architecture" error from the Windows SDK, and various approaches to resolving it.
|
|
6
|
+
|
|
7
|
+
## The Core Issue
|
|
8
|
+
|
|
9
|
+
When building on Windows with node-gyp/prebuildify, we encounter:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um\winnt.h(173,1): fatal error C1189: #error: "No Target Architecture"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This error occurs because:
|
|
16
|
+
|
|
17
|
+
1. The Windows SDK headers require architecture macros (`_M_X64`, `_WIN64`, etc.) to be defined before including `<windows.h>`
|
|
18
|
+
2. Node-gyp doesn't always properly pass these defines when using prebuildify
|
|
19
|
+
3. The `target_arch` variable in binding.gyp conditions might not be evaluated correctly
|
|
20
|
+
|
|
21
|
+
## Why This Happens
|
|
22
|
+
|
|
23
|
+
### Root Cause
|
|
24
|
+
|
|
25
|
+
- Prebuildify calls node-gyp with `--arch` (previously `--target-arch`)
|
|
26
|
+
- The architecture should default to `os.arch()` (which correctly returns `x64` on Windows)
|
|
27
|
+
- However, the architecture defines aren't being set properly in the MSVC project generated by node-gyp
|
|
28
|
+
|
|
29
|
+
### Why Other Projects Don't Have This Issue
|
|
30
|
+
|
|
31
|
+
Projects like node-sqlite3 don't typically encounter this because:
|
|
32
|
+
|
|
33
|
+
1. They don't define architecture-specific macros in binding.gyp (these should come from the compiler)
|
|
34
|
+
2. They may not be using prebuildify, or are using different build configurations
|
|
35
|
+
3. They might be including Windows headers in a different order
|
|
36
|
+
|
|
37
|
+
## Attempted Solutions
|
|
38
|
+
|
|
39
|
+
### 1. Architecture Defines in binding.gyp (Not Sufficient)
|
|
40
|
+
|
|
41
|
+
Attempted to add architecture defines conditionally:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
"conditions": [
|
|
45
|
+
[
|
|
46
|
+
"target_arch=='x64'",
|
|
47
|
+
{
|
|
48
|
+
"defines": [
|
|
49
|
+
"_M_X64",
|
|
50
|
+
"_WIN64"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Result**: Doesn't reliably fix prebuildify builds
|
|
58
|
+
|
|
59
|
+
### 2. Windows Header Wrapper (Failed)
|
|
60
|
+
|
|
61
|
+
Created `windows_headers.h` to force architecture defines:
|
|
62
|
+
|
|
63
|
+
```cpp
|
|
64
|
+
#ifndef _M_X64
|
|
65
|
+
#define _M_X64 1
|
|
66
|
+
#define _AMD64_ 1
|
|
67
|
+
#define _WIN64 1
|
|
68
|
+
#endif
|
|
69
|
+
#include <windows.h>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Result**: Didn't resolve the issue for all files
|
|
73
|
+
|
|
74
|
+
### 3. Direct Source File Defines (Don't Use - Breaks ARM64)
|
|
75
|
+
|
|
76
|
+
Adding architecture defines at the top of each Windows source file:
|
|
77
|
+
|
|
78
|
+
```cpp
|
|
79
|
+
// Force architecture defines for Windows SDK
|
|
80
|
+
#ifndef _M_X64
|
|
81
|
+
#define _M_X64 1
|
|
82
|
+
#endif
|
|
83
|
+
#ifndef _AMD64_
|
|
84
|
+
#define _AMD64_ 1
|
|
85
|
+
#endif
|
|
86
|
+
#ifndef _WIN64
|
|
87
|
+
#define _WIN64 1
|
|
88
|
+
#endif
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Result**: This hardcodes x64 defines and would break ARM64 builds
|
|
92
|
+
|
|
93
|
+
### 4. CL Environment Variable (Current Solution)
|
|
94
|
+
|
|
95
|
+
Created `scripts/prebuildify-wrapper.ts` that sets the `CL` environment variable:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
if (currentArch === "x64") {
|
|
99
|
+
env.CL = "/D_M_X64 /D_WIN64 /D_AMD64_";
|
|
100
|
+
} else if (currentArch === "arm64") {
|
|
101
|
+
env.CL = "/D_M_ARM64 /D_WIN64";
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Result**: Works correctly for both x64 and ARM64 architectures
|
|
106
|
+
|
|
107
|
+
## Current Status
|
|
108
|
+
|
|
109
|
+
### What Works
|
|
110
|
+
|
|
111
|
+
- Building with the prebuildify wrapper that sets CL environment variable
|
|
112
|
+
- This solution properly handles both x64 and ARM64 architectures
|
|
113
|
+
|
|
114
|
+
### What Doesn't Work
|
|
115
|
+
|
|
116
|
+
- Relying on node-gyp/prebuildify to properly evaluate binding.gyp conditions
|
|
117
|
+
- Direct source file defines (would break ARM64 builds)
|
|
118
|
+
- windows_compat.h wrapper (can't distinguish architectures at compile time)
|
|
119
|
+
|
|
120
|
+
### Debug Builds Removed
|
|
121
|
+
|
|
122
|
+
- Debug builds have been removed from the project as they provided no value
|
|
123
|
+
- Windows debug CRT builds cannot be loaded by Node.js due to missing runtime dependencies
|
|
124
|
+
- JavaScript-based memory testing provides adequate leak detection
|
|
125
|
+
|
|
126
|
+
## Other Important Findings
|
|
127
|
+
|
|
128
|
+
### Node.js Version Compatibility
|
|
129
|
+
|
|
130
|
+
- Jest 30 doesn't support Node.js 23
|
|
131
|
+
- CI/CD workflows use Node.js 20, 22, and 24
|
|
132
|
+
- Removed Node.js 23 from test matrices
|
|
133
|
+
|
|
134
|
+
### Related Issues
|
|
135
|
+
|
|
136
|
+
1. **Memory Test Failures**: Initially appeared as "unsupported engine" warnings
|
|
137
|
+
2. **Architecture Detection**: `process.arch` correctly returns `x64` on Windows
|
|
138
|
+
3. **Prebuildify Defaults**: Should use `os.arch()` and `os.platform()` but something in the chain breaks
|
|
139
|
+
|
|
140
|
+
## Potential Future Solutions
|
|
141
|
+
|
|
142
|
+
### 1. Wrapper Script for Prebuildify
|
|
143
|
+
|
|
144
|
+
Create a TypeScript script to explicitly pass architecture:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { arch, platform } from "os";
|
|
148
|
+
import { spawn } from "child_process";
|
|
149
|
+
|
|
150
|
+
spawn(
|
|
151
|
+
"prebuildify",
|
|
152
|
+
[
|
|
153
|
+
"--napi",
|
|
154
|
+
"--tag-libc",
|
|
155
|
+
"--strip",
|
|
156
|
+
"--arch",
|
|
157
|
+
arch(),
|
|
158
|
+
"--platform",
|
|
159
|
+
platform(),
|
|
160
|
+
],
|
|
161
|
+
{ stdio: "inherit" },
|
|
162
|
+
);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 2. Environment Variables
|
|
166
|
+
|
|
167
|
+
Set architecture explicitly:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
set npm_config_arch=x64
|
|
171
|
+
npm run build
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 3. Investigation Areas
|
|
175
|
+
|
|
176
|
+
- Why `target_arch` isn't being evaluated in binding.gyp conditions
|
|
177
|
+
- Why prebuildify + node-gyp isn't passing architecture defines to MSVC
|
|
178
|
+
- Whether this is a node-gyp bug or a configuration issue
|
|
179
|
+
|
|
180
|
+
## References
|
|
181
|
+
|
|
182
|
+
### GitHub Issues
|
|
183
|
+
|
|
184
|
+
- nodejs/node-gyp#600: "gyp: name 'target_arch' is not defined"
|
|
185
|
+
- nodejs/node-gyp#2813: "Build defaults to x86 on a x64 Windows machine"
|
|
186
|
+
- prebuild/prebuildify#53: Changed from `--target-arch` to `--arch`
|
|
187
|
+
|
|
188
|
+
### Similar Projects
|
|
189
|
+
|
|
190
|
+
- node-sqlite3: Uses simpler binding.gyp without architecture defines
|
|
191
|
+
- node-canvas: Uses `WIN32_LEAN_AND_MEAN` but not architecture defines
|
|
192
|
+
- serialport: Focus on exception handling and warning suppression
|
|
193
|
+
|
|
194
|
+
## Why a Wrapper Header Won't Work
|
|
195
|
+
|
|
196
|
+
**Important Discovery**: A single `windows_compat.h` wrapper cannot solve this issue because:
|
|
197
|
+
|
|
198
|
+
1. At compile time, we can't distinguish between x64 and ARM64 (both define `_WIN64`)
|
|
199
|
+
2. The architecture information (`target_arch`) is only available in binding.gyp
|
|
200
|
+
3. Prebuildify doesn't properly pass the conditional defines from binding.gyp to the compiler
|
|
201
|
+
|
|
202
|
+
This means any compile-time logic would incorrectly set x64 defines on ARM64 builds or vice versa.
|
|
203
|
+
|
|
204
|
+
## Conclusion
|
|
205
|
+
|
|
206
|
+
The "No Target Architecture" error is a known limitation of how prebuildify and node-gyp handle Windows builds. The current workaround of adding architecture defines directly to source files is the most reliable solution.
|
|
207
|
+
|
|
208
|
+
**Why Other Projects Don't Have This Issue**:
|
|
209
|
+
|
|
210
|
+
- Projects like node-sqlite avoid `<windows.h>` by using cross-platform APIs
|
|
211
|
+
- Most Node.js addons don't directly interface with Windows system APIs
|
|
212
|
+
|
|
213
|
+
**The Solution**:
|
|
214
|
+
Use `scripts/prebuildify-wrapper.ts` which sets the CL environment variable with architecture-specific defines. This approach:
|
|
215
|
+
|
|
216
|
+
1. Works reliably with prebuildify
|
|
217
|
+
2. Correctly handles both x64 and ARM64 architectures
|
|
218
|
+
3. Doesn't require modifying source files
|
|
219
|
+
4. Avoids hardcoding architecture assumptions
|
|
220
|
+
|
|
221
|
+
## Next Steps
|
|
222
|
+
|
|
223
|
+
1. Consider opening an issue with prebuildify about Windows architecture detection
|
|
224
|
+
2. Test if explicit `--arch x64` flags to prebuildify resolve the issue
|
|
225
|
+
3. Investigate why other native modules don't encounter this problem
|
|
226
|
+
4. Consider if our include order or binding.gyp structure is unusual
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Windows clang-tidy Support
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
clang-tidy has limited support on Windows due to fundamental incompatibilities between clang and MSVC headers. While we've implemented Windows support in our unified `scripts/clang-tidy.ts`, users should be aware of the limitations.
|
|
6
|
+
|
|
7
|
+
## Current Implementation
|
|
8
|
+
|
|
9
|
+
1. **Unified Script**: `scripts/clang-tidy.ts` works on all platforms including Windows
|
|
10
|
+
2. **Automatic Detection**: Finds Node.js headers, MSVC includes, and Windows SDK paths
|
|
11
|
+
3. **Configuration**: Windows-specific checks in `src/windows/.clang-tidy`
|
|
12
|
+
4. **Fallback**: Minimal configuration (`.clang-tidy-windows-minimal`) for basic checks
|
|
13
|
+
|
|
14
|
+
## Known Issues
|
|
15
|
+
|
|
16
|
+
### Header Compatibility
|
|
17
|
+
|
|
18
|
+
- clang-tidy cannot fully parse MSVC STL headers, resulting in errors like:
|
|
19
|
+
- `no member named 'max' in namespace 'std'`
|
|
20
|
+
- `unknown type name 'namespace'`
|
|
21
|
+
- `no template named 'pointer_traits'`
|
|
22
|
+
|
|
23
|
+
### Root Cause
|
|
24
|
+
|
|
25
|
+
- MSVC headers use Microsoft-specific extensions that clang doesn't fully support
|
|
26
|
+
- Node.js native addon headers add additional complexity
|
|
27
|
+
- Even with clang-cl mode, full compatibility isn't achieved
|
|
28
|
+
|
|
29
|
+
## Recommendations
|
|
30
|
+
|
|
31
|
+
### For Windows Developers
|
|
32
|
+
|
|
33
|
+
1. **Use Visual Studio Code Analysis**: The built-in Code Analysis in Visual Studio provides better Windows-specific checking
|
|
34
|
+
2. **Focus on Warnings**: Despite header errors, clang-tidy still catches many issues:
|
|
35
|
+
- Uninitialized variables
|
|
36
|
+
- RAII violations
|
|
37
|
+
- Member initialization issues
|
|
38
|
+
- Ownership problems
|
|
39
|
+
|
|
40
|
+
3. **Run Anyway**: Even with errors, the warnings are valuable:
|
|
41
|
+
```bash
|
|
42
|
+
npm run lint:native
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### For CI/CD
|
|
46
|
+
|
|
47
|
+
Consider skipping clang-tidy on Windows in CI to avoid noise:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# In CI scripts
|
|
51
|
+
if [ "$OS" != "Windows_NT" ]; then
|
|
52
|
+
npm run lint:native
|
|
53
|
+
fi
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Future Improvements
|
|
57
|
+
|
|
58
|
+
- Monitor clang-tidy development for better MSVC support
|
|
59
|
+
- Consider using Visual Studio's built-in clang-tidy integration
|
|
60
|
+
- Investigate using `clangd` as an alternative
|
|
61
|
+
|
|
62
|
+
## What Still Works
|
|
63
|
+
|
|
64
|
+
Despite the header issues, clang-tidy on Windows can still detect:
|
|
65
|
+
|
|
66
|
+
- Uninitialized variables
|
|
67
|
+
- Missing RAII usage
|
|
68
|
+
- Resource leaks in your code (not system headers)
|
|
69
|
+
- Code style issues
|
|
70
|
+
- Many security vulnerabilities
|
|
71
|
+
|
|
72
|
+
The key is to focus on warnings in your own code files, not system header errors.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Windows Memory Testing
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
On Windows, debug builds with CRT memory leak detection face a loading issue where Node.js cannot load native modules that use Windows long paths (`\\?\` prefix). This document describes our approach to memory leak detection on Windows.
|
|
6
|
+
|
|
7
|
+
## The Problem
|
|
8
|
+
|
|
9
|
+
When building with `node-gyp build --debug`, the debug build cannot be loaded by Node.js due to:
|
|
10
|
+
|
|
11
|
+
1. **Missing Debug Runtime Dependencies**: Debug builds require debug versions of the Visual C++ runtime libraries (`ucrtbased.dll`, `vcruntime*d.dll`)
|
|
12
|
+
2. **UNC Path Issues**: Node.js module loader has issues with Windows extended-length paths (`\\?\C:\...`)
|
|
13
|
+
|
|
14
|
+
## Our Solution
|
|
15
|
+
|
|
16
|
+
We've split Windows security testing into three categories:
|
|
17
|
+
|
|
18
|
+
### 1. Input Security Tests (`windows-input-security.test.ts`)
|
|
19
|
+
|
|
20
|
+
Tests for handling malicious inputs:
|
|
21
|
+
|
|
22
|
+
- Path traversal protection
|
|
23
|
+
- Buffer overflow protection
|
|
24
|
+
- Invalid UTF-8 handling
|
|
25
|
+
- Device name rejection
|
|
26
|
+
- Alternate data stream rejection
|
|
27
|
+
|
|
28
|
+
These tests run with regular Release builds and don't require debug memory detection.
|
|
29
|
+
|
|
30
|
+
### 2. Resource Security Tests (`windows-resource-security.test.ts`)
|
|
31
|
+
|
|
32
|
+
Tests for resource/handle leaks during operations:
|
|
33
|
+
|
|
34
|
+
- Concurrent operations safety
|
|
35
|
+
- Handle cleanup on timeout
|
|
36
|
+
- Basic memory pattern monitoring
|
|
37
|
+
|
|
38
|
+
These tests validate functionality with Release builds.
|
|
39
|
+
|
|
40
|
+
### 3. Memory Leak Detection (`windows-memory-check.test.ts`)
|
|
41
|
+
|
|
42
|
+
JavaScript-based memory monitoring that works with Release builds:
|
|
43
|
+
|
|
44
|
+
- Heap memory usage tracking with forced garbage collection
|
|
45
|
+
- Memory growth pattern analysis
|
|
46
|
+
- Handle count monitoring (via `process.report`)
|
|
47
|
+
- Per-operation memory cost calculation
|
|
48
|
+
|
|
49
|
+
## Alternative Windows Memory Leak Detection Tools
|
|
50
|
+
|
|
51
|
+
**Note: Traditional Windows memory debugging tools have significant compatibility issues with Node.js native modules.**
|
|
52
|
+
|
|
53
|
+
### Why These Tools Don't Work Well with Node.js
|
|
54
|
+
|
|
55
|
+
1. **Dr. Memory**: Known incompatibility with Node.js - fails with "Unable to load client library: ucrtbase.dll" error (GitHub issue #2531)
|
|
56
|
+
2. **Visual Leak Detector (VLD)**: Requires debug builds which have loading issues with Node.js native modules
|
|
57
|
+
3. **Application Verifier**: Cannot properly hook into Node.js's memory management system
|
|
58
|
+
4. **Windows CRT Debug Heap**: Requires debug builds that face the UNC path loading issue
|
|
59
|
+
|
|
60
|
+
### Our Approach
|
|
61
|
+
|
|
62
|
+
Instead of these traditional tools, we use:
|
|
63
|
+
|
|
64
|
+
- JavaScript-based memory monitoring with forced garbage collection
|
|
65
|
+
- Process handle tracking via Node.js's built-in `process.report`
|
|
66
|
+
- Heap usage patterns analysis over multiple iterations
|
|
67
|
+
- This approach provides adequate memory leak detection without the compatibility issues
|
|
68
|
+
|
|
69
|
+
## Running Memory Tests
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Run all memory tests (JavaScript-based)
|
|
73
|
+
npm test -- src/windows-memory-check.test.ts
|
|
74
|
+
|
|
75
|
+
# Run resource security tests
|
|
76
|
+
npm test -- src/windows-resource-security.test.ts
|
|
77
|
+
|
|
78
|
+
# Run input security tests
|
|
79
|
+
npm test -- src/windows-input-security.test.ts
|
|
80
|
+
|
|
81
|
+
# Run full memory check suite (includes debug build attempt)
|
|
82
|
+
npm run check:memory
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Key Metrics Monitored
|
|
86
|
+
|
|
87
|
+
1. **Heap Memory Growth**: Should be < 100KB per operation (Windows APIs may have higher baseline)
|
|
88
|
+
2. **Total Memory Retention**: Should be < 5MB after GC
|
|
89
|
+
3. **Handle Count**: Should not increase by more than 10
|
|
90
|
+
4. **Concurrent Operation Memory**: Should be < 10MB for 100 operations
|
|
91
|
+
|
|
92
|
+
## Future Improvements
|
|
93
|
+
|
|
94
|
+
1. Investigate fixing debug build loading:
|
|
95
|
+
- Ship debug CRT dependencies
|
|
96
|
+
- Use manifest embedding for dependency resolution
|
|
97
|
+
- Investigate short path alternatives to avoid UNC paths
|
|
98
|
+
|
|
99
|
+
2. Enhanced JavaScript monitoring:
|
|
100
|
+
- Native memory tracking via N-API
|
|
101
|
+
- V8 heap snapshots comparison
|
|
102
|
+
- Event loop lag correlation
|
|
103
|
+
- Windows performance counters integration
|
|
104
|
+
|
|
105
|
+
3. Native-level monitoring:
|
|
106
|
+
- Custom memory allocator wrappers
|
|
107
|
+
- Direct Windows API handle tracking
|
|
108
|
+
- Integration with Node.js's built-in diagnostics
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Windows ARM64 Development and Prebuildify Setup
|
|
2
|
+
|
|
3
|
+
This document provides guidance for Windows ARM64 native module development, prebuildify configuration, and troubleshooting common issues.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Windows ARM64 support for native Node.js modules is a rapidly evolving area. This document captures key learnings from investigating build failures and researching the ecosystem.
|
|
8
|
+
|
|
9
|
+
## Prebuildify Architecture Support
|
|
10
|
+
|
|
11
|
+
### How Prebuildify Works
|
|
12
|
+
|
|
13
|
+
1. **Build Time**: Creates prebuilt binaries for different platforms/architectures
|
|
14
|
+
2. **Package Time**: Stores binaries in `./prebuilds/[platform]-[arch]/`
|
|
15
|
+
3. **Runtime**: Uses `node-gyp-build` to load the appropriate binary
|
|
16
|
+
|
|
17
|
+
### Platform Naming Conventions
|
|
18
|
+
|
|
19
|
+
- Windows x64: `win32-x64`
|
|
20
|
+
- Windows ARM64: `win32-arm64`
|
|
21
|
+
- Linux x64: `linux-x64`
|
|
22
|
+
- macOS x64: `darwin-x64`
|
|
23
|
+
- macOS ARM64: `darwin-arm64`
|
|
24
|
+
|
|
25
|
+
### Scoped Package Handling
|
|
26
|
+
|
|
27
|
+
For scoped npm packages like `@photostructure/fs-metadata`:
|
|
28
|
+
|
|
29
|
+
- Package scope uses `+` instead of `/` in filenames
|
|
30
|
+
- Example: `@photostructure+fs-metadata.glibc.node`
|
|
31
|
+
- This is standard prebuildify behavior
|
|
32
|
+
|
|
33
|
+
## Windows ARM64 Specific Considerations
|
|
34
|
+
|
|
35
|
+
### Current State of the Ecosystem (2025)
|
|
36
|
+
|
|
37
|
+
1. **GitHub Actions Support**: Windows ARM64 runners are in public preview
|
|
38
|
+
- Free for public repositories
|
|
39
|
+
- Run Windows 11 Desktop image
|
|
40
|
+
- Native ARM64 compilation (no emulation)
|
|
41
|
+
|
|
42
|
+
2. **Common Issues in Other Projects**:
|
|
43
|
+
- **Cypress**: Requires manual addition of "win32-arm64" as valid OS
|
|
44
|
+
- **LMDB-JS**: Reports "No native build was found for platform=win32 arch=arm64"
|
|
45
|
+
- **Cloudflare workerd**: Requires manual workarounds in install scripts
|
|
46
|
+
- Many projects fall back to x64 binaries (run via emulation)
|
|
47
|
+
|
|
48
|
+
### Architecture Detection Challenges
|
|
49
|
+
|
|
50
|
+
Windows SDK headers require architecture-specific defines before including `<windows.h>`:
|
|
51
|
+
|
|
52
|
+
- x64: `/D_M_X64 /D_WIN64 /D_AMD64_`
|
|
53
|
+
- ARM64: `/D_M_ARM64 /D_WIN64`
|
|
54
|
+
|
|
55
|
+
Our solution: Set `CL` environment variable in build scripts (see `scripts/prebuildify-wrapper.ts`)
|
|
56
|
+
|
|
57
|
+
## GitHub Actions Configuration
|
|
58
|
+
|
|
59
|
+
### Best Practices
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
prebuild-win-arm64:
|
|
63
|
+
runs-on: windows-11-arm # Native ARM64 runner
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
- uses: actions/setup-node@v4
|
|
67
|
+
with:
|
|
68
|
+
node-version: 20
|
|
69
|
+
cache: "npm"
|
|
70
|
+
- run: npm ci --ignore-scripts
|
|
71
|
+
- run: npm run build:native
|
|
72
|
+
- uses: actions/upload-artifact@v4
|
|
73
|
+
with:
|
|
74
|
+
name: prebuilds-windows-11-arm
|
|
75
|
+
path: prebuilds/
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Testing Configuration
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
test-win-arm64:
|
|
82
|
+
needs: [prebuild-win-arm64]
|
|
83
|
+
runs-on: windows-11-arm
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/download-artifact@v4
|
|
86
|
+
with:
|
|
87
|
+
path: ./prebuilds
|
|
88
|
+
merge-multiple: true
|
|
89
|
+
- run: npm ci
|
|
90
|
+
- run: npm run tests
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Common Problems and Solutions
|
|
94
|
+
|
|
95
|
+
### Problem: Jest Worker Process Failures
|
|
96
|
+
|
|
97
|
+
**Symptoms**:
|
|
98
|
+
|
|
99
|
+
- "Jest worker encountered 4 child process exceptions, exceeding retry limit"
|
|
100
|
+
- Tests fail only in CI, not locally
|
|
101
|
+
|
|
102
|
+
**Root Causes**:
|
|
103
|
+
|
|
104
|
+
1. Module resolution differs in worker threads
|
|
105
|
+
2. `__dirname` context changes in Jest environment
|
|
106
|
+
3. Relative paths may not resolve correctly
|
|
107
|
+
|
|
108
|
+
**Solutions**:
|
|
109
|
+
|
|
110
|
+
1. Use multiple fallback paths when loading native modules
|
|
111
|
+
2. Try `process.cwd()` in addition to relative paths
|
|
112
|
+
3. Ensure prebuilds are in expected locations
|
|
113
|
+
|
|
114
|
+
### Problem: Native Module Not Found
|
|
115
|
+
|
|
116
|
+
**Symptoms**:
|
|
117
|
+
|
|
118
|
+
- "No native build was found for platform=win32 arch=arm64"
|
|
119
|
+
- Module loads on x64 but not ARM64
|
|
120
|
+
|
|
121
|
+
**Root Causes**:
|
|
122
|
+
|
|
123
|
+
1. Missing prebuilt binary for the platform
|
|
124
|
+
2. Incorrect platform detection
|
|
125
|
+
3. node-gyp-build not finding the prebuild
|
|
126
|
+
|
|
127
|
+
**Solutions**:
|
|
128
|
+
|
|
129
|
+
1. Verify prebuild exists in `prebuilds/win32-arm64/`
|
|
130
|
+
2. Check that `process.platform` === "win32" and `process.arch` === "arm64"
|
|
131
|
+
3. Use `node-gyp-build` diagnostic output to debug loading
|
|
132
|
+
|
|
133
|
+
### Problem: Build Failures on Windows ARM64
|
|
134
|
+
|
|
135
|
+
**Symptoms**:
|
|
136
|
+
|
|
137
|
+
- "No Target Architecture" errors
|
|
138
|
+
- Windows SDK header compilation errors
|
|
139
|
+
|
|
140
|
+
**Root Causes**:
|
|
141
|
+
|
|
142
|
+
- Missing architecture defines for Windows SDK
|
|
143
|
+
- Prebuildify not passing through defines from binding.gyp
|
|
144
|
+
|
|
145
|
+
**Solution**:
|
|
146
|
+
Set environment variables before building:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
if (process.platform === "win32" && process.arch === "arm64") {
|
|
150
|
+
process.env.CL = "/D_M_ARM64 /D_WIN64";
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Testing Strategies
|
|
155
|
+
|
|
156
|
+
### 1. Memory Testing
|
|
157
|
+
|
|
158
|
+
Traditional Windows memory tools don't work with Node.js:
|
|
159
|
+
|
|
160
|
+
- Dr. Memory: "Unable to load client library"
|
|
161
|
+
- Debug CRT: Cannot be loaded by Node.js
|
|
162
|
+
- Visual Leak Detector: Requires debug builds
|
|
163
|
+
|
|
164
|
+
Use JavaScript-based testing instead (see `src/windows-memory-check.test.ts`)
|
|
165
|
+
|
|
166
|
+
### 2. Worker Thread Testing
|
|
167
|
+
|
|
168
|
+
- Test native module loading in both main and worker threads
|
|
169
|
+
- Use integration tests, not mocks
|
|
170
|
+
- Verify actual functionality, not just loading
|
|
171
|
+
|
|
172
|
+
### 3. Cross-Platform Testing
|
|
173
|
+
|
|
174
|
+
- Test on actual ARM64 hardware when possible
|
|
175
|
+
- Use GitHub Actions for CI testing
|
|
176
|
+
- Be aware of performance differences (ARM64 can be slower)
|
|
177
|
+
|
|
178
|
+
## Debugging Tips
|
|
179
|
+
|
|
180
|
+
### 1. Verify Prebuild Location
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# List prebuilds
|
|
184
|
+
ls -la prebuilds/
|
|
185
|
+
|
|
186
|
+
# Check specific platform
|
|
187
|
+
ls -la prebuilds/win32-arm64/
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 2. Test Native Module Loading
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
// test-native-load.js
|
|
194
|
+
const nodeGypBuild = require("node-gyp-build");
|
|
195
|
+
try {
|
|
196
|
+
const binding = nodeGypBuild(process.cwd());
|
|
197
|
+
console.log("✓ Native module loaded");
|
|
198
|
+
console.log("Functions:", Object.keys(binding));
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error("✗ Failed to load:", error.message);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### 3. Check Process Information
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
console.log("Platform:", process.platform);
|
|
208
|
+
console.log("Architecture:", process.arch);
|
|
209
|
+
console.log("Node version:", process.version);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Future Considerations
|
|
213
|
+
|
|
214
|
+
1. **Windows ARM64 Adoption**: Expected to grow with Qualcomm-based laptops
|
|
215
|
+
2. **Tooling Improvements**: Build tools catching up with ARM64 support
|
|
216
|
+
3. **Performance**: Native ARM64 binaries avoid x64 emulation overhead
|
|
217
|
+
4. **Testing**: More ARM64 CI runners becoming available
|
|
218
|
+
|
|
219
|
+
## References
|
|
220
|
+
|
|
221
|
+
- [Prebuildify Documentation](https://github.com/prebuild/prebuildify)
|
|
222
|
+
- [Node-gyp-build](https://github.com/prebuild/node-gyp-build)
|
|
223
|
+
- [Windows ARM64 GitHub Actions](https://github.blog/changelog/2025-04-14-windows-arm64-hosted-runners-now-available-in-public-preview/)
|
|
224
|
+
- [Node.js Native Addons](https://nodejs.org/api/addons.html)
|
|
225
|
+
- [Windows on ARM Documentation](https://docs.microsoft.com/en-us/windows/arm/)
|
|
226
|
+
|
|
227
|
+
## Related Files
|
|
228
|
+
|
|
229
|
+
- `scripts/prebuildify-wrapper.ts` - Handles architecture-specific build configuration
|
|
230
|
+
- `scripts/install.cjs` - Sets up environment for Windows builds
|
|
231
|
+
- `.github/workflows/build.yml` - CI configuration including ARM64 jobs
|
|
232
|
+
- `CLAUDE.md` - Project-specific build notes
|
package/jest.config.cjs
CHANGED
|
@@ -10,10 +10,33 @@ const isESM =
|
|
|
10
10
|
process.env.TEST_ESM === "1" ||
|
|
11
11
|
process.env.NODE_OPTIONS?.includes("--experimental-vm-modules");
|
|
12
12
|
|
|
13
|
+
const nodeVersion = parseInt(process.version.slice(1).split(".")[0], 10);
|
|
14
|
+
|
|
15
|
+
const isNode24ESM = nodeVersion >= 24 && isESM;
|
|
16
|
+
const isWindowsCI = platform === "win32" && process.env.CI;
|
|
17
|
+
|
|
18
|
+
// Determine maxWorkers based on environment
|
|
19
|
+
let maxWorkers = undefined; // undefined means Jest uses default (number of cores - 1)
|
|
20
|
+
let workerIdleMemoryLimit = undefined; // undefined means Jest uses default
|
|
21
|
+
|
|
22
|
+
if (isWindowsCI) {
|
|
23
|
+
console.log("[Jest Config] Windows CI detected, applying workarounds:");
|
|
24
|
+
maxWorkers = 1;
|
|
25
|
+
workerIdleMemoryLimit = "1GB";
|
|
26
|
+
} else if (isNode24ESM) {
|
|
27
|
+
console.log(
|
|
28
|
+
"[Jest Config] Node 24+ with ESM detected, applying workarounds:",
|
|
29
|
+
);
|
|
30
|
+
// Node 24 + ESM detection (avoid "module is already linked")
|
|
31
|
+
maxWorkers = 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
13
34
|
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
14
35
|
const config = {
|
|
15
36
|
displayName: `@photostructure/fs-metadata (${isESM ? "ESM" : "CJS"})`,
|
|
16
37
|
testEnvironment: "jest-environment-node",
|
|
38
|
+
...(maxWorkers != null ? { maxWorkers } : {}),
|
|
39
|
+
...(workerIdleMemoryLimit != null ? { workerIdleMemoryLimit } : {}),
|
|
17
40
|
roots: ["<rootDir>/src"],
|
|
18
41
|
coverageProvider: "v8",
|
|
19
42
|
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
|
@@ -23,6 +46,7 @@ const config = {
|
|
|
23
46
|
setupFilesAfterEnv: [
|
|
24
47
|
"jest-extended/all",
|
|
25
48
|
"<rootDir>/src/test-utils/jest-matchers.ts",
|
|
49
|
+
"<rootDir>/src/test-utils/jest-setup.ts",
|
|
26
50
|
],
|
|
27
51
|
collectCoverage: !argv.includes("--no-coverage"),
|
|
28
52
|
coverageDirectory: "coverage",
|