@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,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom install script that handles Windows architecture defines
|
|
5
|
+
* when node-gyp-build needs to compile from source
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawn } = require("child_process");
|
|
9
|
+
const { platform, arch } = require("os");
|
|
10
|
+
|
|
11
|
+
// If in CI and on Windows, set architecture defines
|
|
12
|
+
if (process.env.CI && platform() === "win32") {
|
|
13
|
+
const currentArch = arch();
|
|
14
|
+
|
|
15
|
+
// Set architecture-specific defines for Windows
|
|
16
|
+
if (currentArch === "x64") {
|
|
17
|
+
process.env.CL = "/D_M_X64 /D_WIN64 /D_AMD64_";
|
|
18
|
+
} else if (currentArch === "arm64") {
|
|
19
|
+
process.env.CL = "/D_M_ARM64 /D_WIN64";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(`Windows CI detected: arch=${currentArch}, CL=${process.env.CL}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Run node-gyp-build
|
|
26
|
+
const child = spawn("npx", ["node-gyp-build"], {
|
|
27
|
+
stdio: "inherit",
|
|
28
|
+
shell: true,
|
|
29
|
+
env: process.env,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
child.on("error", (error) => {
|
|
33
|
+
console.error("Failed to run node-gyp-build:", error);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
child.on("exit", (code) => {
|
|
38
|
+
if (code !== 0) {
|
|
39
|
+
console.error(`node-gyp-build exited with code ${code}`);
|
|
40
|
+
process.exit(code || 1);
|
|
41
|
+
}
|
|
42
|
+
});
|
package/scripts/is-platform.mjs
CHANGED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# macOS AddressSanitizer test script
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
# Colors for output
|
|
7
|
+
RED='\033[0;31m'
|
|
8
|
+
GREEN='\033[0;32m'
|
|
9
|
+
YELLOW='\033[1;33m'
|
|
10
|
+
NC='\033[0m' # No Color
|
|
11
|
+
|
|
12
|
+
echo -e "${YELLOW}=== macOS AddressSanitizer Memory Test ===${NC}"
|
|
13
|
+
|
|
14
|
+
# Check if we're on macOS
|
|
15
|
+
if [[ "$(uname)" != "Darwin" ]]; then
|
|
16
|
+
echo -e "${YELLOW}Not on macOS. Skipping macOS-specific memory tests.${NC}"
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Clean and rebuild with AddressSanitizer
|
|
21
|
+
echo -e "${YELLOW}Cleaning previous builds...${NC}"
|
|
22
|
+
npm run clean:native
|
|
23
|
+
|
|
24
|
+
# Configure build with ASan flags
|
|
25
|
+
echo -e "${YELLOW}Building with AddressSanitizer enabled...${NC}"
|
|
26
|
+
export CFLAGS="-fsanitize=address -g -O1 -fno-omit-frame-pointer"
|
|
27
|
+
export CXXFLAGS="-fsanitize=address -g -O1 -fno-omit-frame-pointer"
|
|
28
|
+
export LDFLAGS="-fsanitize=address"
|
|
29
|
+
|
|
30
|
+
# Set ASan options
|
|
31
|
+
export ASAN_OPTIONS="detect_leaks=1:check_initialization_order=1:strict_init_order=1:print_stats=1:halt_on_error=0"
|
|
32
|
+
export MallocScribble=1
|
|
33
|
+
export MallocGuardEdges=1
|
|
34
|
+
|
|
35
|
+
# Find and set the ASan library path for macOS
|
|
36
|
+
# First try to find the most recent version
|
|
37
|
+
ASAN_LIB=$(find /Library/Developer/CommandLineTools/usr/lib/clang/*/lib/darwin -name "libclang_rt.asan_osx_dynamic.dylib" 2>/dev/null | sort -V | tail -1)
|
|
38
|
+
if [[ -z "$ASAN_LIB" ]]; then
|
|
39
|
+
# Try alternative location
|
|
40
|
+
ASAN_LIB=$(find /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/*/lib/darwin -name "libclang_rt.asan_osx_dynamic.dylib" 2>/dev/null | sort -V | tail -1)
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [[ -n "$ASAN_LIB" ]]; then
|
|
44
|
+
export DYLD_INSERT_LIBRARIES="$ASAN_LIB"
|
|
45
|
+
echo -e "${GREEN}Using ASan library: $ASAN_LIB${NC}"
|
|
46
|
+
else
|
|
47
|
+
echo -e "${RED}Warning: Could not find ASan library. Tests may not run properly.${NC}"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Build the native module
|
|
51
|
+
npm run node-gyp-rebuild
|
|
52
|
+
|
|
53
|
+
# Run tests with ASan
|
|
54
|
+
echo -e "${YELLOW}Running tests with AddressSanitizer...${NC}"
|
|
55
|
+
|
|
56
|
+
# Note: On macOS with SIP enabled, DYLD_INSERT_LIBRARIES is stripped from
|
|
57
|
+
# child processes. Jest uses worker processes, so we need to run tests
|
|
58
|
+
# in a single process to ensure ASAN works correctly.
|
|
59
|
+
|
|
60
|
+
# Run the test and capture output
|
|
61
|
+
TEST_OUTPUT=$(npm test -- --runInBand 2>&1)
|
|
62
|
+
TEST_EXIT_CODE=$?
|
|
63
|
+
|
|
64
|
+
if [[ $TEST_EXIT_CODE -eq 0 ]]; then
|
|
65
|
+
echo -e "${GREEN}✓ Tests passed with AddressSanitizer${NC}"
|
|
66
|
+
else
|
|
67
|
+
# Check if the failure is due to SIP interceptor issues
|
|
68
|
+
if echo "$TEST_OUTPUT" | grep -q "interceptors not installed"; then
|
|
69
|
+
echo -e "${YELLOW}⚠ Tests completed but AddressSanitizer interceptors not installed${NC}"
|
|
70
|
+
echo -e "${YELLOW} This is due to macOS System Integrity Protection (SIP) stripping${NC}"
|
|
71
|
+
echo -e "${YELLOW} DYLD_INSERT_LIBRARIES from child processes. This is expected behavior.${NC}"
|
|
72
|
+
echo -e "${YELLOW} To run ASAN tests properly, you may need to disable SIP temporarily.${NC}"
|
|
73
|
+
# Don't treat this as a failure
|
|
74
|
+
else
|
|
75
|
+
echo -e "${RED}✗ Tests failed with AddressSanitizer${NC}"
|
|
76
|
+
echo "$TEST_OUTPUT"
|
|
77
|
+
# This is a real failure, exit with error
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Run memory leak check using leaks tool
|
|
83
|
+
echo -e "${YELLOW}Running macOS leaks tool...${NC}"
|
|
84
|
+
if command -v leaks >/dev/null 2>&1; then
|
|
85
|
+
# Get the project root directory
|
|
86
|
+
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
87
|
+
|
|
88
|
+
# Check if the native module exists
|
|
89
|
+
if [ ! -f "${PROJECT_ROOT}/build/Release/fs_metadata.node" ]; then
|
|
90
|
+
echo -e "${RED}Native module not found. Skipping leaks test.${NC}"
|
|
91
|
+
exit 1
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Create a simple test script
|
|
95
|
+
cat > /tmp/test-leaks.js << EOF
|
|
96
|
+
const fs = require('fs');
|
|
97
|
+
const binding = require('${PROJECT_ROOT}/build/Release/fs_metadata.node');
|
|
98
|
+
|
|
99
|
+
async function testVolumeMountPoints() {
|
|
100
|
+
for (let i = 0; i < 100; i++) {
|
|
101
|
+
await binding.getVolumeMountPoints();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function testVolumeMetadata() {
|
|
106
|
+
const mountPoints = await binding.getVolumeMountPoints();
|
|
107
|
+
if (mountPoints.length > 0) {
|
|
108
|
+
for (let i = 0; i < 10; i++) {
|
|
109
|
+
await binding.getVolumeMetadata({ mountPoint: mountPoints[0].mountPoint });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function runTests() {
|
|
115
|
+
await testVolumeMountPoints();
|
|
116
|
+
await testVolumeMetadata();
|
|
117
|
+
|
|
118
|
+
// Force garbage collection if available
|
|
119
|
+
if (global.gc) {
|
|
120
|
+
global.gc();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
runTests().then(() => {
|
|
125
|
+
console.log('Tests completed');
|
|
126
|
+
// Give time for cleanup
|
|
127
|
+
setTimeout(() => process.exit(0), 1000);
|
|
128
|
+
}).catch(err => {
|
|
129
|
+
console.error('Test failed:', err);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
});
|
|
132
|
+
EOF
|
|
133
|
+
|
|
134
|
+
# Run with leaks detection
|
|
135
|
+
echo -e "${YELLOW}Executing memory leak test...${NC}"
|
|
136
|
+
if leaks --atExit -- node --expose-gc /tmp/test-leaks.js > /tmp/leaks-output.txt 2>&1; then
|
|
137
|
+
echo -e "${GREEN}✓ No memory leaks detected${NC}"
|
|
138
|
+
# Show summary if available
|
|
139
|
+
if grep -q "Process" /tmp/leaks-output.txt; then
|
|
140
|
+
echo -e "${YELLOW}Summary:${NC}"
|
|
141
|
+
grep -E "(Process|leaks for|total leaked bytes)" /tmp/leaks-output.txt || true
|
|
142
|
+
fi
|
|
143
|
+
else
|
|
144
|
+
echo -e "${RED}✗ Memory leaks detected or leaks tool failed:${NC}"
|
|
145
|
+
cat /tmp/leaks-output.txt
|
|
146
|
+
# Don't exit with failure - leaks tool can have false positives
|
|
147
|
+
echo -e "${YELLOW}Note: The leaks tool may report false positives from Node.js internals.${NC}"
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
rm -f /tmp/test-leaks.js /tmp/leaks-output.txt
|
|
151
|
+
else
|
|
152
|
+
echo -e "${YELLOW}leaks tool not available. Skipping native leak detection.${NC}"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
echo -e "${GREEN}=== All macOS memory tests passed! ===${NC}"
|
package/scripts/post-build.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { copyFile } from "fs/promises";
|
|
4
|
-
import { dirname, join } from "path";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
3
|
+
import { copyFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
const distDir = join(__dirname, "..", "dist");
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Build native module with portable GLIBC compatibility
|
|
3
|
+
# Uses Debian 11 Bullseye (GLIBC 2.31) to balance compatibility and modern toolchain
|
|
4
|
+
#
|
|
5
|
+
# GLIBC versions in official Node.js Docker images:
|
|
6
|
+
# - node:*-buster: GLIBC 2.28 (Debian 10) - Python 3.7, GCC 8.3 (too old)
|
|
7
|
+
# - node:*-bullseye: GLIBC 2.31 (Debian 11) - Python 3.9, GCC 10.2 (good balance)
|
|
8
|
+
# - node:*-bookworm: GLIBC 2.36 (Debian 12) - Python 3.11, GCC 12.2
|
|
9
|
+
# - node:* (default): GLIBC 2.36 (Debian 12 Bookworm)
|
|
10
|
+
# - node:*-alpine: musl libc (not GLIBC)
|
|
11
|
+
#
|
|
12
|
+
# Other common environments:
|
|
13
|
+
# - Ubuntu 20.04 LTS: GLIBC 2.31 (matches our target)
|
|
14
|
+
# - Ubuntu 22.04 LTS: GLIBC 2.35
|
|
15
|
+
# - CentOS 7: GLIBC 2.17 (EOL, not supported)
|
|
16
|
+
# - Amazon Linux 2: GLIBC 2.26 (older, but should work)
|
|
17
|
+
#
|
|
18
|
+
# By targeting GLIBC 2.31, we support Ubuntu 20.04 LTS and newer,
|
|
19
|
+
# while having a modern enough toolchain for Node.js requirements.
|
|
20
|
+
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
# Allow architecture override (for CI cross-compilation)
|
|
24
|
+
TARGET_ARCH="${TARGET_ARCH:-}"
|
|
25
|
+
|
|
26
|
+
# If no target architecture specified, detect from host
|
|
27
|
+
if [ -z "$TARGET_ARCH" ]; then
|
|
28
|
+
ARCH=$(uname -m)
|
|
29
|
+
if [ "$ARCH" = "x86_64" ]; then
|
|
30
|
+
TARGET_ARCH="x64"
|
|
31
|
+
elif [ "$ARCH" = "aarch64" ]; then
|
|
32
|
+
TARGET_ARCH="arm64"
|
|
33
|
+
else
|
|
34
|
+
echo "Unsupported architecture: $ARCH"
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Map to Docker platform architecture
|
|
40
|
+
if [ "$TARGET_ARCH" = "x64" ]; then
|
|
41
|
+
DOCKER_ARCH="amd64"
|
|
42
|
+
elif [ "$TARGET_ARCH" = "arm64" ]; then
|
|
43
|
+
DOCKER_ARCH="arm64"
|
|
44
|
+
else
|
|
45
|
+
echo "Unsupported target architecture: $TARGET_ARCH"
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Determine which build command to use
|
|
50
|
+
BUILD_CMD="${BUILD_CMD:-npm run build:native}"
|
|
51
|
+
|
|
52
|
+
# Check if we're already in a compatible environment
|
|
53
|
+
if [ -f /etc/os-release ]; then
|
|
54
|
+
. /etc/os-release
|
|
55
|
+
if [[ "${ID:-}" == "debian" && "${VERSION_ID:-}" == "11" ]]; then
|
|
56
|
+
echo "Already in Debian 11 Bullseye, building directly..."
|
|
57
|
+
$BUILD_CMD
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Check if Docker is available
|
|
63
|
+
if ! command -v docker &> /dev/null; then
|
|
64
|
+
echo "Docker not found, falling back to local build"
|
|
65
|
+
echo "Warning: Binary may not be portable due to GLIBC version"
|
|
66
|
+
$BUILD_CMD
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
echo "Building native module in Debian 11 Bullseye container for GLIBC 2.31 compatibility..."
|
|
71
|
+
echo "Target architecture: $TARGET_ARCH (Docker platform: linux/$DOCKER_ARCH)"
|
|
72
|
+
|
|
73
|
+
# Create a container, build inside it, then copy artifacts out
|
|
74
|
+
CONTAINER_NAME="fs-metadata-build-$$"
|
|
75
|
+
|
|
76
|
+
# Start container in background
|
|
77
|
+
docker run -d \
|
|
78
|
+
--name "$CONTAINER_NAME" \
|
|
79
|
+
--platform "linux/$DOCKER_ARCH" \
|
|
80
|
+
node:20-bullseye \
|
|
81
|
+
sleep 3600
|
|
82
|
+
|
|
83
|
+
# Copy project files into container
|
|
84
|
+
docker cp . "$CONTAINER_NAME:/tmp/project"
|
|
85
|
+
|
|
86
|
+
# Run build inside container
|
|
87
|
+
# Debian 11 has Python 3.9 and GCC 10.2 which support our requirements
|
|
88
|
+
docker exec "$CONTAINER_NAME" sh -c "
|
|
89
|
+
cd /tmp/project && \
|
|
90
|
+
apt-get update -qq && \
|
|
91
|
+
apt-get install -y -qq build-essential python3 libglib2.0-dev libblkid-dev uuid-dev git && \
|
|
92
|
+
# Verify versions
|
|
93
|
+
echo 'Python version:' && python3 --version && \
|
|
94
|
+
echo 'GCC version:' && gcc --version | head -1 && \
|
|
95
|
+
# Build the project
|
|
96
|
+
npm ci --ignore-scripts && \
|
|
97
|
+
$BUILD_CMD
|
|
98
|
+
"
|
|
99
|
+
|
|
100
|
+
# Copy artifacts back
|
|
101
|
+
docker cp "$CONTAINER_NAME:/tmp/project/prebuilds" . 2>/dev/null || true
|
|
102
|
+
docker cp "$CONTAINER_NAME:/tmp/project/build" . 2>/dev/null || true
|
|
103
|
+
docker cp "$CONTAINER_NAME:/tmp/project/config.gypi" . 2>/dev/null || true
|
|
104
|
+
|
|
105
|
+
# Fix ownership (docker cp preserves container's root ownership)
|
|
106
|
+
if [ -d prebuilds ]; then
|
|
107
|
+
chown -R "$(id -u):$(id -g)" prebuilds
|
|
108
|
+
fi
|
|
109
|
+
if [ -d build ]; then
|
|
110
|
+
chown -R "$(id -u):$(id -g)" build
|
|
111
|
+
fi
|
|
112
|
+
if [ -f config.gypi ]; then
|
|
113
|
+
chown "$(id -u):$(id -g)" config.gypi
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Clean up container
|
|
117
|
+
docker rm -f "$CONTAINER_NAME" >/dev/null
|
|
118
|
+
|
|
119
|
+
echo "Portable build complete!"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { arch, platform } from "node:os";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Wrapper for prebuildify to ensure architecture is explicitly passed This
|
|
8
|
+
* works around the issue where prebuildify doesn't properly evaluate
|
|
9
|
+
* binding.gyp conditions for Windows architecture defines
|
|
10
|
+
*
|
|
11
|
+
* NOTE: if you don't include <windows.h> in your binding.gyp, this script is
|
|
12
|
+
* unnecessary.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Get the current architecture and platform
|
|
16
|
+
const currentArch = arch(); // 'x64', 'arm64', etc.
|
|
17
|
+
const currentPlatform = platform(); // 'win32', 'darwin', 'linux'
|
|
18
|
+
|
|
19
|
+
console.log(`Building for platform: ${currentPlatform}, arch: ${currentArch}`);
|
|
20
|
+
|
|
21
|
+
// Set up environment variables to help node-gyp
|
|
22
|
+
const env = { ...process.env };
|
|
23
|
+
|
|
24
|
+
// Set architecture-specific defines for Windows
|
|
25
|
+
if (currentPlatform === "win32") {
|
|
26
|
+
// Try various environment variables that might work
|
|
27
|
+
env.npm_config_arch = currentArch;
|
|
28
|
+
env.npm_config_target_arch = currentArch;
|
|
29
|
+
env.PREBUILD_ARCH = currentArch;
|
|
30
|
+
|
|
31
|
+
// Try setting compiler flags directly
|
|
32
|
+
if (currentArch === "x64") {
|
|
33
|
+
env.CL = "/D_M_X64 /D_WIN64 /D_AMD64_";
|
|
34
|
+
} else if (currentArch === "arm64") {
|
|
35
|
+
env.CL = "/D_M_ARM64 /D_WIN64";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Build the prebuildify command with explicit architecture
|
|
40
|
+
const args = [
|
|
41
|
+
"--napi",
|
|
42
|
+
"--tag-libc",
|
|
43
|
+
"--strip",
|
|
44
|
+
"--arch",
|
|
45
|
+
currentArch,
|
|
46
|
+
"--platform",
|
|
47
|
+
currentPlatform,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// Add any additional arguments passed to this script
|
|
51
|
+
if (process.argv.length > 2) {
|
|
52
|
+
args.push(...process.argv.slice(2));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`Running: prebuildify ${args.join(" ")}`);
|
|
56
|
+
if (currentPlatform === "win32" && env.CL) {
|
|
57
|
+
console.log(`CL environment variable: ${env.CL}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Spawn prebuildify with the arguments
|
|
61
|
+
const child = spawn("prebuildify", args, {
|
|
62
|
+
stdio: "inherit",
|
|
63
|
+
shell: true,
|
|
64
|
+
env,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
child.on("error", (error) => {
|
|
68
|
+
console.error("Failed to start prebuildify:", error);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
child.on("exit", (code) => {
|
|
73
|
+
if (code !== 0) {
|
|
74
|
+
console.error(`prebuildify exited with code ${code}`);
|
|
75
|
+
process.exit(code || 1);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
import { platform } from "node:os";
|
|
4
|
+
import { exit } from "node:process";
|
|
5
|
+
|
|
6
|
+
const isLinux = platform() === "linux";
|
|
7
|
+
const isMacOS = platform() === "darwin";
|
|
8
|
+
|
|
9
|
+
function run({
|
|
10
|
+
cmd,
|
|
11
|
+
desc,
|
|
12
|
+
exitOnFail: shouldExit = true,
|
|
13
|
+
}: {
|
|
14
|
+
cmd: string;
|
|
15
|
+
desc: string;
|
|
16
|
+
exitOnFail?: boolean;
|
|
17
|
+
}) {
|
|
18
|
+
console.log(`\n▶ ${desc ?? cmd}`);
|
|
19
|
+
try {
|
|
20
|
+
execSync(cmd, { stdio: "inherit" });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error(`✗ Failed: ${desc ?? cmd}: ` + error);
|
|
23
|
+
if (shouldExit) exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
run({ cmd: "npm install", desc: "Installing dependencies" });
|
|
28
|
+
run({ cmd: "npm run update", desc: "Updating dependencies" });
|
|
29
|
+
rmSync("package-lock.json", { force: true });
|
|
30
|
+
run({ cmd: "npm install", desc: "Updating dependencies" });
|
|
31
|
+
run({ cmd: "npm run clean", desc: "Start fresh" });
|
|
32
|
+
run({ cmd: "npm run fmt", desc: "Formatting code" });
|
|
33
|
+
run({ cmd: "npm run lint", desc: "Running linting checks" });
|
|
34
|
+
run({ cmd: "npm run docs", desc: "TypeDoc generation" });
|
|
35
|
+
run({ cmd: "npm run build:dist", desc: "Building distribution files" });
|
|
36
|
+
|
|
37
|
+
// Detect if we're using glibc (vs musl)
|
|
38
|
+
// Check process.report for musl loader - if not found, assume glibc
|
|
39
|
+
const isGlibc = (() => {
|
|
40
|
+
if (!isLinux) return false;
|
|
41
|
+
const report = process.report?.getReport() as any;
|
|
42
|
+
return !report?.sharedObjects?.some((lib: string) => /ld-musl/.test(lib));
|
|
43
|
+
})();
|
|
44
|
+
|
|
45
|
+
// Build native module with portable GLIBC
|
|
46
|
+
if (isLinux && isGlibc) {
|
|
47
|
+
run({
|
|
48
|
+
cmd: "npm run build:linux-glibc",
|
|
49
|
+
desc: "Building native project with portable GLIBC",
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
// Clean old native builds to ensure fresh compilation
|
|
53
|
+
run({ cmd: "npm run clean:native", desc: "Cleaning old native builds" });
|
|
54
|
+
run({ cmd: "npm run build:native", desc: "Building native module" });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
run({ cmd: "npm run tests", desc: "Running tests in ESM & CJS mode" });
|
|
58
|
+
|
|
59
|
+
// Platform-specific checks
|
|
60
|
+
if (isLinux || isMacOS) {
|
|
61
|
+
// Remove stale compile_commands.json to ensure it's regenerated with current settings
|
|
62
|
+
rmSync("compile_commands.json", { force: true });
|
|
63
|
+
run({ cmd: "npm run lint:native", desc: "Running clang-tidy" });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Run comprehensive memory tests (cross-platform)
|
|
67
|
+
// This includes Windows debug memory check on Windows
|
|
68
|
+
run({ cmd: "npm run check:memory", desc: "Comprehensive memory tests" });
|
|
69
|
+
|
|
70
|
+
console.log("\n✅ All precommit checks passed!");
|
|
@@ -76,7 +76,8 @@ fi
|
|
|
76
76
|
|
|
77
77
|
# Build the native module
|
|
78
78
|
echo "Building with AddressSanitizer..."
|
|
79
|
-
npm run
|
|
79
|
+
npm run setup:native
|
|
80
|
+
npm run clean:native
|
|
80
81
|
npm run node-gyp-rebuild
|
|
81
82
|
|
|
82
83
|
# Run tests and capture output
|
|
@@ -148,4 +149,9 @@ if [[ "$VERBOSE" -eq 1 ]] && grep -q "Stats:" "$OUTPUT_FILE"; then
|
|
|
148
149
|
grep -A 20 "Stats:" "$OUTPUT_FILE" | head -20
|
|
149
150
|
fi
|
|
150
151
|
|
|
152
|
+
# Clean build artifacts to ensure no ASAN-compiled code remains
|
|
153
|
+
echo -e "\n${YELLOW}Cleaning build artifacts...${NC}"
|
|
154
|
+
npm run clean:native > /dev/null 2>&1
|
|
155
|
+
echo -e "${GREEN}✓ Build artifacts cleaned${NC}"
|
|
156
|
+
|
|
151
157
|
exit $EXIT_CODE
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// scripts/
|
|
3
|
+
// scripts/setup-native.mjs
|
|
4
|
+
|
|
5
|
+
// This script sets up the config.gypi file to include GIO support when available.
|
|
6
|
+
// It should be run before building native modules with node-gyp.
|
|
4
7
|
|
|
5
8
|
import { execSync } from "node:child_process";
|
|
6
9
|
import { writeFileSync } from "node:fs";
|
package/src/binding.cpp
CHANGED
package/src/common/error_utils.h
CHANGED
|
@@ -18,12 +18,6 @@ inline std::string CreateErrorMessage(const char *operation, int error) {
|
|
|
18
18
|
" failed with error: " + std::to_string(error);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
// Human-readable error with code (used by most callers)
|
|
22
|
-
inline std::string CreateErrorMessageWithStrerror(const char *operation,
|
|
23
|
-
int error) {
|
|
24
|
-
return std::string(strerror(error)) + " (" + std::to_string(error) + ")";
|
|
25
|
-
}
|
|
26
|
-
|
|
27
21
|
// Convenience function for common pattern: "operation failed for 'path': error"
|
|
28
22
|
inline std::string CreatePathErrorMessage(const char *operation,
|
|
29
23
|
const std::string &path, int error) {
|
|
@@ -8,6 +8,8 @@ struct VolumeMetadataOptions {
|
|
|
8
8
|
std::string mountPoint; // Required mount point path
|
|
9
9
|
uint32_t timeoutMs = 5000; // Optional timeout with default
|
|
10
10
|
std::string device; // Optional device path
|
|
11
|
+
bool skipNetworkVolumes =
|
|
12
|
+
false; // Skip detailed info for network volumes to avoid blocking
|
|
11
13
|
|
|
12
14
|
static VolumeMetadataOptions FromObject(const Napi::Object &obj) {
|
|
13
15
|
VolumeMetadataOptions options;
|
|
@@ -25,6 +27,10 @@ struct VolumeMetadataOptions {
|
|
|
25
27
|
if (obj.Has("device")) {
|
|
26
28
|
options.device = obj.Get("device").As<Napi::String>();
|
|
27
29
|
}
|
|
30
|
+
if (obj.Has("skipNetworkVolumes")) {
|
|
31
|
+
options.skipNetworkVolumes =
|
|
32
|
+
obj.Get("skipNetworkVolumes").As<Napi::Boolean>().Value();
|
|
33
|
+
}
|
|
28
34
|
|
|
29
35
|
return options;
|
|
30
36
|
}
|