@photostructure/fs-metadata 0.6.1 → 0.7.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 (89) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/CLAUDE.md +141 -315
  3. package/CODE_OF_CONDUCT.md +11 -11
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +34 -103
  6. package/binding.gyp +97 -22
  7. package/claude.sh +23 -0
  8. package/dist/index.cjs +51 -21
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +5 -0
  11. package/dist/index.d.mts +5 -0
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.mjs +51 -21
  14. package/dist/index.mjs.map +1 -1
  15. package/doc/C++_REVIEW_TODO.md +97 -25
  16. package/doc/GPG_RELEASE_HOWTO.md +44 -13
  17. package/doc/MACOS_API_REFERENCE.md +469 -0
  18. package/doc/SECURITY_AUDIT_2025.md +809 -0
  19. package/doc/SSH_RELEASE_HOWTO.md +28 -24
  20. package/doc/WINDOWS_API_REFERENCE.md +422 -0
  21. package/doc/WINDOWS_ARM64_SECURITY.md +161 -0
  22. package/doc/WINDOWS_DEBUG_GUIDE.md +9 -2
  23. package/doc/examples.md +267 -0
  24. package/doc/gotchas.md +297 -0
  25. package/doc/logo.png +0 -0
  26. package/doc/logo.svg +85 -0
  27. package/doc/macos-asan-sip-issue.md +71 -0
  28. package/doc/social.png +0 -0
  29. package/doc/social.svg +125 -0
  30. package/doc/windows-build.md +226 -0
  31. package/doc/windows-clang-tidy.md +72 -0
  32. package/doc/windows-memory-testing.md +108 -0
  33. package/doc/windows-prebuildify-arm64.md +232 -0
  34. package/jest.config.cjs +23 -0
  35. package/package.json +61 -36
  36. package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  37. package/prebuilds/darwin-x64/@photostructure+fs-metadata.glibc.node +0 -0
  38. package/prebuilds/linux-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  39. package/prebuilds/linux-arm64/@photostructure+fs-metadata.musl.node +0 -0
  40. package/prebuilds/linux-x64/@photostructure+fs-metadata.glibc.node +0 -0
  41. package/prebuilds/linux-x64/@photostructure+fs-metadata.musl.node +0 -0
  42. package/prebuilds/win32-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  43. package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
  44. package/scripts/check-memory.ts +186 -0
  45. package/scripts/clang-tidy.ts +690 -99
  46. package/scripts/install.cjs +42 -0
  47. package/scripts/is-platform.mjs +1 -1
  48. package/scripts/macos-asan.sh +155 -0
  49. package/scripts/post-build.mjs +3 -3
  50. package/scripts/prebuild-linux-glibc.sh +12 -1
  51. package/scripts/prebuildify-wrapper.ts +77 -0
  52. package/scripts/precommit.ts +45 -20
  53. package/scripts/sanitizers-test.sh +1 -1
  54. package/src/common/volume_metadata.h +6 -0
  55. package/src/darwin/hidden.cpp +73 -25
  56. package/src/darwin/path_security.h +149 -0
  57. package/src/darwin/raii_utils.h +104 -4
  58. package/src/darwin/volume_metadata.cpp +132 -58
  59. package/src/darwin/volume_mount_points.cpp +80 -47
  60. package/src/hidden.ts +36 -13
  61. package/src/linux/gio_mount_points.cpp +17 -18
  62. package/src/linux/gio_utils.cpp +92 -37
  63. package/src/linux/gio_utils.h +11 -5
  64. package/src/linux/gio_volume_metadata.cpp +111 -48
  65. package/src/linux/volume_metadata.cpp +67 -4
  66. package/src/object.ts +1 -0
  67. package/src/options.ts +6 -0
  68. package/src/path.ts +11 -0
  69. package/src/remote_info.ts +5 -3
  70. package/src/stack_path.ts +8 -6
  71. package/src/string_enum.ts +1 -0
  72. package/src/test-utils/memory-test-core.ts +336 -0
  73. package/src/test-utils/memory-test-runner.ts +108 -0
  74. package/src/test-utils/platform.ts +46 -1
  75. package/src/test-utils/worker-thread-helper.cjs +154 -27
  76. package/src/types/native_bindings.ts +1 -1
  77. package/src/types/options.ts +6 -0
  78. package/src/windows/drive_status.h +133 -163
  79. package/src/windows/error_utils.h +54 -3
  80. package/src/windows/fs_meta.h +1 -1
  81. package/src/windows/hidden.cpp +60 -43
  82. package/src/windows/security_utils.h +250 -0
  83. package/src/windows/string.h +68 -11
  84. package/src/windows/system_volume.h +1 -1
  85. package/src/windows/thread_pool.h +206 -0
  86. package/src/windows/volume_metadata.cpp +11 -6
  87. package/src/windows/volume_mount_points.cpp +8 -7
  88. package/src/windows/windows_arch.h +39 -0
  89. package/scripts/check-memory.mjs +0 -123
