@openclaw/fs-safe 0.6.0 → 0.7.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 +50 -0
- package/README.md +7 -0
- package/dist/archive-deadline.d.ts +3 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +44 -8
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +1 -0
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +3 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +26 -16
- package/dist/archive-limits.d.ts +11 -3
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +24 -0
- package/dist/archive-native.d.ts +3 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +24 -9
- package/dist/archive-policy.d.ts +2 -0
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +9 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +69 -41
- package/dist/archive-staging.d.ts +3 -0
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +91 -43
- package/dist/archive-tar-admission.d.ts +7 -0
- package/dist/archive-tar-admission.d.ts.map +1 -0
- package/dist/archive-tar-admission.js +43 -0
- package/dist/archive-tar-gnu.d.ts +2 -0
- package/dist/archive-tar-gnu.d.ts.map +1 -0
- package/dist/archive-tar-gnu.js +20 -0
- package/dist/archive-tar-header.d.ts +8 -0
- package/dist/archive-tar-header.d.ts.map +1 -0
- package/dist/archive-tar-header.js +47 -0
- package/dist/archive-tar-meta.d.ts +29 -1
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +104 -35
- package/dist/archive-tar-runtime.d.ts +3 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +3 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +8 -2
- package/dist/archive-zip-names.js +2 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +38 -26
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +2 -4
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +3 -10
- package/dist/byte-budget.d.ts +5 -0
- package/dist/byte-budget.d.ts.map +1 -0
- package/dist/byte-budget.js +9 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +10 -5
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +9 -5
- package/dist/file-store-limit.d.ts +2 -0
- package/dist/file-store-limit.d.ts.map +1 -0
- package/dist/file-store-limit.js +8 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +38 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +26 -26
- package/dist/json-durable-queue-directory.d.ts +2 -0
- package/dist/json-durable-queue-directory.d.ts.map +1 -0
- package/dist/json-durable-queue-directory.js +20 -0
- package/dist/json-durable-queue-ownership.d.ts +14 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -0
- package/dist/json-durable-queue-ownership.js +168 -0
- package/dist/json-durable-queue-retirement.d.ts +9 -0
- package/dist/json-durable-queue-retirement.d.ts.map +1 -0
- package/dist/json-durable-queue-retirement.js +126 -0
- package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
- package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
- package/dist/json-durable-queue-transfer-lock.js +19 -0
- package/dist/json-durable-queue.d.ts +1 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +90 -57
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +27 -8
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +4 -2
- package/dist/native-binding.d.ts +12 -3
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/output-sibling.d.ts.map +1 -1
- package/dist/output-sibling.js +11 -110
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +4 -2
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +27 -48
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +8 -9
- package/dist/private-temp-workspace.d.ts +3 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +81 -56
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -4
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +6 -4
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +136 -82
- package/dist/replace-file-copy-fallback.d.ts +3 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +21 -36
- package/dist/replace-file-copy-source.d.ts +21 -0
- package/dist/replace-file-copy-source.d.ts.map +1 -0
- package/dist/replace-file-copy-source.js +112 -0
- package/dist/replace-file-descriptor.d.ts +13 -3
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +32 -7
- package/dist/replace-file-rename-policy.d.ts +7 -0
- package/dist/replace-file-rename-policy.d.ts.map +1 -0
- package/dist/replace-file-rename-policy.js +30 -0
- package/dist/replace-file-temp-owner.d.ts +46 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -0
- package/dist/replace-file-temp-owner.js +346 -0
- package/dist/replace-file.d.ts +6 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +72 -58
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +40 -26
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +6 -3
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +6 -5
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +17 -13
- package/dist/sibling-staged-file.d.ts +15 -0
- package/dist/sibling-staged-file.d.ts.map +1 -0
- package/dist/sibling-staged-file.js +148 -0
- package/dist/sibling-temp.d.ts +3 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +30 -74
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +48 -27
- package/dist/sidecar-lock-handle.d.ts +6 -2
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +17 -3
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +29 -0
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +29 -7
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +28 -16
- package/dist/suppressed-error.d.ts +6 -0
- package/dist/suppressed-error.d.ts.map +1 -0
- package/dist/suppressed-error.js +15 -0
- package/dist/temp-cleanup.d.ts +2 -0
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +25 -10
- package/dist/temp-workspace-owner.d.ts +23 -0
- package/dist/temp-workspace-owner.d.ts.map +1 -0
- package/dist/temp-workspace-owner.js +320 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/test-hooks.d.ts +5 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/docs/advanced.md +8 -1
- package/docs/archive.md +194 -26
- package/docs/atomic.md +11 -2
- package/docs/durability.md +1 -1
- package/docs/file-store.md +2 -0
- package/docs/install.md +8 -4
- package/docs/json.md +8 -4
- package/docs/native-helper.md +15 -3
- package/docs/native.md +33 -6
- package/docs/output.md +10 -0
- package/docs/public-api.md +6 -3
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +1 -1
- package/docs/regular-file.md +9 -2
- package/docs/root.md +2 -0
- package/docs/secret-file.md +5 -1
- package/docs/secure-file.md +4 -2
- package/docs/security-model.md +18 -0
- package/docs/sidecar-lock.md +4 -0
- package/docs/store.md +13 -0
- package/docs/temp.md +128 -14
- package/package.json +10 -10
package/dist/root-impl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,KAAK,EAAE,MAAM,SAAS,CAAC;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"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"}
|
package/dist/root-impl.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { constants as fsConstants } from "node:fs";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
5
6
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, createNearestExistingDirectoryGuard } from "./directory-guard.js";
|
|
6
7
|
import { FsSafeError } from "./errors.js";
|
|
7
8
|
import { syncDirectoryBestEffort } from "./fsync.js";
|
|
@@ -148,6 +149,7 @@ async function openVerifiedLocalFile(filePath, options) {
|
|
|
148
149
|
}
|
|
149
150
|
return pathStat;
|
|
150
151
|
}, identity);
|
|
152
|
+
await fsSafeTestHooks?.afterOpenedPathIdentityCheck?.(filePath, handle);
|
|
151
153
|
const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath);
|
|
152
154
|
await inspectFileIdentity(async () => {
|
|
153
155
|
const realStat = await fs.stat(realPath, { bigint: true });
|
|
@@ -156,7 +158,7 @@ async function openVerifiedLocalFile(filePath, options) {
|
|
|
156
158
|
}
|
|
157
159
|
return realStat;
|
|
158
160
|
}, identity);
|
|
159
|
-
return openResult({ handle, realPath, stat });
|
|
161
|
+
return { opened: openResult({ handle, realPath, stat }), identity };
|
|
160
162
|
}
|
|
161
163
|
catch (err) {
|
|
162
164
|
await handle.close().catch(() => { });
|
|
@@ -210,8 +212,7 @@ class RootHandle {
|
|
|
210
212
|
async read(relativePath, options = {}) {
|
|
211
213
|
return await readFileInRoot(this.context, {
|
|
212
214
|
relativePath,
|
|
213
|
-
...
|
|
214
|
-
...options,
|
|
215
|
+
...mergeReadOptions(this.defaults, options),
|
|
215
216
|
});
|
|
216
217
|
}
|
|
217
218
|
async readBytes(relativePath, options = {}) {
|
|
@@ -227,8 +228,7 @@ class RootHandle {
|
|
|
227
228
|
async readAbsolute(filePath, options = {}) {
|
|
228
229
|
return await readPathInRoot(this.context, {
|
|
229
230
|
filePath,
|
|
230
|
-
...
|
|
231
|
-
...options,
|
|
231
|
+
...mergeReadOptions(this.defaults, options),
|
|
232
232
|
});
|
|
233
233
|
}
|
|
234
234
|
reader(options = {}) {
|
|
@@ -313,13 +313,14 @@ class RootHandle {
|
|
|
313
313
|
}
|
|
314
314
|
async copyIn(relativePath, sourcePath, options = {}) {
|
|
315
315
|
assertValidRootDestinationPath(relativePath);
|
|
316
|
+
const { maxBytes, ...copyOptions } = this.mutationOptions(options);
|
|
316
317
|
await copyFileInRoot(this.context, {
|
|
317
318
|
sourcePath,
|
|
318
319
|
relativePath,
|
|
319
|
-
maxBytes: this.defaults.maxBytes,
|
|
320
|
+
maxBytes: normalizeMaxBytes(maxBytes, { defaultValue: this.defaults.maxBytes }),
|
|
320
321
|
mkdir: this.defaults.mkdir,
|
|
321
322
|
mode: this.defaults.mode,
|
|
322
|
-
...
|
|
323
|
+
...copyOptions,
|
|
323
324
|
});
|
|
324
325
|
}
|
|
325
326
|
async exists(relativePath) {
|
|
@@ -369,12 +370,24 @@ class RootHandle {
|
|
|
369
370
|
function readDefaults(defaults) {
|
|
370
371
|
return {
|
|
371
372
|
hardlinks: defaults.hardlinks,
|
|
372
|
-
maxBytes: defaults.maxBytes
|
|
373
|
+
maxBytes: normalizeMaxBytes(defaults.maxBytes, { defaultValue: DEFAULT_ROOT_MAX_BYTES }),
|
|
373
374
|
nonBlockingRead: defaults.nonBlockingRead,
|
|
374
375
|
symlinks: defaults.symlinks,
|
|
375
376
|
};
|
|
376
377
|
}
|
|
378
|
+
function mergeReadOptions(defaults, options) {
|
|
379
|
+
const merged = readDefaults(defaults);
|
|
380
|
+
if (options.hardlinks !== undefined)
|
|
381
|
+
merged.hardlinks = options.hardlinks;
|
|
382
|
+
merged.maxBytes = normalizeMaxBytes(options.maxBytes, { defaultValue: merged.maxBytes });
|
|
383
|
+
if (options.nonBlockingRead !== undefined)
|
|
384
|
+
merged.nonBlockingRead = options.nonBlockingRead;
|
|
385
|
+
if (options.symlinks !== undefined)
|
|
386
|
+
merged.symlinks = options.symlinks;
|
|
387
|
+
return merged;
|
|
388
|
+
}
|
|
377
389
|
export async function root(rootDir, defaults = {}) {
|
|
390
|
+
normalizeMaxBytes(defaults.maxBytes);
|
|
378
391
|
return new RootHandle(await resolveRootContext(rootDir), defaults);
|
|
379
392
|
}
|
|
380
393
|
async function openFileInRoot(root, params) {
|
|
@@ -385,10 +398,10 @@ async function openFileInRoot(root, params) {
|
|
|
385
398
|
});
|
|
386
399
|
let opened;
|
|
387
400
|
try {
|
|
388
|
-
opened = await openVerifiedLocalFile(resolved, {
|
|
401
|
+
({ opened } = await openVerifiedLocalFile(resolved, {
|
|
389
402
|
nonBlockingRead: params.nonBlockingRead,
|
|
390
403
|
symlinks: params.symlinks,
|
|
391
|
-
});
|
|
404
|
+
}));
|
|
392
405
|
}
|
|
393
406
|
catch (err) {
|
|
394
407
|
if (err instanceof FsSafeError) {
|
|
@@ -430,9 +443,10 @@ async function readPathInRoot(root, params) {
|
|
|
430
443
|
});
|
|
431
444
|
}
|
|
432
445
|
export async function readLocalFileSafely(params) {
|
|
446
|
+
const maxBytes = normalizeMaxBytes(params.maxBytes);
|
|
433
447
|
const opened = await openLocalFileSafely({ filePath: params.filePath });
|
|
434
448
|
try {
|
|
435
|
-
return await readOpenedFileSafely({ opened, maxBytes
|
|
449
|
+
return await readOpenedFileSafely({ opened, maxBytes });
|
|
436
450
|
}
|
|
437
451
|
finally {
|
|
438
452
|
await opened.handle.close().catch(() => { });
|
|
@@ -440,7 +454,7 @@ export async function readLocalFileSafely(params) {
|
|
|
440
454
|
}
|
|
441
455
|
export async function openLocalFileSafely(params) {
|
|
442
456
|
assertNoNulPathInput(params.filePath, "file path contains a NUL byte");
|
|
443
|
-
return await openVerifiedLocalFile(params.filePath);
|
|
457
|
+
return (await openVerifiedLocalFile(params.filePath)).opened;
|
|
444
458
|
}
|
|
445
459
|
function emitWriteBoundaryWarning(reason) {
|
|
446
460
|
logWarn(`security: fs-safe write boundary warning (${reason})`);
|
|
@@ -743,7 +757,7 @@ async function commitPinnedWriteInRoot(root, pinned, params) {
|
|
|
743
757
|
async function copyFileInRoot(root, params) {
|
|
744
758
|
assertValidRootRelativePath(params.relativePath);
|
|
745
759
|
assertNoNulPathInput(params.sourcePath, "source path contains a NUL byte");
|
|
746
|
-
const source = await openVerifiedLocalFile(params.sourcePath, {
|
|
760
|
+
const { opened: source, identity: sourceIdentity } = await openVerifiedLocalFile(params.sourcePath, {
|
|
747
761
|
hardlinks: params.sourceHardlinks,
|
|
748
762
|
});
|
|
749
763
|
if (params.maxBytes !== undefined && source.stat.size > params.maxBytes) {
|
|
@@ -754,7 +768,7 @@ async function copyFileInRoot(root, params) {
|
|
|
754
768
|
await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
|
|
755
769
|
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
|
|
756
770
|
await serializePathWrite(pinned.targetPath, async () => {
|
|
757
|
-
await assertCopySourceCurrent(source);
|
|
771
|
+
await assertCopySourceCurrent(source, sourceIdentity);
|
|
758
772
|
let identity;
|
|
759
773
|
try {
|
|
760
774
|
identity = await runPinnedWriteHelper({
|
|
@@ -773,7 +787,7 @@ async function copyFileInRoot(root, params) {
|
|
|
773
787
|
throw normalizePinnedWriteError(error);
|
|
774
788
|
}
|
|
775
789
|
try {
|
|
776
|
-
await assertCopySourcePathCurrent(source);
|
|
790
|
+
await assertCopySourcePathCurrent(source, sourceIdentity);
|
|
777
791
|
}
|
|
778
792
|
catch (error) {
|
|
779
793
|
await removePathIfIdentityUnchanged(pinned.targetPath, identity).catch(() => undefined);
|
|
@@ -786,18 +800,18 @@ async function copyFileInRoot(root, params) {
|
|
|
786
800
|
await source.handle.close().catch(() => { });
|
|
787
801
|
}
|
|
788
802
|
}
|
|
789
|
-
async function assertCopySourceCurrent(source) {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
throw new FsSafeError("path-mismatch", "copy source descriptor changed");
|
|
793
|
-
}
|
|
794
|
-
await assertCopySourcePathCurrent(source);
|
|
803
|
+
async function assertCopySourceCurrent(source, identity) {
|
|
804
|
+
await inspectFileIdentity(() => source.handle.stat({ bigint: true }), identity);
|
|
805
|
+
await assertCopySourcePathCurrent(source, identity);
|
|
795
806
|
}
|
|
796
|
-
async function assertCopySourcePathCurrent(source) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
807
|
+
async function assertCopySourcePathCurrent(source, identity) {
|
|
808
|
+
await inspectFileIdentity(async () => {
|
|
809
|
+
const current = await fs.lstat(source.realPath, { bigint: true });
|
|
810
|
+
if (current.isSymbolicLink() || !current.isFile()) {
|
|
811
|
+
throw new FsSafeError("path-mismatch", "copy source path changed");
|
|
812
|
+
}
|
|
813
|
+
return current;
|
|
814
|
+
}, identity);
|
|
801
815
|
}
|
|
802
816
|
async function removePathIfIdentityUnchanged(targetPath, identity) {
|
|
803
817
|
const parentGuard = await createAsyncDirectoryGuard(path.dirname(targetPath));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAaA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAIjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAwER;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AAkID,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAkEF,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF"}
|
package/dist/secret-file.js
CHANGED
|
@@ -3,11 +3,13 @@ import fsp from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
|
|
5
5
|
import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
6
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
6
7
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
7
8
|
import { FsSafeError } from "./errors.js";
|
|
8
9
|
import { sameFileIdentity } from "./file-identity.js";
|
|
9
10
|
import { resolveHomeRelativePath } from "./home-dir.js";
|
|
10
11
|
import { openPinnedFileSync } from "./pinned-open.js";
|
|
12
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
11
13
|
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
12
14
|
import { assertSecretFilePreview, DEFAULT_SECRET_FILE_MAX_BYTES, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
|
|
13
15
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
@@ -19,7 +21,9 @@ export function readSecretFileSync(filePath, label, options = {}) {
|
|
|
19
21
|
if (!resolvedPath) {
|
|
20
22
|
throw new FsSafeError("invalid-path", `${label} file path is empty.`, { cause: undefined });
|
|
21
23
|
}
|
|
22
|
-
const maxBytes = options.maxBytes
|
|
24
|
+
const maxBytes = normalizeMaxBytes(options.maxBytes, {
|
|
25
|
+
defaultValue: DEFAULT_SECRET_FILE_MAX_BYTES,
|
|
26
|
+
});
|
|
23
27
|
function inspectInput(symlinkMessage) {
|
|
24
28
|
const stat = options.rejectSymlink
|
|
25
29
|
? fs.lstatSync(resolvedPath, { bigint: true })
|
|
@@ -112,8 +116,7 @@ async function enforcePrivatePathMode(resolvedPath, expectedMode, kind) {
|
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
118
|
async function enforcePrivateFileIdentityAndMode(resolvedPath, expectedIdentity, expectedMode) {
|
|
115
|
-
const
|
|
116
|
-
const handle = await fsp.open(resolvedPath, fs.constants.O_RDONLY | noFollowFlag);
|
|
119
|
+
const handle = await fsp.open(resolvedPath, resolveReadOpenFlags());
|
|
117
120
|
try {
|
|
118
121
|
const openedStat = await handle.stat();
|
|
119
122
|
if (!openedStat.isFile() || !sameFileIdentity(openedStat, expectedIdentity)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAQA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CA8DjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { readFileHandleBounded } from "./bounded-read.js";
|
|
4
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
4
5
|
import { FsSafeError } from "./errors.js";
|
|
5
6
|
import { resolveHomeRelativePath } from "./home-dir.js";
|
|
7
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
6
8
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
7
9
|
import { assertSecretFilePreview, DEFAULT_SECRET_FILE_MAX_BYTES, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
|
|
8
10
|
export async function readSecretFile(filePath, label, options = {}) {
|
|
@@ -10,7 +12,9 @@ export async function readSecretFile(filePath, label, options = {}) {
|
|
|
10
12
|
if (!resolvedPath) {
|
|
11
13
|
throw new FsSafeError("invalid-path", `${label} file path is empty.`, { cause: undefined });
|
|
12
14
|
}
|
|
13
|
-
const maxBytes = options.maxBytes
|
|
15
|
+
const maxBytes = normalizeMaxBytes(options.maxBytes, {
|
|
16
|
+
defaultValue: DEFAULT_SECRET_FILE_MAX_BYTES,
|
|
17
|
+
});
|
|
14
18
|
async function inspectInput(symlinkMessage) {
|
|
15
19
|
const stat = options.rejectSymlink
|
|
16
20
|
? await fs.lstat(resolvedPath, { bigint: true })
|
|
@@ -32,10 +36,7 @@ export async function readSecretFile(filePath, label, options = {}) {
|
|
|
32
36
|
let raw;
|
|
33
37
|
try {
|
|
34
38
|
const realPath = await fs.realpath(resolvedPath);
|
|
35
|
-
|
|
36
|
-
? fsSync.constants.O_NOFOLLOW
|
|
37
|
-
: 0;
|
|
38
|
-
handle = await fs.open(realPath, fsSync.constants.O_RDONLY | noFollow);
|
|
39
|
+
handle = await fs.open(realPath, resolveReadOpenFlags());
|
|
39
40
|
const openedHandle = handle;
|
|
40
41
|
const openedStat = await inspectFileIdentity(async () => {
|
|
41
42
|
const stat = await openedHandle.stat({ bigint: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAarC,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAG1B,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAC1C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,EAAE,CAAC,EAAE,mBAAmB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B,CAAC;AA4LF,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAe/B"}
|
package/dist/secure-file.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { constants as fsConstants } from "node:fs";
|
|
2
1
|
import fs from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { readFileHandleBounded } from "./bounded-read.js";
|
|
4
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
5
5
|
import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { isWindowsDriveLetterPath, isWindowsNetworkPath } from "./local-file-access.js";
|
|
8
8
|
import { isPathInside, isSymlinkOpenError } from "./path.js";
|
|
9
9
|
import { formatPermissionErrorDetail } from "./permission-exec.js";
|
|
10
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
10
11
|
import { inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, } from "./permissions.js";
|
|
11
12
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
12
|
-
const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsConstants;
|
|
13
|
-
const OPEN_READ_FLAGS = fsConstants.O_RDONLY | (SUPPORTS_NOFOLLOW ? fsConstants.O_NOFOLLOW : 0);
|
|
14
13
|
function isAbsolutePathname(value) {
|
|
15
14
|
return (path.isAbsolute(value) ||
|
|
16
15
|
(process.platform === "win32" &&
|
|
@@ -19,7 +18,7 @@ function isAbsolutePathname(value) {
|
|
|
19
18
|
function label(options) {
|
|
20
19
|
return options.label ?? "Secure file";
|
|
21
20
|
}
|
|
22
|
-
async function openSecureHandle(options) {
|
|
21
|
+
async function openSecureHandle(options, maxBytes) {
|
|
23
22
|
assertNoUnsafeDeviceReadPath(options.filePath);
|
|
24
23
|
if (isWindowsNetworkPath(options.filePath, "win32") && !options.trust?.allowNetworkPath) {
|
|
25
24
|
throw new FsSafeError("invalid-path", `${label(options)} must be a local absolute path.`);
|
|
@@ -32,15 +31,19 @@ async function openSecureHandle(options) {
|
|
|
32
31
|
cause: err,
|
|
33
32
|
});
|
|
34
33
|
});
|
|
35
|
-
if (preStat.
|
|
36
|
-
|
|
34
|
+
if (preStat.isSymbolicLink()) {
|
|
35
|
+
if (!options.trust?.allowSymlink) {
|
|
36
|
+
throw new FsSafeError("symlink", `${label(options)} must not be a symlink: ${options.filePath}`);
|
|
37
|
+
}
|
|
37
38
|
}
|
|
38
|
-
if (preStat.
|
|
39
|
-
throw new FsSafeError("
|
|
39
|
+
else if (!preStat.isFile()) {
|
|
40
|
+
throw new FsSafeError("not-file", `${label(options)} must be a file: ${options.filePath}`);
|
|
40
41
|
}
|
|
41
42
|
let handle;
|
|
42
43
|
try {
|
|
43
|
-
handle = await fs.open(options.filePath,
|
|
44
|
+
handle = await fs.open(options.filePath, resolveReadOpenFlags({
|
|
45
|
+
followSymlinks: options.trust?.allowSymlink === true,
|
|
46
|
+
}));
|
|
44
47
|
}
|
|
45
48
|
catch (err) {
|
|
46
49
|
if (isSymlinkOpenError(err)) {
|
|
@@ -65,8 +68,8 @@ async function openSecureHandle(options) {
|
|
|
65
68
|
}, openedIdentity);
|
|
66
69
|
const realPath = await fs.realpath(options.filePath);
|
|
67
70
|
await inspectFileIdentity(() => fs.stat(realPath, { bigint: true }), openedIdentity);
|
|
68
|
-
if (
|
|
69
|
-
throw new FsSafeError("too-large", `${label(options)} exceeded maxBytes (${
|
|
71
|
+
if (maxBytes !== undefined && openedStat.size > maxBytes) {
|
|
72
|
+
throw new FsSafeError("too-large", `${label(options)} exceeded maxBytes (${maxBytes}).`);
|
|
70
73
|
}
|
|
71
74
|
return { handle, pathStat: openedStat, realPath };
|
|
72
75
|
}
|
|
@@ -165,11 +168,12 @@ async function readHandleWithTimeout(handle, timeoutMs, maxBytes) {
|
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
export async function readSecureFile(options) {
|
|
168
|
-
const
|
|
171
|
+
const maxBytes = normalizeMaxBytes(options.io?.maxBytes);
|
|
172
|
+
const opened = await openSecureHandle(options, maxBytes);
|
|
169
173
|
try {
|
|
170
174
|
await assertTrustedDirs(options, opened.realPath);
|
|
171
175
|
const permissions = await assertSecurePermissions(options, opened.pathStat, opened.realPath);
|
|
172
|
-
const buffer = await readHandleWithTimeout(opened.handle, options.io?.timeoutMs,
|
|
176
|
+
const buffer = await readHandleWithTimeout(opened.handle, options.io?.timeoutMs, maxBytes);
|
|
173
177
|
return { buffer, realPath: opened.realPath, stat: opened.pathStat, permissions };
|
|
174
178
|
}
|
|
175
179
|
finally {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function writeCallbackSibling<T>(params: {
|
|
2
|
+
tempPath: string;
|
|
3
|
+
write: (tempPath: string) => Promise<T>;
|
|
4
|
+
resolveFinalPath: (result: T) => string;
|
|
5
|
+
mode?: number;
|
|
6
|
+
/** Preserve the caller's historical best-effort mode behavior. */
|
|
7
|
+
ignoreModeError?: boolean;
|
|
8
|
+
maxBytes?: number;
|
|
9
|
+
syncTempFile: boolean;
|
|
10
|
+
syncParentDir: boolean;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
filePath: string;
|
|
13
|
+
result: T;
|
|
14
|
+
}>;
|
|
15
|
+
//# sourceMappingURL=sibling-staged-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sibling-staged-file.d.ts","sourceRoot":"","sources":["../src/sibling-staged-file.ts"],"names":[],"mappings":"AAkCA,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CA6G3C"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fsSync, {} from "node:fs";
|
|
2
|
+
import fs, {} from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
5
|
+
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
6
|
+
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
8
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
9
|
+
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
10
|
+
import { serializePathWrite } from "./write-queue.js";
|
|
11
|
+
function assertRegularFile(stat) {
|
|
12
|
+
if (stat.isSymbolicLink()) {
|
|
13
|
+
throw new FsSafeError("symlink", "symlink sibling temp not allowed");
|
|
14
|
+
}
|
|
15
|
+
if (!stat.isFile()) {
|
|
16
|
+
throw new FsSafeError("not-file", "sibling temp must be a regular file");
|
|
17
|
+
}
|
|
18
|
+
if (stat.nlink !== 1n) {
|
|
19
|
+
throw new FsSafeError("hardlink", "sibling temp must have exactly one link");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function inspectStage(inspect, expected) {
|
|
23
|
+
return await inspectFileIdentity(async () => {
|
|
24
|
+
const stat = await inspect();
|
|
25
|
+
assertRegularFile(stat);
|
|
26
|
+
return stat;
|
|
27
|
+
}, expected);
|
|
28
|
+
}
|
|
29
|
+
// Callback paths are not owned until all three admission observations agree.
|
|
30
|
+
// Keep one descriptor and one exact identity through mode, sync, rename and cleanup.
|
|
31
|
+
// Read/write access is needed only when the caller requests file synchronization.
|
|
32
|
+
export async function writeCallbackSibling(params) {
|
|
33
|
+
const parent = path.dirname(params.tempPath);
|
|
34
|
+
const guard = await createAsyncDirectoryGuard(parent);
|
|
35
|
+
const parentIdentity = await inspectFileIdentity(() => fs.lstat(parent, { bigint: true }));
|
|
36
|
+
const assertParent = async () => {
|
|
37
|
+
await assertAsyncDirectoryGuard(guard);
|
|
38
|
+
await inspectFileIdentity(() => fs.lstat(parent, { bigint: true }), parentIdentity);
|
|
39
|
+
};
|
|
40
|
+
let handle;
|
|
41
|
+
let identity;
|
|
42
|
+
let unregister;
|
|
43
|
+
let renamed = false;
|
|
44
|
+
let failure;
|
|
45
|
+
const inspectPath = (pathname, expected) => inspectStage(() => fs.lstat(pathname, { bigint: true }), expected);
|
|
46
|
+
const assertCurrent = async (pathname) => {
|
|
47
|
+
await assertParent();
|
|
48
|
+
const opened = await inspectStage(() => handle.stat({ bigint: true }), identity);
|
|
49
|
+
const current = await inspectPath(pathname, opened);
|
|
50
|
+
if (params.maxBytes !== undefined &&
|
|
51
|
+
(opened.size > params.maxBytes || current.size > params.maxBytes)) {
|
|
52
|
+
throw new FsSafeError("too-large", `sibling temp exceeds maxBytes (${params.maxBytes})`);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
const result = await params.write(params.tempPath);
|
|
57
|
+
await assertParent();
|
|
58
|
+
const before = await inspectPath(params.tempPath);
|
|
59
|
+
try {
|
|
60
|
+
// No create/truncate flags; O_NONBLOCK also bounds a FIFO swap during open.
|
|
61
|
+
const access = params.syncTempFile ? fsSync.constants.O_RDWR : fsSync.constants.O_RDONLY;
|
|
62
|
+
handle = await fs.open(params.tempPath, access | resolveReadOpenFlags());
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error?.code === "ELOOP") {
|
|
66
|
+
throw new FsSafeError("symlink", "symlink sibling temp not allowed", { cause: error });
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
const opened = await inspectStage(() => handle.stat({ bigint: true }), before);
|
|
71
|
+
await inspectPath(params.tempPath, opened);
|
|
72
|
+
await assertParent();
|
|
73
|
+
identity = opened;
|
|
74
|
+
unregister = registerTempPathForExit(params.tempPath, { identity, singleLinkFile: true });
|
|
75
|
+
const filePath = path.resolve(params.resolveFinalPath(result));
|
|
76
|
+
if (path.dirname(filePath) !== parent) {
|
|
77
|
+
throw new Error("Final path must be in the sibling temp directory.");
|
|
78
|
+
}
|
|
79
|
+
if (filePath === params.tempPath) {
|
|
80
|
+
throw new FsSafeError("invalid-path", "final path must differ from the sibling temp path");
|
|
81
|
+
}
|
|
82
|
+
await serializePathWrite(filePath, async () => {
|
|
83
|
+
await assertCurrent(params.tempPath);
|
|
84
|
+
if (params.mode !== undefined) {
|
|
85
|
+
try {
|
|
86
|
+
await handle.chmod(params.mode);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (!params.ignoreModeError)
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (params.syncTempFile) {
|
|
94
|
+
await assertCurrent(params.tempPath);
|
|
95
|
+
try {
|
|
96
|
+
await handle.sync();
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error?.code !== "EPERM")
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
await assertCurrent(params.tempPath);
|
|
104
|
+
await fs.rename(params.tempPath, filePath);
|
|
105
|
+
// A later verification failure never authorizes rollback of the final name.
|
|
106
|
+
renamed = true;
|
|
107
|
+
unregister();
|
|
108
|
+
await assertCurrent(filePath);
|
|
109
|
+
if (params.syncParentDir)
|
|
110
|
+
await syncDirectoryBestEffort(parent);
|
|
111
|
+
await assertCurrent(filePath);
|
|
112
|
+
});
|
|
113
|
+
return { filePath, result };
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
failure = { error };
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
try {
|
|
121
|
+
if (!renamed && identity) {
|
|
122
|
+
try {
|
|
123
|
+
await assertParent();
|
|
124
|
+
await inspectStage(() => handle.stat({ bigint: true }), identity);
|
|
125
|
+
await inspectPath(params.tempPath, identity);
|
|
126
|
+
await fs.unlink(params.tempPath);
|
|
127
|
+
unregister?.();
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
// Preserve observed substitutes; retry only operational cleanup failures.
|
|
131
|
+
if (error instanceof FsSafeError || error?.code === "ENOENT") {
|
|
132
|
+
unregister?.();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
try {
|
|
139
|
+
await handle?.close();
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (failure)
|
|
143
|
+
throw new AggregateError([failure.error, error], "sibling publication and close failed");
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
package/dist/sibling-temp.d.ts
CHANGED
|
@@ -5,8 +5,11 @@ export type WriteSiblingTempFileOptions<T> = {
|
|
|
5
5
|
tempPrefix?: string;
|
|
6
6
|
dirMode?: number;
|
|
7
7
|
chmodDir?: boolean;
|
|
8
|
+
/** Final file mode; omitted preserves the producer's mode. Applied through the retained descriptor. */
|
|
8
9
|
mode?: number;
|
|
10
|
+
/** Sync the staged descriptor before rename; defaults to false. */
|
|
9
11
|
syncTempFile?: boolean;
|
|
12
|
+
/** Best-effort parent directory sync after rename; defaults to false. */
|
|
10
13
|
syncParentDir?: boolean;
|
|
11
14
|
};
|
|
12
15
|
export type WriteSiblingTempFileResult<T> = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAoBxC;AAkBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0ChB"}
|