@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
package/dist/index.mjs CHANGED
@@ -105,18 +105,19 @@ function getCallerDirname() {
105
105
  }
106
106
  var patterns = isWindows ? [
107
107
  // Standard: "at functionName (C:\path\file.js:1:1)"
108
- /\bat\s.+?\((?<path>[A-Z]:\\.+):\d+:\d+\)$/,
108
+ /\bat\s[^(]*\((?<path>[A-Z]:\\.+?):\d+:\d+\)$/,
109
109
  // direct: "at C:\path\file.js:1:1"
110
- /\bat\s(?<path>[A-Z]:\\.+):\d+:\d+$/,
110
+ /\bat\s(?<path>[A-Z]:\\.+?):\d+:\d+$/,
111
111
  // UNC: "at functionName (\\server\share\path\file.js:1:1)"
112
- /\bat\s.+?\((?<path>\\\\.+):\d+:\d+\)$/,
112
+ /\bat\s[^(]*\((?<path>\\\\.+?):\d+:\d+\)$/,
113
113
  // direct: "at \\server\share\path\file.js:1:1"
114
- /\bat\s(?<path>\\\\.+):\d+:\d+$/
114
+ /\bat\s(?<path>\\\\.+?):\d+:\d+$/
115
115
  ] : [
116
116
  // Standard: "at functionName (/path/file.js:1:1)"
117
- /\bat\s.+?\((?<path>\/.+?):\d+:\d+\)$/,
117
+ /\bat\s[^(]*\((?<path>\/.*?):\d+:\d+\)$/,
118
118
  // Anonymous or direct: "at /path/file.js:1:1"
119
- /\bat\s(.+[^/]\s)?(?<path>\/.+?):\d+:\d+$/
119
+ // eslint-disable-next-line security/detect-unsafe-regex -- parsing trusted Node.js stack traces, bounded by line anchors
120
+ /\bat\s(?:[^/\s]+\s+)?(?<path>\/.*?):\d+:\d+$/
120
121
  ];
121
122
  var MaybeUrlRE = /^[a-z]{2,5}:\/\//i;
122
123
  function extractCallerPath(stack) {
@@ -282,13 +283,6 @@ async function mapConcurrent({
282
283
  async function statAsync(path2, options) {
283
284
  return stat(path2, options);
284
285
  }
285
- async function canStatAsync(path2) {
286
- try {
287
- return null != await statAsync(path2);
288
- } catch {
289
- return false;
290
- }
291
- }
292
286
  async function findAncestorDir(dir, file) {
293
287
  dir = resolve(dir);
294
288
  try {
@@ -397,6 +391,12 @@ function toError(cause) {
397
391
  import { dirname as dirname2, resolve as resolve2 } from "path";
398
392
  function normalizePath(mountPoint) {
399
393
  if (isBlank(mountPoint)) return void 0;
394
+ if (mountPoint.includes("..")) {
395
+ throw new Error("Invalid path: contains directory traversal pattern");
396
+ }
397
+ if (mountPoint.includes("\uFFFD") || mountPoint.includes("\0")) {
398
+ throw new Error("Invalid path: contains invalid characters");
399
+ }
400
400
  const result = isWindows ? normalizeWindowsPath(mountPoint) : normalizePosixPath(mountPoint);
401
401
  return result != null ? resolve2(result) : void 0;
402
402
  }
@@ -444,11 +444,20 @@ var LocalSupport = HiddenSupportByPlatform[process.platform]?.supported ?? {
444
444
  systemFlag: false
445
445
  };
446
446
  async function isHiddenImpl(pathname, nativeFn2) {
447
+ debug("isHiddenImpl called with pathname: %s", pathname);
447
448
  const norm = normalizePath(pathname);
448
449
  if (norm == null) {
449
450
  throw new Error("Invalid pathname: " + JSON.stringify(pathname));
450
451
  }
451
- return LocalSupport.dotPrefix && isPosixHidden(norm) || LocalSupport.systemFlag && isSystemHidden(norm, nativeFn2);
452
+ debug("Normalized path: %s", norm);
453
+ debug(
454
+ "LocalSupport: dotPrefix=%s, systemFlag=%s",
455
+ LocalSupport.dotPrefix,
456
+ LocalSupport.systemFlag
457
+ );
458
+ const result = LocalSupport.dotPrefix && isPosixHidden(norm) || LocalSupport.systemFlag && await isSystemHidden(norm, nativeFn2);
459
+ debug("isHiddenImpl returning: %s", result);
460
+ return result;
452
461
  }
453
462
  async function isHiddenRecursiveImpl(path2, nativeFn2) {
454
463
  let norm = normalizePath(path2);
@@ -487,13 +496,26 @@ function isPosixHidden(pathname) {
487
496
  return b.startsWith(".") && b !== "." && b !== "..";
488
497
  }
489
498
  async function isSystemHidden(pathname, nativeFn2) {
499
+ debug("isSystemHidden called with pathname: %s", pathname);
490
500
  if (!LocalSupport.systemFlag) {
501
+ debug("systemFlag not supported on this platform");
491
502
  return false;
492
503
  }
493
- if (isWindows && isRootDirectory(pathname)) {
494
- return false;
504
+ const native = await nativeFn2();
505
+ debug("Calling native isHidden for: %s", pathname);
506
+ try {
507
+ const isHidden2 = await native.isHidden(pathname);
508
+ debug("Native isHidden returned: %s", isHidden2);
509
+ return isHidden2;
510
+ } catch (error) {
511
+ debug("Native isHidden threw error: %s", error);
512
+ const errorStr = String(error);
513
+ if (errorStr.includes("Path not found")) {
514
+ debug("Path not found, returning false");
515
+ return false;
516
+ }
517
+ throw error;
495
518
  }
496
- return await canStatAsync(pathname) && await (await nativeFn2()).isHidden(pathname);
497
519
  }
498
520
  async function getHiddenMetadataImpl(pathname, nativeFn2) {
499
521
  const norm = normalizePath(pathname);
@@ -616,13 +638,15 @@ var LinuxMountTablePathsDefault = [
616
638
  "/etc/mtab"
617
639
  ];
618
640
  var IncludeSystemVolumesDefault = isWindows;
641
+ var SkipNetworkVolumesDefault = false;
619
642
  var OptionsDefault = {
620
643
  timeoutMs: TimeoutMsDefault,
621
644
  maxConcurrency: availableParallelism2(),
622
645
  systemPathPatterns: [...SystemPathPatternsDefault],
623
646
  systemFsTypes: [...SystemFsTypesDefault],
624
647
  linuxMountTablePaths: [...LinuxMountTablePathsDefault],
625
- includeSystemVolumes: IncludeSystemVolumesDefault
648
+ includeSystemVolumes: IncludeSystemVolumesDefault,
649
+ skipNetworkVolumes: SkipNetworkVolumesDefault
626
650
  };
627
651
  function optionsWithDefaults(overrides = {}) {
628
652
  if (!isObject(overrides)) {
@@ -812,16 +836,22 @@ function extractRemoteInfo(fsSpec) {
812
836
  const patterns2 = [
813
837
  {
814
838
  // CIFS/SMB pattern: //hostname/share or //user@host/share
815
- regex: /^\/\/(?:(?<remoteUser>[^/@]+)@)?(?<remoteHost>[^/@]+)\/(?<remoteShare>.+)$/
839
+ regex: (
840
+ // eslint-disable-next-line security/detect-unsafe-regex -- parsing trusted mount paths from OS, bounded by line anchors
841
+ /^\/\/(?:(?<remoteUser>[^/@]+)@)?(?<remoteHost>[^/@]+)\/(?<remoteShare>.*)$/
842
+ )
816
843
  },
817
844
  {
818
845
  // sshfs pattern: sshfs#USER@HOST:REMOTE_PATH
819
- regex: /^(?:(?<protocol>\w+)#)?(?<remoteUser>[^@]+)@(?<remoteHost>[^:]+):(?<remoteShare>.+)$/
846
+ regex: (
847
+ // eslint-disable-next-line security/detect-unsafe-regex -- parsing trusted mount paths from OS, bounded by line anchors
848
+ /^(?:(?<protocol>\w+)#)?(?<remoteUser>[^@]+)@(?<remoteHost>[^:]+):(?<remoteShare>.*)$/
849
+ )
820
850
  },
821
851
  {
822
852
  // NFS pattern: hostname:/share
823
853
  protocol: "nfs",
824
- regex: /^(?<remoteHost>[^:]+):\/(?!\/)(?<remoteShare>.+)$/
854
+ regex: /^(?<remoteHost>[^:]+):\/(?!\/)(?<remoteShare>.*)$/
825
855
  }
826
856
  ];
827
857
  for (const { protocol, regex } of patterns2) {