@openclaw/fs-safe 0.8.2 → 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 +19 -0
- package/README.md +1 -1
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +8 -7
- package/dist/advanced.d.ts +2 -2
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -2
- package/dist/archive-kind.d.ts +1 -0
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +6 -0
- package/dist/archive-limits.d.ts +0 -1
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +0 -3
- package/dist/archive-native.d.ts +1 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +20 -76
- package/dist/archive-parser-errors.d.ts +2 -0
- package/dist/archive-parser-errors.d.ts.map +1 -0
- package/dist/archive-parser-errors.js +12 -0
- 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-read.d.ts.map +1 -1
- package/dist/archive-read.js +7 -13
- 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-wasm.d.ts +1 -1
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +5 -8
- 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 +54 -33
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/atomic.js +1 -1
- package/dist/bounded-read-stream.d.ts +1 -5
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +5 -13
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +31 -4
- package/dist/byte-budget.d.ts +0 -1
- package/dist/byte-budget.d.ts.map +1 -1
- package/dist/byte-budget.js +1 -1
- 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/directory-mode-owner.d.ts.map +1 -1
- package/dist/directory-mode-owner.js +2 -0
- package/dist/error-detail.d.ts +1 -0
- package/dist/error-detail.d.ts.map +1 -1
- package/dist/error-detail.js +6 -0
- package/dist/file-store-boundary.d.ts +1 -1
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -14
- package/dist/file-store-prune.js +17 -8
- package/dist/file-store.js +3 -3
- package/dist/file-sync.d.ts +5 -0
- package/dist/file-sync.d.ts.map +1 -0
- package/dist/file-sync.js +19 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +5 -4
- package/dist/guarded-mutation.d.ts +3 -0
- package/dist/guarded-mutation.d.ts.map +1 -1
- package/dist/guarded-mutation.js +5 -1
- package/dist/home-dir.d.ts +0 -10
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +0 -28
- package/dist/json-durable-queue-ownership.d.ts +1 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +139 -18
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +5 -10
- package/dist/json-store.d.ts.map +1 -1
- package/dist/json-store.js +1 -1
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +2 -3
- package/dist/move-path-cleanup.d.ts +1 -1
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +4 -2
- package/dist/move-path.d.ts +9 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +53 -16
- package/dist/native-binding.d.ts +2 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +0 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -10
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +8 -4
- package/dist/native-pinned-write.js +3 -3
- package/dist/native-staged-file.d.ts +10 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +38 -13
- package/dist/opened-realpath.d.ts +12 -1
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +16 -14
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +26 -2
- package/dist/path-policy.js +3 -3
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +3 -0
- package/dist/pinned-write.d.ts +2 -2
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +17 -22
- package/dist/private-temp-workspace.js +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/regular-file.js +8 -8
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +2 -7
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +11 -20
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +15 -14
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +21 -8
- package/dist/root-context.js +5 -5
- package/dist/root-errors.d.ts +1 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +9 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +17 -28
- package/dist/root-impl.d.ts +2 -1
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +46 -58
- 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 +51 -97
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +16 -15
- package/dist/root-write-mode.d.ts +6 -0
- package/dist/root-write-mode.d.ts.map +1 -0
- package/dist/root-write-mode.js +61 -0
- package/dist/root-write-verification.js +8 -8
- package/dist/safe-path-segment.d.ts +1 -0
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +4 -11
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +2 -10
- package/dist/secret-read-policy.d.ts +4 -0
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +12 -0
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +2 -7
- package/dist/sidecar-lock-policy.d.ts +0 -1
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +0 -4
- package/dist/strict-file-identity.d.ts +1 -1
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/string-coerce.d.ts +0 -9
- package/dist/string-coerce.d.ts.map +1 -1
- package/dist/string-coerce.js +0 -56
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +1 -2
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +1 -12
- package/docs/archive.md +57 -0
- package/docs/atomic.md +53 -19
- package/docs/reading.md +2 -0
- package/docs/root.md +10 -1
- package/docs/staged-file.md +6 -2
- package/docs/types.md +2 -1
- package/docs/writing.md +30 -2
- package/package.json +8 -8
- package/dist/archive-tar-extract.d.ts +0 -11
- package/dist/archive-tar-extract.d.ts.map +0 -1
- package/dist/archive-tar-extract.js +0 -49
- package/dist/fsync.d.ts +0 -2
- package/dist/fsync.d.ts.map +0 -1
- package/dist/fsync.js +0 -1
- package/dist/json-durable-queue-retirement.d.ts +0 -9
- package/dist/json-durable-queue-retirement.d.ts.map +0 -1
- package/dist/json-durable-queue-retirement.js +0 -126
- package/dist/json-durable-queue-transfer-lock.d.ts +0 -2
- package/dist/json-durable-queue-transfer-lock.d.ts.map +0 -1
- package/dist/json-durable-queue-transfer-lock.js +0 -19
- package/dist/mode.d.ts +0 -2
- package/dist/mode.d.ts.map +0 -1
- package/dist/mode.js +0 -3
- package/dist/output-sibling.d.ts +0 -8
- package/dist/output-sibling.d.ts.map +0 -1
- package/dist/output-sibling.js +0 -30
- package/dist/read-error.d.ts +0 -2
- package/dist/read-error.d.ts.map +0 -1
- package/dist/read-error.js +0 -11
- package/dist/short-path.d.ts +0 -2
- package/dist/short-path.d.ts.map +0 -1
- package/dist/short-path.js +0 -7
- package/dist/staged-file.d.ts +0 -10
- package/dist/staged-file.d.ts.map +0 -1
- package/dist/staged-file.js +0 -15
package/dist/archive-tar-wasm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { classifyArchiveParserError } from "./archive-parser-errors.js";
|
|
1
2
|
import { readFileSync } from "node:fs";
|
|
2
3
|
import { Transform } from "node:stream";
|
|
3
|
-
import { ArchiveFormatError
|
|
4
|
-
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError } from "./archive-limits.js";
|
|
4
|
+
import { ArchiveFormatError } from "./archive-errors.js";
|
|
5
5
|
// Node exposes WebAssembly without DOM globals; keep the private ABI types local.
|
|
6
6
|
const wasm = globalThis.WebAssembly;
|
|
7
7
|
let compiled;
|
|
@@ -18,12 +18,9 @@ const types = new Map([
|
|
|
18
18
|
[54, "FIFO"], [55, "ContiguousFile"], [68, "GNUDumpDir"],
|
|
19
19
|
]);
|
|
20
20
|
function parserError(message) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (message.includes(code))
|
|
25
|
-
return new ArchiveLimitError(code);
|
|
26
|
-
}
|
|
21
|
+
const mapped = classifyArchiveParserError(message);
|
|
22
|
+
if (mapped)
|
|
23
|
+
return mapped;
|
|
27
24
|
return new ArchiveFormatError(message.replace(/^archive-header-invalid:/, "invalid TAR header:"));
|
|
28
25
|
}
|
|
29
26
|
/** Backpressure-aware transport only; all TAR semantics live in the Rust core. */
|
package/dist/archive-tar.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ArchiveExtractLimits } from "./archive-limits.js";
|
|
2
|
+
import { type ArchivePlanEntry, type ArchivePlanOptions } from "./archive-plan.js";
|
|
2
3
|
import { type ArchiveEntryFilter, type ArchiveFilteredEntryPolicy } from "./archive-policy.js";
|
|
3
4
|
export type TarEntryInfo = {
|
|
4
5
|
path: string;
|
|
@@ -6,6 +7,7 @@ export type TarEntryInfo = {
|
|
|
6
7
|
size: number;
|
|
7
8
|
mode?: number;
|
|
8
9
|
};
|
|
10
|
+
export declare function createTarEntryPlanner(params: ArchivePlanOptions): (entry: TarEntryInfo) => ArchivePlanEntry | null;
|
|
9
11
|
export declare function createTarEntryPreflightChecker(params: {
|
|
10
12
|
rootDir: string;
|
|
11
13
|
stripComponents?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-tar.d.ts","sourceRoot":"","sources":["../src/archive-tar.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-tar.d.ts","sourceRoot":"","sources":["../src/archive-tar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AASvF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,kBAAkB,GAC9D,CAAC,KAAK,EAAE,YAAY,KAAK,gBAAgB,GAAG,IAAI,CAQjD;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,UAAU,CAAC,EAAE,0BAA0B,CAAC;CACzC,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAGnC"}
|
package/dist/archive-tar.js
CHANGED
|
@@ -1,58 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { archiveEntryKindFromTarType, shouldExtractArchiveEntry, } from "./archive-policy.js";
|
|
4
|
-
import { ArchiveSecurityError } from "./archive-errors.js";
|
|
5
|
-
import { formatErrorDetail } from "./error-detail.js";
|
|
1
|
+
import { createArchiveEntryPlanner, } from "./archive-plan.js";
|
|
2
|
+
import { archiveEntryKindFromTarType, } from "./archive-policy.js";
|
|
6
3
|
const BLOCKED_TAR_ENTRY_TYPES = new Set([
|
|
7
|
-
"SymbolicLink",
|
|
8
|
-
"Link",
|
|
9
4
|
"BlockDevice",
|
|
10
5
|
"CharacterDevice",
|
|
11
6
|
"FIFO",
|
|
12
7
|
"Socket",
|
|
13
8
|
]);
|
|
14
|
-
export function
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
let entryCount = 0;
|
|
18
|
-
const budget = createByteBudgetTracker(limits);
|
|
19
|
-
const trackOutputPath = createArchiveOutputPathTracker();
|
|
9
|
+
export function createTarEntryPlanner(params) {
|
|
10
|
+
// Preserve inherited/getter-backed public options and per-entry policy reads.
|
|
11
|
+
const plan = createArchiveEntryPlanner(params, "tar");
|
|
20
12
|
return (entry) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const canonicalPath = stripArchivePath(entry.path, 0);
|
|
25
|
-
if (!canonicalPath)
|
|
26
|
-
return false;
|
|
27
|
-
const relPath = stripArchivePath(canonicalPath, strip);
|
|
28
|
-
if (!relPath) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
validateArchiveEntryPath(relPath, { escapeLabel: params.escapeLabel });
|
|
32
|
-
assertArchiveEntryPathComponentsWithinLimit(relPath, limits);
|
|
33
|
-
trackOutputPath(relPath, entry.path);
|
|
34
|
-
resolveArchiveOutputPath({
|
|
35
|
-
rootDir: params.rootDir,
|
|
36
|
-
relPath,
|
|
37
|
-
originalPath: entry.path,
|
|
38
|
-
escapeLabel: params.escapeLabel,
|
|
39
|
-
});
|
|
40
|
-
const kind = archiveEntryKindFromTarType(entry.type);
|
|
41
|
-
if (!shouldExtractArchiveEntry({
|
|
42
|
-
filter: params.entryFilter,
|
|
43
|
-
onFiltered: params.onFiltered,
|
|
44
|
-
entry: { path: canonicalPath, kind, size: entry.size },
|
|
45
|
-
})) {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
if (BLOCKED_TAR_ENTRY_TYPES.has(entry.type)) {
|
|
49
|
-
throw new ArchiveSecurityError("entry-link", `tar entry is a link: ${formatErrorDetail(entry.path)}`);
|
|
50
|
-
}
|
|
51
|
-
// Accepted unsupported records remain omitted, as in the native plan.
|
|
52
|
-
if (kind === "other")
|
|
53
|
-
return false;
|
|
54
|
-
budget.startEntry();
|
|
55
|
-
budget.addEntrySize(entry.size);
|
|
56
|
-
return true;
|
|
13
|
+
const kind = BLOCKED_TAR_ENTRY_TYPES.has(entry.type) ? "blocked" : archiveEntryKindFromTarType(entry.type);
|
|
14
|
+
// The public checker accepts structural entry objects, including class getters.
|
|
15
|
+
return plan({ path: entry.path, kind, size: entry.size, mode: entry.mode });
|
|
57
16
|
};
|
|
58
17
|
}
|
|
18
|
+
export function createTarEntryPreflightChecker(params) {
|
|
19
|
+
const plan = createTarEntryPlanner(params);
|
|
20
|
+
return (entry) => plan(entry) !== null;
|
|
21
|
+
}
|
package/dist/archive.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type { ArchiveEntryFilter, ArchiveEntryKind, ArchiveEntryModePolicy, Arch
|
|
|
4
4
|
export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
5
5
|
export { resolveArchiveKind, resolvePackedRootDir, type ArchiveKind } from "./archive-kind.js";
|
|
6
6
|
export { readArchiveEntry } from "./archive-read.js";
|
|
7
|
+
export { inspectTarArchive, type InspectTarArchiveOptions, type InspectedTarEntry } from "./archive-tar-inspect.js";
|
|
7
8
|
export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, type ArchiveExtractLimits, type ArchiveLimitErrorCode, } from "./archive-limits.js";
|
|
8
9
|
export { ArchiveFormatError, type ArchiveFormatErrorCode } from "./archive-errors.js";
|
|
9
10
|
export { ArchiveSecurityError, type ArchiveSecurityErrorCode } from "./archive-staging.js";
|
package/dist/archive.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AA6MpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CjF"}
|
package/dist/archive.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createTarEntryPlanner } from "./archive-tar.js";
|
|
2
|
+
import { inspectTar, replayTar } from "./archive-tar-stream.js";
|
|
3
|
+
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
4
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
5
|
import fs from "node:fs/promises";
|
|
3
6
|
import path from "node:path";
|
|
4
7
|
import { Readable } from "node:stream";
|
|
@@ -6,7 +9,7 @@ import { pipeline } from "node:stream/promises";
|
|
|
6
9
|
import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
7
10
|
import { createPipelineTimeoutError, waitForDeadline, withExtractionDeadline, } from "./archive-deadline.js";
|
|
8
11
|
import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, resolveTarMeterLimits, } from "./archive-limits.js";
|
|
9
|
-
import { resolveArchiveKind } from "./archive-kind.js";
|
|
12
|
+
import { assertPortableArchiveKind, resolveArchiveKind } from "./archive-kind.js";
|
|
10
13
|
import { prepareArchiveDestinationDir, preparePrivateArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
|
|
11
14
|
import { mergePlannedArchiveIntoDestination } from "./archive-merge.js";
|
|
12
15
|
import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
|
|
@@ -17,12 +20,12 @@ import { ArchiveSecurityError } from "./archive-errors.js";
|
|
|
17
20
|
import { extractNativeArchive } from "./archive-native.js";
|
|
18
21
|
import { stageArchiveFileForExtraction } from "./archive-input.js";
|
|
19
22
|
import { getNativeBinding } from "./native.js";
|
|
20
|
-
import {
|
|
21
|
-
import { extractWasmTar } from "./archive-tar-extract.js";
|
|
23
|
+
import { resolveArchiveFilteredEntryPolicy, shouldExtractArchiveEntry, } from "./archive-policy.js";
|
|
22
24
|
import { writeSiblingTempFile } from "./sibling-temp.js";
|
|
23
25
|
export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
24
26
|
export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
|
|
25
27
|
export { readArchiveEntry } from "./archive-read.js";
|
|
28
|
+
export { inspectTarArchive } from "./archive-tar-inspect.js";
|
|
26
29
|
export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, } from "./archive-limits.js";
|
|
27
30
|
export { ArchiveFormatError } from "./archive-errors.js";
|
|
28
31
|
export { ArchiveSecurityError } from "./archive-staging.js";
|
|
@@ -205,35 +208,27 @@ export async function extractArchive(params) {
|
|
|
205
208
|
const limits = resolveExtractLimits(params.limits);
|
|
206
209
|
const tarLimits = resolveTarMeterLimits(limits);
|
|
207
210
|
const native = getNativeBinding();
|
|
211
|
+
// Read the declared public fields before private adapters copy options: class
|
|
212
|
+
// getters and inherited policy must survive identically on every backend.
|
|
213
|
+
const options = {
|
|
214
|
+
archivePath: params.archivePath, destDir: params.destDir,
|
|
215
|
+
stripComponents: params.stripComponents, limits,
|
|
216
|
+
entryModes: params.entryModes, entryFilter: params.entryFilter, onFiltered,
|
|
217
|
+
};
|
|
208
218
|
if (native) {
|
|
209
|
-
await withExtractionDeadline(params.timeoutMs, label, async (deadline) => extractNativeArchive({
|
|
210
|
-
binding: native,
|
|
211
|
-
archivePath: params.archivePath,
|
|
212
|
-
destDir: params.destDir,
|
|
213
|
-
kind,
|
|
214
|
-
stripComponents: params.stripComponents,
|
|
215
|
-
limits,
|
|
216
|
-
tarLimits,
|
|
217
|
-
deadline,
|
|
218
|
-
entryModes: params.entryModes,
|
|
219
|
-
entryFilter: params.entryFilter,
|
|
220
|
-
onFiltered,
|
|
221
|
-
}));
|
|
219
|
+
await withExtractionDeadline(params.timeoutMs, label, async (deadline) => extractNativeArchive({ ...options, binding: native, kind, tarLimits, deadline }));
|
|
222
220
|
return;
|
|
223
221
|
}
|
|
224
|
-
|
|
225
|
-
throw new FsSafeError("helper-unavailable", `${kind} archives require the matching optional native platform package; ` +
|
|
226
|
-
"install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
|
|
227
|
-
}
|
|
222
|
+
assertPortableArchiveKind(kind);
|
|
228
223
|
if (kind === "tar") {
|
|
229
224
|
await withExtractionDeadline(params.timeoutMs, label, async (deadline) => {
|
|
230
225
|
const stagedArchive = await stageArchiveFileForExtraction({
|
|
231
|
-
archivePath:
|
|
226
|
+
archivePath: options.archivePath,
|
|
232
227
|
limits,
|
|
233
228
|
deadline,
|
|
234
229
|
});
|
|
235
230
|
try {
|
|
236
|
-
await extractWasmTar({ archivePath: stagedArchive.path, options
|
|
231
|
+
await extractWasmTar({ archivePath: stagedArchive.path, options, limits, tarLimits, deadline });
|
|
237
232
|
}
|
|
238
233
|
finally {
|
|
239
234
|
await stagedArchive.cleanup();
|
|
@@ -241,14 +236,40 @@ export async function extractArchive(params) {
|
|
|
241
236
|
});
|
|
242
237
|
return;
|
|
243
238
|
}
|
|
244
|
-
await withExtractionDeadline(params.timeoutMs, label, async (deadline) => extractZip({
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
239
|
+
await withExtractionDeadline(params.timeoutMs, label, async (deadline) => extractZip({ ...options, deadline }));
|
|
240
|
+
}
|
|
241
|
+
async function extractWasmTar(params) {
|
|
242
|
+
const { options, deadline, tarLimits } = params;
|
|
243
|
+
const manifest = [];
|
|
244
|
+
await inspectTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal,
|
|
245
|
+
onMember: (entry) => { manifest.push(entry); } });
|
|
246
|
+
deadline.check();
|
|
247
|
+
const destinationRealDir = await prepareArchiveDestinationDir(options.destDir);
|
|
248
|
+
await withStagedArchiveDestination({ destinationRealDir, run: async (stagingPath) => {
|
|
249
|
+
const stagingDir = await fs.realpath(stagingPath);
|
|
250
|
+
const planEntry = createTarEntryPlanner({ ...options, rootDir: destinationRealDir, limits: params.limits });
|
|
251
|
+
const accepted = manifest.flatMap((entry) => {
|
|
252
|
+
deadline.check();
|
|
253
|
+
const planned = planEntry(entry);
|
|
254
|
+
return planned ? [{ ...entry, ...planned }] : [];
|
|
255
|
+
});
|
|
256
|
+
await replayTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal, members: accepted,
|
|
257
|
+
async consume(member, payload) {
|
|
258
|
+
deadline.check();
|
|
259
|
+
await preparePrivateArchiveOutputPath({ destinationDir: stagingDir, destinationRealDir: stagingDir,
|
|
260
|
+
relPath: member.path, outPath: path.join(stagingDir, member.path), originalPath: member.path,
|
|
261
|
+
isDirectory: member.kind === "directory", deadline });
|
|
262
|
+
if (member.kind === "file") {
|
|
263
|
+
await runPinnedWriteHelper({ rootPath: stagingDir, relativeParentPath: path.posix.dirname(member.path),
|
|
264
|
+
basename: path.posix.basename(member.path), mkdir: false, mode: 0o600, overwrite: false,
|
|
265
|
+
maxBytes: member.size, input: { kind: "stream", stream: Readable.from(payload) } });
|
|
266
|
+
}
|
|
267
|
+
deadline.check();
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
deadline.check();
|
|
271
|
+
await mergePlannedArchiveIntoDestination({ entries: accepted, sourceDir: stagingDir,
|
|
272
|
+
destinationDir: options.destDir, destinationRealDir, deadline });
|
|
273
|
+
deadline.check();
|
|
274
|
+
} });
|
|
254
275
|
}
|
package/dist/atomic.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { replaceFileAtomic, replaceFileAtomicSync, type RenameIdentityPolicy, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicRestoreCleanup, type ReplaceFileAtomicRestoreFailureDetails, type ReplaceFileAtomicResult, type ReplaceFileCopyFallbackRestorePolicy, type ReplaceFileDestinationHardlinkPolicy, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
|
|
2
2
|
export { writeTextAtomic, type WriteTextAtomicOptions } from "./text-atomic.js";
|
|
3
3
|
export { replaceDirectoryAtomic, type ReplaceDirectoryAtomicOptions } from "./replace-directory.js";
|
|
4
|
-
export { movePathWithCopyFallback, type MovePathWithCopyFallbackOptions } from "./move-path.js";
|
|
4
|
+
export { movePathWithCopyFallback, type MovePathPublicationReceipt, type MovePathWithCopyFallbackOptions, } from "./move-path.js";
|
|
5
5
|
//# sourceMappingURL=atomic.d.ts.map
|
package/dist/atomic.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACpC,KAAK,sCAAsC,EAC3C,KAAK,uBAAuB,EAC5B,KAAK,oCAAoC,EACzC,KAAK,oCAAoC,EACzC,KAAK,+BAA+B,EACpC,KAAK,4BAA4B,GAClC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACpG,OAAO,
|
|
1
|
+
{"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACpC,KAAK,sCAAsC,EAC3C,KAAK,uBAAuB,EAC5B,KAAK,oCAAoC,EACzC,KAAK,oCAAoC,EACzC,KAAK,+BAA+B,EACpC,KAAK,4BAA4B,GAClC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACpG,OAAO,EACL,wBAAwB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,+BAA+B,GACrC,MAAM,gBAAgB,CAAC"}
|
package/dist/atomic.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { replaceFileAtomic, replaceFileAtomicSync, } from "./replace-file.js";
|
|
2
2
|
export { writeTextAtomic } from "./text-atomic.js";
|
|
3
3
|
export { replaceDirectoryAtomic } from "./replace-directory.js";
|
|
4
|
-
export { movePathWithCopyFallback } from "./move-path.js";
|
|
4
|
+
export { movePathWithCopyFallback, } from "./move-path.js";
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import { Transform } from "node:stream";
|
|
2
2
|
export declare function createMaxBytesTransform(maxBytes: number): Transform;
|
|
3
|
-
export declare function
|
|
4
|
-
handle: {
|
|
5
|
-
createReadStream(): NodeJS.ReadableStream;
|
|
6
|
-
};
|
|
7
|
-
}, maxBytes: number | undefined): NodeJS.ReadableStream;
|
|
3
|
+
export declare function createByteLimitTransform(maxBytes: number, overflowError: (bytes: number) => Error): Transform;
|
|
8
4
|
//# sourceMappingURL=bounded-read-stream.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bounded-read-stream.d.ts","sourceRoot":"","sources":["../src/bounded-read-stream.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"bounded-read-stream.d.ts","sourceRoot":"","sources":["../src/bounded-read-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIxC,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAMnE;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,GACtC,SAAS,CAaX"}
|
|
@@ -1,29 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Transform } from "node:stream";
|
|
2
2
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
export function createMaxBytesTransform(maxBytes) {
|
|
5
5
|
normalizeMaxBytes(maxBytes);
|
|
6
|
+
return createByteLimitTransform(maxBytes, (bytes) => new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`));
|
|
7
|
+
}
|
|
8
|
+
export function createByteLimitTransform(maxBytes, overflowError) {
|
|
6
9
|
let bytes = 0;
|
|
7
10
|
return new Transform({
|
|
8
11
|
transform(chunk, _encoding, callback) {
|
|
9
12
|
const buffer = chunk instanceof Buffer ? chunk : Buffer.from(chunk);
|
|
10
13
|
bytes += buffer.byteLength;
|
|
11
14
|
if (bytes > maxBytes) {
|
|
12
|
-
callback(
|
|
15
|
+
callback(overflowError(bytes));
|
|
13
16
|
return;
|
|
14
17
|
}
|
|
15
18
|
callback(null, buffer);
|
|
16
19
|
},
|
|
17
20
|
});
|
|
18
21
|
}
|
|
19
|
-
export function createBoundedReadStream(opened, maxBytes) {
|
|
20
|
-
const stream = opened.handle.createReadStream();
|
|
21
|
-
if (maxBytes === undefined) {
|
|
22
|
-
return stream;
|
|
23
|
-
}
|
|
24
|
-
const bounded = createMaxBytesTransform(maxBytes);
|
|
25
|
-
// pipeline couples teardown in both directions: source failures reach the
|
|
26
|
-
// returned stream, and an early consumer close destroys the file stream.
|
|
27
|
-
pipeline(stream, bounded, () => undefined);
|
|
28
|
-
return bounded;
|
|
29
|
-
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bounded-read.d.ts","sourceRoot":"","sources":["../src/bounded-read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"bounded-read.d.ts","sourceRoot":"","sources":["../src/bounded-read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AA+DnD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAMjB;AAyBD,oFAAoF;AACpF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7F;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAalF"}
|
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) {
|
package/dist/byte-budget.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"byte-budget.d.ts","sourceRoot":"","sources":["../src/byte-budget.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"byte-budget.d.ts","sourceRoot":"","sources":["../src/byte-budget.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GACtC,MAAM,GAAG,SAAS,CASpB"}
|
package/dist/byte-budget.js
CHANGED
|
@@ -3,7 +3,7 @@ export function normalizeMaxBytes(value, options = {}) {
|
|
|
3
3
|
if (selected === undefined || selected === Number.POSITIVE_INFINITY)
|
|
4
4
|
return selected;
|
|
5
5
|
if (!Number.isSafeInteger(selected) || selected < 0) {
|
|
6
|
-
throw new RangeError(
|
|
6
|
+
throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
|
|
7
7
|
}
|
|
8
8
|
return selected;
|
|
9
9
|
}
|
|
@@ -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;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-mode-owner.d.ts","sourceRoot":"","sources":["../src/directory-mode-owner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIlD,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,GAAG,WAAW,EAAE,MAAM,EAAE,KAAK,GAAG,WAAW,GAAG,IAAI,CAOrG;AAED,8FAA8F;AAC9F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,kBAAkB,
|
|
1
|
+
{"version":3,"file":"directory-mode-owner.d.ts","sourceRoot":"","sources":["../src/directory-mode-owner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIlD,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,GAAG,WAAW,EAAE,MAAM,EAAE,KAAK,GAAG,WAAW,GAAG,IAAI,CAOrG;AAED,8FAA8F;AAC9F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,kBAAkB,CAwDrB"}
|
|
@@ -32,6 +32,8 @@ export function ownDirectoryMode(params) {
|
|
|
32
32
|
checks.check?.();
|
|
33
33
|
const currentMode = await params.inspect();
|
|
34
34
|
checks.check?.();
|
|
35
|
+
if (currentMode === mode && !checks.beforeChmod && !checks.check)
|
|
36
|
+
return;
|
|
35
37
|
if (currentMode !== mode) {
|
|
36
38
|
await params.prepareChmod?.();
|
|
37
39
|
checks.check?.();
|
package/dist/error-detail.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-detail.d.ts","sourceRoot":"","sources":["../src/error-detail.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"error-detail.d.ts","sourceRoot":"","sources":["../src/error-detail.ts"],"names":[],"mappings":"AAKA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/C"}
|
package/dist/error-detail.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import os from "node:os";
|
|
1
2
|
const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
|
|
2
3
|
export function formatErrorDetail(value) {
|
|
3
4
|
return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
4
5
|
}
|
|
6
|
+
export function shortPath(value) {
|
|
7
|
+
const home = os.homedir();
|
|
8
|
+
const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
|
|
9
|
+
return formatErrorDetail(shortened);
|
|
10
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,
|
|
1
|
+
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIvD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAA4C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGhF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,MAAM,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAOjD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,IAAI,EAChB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKhB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,GACnD,OAAO,CAAC,IAAI,CAAC,CASf;AAsCD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAwC1D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAWrE;AAUD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,eAAe,CAOlB;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC;CAC1C,GAAG,eAAe,CAyDlB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import syncFs from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { createByteLimitTransform } from "./bounded-read-stream.js";
|
|
5
5
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
6
6
|
import { pipeline } from "node:stream/promises";
|
|
7
7
|
import { assertSyncDirectoryGuard as assertDirectoryGuardSync, createSyncDirectoryGuard, } from "./directory-guard.js";
|
|
@@ -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
|
}
|
|
@@ -68,18 +68,7 @@ function createMaxBytesTransform(maxBytes) {
|
|
|
68
68
|
if (limit === undefined) {
|
|
69
69
|
return undefined;
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
return new Transform({
|
|
73
|
-
transform(chunk, _encoding, callback) {
|
|
74
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
75
|
-
total += buffer.byteLength;
|
|
76
|
-
if (total > limit) {
|
|
77
|
-
callback(new FsSafeError("too-large", `file exceeds maximum size of ${limit} bytes`));
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
callback(null, buffer);
|
|
81
|
-
},
|
|
82
|
-
});
|
|
71
|
+
return createByteLimitTransform(limit, () => new FsSafeError("too-large", `file exceeds maximum size of ${limit} bytes`));
|
|
83
72
|
}
|
|
84
73
|
export async function writeStreamToTempSource(params) {
|
|
85
74
|
const maxBytes = normalizeMaxBytes(params.maxBytes);
|