@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.
Files changed (231) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +19 -6
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +20 -12
  5. package/dist/archive-entry.d.ts +1 -0
  6. package/dist/archive-entry.d.ts.map +1 -1
  7. package/dist/archive-entry.js +37 -6
  8. package/dist/archive-errors.d.ts +1 -0
  9. package/dist/archive-errors.d.ts.map +1 -1
  10. package/dist/archive-errors.js +4 -0
  11. package/dist/archive-input.d.ts +7 -0
  12. package/dist/archive-input.d.ts.map +1 -1
  13. package/dist/archive-input.js +11 -1
  14. package/dist/archive-limits.js +1 -1
  15. package/dist/archive-native.d.ts.map +1 -1
  16. package/dist/archive-native.js +12 -6
  17. package/dist/archive-policy.d.ts.map +1 -1
  18. package/dist/archive-policy.js +2 -1
  19. package/dist/archive-read.d.ts.map +1 -1
  20. package/dist/archive-read.js +62 -37
  21. package/dist/archive-staging.d.ts.map +1 -1
  22. package/dist/archive-staging.js +3 -2
  23. package/dist/archive-tar-meta.d.ts.map +1 -1
  24. package/dist/archive-tar-meta.js +15 -1
  25. package/dist/archive-tar-runtime.d.ts +1 -0
  26. package/dist/archive-tar-runtime.d.ts.map +1 -1
  27. package/dist/archive-tar-runtime.js +11 -0
  28. package/dist/archive-tar.d.ts.map +1 -1
  29. package/dist/archive-tar.js +8 -4
  30. package/dist/archive-zip-entry.d.ts +7 -1
  31. package/dist/archive-zip-entry.d.ts.map +1 -1
  32. package/dist/archive-zip-entry.js +11 -1
  33. package/dist/archive-zip-integrity.d.ts +5 -0
  34. package/dist/archive-zip-integrity.d.ts.map +1 -0
  35. package/dist/archive-zip-integrity.js +54 -0
  36. package/dist/archive-zip-preflight.d.ts.map +1 -1
  37. package/dist/archive-zip-preflight.js +12 -1
  38. package/dist/archive.d.ts.map +1 -1
  39. package/dist/archive.js +9 -5
  40. package/dist/bounded-read-stream.d.ts.map +1 -1
  41. package/dist/bounded-read-stream.js +13 -2
  42. package/dist/deny-mutations.d.ts +1 -0
  43. package/dist/deny-mutations.d.ts.map +1 -1
  44. package/dist/deny-mutations.js +6 -39
  45. package/dist/device-path.d.ts +1 -0
  46. package/dist/device-path.d.ts.map +1 -1
  47. package/dist/device-path.js +6 -6
  48. package/dist/directory-guard.d.ts.map +1 -1
  49. package/dist/directory-guard.js +5 -4
  50. package/dist/error-detail.d.ts +2 -0
  51. package/dist/error-detail.d.ts.map +1 -0
  52. package/dist/error-detail.js +4 -0
  53. package/dist/errors.d.ts +1 -1
  54. package/dist/errors.d.ts.map +1 -1
  55. package/dist/errors.js +4 -0
  56. package/dist/file-hash.d.ts.map +1 -1
  57. package/dist/file-hash.js +4 -1
  58. package/dist/file-lock-sync.d.ts.map +1 -1
  59. package/dist/file-lock-sync.js +88 -35
  60. package/dist/file-store-boundary.d.ts +6 -0
  61. package/dist/file-store-boundary.d.ts.map +1 -1
  62. package/dist/file-store-boundary.js +20 -5
  63. package/dist/file-store-sync-write.d.ts +9 -0
  64. package/dist/file-store-sync-write.d.ts.map +1 -0
  65. package/dist/file-store-sync-write.js +97 -0
  66. package/dist/file-store.d.ts.map +1 -1
  67. package/dist/file-store.js +62 -139
  68. package/dist/filename.d.ts.map +1 -1
  69. package/dist/filename.js +27 -0
  70. package/dist/guarded-mkdir.d.ts.map +1 -1
  71. package/dist/guarded-mkdir.js +5 -6
  72. package/dist/install-path.js +1 -1
  73. package/dist/json-document-store.d.ts.map +1 -1
  74. package/dist/json-document-store.js +19 -13
  75. package/dist/json.d.ts.map +1 -1
  76. package/dist/json.js +1 -3
  77. package/dist/local-file-access.d.ts +3 -2
  78. package/dist/local-file-access.d.ts.map +1 -1
  79. package/dist/local-file-access.js +12 -6
  80. package/dist/local-roots.d.ts.map +1 -1
  81. package/dist/local-roots.js +35 -30
  82. package/dist/move-path.d.ts.map +1 -1
  83. package/dist/move-path.js +51 -12
  84. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  85. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  86. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  87. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  88. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  89. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  90. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
  91. package/dist/native-binding.d.ts +1 -0
  92. package/dist/native-binding.d.ts.map +1 -1
  93. package/dist/native-operations.d.ts.map +1 -1
  94. package/dist/native-operations.js +14 -1
  95. package/dist/native-pinned-write.d.ts.map +1 -1
  96. package/dist/native-pinned-write.js +44 -17
  97. package/dist/output-sibling.d.ts.map +1 -1
  98. package/dist/output-sibling.js +6 -5
  99. package/dist/path-policy.d.ts.map +1 -1
  100. package/dist/path-policy.js +1 -7
  101. package/dist/path.d.ts.map +1 -1
  102. package/dist/path.js +4 -0
  103. package/dist/permission-exec.d.ts +6 -0
  104. package/dist/permission-exec.d.ts.map +1 -0
  105. package/dist/permission-exec.js +28 -0
  106. package/dist/permissions-windows.d.ts +56 -0
  107. package/dist/permissions-windows.d.ts.map +1 -0
  108. package/dist/permissions-windows.js +285 -0
  109. package/dist/permissions.d.ts +3 -46
  110. package/dist/permissions.d.ts.map +1 -1
  111. package/dist/permissions.js +17 -328
  112. package/dist/pinned-open.d.ts.map +1 -1
  113. package/dist/pinned-open.js +21 -4
  114. package/dist/pinned-write.js +2 -1
  115. package/dist/private-temp-workspace.d.ts.map +1 -1
  116. package/dist/private-temp-workspace.js +45 -4
  117. package/dist/read-error.d.ts +2 -0
  118. package/dist/read-error.d.ts.map +1 -0
  119. package/dist/read-error.js +11 -0
  120. package/dist/regular-file.d.ts.map +1 -1
  121. package/dist/regular-file.js +21 -2
  122. package/dist/replace-directory.d.ts.map +1 -1
  123. package/dist/replace-directory.js +10 -1
  124. package/dist/replace-file-copy-fallback.d.ts +3 -0
  125. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  126. package/dist/replace-file-copy-fallback.js +18 -11
  127. package/dist/replace-file-descriptor.d.ts +33 -0
  128. package/dist/replace-file-descriptor.d.ts.map +1 -0
  129. package/dist/replace-file-descriptor.js +96 -0
  130. package/dist/replace-file.d.ts +9 -2
  131. package/dist/replace-file.d.ts.map +1 -1
  132. package/dist/replace-file.js +64 -72
  133. package/dist/root-context.d.ts +8 -0
  134. package/dist/root-context.d.ts.map +1 -1
  135. package/dist/root-context.js +43 -2
  136. package/dist/root-errors.d.ts +7 -0
  137. package/dist/root-errors.d.ts.map +1 -1
  138. package/dist/root-errors.js +46 -1
  139. package/dist/root-file.d.ts +1 -0
  140. package/dist/root-file.d.ts.map +1 -1
  141. package/dist/root-file.js +2 -0
  142. package/dist/root-impl.d.ts.map +1 -1
  143. package/dist/root-impl.js +216 -169
  144. package/dist/root-path-symlink.d.ts +3 -0
  145. package/dist/root-path-symlink.d.ts.map +1 -0
  146. package/dist/root-path-symlink.js +35 -0
  147. package/dist/root-path.d.ts +1 -0
  148. package/dist/root-path.d.ts.map +1 -1
  149. package/dist/root-path.js +129 -190
  150. package/dist/root-walk.d.ts.map +1 -1
  151. package/dist/root-walk.js +15 -0
  152. package/dist/safe-path-segment.d.ts +2 -0
  153. package/dist/safe-path-segment.d.ts.map +1 -1
  154. package/dist/safe-path-segment.js +12 -0
  155. package/dist/secret-file.d.ts.map +1 -1
  156. package/dist/secret-file.js +29 -6
  157. package/dist/secret-read-async.d.ts.map +1 -1
  158. package/dist/secret-read-async.js +5 -1
  159. package/dist/secure-temp-dir.d.ts.map +1 -1
  160. package/dist/secure-temp-dir.js +6 -1
  161. package/dist/short-path.d.ts +2 -0
  162. package/dist/short-path.d.ts.map +1 -0
  163. package/dist/short-path.js +7 -0
  164. package/dist/sidecar-lock-acquire.d.ts +30 -0
  165. package/dist/sidecar-lock-acquire.d.ts.map +1 -0
  166. package/dist/sidecar-lock-acquire.js +260 -0
  167. package/dist/sidecar-lock-policy.d.ts +3 -0
  168. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  169. package/dist/sidecar-lock-policy.js +20 -3
  170. package/dist/sidecar-lock-reclaim.d.ts +5 -1
  171. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  172. package/dist/sidecar-lock-reclaim.js +59 -9
  173. package/dist/sidecar-lock.d.ts.map +1 -1
  174. package/dist/sidecar-lock.js +27 -226
  175. package/dist/symlink-parents.d.ts.map +1 -1
  176. package/dist/symlink-parents.js +9 -8
  177. package/dist/temp-target.d.ts.map +1 -1
  178. package/dist/temp-target.js +15 -4
  179. package/dist/test-hooks.d.ts +1 -0
  180. package/dist/test-hooks.d.ts.map +1 -1
  181. package/dist/timing.d.ts +2 -0
  182. package/dist/timing.d.ts.map +1 -1
  183. package/dist/timing.js +9 -0
  184. package/dist/trash.d.ts.map +1 -1
  185. package/dist/trash.js +10 -1
  186. package/dist/walk.d.ts.map +1 -1
  187. package/dist/walk.js +15 -0
  188. package/docs/advanced.md +7 -1
  189. package/docs/archive.md +27 -20
  190. package/docs/assets/readme-banner.jpg +0 -0
  191. package/docs/atomic.md +48 -19
  192. package/docs/errors.md +34 -8
  193. package/docs/file-store.md +14 -1
  194. package/docs/filename.md +17 -16
  195. package/docs/index.md +1 -0
  196. package/docs/install-path.md +9 -6
  197. package/docs/json.md +8 -2
  198. package/docs/local-roots.md +79 -103
  199. package/docs/native-helper.md +11 -5
  200. package/docs/native.md +2 -2
  201. package/docs/output.md +8 -3
  202. package/docs/path-scope.md +4 -1
  203. package/docs/path.md +7 -3
  204. package/docs/permissions.md +8 -0
  205. package/docs/public-api.md +109 -0
  206. package/docs/quickstart.md +6 -2
  207. package/docs/reading.md +13 -10
  208. package/docs/root.md +26 -4
  209. package/docs/secret-file.md +21 -4
  210. package/docs/security-model.md +21 -4
  211. package/docs/sidecar-lock.md +15 -5
  212. package/docs/temp.md +42 -5
  213. package/docs/test-hooks.md +2 -0
  214. package/docs/types.md +32 -15
  215. package/docs/writing.md +25 -12
  216. package/package.json +15 -4
  217. package/dist/archive-file-io.d.ts +0 -9
  218. package/dist/archive-file-io.d.ts.map +0 -1
  219. package/dist/archive-file-io.js +0 -11
  220. package/dist/file-store-source.d.ts +0 -5
  221. package/dist/file-store-source.d.ts.map +0 -1
  222. package/dist/file-store-source.js +0 -30
  223. package/dist/file-url.d.ts +0 -2
  224. package/dist/file-url.d.ts.map +0 -1
  225. package/dist/file-url.js +0 -1
  226. package/dist/path-stat.d.ts +0 -4
  227. package/dist/path-stat.d.ts.map +0 -1
  228. package/dist/path-stat.js +0 -15
  229. package/dist/windows-permissions-native.d.ts +0 -9
  230. package/dist/windows-permissions-native.d.ts.map +0 -1
  231. package/dist/windows-permissions-native.js +0 -31
