@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.
Files changed (200) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +1 -1
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +8 -7
  5. package/dist/advanced.d.ts +2 -2
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +2 -2
  8. package/dist/archive-kind.d.ts +1 -0
  9. package/dist/archive-kind.d.ts.map +1 -1
  10. package/dist/archive-kind.js +6 -0
  11. package/dist/archive-limits.d.ts +0 -1
  12. package/dist/archive-limits.d.ts.map +1 -1
  13. package/dist/archive-limits.js +0 -3
  14. package/dist/archive-native.d.ts +1 -0
  15. package/dist/archive-native.d.ts.map +1 -1
  16. package/dist/archive-native.js +20 -76
  17. package/dist/archive-parser-errors.d.ts +2 -0
  18. package/dist/archive-parser-errors.d.ts.map +1 -0
  19. package/dist/archive-parser-errors.js +12 -0
  20. package/dist/archive-plan.d.ts +20 -0
  21. package/dist/archive-plan.d.ts.map +1 -0
  22. package/dist/archive-plan.js +55 -0
  23. package/dist/archive-read.d.ts.map +1 -1
  24. package/dist/archive-read.js +7 -13
  25. package/dist/archive-tar-inspect.d.ts +7 -0
  26. package/dist/archive-tar-inspect.d.ts.map +1 -0
  27. package/dist/archive-tar-inspect.js +59 -0
  28. package/dist/archive-tar-wasm.d.ts +1 -1
  29. package/dist/archive-tar-wasm.d.ts.map +1 -1
  30. package/dist/archive-tar-wasm.js +5 -8
  31. package/dist/archive-tar.d.ts +3 -1
  32. package/dist/archive-tar.d.ts.map +1 -1
  33. package/dist/archive-tar.js +12 -49
  34. package/dist/archive.d.ts +1 -0
  35. package/dist/archive.d.ts.map +1 -1
  36. package/dist/archive.js +54 -33
  37. package/dist/atomic.d.ts +1 -1
  38. package/dist/atomic.d.ts.map +1 -1
  39. package/dist/atomic.js +1 -1
  40. package/dist/bounded-read-stream.d.ts +1 -5
  41. package/dist/bounded-read-stream.d.ts.map +1 -1
  42. package/dist/bounded-read-stream.js +5 -13
  43. package/dist/bounded-read.d.ts.map +1 -1
  44. package/dist/bounded-read.js +31 -4
  45. package/dist/byte-budget.d.ts +0 -1
  46. package/dist/byte-budget.d.ts.map +1 -1
  47. package/dist/byte-budget.js +1 -1
  48. package/dist/directory-durability.js +5 -5
  49. package/dist/directory-guard.d.ts.map +1 -1
  50. package/dist/directory-guard.js +5 -6
  51. package/dist/directory-mode-owner.d.ts.map +1 -1
  52. package/dist/directory-mode-owner.js +2 -0
  53. package/dist/error-detail.d.ts +1 -0
  54. package/dist/error-detail.d.ts.map +1 -1
  55. package/dist/error-detail.js +6 -0
  56. package/dist/file-store-boundary.d.ts +1 -1
  57. package/dist/file-store-boundary.d.ts.map +1 -1
  58. package/dist/file-store-boundary.js +3 -14
  59. package/dist/file-store-prune.js +17 -8
  60. package/dist/file-store.js +3 -3
  61. package/dist/file-sync.d.ts +5 -0
  62. package/dist/file-sync.d.ts.map +1 -0
  63. package/dist/file-sync.js +19 -0
  64. package/dist/guarded-mkdir.d.ts.map +1 -1
  65. package/dist/guarded-mkdir.js +5 -4
  66. package/dist/guarded-mutation.d.ts +3 -0
  67. package/dist/guarded-mutation.d.ts.map +1 -1
  68. package/dist/guarded-mutation.js +5 -1
  69. package/dist/home-dir.d.ts +0 -10
  70. package/dist/home-dir.d.ts.map +1 -1
  71. package/dist/home-dir.js +0 -28
  72. package/dist/json-durable-queue-ownership.d.ts +1 -0
  73. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  74. package/dist/json-durable-queue-ownership.js +139 -18
  75. package/dist/json-durable-queue.d.ts.map +1 -1
  76. package/dist/json-durable-queue.js +5 -10
  77. package/dist/json-store.d.ts.map +1 -1
  78. package/dist/json-store.js +1 -1
  79. package/dist/local-roots.d.ts.map +1 -1
  80. package/dist/local-roots.js +2 -3
  81. package/dist/move-path-cleanup.d.ts +1 -1
  82. package/dist/move-path-cleanup.d.ts.map +1 -1
  83. package/dist/move-path-cleanup.js +4 -2
  84. package/dist/move-path.d.ts +9 -0
  85. package/dist/move-path.d.ts.map +1 -1
  86. package/dist/move-path.js +53 -16
  87. package/dist/native-binding.d.ts +2 -1
  88. package/dist/native-binding.d.ts.map +1 -1
  89. package/dist/native-operations.d.ts +0 -1
  90. package/dist/native-operations.d.ts.map +1 -1
  91. package/dist/native-operations.js +0 -10
  92. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  93. package/dist/native-pinned-write-windows.js +8 -4
  94. package/dist/native-pinned-write.js +3 -3
  95. package/dist/native-staged-file.d.ts +10 -3
  96. package/dist/native-staged-file.d.ts.map +1 -1
  97. package/dist/native-staged-file.js +38 -13
  98. package/dist/opened-realpath.d.ts +12 -1
  99. package/dist/opened-realpath.d.ts.map +1 -1
  100. package/dist/opened-realpath.js +16 -14
  101. package/dist/output.d.ts.map +1 -1
  102. package/dist/output.js +26 -2
  103. package/dist/path-policy.js +3 -3
  104. package/dist/permissions.d.ts +1 -0
  105. package/dist/permissions.d.ts.map +1 -1
  106. package/dist/permissions.js +3 -0
  107. package/dist/pinned-write.d.ts +2 -2
  108. package/dist/pinned-write.d.ts.map +1 -1
  109. package/dist/pinned-write.js +17 -22
  110. package/dist/private-temp-workspace.js +1 -1
  111. package/dist/publish-file.js +2 -2
  112. package/dist/regular-file.js +8 -8
  113. package/dist/replace-file-copy-source.d.ts.map +1 -1
  114. package/dist/replace-file-copy-source.js +2 -7
  115. package/dist/replace-file-descriptor.d.ts.map +1 -1
  116. package/dist/replace-file-descriptor.js +11 -20
  117. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  118. package/dist/replace-file-temp-owner.js +15 -14
  119. package/dist/replace-file.d.ts.map +1 -1
  120. package/dist/replace-file.js +21 -8
  121. package/dist/root-context.js +5 -5
  122. package/dist/root-errors.d.ts +1 -0
  123. package/dist/root-errors.d.ts.map +1 -1
  124. package/dist/root-errors.js +9 -0
  125. package/dist/root-file.d.ts.map +1 -1
  126. package/dist/root-file.js +17 -28
  127. package/dist/root-impl.d.ts +2 -1
  128. package/dist/root-impl.d.ts.map +1 -1
  129. package/dist/root-impl.js +46 -58
  130. package/dist/root-path-existing.d.ts.map +1 -1
  131. package/dist/root-path-existing.js +2 -3
  132. package/dist/root-path-symlink.d.ts.map +1 -1
  133. package/dist/root-path-symlink.js +2 -3
  134. package/dist/root-path.d.ts.map +1 -1
  135. package/dist/root-path.js +51 -97
  136. package/dist/root-paths.d.ts.map +1 -1
  137. package/dist/root-paths.js +16 -15
  138. package/dist/root-write-mode.d.ts +6 -0
  139. package/dist/root-write-mode.d.ts.map +1 -0
  140. package/dist/root-write-mode.js +61 -0
  141. package/dist/root-write-verification.js +8 -8
  142. package/dist/safe-path-segment.d.ts +1 -0
  143. package/dist/safe-path-segment.d.ts.map +1 -1
  144. package/dist/safe-path-segment.js +1 -1
  145. package/dist/secret-file.d.ts.map +1 -1
  146. package/dist/secret-file.js +4 -11
  147. package/dist/secret-read-async.d.ts.map +1 -1
  148. package/dist/secret-read-async.js +2 -10
  149. package/dist/secret-read-policy.d.ts +4 -0
  150. package/dist/secret-read-policy.d.ts.map +1 -1
  151. package/dist/secret-read-policy.js +12 -0
  152. package/dist/sibling-staged-file.d.ts.map +1 -1
  153. package/dist/sibling-staged-file.js +2 -7
  154. package/dist/sidecar-lock-policy.d.ts +0 -1
  155. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  156. package/dist/sidecar-lock-policy.js +0 -4
  157. package/dist/strict-file-identity.d.ts +1 -1
  158. package/dist/strict-file-identity.d.ts.map +1 -1
  159. package/dist/string-coerce.d.ts +0 -9
  160. package/dist/string-coerce.d.ts.map +1 -1
  161. package/dist/string-coerce.js +0 -56
  162. package/dist/symlink-parents.d.ts.map +1 -1
  163. package/dist/symlink-parents.js +1 -2
  164. package/dist/temp-target.d.ts.map +1 -1
  165. package/dist/temp-target.js +1 -12
  166. package/docs/archive.md +57 -0
  167. package/docs/atomic.md +53 -19
  168. package/docs/reading.md +2 -0
  169. package/docs/root.md +10 -1
  170. package/docs/staged-file.md +6 -2
  171. package/docs/types.md +2 -1
  172. package/docs/writing.md +30 -2
  173. package/package.json +8 -8
  174. package/dist/archive-tar-extract.d.ts +0 -11
  175. package/dist/archive-tar-extract.d.ts.map +0 -1
  176. package/dist/archive-tar-extract.js +0 -49
  177. package/dist/fsync.d.ts +0 -2
  178. package/dist/fsync.d.ts.map +0 -1
  179. package/dist/fsync.js +0 -1
  180. package/dist/json-durable-queue-retirement.d.ts +0 -9
  181. package/dist/json-durable-queue-retirement.d.ts.map +0 -1
  182. package/dist/json-durable-queue-retirement.js +0 -126
  183. package/dist/json-durable-queue-transfer-lock.d.ts +0 -2
  184. package/dist/json-durable-queue-transfer-lock.d.ts.map +0 -1
  185. package/dist/json-durable-queue-transfer-lock.js +0 -19
  186. package/dist/mode.d.ts +0 -2
  187. package/dist/mode.d.ts.map +0 -1
  188. package/dist/mode.js +0 -3
  189. package/dist/output-sibling.d.ts +0 -8
  190. package/dist/output-sibling.d.ts.map +0 -1
  191. package/dist/output-sibling.js +0 -30
  192. package/dist/read-error.d.ts +0 -2
  193. package/dist/read-error.d.ts.map +0 -1
  194. package/dist/read-error.js +0 -11
  195. package/dist/short-path.d.ts +0 -2
  196. package/dist/short-path.d.ts.map +0 -1
  197. package/dist/short-path.js +0 -7
  198. package/dist/staged-file.d.ts +0 -10
  199. package/dist/staged-file.d.ts.map +0 -1
  200. package/dist/staged-file.js +0 -15
