@openclaw/fs-safe 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +19 -6
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +20 -12
- package/dist/archive-entry.d.ts +1 -0
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +37 -6
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +4 -0
- package/dist/archive-input.d.ts +7 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +11 -1
- package/dist/archive-limits.js +1 -1
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +12 -6
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +2 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +62 -37
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +3 -2
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +15 -1
- package/dist/archive-tar-runtime.d.ts +1 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +11 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +8 -4
- package/dist/archive-zip-entry.d.ts +7 -1
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +11 -1
- package/dist/archive-zip-integrity.d.ts +5 -0
- package/dist/archive-zip-integrity.d.ts.map +1 -0
- package/dist/archive-zip-integrity.js +54 -0
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +12 -1
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -5
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +13 -2
- package/dist/deny-mutations.d.ts +1 -0
- package/dist/deny-mutations.d.ts.map +1 -1
- package/dist/deny-mutations.js +6 -39
- package/dist/device-path.d.ts +1 -0
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +6 -6
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +5 -4
- package/dist/error-detail.d.ts +2 -0
- package/dist/error-detail.d.ts.map +1 -0
- package/dist/error-detail.js +4 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +4 -1
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +88 -35
- package/dist/file-store-boundary.d.ts +6 -0
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +20 -5
- package/dist/file-store-sync-write.d.ts +9 -0
- package/dist/file-store-sync-write.d.ts.map +1 -0
- package/dist/file-store-sync-write.js +97 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +62 -139
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +27 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +5 -6
- package/dist/install-path.js +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +19 -13
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +1 -3
- package/dist/local-file-access.d.ts +3 -2
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +12 -6
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +35 -30
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +51 -12
- package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
- package/dist/native/darwin-x64/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
- package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
- package/dist/native-binding.d.ts +1 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +14 -1
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +44 -17
- package/dist/output-sibling.d.ts.map +1 -1
- package/dist/output-sibling.js +6 -5
- package/dist/path-policy.d.ts.map +1 -1
- package/dist/path-policy.js +1 -7
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +4 -0
- package/dist/permission-exec.d.ts +6 -0
- package/dist/permission-exec.d.ts.map +1 -0
- package/dist/permission-exec.js +28 -0
- package/dist/permissions-windows.d.ts +56 -0
- package/dist/permissions-windows.d.ts.map +1 -0
- package/dist/permissions-windows.js +285 -0
- package/dist/permissions.d.ts +3 -46
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +17 -328
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +21 -4
- package/dist/pinned-write.js +2 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +45 -4
- package/dist/read-error.d.ts +2 -0
- package/dist/read-error.d.ts.map +1 -0
- package/dist/read-error.js +11 -0
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +21 -2
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +10 -1
- package/dist/replace-file-copy-fallback.d.ts +3 -0
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +18 -11
- package/dist/replace-file-descriptor.d.ts +33 -0
- package/dist/replace-file-descriptor.d.ts.map +1 -0
- package/dist/replace-file-descriptor.js +96 -0
- package/dist/replace-file.d.ts +9 -2
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +64 -72
- package/dist/root-context.d.ts +8 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +43 -2
- package/dist/root-errors.d.ts +7 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +46 -1
- package/dist/root-file.d.ts +1 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +2 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +216 -169
- package/dist/root-path-symlink.d.ts +3 -0
- package/dist/root-path-symlink.d.ts.map +1 -0
- package/dist/root-path-symlink.js +35 -0
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +129 -190
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +15 -0
- package/dist/safe-path-segment.d.ts +2 -0
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +12 -0
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +29 -6
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +5 -1
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +6 -1
- package/dist/short-path.d.ts +2 -0
- package/dist/short-path.d.ts.map +1 -0
- package/dist/short-path.js +7 -0
- package/dist/sidecar-lock-acquire.d.ts +30 -0
- package/dist/sidecar-lock-acquire.d.ts.map +1 -0
- package/dist/sidecar-lock-acquire.js +260 -0
- package/dist/sidecar-lock-policy.d.ts +3 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +20 -3
- package/dist/sidecar-lock-reclaim.d.ts +5 -1
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +59 -9
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +27 -226
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +9 -8
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +15 -4
- package/dist/test-hooks.d.ts +1 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/timing.d.ts +2 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +9 -0
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +10 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +15 -0
- package/docs/advanced.md +7 -1
- package/docs/archive.md +27 -20
- package/docs/assets/readme-banner.jpg +0 -0
- package/docs/atomic.md +48 -19
- package/docs/errors.md +34 -8
- package/docs/file-store.md +14 -1
- package/docs/filename.md +17 -16
- package/docs/index.md +1 -0
- package/docs/install-path.md +9 -6
- package/docs/json.md +8 -2
- package/docs/local-roots.md +79 -103
- package/docs/native-helper.md +11 -5
- package/docs/native.md +2 -2
- package/docs/output.md +8 -3
- package/docs/path-scope.md +4 -1
- package/docs/path.md +7 -3
- package/docs/permissions.md +8 -0
- package/docs/public-api.md +109 -0
- package/docs/quickstart.md +6 -2
- package/docs/reading.md +13 -10
- package/docs/root.md +26 -4
- package/docs/secret-file.md +21 -4
- package/docs/security-model.md +21 -4
- package/docs/sidecar-lock.md +15 -5
- package/docs/temp.md +42 -5
- package/docs/test-hooks.md +2 -0
- package/docs/types.md +32 -15
- package/docs/writing.md +25 -12
- package/package.json +15 -4
- package/dist/archive-file-io.d.ts +0 -9
- package/dist/archive-file-io.d.ts.map +0 -1
- package/dist/archive-file-io.js +0 -11
- package/dist/file-store-source.d.ts +0 -5
- package/dist/file-store-source.d.ts.map +0 -1
- package/dist/file-store-source.js +0 -30
- package/dist/file-url.d.ts +0 -2
- package/dist/file-url.d.ts.map +0 -1
- package/dist/file-url.js +0 -1
- package/dist/path-stat.d.ts +0 -4
- package/dist/path-stat.d.ts.map +0 -1
- package/dist/path-stat.js +0 -15
- package/dist/windows-permissions-native.d.ts +0 -9
- package/dist/windows-permissions-native.d.ts.map +0 -1
- package/dist/windows-permissions-native.js +0 -31
package/dist/file-store.js
CHANGED
|
@@ -1,49 +1,52 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import syncFs from "node:fs";
|
|
3
2
|
import fs from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
6
|
-
import { createSyncDirectoryGuard } from "./directory-guard.js";
|
|
7
5
|
import { FsSafeError } from "./errors.js";
|
|
8
|
-
import { pruneExpiredStoreEntries
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
6
|
+
import { pruneExpiredStoreEntries } from "./file-store-prune.js";
|
|
7
|
+
import { ensureParentInRoot, openWritableStoreRoot, writeStreamToTempSource, } from "./file-store-boundary.js";
|
|
8
|
+
import { writeFileSyncAtomic } from "./file-store-sync-write.js";
|
|
11
9
|
import { createJsonStore } from "./json-document-store.js";
|
|
12
10
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
13
|
-
import {
|
|
11
|
+
import { isNotFoundPathError, resolveSafeRelativePath } from "./path.js";
|
|
12
|
+
import { throwFsSafeReadError } from "./read-error.js";
|
|
14
13
|
import { root } from "./root.js";
|
|
15
14
|
import { DEFAULT_ROOT_MAX_BYTES } from "./root-impl.js";
|
|
15
|
+
import { readRegularFile } from "./regular-file.js";
|
|
16
16
|
import { matchRootFileOpenFailure, openRootFileSync } from "./root-file.js";
|
|
17
|
+
import { assertNoDriveRelativePathSegments } from "./safe-path-segment.js";
|
|
17
18
|
import { writeSecretFileAtomic } from "./secret-file.js";
|
|
18
|
-
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
19
19
|
function assertRelativePath(relativePath) {
|
|
20
20
|
const raw = relativePath.trim();
|
|
21
|
-
if (!raw) {
|
|
22
|
-
throw new FsSafeError("invalid-path", "
|
|
21
|
+
if (!raw || raw !== relativePath) {
|
|
22
|
+
throw new FsSafeError("invalid-path", "store key must be non-empty and unpadded");
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
assertNoDriveRelativePathSegments(raw.replaceAll("\\", "/"), "store key");
|
|
25
|
+
const segments = raw.split("/");
|
|
26
|
+
const delegated = segments.includes("..") || raw.includes("\\") ||
|
|
27
|
+
path.posix.isAbsolute(raw) || path.win32.isAbsolute(raw) || raw.startsWith("//");
|
|
28
|
+
if (delegated)
|
|
29
|
+
return raw;
|
|
30
|
+
if (segments.every((segment) => segment.length === 0 || segment === ".") ||
|
|
31
|
+
segments.some((segment) => segment.length === 0 || segment === ".") ||
|
|
32
|
+
raw.normalize("NFC") !== raw ||
|
|
33
|
+
segments.some((segment) => /[ .]$/u.test(segment))) {
|
|
34
|
+
throw new FsSafeError("invalid-path", "store key must use one canonical relative spelling");
|
|
35
|
+
}
|
|
36
|
+
return raw;
|
|
25
37
|
}
|
|
26
38
|
function resolveStorePath(rootDir, relativePath) {
|
|
27
39
|
return resolveSafeRelativePath(rootDir, assertRelativePath(relativePath));
|
|
28
40
|
}
|
|
29
|
-
function assertStoreFilePath(rootDir, filePath) {
|
|
30
|
-
if (!isPathInside(rootDir, filePath)) {
|
|
31
|
-
throw new FsSafeError("outside-workspace", "file path escapes store root");
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
41
|
function assertMaxBytes(size, maxBytes) {
|
|
35
42
|
if (maxBytes !== undefined && size > maxBytes) {
|
|
36
43
|
throw new FsSafeError("too-large", `file exceeds maximum size of ${maxBytes} bytes`);
|
|
37
44
|
}
|
|
38
45
|
}
|
|
39
46
|
function isNotFound(error) {
|
|
40
|
-
if (!error) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
47
|
return error instanceof FsSafeError
|
|
44
48
|
? error.code === "not-found"
|
|
45
|
-
: error
|
|
46
|
-
error.code === "ENOTDIR";
|
|
49
|
+
: isNotFoundPathError(error);
|
|
47
50
|
}
|
|
48
51
|
function handleSyncStoreReadOpenFailure(opened) {
|
|
49
52
|
return matchRootFileOpenFailure(opened, {
|
|
@@ -56,12 +59,16 @@ function handleSyncStoreReadOpenFailure(opened) {
|
|
|
56
59
|
});
|
|
57
60
|
},
|
|
58
61
|
validation: (failure) => {
|
|
62
|
+
if (failure.error instanceof FsSafeError) {
|
|
63
|
+
throw failure.error;
|
|
64
|
+
}
|
|
59
65
|
// Validation failures mean the path existed but violated store policy
|
|
60
66
|
// (directory, hardlink, symlink race). Do not report them as missing.
|
|
61
67
|
throw new FsSafeError("path-mismatch", "store target failed read validation", {
|
|
62
68
|
cause: failure.error instanceof Error ? failure.error : undefined,
|
|
63
69
|
});
|
|
64
70
|
},
|
|
71
|
+
io: (failure) => throwFsSafeReadError(failure.error, "store"),
|
|
65
72
|
fallback: (failure) => {
|
|
66
73
|
throw new FsSafeError("path-mismatch", "store target changed during read", {
|
|
67
74
|
cause: failure.error instanceof Error ? failure.error : undefined,
|
|
@@ -69,6 +76,31 @@ function handleSyncStoreReadOpenFailure(opened) {
|
|
|
69
76
|
},
|
|
70
77
|
});
|
|
71
78
|
}
|
|
79
|
+
async function readFileStoreCopySource(params) {
|
|
80
|
+
const sourceStat = await fs.lstat(params.sourcePath);
|
|
81
|
+
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
|
|
82
|
+
throw new FsSafeError("not-file", "source path is not a file");
|
|
83
|
+
}
|
|
84
|
+
assertMaxBytes(sourceStat.size, params.maxBytes);
|
|
85
|
+
try {
|
|
86
|
+
return (await readRegularFile({ filePath: params.sourcePath, maxBytes: params.maxBytes }))
|
|
87
|
+
.buffer;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
+
if (message.includes("regular file") || message.includes("not a regular file")) {
|
|
92
|
+
throw new FsSafeError("not-file", "source path is not a file", {
|
|
93
|
+
cause: error instanceof Error ? error : undefined,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (params.maxBytes !== undefined && message.includes(`exceeds ${params.maxBytes} bytes`)) {
|
|
97
|
+
throw new FsSafeError("too-large", `file exceeds maximum size of ${params.maxBytes} bytes`, {
|
|
98
|
+
cause: error instanceof Error ? error : undefined,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
72
104
|
async function copyIntoRoot(params) {
|
|
73
105
|
const relativePath = assertRelativePath(params.relativePath);
|
|
74
106
|
const destination = resolveStorePath(params.rootDir, relativePath);
|
|
@@ -209,7 +241,7 @@ export function fileStore(options) {
|
|
|
209
241
|
if (isNotFound(error)) {
|
|
210
242
|
return null;
|
|
211
243
|
}
|
|
212
|
-
|
|
244
|
+
throwFsSafeReadError(error, "store");
|
|
213
245
|
}
|
|
214
246
|
},
|
|
215
247
|
readJson: async (relativePath, readOptions) => {
|
|
@@ -225,7 +257,7 @@ export function fileStore(options) {
|
|
|
225
257
|
if (isNotFound(error)) {
|
|
226
258
|
return null;
|
|
227
259
|
}
|
|
228
|
-
|
|
260
|
+
throwFsSafeReadError(error, "store");
|
|
229
261
|
}
|
|
230
262
|
},
|
|
231
263
|
remove: async (relativePath) => {
|
|
@@ -264,119 +296,6 @@ export function fileStore(options) {
|
|
|
264
296
|
},
|
|
265
297
|
};
|
|
266
298
|
}
|
|
267
|
-
function ensurePrivateDirectorySync(rootDir, targetDir, mode) {
|
|
268
|
-
const root = path.resolve(rootDir);
|
|
269
|
-
const target = path.resolve(targetDir);
|
|
270
|
-
assertStoreFilePath(root, target);
|
|
271
|
-
let current = root;
|
|
272
|
-
syncFs.mkdirSync(current, { recursive: true, mode });
|
|
273
|
-
const rootStat = syncFs.lstatSync(current);
|
|
274
|
-
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
275
|
-
throw new FsSafeError("not-file", `private store root must be a directory: ${current}`);
|
|
276
|
-
}
|
|
277
|
-
try {
|
|
278
|
-
syncFs.chmodSync(current, mode);
|
|
279
|
-
}
|
|
280
|
-
catch {
|
|
281
|
-
// Best-effort on platforms that do not enforce POSIX modes.
|
|
282
|
-
}
|
|
283
|
-
for (const segment of path.relative(root, target).split(path.sep).filter(Boolean)) {
|
|
284
|
-
current = path.join(current, segment);
|
|
285
|
-
try {
|
|
286
|
-
const stat = syncFs.lstatSync(current);
|
|
287
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
288
|
-
throw new FsSafeError("not-file", `private store directory component must be a directory: ${current}`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
catch (error) {
|
|
292
|
-
if (error.code !== "ENOENT") {
|
|
293
|
-
throw error;
|
|
294
|
-
}
|
|
295
|
-
syncFs.mkdirSync(current, { mode });
|
|
296
|
-
}
|
|
297
|
-
const rootReal = syncFs.realpathSync(root);
|
|
298
|
-
const currentReal = syncFs.realpathSync(current);
|
|
299
|
-
if (!isPathInside(rootReal, currentReal)) {
|
|
300
|
-
throw new FsSafeError("outside-workspace", "private store directory escapes root");
|
|
301
|
-
}
|
|
302
|
-
try {
|
|
303
|
-
syncFs.chmodSync(current, mode);
|
|
304
|
-
}
|
|
305
|
-
catch {
|
|
306
|
-
// Best-effort on platforms that do not enforce POSIX modes.
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
const guard = createSyncDirectoryGuard(target);
|
|
310
|
-
assertSyncDirectoryGuard(guard);
|
|
311
|
-
return guard;
|
|
312
|
-
}
|
|
313
|
-
function writeFileSyncAtomic(params) {
|
|
314
|
-
const filePath = path.resolve(params.filePath);
|
|
315
|
-
assertStoreFilePath(params.rootDir, filePath);
|
|
316
|
-
let parentGuard;
|
|
317
|
-
if (params.privateMode) {
|
|
318
|
-
parentGuard = ensurePrivateDirectorySync(params.rootDir, path.dirname(filePath), params.dirMode);
|
|
319
|
-
try {
|
|
320
|
-
const stat = syncFs.lstatSync(filePath);
|
|
321
|
-
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
322
|
-
throw new FsSafeError("not-file", `private store target must be a regular file: ${filePath}`);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
catch (error) {
|
|
326
|
-
if (error.code !== "ENOENT") {
|
|
327
|
-
throw error;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
parentGuard = ensureParentSync({
|
|
333
|
-
rootDir: params.rootDir,
|
|
334
|
-
filePath,
|
|
335
|
-
mode: params.dirMode,
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
const tempPath = path.join(parentGuard?.dir ?? path.dirname(filePath), `.fs-safe-${process.pid}-${randomUUID()}.tmp`);
|
|
339
|
-
let tempExists = false;
|
|
340
|
-
try {
|
|
341
|
-
getFsSafeTestHooks()?.beforeFileStoreSyncPrivateWrite?.(filePath);
|
|
342
|
-
if (parentGuard) {
|
|
343
|
-
assertSyncDirectoryGuard(parentGuard);
|
|
344
|
-
}
|
|
345
|
-
syncFs.writeFileSync(tempPath, params.content, { flag: "wx", mode: params.mode });
|
|
346
|
-
tempExists = true;
|
|
347
|
-
try {
|
|
348
|
-
syncFs.chmodSync(tempPath, params.mode);
|
|
349
|
-
}
|
|
350
|
-
catch {
|
|
351
|
-
// Best-effort on platforms that do not enforce POSIX modes.
|
|
352
|
-
}
|
|
353
|
-
if (parentGuard) {
|
|
354
|
-
assertSyncDirectoryGuard(parentGuard);
|
|
355
|
-
}
|
|
356
|
-
syncFs.renameSync(tempPath, filePath);
|
|
357
|
-
tempExists = false;
|
|
358
|
-
if (parentGuard) {
|
|
359
|
-
assertSyncDirectoryGuard(parentGuard);
|
|
360
|
-
}
|
|
361
|
-
try {
|
|
362
|
-
syncFs.chmodSync(filePath, params.mode);
|
|
363
|
-
}
|
|
364
|
-
catch {
|
|
365
|
-
// Best-effort on platforms that do not enforce POSIX modes.
|
|
366
|
-
}
|
|
367
|
-
return filePath;
|
|
368
|
-
}
|
|
369
|
-
finally {
|
|
370
|
-
if (tempExists) {
|
|
371
|
-
try {
|
|
372
|
-
syncFs.unlinkSync(tempPath);
|
|
373
|
-
}
|
|
374
|
-
catch {
|
|
375
|
-
// Best-effort cleanup after write failure.
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
299
|
export function fileStoreSync(options) {
|
|
381
300
|
const rootDir = path.resolve(options.rootDir);
|
|
382
301
|
const privateMode = options.private ?? false;
|
|
@@ -413,10 +332,14 @@ export function fileStoreSync(options) {
|
|
|
413
332
|
try {
|
|
414
333
|
assertMaxBytes(opened.stat.size, readOptions?.maxBytes ?? maxBytes);
|
|
415
334
|
const limit = readOptions?.maxBytes ?? maxBytes;
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
335
|
+
try {
|
|
336
|
+
return limit === undefined
|
|
337
|
+
? syncFs.readFileSync(opened.fd, "utf8")
|
|
338
|
+
: readFileDescriptorBoundedSync(opened.fd, limit).toString("utf8");
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
throwFsSafeReadError(error, "store");
|
|
342
|
+
}
|
|
420
343
|
}
|
|
421
344
|
finally {
|
|
422
345
|
syncFs.closeSync(opened.fd);
|
package/dist/filename.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../src/filename.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../src/filename.ts"],"names":[],"mappings":"AA4BA,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAgCxF"}
|
package/dist/filename.js
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { WINDOWS_RESERVED_DEVICE_NAMES } from "./device-path.js";
|
|
2
3
|
const WINDOWS_INVALID_FILE_NAME_CHARACTERS = new Set('<>:"/\\|?*');
|
|
4
|
+
function trimWindowsIgnoredSuffix(value) {
|
|
5
|
+
let end = value.length;
|
|
6
|
+
while (end > 0) {
|
|
7
|
+
const character = value.charCodeAt(end - 1);
|
|
8
|
+
if (character !== 0x20 && character !== 0x2e) {
|
|
9
|
+
break;
|
|
10
|
+
}
|
|
11
|
+
end -= 1;
|
|
12
|
+
}
|
|
13
|
+
return end === value.length ? value : value.slice(0, end);
|
|
14
|
+
}
|
|
15
|
+
function suffixWindowsReservedDeviceName(fileName) {
|
|
16
|
+
const extensionIndex = fileName.indexOf(".");
|
|
17
|
+
const baseNameEnd = extensionIndex < 0 ? fileName.length : extensionIndex;
|
|
18
|
+
const baseName = fileName.slice(0, baseNameEnd);
|
|
19
|
+
const deviceBaseName = trimWindowsIgnoredSuffix(baseName);
|
|
20
|
+
if (!WINDOWS_RESERVED_DEVICE_NAMES.has(deviceBaseName.toUpperCase())) {
|
|
21
|
+
return fileName;
|
|
22
|
+
}
|
|
23
|
+
return `${baseName}_${fileName.slice(baseNameEnd)}`;
|
|
24
|
+
}
|
|
3
25
|
export function sanitizeUntrustedFileName(fileName, fallbackName) {
|
|
4
26
|
const trimmed = typeof fileName === "string" ? fileName.trim() : "";
|
|
5
27
|
if (!trimmed) {
|
|
@@ -21,8 +43,13 @@ export function sanitizeUntrustedFileName(fileName, fallbackName) {
|
|
|
21
43
|
if (!base || base === "." || base === "..") {
|
|
22
44
|
return fallbackName;
|
|
23
45
|
}
|
|
46
|
+
base = suffixWindowsReservedDeviceName(base);
|
|
24
47
|
if (base.length > 200) {
|
|
25
48
|
base = base.slice(0, 200);
|
|
49
|
+
const trailingCodeUnit = base.charCodeAt(base.length - 1);
|
|
50
|
+
if (trailingCodeUnit >= 0xd800 && trailingCodeUnit <= 0xdbff) {
|
|
51
|
+
base = base.slice(0, -1);
|
|
52
|
+
}
|
|
26
53
|
}
|
|
27
54
|
return base;
|
|
28
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guarded-mkdir.d.ts","sourceRoot":"","sources":["../src/guarded-mkdir.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"guarded-mkdir.d.ts","sourceRoot":"","sources":["../src/guarded-mkdir.ts"],"names":[],"mappings":"AAyBA;;;;;GAKG;AACH,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACnE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuDlB"}
|
package/dist/guarded-mkdir.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
5
|
import { isNotFoundPathError, isPathRelativeEscape } from "./path.js";
|
|
6
|
+
import { directoryComponentNotDirectoryError } from "./root-errors.js";
|
|
6
7
|
function isSameOrChildPath(candidate, parent) {
|
|
7
8
|
const parentPrefix = parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`;
|
|
8
9
|
return candidate === parent || candidate.startsWith(parentPrefix);
|
|
@@ -15,9 +16,7 @@ async function realpathOrThrowNotFile(target) {
|
|
|
15
16
|
if (isNotFoundPathError(error)) {
|
|
16
17
|
// A dangling symlink (or a component removed between lstat and
|
|
17
18
|
// realpath) is not a usable directory component.
|
|
18
|
-
throw
|
|
19
|
-
cause: error instanceof Error ? error : undefined,
|
|
20
|
-
});
|
|
19
|
+
throw directoryComponentNotDirectoryError(error instanceof Error ? error : undefined);
|
|
21
20
|
}
|
|
22
21
|
throw error;
|
|
23
22
|
}
|
|
@@ -40,8 +39,8 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
40
39
|
for (const part of relative.split(path.sep).filter(Boolean)) {
|
|
41
40
|
const next = path.join(current, part);
|
|
42
41
|
const parentGuard = await createAsyncDirectoryGuard(current);
|
|
43
|
-
await params.beforeComponent?.(next);
|
|
44
42
|
await assertAsyncDirectoryGuard(parentGuard);
|
|
43
|
+
await params.beforeComponent?.(next);
|
|
45
44
|
try {
|
|
46
45
|
await fs.mkdir(next);
|
|
47
46
|
}
|
|
@@ -52,7 +51,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
52
51
|
}
|
|
53
52
|
const stat = await fs.lstat(next);
|
|
54
53
|
if (!stat.isSymbolicLink() && !stat.isDirectory()) {
|
|
55
|
-
throw
|
|
54
|
+
throw directoryComponentNotDirectoryError();
|
|
56
55
|
}
|
|
57
56
|
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
58
57
|
// segment at a time and realpath-check each segment before descending.
|
|
@@ -72,7 +71,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
72
71
|
// the returned resolved path, not their own lexical parent path.
|
|
73
72
|
const targetStat = await fs.stat(nextReal);
|
|
74
73
|
if (!targetStat.isDirectory()) {
|
|
75
|
-
throw
|
|
74
|
+
throw directoryComponentNotDirectoryError();
|
|
76
75
|
}
|
|
77
76
|
await createAsyncDirectoryGuard(nextReal);
|
|
78
77
|
await assertAsyncDirectoryGuard(parentGuard);
|
package/dist/install-path.js
CHANGED
|
@@ -41,7 +41,7 @@ export function resolveSafeInstallDir(params) {
|
|
|
41
41
|
path.isAbsolute(relative)) {
|
|
42
42
|
return { ok: false, error: params.invalidNameMessage };
|
|
43
43
|
}
|
|
44
|
-
return { ok: true, path:
|
|
44
|
+
return { ok: true, path: resolvedTarget };
|
|
45
45
|
}
|
|
46
46
|
export async function assertCanonicalPathWithinBase(params) {
|
|
47
47
|
const baseDir = path.resolve(params.baseDir);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-document-store.d.ts","sourceRoot":"","sources":["../src/json-document-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"json-document-store.d.ts","sourceRoot":"","sources":["../src/json-document-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AASlE,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,IAAI,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC/B,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAChC,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACxE,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACvC,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzE,CAAC;AA2BF,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC5B,OAAO,GAAE,oBAAyB,GACjC,SAAS,CAAC,CAAC,CAAC,CAiFd"}
|
|
@@ -43,24 +43,30 @@ export function createJsonStore(adapter, options = {}) {
|
|
|
43
43
|
async function withSerializedMutation(run) {
|
|
44
44
|
const canonicalPath = await canonicalPathFromExistingAncestor(adapter.filePath);
|
|
45
45
|
const activePaths = activeStoreMutations.getStore();
|
|
46
|
-
if (activePaths?.
|
|
46
|
+
if (activePaths?.get(canonicalPath)?.active) {
|
|
47
47
|
throw new FsSafeError("store-reentrant-update", `jsonStore cannot write or update ${canonicalPath} from inside its active update callback; return the complete next value from the outer update instead`);
|
|
48
48
|
}
|
|
49
49
|
return await serializePathWrite(`json-store:${canonicalPath}`, async () => {
|
|
50
|
-
const mutationPaths = new
|
|
51
|
-
|
|
50
|
+
const mutationPaths = new Map([...(activePaths ?? [])].filter(([, token]) => token.active));
|
|
51
|
+
const token = { active: true };
|
|
52
|
+
mutationPaths.set(canonicalPath, token);
|
|
52
53
|
return await activeStoreMutations.run(mutationPaths, async () => {
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
try {
|
|
55
|
+
if (!locks || !lockOptions) {
|
|
56
|
+
return await run();
|
|
57
|
+
}
|
|
58
|
+
return await locks.withLock({
|
|
59
|
+
targetPath: adapter.filePath,
|
|
60
|
+
staleMs: lockOptions.staleMs,
|
|
61
|
+
timeoutMs: lockOptions.timeoutMs,
|
|
62
|
+
retry: lockOptions.retry,
|
|
63
|
+
staleRecovery: lockOptions.staleRecovery,
|
|
64
|
+
payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
|
|
65
|
+
}, run);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
token.active = false;
|
|
55
69
|
}
|
|
56
|
-
return await locks.withLock({
|
|
57
|
-
targetPath: adapter.filePath,
|
|
58
|
-
staleMs: lockOptions.staleMs,
|
|
59
|
-
timeoutMs: lockOptions.timeoutMs,
|
|
60
|
-
retry: lockOptions.retry,
|
|
61
|
-
staleRecovery: lockOptions.staleRecovery,
|
|
62
|
-
payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
|
|
63
|
-
}, run);
|
|
64
70
|
});
|
|
65
71
|
});
|
|
66
72
|
}
|
package/dist/json.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAM7B,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
|
package/dist/json.js
CHANGED
|
@@ -7,6 +7,7 @@ import { stringifyJsonDocument } from "./json-stringify.js";
|
|
|
7
7
|
import { readRegularFile, readRegularFileSync, statRegularFile } from "./regular-file.js";
|
|
8
8
|
import { openRootFileSync } from "./root-file.js";
|
|
9
9
|
import { writeTextAtomic } from "./text-atomic.js";
|
|
10
|
+
import { sleep } from "./timing.js";
|
|
10
11
|
const READ_RETRY_MAX_ATTEMPTS = 5;
|
|
11
12
|
const READ_RETRY_BASE_DELAY_MS = 50;
|
|
12
13
|
function isRetryableReadError(err, options) {
|
|
@@ -19,9 +20,6 @@ function isRetryableReadError(err, options) {
|
|
|
19
20
|
const code = getErrorCode(err);
|
|
20
21
|
return code === "ENOENT" || code === "EPERM";
|
|
21
22
|
}
|
|
22
|
-
function sleep(ms) {
|
|
23
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
24
|
-
}
|
|
25
23
|
async function readRegularFileWithRetry(filePath, options = {}) {
|
|
26
24
|
let lastErr;
|
|
27
25
|
for (let attempt = 0; attempt < READ_RETRY_MAX_ATTEMPTS; attempt++) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
export declare function isFileUrl(input: string): boolean;
|
|
1
2
|
export declare function hasEncodedFileUrlSeparator(pathname: string): boolean;
|
|
2
3
|
export declare function isWindowsNetworkPath(filePath: string, platform?: NodeJS.Platform): boolean;
|
|
3
4
|
export declare function isWindowsDriveLetterPath(filePath: string, platform?: NodeJS.Platform): boolean;
|
|
4
5
|
export declare function assertNoWindowsNetworkPath(filePath: string, label?: string): void;
|
|
5
|
-
export declare function safeFileURLToPath(fileUrl: string): string;
|
|
6
|
-
export declare function trySafeFileURLToPath(fileUrl: string): string | undefined;
|
|
6
|
+
export declare function safeFileURLToPath(fileUrl: string, platform?: NodeJS.Platform): string;
|
|
7
|
+
export declare function trySafeFileURLToPath(fileUrl: string, platform?: NodeJS.Platform): string | undefined;
|
|
7
8
|
export declare function basenameFromMediaSource(source?: string): string | undefined;
|
|
8
9
|
//# sourceMappingURL=local-file-access.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-file-access.d.ts","sourceRoot":"","sources":["../src/local-file-access.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"local-file-access.d.ts","sourceRoot":"","sources":["../src/local-file-access.ts"],"names":[],"mappings":"AAOA,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEhD;AAOD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAeT;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAET;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAS,GAAG,IAAI,CAIjF;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,CAqBR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgB3E"}
|
|
@@ -2,6 +2,10 @@ import path from "node:path";
|
|
|
2
2
|
import { fileURLToPath, URL } from "node:url";
|
|
3
3
|
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
4
4
|
const ENCODED_FILE_URL_SEPARATOR_RE = /%(?:2f|5c)/i;
|
|
5
|
+
const FILE_URL_PREFIX_RE = /^file:\/\//i;
|
|
6
|
+
export function isFileUrl(input) {
|
|
7
|
+
return FILE_URL_PREFIX_RE.test(input);
|
|
8
|
+
}
|
|
5
9
|
function isLocalFileUrlHost(hostname) {
|
|
6
10
|
const normalized = normalizeLowercaseStringOrEmpty(hostname);
|
|
7
11
|
return normalized === "" || normalized === "localhost";
|
|
@@ -32,7 +36,7 @@ export function assertNoWindowsNetworkPath(filePath, label = "Path") {
|
|
|
32
36
|
throw new Error(`${label} cannot use Windows network paths: ${filePath}`);
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
|
-
export function safeFileURLToPath(fileUrl) {
|
|
39
|
+
export function safeFileURLToPath(fileUrl, platform = process.platform) {
|
|
36
40
|
let parsed;
|
|
37
41
|
try {
|
|
38
42
|
parsed = new URL(fileUrl);
|
|
@@ -49,13 +53,15 @@ export function safeFileURLToPath(fileUrl) {
|
|
|
49
53
|
if (hasEncodedFileUrlSeparator(parsed.pathname)) {
|
|
50
54
|
throw new Error(`file:// URLs cannot encode path separators: ${fileUrl}`);
|
|
51
55
|
}
|
|
52
|
-
const filePath = fileURLToPath(parsed);
|
|
53
|
-
|
|
56
|
+
const filePath = fileURLToPath(parsed, { windows: platform === "win32" });
|
|
57
|
+
if (isWindowsNetworkPath(filePath, platform)) {
|
|
58
|
+
throw new Error(`Local file URL cannot use Windows network paths: ${filePath}`);
|
|
59
|
+
}
|
|
54
60
|
return filePath;
|
|
55
61
|
}
|
|
56
|
-
export function trySafeFileURLToPath(fileUrl) {
|
|
62
|
+
export function trySafeFileURLToPath(fileUrl, platform = process.platform) {
|
|
57
63
|
try {
|
|
58
|
-
return safeFileURLToPath(fileUrl);
|
|
64
|
+
return safeFileURLToPath(fileUrl, platform);
|
|
59
65
|
}
|
|
60
66
|
catch {
|
|
61
67
|
return undefined;
|
|
@@ -65,7 +71,7 @@ export function basenameFromMediaSource(source) {
|
|
|
65
71
|
if (!source) {
|
|
66
72
|
return undefined;
|
|
67
73
|
}
|
|
68
|
-
if (source
|
|
74
|
+
if (isFileUrl(source)) {
|
|
69
75
|
const filePath = trySafeFileURLToPath(source);
|
|
70
76
|
return filePath ? path.basename(filePath) || undefined : undefined;
|
|
71
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAMA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;
|
|
1
|
+
{"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAMA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAqGF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA6B7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA2CtC"}
|