@openclaw/fs-safe 0.8.6 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/archive-durability.d.ts +24 -0
  3. package/dist/archive-durability.d.ts.map +1 -0
  4. package/dist/archive-durability.js +180 -0
  5. package/dist/archive-gzip-tail.d.ts.map +1 -1
  6. package/dist/archive-gzip-tail.js +2 -1
  7. package/dist/archive-input.js +4 -4
  8. package/dist/archive-kind.d.ts.map +1 -1
  9. package/dist/archive-kind.js +3 -2
  10. package/dist/archive-merge.d.ts +1 -0
  11. package/dist/archive-merge.d.ts.map +1 -1
  12. package/dist/archive-merge.js +41 -8
  13. package/dist/archive-native.d.ts +1 -0
  14. package/dist/archive-native.d.ts.map +1 -1
  15. package/dist/archive-native.js +1 -0
  16. package/dist/archive-options.d.ts +2 -0
  17. package/dist/archive-options.d.ts.map +1 -1
  18. package/dist/archive-parser.wasm +0 -0
  19. package/dist/archive-read.d.ts.map +1 -1
  20. package/dist/archive-read.js +5 -4
  21. package/dist/archive-staging.d.ts +1 -1
  22. package/dist/archive-staging.d.ts.map +1 -1
  23. package/dist/archive-staging.js +24 -19
  24. package/dist/archive.d.ts.map +1 -1
  25. package/dist/archive.js +9 -4
  26. package/dist/copy-publication.d.ts +6 -0
  27. package/dist/copy-publication.d.ts.map +1 -0
  28. package/dist/copy-publication.js +3 -0
  29. package/dist/directory-mode-node.d.ts.map +1 -1
  30. package/dist/directory-mode-node.js +4 -4
  31. package/dist/file-hash.d.ts.map +1 -1
  32. package/dist/file-hash.js +5 -4
  33. package/dist/file-store-sync-write.d.ts +1 -0
  34. package/dist/file-store-sync-write.d.ts.map +1 -1
  35. package/dist/file-store-sync-write.js +9 -6
  36. package/dist/file-store.d.ts +4 -0
  37. package/dist/file-store.d.ts.map +1 -1
  38. package/dist/file-store.js +10 -1
  39. package/dist/fs.d.ts.map +1 -1
  40. package/dist/fs.js +1 -2
  41. package/dist/guarded-mutation.js +1 -1
  42. package/dist/install-path.js +7 -7
  43. package/dist/json-document-store.d.ts +3 -0
  44. package/dist/json-document-store.d.ts.map +1 -1
  45. package/dist/json-document-store.js +1 -0
  46. package/dist/json-durable-queue-directory.js +3 -3
  47. package/dist/json-durable-queue-ownership.js +3 -2
  48. package/dist/json-durable-queue.d.ts.map +1 -1
  49. package/dist/json-durable-queue.js +18 -13
  50. package/dist/move-path-cleanup.d.ts.map +1 -1
  51. package/dist/move-path-cleanup.js +4 -3
  52. package/dist/move-path.d.ts.map +1 -1
  53. package/dist/move-path.js +31 -18
  54. package/dist/opened-file-failure.d.ts +1 -1
  55. package/dist/opened-file-failure.d.ts.map +1 -1
  56. package/dist/opened-file-failure.js +3 -2
  57. package/dist/permissions.js +3 -3
  58. package/dist/private-temp-workspace.d.ts.map +1 -1
  59. package/dist/private-temp-workspace.js +9 -3
  60. package/dist/publish-file.js +21 -21
  61. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  62. package/dist/replace-file-copy-fallback.js +30 -17
  63. package/dist/replace-file-copy-source.d.ts.map +1 -1
  64. package/dist/replace-file-copy-source.js +10 -5
  65. package/dist/replace-file.d.ts.map +1 -1
  66. package/dist/replace-file.js +8 -3
  67. package/dist/root-impl.d.ts +1 -1
  68. package/dist/root-impl.d.ts.map +1 -1
  69. package/dist/root-impl.js +16 -2
  70. package/dist/secret-file.d.ts +1 -0
  71. package/dist/secret-file.d.ts.map +1 -1
  72. package/dist/secret-file.js +1 -0
  73. package/dist/secret-read-async.js +5 -5
  74. package/dist/secure-file.d.ts +1 -1
  75. package/dist/secure-file.d.ts.map +1 -1
  76. package/dist/secure-file.js +19 -9
  77. package/dist/sibling-staged-file.js +7 -7
  78. package/dist/sibling-temp.d.ts.map +1 -1
  79. package/dist/sibling-temp.js +16 -7
  80. package/dist/sidecar-lock-acquire.d.ts +1 -1
  81. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  82. package/dist/sidecar-lock-acquire.js +4 -3
  83. package/dist/sidecar-lock-policy.js +2 -2
  84. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  85. package/dist/sidecar-lock-reclaim.js +16 -4
  86. package/dist/sidecar-lock.d.ts.map +1 -1
  87. package/dist/sidecar-lock.js +2 -1
  88. package/dist/temp-target.d.ts.map +1 -1
  89. package/dist/temp-target.js +9 -4
  90. package/dist/walk.js +2 -2
  91. package/dist/write-open-flags.d.ts.map +1 -1
  92. package/dist/write-open-flags.js +1 -2
  93. package/docs/archive.md +35 -2
  94. package/docs/file-store.md +48 -3
  95. package/docs/json-store.md +10 -0
  96. package/docs/reading.md +6 -0
  97. package/docs/root.md +2 -2
  98. package/docs/secret-file.md +6 -0
  99. package/docs/sidecar-lock.md +2 -0
  100. package/docs/writing.md +5 -3
  101. package/package.json +11 -11
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 - 2026-09-11
4
+
5
+ **Highlights:** Faster bulk extraction and configurable durability, with Windows filesystem fixes and reliable mixed-version lock cleanup.
6
+
7
+ - **Compatibility:** `extractArchive` skips file and directory fsync by default for faster bulk imports; pass `durable: true` to restore the previous durability behavior.
8
+ - Add store-level and per-call `durable` options to FileStore writes, streams, copies, and synchronous writes, plus JSON-store defaults, so reconstructible data can skip fsync while preserving guarded atomic publication.
9
+ - Add `Root.copyIn({ durable: false })`, with per-call, root-default, then `true` precedence, to skip file and parent-directory fsync for reconstructible data.
10
+ - Speed up archive extraction by omitting private staging syncs, deferring opted-in publication durability to one bounded file/directory pass, and sharing duplicate destination guards without weakening containment checks.
11
+ - Keep process-exit lock cleanup working when a legacy shared manager lacks reclaim-guard state, continuing through later managers while preserving retained locks; thanks @metahacker.
12
+ - Publish read-only modes such as `0o400` through the Windows JavaScript write fallback by retaining private `0o600` staging and applying the final mode through the retained handle after rename.
13
+ - Preserve Windows file identity across open-induced `ctime` changes during guarded move fallback, and recreate directory junctions without requiring symlink privileges; thanks @giodl73-repo.
14
+ - Extend synchronous metadata observations to archive, copy, publication, move, and directory-mode paths while keeping data and structural I/O asynchronous and preserving native canonical paths.
15
+ - Keep durable publication coverage reliable under filesystem contention by allowing bounded I/O time and draining unfinished operations before fixture cleanup.
16
+ - Refresh JSZip to 3.10.2, Node declarations to 26.5.1, emnapi runtime build tooling to 2.0.0-alpha.5, the Rust hybrid-array dependency to 0.4.15, and pinned CodeQL actions to 4.38.0.
17
+
3
18
  ## 0.8.6 - 2026-09-07