@@ -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, ArchiveSecurityError, isArchiveTarPathErrorMessage } from "./archive-errors.js";
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
- if (isArchiveTarPathErrorMessage(message))
22
- return new ArchiveSecurityError("entry-path", message);
23
- for (const code of Object.values(ARCHIVE_LIMIT_ERROR_CODE)) {
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. */
@@ -1,4 +1,5 @@
1
- import { type ArchiveExtractLimits } from "./archive-limits.js";
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":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAI7B,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;AAWvF,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,CAqDnC"}
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"}
@@ -1,58 +1,21 @@
1
- import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
2
- import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, resolveExtractLimits, } from "./archive-limits.js";
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 createTarEntryPreflightChecker(params) {
15
- const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
16
- const limits = resolveExtractLimits(params.limits);
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
- entryCount += 1;
22
- assertArchiveEntryCountWithinLimit(entryCount, limits);
23
- validateArchiveEntryPath(entry.path, { escapeLabel: params.escapeLabel });
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";
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AAwDA,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,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,CAgEjF"}
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 fsSync, { constants as fsConstants } from "node:fs";
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 { resolveArchiveEntryMode, resolveArchiveFilteredEntryPolicy, shouldExtractArchiveEntry, } from "./archive-policy.js";
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
- if (kind === "tar-zstd" || kind === "tar-bzip2") {
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: params.archivePath,
226
+ archivePath: options.archivePath,
232
227
  limits,
233
228
  deadline,
234
229
  });
