@openclaw/fs-safe 0.8.1 → 0.8.3

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 (195) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +2 -2
  3. package/dist/advanced.d.ts +2 -2
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +2 -2
  6. package/dist/archive-entry.d.ts.map +1 -1
  7. package/dist/archive-entry.js +5 -0
  8. package/dist/archive-gzip-tail.d.ts +18 -0
  9. package/dist/archive-gzip-tail.d.ts.map +1 -0
  10. package/dist/archive-gzip-tail.js +74 -0
  11. package/dist/archive-kind.d.ts +1 -0
  12. package/dist/archive-kind.d.ts.map +1 -1
  13. package/dist/archive-kind.js +6 -0
  14. package/dist/archive-limits.d.ts +0 -1
  15. package/dist/archive-limits.d.ts.map +1 -1
  16. package/dist/archive-limits.js +0 -3
  17. package/dist/archive-native.d.ts.map +1 -1
  18. package/dist/archive-native.js +5 -8
  19. package/dist/archive-parser-errors.d.ts +2 -0
  20. package/dist/archive-parser-errors.d.ts.map +1 -0
  21. package/dist/archive-parser-errors.js +12 -0
  22. package/dist/archive-parser.wasm +0 -0
  23. package/dist/archive-read.d.ts.map +1 -1
  24. package/dist/archive-read.js +25 -73
  25. package/dist/archive-tar-stream.d.ts +18 -0
  26. package/dist/archive-tar-stream.d.ts.map +1 -0
  27. package/dist/archive-tar-stream.js +95 -0
  28. package/dist/archive-tar-wasm.d.ts +18 -0
  29. package/dist/archive-tar-wasm.d.ts.map +1 -0
  30. package/dist/archive-tar-wasm.js +99 -0
  31. package/dist/archive-tar.d.ts +0 -1
  32. package/dist/archive-tar.d.ts.map +1 -1
  33. package/dist/archive-tar.js +0 -22
  34. package/dist/archive.d.ts.map +1 -1
  35. package/dist/archive.js +44 -114
  36. package/dist/atomic.d.ts +1 -1
  37. package/dist/atomic.d.ts.map +1 -1
  38. package/dist/atomic.js +1 -1
  39. package/dist/bounded-read-stream.d.ts +1 -5
  40. package/dist/bounded-read-stream.d.ts.map +1 -1
  41. package/dist/bounded-read-stream.js +5 -13
  42. package/dist/byte-budget.d.ts +0 -1
  43. package/dist/byte-budget.d.ts.map +1 -1
  44. package/dist/byte-budget.js +1 -1
  45. package/dist/device-path.d.ts +1 -0
  46. package/dist/device-path.d.ts.map +1 -1
  47. package/dist/device-path.js +5 -2
  48. package/dist/directory-mode-owner.d.ts.map +1 -1
  49. package/dist/directory-mode-owner.js +5 -0
  50. package/dist/error-detail.d.ts +1 -0
  51. package/dist/error-detail.d.ts.map +1 -1
  52. package/dist/error-detail.js +6 -0
  53. package/dist/file-store-boundary.d.ts +1 -1
  54. package/dist/file-store-boundary.d.ts.map +1 -1
  55. package/dist/file-store-boundary.js +2 -13
  56. package/dist/file-store.js +1 -1
  57. package/dist/file-sync.d.ts +5 -0
  58. package/dist/file-sync.d.ts.map +1 -0
  59. package/dist/file-sync.js +19 -0
  60. package/dist/guarded-mutation.d.ts +3 -0
  61. package/dist/guarded-mutation.d.ts.map +1 -1
  62. package/dist/guarded-mutation.js +5 -1
  63. package/dist/home-dir.d.ts +0 -10
  64. package/dist/home-dir.d.ts.map +1 -1
  65. package/dist/home-dir.js +0 -28
  66. package/dist/json-durable-queue-ownership.d.ts +1 -0
  67. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  68. package/dist/json-durable-queue-ownership.js +139 -18
  69. package/dist/json-durable-queue.d.ts.map +1 -1
  70. package/dist/json-durable-queue.js +5 -10
  71. package/dist/json-store.d.ts.map +1 -1
  72. package/dist/json-store.js +1 -1
  73. package/dist/local-roots.d.ts.map +1 -1
  74. package/dist/local-roots.js +2 -3
  75. package/dist/move-path-cleanup.d.ts +1 -1
  76. package/dist/move-path-cleanup.d.ts.map +1 -1
  77. package/dist/move-path-cleanup.js +4 -2
  78. package/dist/move-path.d.ts +9 -0
  79. package/dist/move-path.d.ts.map +1 -1
  80. package/dist/move-path.js +53 -16
  81. package/dist/native-operations.d.ts +0 -1
  82. package/dist/native-operations.d.ts.map +1 -1
  83. package/dist/native-operations.js +0 -10
  84. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  85. package/dist/native-pinned-write-windows.js +5 -4
  86. package/dist/native-staged-file.d.ts +8 -1
  87. package/dist/native-staged-file.d.ts.map +1 -1
  88. package/dist/native-staged-file.js +30 -9
  89. package/dist/opened-realpath.d.ts +12 -1
  90. package/dist/opened-realpath.d.ts.map +1 -1
  91. package/dist/opened-realpath.js +8 -7
  92. package/dist/output.d.ts.map +1 -1
  93. package/dist/output.js +26 -2
  94. package/dist/path-policy.js +1 -1
  95. package/dist/permissions.d.ts +1 -0
  96. package/dist/permissions.d.ts.map +1 -1
  97. package/dist/permissions.js +3 -0
  98. package/dist/pinned-write.d.ts +1 -2
  99. package/dist/pinned-write.d.ts.map +1 -1
  100. package/dist/pinned-write.js +2 -11
  101. package/dist/private-temp-workspace.js +1 -1
  102. package/dist/publish-file.js +2 -2
  103. package/dist/replace-file-copy-source.d.ts.map +1 -1
  104. package/dist/replace-file-copy-source.js +2 -7
  105. package/dist/replace-file-descriptor.d.ts.map +1 -1
  106. package/dist/replace-file-descriptor.js +5 -17
  107. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  108. package/dist/replace-file-temp-owner.js +2 -7
  109. package/dist/replace-file.d.ts.map +1 -1
  110. package/dist/replace-file.js +21 -8
  111. package/dist/root-errors.d.ts +1 -0
  112. package/dist/root-errors.d.ts.map +1 -1
  113. package/dist/root-errors.js +9 -0
  114. package/dist/root-file.d.ts.map +1 -1
  115. package/dist/root-file.js +17 -28
  116. package/dist/root-impl.d.ts.map +1 -1
  117. package/dist/root-impl.js +12 -33
  118. package/dist/root-path.d.ts.map +1 -1
  119. package/dist/root-path.js +49 -94
  120. package/dist/root-write-mode.d.ts +6 -0
  121. package/dist/root-write-mode.d.ts.map +1 -0
  122. package/dist/root-write-mode.js +60 -0
  123. package/dist/root-write-verification.js +2 -2
  124. package/dist/safe-path-segment.d.ts +1 -0
  125. package/dist/safe-path-segment.d.ts.map +1 -1
  126. package/dist/safe-path-segment.js +1 -1
  127. package/dist/secret-file.d.ts.map +1 -1
  128. package/dist/secret-file.js +4 -9
  129. package/dist/secret-read-async.d.ts.map +1 -1
  130. package/dist/secret-read-async.js +5 -10
  131. package/dist/secret-read-policy.d.ts +4 -0
  132. package/dist/secret-read-policy.d.ts.map +1 -1
  133. package/dist/secret-read-policy.js +12 -0
  134. package/dist/sibling-staged-file.d.ts.map +1 -1
  135. package/dist/sibling-staged-file.js +2 -7
  136. package/dist/sidecar-lock-policy.d.ts +0 -1
  137. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  138. package/dist/sidecar-lock-policy.js +0 -4
  139. package/dist/string-coerce.d.ts +0 -9
  140. package/dist/string-coerce.d.ts.map +1 -1
  141. package/dist/string-coerce.js +0 -56
  142. package/dist/temp-target.d.ts.map +1 -1
  143. package/dist/temp-target.js +1 -12
  144. package/docs/archive.md +66 -61
  145. package/docs/atomic.md +53 -19
  146. package/docs/contributing.md +35 -1
  147. package/docs/install.md +1 -1
  148. package/docs/native-helper.md +5 -0
  149. package/docs/native.md +16 -10
  150. package/docs/secret-file.md +5 -0
  151. package/docs/staged-file.md +6 -2
  152. package/docs/writing.md +5 -0
  153. package/package.json +19 -16
  154. package/dist/archive-tar-admission.d.ts +0 -7
  155. package/dist/archive-tar-admission.d.ts.map +0 -1
  156. package/dist/archive-tar-admission.js +0 -43
  157. package/dist/archive-tar-gnu.d.ts +0 -2
  158. package/dist/archive-tar-gnu.d.ts.map +0 -1
  159. package/dist/archive-tar-gnu.js +0 -20
  160. package/dist/archive-tar-header.d.ts +0 -8
  161. package/dist/archive-tar-header.d.ts.map +0 -1
  162. package/dist/archive-tar-header.js +0 -47
  163. package/dist/archive-tar-meta.d.ts +0 -34
  164. package/dist/archive-tar-meta.d.ts.map +0 -1
  165. package/dist/archive-tar-meta.js +0 -277
  166. package/dist/archive-tar-pax.d.ts +0 -8
  167. package/dist/archive-tar-pax.d.ts.map +0 -1
  168. package/dist/archive-tar-pax.js +0 -100
  169. package/dist/archive-tar-runtime.d.ts +0 -49
  170. package/dist/archive-tar-runtime.d.ts.map +0 -1
  171. package/dist/archive-tar-runtime.js +0 -22
  172. package/dist/fsync.d.ts +0 -2
  173. package/dist/fsync.d.ts.map +0 -1
  174. package/dist/fsync.js +0 -1
  175. package/dist/json-durable-queue-retirement.d.ts +0 -9
  176. package/dist/json-durable-queue-retirement.d.ts.map +0 -1
  177. package/dist/json-durable-queue-retirement.js +0 -126
  178. package/dist/json-durable-queue-transfer-lock.d.ts +0 -2
  179. package/dist/json-durable-queue-transfer-lock.d.ts.map +0 -1
  180. package/dist/json-durable-queue-transfer-lock.js +0 -19
  181. package/dist/mode.d.ts +0 -2
  182. package/dist/mode.d.ts.map +0 -1
  183. package/dist/mode.js +0 -3
  184. package/dist/output-sibling.d.ts +0 -8
  185. package/dist/output-sibling.d.ts.map +0 -1
  186. package/dist/output-sibling.js +0 -30
  187. package/dist/read-error.d.ts +0 -2
  188. package/dist/read-error.d.ts.map +0 -1
  189. package/dist/read-error.js +0 -11
  190. package/dist/short-path.d.ts +0 -2
  191. package/dist/short-path.d.ts.map +0 -1
  192. package/dist/short-path.js +0 -7
  193. package/dist/staged-file.d.ts +0 -10
  194. package/dist/staged-file.d.ts.map +0 -1
  195. package/dist/staged-file.js +0 -15
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAuClE,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,SAAS;IAER,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAD9D,OAAO,CAAC,GAAG,CAAkB;IAC7B,YAAY,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,aAAA,EAQjG;IACD,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,IAAI;IAIH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAkB/F;IACQ,MAAM,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAKjD;IACQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAKpF;CACF"}
@@ -0,0 +1,99 @@
1
+ import { classifyArchiveParserError } from "./archive-parser-errors.js";
2
+ import { readFileSync } from "node:fs";
3
+ import { Transform } from "node:stream";
4
+ import { ArchiveFormatError } from "./archive-errors.js";
5
+ // Node exposes WebAssembly without DOM globals; keep the private ABI types local.
6
+ const wasm = globalThis.WebAssembly;
7
+ let compiled;
8
+ function instance() {
9
+ // src tests and dist consumers resolve the same generated package artifact.
10
+ compiled ??= new wasm.Module(readFileSync(new URL("../dist/archive-parser.wasm", import.meta.url)));
11
+ if (wasm.Module.imports(compiled).length)
12
+ throw new Error("TAR WASM unexpectedly requires host imports");
13
+ return new wasm.Instance(compiled).exports;
14
+ }
15
+ const types = new Map([
16
+ [0, "File"], [48, "File"], [49, "Link"], [50, "SymbolicLink"],
17
+ [51, "CharacterDevice"], [52, "BlockDevice"], [53, "Directory"],
18
+ [54, "FIFO"], [55, "ContiguousFile"], [68, "GNUDumpDir"],
19
+ ]);
20
+ function parserError(message) {
21
+ const mapped = classifyArchiveParserError(message);
22
+ if (mapped)
23
+ return mapped;
24
+ return new ArchiveFormatError(message.replace(/^archive-header-invalid:/, "invalid TAR header:"));
25
+ }
26
+ /** Backpressure-aware transport only; all TAR semantics live in the Rust core. */
27
+ export class TarParserStream extends Transform {
28
+ onMember;
29
+ abi;
30
+ constructor(limits, onMember) {
31
+ super();
32
+ this.onMember = onMember;
33
+ this.abi = instance();
34
+ if (this.abi.init(limits.maxEntries, limits.maxMetaEntryBytes, limits.maxDecodedBytes, limits.maxManifestBytes, Number(process.platform === "win32")) !== 0) {
35
+ this.abi.dispose();
36
+ this.abi = undefined;
37
+ throw new RangeError("invalid TAR parser limits");
38
+ }
39
+ }
40
+ bytes(pointer, length) {
41
+ const memory = this.abi.memory.buffer;
42
+ if (!Number.isInteger(pointer) || !Number.isInteger(length) || pointer < 0 || length < 0 ||
43
+ pointer > memory.byteLength || length > memory.byteLength - pointer) {
44
+ throw new ArchiveFormatError("invalid TAR WASM memory range");
45
+ }
46
+ return new Uint8Array(memory, pointer, length);
47
+ }
48
+ text() {
49
+ const abi = this.abi;
50
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(this.bytes(abi.text_ptr(), abi.text_len()));
51
+ }
52
+ _transform(chunk, _encoding, callback) {
53
+ try {
54
+ const abi = this.abi;
55
+ for (let offset = 0; offset < chunk.length;) {
56
+ const length = Math.min(65536, chunk.length - offset);
57
+ this.bytes(abi.input_ptr(), length).set(chunk.subarray(offset, offset + length));
58
+ const used = abi.push(length);
59
+ if (used < 0)
60
+ throw parserError(this.text());
61
+ if (used === 0 || used > length)
62
+ throw new ArchiveFormatError("TAR WASM made no progress");
63
+ offset += used;
64
+ const type = abi.member_type();
65
+ if (type >= 0)
66
+ this.onMember?.({
67
+ path: this.text(), type: types.get(type) ?? "Unsupported", size: abi.member_size(),
68
+ mode: abi.member_mode(), offset: abi.member_offset(),
69
+ });
70
+ }
71
+ callback(null, chunk);
72
+ }
73
+ catch (error) {
74
+ callback(error instanceof Error ? error : new Error(String(error)));
75
+ }
76
+ }
77
+ _flush(callback) {
78
+ try {
79
+ if (this.abi.finish() !== 0)
80
+ throw parserError(this.text());
81
+ callback();
82
+ }
83
+ catch (error) {
84
+ callback(error instanceof Error ? error : new Error(String(error)));
85
+ }
86
+ }
87
+ _destroy(error, callback) {
88
+ try {
89
+ this.abi?.dispose();
90
+ }
91
+ catch (cause) {
92
+ error ??= cause instanceof Error ? cause : new Error(String(cause));
93
+ }
94
+ finally {
95
+ this.abi = undefined;
96
+ }
97
+ callback(error);
98
+ }
99
+ }
@@ -6,7 +6,6 @@ export type TarEntryInfo = {
6
6
  size: number;
7
7
  mode?: number;
8
8
  };