@@ -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, } from "./file-store-prune.js";
9
- import { assertSyncDirectoryGuard, ensureParentInRoot, ensureParentSync, openWritableStoreRoot, writeStreamToTempSource, } from "./file-store-boundary.js";
10
- import { readFileStoreCopySource } from "./file-store-source.js";
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 { isPathInside, resolveSafeRelativePath } from "./path.js";
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", "relative path must be non-empty");
21
+ if (!raw || raw !== relativePath) {
22
+ throw new FsSafeError("invalid-path", "store key must be non-empty and unpadded");
23
23
  }
24
- return raw.replaceAll("\\", "/");
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.code === "ENOENT" ||
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
- throw error;
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
- throw error;
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
- const raw = limit === undefined
417
- ? syncFs.readFileSync(opened.fd, "utf8")
418
- : readFileDescriptorBoundedSync(opened.fd, limit).toString("utf8");
419
- return raw;
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);
@@ -1 +1 @@
1
- {"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../src/filename.ts"],"names":[],"mappings":"AAIA,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CA2BxF"}
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":"AA0BA;;;;;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"}
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"}
@@ -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 new FsSafeError("not-file", "directory component must be a directory", {
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 new FsSafeError("not-file", "directory component must be a directory");
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 new FsSafeError("not-file", "directory component must be a directory");
74
+ throw directoryComponentNotDirectoryError();
76
75
  }
77
76
  await createAsyncDirectoryGuard(nextReal);
78
77
  await assertAsyncDirectoryGuard(parentGuard);
@@ -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: targetDir };
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;AAKlE,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,CA0Ed"}
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?.has(canonicalPath)) {
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 Set(activePaths);
51
- mutationPaths.add(canonicalPath);
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
- if (!locks || !lockOptions) {
54
- return await run();
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
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":"AAWA,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,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmBzD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMxE;AAED,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgB3E"}
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
- assertNoWindowsNetworkPath(filePath, "Local file URL");
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.startsWith("file://")) {
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;AAmGF,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,CA0CtC"}
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"}