@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.
- package/CHANGELOG.md +15 -0
- package/dist/archive-durability.d.ts +24 -0
- package/dist/archive-durability.d.ts.map +1 -0
- package/dist/archive-durability.js +180 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +2 -1
- package/dist/archive-input.js +4 -4
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +3 -2
- package/dist/archive-merge.d.ts +1 -0
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +41 -8
- package/dist/archive-native.d.ts +1 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +1 -0
- package/dist/archive-options.d.ts +2 -0
- package/dist/archive-options.d.ts.map +1 -1
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +5 -4
- package/dist/archive-staging.d.ts +1 -1
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +24 -19
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -4
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -0
- package/dist/copy-publication.js +3 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +4 -4
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +5 -4
- package/dist/file-store-sync-write.d.ts +1 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +9 -6
- package/dist/file-store.d.ts +4 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +10 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +1 -2
- package/dist/guarded-mutation.js +1 -1
- package/dist/install-path.js +7 -7
- package/dist/json-document-store.d.ts +3 -0
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +1 -0
- package/dist/json-durable-queue-directory.js +3 -3
- package/dist/json-durable-queue-ownership.js +3 -2
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +18 -13
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +4 -3
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +31 -18
- package/dist/opened-file-failure.d.ts +1 -1
- package/dist/opened-file-failure.d.ts.map +1 -1
- package/dist/opened-file-failure.js +3 -2
- package/dist/permissions.js +3 -3
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +9 -3
- package/dist/publish-file.js +21 -21
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +30 -17
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -5
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +8 -3
- package/dist/root-impl.d.ts +1 -1
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +16 -2
- package/dist/secret-file.d.ts +1 -0
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +1 -0
- package/dist/secret-read-async.js +5 -5
- package/dist/secure-file.d.ts +1 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +19 -9
- package/dist/sibling-staged-file.js +7 -7
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +16 -7
- package/dist/sidecar-lock-acquire.d.ts +1 -1
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +4 -3
- package/dist/sidecar-lock-policy.js +2 -2
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +16 -4
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -1
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +9 -4
- package/dist/walk.js +2 -2
- package/dist/write-open-flags.d.ts.map +1 -1
- package/dist/write-open-flags.js +1 -2
- package/docs/archive.md +35 -2
- package/docs/file-store.md +48 -3
- package/docs/json-store.md +10 -0
- package/docs/reading.md +6 -0
- package/docs/root.md +2 -2
- package/docs/secret-file.md +6 -0
- package/docs/sidecar-lock.md +2 -0
- package/docs/writing.md +5 -3
- 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":"
|
|
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 } =
|
|
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
|
}
|
package/dist/archive-input.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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":"
|
|
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"}
|
package/dist/archive-kind.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { FsSafeError } from "./errors.js";
|
|
@@ -37,7 +38,7 @@ async function hasPackedRootMarker(extractDir, rootMarkers) {
|
|
|
37
38
|
continue;
|
|
38
39
|
}
|
|
39
40
|
try {
|
|
40
|
-
|
|
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 =
|
|
53
|
+
const stat = fsSync.statSync(direct);
|
|
53
54
|
if (stat.isDirectory()) {
|
|
54
55
|
return direct;
|
|
55
56
|
}
|
package/dist/archive-merge.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/archive-merge.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { 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 =
|
|
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 =
|
|
68
|
+
const sourceStat = fsSync.lstatSync(sourcePath);
|
|
63
69
|
check();
|
|
64
70
|
if (sourceStat.isSymbolicLink())
|
|
65
71
|
throw createArchiveSymlinkTraversalError(originalPath);
|
|
66
|
-
const sourceReal =
|
|
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
|
-
|
|
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 =
|
|
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
|
}
|
package/dist/archive-native.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/archive-native.js
CHANGED
|
@@ -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"}
|
package/dist/archive-parser.wasm
CHANGED
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/archive-read.js
CHANGED
|
@@ -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 =
|
|
67
|
+
const resolved = fsSync.realpathSync.native(archivePath);
|
|
67
68
|
const before = await inspectFileIdentity(async () => {
|
|
68
|
-
const stat =
|
|
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 =
|
|
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 =
|
|
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
|
|
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":"
|
|
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"}
|