235
230
  try {
236
- await extractWasmTar({ archivePath: stagedArchive.path, options: { ...params, onFiltered }, limits, tarLimits, deadline });
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
- archivePath: params.archivePath,
246
- destDir: params.destDir,
247
- stripComponents: params.stripComponents,
248
- limits,
249
- deadline,
250
- entryModes: params.entryModes,
251
- entryFilter: params.entryFilter,
252
- onFiltered,
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
@@ -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,EAAE,wBAAwB,EAAE,KAAK,+BAA+B,EAAE,MAAM,gBAAgB,CAAC"}
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 createBoundedReadStream(opened: {
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,EAAY,SAAS,EAAE,MAAM,aAAa,CAAC;AAIlD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAmBnE;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE;IAAE,MAAM,EAAE;QAAE,gBAAgB,IAAI,MAAM,CAAC,cAAc,CAAA;KAAE,CAAA;CAAE,EACjE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,CAAC,cAAc,CAWvB"}
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 { pipeline, Transform } from "node:stream";
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(new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`));
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;AAmDnD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAIjB;AAcD,oFAAoF;AACpF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI7F;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAalF"}
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"}
@@ -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) {
@@ -1,5 +1,4 @@
1
1
  export declare function normalizeMaxBytes(value: number | undefined, options?: {
2
2
  defaultValue?: number;
3
- label?: string;
4
3
  }): number | undefined;
5
4
  //# sourceMappingURL=byte-budget.d.ts.map
@@ -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,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,MAAM,GAAG,SAAS,CASpB"}
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"}
@@ -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(`${options.label ?? "maxBytes"} must be a non-negative safe integer or Infinity`);
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 = await fs.lstat(resolvedPath);
46
+ const identity = fsSync.lstatSync(resolvedPath);
47
47
  assertDirectory(identity, resolvedPath, label);
48
48
  return {
49
49
  path: resolvedPath,
50
- realPath: await fs.realpath(resolvedPath),
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 = await fs.lstat(receipt.path);
65
+ const currentIdentity = fsSync.lstatSync(receipt.path);
66
66
  assertDirectory(currentIdentity, receipt.path, label);
67
67
  if (!sameFileIdentity(receipt.identity, currentIdentity) ||
68
- (await fs.realpath(receipt.path)) !== receipt.realPath) {
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 = await handle.stat();
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;AAUlD,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"}
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"}
@@ -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) : await fs.lstat(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: await fs.realpath(dir), stat };
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
- : await fs.lstat(guard.dir);
18
+ : fsSync.lstatSync(guard.dir);
20
19
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
21
20
  throw directoryComponentNotDirectoryError();
22
21
  }
23
- if (!sameFileIdentity(stat, guard.stat) || (await fs.realpath(guard.dir)) !== guard.realPath) {
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 = await fs.lstat(dir, { bigint: true });
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,CAuDrB"}
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?.();
@@ -1,2 +1,3 @@
1
1
  export declare function formatErrorDetail(value: string): string;
2
+ export declare function shortPath(value: string): string;
2
3
  //# sourceMappingURL=error-detail.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-detail.d.ts","sourceRoot":"","sources":["../src/error-detail.ts"],"names":[],"mappings":"AAGA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIvD"}
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"}
@@ -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,4 +1,4 @@
1
- import { type Readable } from "node:stream";
1
+ import type { Readable } from "node:stream";
2
2
  import { type SyncDirectoryGuard } from "./directory-guard.js";
3
3
  import { type Root } from "./root.js";
4
4
  import { prepareSecretFileWrite } from "./secret-file.js";
@@ -1 +1 @@
1
- {"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvD,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;AAgDD,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
+ {"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 { Transform } from "node:stream";
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 = await handle.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
- let total = 0;
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);