9
- export declare function readTarEntryInfo(entry: unknown): TarEntryInfo;
10
9
  export declare function createTarEntryPreflightChecker(params: {
11
10
  rootDir: string;
12
11
  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,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAyB7D;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,CAqDnC"}
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"}
@@ -11,28 +11,6 @@ const BLOCKED_TAR_ENTRY_TYPES = new Set([
11
11
  "FIFO",
12
12
  "Socket",
13
13
  ]);
14
- export function readTarEntryInfo(entry) {
15
- const p = typeof entry === "object" && entry !== null && "path" in entry
16
- ? String(entry.path)
17
- : "";
18
- const t = typeof entry === "object" && entry !== null && "type" in entry
19
- ? String(entry.type)
20
- : "";
21
- const s = typeof entry === "object" &&
22
- entry !== null &&
23
- "size" in entry &&
24
- typeof entry.size === "number" &&
25
- Number.isFinite(entry.size)
26
- ? Math.max(0, Math.floor(entry.size))
27
- : 0;
28
- const mode = typeof entry === "object" &&
29
- entry !== null &&
30
- "mode" in entry &&
31
- typeof entry.mode === "number"
32
- ? entry.mode
33
- : undefined;
34
- return { path: p, type: t, size: s, mode };
35
- }
36
14
  export function createTarEntryPreflightChecker(params) {
37
15
  const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
38
16
  const limits = resolveExtractLimits(params.limits);
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA+DA,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,CAqKjF"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA6DA,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,CA0DjF"}
package/dist/archive.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { createTarEntryPreflightChecker } from "./archive-tar.js";
2
+ import { inspectTar, replayTar } from "./archive-tar-stream.js";
3
+ import { runPinnedWriteHelper } from "./pinned-write.js";
1
4
  import fsSync, { constants as fsConstants } from "node:fs";
2
5
  import fs from "node:fs/promises";
3
6
  import path from "node:path";
@@ -6,10 +9,9 @@ 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
- import { createTarEntryPreflightChecker, readTarEntryInfo, } from "./archive-tar.js";
13
15
  import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
14
16
  import { isZipSymlinkEntry, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
15
17
  import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
@@ -19,9 +21,6 @@ import { extractNativeArchive } from "./archive-native.js";
19
21
  import { stageArchiveFileForExtraction } from "./archive-input.js";
20
22
  import { getNativeBinding } from "./native.js";
21
23
  import { resolveArchiveEntryMode, resolveArchiveFilteredEntryPolicy, shouldExtractArchiveEntry, } from "./archive-policy.js";
22
- import { importOptionalTar, NODE_TAR_RATIO_DISABLED, normalizeTarParserError } from "./archive-tar-runtime.js";
23
- import { preflightTarMetadata } from "./archive-tar-meta.js";
24
- import { createTarAdmissionPlan } from "./archive-tar-admission.js";
25
24
  import { writeSiblingTempFile } from "./sibling-temp.js";
26
25
  export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
27
26
  export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
@@ -224,123 +223,16 @@ export async function extractArchive(params) {
224
223
  }));
225
224
  return;
226
225
  }
