@spyglassmc/core 0.4.43 → 0.4.45
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/lib/common/UriStore.d.ts +43 -0
- package/lib/common/UriStore.js +166 -0
- package/lib/common/externals/BrowserExternals.js +9 -19
- package/lib/common/externals/NodeJsExternals.d.ts +10 -15
- package/lib/common/externals/NodeJsExternals.js +12 -39
- package/lib/common/externals/index.d.ts +17 -26
- package/lib/common/index.d.ts +1 -0
- package/lib/common/index.js +1 -0
- package/lib/common/util.d.ts +12 -2
- package/lib/common/util.js +31 -12
- package/lib/service/Config.d.ts +15 -25
- package/lib/service/Config.js +88 -4
- package/lib/service/FileWatcher.d.ts +21 -0
- package/lib/service/FileWatcher.js +2 -0
- package/lib/service/Project.d.ts +16 -8
- package/lib/service/Project.js +121 -128
- package/lib/service/Service.d.ts +1 -1
- package/lib/service/fileUtil.d.ts +3 -1
- package/lib/service/fileUtil.js +8 -4
- package/lib/service/index.d.ts +1 -0
- package/lib/service/index.js +1 -0
- package/lib/symbol/Symbol.d.ts +8 -8
- package/lib/symbol/Symbol.js +2 -0
- package/package.json +6 -6
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { RootUriString } from '../service/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Stores a collection of URIs in a trie. Each file URI can optionally have a metadata associated
|
|
4
|
+
* with it. No actual file system I/O is performed by this class.
|
|
5
|
+
*/
|
|
6
|
+
export declare class UriStore {
|
|
7
|
+
#private;
|
|
8
|
+
/**
|
|
9
|
+
* Adds a file URI or a directory URI to the store.
|
|
10
|
+
* Directory URIs must end with a slash (`/`), otherwise it will be treated as a file URI.
|
|
11
|
+
*/
|
|
12
|
+
add(uri: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Returns true if the specified URI exists in the store and is of the expected type.
|
|
15
|
+
* Directory URIs must end with a slash (`/`), otherwise it will be treated as a file URI.
|
|
16
|
+
*/
|
|
17
|
+
has(uri: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Deletes a URI from the store if it exists.
|
|
20
|
+
* For directories, all sub URIs under them will be recursively removed.
|
|
21
|
+
*/
|
|
22
|
+
delete(uri: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* Returns names of all direct children of the URI.
|
|
25
|
+
* An empty result is generated if the directory URI does not exist.
|
|
26
|
+
*/
|
|
27
|
+
getChildrenNames(uri: RootUriString): Generator<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Returns URIs of all files under a directory and its subdirectories.
|
|
30
|
+
* An empty result is generated if the directory URI does not exist.
|
|
31
|
+
*/
|
|
32
|
+
getSubFiles(uri: RootUriString): Generator<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Removes all URIs from the store.
|
|
35
|
+
*/
|
|
36
|
+
clear(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Creates a deep copy of this store.
|
|
39
|
+
*/
|
|
40
|
+
clone(): UriStore;
|
|
41
|
+
[Symbol.iterator](): Generator<string, any, any>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=UriStore.d.ts.map
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { normalizeUriPathname, Uri } from './util.js';
|
|
2
|
+
/**
|
|
3
|
+
* Stores a collection of URIs in a trie. Each file URI can optionally have a metadata associated
|
|
4
|
+
* with it. No actual file system I/O is performed by this class.
|
|
5
|
+
*/
|
|
6
|
+
export class UriStore {
|
|
7
|
+
#trie = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* Adds a file URI or a directory URI to the store.
|
|
10
|
+
* Directory URIs must end with a slash (`/`), otherwise it will be treated as a file URI.
|
|
11
|
+
*/
|
|
12
|
+
add(uri) {
|
|
13
|
+
const [parts, isDir] = this.#dissectUri(uri);
|
|
14
|
+
let node = this.#trie;
|
|
15
|
+
for (const [i, part] of parts.entries()) {
|
|
16
|
+
const isLast = i === parts.length - 1;
|
|
17
|
+
const shouldAddDir = !isLast || isDir;
|
|
18
|
+
if (!node.has(part)) {
|
|
19
|
+
node.set(part, shouldAddDir ? new Map() : {});
|
|
20
|
+
}
|
|
21
|
+
if (!isLast) {
|
|
22
|
+
const next = node.get(part);
|
|
23
|
+
if (next instanceof Map) {
|
|
24
|
+
node = next;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
throw new Error(`Cannot add '${uri}' because ${parts.slice(0, i + 1).join('/')} is a file`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns true if the specified URI exists in the store and is of the expected type.
|
|
34
|
+
* Directory URIs must end with a slash (`/`), otherwise it will be treated as a file URI.
|
|
35
|
+
*/
|
|
36
|
+
has(uri) {
|
|
37
|
+
const [parts, isDir] = this.#dissectUri(uri);
|
|
38
|
+
let node = this.#trie;
|
|
39
|
+
for (const part of parts) {
|
|
40
|
+
if (!(node instanceof Map)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
node = node.get(part);
|
|
44
|
+
}
|
|
45
|
+
return isDir
|
|
46
|
+
? node instanceof Map
|
|
47
|
+
: !!node && typeof node === 'object' && !(node instanceof Map);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Deletes a URI from the store if it exists.
|
|
51
|
+
* For directories, all sub URIs under them will be recursively removed.
|
|
52
|
+
*/
|
|
53
|
+
delete(uri) {
|
|
54
|
+
const [parts, _isDir] = this.#dissectUri(uri);
|
|
55
|
+
let node = this.#trie;
|
|
56
|
+
for (const [i, part] of parts.entries()) {
|
|
57
|
+
if (!(node instanceof Map)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (i === parts.length - 1) {
|
|
61
|
+
node.delete(part);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
node = node.get(part);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Returns names of all direct children of the URI.
|
|
70
|
+
* An empty result is generated if the directory URI does not exist.
|
|
71
|
+
*/
|
|
72
|
+
*getChildrenNames(uri) {
|
|
73
|
+
const [parts, _isDir] = this.#dissectUri(uri);
|
|
74
|
+
let node = this.#trie;
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
if (!(node instanceof Map)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
node = node.get(part);
|
|
80
|
+
}
|
|
81
|
+
if (!(node instanceof Map)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
yield* node.keys();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Returns URIs of all files under a directory and its subdirectories.
|
|
88
|
+
* An empty result is generated if the directory URI does not exist.
|
|
89
|
+
*/
|
|
90
|
+
*getSubFiles(uri) {
|
|
91
|
+
const [parts, _isDir] = this.#dissectUri(uri);
|
|
92
|
+
let node = this.#trie;
|
|
93
|
+
for (const part of parts) {
|
|
94
|
+
if (!(node instanceof Map)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
node = node.get(part);
|
|
98
|
+
}
|
|
99
|
+
if (!(node instanceof Map)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
yield* this.#collect(node, parts);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Removes all URIs from the store.
|
|
106
|
+
*/
|
|
107
|
+
clear() {
|
|
108
|
+
this.#trie.clear();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Creates a deep copy of this store.
|
|
112
|
+
*/
|
|
113
|
+
clone() {
|
|
114
|
+
const clonedStore = new UriStore();
|
|
115
|
+
clonedStore.#trie = structuredClone(this.#trie);
|
|
116
|
+
return clonedStore;
|
|
117
|
+
}
|
|
118
|
+
[Symbol.iterator]() {
|
|
119
|
+
return this.#collect(this.#trie, []);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Dissects a URI string into parts that can be used as edges in the trie.
|
|
123
|
+
*/
|
|
124
|
+
#dissectUri(uriString) {
|
|
125
|
+
const uri = new Uri(uriString);
|
|
126
|
+
const isDir = uriString.endsWith('/');
|
|
127
|
+
const parts = [
|
|
128
|
+
uri.protocol,
|
|
129
|
+
uri.host || 'localhost',
|
|
130
|
+
...normalizeUriPathname(uri.pathname).split('/')
|
|
131
|
+
// Filter out empty segments
|
|
132
|
+
.filter((p) => p)
|
|
133
|
+
.map(decodeURIComponent),
|
|
134
|
+
];
|
|
135
|
+
return [parts, isDir];
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Reconstructs a URI string from its parts.
|
|
139
|
+
*
|
|
140
|
+
* This is the inverse of `#dissectUri`.
|
|
141
|
+
* It handles special cases like omitting the host when it's 'localhost' and adding a trailing
|
|
142
|
+
* slash for directory URIs.
|
|
143
|
+
*/
|
|
144
|
+
#buildUri(parts, isDir) {
|
|
145
|
+
const [protocol, host, ...segments] = parts;
|
|
146
|
+
const pathname = normalizeUriPathname(`/${segments.map(encodeURIComponent).join('/')}`);
|
|
147
|
+
const trailingSlash = isDir && segments.length ? '/' : '';
|
|
148
|
+
return `${protocol}//${host === 'localhost' ? '' : host}${pathname}${trailingSlash}`;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Returns a generator that yields all URIs under a DirectoryNode recursively.
|
|
152
|
+
* @param currentParts Parts corresponding to the URI of the DirectoryNode.
|
|
153
|
+
*/
|
|
154
|
+
*#collect(node, currentParts) {
|
|
155
|
+
for (const [key, value] of node) {
|
|
156
|
+
const nextParts = [...currentParts, key];
|
|
157
|
+
if (value instanceof Map) {
|
|
158
|
+
yield* this.#collect(value, nextParts);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
yield this.#buildUri(nextParts, false);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=UriStore.js.map
|
|
@@ -35,21 +35,7 @@ export class BrowserEventEmitter {
|
|
|
35
35
|
return this;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
on(event, listener) {
|
|
40
|
-
if (event === 'ready') {
|
|
41
|
-
listener();
|
|
42
|
-
}
|
|
43
|
-
return this;
|
|
44
|
-
}
|
|
45
|
-
once(event, listener) {
|
|
46
|
-
if (event === 'ready') {
|
|
47
|
-
listener();
|
|
48
|
-
}
|
|
49
|
-
return this;
|
|
50
|
-
}
|
|
51
|
-
async close() { }
|
|
52
|
-
}
|
|
38
|
+
// TODO: Use Origin Private File System (OPFS) instead
|
|
53
39
|
class BrowserFileSystem {
|
|
54
40
|
static LocalStorageKey = 'spyglassmc-browser-fs';
|
|
55
41
|
states;
|
|
@@ -85,6 +71,9 @@ class BrowserFileSystem {
|
|
|
85
71
|
}
|
|
86
72
|
return new Uint8Array(arrayBufferFromBase64(entry.content));
|
|
87
73
|
}
|
|
74
|
+
async rm(_location, _options) {
|
|
75
|
+
throw new Error('Not implemented');
|
|
76
|
+
}
|
|
88
77
|
async showFile(_path) {
|
|
89
78
|
throw new Error('showFile not supported on browser');
|
|
90
79
|
}
|
|
@@ -94,7 +83,11 @@ class BrowserFileSystem {
|
|
|
94
83
|
if (!entry) {
|
|
95
84
|
throw new Error(`ENOENT: ${location}`);
|
|
96
85
|
}
|
|
97
|
-
return {
|
|
86
|
+
return {
|
|
87
|
+
isDirectory: () => entry.type === 'directory',
|
|
88
|
+
isFile: () => entry.type === 'file',
|
|
89
|
+
isSymbolicLink: () => false,
|
|
90
|
+
};
|
|
98
91
|
}
|
|
99
92
|
async unlink(location) {
|
|
100
93
|
location = location.toString();
|
|
@@ -105,9 +98,6 @@ class BrowserFileSystem {
|
|
|
105
98
|
delete this.states[location];
|
|
106
99
|
this.saveStates();
|
|
107
100
|
}
|
|
108
|
-
watch(_locations) {
|
|
109
|
-
return new BrowserFsWatcher();
|
|
110
|
-
}
|
|
111
101
|
async writeFile(location, data, _options) {
|
|
112
102
|
location = location.toString();
|
|
113
103
|
if (typeof data === 'string') {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import chokidar from 'chokidar';
|
|
2
1
|
import { EventEmitter } from 'node:events';
|
|
3
|
-
import fs from 'node:fs';
|
|
2
|
+
import fs, { promises as fsp } from 'node:fs';
|
|
4
3
|
import type { DecompressedFile, RootUriString } from '../../index.js';
|
|
5
|
-
import type { FsLocation
|
|
6
|
-
export declare function getNodeJsExternals({ cacheRoot }?: {
|
|
4
|
+
import type { FsLocation } from './index.js';
|
|
5
|
+
export declare function getNodeJsExternals({ cacheRoot, nodeFsp }?: {
|
|
7
6
|
cacheRoot?: RootUriString;
|
|
7
|
+
nodeFsp?: typeof fsp;
|
|
8
8
|
}): Readonly<{
|
|
9
9
|
archive: {
|
|
10
10
|
decompressBall(buffer: Uint8Array<ArrayBuffer>, options: {
|
|
@@ -31,12 +31,12 @@ export declare function getNodeJsExternals({ cacheRoot }?: {
|
|
|
31
31
|
} | undefined): Promise<undefined>;
|
|
32
32
|
readdir(location: FsLocation): Promise<fs.Dirent<string>[]>;
|
|
33
33
|
readFile(location: FsLocation): Promise<NonSharedBuffer>;
|
|
34
|
+
rm(location: FsLocation, options: {
|
|
35
|
+
recursive?: boolean;
|
|
36
|
+
} | undefined): Promise<void>;
|
|
34
37
|
showFile(location: FsLocation): Promise<void>;
|
|
35
38
|
stat(location: FsLocation): Promise<fs.Stats>;
|
|
36
39
|
unlink(location: FsLocation): Promise<void>;
|
|
37
|
-
watch(locations: FsLocation[], { usePolling }?: {
|
|
38
|
-
usePolling?: boolean;
|
|
39
|
-
}): ChokidarWatcherWrapper;
|
|
40
40
|
writeFile(location: FsLocation, data: string | Uint8Array<ArrayBuffer>, options: {
|
|
41
41
|
mode: number;
|
|
42
42
|
} | undefined): Promise<void>;
|
|
@@ -72,12 +72,12 @@ export declare const NodeJsExternals: Readonly<{
|
|
|
72
72
|
} | undefined): Promise<undefined>;
|
|
73
73
|
readdir(location: FsLocation): Promise<fs.Dirent<string>[]>;
|
|
74
74
|
readFile(location: FsLocation): Promise<NonSharedBuffer>;
|
|
75
|
+
rm(location: FsLocation, options: {
|
|
76
|
+
recursive?: boolean;
|
|
77
|
+
} | undefined): Promise<void>;
|
|
75
78
|
showFile(location: FsLocation): Promise<void>;
|
|
76
79
|
stat(location: FsLocation): Promise<fs.Stats>;
|
|
77
80
|
unlink(location: FsLocation): Promise<void>;
|
|
78
|
-
watch(locations: FsLocation[], { usePolling }?: {
|
|
79
|
-
usePolling?: boolean;
|
|
80
|
-
}): ChokidarWatcherWrapper;
|
|
81
81
|
writeFile(location: FsLocation, data: string | Uint8Array<ArrayBuffer>, options: {
|
|
82
82
|
mode: number;
|
|
83
83
|
} | undefined): Promise<void>;
|
|
@@ -87,11 +87,6 @@ export declare const NodeJsExternals: Readonly<{
|
|
|
87
87
|
getCache: () => Promise<HttpCache>;
|
|
88
88
|
};
|
|
89
89
|
}>;
|
|
90
|
-
declare class ChokidarWatcherWrapper extends EventEmitter implements FsWatcher {
|
|
91
|
-
#private;
|
|
92
|
-
constructor(watcher: chokidar.FSWatcher);
|
|
93
|
-
close(): Promise<void>;
|
|
94
|
-
}
|
|
95
90
|
/**
|
|
96
91
|
* A non-spec-compliant, non-complete implementation of the Cache Web API for use in Spyglass.
|
|
97
92
|
* This class stores the cached response on the file system under the cache root.
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/60592
|
|
2
|
-
import chokidar from 'chokidar';
|
|
3
1
|
import decompress from 'decompress';
|
|
4
2
|
import { Buffer } from 'node:buffer';
|
|
5
3
|
import cp from 'node:child_process';
|
|
@@ -12,10 +10,9 @@ import stream from 'node:stream';
|
|
|
12
10
|
import url from 'node:url';
|
|
13
11
|
import { promisify } from 'node:util';
|
|
14
12
|
import zlib from 'node:zlib';
|
|
15
|
-
import { Uri } from '../util.js';
|
|
16
13
|
const gunzip = promisify(zlib.gunzip);
|
|
17
14
|
const gzip = promisify(zlib.gzip);
|
|
18
|
-
export function getNodeJsExternals({ cacheRoot } = {}) {
|
|
15
|
+
export function getNodeJsExternals({ cacheRoot, nodeFsp = fsp } = {}) {
|
|
19
16
|
return Object.freeze({
|
|
20
17
|
archive: {
|
|
21
18
|
decompressBall(buffer, options) {
|
|
@@ -51,19 +48,22 @@ export function getNodeJsExternals({ cacheRoot } = {}) {
|
|
|
51
48
|
event: { EventEmitter },
|
|
52
49
|
fs: {
|
|
53
50
|
chmod(location, mode) {
|
|
54
|
-
return
|
|
51
|
+
return nodeFsp.chmod(toFsPathLike(location), mode);
|
|
55
52
|
},
|
|
56
53
|
async mkdir(location, options) {
|
|
57
|
-
return void (await
|
|
54
|
+
return void (await nodeFsp.mkdir(toFsPathLike(location), options));
|
|
58
55
|
},
|
|
59
56
|
readdir(location) {
|
|
60
|
-
return
|
|
57
|
+
return nodeFsp.readdir(toFsPathLike(location), {
|
|
61
58
|
encoding: 'utf-8',
|
|
62
59
|
withFileTypes: true,
|
|
63
60
|
});
|
|
64
61
|
},
|
|
65
62
|
readFile(location) {
|
|
66
|
-
return
|
|
63
|
+
return nodeFsp.readFile(toFsPathLike(location));
|
|
64
|
+
},
|
|
65
|
+
rm(location, options) {
|
|
66
|
+
return nodeFsp.rm(toFsPathLike(location), options);
|
|
67
67
|
},
|
|
68
68
|
async showFile(location) {
|
|
69
69
|
const execFile = promisify(cp.execFile);
|
|
@@ -82,19 +82,13 @@ export function getNodeJsExternals({ cacheRoot } = {}) {
|
|
|
82
82
|
return void (await execFile(command, [toPath(location)]));
|
|
83
83
|
},
|
|
84
84
|
stat(location) {
|
|
85
|
-
return
|
|
85
|
+
return nodeFsp.stat(toFsPathLike(location));
|
|
86
86
|
},
|
|
87
87
|
unlink(location) {
|
|
88
|
-
return
|
|
89
|
-
},
|
|
90
|
-
watch(locations, { usePolling = false } = {}) {
|
|
91
|
-
return new ChokidarWatcherWrapper(chokidar.watch(locations.map(toPath), {
|
|
92
|
-
usePolling,
|
|
93
|
-
disableGlobbing: true,
|
|
94
|
-
}));
|
|
88
|
+
return nodeFsp.unlink(toFsPathLike(location));
|
|
95
89
|
},
|
|
96
90
|
writeFile(location, data, options) {
|
|
97
|
-
return
|
|
91
|
+
return nodeFsp.writeFile(toFsPathLike(location), data, options);
|
|
98
92
|
},
|
|
99
93
|
},
|
|
100
94
|
web: {
|
|
@@ -110,11 +104,6 @@ export const NodeJsExternals = getNodeJsExternals();
|
|
|
110
104
|
* @returns A {@link fs.PathLike}.
|
|
111
105
|
*/
|
|
112
106
|
function toFsPathLike(path) {
|
|
113
|
-
if (path instanceof Uri) {
|
|
114
|
-
// Convert WHATWG URL to string so that it will be converted
|
|
115
|
-
// to Node.js URL by the next if-block.
|
|
116
|
-
path = path.toString();
|
|
117
|
-
}
|
|
118
107
|
if (typeof path === 'string' && path.startsWith('file:')) {
|
|
119
108
|
return new url.URL(path);
|
|
120
109
|
}
|
|
@@ -126,23 +115,7 @@ function toPath(path) {
|
|
|
126
115
|
}
|
|
127
116
|
return uriToPath(path);
|
|
128
117
|
}
|
|
129
|
-
const uriToPath = (uri) => url.fileURLToPath(uri
|
|
130
|
-
const uriFromPath = (path) => url.pathToFileURL(path).toString();
|
|
131
|
-
class ChokidarWatcherWrapper extends EventEmitter {
|
|
132
|
-
#watcher;
|
|
133
|
-
constructor(watcher) {
|
|
134
|
-
super();
|
|
135
|
-
this.#watcher = watcher
|
|
136
|
-
.on('ready', () => this.emit('ready'))
|
|
137
|
-
.on('add', (path) => this.emit('add', uriFromPath(path)))
|
|
138
|
-
.on('change', (path) => this.emit('change', uriFromPath(path)))
|
|
139
|
-
.on('unlink', (path) => this.emit('unlink', uriFromPath(path)))
|
|
140
|
-
.on('error', (e) => this.emit('error', e));
|
|
141
|
-
}
|
|
142
|
-
close() {
|
|
143
|
-
return this.#watcher.close();
|
|
144
|
-
}
|
|
145
|
-
}
|
|
118
|
+
const uriToPath = (uri) => url.fileURLToPath(uri);
|
|
146
119
|
/**
|
|
147
120
|
* A non-spec-compliant, non-complete implementation of the Cache Web API for use in Spyglass.
|
|
148
121
|
* This class stores the cached response on the file system under the cache root.
|
|
@@ -57,27 +57,22 @@ export interface ExternalFileSystem {
|
|
|
57
57
|
mode?: number;
|
|
58
58
|
recursive?: boolean;
|
|
59
59
|
}): Promise<void>;
|
|
60
|
-
readdir(location: FsLocation): Promise<
|
|
61
|
-
name: string;
|
|
62
|
-
isDirectory(): boolean;
|
|
63
|
-
isFile(): boolean;
|
|
64
|
-
isSymbolicLink(): boolean;
|
|
65
|
-
}[]>;
|
|
60
|
+
readdir(location: FsLocation): Promise<ExternalDirEntry[]>;
|
|
66
61
|
readFile(location: FsLocation): Promise<Uint8Array<ArrayBuffer>>;
|
|
62
|
+
rm(location: FsLocation, options?: {
|
|
63
|
+
recursive?: boolean;
|
|
64
|
+
}): Promise<void>;
|
|
67
65
|
/**
|
|
68
66
|
* Show the file/directory in the platform-specific explorer program.
|
|
69
67
|
*
|
|
70
68
|
* Should not be called with unsanitized user-input path due to the possibility of arbitrary code execution.
|
|
71
69
|
*/
|
|
72
70
|
showFile(path: FsLocation): Promise<void>;
|
|
73
|
-
stat(location: FsLocation): Promise<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
stat(location: FsLocation): Promise<ExternalStats>;
|
|
72
|
+
/**
|
|
73
|
+
* @deprecated Use `rm` instead
|
|
74
|
+
*/
|
|
77
75
|
unlink(location: FsLocation): Promise<void>;
|
|
78
|
-
watch(locations: FsLocation[], options: {
|
|
79
|
-
usePolling?: boolean;
|
|
80
|
-
}): FsWatcher;
|
|
81
76
|
/**
|
|
82
77
|
* @param options `mode` - File mode bit mask (e.g. `0o775`).
|
|
83
78
|
*/
|
|
@@ -85,21 +80,17 @@ export interface ExternalFileSystem {
|
|
|
85
80
|
mode: number;
|
|
86
81
|
}): Promise<void>;
|
|
87
82
|
}
|
|
83
|
+
export interface ExternalStats {
|
|
84
|
+
isDirectory(): boolean;
|
|
85
|
+
isFile(): boolean;
|
|
86
|
+
isSymbolicLink(): boolean;
|
|
87
|
+
}
|
|
88
|
+
interface ExternalDirEntry extends ExternalStats {
|
|
89
|
+
name: string;
|
|
90
|
+
}
|
|
88
91
|
/**
|
|
89
92
|
* A file URI string or a URI object.
|
|
90
93
|
*/
|
|
91
94
|
export type FsLocation = string | Uri;
|
|
92
|
-
export
|
|
93
|
-
on(eventName: 'ready', listener: () => unknown): this;
|
|
94
|
-
once(eventName: 'ready', listener: () => unknown): this;
|
|
95
|
-
on(eventName: 'add', listener: (uri: string) => unknown): this;
|
|
96
|
-
once(eventName: 'add', listener: (uri: string) => unknown): this;
|
|
97
|
-
on(eventName: 'change', listener: (uri: string) => unknown): this;
|
|
98
|
-
once(eventName: 'change', listener: (uri: string) => unknown): this;
|
|
99
|
-
on(eventName: 'unlink', listener: (uri: string) => unknown): this;
|
|
100
|
-
once(eventName: 'unlink', listener: (uri: string) => unknown): this;
|
|
101
|
-
on(eventName: 'error', listener: (error: Error) => unknown): this;
|
|
102
|
-
once(eventName: 'error', listener: (error: Error) => unknown): this;
|
|
103
|
-
close(): Promise<void>;
|
|
104
|
-
}
|
|
95
|
+
export {};
|
|
105
96
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/common/index.d.ts
CHANGED
package/lib/common/index.js
CHANGED
package/lib/common/util.d.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import externalBinarySearch from 'binary-search';
|
|
2
|
-
import { URL } from 'whatwg-url';
|
|
3
2
|
import type { AstNode } from '../node/index.js';
|
|
4
3
|
import type { ProcessorContext } from '../service/index.js';
|
|
5
4
|
import type { Externals } from './externals/index.js';
|
|
6
5
|
import type { DeepReadonly, ReadWrite } from './ReadonlyProxy.js';
|
|
7
|
-
export declare const Uri:
|
|
6
|
+
export declare const Uri: {
|
|
7
|
+
new (url: string | URL, base?: string | URL): URL;
|
|
8
|
+
prototype: URL;
|
|
9
|
+
canParse(url: string | URL, base?: string | URL): boolean;
|
|
10
|
+
createObjectURL(obj: Blob | MediaSource): string;
|
|
11
|
+
parse(url: string | URL, base?: string | URL): URL | null;
|
|
12
|
+
revokeObjectURL(url: string): void;
|
|
13
|
+
};
|
|
8
14
|
export type Uri = URL;
|
|
9
15
|
/**
|
|
10
16
|
* `NodeJS.Timeout` on Node.js and `number` on browser.
|
|
11
17
|
*/
|
|
12
18
|
export type IntervalId = any;
|
|
19
|
+
export type DeepPartial<T> = T extends object ? {
|
|
20
|
+
[P in keyof T]?: DeepPartial<T[P]>;
|
|
21
|
+
} : T;
|
|
13
22
|
/**
|
|
14
23
|
* @param getKey A function that takes the actual arguments being passed into the decorated method, and returns anything.
|
|
15
24
|
* The result of this function will be used as the key to identify the `Promise`. By default the first element in the argument
|
|
@@ -110,6 +119,7 @@ export declare function emplaceMap<K, V>(map: Map<K, V>, key: K, handler: {
|
|
|
110
119
|
* @returns If `val` is an non-null object or a callable object (i.e. function).
|
|
111
120
|
*/
|
|
112
121
|
export declare function isObject(val: unknown): val is object;
|
|
122
|
+
export declare function normalizeUriPathname(pathname: string): string;
|
|
113
123
|
export declare function normalizeUri(uri: string): string;
|
|
114
124
|
/**
|
|
115
125
|
* Return a read-write TARGET type if the INPUT type is read-write, and a
|
package/lib/common/util.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import externalBinarySearch from 'binary-search';
|
|
2
2
|
import rfdc from 'rfdc';
|
|
3
|
-
import { URL } from 'whatwg-url';
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// URLs with hosts properly.
|
|
3
|
+
import { URL as WhatwgURL } from 'whatwg-url';
|
|
4
|
+
// We try to use the `URL` class built-in to the JavaScript runtime if possible, but falls back to
|
|
5
|
+
// use the `URL` class from the `whatwg-url` package if a certain bug exists.
|
|
6
|
+
// See more at https://github.com/SpyglassMC/Spyglass/issues/1763.
|
|
8
7
|
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
// The name "URI" instead of "URL" is used to align with LSP terminology.
|
|
9
|
+
export const Uri = isBuiltInURLGood() ? URL : WhatwgURL;
|
|
10
|
+
function isBuiltInURLGood() {
|
|
11
|
+
try {
|
|
12
|
+
return new URL('archive://mcdoc.tar.gz/foo.mcdoc').host === 'mcdoc.tar.gz';
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
15
18
|
/**
|
|
16
19
|
* @param getKey A function that takes the actual arguments being passed into the decorated method, and returns anything.
|
|
17
20
|
* The result of this function will be used as the key to identify the `Promise`. By default the first element in the argument
|
|
@@ -243,9 +246,25 @@ export function emplaceMap(map, key, handler) {
|
|
|
243
246
|
export function isObject(val) {
|
|
244
247
|
return typeof val === 'function' || (!!val && typeof val === 'object');
|
|
245
248
|
}
|
|
249
|
+
export function normalizeUriPathname(pathname) {
|
|
250
|
+
// Normalize drive letters on Windows to use lowercase letters, and ensure all colons are not encoded.
|
|
251
|
+
// See also LSP spec text, quoted below.
|
|
252
|
+
//
|
|
253
|
+
// > Care should be taken to handle encoding in URIs. For example, some clients (such as VS Code) may
|
|
254
|
+
// > encode colons in drive letters while others do not. The URIs below are both valid, but clients and
|
|
255
|
+
// > servers should be consistent with the form they use themselves to ensure the other party doesn’t
|
|
256
|
+
// > interpret them as distinct URIs. Clients and servers should not assume that each other are encoding
|
|
257
|
+
// > the same way (for example a client encoding colons in drive letters cannot assume server responses
|
|
258
|
+
// > will have encoded colons). The same applies to casing of drive letters - one party should not assume
|
|
259
|
+
// > the other party will return paths with drive letters cased the same as itself.
|
|
260
|
+
// > -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#uri
|
|
261
|
+
return pathname
|
|
262
|
+
.replace(/%3A/gi, ':')
|
|
263
|
+
.replace(/^\/[A-Z]:\//, (match) => match.toLowerCase());
|
|
264
|
+
}
|
|
246
265
|
export function normalizeUri(uri) {
|
|
247
266
|
const obj = new Uri(uri);
|
|
248
|
-
obj.pathname = obj.pathname
|
|
267
|
+
obj.pathname = normalizeUriPathname(obj.pathname);
|
|
249
268
|
return obj.toString();
|
|
250
269
|
}
|
|
251
270
|
//# sourceMappingURL=util.js.map
|