@openclaw/fs-safe 0.8.3 → 0.8.4
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 +10 -0
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +8 -7
- package/dist/archive-native.d.ts +1 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +15 -68
- package/dist/archive-plan.d.ts +20 -0
- package/dist/archive-plan.d.ts.map +1 -0
- package/dist/archive-plan.js +55 -0
- package/dist/archive-tar-inspect.d.ts +7 -0
- package/dist/archive-tar-inspect.d.ts.map +1 -0
- package/dist/archive-tar-inspect.js +59 -0
- package/dist/archive-tar.d.ts +3 -1
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +12 -49
- package/dist/archive.d.ts +1 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +20 -36
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +31 -4
- package/dist/directory-durability.js +5 -5
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +5 -6
- package/dist/file-store-boundary.js +1 -1
- package/dist/file-store-prune.js +17 -8
- package/dist/file-store.js +2 -2
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +5 -4
- package/dist/native-binding.d.ts +2 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +6 -3
- package/dist/native-pinned-write.js +3 -3
- package/dist/native-staged-file.d.ts +2 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +12 -8
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +10 -9
- package/dist/path-policy.js +2 -2
- package/dist/pinned-write.d.ts +1 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +15 -11
- package/dist/regular-file.js +8 -8
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +8 -4
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +13 -7
- package/dist/root-context.js +5 -5
- package/dist/root-impl.d.ts +2 -1
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +35 -26
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +2 -3
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +2 -3
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +3 -4
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +16 -15
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +5 -4
- package/dist/root-write-verification.js +6 -6
- package/dist/secret-file.js +2 -2
- package/dist/strict-file-identity.d.ts +1 -1
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +1 -2
- package/docs/archive.md +57 -0
- package/docs/reading.md +2 -0
- package/docs/root.md +10 -1
- package/docs/types.md +2 -1
- package/docs/writing.md +26 -3
- package/package.json +8 -8
package/dist/bounded-read.js
CHANGED
|
@@ -21,11 +21,24 @@ function appendChunk(params) {
|
|
|
21
21
|
params.chunks.push(Buffer.from(params.scratch.subarray(0, params.bytesRead)));
|
|
22
22
|
return total;
|
|
23
23
|
}
|
|
24
|
-
async function readBoundedAsync(maxBytes, readChunk) {
|
|
24
|
+
async function readBoundedAsync(maxBytes, readChunk, observeRegularFileSize) {
|
|
25
25
|
normalizeMaxBytes(maxBytes);
|
|
26
26
|
const chunks = [];
|
|
27
|
-
const scratch = createScratchBuffer(maxBytes);
|
|
28
27
|
let total = 0;
|
|
28
|
+
const size = observeRegularFileSize?.();
|
|
29
|
+
if (size !== undefined) {
|
|
30
|
+
const first = Buffer.allocUnsafe(Math.min(maxBytes, size) + 1);
|
|
31
|
+
const bytesRead = await readChunk(first, first.length);
|
|
32
|
+
if (bytesRead === 0)
|
|
33
|
+
return first.subarray(0, 0);
|
|
34
|
+
// Short reads can occur before EOF. Only accept one when a fresh fd size
|
|
35
|
+
// observation proves we consumed at least the entire current file size.
|
|
36
|
+
const currentSize = bytesRead < first.length ? observeRegularFileSize?.() : undefined;
|
|
37
|
+
if (currentSize !== undefined && bytesRead >= currentSize)
|
|
38
|
+
return first.subarray(0, bytesRead);
|
|
39
|
+
total = appendChunk({ chunks, scratch: first, bytesRead, total, maxBytes });
|
|
40
|
+
}
|
|
41
|
+
const scratch = createScratchBuffer(maxBytes);
|
|
29
42
|
while (true) {
|
|
30
43
|
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
31
44
|
const bytesRead = await readChunk(scratch, length);
|
|
@@ -41,9 +54,22 @@ async function readBoundedAsync(maxBytes, readChunk) {
|
|
|
41
54
|
* force an unbounded allocation.
|
|
42
55
|
*/
|
|
43
56
|
export async function readFileHandleBounded(handle, maxBytes) {
|
|
57
|
+
normalizeMaxBytes(maxBytes);
|
|
58
|
+
const fd = "fd" in handle && typeof handle.fd === "number" ? handle.fd : undefined;
|
|
44
59
|
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
45
60
|
return (await handle.read(scratch, 0, length, null)).bytesRead;
|
|
46
|
-
});
|
|
61
|
+
}, fd === undefined ? undefined : () => regularFileSize(fd));
|
|
62
|
+
}
|
|
63
|
+
function regularFileSize(fd) {
|
|
64
|
+
try {
|
|
65
|
+
const stat = fs.fstatSync(fd);
|
|
66
|
+
return stat.isFile() && Number.isSafeInteger(stat.size) && stat.size >= 0
|
|
67
|
+
? stat.size : undefined;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Size is only an optimization hint; retain the reader's original errors.
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
47
73
|
}
|
|
48
74
|
function readDescriptorChunk(fd, scratch, length) {
|
|
49
75
|
return new Promise((resolve, reject) => {
|
|
@@ -58,9 +84,10 @@ function readDescriptorChunk(fd, scratch, length) {
|
|
|
58
84
|
}
|
|
59
85
|
/** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
60
86
|
export async function readFileDescriptorBounded(fd, maxBytes) {
|
|
87
|
+
normalizeMaxBytes(maxBytes);
|
|
61
88
|
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
62
89
|
return await readDescriptorChunk(fd, scratch, length);
|
|
63
|
-
});
|
|
90
|
+
}, () => regularFileSize(fd));
|
|
64
91
|
}
|
|
65
92
|
/** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
66
93
|
export function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
@@ -43,11 +43,11 @@ function assertDirectory(identity, pathname, label) {
|
|
|
43
43
|
}
|
|
44
44
|
async function createDirectoryReceipt(directoryPath, label) {
|
|
45
45
|
const resolvedPath = path.resolve(directoryPath);
|
|
46
|
-
const identity =
|
|
46
|
+
const identity = fsSync.lstatSync(resolvedPath);
|
|
47
47
|
assertDirectory(identity, resolvedPath, label);
|
|
48
48
|
return {
|
|
49
49
|
path: resolvedPath,
|
|
50
|
-
realPath:
|
|
50
|
+
realPath: fsSync.realpathSync.native(resolvedPath),
|
|
51
51
|
identity,
|
|
52
52
|
};
|
|
53
53
|
}
|
|
@@ -62,10 +62,10 @@ function createDirectoryReceiptSync(directoryPath, label) {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
async function assertDirectoryReceiptCurrent(receipt, label) {
|
|
65
|
-
const currentIdentity =
|
|
65
|
+
const currentIdentity = fsSync.lstatSync(receipt.path);
|
|
66
66
|
assertDirectory(currentIdentity, receipt.path, label);
|
|
67
67
|
if (!sameFileIdentity(receipt.identity, currentIdentity) ||
|
|
68
|
-
(
|
|
68
|
+
(fsSync.realpathSync.native(receipt.path)) !== receipt.realPath) {
|
|
69
69
|
throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -78,7 +78,7 @@ function assertDirectoryReceiptCurrentSync(receipt, label) {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
async function assertOpenDirectoryCurrent(handle, receipt, label) {
|
|
81
|
-
const openedIdentity =
|
|
81
|
+
const openedIdentity = fsSync.fstatSync(handle.fd);
|
|
82
82
|
assertDirectory(openedIdentity, receipt.path, label);
|
|
83
83
|
if (!sameFileIdentity(receipt.identity, openedIdentity)) {
|
|
84
84
|
throw new FsSafeError("path-mismatch", `${label} handle changed during directory sync: ${receipt.path}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AASlD,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,KAAK,GAAG,WAAW,GAAG,KAAK,IAAI;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC;AAE9E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC;AAC7H,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACnH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAStH,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU5F;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAMxE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAQxE;AAED,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACxH,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAqBzJ,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAcpB;AAGD,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAM7H"}
|
package/dist/directory-guard.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { FsSafeError } from "./errors.js";
|
|
5
4
|
import { sameFileIdentity } from "./file-identity.js";
|
|
@@ -7,20 +6,20 @@ import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
|
7
6
|
import { isNotFoundPathError } from "./path.js";
|
|
8
7
|
import { directoryComponentNotDirectoryError } from "./root-errors.js";
|
|
9
8
|
export async function createAsyncDirectoryGuard(dir, options) {
|
|
10
|
-
const stat = options?.bigint ? await inspectDirectoryIdentity(dir) :
|
|
9
|
+
const stat = options?.bigint ? await inspectDirectoryIdentity(dir) : fsSync.lstatSync(dir);
|
|
11
10
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
12
11
|
throw directoryComponentNotDirectoryError();
|
|
13
12
|
}
|
|
14
|
-
return { dir, realPath:
|
|
13
|
+
return { dir, realPath: fsSync.realpathSync.native(dir), stat };
|
|
15
14
|
}
|
|
16
15
|
export async function assertAsyncDirectoryGuard(guard) {
|
|
17
16
|
const stat = typeof guard.stat.dev === "bigint" && typeof guard.stat.ino === "bigint"
|
|
18
17
|
? await inspectDirectoryIdentity(guard.dir, { dev: guard.stat.dev, ino: guard.stat.ino })
|
|
19
|
-
:
|
|
18
|
+
: fsSync.lstatSync(guard.dir);
|
|
20
19
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
21
20
|
throw directoryComponentNotDirectoryError();
|
|
22
21
|
}
|
|
23
|
-
if (!sameFileIdentity(stat, guard.stat) ||
|
|
22
|
+
if (!sameFileIdentity(stat, guard.stat) || fsSync.realpathSync.native(guard.dir) !== guard.realPath) {
|
|
24
23
|
throw new FsSafeError("path-mismatch", "directory changed during operation");
|
|
25
24
|
}
|
|
26
25
|
}
|
|
@@ -75,7 +74,7 @@ export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
|
|
|
75
74
|
// Recovery receipts must retain every identity bit, including on Windows.
|
|
76
75
|
export async function inspectDirectoryIdentity(dir, expected) {
|
|
77
76
|
return await inspectFileIdentity(async () => {
|
|
78
|
-
const stat =
|
|
77
|
+
const stat = fsSync.lstatSync(dir, { bigint: true });
|
|
79
78
|
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
80
79
|
throw directoryComponentNotDirectoryError();
|
|
81
80
|
return stat;
|
|
@@ -49,7 +49,7 @@ async function chmodDirectoryInRootBestEffort(scopedRoot, relativePath, mode) {
|
|
|
49
49
|
: 0;
|
|
50
50
|
const handle = await fs.open(dirPath, syncFs.constants.O_RDONLY | directoryFlag | noFollowFlag);
|
|
51
51
|
try {
|
|
52
|
-
const stat =
|
|
52
|
+
const stat = syncFs.fstatSync(handle.fd);
|
|
53
53
|
if (!stat.isDirectory()) {
|
|
54
54
|
return;
|
|
55
55
|
}
|
package/dist/file-store-prune.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fsSync, {} from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { FsSafeError } from "./errors.js";
|
|
@@ -10,29 +11,29 @@ export async function pruneExpiredStoreEntries(params) {
|
|
|
10
11
|
const maxDepth = params.options.maxDepth;
|
|
11
12
|
const pruneEmptyDirs = (recursive || maxDepth !== undefined) && (params.options.pruneEmptyDirs ?? false);
|
|
12
13
|
await fs.mkdir(params.rootDir, { recursive: true, mode: params.dirMode });
|
|
13
|
-
const rootReal =
|
|
14
|
+
const rootReal = fsSync.realpathSync.native(params.rootDir);
|
|
14
15
|
const scopedRoot = await root(rootReal);
|
|
15
16
|
const rootGuard = {
|
|
16
17
|
dir: rootReal,
|
|
17
18
|
realPath: rootReal,
|
|
18
|
-
stat:
|
|
19
|
+
stat: fsSync.lstatSync(rootReal),
|
|
19
20
|
};
|
|
20
21
|
async function assertRootGuard() {
|
|
21
|
-
const stat =
|
|
22
|
+
const stat = fsSync.lstatSync(rootGuard.dir);
|
|
22
23
|
if (stat.isSymbolicLink() ||
|
|
23
24
|
!stat.isDirectory() ||
|
|
24
25
|
stat.dev !== rootGuard.stat.dev ||
|
|
25
26
|
stat.ino !== rootGuard.stat.ino ||
|
|
26
|
-
|
|
27
|
+
fsSync.realpathSync.native(rootGuard.dir) !== rootGuard.realPath) {
|
|
27
28
|
throw new FsSafeError("path-mismatch", "store root changed during prune");
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
async function readStableDirectory(dir) {
|
|
31
|
-
const before =
|
|
32
|
+
const before = observeOrNull(() => fsSync.lstatSync(dir));
|
|
32
33
|
if (!before || before.isSymbolicLink() || !before.isDirectory()) {
|
|
33
34
|
return null;
|
|
34
35
|
}
|
|
35
|
-
const real =
|
|
36
|
+
const real = observeOrNull(() => fsSync.realpathSync.native(dir));
|
|
36
37
|
if (!real || !isPathInside(rootReal, real)) {
|
|
37
38
|
return null;
|
|
38
39
|
}
|
|
@@ -40,7 +41,7 @@ export async function pruneExpiredStoreEntries(params) {
|
|
|
40
41
|
if (!entries) {
|
|
41
42
|
return null;
|
|
42
43
|
}
|
|
43
|
-
const after =
|
|
44
|
+
const after = observeOrNull(() => fsSync.lstatSync(dir));
|
|
44
45
|
if (!after || before.dev !== after.dev || before.ino !== after.ino) {
|
|
45
46
|
return null;
|
|
46
47
|
}
|
|
@@ -54,7 +55,7 @@ export async function pruneExpiredStoreEntries(params) {
|
|
|
54
55
|
for (const entry of entries) {
|
|
55
56
|
const fullPath = path.join(dir, entry.name);
|
|
56
57
|
const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
57
|
-
const stat =
|
|
58
|
+
const stat = observeOrNull(() => fsSync.lstatSync(fullPath));
|
|
58
59
|
if (!stat || stat.isSymbolicLink()) {
|
|
59
60
|
continue;
|
|
60
61
|
}
|
|
@@ -84,3 +85,11 @@ export async function pruneExpiredStoreEntries(params) {
|
|
|
84
85
|
}
|
|
85
86
|
await pruneDir(rootReal, "", 0);
|
|
86
87
|
}
|
|
88
|
+
function observeOrNull(observe) {
|
|
89
|
+
try {
|
|
90
|
+
return observe();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
package/dist/file-store.js
CHANGED
|
@@ -66,7 +66,7 @@ function handleSyncStoreReadOpenFailure(opened) {
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
async function readFileStoreCopySource(params) {
|
|
69
|
-
const sourceStat =
|
|
69
|
+
const sourceStat = syncFs.lstatSync(params.sourcePath);
|
|
70
70
|
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
|
|
71
71
|
throw new FsSafeError("not-file", "source path is not a file");
|
|
72
72
|
}
|
|
@@ -93,7 +93,7 @@ async function readFileStoreCopySource(params) {
|
|
|
93
93
|
async function copyIntoRoot(params) {
|
|
94
94
|
const relativePath = assertRelativePath(params.relativePath);
|
|
95
95
|
const destination = resolveStorePath(params.rootDir, relativePath);
|
|
96
|
-
const sourceStat =
|
|
96
|
+
const sourceStat = syncFs.lstatSync(params.sourcePath);
|
|
97
97
|
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
|
|
98
98
|
throw new FsSafeError("not-file", "source path is not a file");
|
|
99
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guarded-mkdir.d.ts","sourceRoot":"","sources":["../src/guarded-mkdir.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"guarded-mkdir.d.ts","sourceRoot":"","sources":["../src/guarded-mkdir.ts"],"names":[],"mappings":"AA0BA;;;;;GAKG;AACH,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,CAuDlB"}
|
package/dist/guarded-mkdir.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
@@ -10,7 +11,7 @@ function isSameOrChildPath(candidate, parent) {
|
|
|
10
11
|
}
|
|
11
12
|
async function realpathOrThrowNotFile(target) {
|
|
12
13
|
try {
|
|
13
|
-
return path.resolve(
|
|
14
|
+
return path.resolve(fsSync.realpathSync.native(target));
|
|
14
15
|
}
|
|
15
16
|
catch (error) {
|
|
16
17
|
if (isNotFoundPathError(error)) {
|
|
@@ -29,7 +30,7 @@ async function realpathOrThrowNotFile(target) {
|
|
|
29
30
|
*/
|
|
30
31
|
export async function mkdirPathComponentsWithGuards(params) {
|
|
31
32
|
const root = path.resolve(params.rootReal);
|
|
32
|
-
const rootCanonical = path.resolve(
|
|
33
|
+
const rootCanonical = path.resolve(fsSync.realpathSync.native(root));
|
|
33
34
|
const target = path.resolve(params.targetPath);
|
|
34
35
|
const relative = path.relative(root, target);
|
|
35
36
|
if (isPathRelativeEscape(relative)) {
|
|
@@ -49,7 +50,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
49
50
|
throw error;
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
|
-
const stat =
|
|
53
|
+
const stat = fsSync.lstatSync(next);
|
|
53
54
|
if ((params.rejectSymlinks && stat.isSymbolicLink()) || (!stat.isSymbolicLink() && !stat.isDirectory())) {
|
|
54
55
|
throw directoryComponentNotDirectoryError();
|
|
55
56
|
}
|
|
@@ -69,7 +70,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
69
70
|
// every subsequent segment. Callers that need the final directory
|
|
70
71
|
// (e.g. to guard it themselves after this function returns) must use
|
|
71
72
|
// the returned resolved path, not their own lexical parent path.
|
|
72
|
-
const targetStat =
|
|
73
|
+
const targetStat = fsSync.statSync(nextReal);
|
|
73
74
|
if (!targetStat.isDirectory()) {
|
|
74
75
|
throw directoryComponentNotDirectoryError();
|
|
75
76
|
}
|
package/dist/native-binding.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TarMeterLimits } from "./archive-limits.js";
|
|
2
|
+
import type { ArchiveMemberKind } from "./archive-plan.js";
|
|
2
3
|
export interface NativeFileHash {
|
|
3
4
|
bytes: number;
|
|
4
5
|
digest: string;
|
|
@@ -16,7 +17,7 @@ export interface NativeFileIdentity {
|
|
|
16
17
|
export interface NativeArchiveEntry {
|
|
17
18
|
index: number;
|
|
18
19
|
path: string;
|
|
19
|
-
kind:
|
|
20
|
+
kind: ArchiveMemberKind;
|
|
20
21
|
size: number;
|
|
21
22
|
mode: number;
|
|
22
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,7 +35,8 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
35
35
|
// reopenable until the published name has been identity-fenced.
|
|
36
36
|
fsSync.fchmodSync(tempFd, 0o600);
|
|
37
37
|
await writeNativeInput(tempFd, params.input, params.maxBytes);
|
|
38
|
-
|
|
38
|
+
if (params.sync !== false)
|
|
39
|
+
syncFileBestEffortSync(tempFd);
|
|
39
40
|
if (params.overwrite === false) {
|
|
40
41
|
binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
|
|
41
42
|
}
|
|
@@ -54,7 +55,8 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
54
55
|
// verifiable and so broader modes are never exposed before that fence.
|
|
55
56
|
try {
|
|
56
57
|
fsSync.fchmodSync(targetFd, params.mode);
|
|
57
|
-
|
|
58
|
+
if (params.sync !== false)
|
|
59
|
+
syncFileBestEffortSync(targetFd);
|
|
58
60
|
}
|
|
59
61
|
catch (error) {
|
|
60
62
|
closeWriteFd(targetFd);
|
|
@@ -67,7 +69,8 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
67
69
|
});
|
|
68
70
|
throw error;
|
|
69
71
|
}
|
|
70
|
-
|
|
72
|
+
if (params.sync !== false)
|
|
73
|
+
syncFileBestEffortSync(parentFd);
|
|
71
74
|
// Verification follows publication and final chmod, outside rollback handling.
|
|
72
75
|
await params.verifyPublished?.(targetFd, verificationIdentity, parentGuard);
|
|
73
76
|
completed = true;
|
|
@@ -103,13 +103,13 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
103
103
|
binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
|
|
104
104
|
}
|
|
105
105
|
parentFd = binding.openBeneath(root.fd, params.relativeParentPath, directoryFlags).fd;
|
|
106
|
-
const parentPath =
|
|
106
|
+
const parentPath = fsSync.realpathSync.native(params.relativeParentPath
|
|
107
107
|
? path.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
108
108
|
: params.rootPath);
|
|
109
109
|
const directory = windows ? undefined : describeStagedDirectory(parentFd, parentPath);
|
|
110
110
|
const parentPathStat = exactRoot
|
|
111
111
|
? await inspectDirectoryIdentity(parentPath, inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true })))
|
|
112
|
-
:
|
|
112
|
+
: fsSync.lstatSync(parentPath);
|
|
113
113
|
if (windows && !exactRoot) {
|
|
114
114
|
const parentIdentity = binding.fstatIdentity(parentFd);
|
|
115
115
|
if (parentPathStat.isSymbolicLink() || !sameNativeIdentity(parentPathStat, parentIdentity)) {
|
|
@@ -122,7 +122,7 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
122
122
|
const verificationGuard = { dir: parentPath, realPath: parentPath, stat: parentPathStat };
|
|
123
123
|
if (params.overwrite === false) {
|
|
124
124
|
try {
|
|
125
|
-
|
|
125
|
+
fsSync.lstatSync(path.join(parentPath, params.basename));
|
|
126
126
|
throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
|
|
127
127
|
}
|
|
128
128
|
catch (error) {
|
|
@@ -8,8 +8,8 @@ export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding,
|
|
|
8
8
|
export declare function assertNativeStaging(binding: NativeBinding): asserts binding is NativeStagingBinding;
|
|
9
9
|
declare class NativeStagedFile implements StagedFile {
|
|
10
10
|
#private;
|
|
11
|
-
constructor(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number);
|
|
12
|
-
static create(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean): Promise<NativeStagedFile>;
|
|
11
|
+
constructor(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number, sync: boolean);
|
|
12
|
+
static create(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean, sync?: boolean): Promise<NativeStagedFile>;
|
|
13
13
|
static write(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], params: PinnedWriteParams, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
|
|
14
14
|
get receipt(): StagedFileReceipt;
|
|
15
15
|
assertCurrent(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAKlE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,wBAAwB,EAGxB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,QAAQ,CAAC,IAAI,CAC9D,aAAa,EACb,kBAAkB,GAAG,mBAAmB,GAAG,kBAAkB,CAC9D,CAAC,CAAC;AAEH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,IAAI,oBAAoB,CAUnG;AA6BD,cAAM,gBAAiB,YAAW,UAAU;;
|
|
1
|
+
{"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAKlE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,wBAAwB,EAGxB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,QAAQ,CAAC,IAAI,CAC9D,aAAa,EACb,kBAAkB,GAAG,mBAAmB,GAAG,kBAAkB,CAC9D,CAAC,CAAC;AAEH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,IAAI,oBAAoB,CAUnG;AA6BD,cAAM,gBAAiB,YAAW,UAAU;;IAW1C,YACE,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,aAAa,EAAE,OAAO,EACtB,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,OAAO,EAQd;IAGD,OAAa,MAAM,CACjB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,EAEjB,aAAa,UAAO,EACpB,IAAI,UAAO,GACV,OAAO,CAAC,gBAAgB,CAAC,CAc3B;IAED,OAAa,KAAK,CAChB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,sBAAsB,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAU3B;IAED,IAAI,OAAO,IAAI,iBAAiB,CAK/B;IAoFK,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnC;IAEK,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAqD9F;IA6CK,OAAO,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAMjD;IAEK,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAO3C;CACF;AAED,eAAO,MAAM,iBAAiB,EAAE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,UAAU,CAClF,CAAC;AAC1B,eAAO,MAAM,gBAAgB,+BAAyB,CAAC;AAEvD,YAAY,EACV,oBAAoB,EAAE,UAAU,EAAE,wBAAwB,EAAE,wBAAwB,EACpF,qBAAqB,EAAE,iBAAiB,GACzC,MAAM,wBAAwB,CAAC;AAEhC,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACrC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,UAAU,CAAC,CAUtB"}
|
|
@@ -92,23 +92,25 @@ class NativeStagedFile {
|
|
|
92
92
|
#directory;
|
|
93
93
|
#portableNames;
|
|
94
94
|
#publishedMode;
|
|
95
|
+
#sync;
|
|
95
96
|
#name = `.fs-safe-${randomUUID()}.tmp`;
|
|
96
97
|
#state = { status: "open", publication: NOT_PUBLISHED };
|
|
97
98
|
#receipt;
|
|
98
|
-
constructor(binding, parentFd, directory, portableNames, publishedMode) {
|
|
99
|
+
constructor(binding, parentFd, directory, portableNames, publishedMode, sync) {
|
|
99
100
|
this.#binding = binding;
|
|
100
101
|
this.#parentFd = parentFd;
|
|
101
102
|
this.#directory = directory;
|
|
102
103
|
this.#portableNames = portableNames;
|
|
103
104
|
this.#publishedMode = publishedMode;
|
|
105
|
+
this.#sync = sync;
|
|
104
106
|
}
|
|
105
107
|
// Takes ownership of parentFd, including all construction and preparation failures.
|
|
106
108
|
static async create(binding, parentFd, directory, input, mode, maxBytes,
|
|
107
109
|
// Public staging uses portable names; existing POSIX writes accept literal names.
|
|
108
|
-
portableNames = true) {
|
|
110
|
+
portableNames = true, sync = true) {
|
|
109
111
|
let staged;
|
|
110
112
|
try {
|
|
111
|
-
staged = new NativeStagedFile(binding, parentFd, directory, portableNames, mode);
|
|
113
|
+
staged = new NativeStagedFile(binding, parentFd, directory, portableNames, mode, sync);
|
|
112
114
|
}
|
|
113
115
|
catch (error) {
|
|
114
116
|
try {
|
|
@@ -127,7 +129,7 @@ class NativeStagedFile {
|
|
|
127
129
|
try {
|
|
128
130
|
// This owner never escapes. Only the internal verifier borrows its fd;
|
|
129
131
|
// public descriptor methods remain await-free and cannot race disposal.
|
|
130
|
-
const staged = __addDisposableResource(env_1, await NativeStagedFile.create(binding, parentFd, directory, params.input, params.mode, params.maxBytes, false), true);
|
|
132
|
+
const staged = __addDisposableResource(env_1, await NativeStagedFile.create(binding, parentFd, directory, params.input, params.mode, params.maxBytes, false, params.sync), true);
|
|
131
133
|
const published = await staged.publish(params.basename, { overwrite: params.overwrite !== false });
|
|
132
134
|
const identity = published.staged.identity;
|
|
133
135
|
await params.verifyPublished?.(staged.#file(), identity, parentGuard);
|
|
@@ -192,7 +194,8 @@ class NativeStagedFile {
|
|
|
192
194
|
const fd = state.fileFd;
|
|
193
195
|
fs.fchmodSync(fd, 0o600);
|
|
194
196
|
await writeNativeInput(fd, input, maxBytes);
|
|
195
|
-
|
|
197
|
+
if (this.#sync)
|
|
198
|
+
syncFileBestEffortSync(fd);
|
|
196
199
|
const stat = fs.fstatSync(fd, { bigint: true });
|
|
197
200
|
this.#receipt = Object.freeze({
|
|
198
201
|
directory: this.#directory,
|
|
@@ -266,13 +269,14 @@ class NativeStagedFile {
|
|
|
266
269
|
const fd = this.#file();
|
|
267
270
|
if (this.#publishedMode !== 0o600 || stagedMode !== 0o600)
|
|
268
271
|
fs.fchmodSync(fd, this.#publishedMode);
|
|
269
|
-
//
|
|
272
|
+
// With sync enabled, content was synced before rename. A lost chmod leaves staged 0600,
|
|
270
273
|
// no wider than modes retaining owner rw. Restrictive modes and observed
|
|
271
274
|
// widening of the stage still need the permission correction made durable.
|
|
272
|
-
if ((this.#publishedMode & 0o600) !== 0o600 || (stagedMode & ~this.#publishedMode) !== 0) {
|
|
275
|
+
if (this.#sync && ((this.#publishedMode & 0o600) !== 0o600 || (stagedMode & ~this.#publishedMode) !== 0)) {
|
|
273
276
|
syncFileBestEffortSync(fd);
|
|
274
277
|
}
|
|
275
|
-
|
|
278
|
+
if (this.#sync)
|
|
279
|
+
syncFileBestEffortSync(this.#parentFd);
|
|
276
280
|
this.#assertNamed(basename);
|
|
277
281
|
assertStagedDirectoryCurrent(this.#directory);
|
|
278
282
|
return receipt;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACxC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC;AACpD,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,WAAW,CAAA;CAAE,CAAC,CAAC"}
|
package/dist/opened-realpath.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { openedPathResolutionError } from "./opened-file-failure.js";
|
|
4
5
|
import { sameFileIdentity } from "./file-identity.js";
|
|
5
6
|
import { isNotFoundPathError } from "./path.js";
|
|
6
7
|
export async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
|
|
7
|
-
const handleStat =
|
|
8
|
+
const handleStat = fsSync.fstatSync(handle.fd);
|
|
8
9
|
return (await resolveOpenedFileRealPathForFd(handle.fd, handleStat, ioPath)).realPath;
|
|
9
10
|
}
|
|
10
11
|
export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
|
|
@@ -15,8 +16,8 @@ export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
|
|
|
15
16
|
: [];
|
|
16
17
|
for (const fdPath of fdCandidates) {
|
|
17
18
|
try {
|
|
18
|
-
const fdRealPath =
|
|
19
|
-
const fdRealStat = statOptions ?
|
|
19
|
+
const fdRealPath = fsSync.realpathSync.native(fdPath);
|
|
20
|
+
const fdRealStat = statOptions ? fsSync.statSync(fdRealPath, statOptions) : fsSync.statSync(fdRealPath);
|
|
20
21
|
if (sameFileIdentity(handleStat, fdRealStat)) {
|
|
21
22
|
return { realPath: fdRealPath, stat: fdRealStat };
|
|
22
23
|
}
|
|
@@ -26,8 +27,8 @@ export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
try {
|
|
29
|
-
const ioRealPath =
|
|
30
|
-
const ioRealStat = statOptions ?
|
|
30
|
+
const ioRealPath = fsSync.realpathSync.native(ioPath);
|
|
31
|
+
const ioRealStat = statOptions ? fsSync.statSync(ioRealPath, statOptions) : fsSync.statSync(ioRealPath);
|
|
31
32
|
if (sameFileIdentity(handleStat, ioRealStat)) {
|
|
32
33
|
return { realPath: ioRealPath, stat: ioRealStat };
|
|
33
34
|
}
|
|
@@ -52,7 +53,7 @@ export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
|
|
|
52
53
|
async function resolveOpenedFileRealPathFromParent(handleStat, ioPath, statOptions) {
|
|
53
54
|
let parentReal;
|
|
54
55
|
try {
|
|
55
|
-
parentReal =
|
|
56
|
+
parentReal = fsSync.realpathSync.native(path.dirname(ioPath));
|
|
56
57
|
}
|
|
57
58
|
catch (err) {
|
|
58
59
|
if (isNotFoundPathError(err)) {
|
|
@@ -73,10 +74,10 @@ async function resolveOpenedFileRealPathFromParent(handleStat, ioPath, statOptio
|
|
|
73
74
|
for (const entry of entries.toSorted()) {
|
|
74
75
|
const candidatePath = path.join(parentReal, entry);
|
|
75
76
|
try {
|
|
76
|
-
const candidateStat = statOptions ?
|
|
77
|
+
const candidateStat = statOptions ? fsSync.lstatSync(candidatePath, statOptions) : fsSync.lstatSync(candidatePath);
|
|
77
78
|
if (candidateStat.isFile() && sameFileIdentity(handleStat, candidateStat)) {
|
|
78
|
-
const realPath =
|
|
79
|
-
const stat = statOptions ?
|
|
79
|
+
const realPath = fsSync.realpathSync.native(candidatePath);
|
|
80
|
+
const stat = statOptions ? fsSync.statSync(realPath, statOptions) : fsSync.statSync(realPath);
|
|
80
81
|
if (sameFileIdentity(handleStat, stat))
|
|
81
82
|
return { realPath, stat };
|
|
82
83
|
}
|
package/dist/path-policy.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import fs from "node:fs
|
|
1
|
+
import fs from "node:fs";
|
|
2
2
|
import { ROOT_PATH_ALIAS_POLICIES, resolveRootPath, } from "./root-path.js";
|
|
3
3
|
import { isNotFoundPathError } from "./path.js";
|
|
4
4
|
import { shortPath } from "./error-detail.js";
|
|
@@ -27,7 +27,7 @@ export async function assertNoHardlinkedFinalPath(params) {
|
|
|
27
27
|
}
|
|
28
28
|
let stat;
|
|
29
29
|
try {
|
|
30
|
-
stat =
|
|
30
|
+
stat = fs.statSync(params.filePath);
|
|
31
31
|
}
|
|
32
32
|
catch (err) {
|
|
33
33
|
if (isNotFoundPathError(err)) {
|
package/dist/pinned-write.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAA4F,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG7J,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAY3D,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAsDzC,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAE5C,eAAe,CAAC,EAAE,CAChB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,sBAAsB,EAChC,WAAW,EAAE,sBAAsB,KAChC,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgB/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
|
|
1
|
+
{"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAA4F,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG7J,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAY3D,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAsDzC,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAE5C,eAAe,CAAC,EAAE,CAChB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,sBAAsB,EAChC,WAAW,EAAE,sBAAsB,KAChC,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgB/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
|