227
- if (kind === "tar-zstd" || kind === "tar-bzip2") {
228
- throw new FsSafeError("helper-unavailable", `${kind} archives require the matching optional native platform package; ` +
229
- "install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
230
- }
226
+ assertPortableArchiveKind(kind);
231
227
  if (kind === "tar") {
232
228
  await withExtractionDeadline(params.timeoutMs, label, async (deadline) => {
233
- const tar = await importOptionalTar();
234
229
  const stagedArchive = await stageArchiveFileForExtraction({
235
230
  archivePath: params.archivePath,
236
231
  limits,
237
232
  deadline,
238
233
  });
239
234
  try {
240
- const manifest = [];
241
- await preflightTarMetadata({
242
- archivePath: stagedArchive.path,
243
- limits: tarLimits,
244
- signal: deadline.signal,
245
- onMember: (entry) => { manifest.push(entry); },
246
- });
247
- deadline.check();
248
- const destinationRealDir = await prepareArchiveDestinationDir(params.destDir);
249
- await withStagedArchiveDestination({
250
- destinationRealDir,
251
- run: async (stagingDir) => {
252
- deadline.check();
253
- const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
254
- const checkTarEntrySafety = createTarEntryPreflightChecker({
255
- rootDir: destinationRealDir,
256
- stripComponents: params.stripComponents,
257
- limits,
258
- entryFilter: params.entryFilter,
259
- onFiltered,
260
- });
261
- const admission = createTarAdmissionPlan(manifest, (entry) => {
262
- deadline.check();
263
- return checkTarEntrySafety(entry);
264
- }, strip);
265
- const acceptedEntries = [];
266
- // Extract privately, then merge through the safe-open boundary.
267
- const extractor = tar.x({
268
- cwd: stagingDir,
269
- // fs-safe owns stripping: node-tar counts the `.` and empty path
270
- // components that stripArchivePath() drops, so the two disagree.
271
- strip: 0,
272
- gzip: params.tarGzip,
273
- signal: deadline.signal,
274
- preservePaths: false,
275
- noChmod: true,
276
- preserveOwner: false,
277
- noMtime: true,
278
- dmode: 0o700,
279
- strict: true,
280
- maxMetaEntrySize: tarLimits.maxMetaEntryBytes,
281
- maxDecompressionRatio: NODE_TAR_RATIO_DISABLED,
282
- filter(_entryPath, entry) {
283
- try {
284
- const info = readTarEntryInfo(entry);
285
- const relPath = admission.consume(info);
286
- if (!relPath) {
287
- return false;
288
- }
289
- const kind = info.type === "Directory" || info.type === "GNUDumpDir" ? "directory" : "file";
290
- // Keep archived modes before changing node-tar's creation mode.
291
- entry.path = relPath;
292
- acceptedEntries.push({
293
- path: relPath,
294
- kind,
295
- mode: resolveArchiveEntryMode({
296
- kind,
297
- archivedMode: info.mode,
298
- policy: params.entryModes,
299
- }),
300
- });
301
- entry.mode = kind === "directory" ? 0o700 : 0o600;
302
- return true;
303
- }
304
- catch (error) {
305
- // Abort through the parser so pipeline tears down both the
306
- // archive reader and unpacker instead of leaving a paused
307
- // stream behind after a policy rejection.
308
- this.abort(error instanceof Error ? error : new Error(String(error)));
309
- return false;
310
- }
311
- },
312
- onReadEntry(entry) {
313
- try {
314
- deadline.check();
315
- }
316
- catch (err) {
317
- const error = err instanceof Error ? err : new Error(String(err));
318
- // EventEmitter binds `this` to tar.Unpack, exposing abort().
319
- const emitter = this;
320
- emitter.abort?.(error);
321
- }
322
- },
323
- });
324
- try {
325
- await pipeline(fsSync.createReadStream(stagedArchive.path), extractor, {
326
- signal: deadline.signal,
327
- });
328
- }
329
- catch (error) {
330
- throw normalizeTarParserError(createPipelineTimeoutError(error, deadline));
331
- }
332
- admission.finish();
333
- deadline.check();
334
- await mergePlannedArchiveIntoDestination({
335
- entries: acceptedEntries,
336
- sourceDir: stagingDir,
337
- destinationDir: params.destDir,
338
- destinationRealDir,
339
- deadline,
340
- });
341
- deadline.check();
342
- },
343
- });
235
+ await extractWasmTar({ archivePath: stagedArchive.path, options: { ...params, onFiltered }, limits, tarLimits, deadline });
344
236
  }
345
237
  finally {
346
238
  await stagedArchive.cleanup();
@@ -359,3 +251,41 @@ export async function extractArchive(params) {
359
251
  onFiltered,
360
252
  }));
361
253
  }
254
+ async function extractWasmTar(params) {
255
+ const { options, deadline, tarLimits } = params;
256
+ const manifest = [];
257
+ await inspectTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal,
258
+ onMember: (entry) => { manifest.push(entry); } });
259
+ deadline.check();
260
+ const destinationRealDir = await prepareArchiveDestinationDir(options.destDir);
261
+ await withStagedArchiveDestination({ destinationRealDir, run: async (stagingPath) => {
262
+ const stagingDir = await fs.realpath(stagingPath);
263
+ const check = createTarEntryPreflightChecker({ rootDir: destinationRealDir,
264
+ stripComponents: options.stripComponents, limits: params.limits,
265
+ entryFilter: options.entryFilter, onFiltered: options.onFiltered });
266
+ const strip = Math.max(0, Math.floor(options.stripComponents ?? 0));
267
+ const accepted = manifest.filter((entry) => { deadline.check(); return check(entry); }).map((entry) => {
268
+ const kind = entry.type === "Directory" || entry.type === "GNUDumpDir" ? "directory" : "file";
269
+ return { ...entry, path: stripArchivePath(entry.path, strip), kind,
270
+ mode: resolveArchiveEntryMode({ kind, archivedMode: entry.mode, policy: options.entryModes }) };
271
+ });
272
+ await replayTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal, members: accepted,
273
+ async consume(member, payload) {
274
+ deadline.check();
275
+ await preparePrivateArchiveOutputPath({ destinationDir: stagingDir, destinationRealDir: stagingDir,
276
+ relPath: member.path, outPath: path.join(stagingDir, member.path), originalPath: member.path,
277
+ isDirectory: member.kind === "directory", deadline });
278
+ if (member.kind === "file") {
279
+ await runPinnedWriteHelper({ rootPath: stagingDir, relativeParentPath: path.posix.dirname(member.path),
280
+ basename: path.posix.basename(member.path), mkdir: false, mode: 0o600, overwrite: false,
281
+ maxBytes: member.size, input: { kind: "stream", stream: Readable.from(payload) } });
282
+ }
283
+ deadline.check();
284
+ },
285
+ });
286
+ deadline.check();
287
+ await mergePlannedArchiveIntoDestination({ entries: accepted, sourceDir: stagingDir,
288
+ destinationDir: options.destDir, destinationRealDir, deadline });
289
+ deadline.check();
290
+ } });
291
+ }
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,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
  }