@@ -0,0 +1,161 @@
1
+ # Windows ARM64 Security Configuration
2
+
3
+ ## Overview
4
+
5
+ This document explains the security compilation flags used for ARM64 builds on Windows and why certain x64-specific flags are intentionally omitted.
6
+
7
+ ## ARM64 Security Flags in binding.gyp
8
+
9
+ ### Compiler Flags (`VCCLCompilerTool`)
10
+
11
+ #### `/guard:cf` - Control Flow Guard ✅ ENABLED
12
+
13
+ - **Supported**: Yes, ARM64 supports Control Flow Guard
14
+ - **Purpose**: Prevents exploitation of memory corruption vulnerabilities
15
+ - **Status**: Fully supported on ARM64 Windows since Windows 10
16
+
17
+ #### `/ZH:SHA_256` - Hash Algorithm ✅ ENABLED
18
+
19
+ - **Supported**: Yes, platform-independent
20
+ - **Purpose**: Specifies SHA-256 hash algorithm for file checksums
21
+ - **Status**: Standard security practice across all architectures
22
+
23
+ #### `/sdl` - Security Development Lifecycle ✅ ENABLED
24
+
25
+ - **Supported**: Yes, platform-independent
26
+ - **Purpose**: Enables additional compile-time security checks
27
+ - **Status**: Recommended for all builds
28
+
29
+ ### Linker Flags (`VCLinkerTool`)
30
+
31
+ #### `/guard:cf` - Control Flow Guard at Link Time ✅ ENABLED
32
+
33
+ - **Supported**: Yes, ARM64 supports CFG
34
+ - **Purpose**: Enables CFG checks during linking
35
+ - **Status**: Must match compiler flag
36
+
37
+ #### `/DYNAMICBASE` - ASLR ✅ ENABLED
38
+
39
+ - **Supported**: Yes, ARM64 supports ASLR
40
+ - **Purpose**: Address Space Layout Randomization
41
+ - **Status**: Critical security feature, enabled on all architectures
42
+
43
+ ## Flags Intentionally Omitted for ARM64
44
+
45
+ ### `/Qspectre` - Spectre Mitigation ❌ NOT AVAILABLE
46
+
47
+ **Why omitted**: This flag is x64/x86-specific and not available for ARM64
48
+
49
+ - **Spectre vulnerability**: CPU speculative execution side-channel attack
50
+ - **x64 mitigation**: Compiler inserts speculation barriers
51
+ - **ARM64 status**: ARM processors have different speculative execution behavior
52
+ - **Alternative**: ARM64 has hardware-level mitigations built into the CPU architecture
53
+ - **Reference**: [Microsoft ARM64 Security](https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/arm64-exception-handling)
54
+
55
+ ### `/CETCOMPAT` - Control-flow Enforcement Technology ❌ NOT AVAILABLE
56
+
57
+ **Why omitted**: Intel CET is x64-specific and not available for ARM64
58
+
59
+ - **Intel CET**: Hardware-based control-flow integrity (shadow stack)
60
+ - **x64 status**: Available on recent Intel CPUs (11th gen+)
61
+ - **ARM64 alternative**: ARM has different hardware security features:
62
+ - **PAC (Pointer Authentication Codes)**: Cryptographic signatures on pointers
63
+ - **BTI (Branch Target Identification)**: Forward-edge control flow integrity
64
+ - **Future**: When compiler support for ARM64 shadow stack stabilizes, we may add it
65
+ - **Reference**: [ARM Pointer Authentication](https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#pointer-authentication-on-arm64)
66
+
67
+ ## ARM64-Specific Security Features
68
+
69
+ While ARM64 doesn't support `/Qspectre` or `/CETCOMPAT`, it has equivalent or superior hardware-level security features:
70
+
71
+ ### 1. Pointer Authentication (PAC)
72
+
73
+ - Cryptographically signs pointers to prevent corruption
74
+ - Protects return addresses and function pointers
75
+ - Hardware-accelerated, minimal performance overhead
76
+ - **Status**: Supported in hardware, compiler support evolving
77
+
78
+ ### 2. Branch Target Identification (BTI)
79
+
80
+ - Ensures indirect branches land on valid targets
81
+ - Prevents code-reuse attacks (ROP/JOP)
82
+ - Hardware-enforced control flow integrity
83
+ - **Status**: Supported in hardware, compiler support evolving
84
+
85
+ ### 3. Memory Tagging Extension (MTE)
86
+
87
+ - Hardware-based memory safety
88
+ - Detects use-after-free and buffer overflows
89
+ - Probabilistic detection with ~0-15% overhead
90
+ - **Status**: ARMv8.5-A+, not yet in consumer Windows devices
91
+
92
+ ## Comparison: x64 vs ARM64 Security
93
+
94
+ | Feature | x64 | ARM64 | Notes |
95
+ | ------------------ | ------------------------------- | ----------------------- | ----------------------------- |
96
+ | Control Flow Guard | ✅ `/guard:cf` | ✅ `/guard:cf` | Same implementation |
97
+ | ASLR | ✅ `/DYNAMICBASE` | ✅ `/DYNAMICBASE` | Same implementation |
98
+ | Spectre Mitigation | ✅ `/Qspectre` | ⚠️ Hardware mitigations | Different approaches |
99
+ | Shadow Stack | ✅ `/CETCOMPAT` (Intel CET) | ⚠️ PAC (different tech) | Both protect return addresses |
100
+ | Branch Protection | ✅ CET Indirect Branch Tracking | ✅ BTI | Similar purpose |
101
+ | Memory Safety | ⚠️ Software-based | ✅ MTE (future) | ARM has hardware advantage |
102
+
103
+ ## Build Configuration
104
+
105
+ ### Current ARM64 Flags (binding.gyp)
106
+
107
+ ```json
108
+ {
109
+ "target_arch=='arm64'": {
110
+ "defines": ["_M_ARM64", "_WIN64"],
111
+ "msvs_settings": {
112
+ "VCCLCompilerTool": {
113
+ "AdditionalOptions": ["/guard:cf", "/ZH:SHA_256", "/sdl"],
114
+ "ExceptionHandling": 1,
115
+ "RuntimeTypeInfo": "true"
116
+ },
117
+ "VCLinkerTool": {
118
+ "AdditionalOptions": ["/guard:cf", "/DYNAMICBASE"]
119
+ }
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### Security Posture
126
+
127
+ ✅ **Current**: ARM64 builds have equivalent or better security than x64
128
+
129
+ - All applicable mitigations are enabled
130
+ - ARM64 hardware features provide additional protection
131
+ - No security regression compared to x64
132
+
133
+ ⏳ **Future Improvements**:
134
+
135
+ - Monitor compiler support for ARM64 shadow stack
136
+ - Evaluate PAC/BTI enablement when stable
137
+ - Consider MTE when available in consumer devices
138
+
139
+ ## Testing
140
+
141
+ ARM64 builds are tested on:
142
+
143
+ - Windows 11 ARM64 (native)
144
+ - Windows 10 ARM64 (emulation on x64)
145
+
146
+ Security features are validated through:
147
+
148
+ - Static analysis during compilation
149
+ - Runtime testing on ARM64 devices
150
+ - Memory safety tests (windows-memory-check.test.ts)
151
+
152
+ ## References
153
+
154
+ - [Microsoft ARM64 ABI](https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions)
155
+ - [ARM Security Features](https://developer.arm.com/documentation/102433/0100)
156
+ - [Windows ARM64 Security](https://learn.microsoft.com/en-us/windows/arm/overview)
157
+ - [Control Flow Guard on ARM64](https://learn.microsoft.com/en-us/windows/win32/secbp/control-flow-guard)
158
+
159
+ ## Last Updated
160
+
161
+ 2025-10-23 - Initial documentation of ARM64 security configuration
@@ -1,7 +1,9 @@
1
1
  # Windows and Alpine Linux Debugging Guide for debuglog.test.ts
2
2
 
3
3
  ## Issue Summary
4
+
4
5
  The debuglog.test.ts file was failing on:
6
+
5
7
  1. **Windows CI**: "Converting circular structure to JSON" error in Jest's message passing system when `execFileSync` threw errors with circular references
6
8
  2. **Alpine Linux**: `ETIMEDOUT` errors when spawning child processes with `npx`
7
9
 
@@ -28,11 +30,13 @@ The debuglog.test.ts file was failing on:
28
30
  To test these changes on your Windows test box:
29
31
 
30
32
  1. **Run the specific test file:**
33
+
31
34
  ```powershell
32
35
  npm test -- src/debuglog.test.ts
33
36
  ```
34
37
 
35
38
  2. **Run with verbose output to see debugging info:**
39
+
36
40
  ```powershell
37
41
  npm test -- src/debuglog.test.ts --verbose
38
42
  ```
@@ -43,11 +47,12 @@ To test these changes on your Windows test box:
43
47
  - Whether `npx tsx` is available and working correctly
44
48
 
45
49
  4. **To test individual child scripts directly:**
50
+
46
51
  ```powershell
47
52
  # Test debuglog-child.ts
48
53
  $env:NODE_DEBUG="fs-metadata"
49
54
  npx tsx src/test-utils/debuglog-child.ts
50
-
55
+
51
56
  # Test debuglog-enabled-child.ts
52
57
  $env:NODE_DEBUG="fs-metadata"
53
58
  npx tsx src/test-utils/debuglog-enabled-child.ts
@@ -61,12 +66,14 @@ To test these changes on your Windows test box:
61
66
  ## Debugging Tips
62
67
 
63
68
  1. **Enable Node.js debugging:**
69
+
64
70
  ```powershell
65
71
  $env:NODE_OPTIONS="--trace-warnings"
66
72
  npm test -- src/debuglog.test.ts
67
73
  ```
68
74
 
69
75
  2. **Check npx/tsx availability:**
76
+
70
77
  ```powershell
71
78
  npx --version
72
79
  npx tsx --version
@@ -86,4 +93,4 @@ To test these changes on your Windows test box:
86
93
 
87
94
  ## Rollback Plan
88
95
 
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.
96
+ 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.
@@ -0,0 +1,267 @@
1
+ # Examples
2
+
3
+ This guide provides practical examples for using `@photostructure/fs-metadata`.
4
+
5
+ ## Basic Usage
6
+
7
+ ### List All Mounted Volumes
8
+
9
+ ```typescript
10
+ import { getVolumeMountPoints } from "@photostructure/fs-metadata";
11
+
12
+ const mountPoints = await getVolumeMountPoints();
13
+
14
+ // Example output on Windows:
15
+ // [
16
+ // { mountPoint: 'C:\\', status: 'healthy' },
17
+ // { mountPoint: 'D:\\', status: 'healthy' },
18
+ // { mountPoint: 'E:\\', status: 'unavailable' }
19
+ // ]
20
+
21
+ // Example output on Linux:
22
+ // [
23
+ // { mountPoint: '/', status: 'healthy' },
24
+ // { mountPoint: '/home', status: 'healthy' },
25
+ // { mountPoint: '/mnt/nas', status: 'timeout' }
26
+ // ]
27
+ ```
28
+
29
+ ### Get Volume Metadata
30
+
31
+ ```typescript
32
+ import { getVolumeMetadata } from "@photostructure/fs-metadata";
33
+
34
+ const metadata = await getVolumeMetadata("/");
35
+
36
+ // Example output:
37
+ // {
38
+ // mountPoint: '/',
39
+ // mountFrom: '/dev/sda1',
40
+ // fstype: 'ext4',
41
+ // size: 500107862016,
42
+ // used: 234567890123,
43
+ // available: 239539971893,
44
+ // status: 'healthy'
45
+ // }
46
+ ```
47
+
48
+ ### Get All Volume Metadata
49
+
50
+ ```typescript
51
+ import { getAllVolumeMetadata } from "@photostructure/fs-metadata";
52
+
53
+ // Get all volumes including system volumes
54
+ const allVolumes = await getAllVolumeMetadata({ includeSystemVolumes: true });
55
+
56
+ // Filter healthy volumes only
57
+ const healthyVolumes = allVolumes.filter((v) => v.status === "healthy");
58
+
59
+ // Calculate total storage
60
+ const totalStorage = healthyVolumes.reduce((sum, v) => sum + v.size, 0);
61
+ const totalUsed = healthyVolumes.reduce((sum, v) => sum + v.used, 0);
62
+ ```
63
+
64
+ ## Hidden Files
65
+
66
+ ### Check if File is Hidden
67
+
68
+ ```typescript
69
+ import { isHidden } from "@photostructure/fs-metadata";
70
+
71
+ // Simple check
72
+ const hidden = await isHidden("/path/to/file.txt");
73
+
74
+ // Check with timeout
75
+ const hidden2 = await isHidden("/mnt/slow-network/file.txt", {
76
+ timeoutMs: 5000,
77
+ });
78
+ ```
79
+
80
+ ### Set Hidden Attribute
81
+
82
+ ```typescript
83
+ import { setHidden } from "@photostructure/fs-metadata";
84
+
85
+ // Hide a file
86
+ await setHidden("/path/to/file.txt", true);
87
+
88
+ // Unhide a file
89
+ await setHidden("/path/to/.hidden-file", false);
90
+
91
+ // Note: On POSIX systems (Linux/macOS), this will rename the file
92
+ // to add/remove a leading dot. On Windows, it sets the hidden attribute.
93
+ ```
94
+
95
+ ### Recursive Hidden Check
96
+
97
+ ```typescript
98
+ import { isHiddenRecursive } from "@photostructure/fs-metadata";
99
+
100
+ // Check if file or any parent directory is hidden
101
+ const hidden = await isHiddenRecursive("/home/user/.config/app/settings.json");
102
+ // Returns true because .config is hidden
103
+
104
+ // Works with Windows hidden attributes too
105
+ const hidden2 = await isHiddenRecursive("C:\\Users\\Public\\Desktop\\file.txt");
106
+ ```
107
+
108
+ ### Get Hidden Metadata
109
+
110
+ ```typescript
111
+ import { getHiddenMetadata } from "@photostructure/fs-metadata";
112
+
113
+ const metadata = await getHiddenMetadata("/path/to/file");
114
+
115
+ // Example output:
116
+ // {
117
+ // hidden: true,
118
+ // hiddenByAncestor: false,
119
+ // localSupport: 'native', // or 'posix' or 'none'
120
+ // exists: true
121
+ // }
122
+ ```
123
+
124
+ ## CommonJS Usage
125
+
126
+ ```javascript
127
+ const {
128
+ getVolumeMountPoints,
129
+ getVolumeMetadata,
130
+ isHidden,
131
+ setHidden,
132
+ } = require("@photostructure/fs-metadata");
133
+
134
+ async function main() {
135
+ const mountPoints = await getVolumeMountPoints();
136
+ console.log("Mount points:", mountPoints);
137
+
138
+ const metadata = await getVolumeMetadata(mountPoints[0].mountPoint);
139
+ console.log("Volume metadata:", metadata);
140
+ }
141
+
142
+ main().catch(console.error);
143
+ ```
144
+
145
+ ## Error Handling
146
+
147
+ ```typescript
148
+ import {
149
+ getVolumeMetadata,
150
+ VolumeMountPointNotAccessibleError,
151
+ TimeoutError,
152
+ } from "@photostructure/fs-metadata";
153
+
154
+ try {
155
+ const metadata = await getVolumeMetadata("/mnt/network-drive", {
156
+ timeoutMs: 10000, // 10 second timeout
157
+ });
158
+ } catch (error) {
159
+ if (error instanceof TimeoutError) {
160
+ console.error("Operation timed out - network drive may be unreachable");
161
+ } else if (error instanceof VolumeMountPointNotAccessibleError) {
162
+ console.error("Volume is not accessible:", error.message);
163
+ } else {
164
+ console.error("Unexpected error:", error);
165
+ }
166
+ }
167
+ ```
168
+
169
+ ## Working with Network Volumes
170
+
171
+ ```typescript
172
+ import {
173
+ getVolumeMountPoints,
174
+ getVolumeMetadata,
175
+ } from "@photostructure/fs-metadata";
176
+
177
+ // Network volumes may timeout or be unavailable
178
+ const mountPoints = await getVolumeMountPoints({ timeoutMs: 30000 });
179
+
180
+ // Filter out unhealthy volumes
181
+ const availableVolumes = mountPoints.filter((mp) => mp.status === "healthy");
182
+
183
+ // Get metadata with extended timeout for network drives
184
+ for (const mp of availableVolumes) {
185
+ try {
186
+ const metadata = await getVolumeMetadata(mp.mountPoint, {
187
+ timeoutMs: 20000, // 20 seconds for network volumes
188
+ });
189
+ console.log(
190
+ `${mp.mountPoint}: ${metadata.used} of ${metadata.size} bytes used`,
191
+ );
192
+ } catch (error) {
193
+ console.error(
194
+ `Failed to get metadata for ${mp.mountPoint}:`,
195
+ error.message,
196
+ );
197
+ }
198
+ }
199
+ ```
200
+
201
+ ## Platform-Specific Examples
202
+
203
+ ### Windows: List Drive Letters
204
+
205
+ ```typescript
206
+ import { getVolumeMountPoints } from "@photostructure/fs-metadata";
207
+
208
+ const volumes = await getVolumeMountPoints();
209
+ const driveLetters = volumes
210
+ .filter((v) => v.status === "healthy")
211
+ .map((v) => v.mountPoint)
212
+ .filter((mp) => /^[A-Z]:\\$/.test(mp))
213
+ .sort();
214
+
215
+ console.log("Available drives:", driveLetters);
216
+ // Output: ["C:\\", "D:\\", "E:\\"]
217
+ ```
218
+
219
+ ### Linux: Filter System Volumes
220
+
221
+ ```typescript
222
+ import { getAllVolumeMetadata } from "@photostructure/fs-metadata";
223
+
224
+ // Get only user-accessible volumes (excludes /proc, /sys, etc.)
225
+ const userVolumes = await getAllVolumeMetadata({
226
+ includeSystemVolumes: false,
227
+ });
228
+
229
+ // Custom filtering for specific filesystem types
230
+ const dataVolumes = userVolumes.filter((v) =>
231
+ ["ext4", "xfs", "btrfs", "zfs"].includes(v.fstype),
232
+ );
233
+ ```
234
+
235
+ ### macOS: APFS Volumes
236
+
237
+ ```typescript
238
+ import { getAllVolumeMetadata } from "@photostructure/fs-metadata";
239
+
240
+ const volumes = await getAllVolumeMetadata();
241
+
242
+ // Find APFS volumes
243
+ const apfsVolumes = volumes.filter((v) => v.fstype === "apfs");
244
+
245
+ // APFS containers share space, so available space might be the same
246
+ const containers = new Map();
247
+ for (const vol of apfsVolumes) {
248
+ const key = `${vol.size}-${vol.available}`;
249
+ if (!containers.has(key)) {
250
+ containers.set(key, []);
251
+ }
252
+ containers.get(key).push(vol.mountPoint);
253
+ }
254
+ ```
255
+
256
+ ## Debug Logging
257
+
258
+ ```typescript
259
+ // Enable debug logging before importing
260
+ process.env.NODE_DEBUG = "fs-meta";
261
+
262
+ import { getVolumeMetadata } from "@photostructure/fs-metadata";
263
+
264
+ // Now operations will log debug information to stderr
265
+ const metadata = await getVolumeMetadata("/");
266
+ // Debug output includes native code operations and timing information
267
+ ```