@photostructure/fs-metadata 1.1.0 → 1.2.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 +6 -0
- package/binding.gyp +1 -0
- package/dist/index.cjs +168 -120
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -1
- package/dist/index.d.mts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.mjs +167 -120
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/darwin-x64/@photostructure+fs-metadata.glibc.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/src/binding.cpp +11 -0
- package/src/darwin/get_mount_point.cpp +96 -0
- package/src/darwin/get_mount_point.h +13 -0
- package/src/index.ts +27 -0
- package/src/mount_point_for_path.ts +54 -0
- package/src/types/native_bindings.ts +7 -0
- package/src/volume_metadata.ts +27 -7
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,12 @@ Fixed for any bug fixes.
|
|
|
14
14
|
Security in case of vulnerabilities.
|
|
15
15
|
-->
|
|
16
16
|
|
|
17
|
+
## 1.2.0 - 2026-03-26
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- New `getMountPointForPath(pathname)` function: a lightweight alternative to `getVolumeMetadataForPath()` that returns only the mount point string without fetching full volume metadata (size, UUID, label, etc.). On macOS, uses a single `fstatfs()` call — no DiskArbitration, IOKit, or space calculations. On Linux/Windows, uses the same device-ID matching logic. Handles symlinks and APFS firmlinks correctly.
|
|
22
|
+
|
|
17
23
|
## 1.1.0 - 2026-03-16
|
|
18
24
|
|
|
19
25
|
### Added
|
package/binding.gyp
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,7 @@ __export(index_exports, {
|
|
|
40
40
|
VolumeHealthStatuses: () => VolumeHealthStatuses,
|
|
41
41
|
getAllVolumeMetadata: () => getAllVolumeMetadata,
|
|
42
42
|
getHiddenMetadata: () => getHiddenMetadata,
|
|
43
|
+
getMountPointForPath: () => getMountPointForPath,
|
|
43
44
|
getTimeoutMsDefault: () => getTimeoutMsDefault,
|
|
44
45
|
getVolumeMetadata: () => getVolumeMetadata,
|
|
45
46
|
getVolumeMetadataForPath: () => getVolumeMetadataForPath,
|
|
@@ -630,6 +631,74 @@ async function setHiddenImpl(pathname, hide, method, nativeFn2) {
|
|
|
630
631
|
return { pathname: norm, actions };
|
|
631
632
|
}
|
|
632
633
|
|
|
634
|
+
// src/mount_point_for_path.ts
|
|
635
|
+
var import_promises6 = require("fs/promises");
|
|
636
|
+
var import_node_path7 = require("path");
|
|
637
|
+
|
|
638
|
+
// src/volume_metadata.ts
|
|
639
|
+
var import_promises5 = require("fs/promises");
|
|
640
|
+
var import_node_path6 = require("path");
|
|
641
|
+
|
|
642
|
+
// src/linux/dev_disk.ts
|
|
643
|
+
var import_promises3 = require("fs/promises");
|
|
644
|
+
var import_node_path5 = require("path");
|
|
645
|
+
async function getUuidFromDevDisk(devicePath) {
|
|
646
|
+
try {
|
|
647
|
+
const result = await getBasenameLinkedTo(
|
|
648
|
+
"/dev/disk/by-uuid",
|
|
649
|
+
(0, import_node_path5.resolve)(devicePath)
|
|
650
|
+
);
|
|
651
|
+
debug("[getUuidFromDevDisk] result: %o", result);
|
|
652
|
+
return result;
|
|
653
|
+
} catch (error) {
|
|
654
|
+
debug("[getUuidFromDevDisk] failed: " + error);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async function getLabelFromDevDisk(devicePath) {
|
|
659
|
+
try {
|
|
660
|
+
const result = await getBasenameLinkedTo(
|
|
661
|
+
"/dev/disk/by-label",
|
|
662
|
+
(0, import_node_path5.resolve)(devicePath)
|
|
663
|
+
);
|
|
664
|
+
debug("[getLabelFromDevDisk] result: %o", result);
|
|
665
|
+
return result;
|
|
666
|
+
} catch (error) {
|
|
667
|
+
debug("[getLabelFromDevDisk] failed: " + error);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async function getBasenameLinkedTo(linkDir, linkPath) {
|
|
672
|
+
for await (const ea of readLinks(linkDir)) {
|
|
673
|
+
if (ea.linkTarget === linkPath) {
|
|
674
|
+
return decodeEscapeSequences(ea.dirent.name);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
async function* readLinks(directory) {
|
|
680
|
+
for (const dirent of await (0, import_promises3.readdir)(directory, { withFileTypes: true })) {
|
|
681
|
+
if (dirent.isSymbolicLink()) {
|
|
682
|
+
try {
|
|
683
|
+
const linkTarget = (0, import_node_path5.resolve)(
|
|
684
|
+
directory,
|
|
685
|
+
await (0, import_promises3.readlink)((0, import_node_path5.join)(directory, dirent.name))
|
|
686
|
+
);
|
|
687
|
+
yield { dirent, linkTarget };
|
|
688
|
+
} catch {
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/linux/mount_points.ts
|
|
695
|
+
var import_promises4 = require("fs/promises");
|
|
696
|
+
|
|
697
|
+
// src/mount_point.ts
|
|
698
|
+
function isMountPoint(obj) {
|
|
699
|
+
return isObject(obj) && "mountPoint" in obj && isNotBlank(obj.mountPoint);
|
|
700
|
+
}
|
|
701
|
+
|
|
633
702
|
// src/options.ts
|
|
634
703
|
var import_node_os2 = require("os");
|
|
635
704
|
var import_node_process3 = require("process");
|
|
@@ -830,113 +899,6 @@ function optionsWithDefaults(overrides = {}) {
|
|
|
830
899
|
};
|
|
831
900
|
}
|
|
832
901
|
|
|
833
|
-
// src/string_enum.ts
|
|
834
|
-
function stringEnum(...o) {
|
|
835
|
-
const set = new Set(o);
|
|
836
|
-
const dict = {};
|
|
837
|
-
for (const key of o) {
|
|
838
|
-
dict[key] = key;
|
|
839
|
-
}
|
|
840
|
-
return {
|
|
841
|
-
...dict,
|
|
842
|
-
values: Object.freeze([...set]),
|
|
843
|
-
size: set.size,
|
|
844
|
-
get: (s) => s != null && set.has(s) ? s : void 0
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
// src/volume_health_status.ts
|
|
849
|
-
var VolumeHealthStatuses = stringEnum(
|
|
850
|
-
"healthy",
|
|
851
|
-
"timeout",
|
|
852
|
-
"inaccessible",
|
|
853
|
-
"disconnected",
|
|
854
|
-
"unknown"
|
|
855
|
-
);
|
|
856
|
-
async function directoryStatus(dir, timeoutMs, canReaddirImpl = canReaddir) {
|
|
857
|
-
try {
|
|
858
|
-
if (await canReaddirImpl(dir, timeoutMs)) {
|
|
859
|
-
return { status: VolumeHealthStatuses.healthy };
|
|
860
|
-
}
|
|
861
|
-
} catch (error) {
|
|
862
|
-
debug("[directoryStatus] %s: %s", dir, error);
|
|
863
|
-
let status = VolumeHealthStatuses.unknown;
|
|
864
|
-
if (error instanceof TimeoutError) {
|
|
865
|
-
status = VolumeHealthStatuses.timeout;
|
|
866
|
-
} else if (isObject(error) && error instanceof Error && "code" in error) {
|
|
867
|
-
if (error.code === "EPERM" || error.code === "EACCES") {
|
|
868
|
-
status = VolumeHealthStatuses.inaccessible;
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
return { status, error: toError(error) };
|
|
872
|
-
}
|
|
873
|
-
return { status: VolumeHealthStatuses.unknown };
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
// src/volume_metadata.ts
|
|
877
|
-
var import_promises5 = require("fs/promises");
|
|
878
|
-
var import_node_path6 = require("path");
|
|
879
|
-
|
|
880
|
-
// src/linux/dev_disk.ts
|
|
881
|
-
var import_promises3 = require("fs/promises");
|
|
882
|
-
var import_node_path5 = require("path");
|
|
883
|
-
async function getUuidFromDevDisk(devicePath) {
|
|
884
|
-
try {
|
|
885
|
-
const result = await getBasenameLinkedTo(
|
|
886
|
-
"/dev/disk/by-uuid",
|
|
887
|
-
(0, import_node_path5.resolve)(devicePath)
|
|
888
|
-
);
|
|
889
|
-
debug("[getUuidFromDevDisk] result: %o", result);
|
|
890
|
-
return result;
|
|
891
|
-
} catch (error) {
|
|
892
|
-
debug("[getUuidFromDevDisk] failed: " + error);
|
|
893
|
-
return;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
async function getLabelFromDevDisk(devicePath) {
|
|
897
|
-
try {
|
|
898
|
-
const result = await getBasenameLinkedTo(
|
|
899
|
-
"/dev/disk/by-label",
|
|
900
|
-
(0, import_node_path5.resolve)(devicePath)
|
|
901
|
-
);
|
|
902
|
-
debug("[getLabelFromDevDisk] result: %o", result);
|
|
903
|
-
return result;
|
|
904
|
-
} catch (error) {
|
|
905
|
-
debug("[getLabelFromDevDisk] failed: " + error);
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
async function getBasenameLinkedTo(linkDir, linkPath) {
|
|
910
|
-
for await (const ea of readLinks(linkDir)) {
|
|
911
|
-
if (ea.linkTarget === linkPath) {
|
|
912
|
-
return decodeEscapeSequences(ea.dirent.name);
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
return;
|
|
916
|
-
}
|
|
917
|
-
async function* readLinks(directory) {
|
|
918
|
-
for (const dirent of await (0, import_promises3.readdir)(directory, { withFileTypes: true })) {
|
|
919
|
-
if (dirent.isSymbolicLink()) {
|
|
920
|
-
try {
|
|
921
|
-
const linkTarget = (0, import_node_path5.resolve)(
|
|
922
|
-
directory,
|
|
923
|
-
await (0, import_promises3.readlink)((0, import_node_path5.join)(directory, dirent.name))
|
|
924
|
-
);
|
|
925
|
-
yield { dirent, linkTarget };
|
|
926
|
-
} catch {
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
// src/linux/mount_points.ts
|
|
933
|
-
var import_promises4 = require("fs/promises");
|
|
934
|
-
|
|
935
|
-
// src/mount_point.ts
|
|
936
|
-
function isMountPoint(obj) {
|
|
937
|
-
return isObject(obj) && "mountPoint" in obj && isNotBlank(obj.mountPoint);
|
|
938
|
-
}
|
|
939
|
-
|
|
940
902
|
// src/remote_info.ts
|
|
941
903
|
function isRemoteInfo(obj) {
|
|
942
904
|
if (!isObject(obj)) return false;
|
|
@@ -1318,6 +1280,49 @@ function extractUUID(uuid) {
|
|
|
1318
1280
|
return toS(uuid).match(uuidRegex)?.[0];
|
|
1319
1281
|
}
|
|
1320
1282
|
|
|
1283
|
+
// src/string_enum.ts
|
|
1284
|
+
function stringEnum(...o) {
|
|
1285
|
+
const set = new Set(o);
|
|
1286
|
+
const dict = {};
|
|
1287
|
+
for (const key of o) {
|
|
1288
|
+
dict[key] = key;
|
|
1289
|
+
}
|
|
1290
|
+
return {
|
|
1291
|
+
...dict,
|
|
1292
|
+
values: Object.freeze([...set]),
|
|
1293
|
+
size: set.size,
|
|
1294
|
+
get: (s) => s != null && set.has(s) ? s : void 0
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// src/volume_health_status.ts
|
|
1299
|
+
var VolumeHealthStatuses = stringEnum(
|
|
1300
|
+
"healthy",
|
|
1301
|
+
"timeout",
|
|
1302
|
+
"inaccessible",
|
|
1303
|
+
"disconnected",
|
|
1304
|
+
"unknown"
|
|
1305
|
+
);
|
|
1306
|
+
async function directoryStatus(dir, timeoutMs, canReaddirImpl = canReaddir) {
|
|
1307
|
+
try {
|
|
1308
|
+
if (await canReaddirImpl(dir, timeoutMs)) {
|
|
1309
|
+
return { status: VolumeHealthStatuses.healthy };
|
|
1310
|
+
}
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
debug("[directoryStatus] %s: %s", dir, error);
|
|
1313
|
+
let status = VolumeHealthStatuses.unknown;
|
|
1314
|
+
if (error instanceof TimeoutError) {
|
|
1315
|
+
status = VolumeHealthStatuses.timeout;
|
|
1316
|
+
} else if (isObject(error) && error instanceof Error && "code" in error) {
|
|
1317
|
+
if (error.code === "EPERM" || error.code === "EACCES") {
|
|
1318
|
+
status = VolumeHealthStatuses.inaccessible;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return { status, error: toError(error) };
|
|
1322
|
+
}
|
|
1323
|
+
return { status: VolumeHealthStatuses.unknown };
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1321
1326
|
// src/array.ts
|
|
1322
1327
|
function uniqBy(arr, keyFn) {
|
|
1323
1328
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1487,6 +1492,15 @@ async function getVolumeMetadataForPathImpl(pathname, opts, nativeFn2) {
|
|
|
1487
1492
|
nativeFn2
|
|
1488
1493
|
);
|
|
1489
1494
|
}
|
|
1495
|
+
const mountPoint = await findMountPointByDeviceId(
|
|
1496
|
+
resolved,
|
|
1497
|
+
resolvedStat,
|
|
1498
|
+
opts,
|
|
1499
|
+
nativeFn2
|
|
1500
|
+
);
|
|
1501
|
+
return getVolumeMetadataImpl({ ...opts, mountPoint }, nativeFn2);
|
|
1502
|
+
}
|
|
1503
|
+
async function findMountPointByDeviceId(resolved, resolvedStat, opts, nativeFn2) {
|
|
1490
1504
|
const targetDev = resolvedStat.dev;
|
|
1491
1505
|
const mountPoints = await getVolumeMountPointsImpl(
|
|
1492
1506
|
{ ...opts, includeSystemVolumes: true },
|
|
@@ -1495,14 +1509,14 @@ async function getVolumeMetadataForPathImpl(pathname, opts, nativeFn2) {
|
|
|
1495
1509
|
const prefixMatches = [];
|
|
1496
1510
|
const deviceMatches = [];
|
|
1497
1511
|
await Promise.all(
|
|
1498
|
-
mountPoints.map(async ({ mountPoint
|
|
1512
|
+
mountPoints.map(async ({ mountPoint }) => {
|
|
1499
1513
|
try {
|
|
1500
|
-
const mpDev = (await statAsync(
|
|
1514
|
+
const mpDev = (await statAsync(mountPoint)).dev;
|
|
1501
1515
|
if (mpDev !== targetDev) return;
|
|
1502
|
-
if (isAncestorOrSelf(
|
|
1503
|
-
prefixMatches.push(
|
|
1516
|
+
if (isAncestorOrSelf(mountPoint, resolved)) {
|
|
1517
|
+
prefixMatches.push(mountPoint);
|
|
1504
1518
|
} else {
|
|
1505
|
-
deviceMatches.push(
|
|
1519
|
+
deviceMatches.push(mountPoint);
|
|
1506
1520
|
}
|
|
1507
1521
|
} catch {
|
|
1508
1522
|
}
|
|
@@ -1511,13 +1525,10 @@ async function getVolumeMetadataForPathImpl(pathname, opts, nativeFn2) {
|
|
|
1511
1525
|
const candidates = prefixMatches.length > 0 ? prefixMatches : deviceMatches;
|
|
1512
1526
|
if (candidates.length === 0) {
|
|
1513
1527
|
throw new Error(
|
|
1514
|
-
"No mount point found for path: " + JSON.stringify(
|
|
1528
|
+
"No mount point found for path: " + JSON.stringify(resolved)
|
|
1515
1529
|
);
|
|
1516
1530
|
}
|
|
1517
|
-
|
|
1518
|
-
(a, b) => a.length >= b.length ? a : b
|
|
1519
|
-
);
|
|
1520
|
-
return getVolumeMetadataImpl({ ...opts, mountPoint }, nativeFn2);
|
|
1531
|
+
return candidates.reduce((a, b) => a.length >= b.length ? a : b);
|
|
1521
1532
|
}
|
|
1522
1533
|
async function getAllVolumeMetadataImpl(opts, nativeFn2) {
|
|
1523
1534
|
const o = optionsWithDefaults(opts);
|
|
@@ -1570,15 +1581,44 @@ async function getAllVolumeMetadataImpl(opts, nativeFn2) {
|
|
|
1570
1581
|
);
|
|
1571
1582
|
}
|
|
1572
1583
|
|
|
1584
|
+
// src/mount_point_for_path.ts
|
|
1585
|
+
async function getMountPointForPathImpl(pathname, opts, nativeFn2) {
|
|
1586
|
+
if (isBlank(pathname)) {
|
|
1587
|
+
throw new TypeError("Invalid pathname: got " + JSON.stringify(pathname));
|
|
1588
|
+
}
|
|
1589
|
+
const resolved = await (0, import_promises6.realpath)(pathname);
|
|
1590
|
+
const resolvedStat = await statAsync(resolved);
|
|
1591
|
+
const dir = resolvedStat.isDirectory() ? resolved : (0, import_node_path7.dirname)(resolved);
|
|
1592
|
+
if (isMacOS) {
|
|
1593
|
+
const native = await nativeFn2();
|
|
1594
|
+
if (native.getMountPoint) {
|
|
1595
|
+
debug("[getMountPointForPath] using native getMountPoint for %s", dir);
|
|
1596
|
+
const p = native.getMountPoint(dir);
|
|
1597
|
+
const mountPoint = await withTimeout({
|
|
1598
|
+
desc: "getMountPoint()",
|
|
1599
|
+
timeoutMs: opts.timeoutMs,
|
|
1600
|
+
promise: p
|
|
1601
|
+
});
|
|
1602
|
+
if (isNotBlank(mountPoint)) {
|
|
1603
|
+
debug("[getMountPointForPath] resolved to %s", mountPoint);
|
|
1604
|
+
return mountPoint;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
throw new Error("getMountPoint native function unavailable");
|
|
1608
|
+
}
|
|
1609
|
+
debug("[getMountPointForPath] using device matching for %s", resolved);
|
|
1610
|
+
return findMountPointByDeviceId(resolved, resolvedStat, opts, nativeFn2);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1573
1613
|
// src/index.ts
|
|
1574
1614
|
var nativeFn = defer2(async () => {
|
|
1575
1615
|
const start = Date.now();
|
|
1576
1616
|
try {
|
|
1577
|
-
const
|
|
1578
|
-
const dir = await findAncestorDir(
|
|
1617
|
+
const dirname6 = _dirname();
|
|
1618
|
+
const dir = await findAncestorDir(dirname6, "binding.gyp");
|
|
1579
1619
|
if (dir == null) {
|
|
1580
1620
|
throw new Error(
|
|
1581
|
-
"Could not find bindings.gyp in any ancestor directory of " +
|
|
1621
|
+
"Could not find bindings.gyp in any ancestor directory of " + dirname6
|
|
1582
1622
|
);
|
|
1583
1623
|
}
|
|
1584
1624
|
const bindings = (0, import_node_gyp_build.default)(dir);
|
|
@@ -1608,6 +1648,13 @@ function getVolumeMetadataForPath(pathname, opts) {
|
|
|
1608
1648
|
nativeFn
|
|
1609
1649
|
);
|
|
1610
1650
|
}
|
|
1651
|
+
function getMountPointForPath(pathname, opts) {
|
|
1652
|
+
return getMountPointForPathImpl(
|
|
1653
|
+
pathname,
|
|
1654
|
+
optionsWithDefaults(opts),
|
|
1655
|
+
nativeFn
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1611
1658
|
function getAllVolumeMetadata(opts) {
|
|
1612
1659
|
return getAllVolumeMetadataImpl(optionsWithDefaults(opts), nativeFn);
|
|
1613
1660
|
}
|
|
@@ -1635,6 +1682,7 @@ function setHidden(pathname, hidden, method = "auto") {
|
|
|
1635
1682
|
VolumeHealthStatuses,
|
|
1636
1683
|
getAllVolumeMetadata,
|
|
1637
1684
|
getHiddenMetadata,
|
|
1685
|
+
getMountPointForPath,
|
|
1638
1686
|
getTimeoutMsDefault,
|
|
1639
1687
|
getVolumeMetadata,
|
|
1640
1688
|
getVolumeMetadataForPath,
|