@j0hanz/filesystem-mcp 2.8.1 → 2.9.1
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/README.md +33 -27
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +10 -1
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +4 -4
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +1 -1
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +7 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/file-uri.d.ts +12 -3
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +35 -7
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fmt.d.ts +2 -0
- package/dist/core/fmt.d.ts.map +1 -1
- package/dist/core/fmt.js +16 -0
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +16 -1
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +127 -32
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +15 -0
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +119 -14
- package/dist/core/glob.js.map +1 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +5 -3
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-utils.d.ts +42 -6
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +56 -10
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +111 -7
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +453 -65
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +6 -0
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +26 -3
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +9 -6
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +22 -19
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +6 -0
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +161 -33
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +64 -15
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +1 -0
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +3 -0
- package/dist/core/store.js.map +1 -1
- package/dist/core/util.d.ts +12 -1
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +16 -1
- package/dist/core/util.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +20 -11
- package/dist/resources.js.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +121 -43
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +8 -4
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +36 -9
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +50 -11
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.js +1 -1
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +14 -12
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +15 -9
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +141 -86
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +72 -26
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.js +13 -6
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +113 -48
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +50 -35
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +15 -33
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +26 -8
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +1 -0
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/stdio.js +1 -1
- package/dist/transport/stdio.js.map +1 -1
- package/package.json +5 -5
|
@@ -29,17 +29,53 @@ export declare const CHAR_COLON = 58;
|
|
|
29
29
|
export declare const trimTrailingDotsAndSpaces: (segment: string) => string;
|
|
30
30
|
export declare function getReservedDeviceNameForPath(requestedPath: string): string | undefined;
|
|
31
31
|
export declare function isWindowsDriveRelativePath(requestedPath: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* True for a path that names a Windows drive: `C:\x`, `C:/x`, `C:`, and the
|
|
34
|
+
* drive-relative `C:x`. Only Windows resolves one. On POSIX it is a relative
|
|
35
|
+
* path, so `C:\x` would become a file of that name under the root.
|
|
36
|
+
*/
|
|
37
|
+
export declare const isWindowsDrivePath: (requestedPath: string) => boolean;
|
|
38
|
+
/**
|
|
39
|
+
* A drive path this host refuses before resolving it: drive-relative anywhere,
|
|
40
|
+
* and any drive path where the host is not Windows.
|
|
41
|
+
*/
|
|
42
|
+
export declare const isUnresolvableDrivePath: (requestedPath: string) => boolean;
|
|
43
|
+
export declare function expandHome(filepath: string): string;
|
|
32
44
|
export declare function normalizePath(p: string): string;
|
|
33
|
-
export declare function normalizeCaseForComparison(value: string): string;
|
|
34
45
|
export declare function isSamePath(left: string, right: string): boolean;
|
|
35
46
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
47
|
+
* What two names share when they differ only in Unicode form (NFC or NFD) or
|
|
48
|
+
* in which space character they use (U+0020, U+00A0, U+202F, and every other
|
|
49
|
+
* Zs), case-folded where the filesystem folds case. A model types back
|
|
50
|
+
* `caf\u00e9` and `shot PM.png` for the `cafe\u0301` and `shot\u202fPM.png` a
|
|
51
|
+
* listing showed it (#101).
|
|
52
|
+
*/
|
|
53
|
+
export declare function unicodeMatchKey(name: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* True when two paths are one path once case (where the filesystem folds it)
|
|
56
|
+
* and Unicode spelling are folded: the spellings the path guard resolves to
|
|
57
|
+
* one entry when only one of them is on disk.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isEquivalentPath(left: string, right: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* False for a name of printable ASCII with no space, which no other entry can
|
|
62
|
+
* share a `unicodeMatchKey` with — except a name holding U+212A, U+037E or
|
|
63
|
+
* U+1FEF, which NFC maps to ASCII; those stay unmatched. A missed lookup of
|
|
64
|
+
* such a name skips the directory scan, so creating `new-file.ts` costs what
|
|
65
|
+
* it did.
|
|
66
|
+
*/
|
|
67
|
+
export declare function mayHaveEquivalentSpelling(name: string): boolean;
|
|
68
|
+
/** `name` with each non-ASCII code unit as a `\uXXXX` escape, which a JSON string argument accepts. */
|
|
69
|
+
export declare function escapeNonAscii(name: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* A write target that already exists is realpathed to its on-disk spelling,
|
|
72
|
+
* so a rename that changes only case (`foo.txt` -> `Foo.txt` on a
|
|
73
|
+
* case-insensitive filesystem) or only Unicode spelling (`cafe\u0301.txt` ->
|
|
74
|
+
* `caf\u00e9.txt`) resolves to the source itself. Returns the resolved target
|
|
75
|
+
* re-spelled with the requested basename when the two differ only that way;
|
|
40
76
|
* otherwise the resolved target unchanged. Parent segments are not re-spelled.
|
|
41
77
|
*/
|
|
42
|
-
export declare function
|
|
78
|
+
export declare function respellSpellingOnlyTarget(requestedPath: string, resolvedTarget: string): string;
|
|
43
79
|
export declare function normalizeAllowedDirectory(dir: string): string;
|
|
44
80
|
export declare function isPathInsideDirectory(normalizedDirectory: string, normalizedCandidate: string): boolean;
|
|
45
81
|
export declare function isPathWithinDirectories(normalizedPath: string, allowedDirs: readonly string[]): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../src/core/path-utils.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnE,4FAA4F;AAC5F,MAAM,WAAW,UAAU;IACzB,WAAW,IAAI,OAAO,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC;IAClB,cAAc,IAAI,OAAO,CAAC;CAC3B;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAK9D;AAID;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAClC,IAAI,CAON;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAM9D;AAID,eAAO,MAAM,UAAU,SAA+B,CAAC;AAEvD,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,OAAqC,CAAC;AAE7E,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,OACoB,CAAC;AAE5D,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAAqC,CAAC;AAElF,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAEhE;AAID,eAAO,MAAM,UAAU,KAAK,CAAC;AA4B7B;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,MAAM,KAAG,MAAuC,CAAC;AASpG,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAStF;AAED,wBAAgB,0BAA0B,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAOzE;
|
|
1
|
+
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../src/core/path-utils.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnE,4FAA4F;AAC5F,MAAM,WAAW,UAAU;IACzB,WAAW,IAAI,OAAO,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC;IAClB,cAAc,IAAI,OAAO,CAAC;CAC3B;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAK9D;AAID;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAClC,IAAI,CAON;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAM9D;AAID,eAAO,MAAM,UAAU,SAA+B,CAAC;AAEvD,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,OAAqC,CAAC;AAE7E,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,OACoB,CAAC;AAE5D,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAAqC,CAAC;AAElF,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAEhE;AAID,eAAO,MAAM,UAAU,KAAK,CAAC;AA4B7B;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,MAAM,KAAG,MAAuC,CAAC;AASpG,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAStF;AAED,wBAAgB,0BAA0B,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAOzE;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,eAAe,MAAM,KAAG,OACN,CAAC;AAEtD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAAI,eAAe,MAAM,KAAG,OAC4B,CAAC;AAE7F,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAa/C;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAO/D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAIrE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED,uGAAuG;AACvG,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAO/F;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAa7D;AAMD,wBAAgB,qBAAqB,CACnC,mBAAmB,EAAE,MAAM,EAC3B,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAST;AAED,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAET"}
|
package/dist/core/path-utils.js
CHANGED
|
@@ -97,7 +97,18 @@ export function isWindowsDriveRelativePath(requestedPath) {
|
|
|
97
97
|
const { root } = win32.parse(requestedPath);
|
|
98
98
|
return root.length === 2;
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
/**
|
|
101
|
+
* True for a path that names a Windows drive: `C:\x`, `C:/x`, `C:`, and the
|
|
102
|
+
* drive-relative `C:x`. Only Windows resolves one. On POSIX it is a relative
|
|
103
|
+
* path, so `C:\x` would become a file of that name under the root.
|
|
104
|
+
*/
|
|
105
|
+
export const isWindowsDrivePath = (requestedPath) => /^[A-Za-z]:/u.test(win32.parse(requestedPath).root);
|
|
106
|
+
/**
|
|
107
|
+
* A drive path this host refuses before resolving it: drive-relative anywhere,
|
|
108
|
+
* and any drive path where the host is not Windows.
|
|
109
|
+
*/
|
|
110
|
+
export const isUnresolvableDrivePath = (requestedPath) => IS_WINDOWS ? isWindowsDriveRelativePath(requestedPath) : isWindowsDrivePath(requestedPath);
|
|
111
|
+
export function expandHome(filepath) {
|
|
101
112
|
if (filepath === '~' || filepath.startsWith('~/') || filepath.startsWith('~\\')) {
|
|
102
113
|
const rest = filepath.slice(1).replace(/^[/\\]+/, '');
|
|
103
114
|
return rest ? join(HOMEDIR, rest) : HOMEDIR;
|
|
@@ -117,7 +128,7 @@ export function normalizePath(p) {
|
|
|
117
128
|
return resolved;
|
|
118
129
|
}
|
|
119
130
|
const IS_CASE_INSENSITIVE_FS = IS_WINDOWS || platform() === 'darwin';
|
|
120
|
-
|
|
131
|
+
function normalizeCaseForComparison(value) {
|
|
121
132
|
return IS_CASE_INSENSITIVE_FS ? value.toLowerCase() : value;
|
|
122
133
|
}
|
|
123
134
|
export function isSamePath(left, right) {
|
|
@@ -129,18 +140,53 @@ export function isSamePath(left, right) {
|
|
|
129
140
|
return leftResolved === rightResolved;
|
|
130
141
|
}
|
|
131
142
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
143
|
+
* What two names share when they differ only in Unicode form (NFC or NFD) or
|
|
144
|
+
* in which space character they use (U+0020, U+00A0, U+202F, and every other
|
|
145
|
+
* Zs), case-folded where the filesystem folds case. A model types back
|
|
146
|
+
* `caf\u00e9` and `shot PM.png` for the `cafe\u0301` and `shot\u202fPM.png` a
|
|
147
|
+
* listing showed it (#101).
|
|
148
|
+
*/
|
|
149
|
+
export function unicodeMatchKey(name) {
|
|
150
|
+
return normalizeCaseForComparison(name.normalize('NFC').replace(/\p{Zs}/gu, ' '));
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* True when two paths are one path once case (where the filesystem folds it)
|
|
154
|
+
* and Unicode spelling are folded: the spellings the path guard resolves to
|
|
155
|
+
* one entry when only one of them is on disk.
|
|
156
|
+
*/
|
|
157
|
+
export function isEquivalentPath(left, right) {
|
|
158
|
+
return (isSamePath(left, right) || unicodeMatchKey(resolve(left)) === unicodeMatchKey(resolve(right)));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* False for a name of printable ASCII with no space, which no other entry can
|
|
162
|
+
* share a `unicodeMatchKey` with — except a name holding U+212A, U+037E or
|
|
163
|
+
* U+1FEF, which NFC maps to ASCII; those stay unmatched. A missed lookup of
|
|
164
|
+
* such a name skips the directory scan, so creating `new-file.ts` costs what
|
|
165
|
+
* it did.
|
|
166
|
+
*/
|
|
167
|
+
export function mayHaveEquivalentSpelling(name) {
|
|
168
|
+
return /[^\x21-\x7e]/u.test(name);
|
|
169
|
+
}
|
|
170
|
+
/** `name` with each non-ASCII code unit as a `\uXXXX` escape, which a JSON string argument accepts. */
|
|
171
|
+
export function escapeNonAscii(name) {
|
|
172
|
+
return name.replace(/[^\x20-\x7e]/gu, (char) => Array.from({ length: char.length }, (_, index) => `\\u${char.charCodeAt(index).toString(16).padStart(4, '0')}`).join(''));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A write target that already exists is realpathed to its on-disk spelling,
|
|
176
|
+
* so a rename that changes only case (`foo.txt` -> `Foo.txt` on a
|
|
177
|
+
* case-insensitive filesystem) or only Unicode spelling (`cafe\u0301.txt` ->
|
|
178
|
+
* `caf\u00e9.txt`) resolves to the source itself. Returns the resolved target
|
|
179
|
+
* re-spelled with the requested basename when the two differ only that way;
|
|
136
180
|
* otherwise the resolved target unchanged. Parent segments are not re-spelled.
|
|
137
181
|
*/
|
|
138
|
-
export function
|
|
182
|
+
export function respellSpellingOnlyTarget(requestedPath, resolvedTarget) {
|
|
139
183
|
const requestedName = basename(requestedPath);
|
|
140
|
-
|
|
184
|
+
const resolvedName = basename(resolvedTarget);
|
|
185
|
+
if (requestedName === resolvedName)
|
|
141
186
|
return resolvedTarget;
|
|
142
|
-
|
|
143
|
-
|
|
187
|
+
return unicodeMatchKey(requestedName) === unicodeMatchKey(resolvedName)
|
|
188
|
+
? join(dirname(resolvedTarget), requestedName)
|
|
189
|
+
: resolvedTarget;
|
|
144
190
|
}
|
|
145
191
|
export function normalizeAllowedDirectory(dir) {
|
|
146
192
|
const trimmed = dir.trim();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../../src/core/path-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir, platform } from 'node:os';\nimport {\n basename,\n delimiter,\n dirname,\n join,\n parse,\n relative,\n resolve,\n sep,\n win32,\n} from 'node:path';\n\n// ─── Shared primitives (no intra-package imports; every core module may import this) ──\n\nexport type EntryType = 'file' | 'directory' | 'symlink' | 'other';\n\n/** The three predicates both `Dirent` and `Stats` expose - all `resolveEntryType` needs. */\nexport interface DirentLike {\n isDirectory(): boolean;\n isFile(): boolean;\n isSymbolicLink(): boolean;\n}\n\nexport function resolveEntryType(dirent: DirentLike): EntryType {\n if (dirent.isDirectory()) return 'directory';\n if (dirent.isFile()) return 'file';\n if (dirent.isSymbolicLink()) return 'symlink';\n return 'other';\n}\n\nconst warnedSettings = new Set<string>();\n\n/**\n * Warn once per (setting, value) that a configured value was rejected, and say\n * what was used instead. console.error, not Logger: this module stays\n * dependency-free to avoid import cycles, and a startup misconfiguration is\n * worth printing even when the operator asked for errors only.\n */\nexport function warnInvalidSetting(\n name: string,\n value: string,\n expected: string,\n fallback: string | number | boolean,\n): void {\n const key = `${name}:${value}`;\n if (warnedSettings.has(key)) return;\n warnedSettings.add(key);\n console.error(\n `[warning] Invalid ${name} value: ${value} (must be ${expected}). Using default: ${String(fallback)}`,\n );\n}\n\nexport function splitDirList(val: string | undefined): string[] {\n if (!val) return [];\n return val\n .split(delimiter)\n .map((p) => p.trim())\n .filter((p) => p.length > 0);\n}\n\n// ─── Character-level path primitives ─────────────────────────────────────────\n\nexport const IS_WINDOWS = process.platform === 'win32';\n\nexport const isSlash = (code: number): boolean => code === 47 || code === 92;\n\nexport const isAlpha = (code: number): boolean =>\n (code >= 65 && code <= 90) || (code >= 97 && code <= 122);\n\nexport const toPosixPath = (value: string): string => value.replaceAll('\\\\', '/');\n\n/** `path.relative` with forward slashes, so displayed paths match across platforms. */\nexport function toPosixRelative(from: string, to: string): string {\n return toPosixPath(relative(from, to));\n}\n\n// ─── Path normalization and containment ──────────────────────────────────────\n\nexport const CHAR_COLON = 58;\nconst HOMEDIR = homedir();\n\nconst RESERVED_DEVICE_NAMES = new Set<string>([\n 'CON',\n 'PRN',\n 'AUX',\n 'NUL',\n 'COM1',\n 'COM2',\n 'COM3',\n 'COM4',\n 'COM5',\n 'COM6',\n 'COM7',\n 'COM8',\n 'COM9',\n 'LPT1',\n 'LPT2',\n 'LPT3',\n 'LPT4',\n 'LPT5',\n 'LPT6',\n 'LPT7',\n 'LPT8',\n 'LPT9',\n]);\n\n/**\n * Win32 strips trailing dots and spaces at the syscall boundary (a request for\n * `.env ` creates `.env`), so every name check must trim them first or it is\n * bypassed on Windows.\n */\nexport const trimTrailingDotsAndSpaces = (segment: string): string => segment.replace(/[. ]+$/, '');\n\nfunction getReservedDeviceName(segment: string): string | undefined {\n const trimmed = trimTrailingDotsAndSpaces(segment);\n const withoutStream = trimmed.split(':')[0] ?? '';\n const baseName = (withoutStream.split('.')[0] ?? '').toUpperCase();\n return RESERVED_DEVICE_NAMES.has(baseName) ? baseName : undefined;\n}\n\nexport function getReservedDeviceNameForPath(requestedPath: string): string | undefined {\n const segments = requestedPath.split(/[\\\\/]/u);\n for (const segment of segments) {\n const reserved = getReservedDeviceName(segment);\n if (reserved) {\n return reserved;\n }\n }\n return undefined;\n}\n\nexport function isWindowsDriveRelativePath(requestedPath: string): boolean {\n // Check on all platforms so cross-platform clients cannot smuggle drive-relative\n // inputs (e.g. C:relative) to a POSIX-hosted server where path.resolve would\n // silently expand them relative to CWD. win32.parse is the platform-independent\n // binding, so this reads the same on a POSIX host.\n const { root } = win32.parse(requestedPath);\n return root.length === 2;\n}\n\nfunction expandHome(filepath: string): string {\n if (filepath === '~' || filepath.startsWith('~/') || filepath.startsWith('~\\\\')) {\n const rest = filepath.slice(1).replace(/^[/\\\\]+/, '');\n return rest ? join(HOMEDIR, rest) : HOMEDIR;\n }\n return filepath;\n}\n\nexport function normalizePath(p: string): string {\n const resolved = resolve(expandHome(p));\n\n // On Windows only the drive letter is lowercased (e.g. \"C:\\Foo\\Bar\").\n // The rest of the path retains its original casing.\n // IMPORTANT: callers must use isSamePath / isPathInsideDirectory for all\n // equality and containment checks — never raw string equality — because\n // those helpers apply full case-folding before comparing.\n if (IS_WINDOWS && resolved.length >= 2 && resolved.charCodeAt(1) === CHAR_COLON) {\n return resolved.charAt(0).toLowerCase() + resolved.slice(1);\n }\n\n return resolved;\n}\n\nconst IS_CASE_INSENSITIVE_FS = IS_WINDOWS || platform() === 'darwin';\n\nexport function normalizeCaseForComparison(value: string): string {\n return IS_CASE_INSENSITIVE_FS ? value.toLowerCase() : value;\n}\n\nexport function isSamePath(left: string, right: string): boolean {\n if (left === right) {\n return true;\n }\n const leftResolved = normalizeCaseForComparison(resolve(left));\n const rightResolved = normalizeCaseForComparison(resolve(right));\n return leftResolved === rightResolved;\n}\n\n/**\n * On a case-insensitive filesystem a write target that already exists is\n * realpathed to its on-disk spelling, so a case-only rename (`foo.txt` ->\n * `Foo.txt`) resolves to the source itself. Returns the resolved target\n * re-spelled with the requested basename when the two differ only by case;\n * otherwise the resolved target unchanged. Parent segments are not re-spelled.\n */\nexport function respellCaseOnlyTarget(requestedPath: string, resolvedTarget: string): string {\n const requestedName = basename(requestedPath);\n if (requestedName === basename(resolvedTarget)) return resolvedTarget;\n const respelled = join(dirname(resolvedTarget), requestedName);\n return isSamePath(respelled, resolvedTarget) ? respelled : resolvedTarget;\n}\n\nexport function normalizeAllowedDirectory(dir: string): string {\n const trimmed = dir.trim();\n if (trimmed.length === 0) return '';\n\n const normalized = normalizePath(trimmed);\n const { root } = parse(normalized);\n\n // Keep filesystem roots as-is (\"/\", \"c:\\\\\", \"\\\\\\\\server\\\\share\\\\\").\n if (isSamePath(normalized, root)) {\n return root;\n }\n\n return normalized.length > 1 && normalized.endsWith(sep) ? normalized.slice(0, -1) : normalized;\n}\n\n// `\\` separates path segments only on Windows. On POSIX it is a filename\n// character, so `/root\\x` names a sibling of `/root`, not a child.\nconst isPathSeparator = (code: number): boolean => code === 47 || (IS_WINDOWS && code === 92);\n\nexport function isPathInsideDirectory(\n normalizedDirectory: string,\n normalizedCandidate: string,\n): boolean {\n const root = normalizeCaseForComparison(normalizedDirectory);\n const candidate = normalizeCaseForComparison(normalizedCandidate);\n\n if (root === candidate) return true;\n if (!candidate.startsWith(root)) return false;\n\n if (isPathSeparator(root.charCodeAt(root.length - 1))) return true;\n return isPathSeparator(candidate.charCodeAt(root.length));\n}\n\nexport function isPathWithinDirectories(\n normalizedPath: string,\n allowedDirs: readonly string[],\n): boolean {\n return allowedDirs.some((dir) => isPathInsideDirectory(dir, normalizedPath));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../../src/core/path-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir, platform } from 'node:os';\nimport {\n basename,\n delimiter,\n dirname,\n join,\n parse,\n relative,\n resolve,\n sep,\n win32,\n} from 'node:path';\n\n// ─── Shared primitives (no intra-package imports; every core module may import this) ──\n\nexport type EntryType = 'file' | 'directory' | 'symlink' | 'other';\n\n/** The three predicates both `Dirent` and `Stats` expose - all `resolveEntryType` needs. */\nexport interface DirentLike {\n isDirectory(): boolean;\n isFile(): boolean;\n isSymbolicLink(): boolean;\n}\n\nexport function resolveEntryType(dirent: DirentLike): EntryType {\n if (dirent.isDirectory()) return 'directory';\n if (dirent.isFile()) return 'file';\n if (dirent.isSymbolicLink()) return 'symlink';\n return 'other';\n}\n\nconst warnedSettings = new Set<string>();\n\n/**\n * Warn once per (setting, value) that a configured value was rejected, and say\n * what was used instead. console.error, not Logger: this module stays\n * dependency-free to avoid import cycles, and a startup misconfiguration is\n * worth printing even when the operator asked for errors only.\n */\nexport function warnInvalidSetting(\n name: string,\n value: string,\n expected: string,\n fallback: string | number | boolean,\n): void {\n const key = `${name}:${value}`;\n if (warnedSettings.has(key)) return;\n warnedSettings.add(key);\n console.error(\n `[warning] Invalid ${name} value: ${value} (must be ${expected}). Using default: ${String(fallback)}`,\n );\n}\n\nexport function splitDirList(val: string | undefined): string[] {\n if (!val) return [];\n return val\n .split(delimiter)\n .map((p) => p.trim())\n .filter((p) => p.length > 0);\n}\n\n// ─── Character-level path primitives ─────────────────────────────────────────\n\nexport const IS_WINDOWS = process.platform === 'win32';\n\nexport const isSlash = (code: number): boolean => code === 47 || code === 92;\n\nexport const isAlpha = (code: number): boolean =>\n (code >= 65 && code <= 90) || (code >= 97 && code <= 122);\n\nexport const toPosixPath = (value: string): string => value.replaceAll('\\\\', '/');\n\n/** `path.relative` with forward slashes, so displayed paths match across platforms. */\nexport function toPosixRelative(from: string, to: string): string {\n return toPosixPath(relative(from, to));\n}\n\n// ─── Path normalization and containment ──────────────────────────────────────\n\nexport const CHAR_COLON = 58;\nconst HOMEDIR = homedir();\n\nconst RESERVED_DEVICE_NAMES = new Set<string>([\n 'CON',\n 'PRN',\n 'AUX',\n 'NUL',\n 'COM1',\n 'COM2',\n 'COM3',\n 'COM4',\n 'COM5',\n 'COM6',\n 'COM7',\n 'COM8',\n 'COM9',\n 'LPT1',\n 'LPT2',\n 'LPT3',\n 'LPT4',\n 'LPT5',\n 'LPT6',\n 'LPT7',\n 'LPT8',\n 'LPT9',\n]);\n\n/**\n * Win32 strips trailing dots and spaces at the syscall boundary (a request for\n * `.env ` creates `.env`), so every name check must trim them first or it is\n * bypassed on Windows.\n */\nexport const trimTrailingDotsAndSpaces = (segment: string): string => segment.replace(/[. ]+$/, '');\n\nfunction getReservedDeviceName(segment: string): string | undefined {\n const trimmed = trimTrailingDotsAndSpaces(segment);\n const withoutStream = trimmed.split(':')[0] ?? '';\n const baseName = (withoutStream.split('.')[0] ?? '').toUpperCase();\n return RESERVED_DEVICE_NAMES.has(baseName) ? baseName : undefined;\n}\n\nexport function getReservedDeviceNameForPath(requestedPath: string): string | undefined {\n const segments = requestedPath.split(/[\\\\/]/u);\n for (const segment of segments) {\n const reserved = getReservedDeviceName(segment);\n if (reserved) {\n return reserved;\n }\n }\n return undefined;\n}\n\nexport function isWindowsDriveRelativePath(requestedPath: string): boolean {\n // Check on all platforms so cross-platform clients cannot smuggle drive-relative\n // inputs (e.g. C:relative) to a POSIX-hosted server where path.resolve would\n // silently expand them relative to CWD. win32.parse is the platform-independent\n // binding, so this reads the same on a POSIX host.\n const { root } = win32.parse(requestedPath);\n return root.length === 2;\n}\n\n/**\n * True for a path that names a Windows drive: `C:\\x`, `C:/x`, `C:`, and the\n * drive-relative `C:x`. Only Windows resolves one. On POSIX it is a relative\n * path, so `C:\\x` would become a file of that name under the root.\n */\nexport const isWindowsDrivePath = (requestedPath: string): boolean =>\n /^[A-Za-z]:/u.test(win32.parse(requestedPath).root);\n\n/**\n * A drive path this host refuses before resolving it: drive-relative anywhere,\n * and any drive path where the host is not Windows.\n */\nexport const isUnresolvableDrivePath = (requestedPath: string): boolean =>\n IS_WINDOWS ? isWindowsDriveRelativePath(requestedPath) : isWindowsDrivePath(requestedPath);\n\nexport function expandHome(filepath: string): string {\n if (filepath === '~' || filepath.startsWith('~/') || filepath.startsWith('~\\\\')) {\n const rest = filepath.slice(1).replace(/^[/\\\\]+/, '');\n return rest ? join(HOMEDIR, rest) : HOMEDIR;\n }\n return filepath;\n}\n\nexport function normalizePath(p: string): string {\n const resolved = resolve(expandHome(p));\n\n // On Windows only the drive letter is lowercased (e.g. \"C:\\Foo\\Bar\").\n // The rest of the path retains its original casing.\n // IMPORTANT: callers must use isSamePath / isPathInsideDirectory for all\n // equality and containment checks — never raw string equality — because\n // those helpers apply full case-folding before comparing.\n if (IS_WINDOWS && resolved.length >= 2 && resolved.charCodeAt(1) === CHAR_COLON) {\n return resolved.charAt(0).toLowerCase() + resolved.slice(1);\n }\n\n return resolved;\n}\n\nconst IS_CASE_INSENSITIVE_FS = IS_WINDOWS || platform() === 'darwin';\n\nfunction normalizeCaseForComparison(value: string): string {\n return IS_CASE_INSENSITIVE_FS ? value.toLowerCase() : value;\n}\n\nexport function isSamePath(left: string, right: string): boolean {\n if (left === right) {\n return true;\n }\n const leftResolved = normalizeCaseForComparison(resolve(left));\n const rightResolved = normalizeCaseForComparison(resolve(right));\n return leftResolved === rightResolved;\n}\n\n/**\n * What two names share when they differ only in Unicode form (NFC or NFD) or\n * in which space character they use (U+0020, U+00A0, U+202F, and every other\n * Zs), case-folded where the filesystem folds case. A model types back\n * `caf\\u00e9` and `shot PM.png` for the `cafe\\u0301` and `shot\\u202fPM.png` a\n * listing showed it (#101).\n */\nexport function unicodeMatchKey(name: string): string {\n return normalizeCaseForComparison(name.normalize('NFC').replace(/\\p{Zs}/gu, ' '));\n}\n\n/**\n * True when two paths are one path once case (where the filesystem folds it)\n * and Unicode spelling are folded: the spellings the path guard resolves to\n * one entry when only one of them is on disk.\n */\nexport function isEquivalentPath(left: string, right: string): boolean {\n return (\n isSamePath(left, right) || unicodeMatchKey(resolve(left)) === unicodeMatchKey(resolve(right))\n );\n}\n\n/**\n * False for a name of printable ASCII with no space, which no other entry can\n * share a `unicodeMatchKey` with — except a name holding U+212A, U+037E or\n * U+1FEF, which NFC maps to ASCII; those stay unmatched. A missed lookup of\n * such a name skips the directory scan, so creating `new-file.ts` costs what\n * it did.\n */\nexport function mayHaveEquivalentSpelling(name: string): boolean {\n return /[^\\x21-\\x7e]/u.test(name);\n}\n\n/** `name` with each non-ASCII code unit as a `\\uXXXX` escape, which a JSON string argument accepts. */\nexport function escapeNonAscii(name: string): string {\n return name.replace(/[^\\x20-\\x7e]/gu, (char) =>\n Array.from(\n { length: char.length },\n (_, index) => `\\\\u${char.charCodeAt(index).toString(16).padStart(4, '0')}`,\n ).join(''),\n );\n}\n\n/**\n * A write target that already exists is realpathed to its on-disk spelling,\n * so a rename that changes only case (`foo.txt` -> `Foo.txt` on a\n * case-insensitive filesystem) or only Unicode spelling (`cafe\\u0301.txt` ->\n * `caf\\u00e9.txt`) resolves to the source itself. Returns the resolved target\n * re-spelled with the requested basename when the two differ only that way;\n * otherwise the resolved target unchanged. Parent segments are not re-spelled.\n */\nexport function respellSpellingOnlyTarget(requestedPath: string, resolvedTarget: string): string {\n const requestedName = basename(requestedPath);\n const resolvedName = basename(resolvedTarget);\n if (requestedName === resolvedName) return resolvedTarget;\n return unicodeMatchKey(requestedName) === unicodeMatchKey(resolvedName)\n ? join(dirname(resolvedTarget), requestedName)\n : resolvedTarget;\n}\n\nexport function normalizeAllowedDirectory(dir: string): string {\n const trimmed = dir.trim();\n if (trimmed.length === 0) return '';\n\n const normalized = normalizePath(trimmed);\n const { root } = parse(normalized);\n\n // Keep filesystem roots as-is (\"/\", \"c:\\\\\", \"\\\\\\\\server\\\\share\\\\\").\n if (isSamePath(normalized, root)) {\n return root;\n }\n\n return normalized.length > 1 && normalized.endsWith(sep) ? normalized.slice(0, -1) : normalized;\n}\n\n// `\\` separates path segments only on Windows. On POSIX it is a filename\n// character, so `/root\\x` names a sibling of `/root`, not a child.\nconst isPathSeparator = (code: number): boolean => code === 47 || (IS_WINDOWS && code === 92);\n\nexport function isPathInsideDirectory(\n normalizedDirectory: string,\n normalizedCandidate: string,\n): boolean {\n const root = normalizeCaseForComparison(normalizedDirectory);\n const candidate = normalizeCaseForComparison(normalizedCandidate);\n\n if (root === candidate) return true;\n if (!candidate.startsWith(root)) return false;\n\n if (isPathSeparator(root.charCodeAt(root.length - 1))) return true;\n return isPathSeparator(candidate.charCodeAt(root.length));\n}\n\nexport function isPathWithinDirectories(\n normalizedPath: string,\n allowedDirs: readonly string[],\n): boolean {\n return allowedDirs.some((dir) => isPathInsideDirectory(dir, normalizedPath));\n}\n"]}
|
package/dist/core/path.d.ts
CHANGED
|
@@ -3,6 +3,19 @@ export interface ValidatedPathDetails {
|
|
|
3
3
|
resolvedPath: string;
|
|
4
4
|
isSymlink: boolean;
|
|
5
5
|
}
|
|
6
|
+
/** How one walk prints the paths it found; see `PathGuard.resultPaths`. */
|
|
7
|
+
export interface ResultPaths {
|
|
8
|
+
/** The root the relative paths are relative to, as configured; the walked directory when there is none. */
|
|
9
|
+
readonly root: string;
|
|
10
|
+
/** The walked directory is that root itself. */
|
|
11
|
+
readonly atRoot: boolean;
|
|
12
|
+
show(absolutePath: string): string;
|
|
13
|
+
}
|
|
14
|
+
/** A root and every spelling that names it: as configured, and its realpath when that differs. */
|
|
15
|
+
interface RootSpellings {
|
|
16
|
+
readonly root: string;
|
|
17
|
+
readonly spellings: readonly string[];
|
|
18
|
+
}
|
|
6
19
|
export interface ServerOptions {
|
|
7
20
|
allowCwd?: boolean;
|
|
8
21
|
cliAllowedDirs?: string[];
|
|
@@ -13,11 +26,16 @@ export declare function resolveAllowedDirectoriesState(dirs: readonly string[],
|
|
|
13
26
|
/**
|
|
14
27
|
* TOCTOU note: validation returns a plain string and the fs operation is a
|
|
15
28
|
* later syscall, so a symlink swapped in between could redirect it. Reads and
|
|
16
|
-
* appends in core/fs.ts open with O_NOFOLLOW (final component)
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
29
|
+
* appends in core/fs.ts open with O_NOFOLLOW (final component) and re-check the
|
|
30
|
+
* fd's real path on Linux (ancestor swap). stat's directory count opens and
|
|
31
|
+
* re-checks the directory, and reads entries through that descriptor, only on
|
|
32
|
+
* Linux with procfs; elsewhere it reads the directory by path, so the swap
|
|
33
|
+
* window stays there, like the read path's non-Linux residual. Residuals: the
|
|
34
|
+
* directory walker behind list and the search tools (core/glob.ts) still
|
|
35
|
+
* resolves each entry by path, as the count does for a symlinked entry;
|
|
36
|
+
* write paths that resolve a parent by name
|
|
37
|
+
* (`rename`, `mkdir`, `rm`, `cp`) would need openat()-relative-to-dirfd, which
|
|
38
|
+
* Node lacks; non-Linux skips the fd re-check.
|
|
21
39
|
*/
|
|
22
40
|
export declare class PathGuard {
|
|
23
41
|
private allowedDirectoriesState;
|
|
@@ -39,14 +57,28 @@ export declare class PathGuard {
|
|
|
39
57
|
* field — so the recompute can rebuild from it without re-filtering.
|
|
40
58
|
*/
|
|
41
59
|
private grantedDirectories;
|
|
60
|
+
/**
|
|
61
|
+
* The grants a user approved for a path a tool asked about, as opposed to
|
|
62
|
+
* the workspace roots a client declared. Relative paths never resolve
|
|
63
|
+
* against these: one approval must not change what a relative path names,
|
|
64
|
+
* neither in a later round of the same call nor in a later call.
|
|
65
|
+
*/
|
|
66
|
+
private accessGrants;
|
|
67
|
+
/**
|
|
68
|
+
* Where relative paths resolve: the roots minus `accessGrants`, each with
|
|
69
|
+
* its spellings, plus every spelling flattened for containment.
|
|
70
|
+
*/
|
|
71
|
+
private relativeBases;
|
|
42
72
|
private rootBoundaries;
|
|
43
73
|
private readonly options;
|
|
44
74
|
constructor(options?: ServerOptions);
|
|
45
75
|
/**
|
|
46
76
|
* `expanded` is the containment set (each root plus its realpath alias);
|
|
47
77
|
* `roots` is what a caller choosing "the root" sees, one entry per root.
|
|
78
|
+
* `relativeBases` lists the roots relative paths resolve against, with
|
|
79
|
+
* their spellings; without it, every root, each spelled only as given.
|
|
48
80
|
*/
|
|
49
|
-
initialize(expanded: readonly string[], roots?: readonly string[]): void;
|
|
81
|
+
initialize(expanded: readonly string[], roots?: readonly string[], relativeBases?: readonly RootSpellings[]): void;
|
|
50
82
|
isInitialized(): boolean;
|
|
51
83
|
getAllowedDirectories(): string[];
|
|
52
84
|
/**
|
|
@@ -63,6 +95,16 @@ export declare class PathGuard {
|
|
|
63
95
|
*/
|
|
64
96
|
getRoots(): string[];
|
|
65
97
|
isSensitive(filePath: string): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* True when a walked entry is inside the allowed directories and not sensitive,
|
|
100
|
+
* on both its requested and resolved paths. Every entry is realpath-resolved: a
|
|
101
|
+
* symlinked ancestor pointing outside the sandbox would otherwise pass the
|
|
102
|
+
* lexical check (fs.glob follows symlinks and yields external entries as
|
|
103
|
+
* non-symlink dirents). Escapes and sensitive targets throw ACCESS_DENIED and
|
|
104
|
+
* skippable errors filter the entry; anything else is fatal. The accepted
|
|
105
|
+
* TOCTOU window is documented on this class.
|
|
106
|
+
*/
|
|
107
|
+
isEntryAccessible(entryPath: string): Promise<boolean>;
|
|
66
108
|
validateExistingPath(requestedPath: string): Promise<string>;
|
|
67
109
|
/**
|
|
68
110
|
* True when a grant must never admit `targetDir` — an unsafe path (home,
|
|
@@ -98,20 +140,82 @@ export declare class PathGuard {
|
|
|
98
140
|
* pre-checks passed. The caller leaves a refused path to fail with
|
|
99
141
|
* ACCESS_DENIED during the operation. Idempotent: re-granting an already
|
|
100
142
|
* allowed directory is a no-op via the dedup in `initialize`.
|
|
143
|
+
*
|
|
144
|
+
* `kind` says where the grant came from: `access` for a path the user
|
|
145
|
+
* approved when a tool asked, `workspace` for a root the client declared.
|
|
146
|
+
* Only a workspace root becomes a place relative paths resolve.
|
|
101
147
|
*/
|
|
102
|
-
applyGrant(targetDir: string): Promise<boolean>;
|
|
148
|
+
applyGrant(targetDir: string, kind?: 'access' | 'workspace'): Promise<boolean>;
|
|
103
149
|
private isWithinBoundary;
|
|
104
150
|
private validateAccess;
|
|
105
151
|
private handleRealpathError;
|
|
106
152
|
validateExistingPathDetailed(requestedPath: string): Promise<ValidatedPathDetails>;
|
|
107
153
|
validateExistingDirectory(requestedPath: string): Promise<string>;
|
|
108
154
|
resolvePathOrRoot(pathValue: string | undefined): string;
|
|
155
|
+
/**
|
|
156
|
+
* Where the relative paths a caller sent should point, keyed by the spelling
|
|
157
|
+
* sent; a path that keeps its meaning is absent. A relative path resolves
|
|
158
|
+
* against the working directory, as it always has, while that lands inside
|
|
159
|
+
* a root. When it does not — a desktop host starts the server outside the
|
|
160
|
+
* project, and an HTTP client never shares its working directory — it
|
|
161
|
+
* resolves against the roots: the one root, or with several, the one it
|
|
162
|
+
* exists under. Searches print paths relative to the root (`resultPaths`),
|
|
163
|
+
* so this is what lets one tool's output feed the next.
|
|
164
|
+
*
|
|
165
|
+
* "Roots" here are the configured ones and those a client declared, never an
|
|
166
|
+
* access grant (see `accessGrants`). Absolute, `~` and drive paths keep their
|
|
167
|
+
* meaning, as does every path when there is no such root; validation judges
|
|
168
|
+
* them as before. A Windows drive path on POSIX is left for validation to
|
|
169
|
+
* refuse: joined to a root, `C:\x` would be an absolute path that passes. A
|
|
170
|
+
* relative path that exists under several roots, or under none of several,
|
|
171
|
+
* is refused rather than guessed.
|
|
172
|
+
*/
|
|
173
|
+
resolveRelativePaths(paths: readonly string[]): Promise<Map<string, string>>;
|
|
174
|
+
private resolveAgainstRoots;
|
|
175
|
+
/**
|
|
176
|
+
* How a tool prints the paths a walk of `walkedDir` found, so the caller can
|
|
177
|
+
* pass each one straight back: relative to the root holding `walkedDir`
|
|
178
|
+
* when `resolveRelativePaths` maps that spelling back to the same path,
|
|
179
|
+
* absolute otherwise. A scoped search of `src` prints `src/a.ts`, not
|
|
180
|
+
* `a.ts`, which would name `<root>/a.ts`. `root` is the root as configured,
|
|
181
|
+
* as `list_roots` shows it, even when the walk went through its realpath.
|
|
182
|
+
*
|
|
183
|
+
* The absolute fallback covers a working directory in a subfolder of a root
|
|
184
|
+
* (relative paths resolve there), a directory under an access grant only
|
|
185
|
+
* (relative paths never resolve there), several roots with the working
|
|
186
|
+
* directory outside them (a name two roots share would read back as
|
|
187
|
+
* ambiguous, and which names are shared can change between the print and
|
|
188
|
+
* the read), and a name that would not read back as given: `~`, blank, or a
|
|
189
|
+
* POSIX name holding a backslash or starting with a drive (`C:`).
|
|
190
|
+
*/
|
|
191
|
+
resultPaths(walkedDir: string): ResultPaths;
|
|
109
192
|
isAllowedRoot(normalizedPath: string): boolean;
|
|
110
193
|
private assertNotSensitiveFile;
|
|
111
194
|
private throwAccessDenied;
|
|
112
195
|
private resolveNearestExistingAncestor;
|
|
196
|
+
/**
|
|
197
|
+
* `normalized` re-spelled with the on-disk names of the components that are
|
|
198
|
+
* missing as typed but present under another Unicode spelling (#101), or
|
|
199
|
+
* `undefined` when no component changed. Only names below a root are
|
|
200
|
+
* matched, never the root's own. Each directory is scanned only after its
|
|
201
|
+
* real path is checked to be inside the roots, and each matched entry is
|
|
202
|
+
* realpathed and checked again, so a link found this way cannot lead out
|
|
203
|
+
* (`resolveLast: false` leaves the last one unresolved, for delete). The
|
|
204
|
+
* first component with no match ends the walk; it and the rest stay as
|
|
205
|
+
* typed, so a write still creates them. The result is a spelling only:
|
|
206
|
+
* the caller runs its own containment and denylist checks on it.
|
|
207
|
+
*/
|
|
208
|
+
private respellMissing;
|
|
209
|
+
/**
|
|
210
|
+
* The entry of `realDir` that `name` names: `name` itself when it is
|
|
211
|
+
* there, else the one entry with the same `unicodeMatchKey`. Two or more
|
|
212
|
+
* such entries are refused, never chosen between. `undefined` when none
|
|
213
|
+
* matches or the directory cannot be read.
|
|
214
|
+
*/
|
|
215
|
+
private findEquivalentEntry;
|
|
113
216
|
validatePathForWrite(requestedPath: string): Promise<string>;
|
|
114
217
|
validatePathForDelete(requestedPath: string): Promise<string>;
|
|
115
218
|
recomputeAllowedDirectories(): Promise<void>;
|
|
116
219
|
}
|
|
220
|
+
export {};
|
|
117
221
|
//# sourceMappingURL=path.d.ts.map
|
package/dist/core/path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/core/path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/core/path.ts"],"names":[],"mappings":"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"}
|