4
19
 
5
20
  **Highlights:** Clearer guidance for atomic writes on Windows exFAT, with a repeatable filesystem compatibility probe.
@@ -0,0 +1,24 @@
1
+ import { type ExtractionDeadline } from "./archive-deadline.js";
2
+ import type { AsyncDirectoryGuard } from "./directory-guard.js";
3
+ import type { PublishedWriteIdentity } from "./pinned-write.js";
4
+ import type { Root } from "./root.js";
5
+ export type ArchivePublishedFile = {
6
+ relativePath: string;
7
+ identity: PublishedWriteIdentity;
8
+ guards: readonly AsyncDirectoryGuard[];
9
+ };
10
+ export type ArchivePublishedDirectory = {
11
+ guard: AsyncDirectoryGuard;
12
+ parents: readonly AsyncDirectoryGuard[];
13
+ mode: number;
14
+ };
15
+ export declare function finalizeArchivePublication(params: {
16
+ targetRoot: Root;
17
+ destinationGuard: AsyncDirectoryGuard;
18
+ sourceGuard: AsyncDirectoryGuard;
19
+ files: readonly ArchivePublishedFile[];
20
+ directories: readonly ArchivePublishedDirectory[];
21
+ durable: boolean;
22
+ deadline?: ExtractionDeadline;
23
+ }): Promise<void>;
24
+ //# sourceMappingURL=archive-durability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-durability.d.ts","sourceRoot":"","sources":["../src/archive-durability.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAElG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAIhE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAMtC,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,sBAAsB,CAAC;IACjC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACxC,CAAC;AACF,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,mBAAmB,CAAC;IAC3B,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,UAAU,EAAE,IAAI,CAAC;IACjB,gBAAgB,EAAE,mBAAmB,CAAC;IACtC,WAAW,EAAE,mBAAmB,CAAC;IACjC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACvC,WAAW,EAAE,SAAS,yBAAyB,EAAE,CAAC;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,GAAG,OAAO,CAAC,IAAI,CAAC,CA4FhB"}
@@ -0,0 +1,180 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import fsSync from "node:fs";
54
+ import path from "node:path";
55
+ import { ownExtractionDestinationMutation } from "./archive-deadline.js";
56
+ import { assertDirectoryIdentityGuard, assertResolvedInsideDestination, createArchiveSymlinkTraversalError } from "./archive-staging.js";
57
+ import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
58
+ import { pinDirectory, syncDirectory } from "./directory-durability.js";
59
+ import { syncFileBestEffort } from "./file-sync.js";
60
+ import { normalizePinnedWriteError } from "./root-errors.js";
61
+ import { inspectFileIdentity } from "./strict-file-identity.js";
62
+ import { getFsSafeTestHooks } from "./test-hooks.js";
63
+ import { FsSafeError } from "./errors.js";
64
+ export async function finalizeArchivePublication(params) {
65
+ const check = () => params.deadline?.check();
66
+ const assertGuards = async (guards) => {
67
+ for (const guard of [params.destinationGuard, ...guards, params.sourceGuard]) {
68
+ await assertDirectoryIdentityGuard(guard);
69
+ check();
70
+ }
71
+ };
72
+ await ownExtractionDestinationMutation(params.deadline, async () => {
73
+ if (params.durable) {
74
+ // Join every active sync before propagating failure or starting another batch.
75
+ for (let offset = 0; offset < params.files.length; offset += 8) {
76
+ check();
77
+ const results = await Promise.allSettled(params.files.slice(offset, offset + 8).map(async (file) => {
78
+ const env_1 = { stack: [], error: void 0, hasError: false };
79
+ try {
80
+ await assertGuards(file.guards);
81
+ const opened = __addDisposableResource(env_1, await params.targetRoot.open(file.relativePath, { hardlinks: "reject", symlinks: "reject" })
82
+ .catch((error) => {
83
+ if (error instanceof FsSafeError && (error.code === "hardlink" || error.code === "path-alias")) {
84
+ throw createArchiveSymlinkTraversalError(file.relativePath);
85
+ }
86
+ throw error;
87
+ }), true);
88
+ check();
89
+ await inspectFileIdentity(() => fsSync.fstatSync(opened.handle.fd, { bigint: true }), file.identity);
90
+ check();
91
+ await syncFileBestEffort(opened.handle).catch((error) => { throw normalizePinnedWriteError(error); });
92
+ check();
93
+ await assertGuards(file.guards);
94
+ await inspectFileIdentity(async () => {
95
+ const stat = fsSync.lstatSync(opened.realPath, { bigint: true });
96
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink > 1n) {
97
+ throw new FsSafeError("path-mismatch", "archive file changed during durability pass");
98
+ }
99
+ return stat;
100
+ }, file.identity);
101
+ check();
102
+ }
103
+ catch (e_1) {
104
+ env_1.error = e_1;
105
+ env_1.hasError = true;
106
+ }
107
+ finally {
108
+ const result_1 = __disposeResources(env_1);
109
+ if (result_1)
110
+ await result_1;
111
+ }
112
+ }));
113
+ const failure = results.find((result) => result.status === "rejected");
114
+ if (failure?.status === "rejected")
115
+ throw failure.reason;
116
+ }
117
+ }
118
+ // Leave working modes intact until file syncing finishes. Re-pin one directory
119
+ // at a time against its original guard, so wide archives do not exhaust fds.
120
+ const directories = [...params.directories].sort((a, b) => b.guard.dir.split(path.sep).length - a.guard.dir.split(path.sep).length);
121
+ for (const directory of directories) {
122
+ const guards = [...directory.parents, directory.guard];
123
+ await assertGuards(guards);
124
+ const owner = await pinNodeDirectoryForMode(directory.guard.dir);
125
+ let pinned;
126
+ try {
127
+ check();
128
+ await assertGuards(guards);
129
+ if (params.durable && process.platform !== "win32") {
130
+ // An existing search-only directory may become readable in its final mode.
131
+ try {
132
+ pinned = await pinDirectory(directory.guard.dir);
133
+ }
134
+ catch (error) {
135
+ if (error.code !== "EACCES")
136
+ throw error;
137
+ }
138
+ check();
139
+ }
140
+ await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("chmod", directory.guard.dir);
141
+ check();
142
+ await assertGuards(guards);
143
+ await owner.apply(directory.mode, { check, beforeChmod: async () => {
144
+ await assertGuards(guards);
145
+ await assertResolvedInsideDestination({ destinationRealDir: params.destinationGuard.realPath,
146
+ targetPath: directory.guard.dir, originalPath: path.relative(params.targetRoot.rootDir, directory.guard.dir) });
147
+ check();
148
+ } });
149
+ check();
150
+ await assertGuards(guards);
151
+ if (params.durable) {
152
+ if (process.platform === "win32") {
153
+ await syncDirectory(directory.guard.dir).catch((error) => { throw normalizePinnedWriteError(error); });
154
+ }
155
+ else {
156
+ pinned ??= await pinDirectory(directory.guard.dir);
157
+ check();
158
+ await pinned.sync().catch((error) => { throw normalizePinnedWriteError(error); });
159
+ }
160
+ check();
161
+ await assertGuards(guards);
162
+ }
163
+ }
164
+ finally {
165
+ try {
166
+ await pinned?.close();
167
+ }
168
+ finally {
169
+ await owner.close();
170
+ }
171
+ }
172
+ }
173
+ if (params.durable) {
174
+ await assertGuards([]);
175
+ await syncDirectory(params.destinationGuard.realPath).catch((error) => { throw normalizePinnedWriteError(error); });
176
+ check();
177
+ await assertGuards([]);
178
+ }
179
+ });
180
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"archive-gzip-tail.d.ts","sourceRoot":"","sources":["../src/archive-gzip-tail.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC;8EAC8E;AAC9E,qBAAa,SAAU,SAAQ,QAAQ;IAIzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,WAAW,CAAqB;IAExC,YAA6B,OAAO,EAAE,MAAM,EAE3C;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAcxG;IAEQ,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAE9D;IAEQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAGpF;CACF;AAED;6EAC6E;AAC7E,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBvH"}
1
+ {"version":3,"file":"archive-gzip-tail.d.ts","sourceRoot":"","sources":["../src/archive-gzip-tail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC;8EAC8E;AAC9E,qBAAa,SAAU,SAAQ,QAAQ;IAIzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,WAAW,CAAqB;IAExC,YAA6B,OAAO,EAAE,MAAM,EAE3C;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAcxG;IAEQ,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAE9D;IAEQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAGpF;CACF;AAED;6EAC6E;AAC7E,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBvH"}
@@ -1,3 +1,4 @@
1
+ import fsSync from "node:fs";
1
2
  import fs from "node:fs/promises";
2
3
  import { Writable } from "node:stream";
3
4
  import { ArchiveFormatError } from "./archive-errors.js";
@@ -48,7 +49,7 @@ export async function validateGzipContainerTail(filePath, consumed, signal) {
48
49
  signal?.throwIfAborted();
49
50
  const handle = await fs.open(filePath, "r");
50
51
  try {
51
- const { size } = await handle.stat();
52
+ const { size } = fsSync.fstatSync(handle.fd);
52
53
  if (!Number.isSafeInteger(consumed) || consumed <= 0 || consumed > size) {
53
54
  throw new ArchiveFormatError("invalid gzip consumed-input boundary");
54
55
  }
@@ -1,4 +1,4 @@
1
- import { constants as fsConstants } from "node:fs";
1
+ import fsSync, { constants as fsConstants } from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, } from "./archive-limits.js";
@@ -25,7 +25,7 @@ export async function stageArchiveFileForExtraction(params) {
25
25
  params.deadline.check();
26
26
  const sourcePath = path.resolve(params.archivePath);
27
27
  const initialStat = await inspectFileIdentity(async () => {
28
- const stat = await fs.lstat(sourcePath, { bigint: true });
28
+ const stat = fsSync.lstatSync(sourcePath, { bigint: true });
29
29
  if (stat.isSymbolicLink() || !stat.isFile()) {
30
30
  throw new Error(`archive is not a regular file: ${params.archivePath}`);
31
31
  }
@@ -43,13 +43,13 @@ export async function stageArchiveFileForExtraction(params) {
43
43
  fileName: path.basename(sourcePath),
44
44
  });
45
45
  const opened = await inspectFileIdentity(async () => {
46
- const stat = await handle.stat({ bigint: true });
46
+ const stat = fsSync.fstatSync(handle.fd, { bigint: true });
47
47
  if (!stat.isFile())
48
48
  throw new Error("archive changed during validation");
49
49
  return stat;
50
50
  }, initialStat);
51
51
  await inspectFileIdentity(async () => {
52
- const stat = await fs.lstat(sourcePath, { bigint: true });
52
+ const stat = fsSync.lstatSync(sourcePath, { bigint: true });
53
53
  if (stat.isSymbolicLink() || !stat.isFile())
54
54
  throw new Error("archive changed during validation");
55
55
  return stat;
@@ -1 +1 @@
1
- {"version":3,"file":"archive-kind.d.ts","sourceRoot":"","sources":["../src/archive-kind.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,WAAW,GAAG,UAAU,GAAG,KAAK,CAAC;AAmBnE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAcvE;AAED,KAAK,2BAA2B,GAAG;IACjC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAkBF,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAQjE"}
1
+ {"version":3,"file":"archive-kind.d.ts","sourceRoot":"","sources":["../src/archive-kind.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,WAAW,GAAG,UAAU,GAAG,KAAK,CAAC;AAmBnE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAcvE;AAED,KAAK,2BAA2B,GAAG;IACjC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAkBF,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAQjE"}
@@ -1,3 +1,4 @@
1
+ import fsSync from "node:fs";
1
2
  import fs from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { FsSafeError } from "./errors.js";
@@ -37,7 +38,7 @@ async function hasPackedRootMarker(extractDir, rootMarkers) {
37
38
  continue;
38
39
  }
39
40
  try {
40
- await fs.stat(path.join(extractDir, trimmed));
41
+ fsSync.statSync(path.join(extractDir, trimmed));
41
42
  return true;
42
43
  }
43
44
  catch {
@@ -49,7 +50,7 @@ async function hasPackedRootMarker(extractDir, rootMarkers) {
49
50
  export async function resolvePackedRootDir(extractDir, options) {
50
51
  const direct = path.join(extractDir, "package");
51
52
  try {
52
- const stat = await fs.stat(direct);
53
+ const stat = fsSync.statSync(direct);
53
54
  if (stat.isDirectory()) {
54
55
  return direct;
55
56
  }
@@ -12,6 +12,7 @@ type MergeParams = {
12
12
  };
13
13
  export declare function mergePlannedArchiveIntoDestination(params: MergeParams & {
14
14
  entries: readonly ArchivePublicationEntry[];
15
+ durable?: boolean;
15
16
  }): Promise<void>;
16
17
  export declare function mergeExtractedTreeIntoDestination(params: MergeParams): Promise<void>;
17
18
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"archive-merge.d.ts","sourceRoot":"","sources":["../src/archive-merge.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAelG,MAAM,MAAM,uBAAuB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACjG,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,CAAC;AAEF,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,WAAW,GAAG;IAAE,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAA;CAAE,GACpE,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1F"}
1
+ {"version":3,"file":"archive-merge.d.ts","sourceRoot":"","sources":["../src/archive-merge.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAkBlG,MAAM,MAAM,uBAAuB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACjG,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,CAAC;AAEF,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,WAAW,GAAG;IAAE,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvF,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1F"}
@@ -1,3 +1,4 @@
1
+ import fsSync from "node:fs";
1
2
  import fs from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { ownExtractionDestinationMutation } from "./archive-deadline.js";
@@ -10,13 +11,18 @@ import { formatErrorDetail } from "./error-detail.js";
10
11
  import { isPathInside } from "./path.js";
11
12
  import { root } from "./root.js";
12
13
  import { getFsSafeTestHooks } from "./test-hooks.js";
14
+ import { onCopyPublication } from "./copy-publication.js";
15
+ import { syncFileBestEffortSync } from "./file-sync.js";
16
+ import { finalizeArchivePublication } from "./archive-durability.js";
13
17
  export async function mergePlannedArchiveIntoDestination(params) {
14
- await mergeTree(params, params.entries);
18
+ await mergeTree(params, params.entries, params.durable === true);
15
19
  }
16
20
  export async function mergeExtractedTreeIntoDestination(params) {
17
21
  await mergeTree(params);
18
22
  }
19
- async function mergeTree(params, publication) {
23
+ async function mergeTree(params, publication, durable = true) {
24
+ const publishedFiles = [];
25
+ const publishedDirectories = [];
20
26
  const check = () => params.deadline?.check();
21
27
  check();
22
28
  const destinationGuard = await createDirectoryIdentityGuard(params.destinationRealDir);
@@ -29,7 +35,7 @@ async function mergeTree(params, publication) {
29
35
  for (const entry of publication ?? []) {
30
36
  // Resolve admitted spelling in private staging, preserving the volume's case
31
37
  // and Unicode behavior without assigning explicit modes to distinct parents.
32
- const stagedPath = await fs.realpath(path.join(params.sourceDir, entry.path));
38
+ const stagedPath = fsSync.realpathSync.native(path.join(params.sourceDir, entry.path));
33
39
  check();
34
40
  if (!isPathInside(sourceGuard.realPath, stagedPath) || plan.has(stagedPath)) {
35
41
  throw new FsSafeError("path-mismatch", "archive publication paths changed in staging");
@@ -59,11 +65,11 @@ async function mergeTree(params, publication) {
59
65
  const relPath = path.relative(params.sourceDir, sourcePath);
60
66
  const originalPath = relPath.split(path.sep).join("/");
61
67
  const destinationPath = path.join(params.destinationDir, relPath);
62
- const sourceStat = await fs.lstat(sourcePath);
68
+ const sourceStat = fsSync.lstatSync(sourcePath);
63
69
  check();
64
70
  if (sourceStat.isSymbolicLink())
65
71
  throw createArchiveSymlinkTraversalError(originalPath);
66
- const sourceReal = await fs.realpath(sourcePath);
72
+ const sourceReal = fsSync.realpathSync.native(sourcePath);
67
73
  check();
68
74
  if (!isPathInside(sourceGuard.realPath, sourceReal))
69
75
  throw createArchiveSymlinkTraversalError(originalPath);
@@ -78,7 +84,7 @@ async function mergeTree(params, publication) {
78
84
  const mode = plan ? planned?.mode ?? 0o755 : sourceStat.mode & 0o777;
79
85
  await preparePrivateArchiveOutputPath({
80
86
  ...params, relPath, outPath: destinationPath, originalPath, isDirectory: kind === "directory",
81
- }, assertGuards);
87
+ }, assertGuards, destinationGuard);
82
88
  check();
83
89
  if (kind === "directory") {
84
90
  // Ownership spans open, descendants, finalization and close, including timeout.
@@ -98,6 +104,11 @@ async function mergeTree(params, publication) {
98
104
  ancestors.push({ guard, owner });
99
105
  try {
100
106
  await walk(sourcePath);
107
+ if (publication) {
108
+ await assertGuards();
109
+ publishedDirectories.push({ guard, mode, parents: ancestors.slice(0, -1).map((ancestor) => ancestor.guard) });
110
+ return;
111
+ }
101
112
  await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("chmod", destinationPath);
102
113
  check();
103
114
  await assertGuards();
@@ -129,14 +140,32 @@ async function mergeTree(params, publication) {
129
140
  await ownExtractionDestinationMutation(params.deadline, async () => {
130
141
  await assertGuards();
131
142
  try {
132
- await targetRoot.copyIn(relPath, sourcePath, { mkdir: true, mode });
143
+ const options = {
144
+ mkdir: true, mode, durable: publication ? false : true,
145
+ };
146
+ if (publication && durable) {
147
+ options[onCopyPublication] = async (fd, identity) => {
148
+ check();
149
+ if ((mode & 0o400) === 0) {
150
+ // Final mode may prohibit reopening. Sync the writer's still-owned
151
+ // descriptor without widening permissions or syncing its parent.
152
+ syncFileBestEffortSync(fd);
153
+ check();
154
+ }
155
+ else {
156
+ publishedFiles.push({ relativePath: relPath, identity,
157
+ guards: ancestors.map((ancestor) => ancestor.guard) });
158
+ }
159
+ };
160
+ }
161
+ await targetRoot.copyIn(relPath, sourcePath, options);
133
162
  check();
134
163
  await assertGuards();
135
164
  await assertResolvedInsideDestination({
136
165
  destinationRealDir: params.destinationRealDir, targetPath: destinationPath, originalPath,
137
166
  });
138
167
  check();
139
- const stat = await fs.lstat(destinationPath);
168
+ const stat = fsSync.lstatSync(destinationPath);
140
169
  check();
141
170
  if (stat.isSymbolicLink() || (stat.isFile() && stat.nlink > 1)) {
142
171
  throw createArchiveSymlinkTraversalError(originalPath);
@@ -154,4 +183,8 @@ async function mergeTree(params, publication) {
154
183
  }
155
184
  };
156
185
  await walk(params.sourceDir);
186
+ if (publication) {
187
+ await finalizeArchivePublication({ targetRoot, destinationGuard, sourceGuard,
188
+ files: publishedFiles, directories: publishedDirectories, durable, deadline: params.deadline });
189
+ }
157
190
  }
@@ -5,6 +5,7 @@ import type { ExtractArchiveOptions } from "./archive-options.js";
5
5
  import type { NativeBinding } from "./native.js";
6
6
  export declare function throwMappedNativeArchiveError(error: unknown): never;
7
7
  export declare function extractNativeArchive(params: {
8
+ durable?: boolean;
8
9
  binding: NativeBinding;
9
10
  archivePath: string;
10
11
  destDir: string;
@@ -1 +1 @@
1
- {"version":3,"file":"archive-native.d.ts","sourceRoot":"","sources":["../src/archive-native.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAOlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAYnE;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,4BAA4B,CAAC;IACrC,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;CAClD,GAAG,OAAO,CAAC,IAAI,CAAC,CA6FhB"}
1
+ {"version":3,"file":"archive-native.d.ts","sourceRoot":"","sources":["../src/archive-native.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAOlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAYnE;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,4BAA4B,CAAC;IACrC,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;CAClD,GAAG,OAAO,CAAC,IAAI,CAAC,CA8FhB"}
@@ -92,6 +92,7 @@ export async function extractNativeArchive(params) {
92
92
  params.deadline.check();
93
93
  await mergePlannedArchiveIntoDestination({
94
94
  entries: plan,
95
+ durable: params.durable,
95
96
  sourceDir: stagingDir,
96
97
  destinationDir: params.destDir,
97
98
  destinationRealDir,
@@ -9,6 +9,8 @@ export type ExtractArchiveOptions = {
9
9
  archivePath: string;
10
10
  destDir: string;
11
11
  timeoutMs: number;
12
+ /** Sync published files and directories before returning. Defaults to false. */
13
+ durable?: boolean;
12
14
  kind?: ArchiveKind;
13
15
  stripComponents?: number;
14
16
  tarGzip?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"archive-options.d.ts","sourceRoot":"","sources":["../src/archive-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,KAAK,EACV,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC3B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,UAAU,CAAC,EAAE,sBAAsB,CAAC;IACpC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,UAAU,CAAC,EAAE,0BAA0B,CAAC;CACzC,CAAC"}
1
+ {"version":3,"file":"archive-options.d.ts","sourceRoot":"","sources":["../src/archive-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,KAAK,EACV,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC3B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,UAAU,CAAC,EAAE,sBAAsB,CAAC;IACpC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,UAAU,CAAC,EAAE,0BAA0B,CAAC;CACzC,CAAC"}
Binary file
@@ -1 +1 @@
1
- {"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAcA,OAAO,EAAiD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAkLpG,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAAE,GAChD,OAAO,CAAC,MAAM,CAAC,CA6EjB"}
1
+ {"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAeA,OAAO,EAAiD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAkLpG,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAAE,GAChD,OAAO,CAAC,MAAM,CAAC,CA6EjB"}
@@ -1,4 +1,5 @@
1
1
  import { classifyArchiveParserError } from "./archive-parser-errors.js";
2
+ import fsSync from "node:fs";
2
3
  import fs from "node:fs/promises";
3
4
  import { Readable } from "node:stream";
4
5
  import { readFileHandleBounded } from "./bounded-read.js";
@@ -63,9 +64,9 @@ async function readStreamBounded(stream, maxBytes) {
63
64
  return Buffer.concat(chunks, total);
64
65
  }
65
66
  async function stageArchiveInput(archivePath) {
66
- const resolved = await fs.realpath(archivePath);
67
+ const resolved = fsSync.realpathSync.native(archivePath);
67
68
  const before = await inspectFileIdentity(async () => {
68
- const stat = await fs.lstat(archivePath, { bigint: true });
69
+ const stat = fsSync.lstatSync(archivePath, { bigint: true });
69
70
  if (stat.isSymbolicLink() || !stat.isFile()) {
70
71
  throw new Error(`archive is not a regular file: ${archivePath}`);
71
72
  }
@@ -76,13 +77,13 @@ async function stageArchiveInput(archivePath) {
76
77
  try {
77
78
  staged = await tempFile({ prefix: "fs-safe-archive-read", fileName: "archive.bin" });
78
79
  const opened = await inspectFileIdentity(async () => {
79
- const stat = await handle.stat({ bigint: true });
80
+ const stat = fsSync.fstatSync(handle.fd, { bigint: true });
80
81
  if (!stat.isFile())
81
82
  throw new Error("archive changed during validation");
82
83
  return stat;
83
84
  }, before);
84
85
  await inspectFileIdentity(async () => {
85
- const stat = await fs.lstat(resolved, { bigint: true });
86
+ const stat = fsSync.lstatSync(resolved, { bigint: true });
86
87
  if (stat.isSymbolicLink() || !stat.isFile())
87
88
  throw new Error("archive changed during validation");
88
89
  return stat;
@@ -20,7 +20,7 @@ type ArchiveOutputPathParams = {
20
20
  deadline?: ExtractionDeadline;
21
21
  };
22
22
  export declare function prepareArchiveOutputPath(params: ArchiveOutputPathParams): Promise<void>;
23
- export declare function preparePrivateArchiveOutputPath(params: ArchiveOutputPathParams, assertGuards?: () => Promise<void>): Promise<void>;
23
+ export declare function preparePrivateArchiveOutputPath(params: ArchiveOutputPathParams, assertGuards?: () => Promise<void>, destinationGuard?: AsyncDirectoryGuard): Promise<void>;
24
24
  export declare function withStagedArchiveDestination<T>(params: {
25
25
  destinationRealDir: string;
26
26
  stagingDirPrefix?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"archive-staging.d.ts","sourceRoot":"","sources":["../src/archive-staging.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EAErB,MAAM,qBAAqB,CAAC;AAkB7B,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAS1F,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAS5F;AAED,wBAAsB,4BAA4B,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY5F;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BnF;AA0BD,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAahB;AAiBD,KAAK,uBAAuB,GAAG;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,CAAC;AAEF,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7F;AAED,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,uBAAuB,EAAE,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAClE,OAAO,CAAC,IAAI,CAAC,CAEf;AA8GD,wBAAsB,4BAA4B,CAAC,CAAC,EAAE,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACzC,GAAG,OAAO,CAAC,CAAC,CAAC,CA6Bb;AAED,OAAO,EAAE,iCAAiC,EAAE,MAAM,oBAAoB,CAAC;AAEvE,wBAAgB,kCAAkC,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,CAE7F"}
1
+ {"version":3,"file":"archive-staging.d.ts","sourceRoot":"","sources":["../src/archive-staging.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EAErB,MAAM,qBAAqB,CAAC;AAkB7B,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAS1F,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAS5F;AAED,wBAAsB,4BAA4B,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY5F;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BnF;AA0BD,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAahB;AAiBD,KAAK,uBAAuB,GAAG;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,CAAC;AAEF,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7F;AAED,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,uBAAuB,EAAE,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACnE,gBAAgB,CAAC,EAAE,mBAAmB,GACrC,OAAO,CAAC,IAAI,CAAC,CAEf;AAkHD,wBAAsB,4BAA4B,CAAC,CAAC,EAAE,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACzC,GAAG,OAAO,CAAC,CAAC,CAAC,CA6Bb;AAED,OAAO,EAAE,iCAAiC,EAAE,MAAM,oBAAoB,CAAC;AAEvE,wBAAgB,kCAAkC,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,CAE7F"}