@@ -8,6 +8,7 @@ export type UnsafeDeviceReadPathOptions = {
8
8
  platform?: NodeJS.Platform;
9
9
  };
10
10
  export declare const WINDOWS_RESERVED_DEVICE_NAMES: ReadonlySet<string>;
11
+ export declare function isWindowsReservedDeviceName(name: string): boolean;
11
12
  export declare function matchUnsafeDeviceReadPath(filePath: string, options?: UnsafeDeviceReadPathOptions): UnsafeDeviceReadPathMatch | undefined;
12
13
  export declare function isUnsafeDeviceReadPath(filePath: string, options?: UnsafeDeviceReadPathOptions): boolean;
13
14
  export declare function assertNoUnsafeDeviceReadPath(filePath: string, options?: UnsafeDeviceReadPathOptions): void;
@@ -1 +1 @@
1
- {"version":3,"file":"device-path.d.ts","sourceRoot":"","sources":["../src/device-path.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,0BAA0B,GAClC,cAAc,GACd,UAAU,GACV,gBAAgB,CAAC;AAErB,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,0BAA0B,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B,CAAC;AAcF,eAAO,MAAM,6BAA6B,EAAE,WAAW,CAAC,MAAM,CAgC5D,CAAC;AAkFH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,2BAAgC,GACxC,yBAAyB,GAAG,SAAS,CAWvC;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAET;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,2BAA2B,GACpC,IAAI,CAON"}
1
+ {"version":3,"file":"device-path.d.ts","sourceRoot":"","sources":["../src/device-path.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,0BAA0B,GAClC,cAAc,GACd,UAAU,GACV,gBAAgB,CAAC;AAErB,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,0BAA0B,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B,CAAC;AAcF,eAAO,MAAM,6BAA6B,EAAE,WAAW,CAAC,MAAM,CAgC5D,CAAC;AAsEH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE;AAcD,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,2BAAgC,GACxC,yBAAyB,GAAG,SAAS,CAWvC;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAET;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,2BAA2B,GACpC,IAAI,CAON"}
@@ -98,8 +98,11 @@ function normalizeWindowsDeviceBaseName(filePath) {
98
98
  const normalized = trimTrailingWindowsSeparators(filePath.replace(/\//g, "\\"));
99
99
  const lastSegment = normalized.split("\\").filter(Boolean).at(-1) ?? normalized;
100
100
  const withoutStream = lastSegment.split(":")[0] ?? lastSegment;
101
- const withoutTrailingIgnoredChars = trimTrailingWindowsIgnoredChars(withoutStream);
102
- return (withoutTrailingIgnoredChars.split(".")[0] ?? withoutTrailingIgnoredChars).toUpperCase();
101
+ const stem = withoutStream.split(".")[0] ?? withoutStream;
102
+ return trimTrailingWindowsIgnoredChars(stem).toUpperCase();
103
+ }
104
+ export function isWindowsReservedDeviceName(name) {
105
+ return name.length > 0 && WINDOWS_RESERVED_DEVICE_NAMES.has(normalizeWindowsDeviceBaseName(name));
103
106
  }
104
107
  function matchWindowsDeviceReadPath(filePath) {
105
108
  const normalized = filePath.replace(/\//g, "\\");
@@ -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,CAoDrB"}
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"}
@@ -26,9 +26,14 @@ export function ownDirectoryMode(params) {
26
26
  check?.();
27
27
  }),
28
28
  apply: (mode, checks = {}) => enqueue(async () => {
29
+ // inspect() reports permission bits only; chmod ignores file-type bits,
30
+ // so tolerate raw stat modes (e.g. S_IFDIR | 0o755) by masking up front.
31
+ mode &= 0o7777;
29
32
  checks.check?.();
30
33
  const currentMode = await params.inspect();
31
34
  checks.check?.();
35
+ if (currentMode === mode && !checks.beforeChmod && !checks.check)
36
+ return;
32
37
  if (currentMode !== mode) {
33
38
  await params.prepareChmod?.();
34
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";
@@ -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);
@@ -11,7 +11,7 @@ import { writeFileSyncAtomic } from "./file-store-sync-write.js";
11
11
  import { createJsonStore } from "./json-document-store.js";
12
12
  import { stringifyJsonDocument } from "./json-stringify.js";
13
13
  import { isNotFoundPathError, resolveSafeRelativePath, splitSafeRelativePath } from "./path.js";
14
- import { throwFsSafeReadError } from "./read-error.js";
14
+ import { throwFsSafeReadError } from "./root-errors.js";
15
15
  import { root } from "./root.js";
16
16
  import { DEFAULT_ROOT_MAX_BYTES } from "./root-impl.js";
17
17
  import { readRegularFile } from "./regular-file.js";
@@ -0,0 +1,5 @@
1
+ import fs from "node:fs";
2
+ import type { FileHandle } from "node:fs/promises";
3
+ export declare function syncFileBestEffort(handle: Pick<FileHandle, "sync">): Promise<void>;
4
+ export declare function syncFileBestEffortSync(fd: number, fsModule?: Pick<typeof fs, "fsyncSync">): void;
5
+ //# sourceMappingURL=file-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-sync.d.ts","sourceRoot":"","sources":["../src/file-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAMxF;AAED,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,MAAM,EACV,QAAQ,GAAE,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAM,GAC1C,IAAI,CAMN"}
@@ -0,0 +1,19 @@
1
+ import fs from "node:fs";
2
+ export async function syncFileBestEffort(handle) {
3
+ try {
4
+ await handle.sync();
5
+ }
6
+ catch (error) {
7
+ if (error?.code !== "EPERM")
8
+ throw error;
9
+ }
10
+ }
11
+ export function syncFileBestEffortSync(fd, fsModule = fs) {
12
+ try {
13
+ fsModule.fsyncSync(fd);
14
+ }
15
+ catch (error) {
16
+ if (error?.code !== "EPERM")
17
+ throw error;
18
+ }
19
+ }
@@ -1,3 +1,4 @@
1
+ import { type BigIntStats } from "node:fs";
1
2
  import { type AnyAsyncDirectoryGuard, type SyncDirectoryGuard } from "./directory-guard.js";
2
3
  export declare function withAsyncDirectoryGuards<T>(guards: readonly AnyAsyncDirectoryGuard[], mutate: () => Promise<T>, options?: {
3
4
  verifyAfter?: boolean;
@@ -8,6 +9,8 @@ export declare function withSyncDirectoryGuards<T>(guards: readonly SyncDirector
8
9
  }): T;
9
10
  export declare function guardedRename(params: {
10
11
  assertBeforeRename?: () => void;
12
+ onSourceInspected?: (identity: Pick<BigIntStats, "dev" | "ino">) => void;
13
+ onRenamed?: () => void;
11
14
  from: string;
12
15
  to: string;
13
16
  targetRoot?: string;