@openclaw/fs-safe 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +390 -0
- package/SECURITY.md +12 -0
- package/dist/absolute-path.d.ts +19 -0
- package/dist/absolute-path.d.ts.map +1 -0
- package/dist/absolute-path.js +98 -0
- package/dist/advanced.d.ts +22 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +24 -0
- package/dist/archive-entry.d.ts +13 -0
- package/dist/archive-entry.d.ts.map +1 -0
- package/dist/archive-entry.js +47 -0
- package/dist/archive-kind.d.ts +8 -0
- package/dist/archive-kind.d.ts.map +1 -0
- package/dist/archive-kind.js +58 -0
- package/dist/archive-limits.d.ts +40 -0
- package/dist/archive-limits.d.ts.map +1 -0
- package/dist/archive-limits.js +92 -0
- package/dist/archive-staging.d.ts +26 -0
- package/dist/archive-staging.d.ts.map +1 -0
- package/dist/archive-staging.js +171 -0
- package/dist/archive-tar.d.ts +14 -0
- package/dist/archive-tar.d.ts.map +1 -0
- package/dist/archive-tar.js +52 -0
- package/dist/archive-utils.d.ts +3 -0
- package/dist/archive-utils.d.ts.map +1 -0
- package/dist/archive-utils.js +26 -0
- package/dist/archive-zip-preflight.d.ts +7 -0
- package/dist/archive-zip-preflight.d.ts.map +1 -0
- package/dist/archive-zip-preflight.js +170 -0
- package/dist/archive.d.ts +24 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +251 -0
- package/dist/async-lock.d.ts +2 -0
- package/dist/async-lock.d.ts.map +1 -0
- package/dist/async-lock.js +17 -0
- package/dist/atomic.d.ts +5 -0
- package/dist/atomic.d.ts.map +1 -0
- package/dist/atomic.js +4 -0
- package/dist/boundary-file-read.d.ts +44 -0
- package/dist/boundary-file-read.d.ts.map +1 -0
- package/dist/boundary-file-read.js +129 -0
- package/dist/boundary-path.d.ts +39 -0
- package/dist/boundary-path.d.ts.map +1 -0
- package/dist/boundary-path.js +598 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +1 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +20 -0
- package/dist/file-identity.d.ts +6 -0
- package/dist/file-identity.d.ts.map +1 -0
- package/dist/file-identity.js +14 -0
- package/dist/file-lock.d.ts +20 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +34 -0
- package/dist/file-store.d.ts +66 -0
- package/dist/file-store.d.ts.map +1 -0
- package/dist/file-store.js +436 -0
- package/dist/file-url.d.ts +2 -0
- package/dist/file-url.d.ts.map +1 -0
- package/dist/file-url.js +1 -0
- package/dist/filename.d.ts +2 -0
- package/dist/filename.d.ts.map +1 -0
- package/dist/filename.js +25 -0
- package/dist/fs-pinned-path-helper.d.ts +7 -0
- package/dist/fs-pinned-path-helper.d.ts.map +1 -0
- package/dist/fs-pinned-path-helper.js +182 -0
- package/dist/fs-pinned-write-helper.d.ts +21 -0
- package/dist/fs-pinned-write-helper.d.ts.map +1 -0
- package/dist/fs-pinned-write-helper.js +263 -0
- package/dist/fs.d.ts +13 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +30 -0
- package/dist/hardlink-guards.d.ts +7 -0
- package/dist/hardlink-guards.d.ts.map +1 -0
- package/dist/hardlink-guards.js +30 -0
- package/dist/home-dir.d.ts +22 -0
- package/dist/home-dir.d.ts.map +1 -0
- package/dist/home-dir.js +107 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/install-path.d.ts +20 -0
- package/dist/install-path.d.ts.map +1 -0
- package/dist/install-path.js +94 -0
- package/dist/install-safe-path.d.ts +20 -0
- package/dist/install-safe-path.d.ts.map +1 -0
- package/dist/install-safe-path.js +94 -0
- package/dist/json-document-store.d.ts +30 -0
- package/dist/json-document-store.d.ts.map +1 -0
- package/dist/json-document-store.js +68 -0
- package/dist/json-file.d.ts +3 -0
- package/dist/json-file.d.ts.map +1 -0
- package/dist/json-file.js +123 -0
- package/dist/json-files.d.ts +20 -0
- package/dist/json-files.d.ts.map +1 -0
- package/dist/json-files.js +153 -0
- package/dist/json-store.d.ts +9 -0
- package/dist/json-store.d.ts.map +1 -0
- package/dist/json-store.js +12 -0
- package/dist/json.d.ts +17 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +198 -0
- package/dist/local-file-access.d.ts +8 -0
- package/dist/local-file-access.d.ts.map +1 -0
- package/dist/local-file-access.js +73 -0
- package/dist/local-roots.d.ts +26 -0
- package/dist/local-roots.d.ts.map +1 -0
- package/dist/local-roots.js +150 -0
- package/dist/mode.d.ts +2 -0
- package/dist/mode.d.ts.map +1 -0
- package/dist/mode.js +3 -0
- package/dist/move-path.d.ts +6 -0
- package/dist/move-path.d.ts.map +1 -0
- package/dist/move-path.js +18 -0
- package/dist/path-alias-guards.d.ts +19 -0
- package/dist/path-alias-guards.d.ts.map +1 -0
- package/dist/path-alias-guards.js +21 -0
- package/dist/path-guards.d.ts +7 -0
- package/dist/path-guards.d.ts.map +1 -0
- package/dist/path-guards.js +49 -0
- package/dist/path-policy.d.ts +25 -0
- package/dist/path-policy.d.ts.map +1 -0
- package/dist/path-policy.js +50 -0
- package/dist/path-safety.d.ts +12 -0
- package/dist/path-safety.d.ts.map +1 -0
- package/dist/path-safety.js +50 -0
- package/dist/path-scope.d.ts +2 -0
- package/dist/path-scope.d.ts.map +1 -0
- package/dist/path-scope.js +1 -0
- package/dist/path.d.ts +18 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +123 -0
- package/dist/permissions-public.d.ts +2 -0
- package/dist/permissions-public.d.ts.map +1 -0
- package/dist/permissions-public.js +1 -0
- package/dist/permissions.d.ts +86 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +435 -0
- package/dist/pinned-helper.d.ts +9 -0
- package/dist/pinned-helper.d.ts.map +1 -0
- package/dist/pinned-helper.js +19 -0
- package/dist/pinned-open.d.ts +24 -0
- package/dist/pinned-open.d.ts.map +1 -0
- package/dist/pinned-open.js +71 -0
- package/dist/pinned-path.d.ts +7 -0
- package/dist/pinned-path.d.ts.map +1 -0
- package/dist/pinned-path.js +21 -0
- package/dist/pinned-python-config.d.ts +9 -0
- package/dist/pinned-python-config.d.ts.map +1 -0
- package/dist/pinned-python-config.js +39 -0
- package/dist/pinned-python.d.ts +12 -0
- package/dist/pinned-python.d.ts.map +1 -0
- package/dist/pinned-python.js +599 -0
- package/dist/pinned-write.d.ts +33 -0
- package/dist/pinned-write.d.ts.map +1 -0
- package/dist/pinned-write.js +198 -0
- package/dist/private-file-store.d.ts +60 -0
- package/dist/private-file-store.d.ts.map +1 -0
- package/dist/private-file-store.js +233 -0
- package/dist/private-temp-workspace.d.ts +39 -0
- package/dist/private-temp-workspace.d.ts.map +1 -0
- package/dist/private-temp-workspace.js +185 -0
- package/dist/regular-file.d.ts +38 -0
- package/dist/regular-file.d.ts.map +1 -0
- package/dist/regular-file.js +239 -0
- package/dist/replace-directory.d.ts +7 -0
- package/dist/replace-directory.d.ts.map +1 -0
- package/dist/replace-directory.js +32 -0
- package/dist/replace-file.d.ts +41 -0
- package/dist/replace-file.d.ts.map +1 -0
- package/dist/replace-file.js +325 -0
- package/dist/root-file.d.ts +44 -0
- package/dist/root-file.d.ts.map +1 -0
- package/dist/root-file.js +129 -0
- package/dist/root-path.d.ts +39 -0
- package/dist/root-path.d.ts.map +1 -0
- package/dist/root-path.js +598 -0
- package/dist/root-paths.d.ts +92 -0
- package/dist/root-paths.d.ts.map +1 -0
- package/dist/root-paths.js +338 -0
- package/dist/root.d.ts +108 -0
- package/dist/root.d.ts.map +1 -0
- package/dist/root.js +1332 -0
- package/dist/safe-open-sync.d.ts +24 -0
- package/dist/safe-open-sync.d.ts.map +1 -0
- package/dist/safe-open-sync.js +71 -0
- package/dist/safe-root.d.ts +123 -0
- package/dist/safe-root.d.ts.map +1 -0
- package/dist/safe-root.js +1060 -0
- package/dist/secret-file.d.ts +17 -0
- package/dist/secret-file.d.ts.map +1 -0
- package/dist/secret-file.js +232 -0
- package/dist/secret.d.ts +2 -0
- package/dist/secret.d.ts.map +1 -0
- package/dist/secret.js +1 -0
- package/dist/secure-file.d.ts +32 -0
- package/dist/secure-file.d.ts.map +1 -0
- package/dist/secure-file.js +163 -0
- package/dist/secure-temp-dir.d.ts +27 -0
- package/dist/secure-temp-dir.d.ts.map +1 -0
- package/dist/secure-temp-dir.js +155 -0
- package/dist/secure-temp-workspace.d.ts +25 -0
- package/dist/secure-temp-workspace.d.ts.map +1 -0
- package/dist/secure-temp-workspace.js +136 -0
- package/dist/sibling-temp-file.d.ts +16 -0
- package/dist/sibling-temp-file.d.ts.map +1 -0
- package/dist/sibling-temp-file.js +73 -0
- package/dist/sibling-temp-write.d.ts +8 -0
- package/dist/sibling-temp-write.d.ts.map +1 -0
- package/dist/sibling-temp-write.js +40 -0
- package/dist/sibling-temp.d.ts +23 -0
- package/dist/sibling-temp.d.ts.map +1 -0
- package/dist/sibling-temp.js +119 -0
- package/dist/sidecar-lock.d.ts +50 -0
- package/dist/sidecar-lock.d.ts.map +1 -0
- package/dist/sidecar-lock.js +235 -0
- package/dist/store.d.ts +3 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +2 -0
- package/dist/string-coerce.d.ts +14 -0
- package/dist/string-coerce.d.ts.map +1 -0
- package/dist/string-coerce.js +72 -0
- package/dist/symlink-parents.d.ts +12 -0
- package/dist/symlink-parents.d.ts.map +1 -0
- package/dist/symlink-parents.js +78 -0
- package/dist/temp-cleanup.d.ts +6 -0
- package/dist/temp-cleanup.d.ts.map +1 -0
- package/dist/temp-cleanup.js +42 -0
- package/dist/temp-target.d.ts +28 -0
- package/dist/temp-target.d.ts.map +1 -0
- package/dist/temp-target.js +84 -0
- package/dist/temp.d.ts +3 -0
- package/dist/temp.d.ts.map +1 -0
- package/dist/temp.js +2 -0
- package/dist/test-hooks.d.ts +9 -0
- package/dist/test-hooks.d.ts.map +1 -0
- package/dist/test-hooks.js +13 -0
- package/dist/text-atomic.d.ts +7 -0
- package/dist/text-atomic.d.ts.map +1 -0
- package/dist/text-atomic.js +13 -0
- package/dist/timing.d.ts +6 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +24 -0
- package/dist/trash.d.ts +5 -0
- package/dist/trash.d.ts.map +1 -0
- package/dist/trash.js +127 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/walk.d.ts +26 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +169 -0
- package/dist/write-queue.d.ts +2 -0
- package/dist/write-queue.d.ts.map +1 -0
- package/dist/write-queue.js +18 -0
- package/package.json +117 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
function getErrorCode(err) {
|
|
6
|
+
return err instanceof Error ? err.code : undefined;
|
|
7
|
+
}
|
|
8
|
+
export class JsonFileReadError extends Error {
|
|
9
|
+
filePath;
|
|
10
|
+
reason;
|
|
11
|
+
constructor(filePath, reason, cause) {
|
|
12
|
+
super(`Failed to ${reason} JSON file: ${filePath}`, { cause });
|
|
13
|
+
this.name = "JsonFileReadError";
|
|
14
|
+
this.filePath = filePath;
|
|
15
|
+
this.reason = reason;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function replaceFileWithWindowsFallback(tempPath, filePath, mode) {
|
|
19
|
+
try {
|
|
20
|
+
await fs.rename(tempPath, filePath);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const code = getErrorCode(err);
|
|
25
|
+
if (process.platform !== "win32" || (code !== "EPERM" && code !== "EEXIST")) {
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const existing = await fs.lstat(filePath).catch(() => null);
|
|
30
|
+
if (existing?.isSymbolicLink()) {
|
|
31
|
+
await fs.rm(filePath, { force: true });
|
|
32
|
+
await fs.rename(tempPath, filePath);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await fs.copyFile(tempPath, filePath);
|
|
36
|
+
try {
|
|
37
|
+
await fs.chmod(filePath, mode);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// best-effort; ignore on platforms without chmod
|
|
41
|
+
}
|
|
42
|
+
await fs.rm(tempPath, { force: true }).catch(() => undefined);
|
|
43
|
+
}
|
|
44
|
+
export async function readJsonFile(filePath) {
|
|
45
|
+
try {
|
|
46
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function readDurableJsonFile(filePath) {
|
|
54
|
+
let raw;
|
|
55
|
+
try {
|
|
56
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (getErrorCode(err) === "ENOENT") {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
throw new JsonFileReadError(filePath, "read", err);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(raw);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
throw new JsonFileReadError(filePath, "parse", err);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function readJsonFileSync(filePath) {
|
|
72
|
+
try {
|
|
73
|
+
const raw = readFileSync(filePath, "utf8");
|
|
74
|
+
return JSON.parse(raw);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export async function writeJsonAtomic(filePath, value, options) {
|
|
81
|
+
const text = JSON.stringify(value, null, 2);
|
|
82
|
+
await writeTextAtomic(filePath, text, {
|
|
83
|
+
mode: options?.mode,
|
|
84
|
+
ensureDirMode: options?.ensureDirMode,
|
|
85
|
+
appendTrailingNewline: options?.trailingNewline,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export async function writeTextAtomic(filePath, content, options) {
|
|
89
|
+
const mode = options?.mode ?? 0o600;
|
|
90
|
+
const payload = options?.appendTrailingNewline && !content.endsWith("\n") ? `${content}\n` : content;
|
|
91
|
+
const mkdirOptions = { recursive: true };
|
|
92
|
+
if (typeof options?.ensureDirMode === "number") {
|
|
93
|
+
mkdirOptions.mode = options.ensureDirMode;
|
|
94
|
+
}
|
|
95
|
+
await fs.mkdir(path.dirname(filePath), mkdirOptions);
|
|
96
|
+
const parentDir = path.dirname(filePath);
|
|
97
|
+
const tmp = `${filePath}.${randomUUID()}.tmp`;
|
|
98
|
+
try {
|
|
99
|
+
const tmpHandle = await fs.open(tmp, "w", mode);
|
|
100
|
+
try {
|
|
101
|
+
await tmpHandle.writeFile(payload, { encoding: "utf8" });
|
|
102
|
+
await tmpHandle.sync();
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await tmpHandle.close().catch(() => undefined);
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
await fs.chmod(tmp, mode);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// best-effort; ignore on platforms without chmod
|
|
112
|
+
}
|
|
113
|
+
await replaceFileWithWindowsFallback(tmp, filePath, mode);
|
|
114
|
+
try {
|
|
115
|
+
const dirHandle = await fs.open(parentDir, "r");
|
|
116
|
+
try {
|
|
117
|
+
await dirHandle.sync();
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
await dirHandle.close().catch(() => undefined);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// best-effort; some platforms/filesystems do not support syncing directories.
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
await fs.chmod(filePath, mode);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// best-effort; ignore on platforms without chmod
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
await fs.rm(tmp, { force: true }).catch(() => undefined);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export function createAsyncLock() {
|
|
138
|
+
let lock = Promise.resolve();
|
|
139
|
+
return async function withLock(fn) {
|
|
140
|
+
const prev = lock;
|
|
141
|
+
let release;
|
|
142
|
+
lock = new Promise((resolve) => {
|
|
143
|
+
release = resolve;
|
|
144
|
+
});
|
|
145
|
+
await prev;
|
|
146
|
+
try {
|
|
147
|
+
return await fn();
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
release?.();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type JsonFileStoreOptions, type JsonStore, type JsonStoreLockOptions } from "./json-document-store.js";
|
|
2
|
+
export type JsonStoreOptions<T> = JsonFileStoreOptions & {
|
|
3
|
+
filePath: string;
|
|
4
|
+
dirMode?: number;
|
|
5
|
+
mode?: number;
|
|
6
|
+
};
|
|
7
|
+
export type { JsonFileStoreOptions, JsonStore, JsonStoreLockOptions };
|
|
8
|
+
export declare function jsonStore<T>(options: JsonStoreOptions<T>): JsonStore<T>;
|
|
9
|
+
//# sourceMappingURL=json-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-store.d.ts","sourceRoot":"","sources":["../src/json-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACd,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,oBAAoB,GAAG;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,YAAY,EAAE,oBAAoB,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAC;AAEtE,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAQvE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileStore } from "./file-store.js";
|
|
3
|
+
import { createJsonStore, } from "./json-document-store.js";
|
|
4
|
+
export function jsonStore(options) {
|
|
5
|
+
const filePath = path.resolve(options.filePath);
|
|
6
|
+
return fileStore({
|
|
7
|
+
rootDir: path.dirname(filePath),
|
|
8
|
+
private: true,
|
|
9
|
+
mode: options.mode,
|
|
10
|
+
dirMode: options.dirMode,
|
|
11
|
+
}).json(path.basename(filePath), options);
|
|
12
|
+
}
|
package/dist/json.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function tryReadJsonSync<T = unknown>(pathname: string): T | null;
|
|
2
|
+
export declare function writeJsonSync(pathname: string, data: unknown): void;
|
|
3
|
+
export declare class JsonFileReadError extends Error {
|
|
4
|
+
readonly filePath: string;
|
|
5
|
+
readonly reason: "read" | "parse";
|
|
6
|
+
constructor(filePath: string, reason: "read" | "parse", cause: unknown);
|
|
7
|
+
}
|
|
8
|
+
export declare function tryReadJson<T>(filePath: string): Promise<T | null>;
|
|
9
|
+
export declare function readJson<T>(filePath: string): Promise<T>;
|
|
10
|
+
export declare function readJsonIfExists<T>(filePath: string): Promise<T | null>;
|
|
11
|
+
export declare function readJsonSync<T = unknown>(filePath: string): T;
|
|
12
|
+
export declare function writeJson(filePath: string, value: unknown, options?: {
|
|
13
|
+
mode?: number;
|
|
14
|
+
trailingNewline?: boolean;
|
|
15
|
+
dirMode?: number;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AA8FA,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAOvE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,QAmB5D;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;gBAEtB,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO;CAMvE;AAED,wBAAsB,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAOxE;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAY9D;AAED,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAe7E;AAED,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,CAY7D;AAED,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,iBAQzE"}
|
package/dist/json.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fsSync from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { readRegularFile, readRegularFileSync } from "./regular-file.js";
|
|
5
|
+
import { writeTextAtomic } from "./text-atomic.js";
|
|
6
|
+
const JSON_FILE_MODE = 0o600;
|
|
7
|
+
const JSON_DIR_MODE = 0o700;
|
|
8
|
+
const SUPPORTS_SYNC_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsSync.constants;
|
|
9
|
+
function getErrorCode(err) {
|
|
10
|
+
return err instanceof Error ? err.code : undefined;
|
|
11
|
+
}
|
|
12
|
+
function trySetSecureMode(pathname) {
|
|
13
|
+
let fd;
|
|
14
|
+
try {
|
|
15
|
+
fd = fsSync.openSync(pathname, fsSync.constants.O_RDONLY |
|
|
16
|
+
(SUPPORTS_SYNC_NOFOLLOW ? fsSync.constants.O_NOFOLLOW : 0));
|
|
17
|
+
fsSync.fchmodSync(fd, JSON_FILE_MODE);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// best-effort on platforms without chmod support
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
if (fd !== undefined) {
|
|
24
|
+
try {
|
|
25
|
+
fsSync.closeSync(fd);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// best-effort cleanup
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function trySyncDirectory(pathname) {
|
|
34
|
+
let fd;
|
|
35
|
+
try {
|
|
36
|
+
fd = fsSync.openSync(path.dirname(pathname), "r");
|
|
37
|
+
fsSync.fsyncSync(fd);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// best-effort; some platforms/filesystems do not support syncing directories.
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
if (fd !== undefined) {
|
|
44
|
+
try {
|
|
45
|
+
fsSync.closeSync(fd);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// best-effort cleanup
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function renameJsonFileWithFallback(tmpPath, pathname) {
|
|
54
|
+
try {
|
|
55
|
+
fsSync.renameSync(tmpPath, pathname);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const code = error.code;
|
|
60
|
+
if (code === "EPERM" || code === "EEXIST") {
|
|
61
|
+
const existing = (() => {
|
|
62
|
+
try {
|
|
63
|
+
return fsSync.lstatSync(pathname);
|
|
64
|
+
}
|
|
65
|
+
catch (lstatError) {
|
|
66
|
+
if (lstatError.code === "ENOENT") {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
throw lstatError;
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
if (existing?.isSymbolicLink()) {
|
|
73
|
+
fsSync.rmSync(pathname, { force: true });
|
|
74
|
+
fsSync.renameSync(tmpPath, pathname);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
fsSync.copyFileSync(tmpPath, pathname);
|
|
78
|
+
fsSync.rmSync(tmpPath, { force: true });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function writeTempJsonFile(pathname, payload) {
|
|
85
|
+
const fd = fsSync.openSync(pathname, "wx", JSON_FILE_MODE);
|
|
86
|
+
try {
|
|
87
|
+
fsSync.writeFileSync(fd, payload, "utf8");
|
|
88
|
+
fsSync.fsyncSync(fd);
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
fsSync.closeSync(fd);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function tryReadJsonSync(pathname) {
|
|
95
|
+
try {
|
|
96
|
+
const raw = readRegularFileSync({ filePath: pathname }).buffer.toString("utf8");
|
|
97
|
+
return JSON.parse(raw);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export function writeJsonSync(pathname, data) {
|
|
104
|
+
const targetPath = pathname;
|
|
105
|
+
const tmpPath = `${targetPath}.${randomUUID()}.tmp`;
|
|
106
|
+
const payload = `${JSON.stringify(data, null, 2)}\n`;
|
|
107
|
+
fsSync.mkdirSync(path.dirname(targetPath), { recursive: true, mode: JSON_DIR_MODE });
|
|
108
|
+
try {
|
|
109
|
+
writeTempJsonFile(tmpPath, payload);
|
|
110
|
+
trySetSecureMode(tmpPath);
|
|
111
|
+
renameJsonFileWithFallback(tmpPath, targetPath);
|
|
112
|
+
trySetSecureMode(targetPath);
|
|
113
|
+
trySyncDirectory(targetPath);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
try {
|
|
117
|
+
fsSync.rmSync(tmpPath, { force: true });
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// best-effort cleanup when rename does not happen
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export class JsonFileReadError extends Error {
|
|
125
|
+
filePath;
|
|
126
|
+
reason;
|
|
127
|
+
constructor(filePath, reason, cause) {
|
|
128
|
+
super(`Failed to ${reason} JSON file: ${filePath}`, { cause });
|
|
129
|
+
this.name = "JsonFileReadError";
|
|
130
|
+
this.filePath = filePath;
|
|
131
|
+
this.reason = reason;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export async function tryReadJson(filePath) {
|
|
135
|
+
try {
|
|
136
|
+
const raw = (await readRegularFile({ filePath })).buffer.toString("utf8");
|
|
137
|
+
return JSON.parse(raw);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
export async function readJson(filePath) {
|
|
144
|
+
let raw;
|
|
145
|
+
try {
|
|
146
|
+
raw = (await readRegularFile({ filePath })).buffer.toString("utf8");
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
throw new JsonFileReadError(filePath, "read", err);
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
return JSON.parse(raw);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
throw new JsonFileReadError(filePath, "parse", err);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export async function readJsonIfExists(filePath) {
|
|
159
|
+
let raw;
|
|
160
|
+
try {
|
|
161
|
+
raw = (await readRegularFile({ filePath })).buffer.toString("utf8");
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
if (getErrorCode(err) === "ENOENT") {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
throw new JsonFileReadError(filePath, "read", err);
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
return JSON.parse(raw);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
throw new JsonFileReadError(filePath, "parse", err);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export function readJsonSync(filePath) {
|
|
177
|
+
let raw;
|
|
178
|
+
try {
|
|
179
|
+
raw = readRegularFileSync({ filePath }).buffer.toString("utf8");
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
throw new JsonFileReadError(filePath, "read", err);
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
return JSON.parse(raw);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
throw new JsonFileReadError(filePath, "parse", err);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
export async function writeJson(filePath, value, options) {
|
|
192
|
+
const text = JSON.stringify(value, null, 2);
|
|
193
|
+
await writeTextAtomic(filePath, text, {
|
|
194
|
+
mode: options?.mode,
|
|
195
|
+
dirMode: options?.dirMode,
|
|
196
|
+
trailingNewline: options?.trailingNewline,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function hasEncodedFileUrlSeparator(pathname: string): boolean;
|
|
2
|
+
export declare function isWindowsNetworkPath(filePath: string, platform?: NodeJS.Platform): boolean;
|
|
3
|
+
export declare function isWindowsDriveLetterPath(filePath: string, platform?: NodeJS.Platform): boolean;
|
|
4
|
+
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;
|
|
7
|
+
export declare function basenameFromMediaSource(source?: string): string | undefined;
|
|
8
|
+
//# sourceMappingURL=local-file-access.d.ts.map
|
|
@@ -0,0 +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,CAMT;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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath, URL } from "node:url";
|
|
3
|
+
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
4
|
+
const ENCODED_FILE_URL_SEPARATOR_RE = /%(?:2f|5c)/i;
|
|
5
|
+
function isLocalFileUrlHost(hostname) {
|
|
6
|
+
const normalized = normalizeLowercaseStringOrEmpty(hostname);
|
|
7
|
+
return normalized === "" || normalized === "localhost";
|
|
8
|
+
}
|
|
9
|
+
export function hasEncodedFileUrlSeparator(pathname) {
|
|
10
|
+
return ENCODED_FILE_URL_SEPARATOR_RE.test(pathname);
|
|
11
|
+
}
|
|
12
|
+
export function isWindowsNetworkPath(filePath, platform = process.platform) {
|
|
13
|
+
if (platform !== "win32") {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const normalized = filePath.replace(/\//g, "\\");
|
|
17
|
+
return normalized.startsWith("\\\\?\\UNC\\") || normalized.startsWith("\\\\");
|
|
18
|
+
}
|
|
19
|
+
export function isWindowsDriveLetterPath(filePath, platform = process.platform) {
|
|
20
|
+
return platform === "win32" && /^[A-Za-z]:[\\/]/.test(filePath);
|
|
21
|
+
}
|
|
22
|
+
export function assertNoWindowsNetworkPath(filePath, label = "Path") {
|
|
23
|
+
if (isWindowsNetworkPath(filePath)) {
|
|
24
|
+
throw new Error(`${label} cannot use Windows network paths: ${filePath}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function safeFileURLToPath(fileUrl) {
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = new URL(fileUrl);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new Error(`Invalid file:// URL: ${fileUrl}`);
|
|
34
|
+
}
|
|
35
|
+
if (parsed.protocol !== "file:") {
|
|
36
|
+
throw new Error(`Invalid file:// URL: ${fileUrl}`);
|
|
37
|
+
}
|
|
38
|
+
if (!isLocalFileUrlHost(parsed.hostname)) {
|
|
39
|
+
throw new Error(`file:// URLs with remote hosts are not allowed: ${fileUrl}`);
|
|
40
|
+
}
|
|
41
|
+
if (hasEncodedFileUrlSeparator(parsed.pathname)) {
|
|
42
|
+
throw new Error(`file:// URLs cannot encode path separators: ${fileUrl}`);
|
|
43
|
+
}
|
|
44
|
+
const filePath = fileURLToPath(parsed);
|
|
45
|
+
assertNoWindowsNetworkPath(filePath, "Local file URL");
|
|
46
|
+
return filePath;
|
|
47
|
+
}
|
|
48
|
+
export function trySafeFileURLToPath(fileUrl) {
|
|
49
|
+
try {
|
|
50
|
+
return safeFileURLToPath(fileUrl);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function basenameFromMediaSource(source) {
|
|
57
|
+
if (!source) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
if (source.startsWith("file://")) {
|
|
61
|
+
const filePath = trySafeFileURLToPath(source);
|
|
62
|
+
return filePath ? path.basename(filePath) || undefined : undefined;
|
|
63
|
+
}
|
|
64
|
+
if (/^https?:\/\//i.test(source)) {
|
|
65
|
+
try {
|
|
66
|
+
return path.basename(new URL(source).pathname) || undefined;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return path.basename(source) || undefined;
|
|
73
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type HardlinkPolicy, type ReadResult, type SymlinkPolicy } from "./root.js";
|
|
2
|
+
export type LocalRootsPathResult = {
|
|
3
|
+
path: string;
|
|
4
|
+
root: string;
|
|
5
|
+
};
|
|
6
|
+
export type LocalRootsReadResult = ReadResult & {
|
|
7
|
+
root: string;
|
|
8
|
+
};
|
|
9
|
+
export type LocalRootsInputOptions = {
|
|
10
|
+
filePath: string;
|
|
11
|
+
roots: readonly string[];
|
|
12
|
+
label?: string;
|
|
13
|
+
};
|
|
14
|
+
export type ResolveLocalPathFromRootsSyncOptions = LocalRootsInputOptions & {
|
|
15
|
+
allowMissing?: boolean;
|
|
16
|
+
requireFile?: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type ReadLocalFileFromRootsOptions = LocalRootsInputOptions & {
|
|
19
|
+
hardlinks?: HardlinkPolicy;
|
|
20
|
+
maxBytes?: number;
|
|
21
|
+
nonBlockingRead?: boolean;
|
|
22
|
+
symlinks?: SymlinkPolicy;
|
|
23
|
+
};
|
|
24
|
+
export declare function resolveLocalPathFromRootsSync(options: ResolveLocalPathFromRootsSyncOptions): LocalRootsPathResult | null;
|
|
25
|
+
export declare function readLocalFileFromRoots(options: ReadLocalFileFromRootsOptions): Promise<LocalRootsReadResult | null>;
|
|
26
|
+
//# sourceMappingURL=local-roots.d.ts.map
|
|
@@ -0,0 +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;AA0FF,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,CAgCtC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { expandHomePrefix, resolveUserPath } from "./home-dir.js";
|
|
5
|
+
import { safeFileURLToPath } from "./local-file-access.js";
|
|
6
|
+
import { isPathInside } from "./path.js";
|
|
7
|
+
import { root } from "./root.js";
|
|
8
|
+
function resolveLocalPathInput(input, label) {
|
|
9
|
+
if (input.startsWith("file://")) {
|
|
10
|
+
try {
|
|
11
|
+
return safeFileURLToPath(input);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
const location = label === "file path" ? "" : ` in ${label}`;
|
|
15
|
+
throw new Error(`Invalid file:// URL${location}: ${input}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (input.includes("\0")) {
|
|
19
|
+
throw new FsSafeError("invalid-path", `${label} must not contain NUL bytes`);
|
|
20
|
+
}
|
|
21
|
+
return resolveUserPath(input);
|
|
22
|
+
}
|
|
23
|
+
function resolveLocalRootInput(input, label) {
|
|
24
|
+
const trimmed = input.trim();
|
|
25
|
+
if (!trimmed) {
|
|
26
|
+
throw new FsSafeError("invalid-path", `${label} entry is required`);
|
|
27
|
+
}
|
|
28
|
+
const resolved = trimmed.startsWith("file://")
|
|
29
|
+
? resolveLocalPathInput(trimmed, label)
|
|
30
|
+
: expandHomePrefix(trimmed);
|
|
31
|
+
if (resolved.includes("\0")) {
|
|
32
|
+
throw new FsSafeError("invalid-path", `${label} entry must not contain NUL bytes`);
|
|
33
|
+
}
|
|
34
|
+
if (!path.isAbsolute(resolved)) {
|
|
35
|
+
throw new FsSafeError("invalid-path", `${label} entries must be absolute paths: ${input}`);
|
|
36
|
+
}
|
|
37
|
+
return path.resolve(resolved);
|
|
38
|
+
}
|
|
39
|
+
function isPathInsideRoot(candidate, rootDir) {
|
|
40
|
+
return isPathInside(rootDir, candidate);
|
|
41
|
+
}
|
|
42
|
+
function resolveRootRealSync(rootDir) {
|
|
43
|
+
try {
|
|
44
|
+
const stat = fsSync.lstatSync(rootDir);
|
|
45
|
+
if (!stat.isDirectory()) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return fsSync.realpathSync(rootDir);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function resolveCandidateCanonicalSync(filePath) {
|
|
55
|
+
try {
|
|
56
|
+
const stat = fsSync.lstatSync(filePath);
|
|
57
|
+
return {
|
|
58
|
+
exists: true,
|
|
59
|
+
canonicalPath: fsSync.realpathSync(filePath),
|
|
60
|
+
isFile: stat.isFile(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
if (err.code !== "ENOENT") {
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
let cursor = filePath;
|
|
69
|
+
const missingSegments = [];
|
|
70
|
+
while (true) {
|
|
71
|
+
const parent = path.dirname(cursor);
|
|
72
|
+
if (parent === cursor) {
|
|
73
|
+
return { exists: false, canonicalPath: filePath };
|
|
74
|
+
}
|
|
75
|
+
missingSegments.unshift(path.basename(cursor));
|
|
76
|
+
cursor = parent;
|
|
77
|
+
try {
|
|
78
|
+
fsSync.lstatSync(cursor);
|
|
79
|
+
const ancestorReal = fsSync.realpathSync(cursor);
|
|
80
|
+
return {
|
|
81
|
+
exists: false,
|
|
82
|
+
canonicalPath: path.join(ancestorReal, ...missingSegments),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
if (err.code !== "ENOENT") {
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function resolveLocalPathFromRootsSync(options) {
|
|
93
|
+
const label = options.label ?? "local roots";
|
|
94
|
+
const requestedPath = path.resolve(resolveLocalPathInput(options.filePath, "file path"));
|
|
95
|
+
for (const rootEntry of options.roots) {
|
|
96
|
+
const rootDir = resolveLocalRootInput(rootEntry, label);
|
|
97
|
+
const rootReal = resolveRootRealSync(rootDir);
|
|
98
|
+
if (!rootReal) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
let candidate;
|
|
102
|
+
try {
|
|
103
|
+
candidate = resolveCandidateCanonicalSync(requestedPath);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (!candidate.exists && options.allowMissing !== true) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (candidate.exists && options.requireFile === true && !candidate.isFile) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (isPathInsideRoot(candidate.canonicalPath, rootReal)) {
|
|
115
|
+
return { path: candidate.canonicalPath, root: rootReal };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
export async function readLocalFileFromRoots(options) {
|
|
121
|
+
const label = options.label ?? "local roots";
|
|
122
|
+
const requestedPath = path.resolve(resolveLocalPathInput(options.filePath, "file path"));
|
|
123
|
+
for (const rootEntry of options.roots) {
|
|
124
|
+
const rootDir = resolveLocalRootInput(rootEntry, label);
|
|
125
|
+
let scopedRoot;
|
|
126
|
+
try {
|
|
127
|
+
scopedRoot = await root(rootDir);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const relativePath = path.relative(scopedRoot.rootDir, requestedPath);
|
|
133
|
+
if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const result = await scopedRoot.read(relativePath, {
|
|
138
|
+
hardlinks: options.hardlinks,
|
|
139
|
+
maxBytes: options.maxBytes,
|
|
140
|
+
nonBlockingRead: options.nonBlockingRead,
|
|
141
|
+
symlinks: options.symlinks,
|
|
142
|
+
});
|
|
143
|
+
return { ...result, root: scopedRoot.rootReal };
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
package/dist/mode.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mode.d.ts","sourceRoot":"","sources":["../src/mode.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD"}
|
package/dist/mode.js
ADDED