@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,832 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { exec as execCallback, execSync } from "node:child_process";
|
|
3
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { cpus, platform } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
|
|
8
|
+
const exec = promisify(execCallback);
|
|
9
|
+
|
|
10
|
+
// Check for environment variable to skip
|
|
11
|
+
if (process.env.SKIP_CLANG_TIDY) {
|
|
12
|
+
console.log("Skipping clang-tidy (SKIP_CLANG_TIDY is set)");
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Platform detection
|
|
17
|
+
const isWindows = platform() === "win32";
|
|
18
|
+
const isMacOS = platform() === "darwin";
|
|
19
|
+
const isLinux = platform() === "linux";
|
|
20
|
+
|
|
21
|
+
// Colors for output
|
|
22
|
+
const colors = {
|
|
23
|
+
reset: "\x1b[0m",
|
|
24
|
+
red: "\x1b[31m",
|
|
25
|
+
green: "\x1b[32m",
|
|
26
|
+
yellow: "\x1b[33m",
|
|
27
|
+
blue: "\x1b[34m",
|
|
28
|
+
dim: "\x1b[2m",
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
// Platform-specific warnings
|
|
32
|
+
if (isMacOS) {
|
|
33
|
+
console.log(
|
|
34
|
+
"Note: clang-tidy on macOS with Homebrew LLVM may report false positives",
|
|
35
|
+
);
|
|
36
|
+
console.log(
|
|
37
|
+
"due to header path issues. Set SKIP_CLANG_TIDY=1 to skip this check.",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check for required tools (non-Windows only)
|
|
42
|
+
function checkCommand(command: string, installHint: string): boolean {
|
|
43
|
+
if (isWindows) return true; // Skip on Windows
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
execSync(`which ${command}`, { stdio: "ignore" });
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
console.error(`Error: '${command}' not found in PATH.`);
|
|
50
|
+
console.error(`To install: ${installHint}`);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Find clang-tidy binary
|
|
56
|
+
function findClangTidy(): string | null {
|
|
57
|
+
if (isWindows) {
|
|
58
|
+
// Windows-specific paths
|
|
59
|
+
const windowsPaths = [
|
|
60
|
+
"C:\\Program Files\\LLVM\\bin\\clang-tidy.exe",
|
|
61
|
+
"C:\\Program Files (x86)\\LLVM\\bin\\clang-tidy.exe",
|
|
62
|
+
// Visual Studio 2022
|
|
63
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\Llvm\\x64\\bin\\clang-tidy.exe",
|
|
64
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Tools\\Llvm\\x64\\bin\\clang-tidy.exe",
|
|
65
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\Llvm\\x64\\bin\\clang-tidy.exe",
|
|
66
|
+
// Visual Studio 2019
|
|
67
|
+
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\Llvm\\bin\\clang-tidy.exe",
|
|
68
|
+
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional\\VC\\Tools\\Llvm\\bin\\clang-tidy.exe",
|
|
69
|
+
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\VC\\Tools\\Llvm\\bin\\clang-tidy.exe",
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
for (const path of windowsPaths) {
|
|
73
|
+
if (existsSync(path)) {
|
|
74
|
+
return path;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Try to find in PATH
|
|
79
|
+
try {
|
|
80
|
+
execSync("where clang-tidy", { stdio: "ignore" });
|
|
81
|
+
return "clang-tidy";
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
// Unix-like systems
|
|
87
|
+
const versions = ["", "-18", "-17", "-16", "-15", "-14"];
|
|
88
|
+
for (const version of versions) {
|
|
89
|
+
try {
|
|
90
|
+
const result = execSync(`which clang-tidy${version}`, {
|
|
91
|
+
encoding: "utf8",
|
|
92
|
+
}).trim();
|
|
93
|
+
// On macOS, return the full path if it's in Homebrew
|
|
94
|
+
// This allows us to detect it for filtering purposes
|
|
95
|
+
if (
|
|
96
|
+
isMacOS &&
|
|
97
|
+
(result.includes("/opt/homebrew") ||
|
|
98
|
+
result.includes("/usr/local") ||
|
|
99
|
+
result.includes("/Cellar"))
|
|
100
|
+
) {
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
return `clang-tidy${version}`;
|
|
104
|
+
} catch {
|
|
105
|
+
// Continue trying
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// On macOS, check Homebrew locations explicitly
|
|
110
|
+
if (isMacOS) {
|
|
111
|
+
const brewPrefixes = ["/opt/homebrew", "/usr/local"];
|
|
112
|
+
for (const prefix of brewPrefixes) {
|
|
113
|
+
const paths = [
|
|
114
|
+
`${prefix}/opt/clang-tidy/bin/clang-tidy`,
|
|
115
|
+
`${prefix}/opt/llvm/bin/clang-tidy`,
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
for (const path of paths) {
|
|
119
|
+
if (existsSync(path)) {
|
|
120
|
+
return path;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Try versioned LLVM formulas
|
|
125
|
+
for (let v = 18; v >= 14; v--) {
|
|
126
|
+
const versionedPath = `${prefix}/opt/llvm@${v}/bin/clang-tidy`;
|
|
127
|
+
if (existsSync(versionedPath)) {
|
|
128
|
+
return versionedPath;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return "clang-tidy"; // fallback
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Generate compile_commands.json for Windows
|
|
139
|
+
async function generateWindowsCompileCommands(): Promise<boolean> {
|
|
140
|
+
console.log("Generating compile_commands.json for Windows...");
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
execSync("npm run setup:native", { stdio: "inherit" });
|
|
144
|
+
|
|
145
|
+
const nodeVersion = process.version.slice(1);
|
|
146
|
+
|
|
147
|
+
// Try multiple possible locations for node-gyp headers
|
|
148
|
+
const possibleNodeGypPaths = [
|
|
149
|
+
`${process.env.USERPROFILE}\\.node-gyp\\${nodeVersion}`,
|
|
150
|
+
`${process.env.LOCALAPPDATA}\\node-gyp\\Cache\\${nodeVersion}`,
|
|
151
|
+
`${process.env.APPDATA}\\npm\\node_modules\\node-gyp\\cache\\${nodeVersion}`,
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
let nodeGyp = "";
|
|
155
|
+
for (const path of possibleNodeGypPaths) {
|
|
156
|
+
if (existsSync(join(path, "include", "node", "node.h"))) {
|
|
157
|
+
nodeGyp = path;
|
|
158
|
+
console.log("Found Node.js headers at:", nodeGyp);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// If not found, install them
|
|
164
|
+
if (!nodeGyp) {
|
|
165
|
+
console.log("Installing Node.js headers...");
|
|
166
|
+
execSync("npx node-gyp install", { stdio: "inherit" });
|
|
167
|
+
|
|
168
|
+
// Check again
|
|
169
|
+
for (const path of possibleNodeGypPaths) {
|
|
170
|
+
if (existsSync(join(path, "include", "node", "node.h"))) {
|
|
171
|
+
nodeGyp = path;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!nodeGyp) {
|
|
177
|
+
// Fallback to default
|
|
178
|
+
nodeGyp = `${process.env.USERPROFILE}\\.node-gyp\\${nodeVersion}`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Get all Windows sources
|
|
183
|
+
const windowsSources: string[] = [];
|
|
184
|
+
if (existsSync(join("src", "windows"))) {
|
|
185
|
+
const entries = require("fs").readdirSync(join("src", "windows"));
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
if (entry.endsWith(".cpp") || entry.endsWith(".h")) {
|
|
188
|
+
windowsSources.push(join("src", "windows", entry));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Add binding.cpp
|
|
194
|
+
const sources = [...windowsSources, "src/binding.cpp"];
|
|
195
|
+
|
|
196
|
+
// Find MSVC include paths
|
|
197
|
+
const msvcPaths = [
|
|
198
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC",
|
|
199
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Tools\\MSVC",
|
|
200
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC",
|
|
201
|
+
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC",
|
|
202
|
+
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC",
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
let msvcInclude = "";
|
|
206
|
+
for (const basePath of msvcPaths) {
|
|
207
|
+
if (existsSync(basePath)) {
|
|
208
|
+
const versions = require("fs").readdirSync(basePath);
|
|
209
|
+
if (versions.length > 0) {
|
|
210
|
+
const version = versions.sort().reverse()[0];
|
|
211
|
+
msvcInclude = join(basePath, version, "include");
|
|
212
|
+
if (existsSync(msvcInclude)) {
|
|
213
|
+
console.log("Found MSVC includes at:", msvcInclude);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Find Windows SDK paths
|
|
221
|
+
const sdkBase = "C:\\Program Files (x86)\\Windows Kits\\10\\Include";
|
|
222
|
+
let sdkVersion = "";
|
|
223
|
+
if (existsSync(sdkBase)) {
|
|
224
|
+
const versions = require("fs")
|
|
225
|
+
.readdirSync(sdkBase)
|
|
226
|
+
.filter((v) => v.match(/^\d+\.\d+\.\d+\.\d+$/))
|
|
227
|
+
.sort()
|
|
228
|
+
.reverse();
|
|
229
|
+
if (versions.length > 0) {
|
|
230
|
+
sdkVersion = versions[0];
|
|
231
|
+
console.log("Found Windows SDK version:", sdkVersion);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Create compile commands with absolute paths
|
|
236
|
+
const commands = sources.map((source: string) => ({
|
|
237
|
+
directory: process.cwd(),
|
|
238
|
+
file: source,
|
|
239
|
+
command: [
|
|
240
|
+
"clang++", // Use clang++ for clang-tidy compatibility
|
|
241
|
+
"-c",
|
|
242
|
+
source,
|
|
243
|
+
`-I${process.cwd()}/src/windows`,
|
|
244
|
+
`-I${process.cwd()}/node_modules/node-addon-api`,
|
|
245
|
+
`-I${nodeGyp}/include/node`,
|
|
246
|
+
msvcInclude ? `-I${msvcInclude}` : "",
|
|
247
|
+
sdkVersion ? `-I${sdkBase}\\${sdkVersion}\\ucrt` : "",
|
|
248
|
+
sdkVersion ? `-I${sdkBase}\\${sdkVersion}\\shared` : "",
|
|
249
|
+
sdkVersion ? `-I${sdkBase}\\${sdkVersion}\\um` : "",
|
|
250
|
+
sdkVersion ? `-I${sdkBase}\\${sdkVersion}\\winrt` : "",
|
|
251
|
+
"-DWIN32",
|
|
252
|
+
"-D_WINDOWS",
|
|
253
|
+
"-D_WIN64",
|
|
254
|
+
"-D_M_X64=1",
|
|
255
|
+
"-D_AMD64_=1",
|
|
256
|
+
"-DNAPI_VERSION=8",
|
|
257
|
+
"-DNODE_ADDON_API_DISABLE_DEPRECATED",
|
|
258
|
+
"-DBUILDING_NODE_EXTENSION",
|
|
259
|
+
"-std=c++17",
|
|
260
|
+
"-fms-compatibility",
|
|
261
|
+
"-fms-extensions",
|
|
262
|
+
"-Wno-microsoft-include",
|
|
263
|
+
]
|
|
264
|
+
.filter((arg) => arg)
|
|
265
|
+
.join(" "),
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
writeFileSync("compile_commands.json", JSON.stringify(commands, null, 2));
|
|
269
|
+
console.log(
|
|
270
|
+
`Created compile_commands.json with ${commands.length} entries`,
|
|
271
|
+
);
|
|
272
|
+
return true;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error("Failed to generate compile_commands.json:", error);
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Generate compile_commands.json for Unix-like systems
|
|
280
|
+
async function generateUnixCompileCommands(): Promise<void> {
|
|
281
|
+
console.log("Generating compile_commands.json...");
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
!checkCommand(
|
|
285
|
+
"bear",
|
|
286
|
+
isLinux
|
|
287
|
+
? "sudo apt-get install bear"
|
|
288
|
+
: isMacOS
|
|
289
|
+
? "brew install bear"
|
|
290
|
+
: "see https://github.com/rizsotto/Bear",
|
|
291
|
+
)
|
|
292
|
+
) {
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
execSync("npm run setup:native && bear -- npm run node-gyp-rebuild", {
|
|
297
|
+
stdio: "inherit",
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (!existsSync("compile_commands.json")) {
|
|
301
|
+
console.error("Failed to generate compile_commands.json");
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Get source files to check
|
|
307
|
+
async function getSourceFiles(): Promise<string[]> {
|
|
308
|
+
if (isWindows) {
|
|
309
|
+
// Windows-specific files
|
|
310
|
+
const files: string[] = [];
|
|
311
|
+
const windowsDir = join("src", "windows");
|
|
312
|
+
|
|
313
|
+
if (existsSync(windowsDir)) {
|
|
314
|
+
const entries = require("fs").readdirSync(windowsDir);
|
|
315
|
+
for (const entry of entries) {
|
|
316
|
+
if (entry.endsWith(".cpp") || entry.endsWith(".h")) {
|
|
317
|
+
files.push(join(windowsDir, entry));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Also include binding.cpp
|
|
323
|
+
files.push(join("src", "binding.cpp"));
|
|
324
|
+
return files;
|
|
325
|
+
} else {
|
|
326
|
+
// Platform-specific exclusions for Unix-like systems
|
|
327
|
+
let excludePattern = "";
|
|
328
|
+
if (isMacOS) {
|
|
329
|
+
excludePattern = "| grep -v -E '(windows|linux)/'";
|
|
330
|
+
} else if (isLinux) {
|
|
331
|
+
excludePattern = "| grep -v -E '(windows|darwin)/'";
|
|
332
|
+
} else {
|
|
333
|
+
excludePattern = "| grep -v -E '(windows|darwin|linux)/'";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const { stdout } = await exec(
|
|
337
|
+
`find src -name '*.cpp' -o -name '*.h' | grep -E '\\.(cpp|h)$' ${excludePattern}`,
|
|
338
|
+
);
|
|
339
|
+
return stdout
|
|
340
|
+
.trim()
|
|
341
|
+
.split("\n")
|
|
342
|
+
.filter((f) => f);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Filter out known macOS Homebrew LLVM header issues
|
|
347
|
+
function filterMacOSHeaderErrors(output: string): {
|
|
348
|
+
filteredOutput: string;
|
|
349
|
+
errors: number;
|
|
350
|
+
warnings: number;
|
|
351
|
+
filtered: number;
|
|
352
|
+
} {
|
|
353
|
+
const lines = output.split("\n");
|
|
354
|
+
const filteredLines: string[] = [];
|
|
355
|
+
let errors = 0;
|
|
356
|
+
let warnings = 0;
|
|
357
|
+
let filtered = 0;
|
|
358
|
+
|
|
359
|
+
// Patterns for known Homebrew LLVM vs Apple clang header mismatches
|
|
360
|
+
const knownHeaderErrors = [
|
|
361
|
+
// Standard library headers not found
|
|
362
|
+
/'(functional|chrono|cstring|iostream|string|vector|memory|algorithm|map|set|iterator)' file not found/,
|
|
363
|
+
// Apple framework headers not found when using Homebrew clang-tidy
|
|
364
|
+
/'CoreFoundation\/CoreFoundation\.h' file not found/,
|
|
365
|
+
/'DiskArbitration\/DiskArbitration\.h' file not found/,
|
|
366
|
+
// LLVM-specific errors that don't affect actual compilation
|
|
367
|
+
/unknown type name '_LIBCPP_/,
|
|
368
|
+
/no member named '\w+' in namespace 'std'/,
|
|
369
|
+
/no template named '\w+' in namespace 'std'/,
|
|
370
|
+
];
|
|
371
|
+
|
|
372
|
+
for (const line of lines) {
|
|
373
|
+
let isKnownError = false;
|
|
374
|
+
|
|
375
|
+
// Check if this is a known header error
|
|
376
|
+
if (line.includes(" error:")) {
|
|
377
|
+
for (const pattern of knownHeaderErrors) {
|
|
378
|
+
if (pattern.test(line)) {
|
|
379
|
+
isKnownError = true;
|
|
380
|
+
filtered++;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!isKnownError) {
|
|
387
|
+
filteredLines.push(line);
|
|
388
|
+
|
|
389
|
+
// Count errors and warnings
|
|
390
|
+
if (line.includes(" error:")) {
|
|
391
|
+
errors++;
|
|
392
|
+
}
|
|
393
|
+
if (line.includes(" warning:")) {
|
|
394
|
+
warnings++;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
filteredOutput: filteredLines.join("\n"),
|
|
401
|
+
errors,
|
|
402
|
+
warnings,
|
|
403
|
+
filtered,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Filter out known Windows header issues
|
|
408
|
+
function filterWindowsHeaderErrors(output: string): {
|
|
409
|
+
filteredOutput: string;
|
|
410
|
+
errors: number;
|
|
411
|
+
warnings: number;
|
|
412
|
+
} {
|
|
413
|
+
const lines = output.split("\n");
|
|
414
|
+
const filteredLines: string[] = [];
|
|
415
|
+
let errors = 0;
|
|
416
|
+
let warnings = 0;
|
|
417
|
+
let skipNextLine = false;
|
|
418
|
+
let inSystemHeader = false;
|
|
419
|
+
|
|
420
|
+
// Patterns for known header issues that we want to filter out
|
|
421
|
+
const systemHeaderPatterns = [
|
|
422
|
+
// System header paths - match the entire error line
|
|
423
|
+
/C:\\Program Files.*:\d+:\d+: (error|warning):/,
|
|
424
|
+
/C:\\Users\\.*\\AppData.*:\d+:\d+: (error|warning):/,
|
|
425
|
+
/\.node-gyp.*:\d+:\d+: (error|warning):/,
|
|
426
|
+
/Windows Kits.*:\d+:\d+: (error|warning):/,
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
// Known system header error messages that appear in user files
|
|
430
|
+
const systemHeaderErrors = [
|
|
431
|
+
// File not found errors - these are the most common MSVC header issues
|
|
432
|
+
/'(chrono|functional|windows\.h|iostream|string|vector|memory|algorithm|map|set|unordered_map|iterator|utility|tuple|type_traits|cstddef|cstdint|exception|new|limits|stdexcept)' file not found/,
|
|
433
|
+
/no member named '\w+' in the global namespace/,
|
|
434
|
+
/no member named '\w+' in namespace 'std'/,
|
|
435
|
+
/no template named '\w+' in namespace 'std'/,
|
|
436
|
+
/no template named 'pointer_traits'/,
|
|
437
|
+
/unknown type name 'stream(pos|off)'/,
|
|
438
|
+
/no type named 'string' in namespace 'std'/,
|
|
439
|
+
/use of undeclared identifier '_Elem'/,
|
|
440
|
+
/use of undeclared identifier 'tuple'/,
|
|
441
|
+
/cannot initialize return object of type 'int' with an lvalue of type 'const char/,
|
|
442
|
+
/expected ';' after expression/,
|
|
443
|
+
/unknown type name 'namespace'/,
|
|
444
|
+
/expected unqualified-id/,
|
|
445
|
+
/no type named 'type' in/,
|
|
446
|
+
/declaration of anonymous struct must be a definition/,
|
|
447
|
+
];
|
|
448
|
+
|
|
449
|
+
for (let i = 0; i < lines.length; i++) {
|
|
450
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
451
|
+
const line = lines[i];
|
|
452
|
+
|
|
453
|
+
if (skipNextLine) {
|
|
454
|
+
skipNextLine = false;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Check if this is a system header location
|
|
459
|
+
let isSystemHeader = false;
|
|
460
|
+
for (const pattern of systemHeaderPatterns) {
|
|
461
|
+
if (pattern.test(line)) {
|
|
462
|
+
isSystemHeader = true;
|
|
463
|
+
inSystemHeader = true;
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Check if this is a known system header error in a user file
|
|
469
|
+
if (!isSystemHeader && line.includes(" error:")) {
|
|
470
|
+
for (const pattern of systemHeaderErrors) {
|
|
471
|
+
if (pattern.test(line)) {
|
|
472
|
+
isSystemHeader = true;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Reset inSystemHeader flag when we see a new file
|
|
479
|
+
if (line.match(/^[^:]+\.(cpp|h|hpp):\d+:\d+: (error|warning):/)) {
|
|
480
|
+
inSystemHeader = false;
|
|
481
|
+
for (const pattern of systemHeaderPatterns) {
|
|
482
|
+
if (pattern.test(line)) {
|
|
483
|
+
inSystemHeader = true;
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (!isSystemHeader && !inSystemHeader) {
|
|
490
|
+
// Only add non-header errors to output
|
|
491
|
+
filteredLines.push(line);
|
|
492
|
+
|
|
493
|
+
// Count errors and warnings
|
|
494
|
+
if (line.includes(" error:")) {
|
|
495
|
+
errors++;
|
|
496
|
+
}
|
|
497
|
+
if (line.includes(" warning:")) {
|
|
498
|
+
warnings++;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
filteredOutput: filteredLines.join("\n"),
|
|
505
|
+
errors,
|
|
506
|
+
warnings,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Run clang-tidy on a single file
|
|
511
|
+
async function runClangTidyOnFile(
|
|
512
|
+
clangTidy: string,
|
|
513
|
+
file: string,
|
|
514
|
+
): Promise<{
|
|
515
|
+
file: string;
|
|
516
|
+
output: string;
|
|
517
|
+
errors: number;
|
|
518
|
+
warnings: number;
|
|
519
|
+
filtered?: number;
|
|
520
|
+
}> {
|
|
521
|
+
try {
|
|
522
|
+
let extraArgs = "";
|
|
523
|
+
|
|
524
|
+
// Platform-specific config and arguments
|
|
525
|
+
if (isWindows) {
|
|
526
|
+
// Always use src/windows/.clang-tidy for Windows
|
|
527
|
+
const configPath = join("src", "windows", ".clang-tidy");
|
|
528
|
+
if (existsSync(configPath)) {
|
|
529
|
+
extraArgs = `--config-file=${configPath}`;
|
|
530
|
+
}
|
|
531
|
+
} else if (isMacOS && clangTidy.includes("/opt/")) {
|
|
532
|
+
// macOS with Homebrew LLVM needs extra paths
|
|
533
|
+
// The compile_commands.json uses Apple's /usr/bin/cc, but we're running
|
|
534
|
+
// Homebrew's clang-tidy which needs Homebrew's C++ stdlib
|
|
535
|
+
const sdkPath = execSync("xcrun --show-sdk-path", {
|
|
536
|
+
encoding: "utf8",
|
|
537
|
+
}).trim();
|
|
538
|
+
|
|
539
|
+
// Extract Homebrew prefix from clang-tidy path
|
|
540
|
+
const brewPrefix = clangTidy.includes("/opt/homebrew")
|
|
541
|
+
? "/opt/homebrew"
|
|
542
|
+
: "/usr/local";
|
|
543
|
+
|
|
544
|
+
extraArgs =
|
|
545
|
+
`--extra-arg=-nostdinc++ ` + // Ignore built-in C++ paths
|
|
546
|
+
`--extra-arg=-isystem${brewPrefix}/opt/llvm/include/c++/v1 ` +
|
|
547
|
+
`--extra-arg=-isysroot${sdkPath} ` +
|
|
548
|
+
`--extra-arg=-isystem${sdkPath}/usr/include ` +
|
|
549
|
+
`--extra-arg=-F${sdkPath}/System/Library/Frameworks`;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const { stdout, stderr } = await exec(
|
|
553
|
+
`${isWindows ? `"${clangTidy}"` : clangTidy} -p . ${extraArgs} "${file}" 2>&1`,
|
|
554
|
+
);
|
|
555
|
+
let output = stdout + stderr;
|
|
556
|
+
|
|
557
|
+
let errors = 0;
|
|
558
|
+
let warnings = 0;
|
|
559
|
+
let filteredCount = 0;
|
|
560
|
+
|
|
561
|
+
// Filter platform-specific header errors
|
|
562
|
+
if (isWindows) {
|
|
563
|
+
const filtered = filterWindowsHeaderErrors(output);
|
|
564
|
+
output = filtered.filteredOutput;
|
|
565
|
+
errors = filtered.errors;
|
|
566
|
+
warnings = filtered.warnings;
|
|
567
|
+
} else if (isMacOS && clangTidy.includes("/opt/")) {
|
|
568
|
+
// Filter Homebrew LLVM errors on macOS
|
|
569
|
+
const filtered = filterMacOSHeaderErrors(output);
|
|
570
|
+
output = filtered.filteredOutput;
|
|
571
|
+
errors = filtered.errors;
|
|
572
|
+
warnings = filtered.warnings;
|
|
573
|
+
filteredCount = filtered.filtered;
|
|
574
|
+
} else {
|
|
575
|
+
const lines = output.split("\n");
|
|
576
|
+
for (const line of lines) {
|
|
577
|
+
if (line.includes(" warning:")) warnings++;
|
|
578
|
+
if (line.includes(" error:")) errors++;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return { file, output, errors, warnings, filtered: filteredCount };
|
|
583
|
+
} catch (error: any) {
|
|
584
|
+
let output = error.stdout || error.stderr || error.message;
|
|
585
|
+
let errors = 0;
|
|
586
|
+
let warnings = 0;
|
|
587
|
+
let filteredCount = 0;
|
|
588
|
+
|
|
589
|
+
// Filter platform-specific header errors
|
|
590
|
+
if (isWindows) {
|
|
591
|
+
const filtered = filterWindowsHeaderErrors(output);
|
|
592
|
+
output = filtered.filteredOutput;
|
|
593
|
+
errors = filtered.errors;
|
|
594
|
+
warnings = filtered.warnings;
|
|
595
|
+
} else if (isMacOS && clangTidy.includes("/opt/")) {
|
|
596
|
+
// Filter Homebrew LLVM errors on macOS
|
|
597
|
+
const filtered = filterMacOSHeaderErrors(output);
|
|
598
|
+
output = filtered.filteredOutput;
|
|
599
|
+
errors = filtered.errors;
|
|
600
|
+
warnings = filtered.warnings;
|
|
601
|
+
filteredCount = filtered.filtered;
|
|
602
|
+
} else {
|
|
603
|
+
const lines = output.split("\n");
|
|
604
|
+
for (const line of lines) {
|
|
605
|
+
if (line.includes(" warning:")) warnings++;
|
|
606
|
+
if (line.includes(" error:")) errors++;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return { file, output, errors, warnings, filtered: filteredCount };
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Main function
|
|
615
|
+
async function main(): Promise<void> {
|
|
616
|
+
const clangTidy = findClangTidy();
|
|
617
|
+
|
|
618
|
+
if (!clangTidy) {
|
|
619
|
+
console.error(`${colors.red}Error: clang-tidy not found${colors.reset}`);
|
|
620
|
+
if (isWindows) {
|
|
621
|
+
console.error("\nTo install clang-tidy on Windows:");
|
|
622
|
+
console.error(
|
|
623
|
+
"1. Install LLVM: https://github.com/llvm/llvm-project/releases",
|
|
624
|
+
);
|
|
625
|
+
console.error("2. Or install Visual Studio 2019/2022 with C++ tools");
|
|
626
|
+
} else if (isMacOS) {
|
|
627
|
+
console.error("\nTo install on macOS:");
|
|
628
|
+
console.error(" Option 1: brew install clang-tidy");
|
|
629
|
+
console.error(" Option 2: brew install llvm");
|
|
630
|
+
} else {
|
|
631
|
+
console.error("\nTo install on Linux:");
|
|
632
|
+
console.error(" sudo apt-get install clang-tidy");
|
|
633
|
+
}
|
|
634
|
+
process.exit(1);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
console.log(`${colors.blue}=== Running clang-tidy ===${colors.reset}`);
|
|
638
|
+
console.log(`${colors.dim}Using: ${clangTidy}${colors.reset}`);
|
|
639
|
+
console.log(`${colors.dim}Platform: ${platform()}${colors.reset}`);
|
|
640
|
+
|
|
641
|
+
// Generate or check compile_commands.json
|
|
642
|
+
if (!existsSync("compile_commands.json")) {
|
|
643
|
+
if (isWindows) {
|
|
644
|
+
if (!(await generateWindowsCompileCommands())) {
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
await generateUnixCompileCommands();
|
|
649
|
+
}
|
|
650
|
+
} else {
|
|
651
|
+
console.log("Using existing compile_commands.json");
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Get files to check
|
|
655
|
+
const files = await getSourceFiles();
|
|
656
|
+
if (files.length === 0) {
|
|
657
|
+
console.log(
|
|
658
|
+
`${colors.yellow}No source files found to check${colors.reset}`,
|
|
659
|
+
);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
console.log(
|
|
664
|
+
`${colors.dim}Checking ${files.length} files...${colors.reset}\n`,
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
// Run clang-tidy on files
|
|
668
|
+
const parallelism = isWindows ? 1 : Math.min(cpus().length, 8);
|
|
669
|
+
const results: Array<{
|
|
670
|
+
file: string;
|
|
671
|
+
output: string;
|
|
672
|
+
errors: number;
|
|
673
|
+
warnings: number;
|
|
674
|
+
}> = [];
|
|
675
|
+
|
|
676
|
+
// Process files
|
|
677
|
+
if (isWindows) {
|
|
678
|
+
// Sequential on Windows to avoid issues
|
|
679
|
+
for (const file of files) {
|
|
680
|
+
const result = await runClangTidyOnFile(clangTidy, file);
|
|
681
|
+
results.push(result);
|
|
682
|
+
|
|
683
|
+
// Show progress
|
|
684
|
+
const relPath = file.replace(
|
|
685
|
+
process.cwd() + (isWindows ? "\\" : "/"),
|
|
686
|
+
"",
|
|
687
|
+
);
|
|
688
|
+
if (result.errors > 0) {
|
|
689
|
+
console.log(
|
|
690
|
+
`${colors.red}✗${colors.reset} ${relPath} (${result.errors} errors, ${result.warnings} warnings)`,
|
|
691
|
+
);
|
|
692
|
+
// Show first few errors (already filtered on Windows)
|
|
693
|
+
const errorLines = result.output
|
|
694
|
+
.split("\n")
|
|
695
|
+
.filter(
|
|
696
|
+
(line) => line.includes(" error:") || line.includes(" warning:"),
|
|
697
|
+
);
|
|
698
|
+
errorLines
|
|
699
|
+
.slice(0, 5)
|
|
700
|
+
.forEach((line) =>
|
|
701
|
+
console.log(` ${colors.dim}${line}${colors.reset}`),
|
|
702
|
+
);
|
|
703
|
+
if (errorLines.length > 5) {
|
|
704
|
+
console.log(
|
|
705
|
+
` ${colors.dim}... and ${errorLines.length - 5} more${colors.reset}`,
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
} else if (result.warnings > 0) {
|
|
709
|
+
console.log(
|
|
710
|
+
`${colors.yellow}⚠${colors.reset} ${relPath} (${result.warnings} warnings)`,
|
|
711
|
+
);
|
|
712
|
+
// Show warnings (already filtered on Windows)
|
|
713
|
+
const warningLines = result.output
|
|
714
|
+
.split("\n")
|
|
715
|
+
.filter((line) => line.includes(" warning:"));
|
|
716
|
+
warningLines.forEach((line) =>
|
|
717
|
+
console.log(` ${colors.dim}${line}${colors.reset}`),
|
|
718
|
+
);
|
|
719
|
+
} else {
|
|
720
|
+
console.log(`${colors.green}✓${colors.reset} ${relPath}`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
} else {
|
|
724
|
+
// Parallel on Unix-like systems
|
|
725
|
+
for (let i = 0; i < files.length; i += parallelism) {
|
|
726
|
+
const chunk = files.slice(i, i + parallelism);
|
|
727
|
+
const chunkResults = await Promise.all(
|
|
728
|
+
chunk.map((file) => runClangTidyOnFile(clangTidy, file)),
|
|
729
|
+
);
|
|
730
|
+
results.push(...chunkResults);
|
|
731
|
+
|
|
732
|
+
// Show progress
|
|
733
|
+
for (const result of chunkResults) {
|
|
734
|
+
const relPath = result.file.replace(process.cwd() + "/", "");
|
|
735
|
+
if (result.errors > 0) {
|
|
736
|
+
console.log(
|
|
737
|
+
`${colors.red}✗${colors.reset} ${relPath} (${result.errors} errors, ${result.warnings} warnings)`,
|
|
738
|
+
);
|
|
739
|
+
// Show actual errors
|
|
740
|
+
const errorLines = result.output
|
|
741
|
+
.split("\n")
|
|
742
|
+
.filter(
|
|
743
|
+
(line) => line.includes(" error:") || line.includes(" warning:"),
|
|
744
|
+
);
|
|
745
|
+
errorLines.forEach((line) =>
|
|
746
|
+
console.log(` ${colors.dim}${line}${colors.reset}`),
|
|
747
|
+
);
|
|
748
|
+
} else if (result.warnings > 0) {
|
|
749
|
+
console.log(
|
|
750
|
+
`${colors.yellow}⚠${colors.reset} ${relPath} (${result.warnings} warnings)`,
|
|
751
|
+
);
|
|
752
|
+
// Show warnings
|
|
753
|
+
const warningLines = result.output
|
|
754
|
+
.split("\n")
|
|
755
|
+
.filter((line) => line.includes(" warning:"));
|
|
756
|
+
warningLines.forEach((line) =>
|
|
757
|
+
console.log(` ${colors.dim}${line}${colors.reset}`),
|
|
758
|
+
);
|
|
759
|
+
} else {
|
|
760
|
+
console.log(`${colors.green}✓${colors.reset} ${relPath}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Summary
|
|
767
|
+
const totalErrors = results.reduce((sum, r) => sum + r.errors, 0);
|
|
768
|
+
const totalWarnings = results.reduce((sum, r) => sum + r.warnings, 0);
|
|
769
|
+
|
|
770
|
+
console.log(`\n${colors.blue}=== Summary ===${colors.reset}`);
|
|
771
|
+
if (totalErrors > 0) {
|
|
772
|
+
console.log(`${colors.red}✗ ${totalErrors} errors found${colors.reset}`);
|
|
773
|
+
}
|
|
774
|
+
if (totalWarnings > 0) {
|
|
775
|
+
console.log(
|
|
776
|
+
`${colors.yellow}⚠ ${totalWarnings} warnings found${colors.reset}`,
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
if (totalErrors === 0 && totalWarnings === 0) {
|
|
780
|
+
console.log(`${colors.green}✓ No issues found${colors.reset}`);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (isWindows) {
|
|
784
|
+
console.log(
|
|
785
|
+
`\n${colors.dim}Windows: Using src/windows/.clang-tidy with header error filtering${colors.reset}`,
|
|
786
|
+
);
|
|
787
|
+
console.log(
|
|
788
|
+
`${colors.dim}Note: System header errors are automatically filtered out${colors.reset}`,
|
|
789
|
+
);
|
|
790
|
+
} else if (isMacOS && clangTidy.includes("/opt/")) {
|
|
791
|
+
console.log(
|
|
792
|
+
`\n${colors.dim}macOS: Using Homebrew LLVM clang-tidy with header error filtering${colors.reset}`,
|
|
793
|
+
);
|
|
794
|
+
console.log(
|
|
795
|
+
`${colors.dim}Note: Standard library header errors from toolchain mismatch are filtered out${colors.reset}`,
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
// Show filtering statistics for transparency
|
|
799
|
+
const totalFilesChecked = files.length;
|
|
800
|
+
const totalFilteredErrors = results.reduce(
|
|
801
|
+
(sum, r) => sum + (r.filtered || 0),
|
|
802
|
+
0,
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
if (totalFilteredErrors > 0) {
|
|
806
|
+
console.log(
|
|
807
|
+
`${colors.dim}Filtered ${totalFilteredErrors} known header incompatibility error(s)${colors.reset}`,
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Sanity check: If we analyzed files and found zero issues after filtering, show validation
|
|
812
|
+
if (totalErrors === 0 && totalWarnings === 0 && totalFilesChecked > 0) {
|
|
813
|
+
if (totalFilteredErrors > 0) {
|
|
814
|
+
console.log(
|
|
815
|
+
`${colors.dim}✓ Verified: ${totalFilesChecked} files analyzed cleanly (${totalFilteredErrors} known issues filtered)${colors.reset}`,
|
|
816
|
+
);
|
|
817
|
+
} else {
|
|
818
|
+
console.log(
|
|
819
|
+
`${colors.dim}✓ Verified: ${totalFilesChecked} files analyzed with no issues${colors.reset}`,
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
process.exit(totalErrors > 0 ? 1 : 0);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Run
|
|
829
|
+
main().catch((err) => {
|
|
830
|
+
console.error(`${colors.red}Error: ${err.message}${colors.reset}`);
|
|
831
|
+
process.exit(1);
|
|
832
|
+
});
|