wgsl-analyzer-web 0.12.0-next.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/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +24 -0
- package/README.md +83 -0
- package/dist/assets/wgsl_analyzer.js +2 -0
- package/dist/assets/wgsl_analyzer.wasm +0 -0
- package/dist/assets/worker.js +91 -0
- package/dist/emscripten.d.ts +23 -0
- package/dist/emscripten.d.ts.map +1 -0
- package/dist/emscripten.js +5 -0
- package/dist/emscripten.js.map +1 -0
- package/dist/fs.d.ts +26 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +21 -0
- package/dist/fs.js.map +1 -0
- package/dist/host.d.ts +32 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +53 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +125 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +41 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +6 -0
- package/dist/protocol.js.map +1 -0
- package/dist/worker.d.ts +10 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +53 -0
- package/dist/worker.js.map +1 -0
- package/package.json +59 -0
- package/src/emscripten.ts +28 -0
- package/src/fs.ts +39 -0
- package/src/host.ts +82 -0
- package/src/index.ts +166 -0
- package/src/protocol.ts +27 -0
- package/src/wgsl_analyzer.d.ts +12 -0
- package/src/worker.ts +60 -0
package/dist/fs.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeding the emscripten in-memory filesystem.
|
|
3
|
+
*
|
|
4
|
+
* This runs once the module factory has resolved, and before `callMain`,
|
|
5
|
+
* so the workspace is already on disk by the time the server's VFS scans it.
|
|
6
|
+
*/
|
|
7
|
+
/** Writes one file, creating its parent directories first. */
|
|
8
|
+
export function writeFile(fs, path, contents) {
|
|
9
|
+
const slash = path.lastIndexOf("/");
|
|
10
|
+
if (slash > 0)
|
|
11
|
+
fs.mkdirTree(path.slice(0, slash));
|
|
12
|
+
fs.writeFile(path, contents);
|
|
13
|
+
}
|
|
14
|
+
/** Populates `root` with `files`, whose keys are paths relative to it. */
|
|
15
|
+
export function seedWorkspace(fs, root, files) {
|
|
16
|
+
fs.mkdirTree(root);
|
|
17
|
+
for (const [relative, contents] of Object.entries(files)) {
|
|
18
|
+
writeFile(fs, `${root}/${relative.replace(/^\/+/, "")}`, contents);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=fs.js.map
|
package/dist/fs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH,8DAA8D;AAC9D,MAAM,UAAU,SAAS,CAAC,EAAgB,EAAE,IAAY,EAAE,QAA6B;IACtF,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,KAAK,GAAG,CAAC;QAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAClD,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,aAAa,CAC5B,EAAgB,EAChB,IAAY,EACZ,KAA0C;IAE1C,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACnB,KAAK,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1D,SAAS,CAAC,EAAE,EAAE,GAAG,IAAI,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IACpE,CAAC;AACF,CAAC"}
|
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosts the emscripten module: boots it, seeds the workspace, and carries the
|
|
3
|
+
* message stream.
|
|
4
|
+
*/
|
|
5
|
+
import type { ModuleFactory } from "./emscripten.js";
|
|
6
|
+
import type { WorkspaceFiles } from "./protocol.js";
|
|
7
|
+
export interface HostOptions {
|
|
8
|
+
/** Absolute path of the workspace inside the in-memory filesystem. */
|
|
9
|
+
readonly root: string;
|
|
10
|
+
/** Files to seed, keyed by path relative to {@link HostOptions.root}. */
|
|
11
|
+
readonly files: WorkspaceFiles;
|
|
12
|
+
/** argv for `main()`. Empty selects the default `lsp-server` subcommand. */
|
|
13
|
+
readonly args: readonly string[];
|
|
14
|
+
/** Receives each parsed JSON-RPC message from the server. */
|
|
15
|
+
readonly onMessage: (message: unknown) => void;
|
|
16
|
+
/** Receives the server's stderr, line by line. */
|
|
17
|
+
readonly onStderr: (line: string) => void;
|
|
18
|
+
/** Called if `main()` returns. */
|
|
19
|
+
readonly onExit: (code: number) => void;
|
|
20
|
+
}
|
|
21
|
+
/** A running server. Once it has exited, every method does nothing. */
|
|
22
|
+
export interface Host {
|
|
23
|
+
/** Sends one JSON-RPC message to the server. */
|
|
24
|
+
send(message: unknown): void;
|
|
25
|
+
/** Creates or replaces a workspace file, given its path relative to the root. */
|
|
26
|
+
writeFile(relativePath: string, contents: string | Uint8Array): void;
|
|
27
|
+
/** Removes a workspace file, if present. */
|
|
28
|
+
deleteFile(relativePath: string): void;
|
|
29
|
+
}
|
|
30
|
+
/** Boots the server and resolves once `main()` has been started. */
|
|
31
|
+
export declare function startHost(factory: ModuleFactory, options: HostOptions): Promise<Host>;
|
|
32
|
+
//# sourceMappingURL=host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,MAAM,WAAW,WAAW;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/C,kDAAkD;IAClD,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,uEAAuE;AACvE,MAAM,WAAW,IAAI;IACpB,gDAAgD;IAChD,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,iFAAiF;IACjF,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACrE,4CAA4C;IAC5C,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED,oEAAoE;AACpE,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA8C3F"}
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosts the emscripten module: boots it, seeds the workspace, and carries the
|
|
3
|
+
* message stream.
|
|
4
|
+
*/
|
|
5
|
+
import { seedWorkspace, writeFile } from "./fs.js";
|
|
6
|
+
/** Boots the server and resolves once `main()` has been started. */
|
|
7
|
+
export async function startHost(factory, options) {
|
|
8
|
+
const { root } = options;
|
|
9
|
+
const resolve = (relativePath) => `${root}/${relativePath.replace(/^\/+/, "")}`;
|
|
10
|
+
// Messages for the server, which pulls them one at a time. It never closes.
|
|
11
|
+
const inbox = new TransformStream();
|
|
12
|
+
const reader = inbox.readable.getReader();
|
|
13
|
+
const writer = inbox.writable.getWriter();
|
|
14
|
+
// With `-sEXIT_RUNTIME`, calling into the module after `main()` returns is an
|
|
15
|
+
// error, and the page can still send messages after that.
|
|
16
|
+
let exited = false;
|
|
17
|
+
const module = await factory({
|
|
18
|
+
noInitialRun: true,
|
|
19
|
+
printErr: options.onStderr,
|
|
20
|
+
onExit: (code) => {
|
|
21
|
+
exited = true;
|
|
22
|
+
// Messages are delivered from microtasks too, so those sent before exiting come first.
|
|
23
|
+
queueMicrotask(() => options.onExit(code));
|
|
24
|
+
},
|
|
25
|
+
lspNextMessage: () => reader.read().then(({ value }) => value),
|
|
26
|
+
lspOnMessage: (body) => options.onMessage(JSON.parse(body)),
|
|
27
|
+
});
|
|
28
|
+
// Needs to run after WasmFS was iniialized.
|
|
29
|
+
seedWorkspace(module.FS, root, options.files);
|
|
30
|
+
module.FS.chdir(root);
|
|
31
|
+
// WasmFS's `chdir` does not go through `FS.handleError`, so a failure is
|
|
32
|
+
// otherwise silent.
|
|
33
|
+
if (module.FS.cwd() !== root)
|
|
34
|
+
throw new Error(`cannot change directory to ${root}`);
|
|
35
|
+
// Returns immediately: -sPROXY_TO_PTHREAD runs main() on a pthread.
|
|
36
|
+
module.callMain(options.args);
|
|
37
|
+
return {
|
|
38
|
+
send: (message) => {
|
|
39
|
+
if (!exited)
|
|
40
|
+
void writer.write(JSON.stringify(message));
|
|
41
|
+
},
|
|
42
|
+
writeFile: (relativePath, contents) => {
|
|
43
|
+
if (!exited)
|
|
44
|
+
writeFile(module.FS, resolve(relativePath), contents);
|
|
45
|
+
},
|
|
46
|
+
deleteFile: (relativePath) => {
|
|
47
|
+
const path = resolve(relativePath);
|
|
48
|
+
if (!exited && module.FS.analyzePath(path).exists)
|
|
49
|
+
module.FS.unlink(path);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=host.js.map
|
package/dist/host.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AA4BnD,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAsB,EAAE,OAAoB;IAC3E,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IACzB,MAAM,OAAO,GAAG,CAAC,YAAoB,EAAU,EAAE,CAAC,GAAG,IAAI,IAAI,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;IAEhG,4EAA4E;IAC5E,MAAM,KAAK,GAAG,IAAI,eAAe,EAAkB,CAAC;IACpD,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;IAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;IAE1C,8EAA8E;IAC9E,0DAA0D;IAC1D,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;QAC5B,YAAY,EAAE,IAAI;QAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAChB,MAAM,GAAG,IAAI,CAAC;YACd,uFAAuF;YACvF,cAAc,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,cAAc,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAe,CAAC;QACxE,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;KAC3D,CAAC,CAAC;IAEH,4CAA4C;IAC5C,aAAa,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACtB,yEAAyE;IACzE,oBAAoB;IACpB,IAAI,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,EAAE,CAAC,CAAC;IAEpF,oEAAoE;IACpE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9B,OAAO;QACN,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE;YACjB,IAAI,CAAC,MAAM;gBAAE,KAAK,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,SAAS,EAAE,CAAC,YAAY,EAAE,QAAQ,EAAE,EAAE;YACrC,IAAI,CAAC,MAAM;gBAAE,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,CAAC;QACpE,CAAC;QACD,UAAU,EAAE,CAAC,YAAY,EAAE,EAAE;YAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;YACnC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,MAAM;gBAAE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3E,CAAC;KACD,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wgsl-analyzer as a language server in a Web Worker.
|
|
3
|
+
*
|
|
4
|
+
* The server is the `wgsl-analyzer` binary compiled to
|
|
5
|
+
* `wasm32-unknown-emscripten`, running its ordinary `main_loop` over a transport.
|
|
6
|
+
* This module hosts it and exposes the message stream.
|
|
7
|
+
*
|
|
8
|
+
* The page must be cross-origin isolated. The build uses shared memory, so
|
|
9
|
+
* without `Cross-Origin-Opener-Policy: same-origin` and
|
|
10
|
+
* `Cross-Origin-Embedder-Policy: require-corp` nothing will start.
|
|
11
|
+
*/
|
|
12
|
+
import type { WorkspaceFiles } from "./protocol.js";
|
|
13
|
+
export type { WorkspaceFiles } from "./protocol.js";
|
|
14
|
+
export interface StartOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Directory serving `worker.js`, `wgsl_analyzer.js` and `wgsl_analyzer.wasm`.
|
|
17
|
+
* They must sit side by side. Defaults to `"/wgsl-analyzer/"`.
|
|
18
|
+
*/
|
|
19
|
+
readonly baseUrl?: string | URL;
|
|
20
|
+
/** Absolute path of the workspace in the in-memory filesystem. Defaults to `"/workspace"`. */
|
|
21
|
+
readonly root?: string;
|
|
22
|
+
/** Files to seed, keyed by path relative to {@link StartOptions.root}. */
|
|
23
|
+
readonly files: WorkspaceFiles;
|
|
24
|
+
/** argv for `main()`. Empty selects the default `lsp-server` subcommand. */
|
|
25
|
+
readonly args?: readonly string[];
|
|
26
|
+
/** Receives the server's stderr, line by line. */
|
|
27
|
+
readonly onStderr?: (line: string) => void;
|
|
28
|
+
/** Called if `main()` returns. */
|
|
29
|
+
readonly onExit?: (code: number) => void;
|
|
30
|
+
}
|
|
31
|
+
export interface Disposable {
|
|
32
|
+
dispose(): void;
|
|
33
|
+
}
|
|
34
|
+
/** A running wgsl-analyzer language server. */
|
|
35
|
+
export declare class WgslAnalyzerServer {
|
|
36
|
+
#private;
|
|
37
|
+
private constructor();
|
|
38
|
+
/** The workspace root as a URI, suitable for `initialize`. */
|
|
39
|
+
get rootUri(): string;
|
|
40
|
+
/** Builds the URI of a workspace file, given its path relative to the root. */
|
|
41
|
+
uriOf(relativePath: string): string;
|
|
42
|
+
/** Boots the server and resolves once it is ready to accept messages. */
|
|
43
|
+
static start(options: StartOptions): Promise<WgslAnalyzerServer>;
|
|
44
|
+
/** Sends one JSON-RPC message to the server. */
|
|
45
|
+
sendMessage(message: unknown): void;
|
|
46
|
+
/** Subscribes to messages coming back from the server. */
|
|
47
|
+
onMessage(listener: (message: unknown) => void): Disposable;
|
|
48
|
+
/**
|
|
49
|
+
* Creates or replaces a file in the in-memory filesystem.
|
|
50
|
+
*
|
|
51
|
+
* The server does not watch the filesystem, so follow this with a
|
|
52
|
+
* `workspace/didChangeWatchedFiles` notification for the file. Edits to an
|
|
53
|
+
* open document go through `textDocument/didChange` instead.
|
|
54
|
+
*/
|
|
55
|
+
writeFile(relativePath: string, contents: string | Uint8Array): void;
|
|
56
|
+
/** Removes a file from the in-memory filesystem. See {@link WgslAnalyzerServer.writeFile}. */
|
|
57
|
+
deleteFile(relativePath: string): void;
|
|
58
|
+
/** Stops the server and tears down the worker. */
|
|
59
|
+
dispose(): void;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAA8B,cAAc,EAAE,MAAM,eAAe,CAAC;AAEhF,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC5B;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IAChC,8FAA8F;IAC9F,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,kDAAkD;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,kCAAkC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,UAAU;IAC1B,OAAO,IAAI,IAAI,CAAC;CAChB;AAED,+CAA+C;AAC/C,qBAAa,kBAAkB;;IAM9B,OAAO,eAGN;IAED,8DAA8D;IAC9D,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,+EAA+E;IAC/E,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElC;IAED,yEAAyE;IACzE,OAAa,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAuDrE;IAED,gDAAgD;IAChD,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAIlC;IAED,0DAA0D;IAC1D,SAAS,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,UAAU,CAO1D;IAED;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAGnE;IAED,8FAA8F;IAC9F,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAGrC;IAED,kDAAkD;IAClD,OAAO,IAAI,IAAI,CAUd;CACD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wgsl-analyzer as a language server in a Web Worker.
|
|
3
|
+
*
|
|
4
|
+
* The server is the `wgsl-analyzer` binary compiled to
|
|
5
|
+
* `wasm32-unknown-emscripten`, running its ordinary `main_loop` over a transport.
|
|
6
|
+
* This module hosts it and exposes the message stream.
|
|
7
|
+
*
|
|
8
|
+
* The page must be cross-origin isolated. The build uses shared memory, so
|
|
9
|
+
* without `Cross-Origin-Opener-Policy: same-origin` and
|
|
10
|
+
* `Cross-Origin-Embedder-Policy: require-corp` nothing will start.
|
|
11
|
+
*/
|
|
12
|
+
/** A running wgsl-analyzer language server. */
|
|
13
|
+
export class WgslAnalyzerServer {
|
|
14
|
+
#worker;
|
|
15
|
+
#listeners = new Set();
|
|
16
|
+
#root;
|
|
17
|
+
#disposed = false;
|
|
18
|
+
constructor(worker, root) {
|
|
19
|
+
this.#worker = worker;
|
|
20
|
+
this.#root = root;
|
|
21
|
+
}
|
|
22
|
+
/** The workspace root as a URI, suitable for `initialize`. */
|
|
23
|
+
get rootUri() {
|
|
24
|
+
return `file://${this.#root}`;
|
|
25
|
+
}
|
|
26
|
+
/** Builds the URI of a workspace file, given its path relative to the root. */
|
|
27
|
+
uriOf(relativePath) {
|
|
28
|
+
return `file://${this.#root}/${relativePath.replace(/^\/+/, "")}`;
|
|
29
|
+
}
|
|
30
|
+
/** Boots the server and resolves once it is ready to accept messages. */
|
|
31
|
+
static async start(options) {
|
|
32
|
+
const base = new URL(String(options.baseUrl ?? "/wgsl-analyzer/"), globalThis.location.href);
|
|
33
|
+
const root = options.root ?? "/workspace";
|
|
34
|
+
const worker = new Worker(new URL("worker.js", base), { type: "module" });
|
|
35
|
+
const server = new WgslAnalyzerServer(worker, root);
|
|
36
|
+
await new Promise((resolve, reject) => {
|
|
37
|
+
const onFirstMessage = (event) => {
|
|
38
|
+
const message = event.data;
|
|
39
|
+
if (message.type === "ready") {
|
|
40
|
+
worker.removeEventListener("message", onFirstMessage);
|
|
41
|
+
resolve();
|
|
42
|
+
}
|
|
43
|
+
else if (message.type === "error") {
|
|
44
|
+
worker.removeEventListener("message", onFirstMessage);
|
|
45
|
+
reject(new Error(`${message.message}\n${message.stack ?? ""}`));
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
worker.addEventListener("message", onFirstMessage);
|
|
49
|
+
worker.addEventListener("error", (event) => reject(new Error(`worker failed to load: ${event.message}`)), { once: true });
|
|
50
|
+
const boot = {
|
|
51
|
+
type: "boot",
|
|
52
|
+
root,
|
|
53
|
+
files: options.files,
|
|
54
|
+
args: options.args ?? [],
|
|
55
|
+
};
|
|
56
|
+
worker.postMessage(boot);
|
|
57
|
+
});
|
|
58
|
+
worker.addEventListener("message", (event) => {
|
|
59
|
+
const message = event.data;
|
|
60
|
+
switch (message.type) {
|
|
61
|
+
case "lsp":
|
|
62
|
+
for (const listener of server.#listeners)
|
|
63
|
+
listener(message.message);
|
|
64
|
+
break;
|
|
65
|
+
case "stderr":
|
|
66
|
+
options.onStderr?.(message.line);
|
|
67
|
+
break;
|
|
68
|
+
case "exit":
|
|
69
|
+
options.onExit?.(message.code);
|
|
70
|
+
break;
|
|
71
|
+
case "error":
|
|
72
|
+
options.onStderr?.(`[worker] ${message.message}`);
|
|
73
|
+
break;
|
|
74
|
+
default:
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return server;
|
|
79
|
+
}
|
|
80
|
+
/** Sends one JSON-RPC message to the server. */
|
|
81
|
+
sendMessage(message) {
|
|
82
|
+
if (this.#disposed)
|
|
83
|
+
throw new Error("server has been disposed");
|
|
84
|
+
const envelope = { type: "lsp", message };
|
|
85
|
+
this.#worker.postMessage(envelope);
|
|
86
|
+
}
|
|
87
|
+
/** Subscribes to messages coming back from the server. */
|
|
88
|
+
onMessage(listener) {
|
|
89
|
+
this.#listeners.add(listener);
|
|
90
|
+
return {
|
|
91
|
+
dispose: () => {
|
|
92
|
+
this.#listeners.delete(listener);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Creates or replaces a file in the in-memory filesystem.
|
|
98
|
+
*
|
|
99
|
+
* The server does not watch the filesystem, so follow this with a
|
|
100
|
+
* `workspace/didChangeWatchedFiles` notification for the file. Edits to an
|
|
101
|
+
* open document go through `textDocument/didChange` instead.
|
|
102
|
+
*/
|
|
103
|
+
writeFile(relativePath, contents) {
|
|
104
|
+
const message = { type: "writeFile", path: relativePath, contents };
|
|
105
|
+
this.#worker.postMessage(message);
|
|
106
|
+
}
|
|
107
|
+
/** Removes a file from the in-memory filesystem. See {@link WgslAnalyzerServer.writeFile}. */
|
|
108
|
+
deleteFile(relativePath) {
|
|
109
|
+
const message = { type: "deleteFile", path: relativePath };
|
|
110
|
+
this.#worker.postMessage(message);
|
|
111
|
+
}
|
|
112
|
+
/** Stops the server and tears down the worker. */
|
|
113
|
+
dispose() {
|
|
114
|
+
if (this.#disposed)
|
|
115
|
+
return;
|
|
116
|
+
this.#disposed = true;
|
|
117
|
+
this.#listeners.clear();
|
|
118
|
+
// `exit` ends the server's main loop, if the server reads it before the
|
|
119
|
+
// worker is terminated on the next macrotask.
|
|
120
|
+
const exit = { type: "lsp", message: { jsonrpc: "2.0", method: "exit" } };
|
|
121
|
+
this.#worker.postMessage(exit);
|
|
122
|
+
setTimeout(() => this.#worker.terminate(), 0);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control messages exchanged between the page and the worker hosting the
|
|
3
|
+
* emscripten module.
|
|
4
|
+
*/
|
|
5
|
+
/** Contents of a workspace, keyed by path relative to its root in the in-memory filesystem. */
|
|
6
|
+
export type WorkspaceFiles = Record<string, string | Uint8Array>;
|
|
7
|
+
/** Page to worker. */
|
|
8
|
+
export type HostMessage = {
|
|
9
|
+
readonly type: "boot";
|
|
10
|
+
readonly root: string;
|
|
11
|
+
readonly files: WorkspaceFiles;
|
|
12
|
+
readonly args: readonly string[];
|
|
13
|
+
} | {
|
|
14
|
+
readonly type: "lsp";
|
|
15
|
+
readonly message: unknown;
|
|
16
|
+
} | {
|
|
17
|
+
readonly type: "writeFile";
|
|
18
|
+
readonly path: string;
|
|
19
|
+
readonly contents: string | Uint8Array;
|
|
20
|
+
} | {
|
|
21
|
+
readonly type: "deleteFile";
|
|
22
|
+
readonly path: string;
|
|
23
|
+
};
|
|
24
|
+
/** Worker to page. */
|
|
25
|
+
export type WorkerMessage = {
|
|
26
|
+
readonly type: "ready";
|
|
27
|
+
} | {
|
|
28
|
+
readonly type: "lsp";
|
|
29
|
+
readonly message: unknown;
|
|
30
|
+
} | {
|
|
31
|
+
readonly type: "stderr";
|
|
32
|
+
readonly line: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly type: "exit";
|
|
35
|
+
readonly code: number;
|
|
36
|
+
} | {
|
|
37
|
+
readonly type: "error";
|
|
38
|
+
readonly message: string;
|
|
39
|
+
readonly stack?: string | undefined;
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,+FAA+F;AAC/F,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC;AAEjE,sBAAsB;AACtB,MAAM,MAAM,WAAW,GACpB;IACA,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;CAAE,GAC7F;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,sBAAsB;AACtB,MAAM,MAAM,aAAa,GACtB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker entry point: relays between the page and the {@link Host}.
|
|
3
|
+
*
|
|
4
|
+
* This file is bundled to `dist/worker.js` and must be served next to
|
|
5
|
+
* `wgsl_analyzer.js`. The dynamic import below resolves relative to it, and the
|
|
6
|
+
* glue spawns its pthread pool with
|
|
7
|
+
* `new Worker(new URL("wgsl_analyzer.js", import.meta.url))`.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker entry point: relays between the page and the {@link Host}.
|
|
3
|
+
*
|
|
4
|
+
* This file is bundled to `dist/worker.js` and must be served next to
|
|
5
|
+
* `wgsl_analyzer.js`. The dynamic import below resolves relative to it, and the
|
|
6
|
+
* glue spawns its pthread pool with
|
|
7
|
+
* `new Worker(new URL("wgsl_analyzer.js", import.meta.url))`.
|
|
8
|
+
*/
|
|
9
|
+
import { startHost } from "./host.js";
|
|
10
|
+
const post = (message) => {
|
|
11
|
+
self.postMessage(message);
|
|
12
|
+
};
|
|
13
|
+
let host = null;
|
|
14
|
+
self.onmessage = (event) => {
|
|
15
|
+
const message = event.data;
|
|
16
|
+
switch (message.type) {
|
|
17
|
+
case "boot":
|
|
18
|
+
boot(message).catch((error) => {
|
|
19
|
+
post({
|
|
20
|
+
type: "error",
|
|
21
|
+
message: String(error),
|
|
22
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
break;
|
|
26
|
+
case "lsp":
|
|
27
|
+
host?.send(message.message);
|
|
28
|
+
break;
|
|
29
|
+
case "writeFile":
|
|
30
|
+
host?.writeFile(message.path, message.contents);
|
|
31
|
+
break;
|
|
32
|
+
case "deleteFile":
|
|
33
|
+
host?.deleteFile(message.path);
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
async function boot(message) {
|
|
40
|
+
// Left external by the bundler so it resolves next to dist/worker.js at
|
|
41
|
+
// runtime rather than being inlined.
|
|
42
|
+
const { default: createWgslAnalyzer } = await import("./wgsl_analyzer.js");
|
|
43
|
+
host = await startHost(createWgslAnalyzer, {
|
|
44
|
+
root: message.root,
|
|
45
|
+
files: message.files,
|
|
46
|
+
args: message.args,
|
|
47
|
+
onMessage: (lsp) => post({ type: "lsp", message: lsp }),
|
|
48
|
+
onStderr: (line) => post({ type: "stderr", line }),
|
|
49
|
+
onExit: (code) => post({ type: "exit", code }),
|
|
50
|
+
});
|
|
51
|
+
post({ type: "ready" });
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAa,SAAS,EAAE,MAAM,WAAW,CAAC;AAGjD,MAAM,IAAI,GAAG,CAAC,OAAsB,EAAQ,EAAE;IAC7C,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC,CAAC;AAEF,IAAI,IAAI,GAAgB,IAAI,CAAC;AAE7B,IAAI,CAAC,SAAS,GAAG,CAAC,KAAgC,EAAQ,EAAE;IAC3D,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;IAC3B,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,MAAM;YACV,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBACtC,IAAI,CAAC;oBACJ,IAAI,EAAE,OAAO;oBACb,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;oBACtB,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;iBACvD,CAAC,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,MAAM;QACP,KAAK,KAAK;YACT,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC5B,MAAM;QACP,KAAK,WAAW;YACf,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;YAChD,MAAM;QACP,KAAK,YAAY;YAChB,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC/B,MAAM;QACP;YACC,MAAM;IACR,CAAC;AACF,CAAC,CAAC;AAEF,KAAK,UAAU,IAAI,CAAC,OAA+C;IAClE,wEAAwE;IACxE,qCAAqC;IACrC,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAE3E,IAAI,GAAG,MAAM,SAAS,CAAC,kBAAkB,EAAE;QAC1C,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;QACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAClD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;KAC9C,CAAC,CAAC;IAEH,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AACzB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wgsl-analyzer-web",
|
|
3
|
+
"version": "0.12.0-next.0",
|
|
4
|
+
"description": "Run the wgsl-analyzer language server in a Web Worker",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/wgsl-analyzer/wgsl-analyzer"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"wgsl",
|
|
12
|
+
"wesl",
|
|
13
|
+
"language-server",
|
|
14
|
+
"lsp",
|
|
15
|
+
"wasm",
|
|
16
|
+
"web-worker"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json",
|
|
25
|
+
"./assets/*": "./dist/assets/*"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"src",
|
|
30
|
+
"LICENSE-APACHE",
|
|
31
|
+
"LICENSE-MIT"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"provenance": true
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@biomejs/biome": "^2.5.8",
|
|
39
|
+
"@tsconfig/strictest": "^2.0.8",
|
|
40
|
+
"@types/node": "^26.4.1",
|
|
41
|
+
"esbuild": "^0.28.2",
|
|
42
|
+
"typescript": "^7.0.2",
|
|
43
|
+
"vscode-jsonrpc": "^9.0.2"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "pnpm run build:lib && pnpm run build:worker",
|
|
47
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
48
|
+
"build:lib": "pnpm run clean && tsc",
|
|
49
|
+
"build:worker": "esbuild src/worker.ts --bundle --format=esm --target=es2024 --external:./wgsl_analyzer.js --outfile=dist/assets/worker.js",
|
|
50
|
+
"check": "pnpm exec biome check",
|
|
51
|
+
"ci": "pnpm exec biome ci && pnpm run typecheck",
|
|
52
|
+
"fix": "pnpm exec biome check --write",
|
|
53
|
+
"format": "pnpm exec biome format --write",
|
|
54
|
+
"lint": "pnpm exec biome lint",
|
|
55
|
+
"test": "node --test \"tests/unit/*.test.ts\"",
|
|
56
|
+
"test:smoke": "node --test --test-force-exit --test-concurrency=1 \"tests/smoke/*.test.ts\"",
|
|
57
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.node.json"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The emscripten module surface this package drives.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { EmscriptenFs } from "./fs.js";
|
|
6
|
+
|
|
7
|
+
/** The emscripten module members this package touches. */
|
|
8
|
+
export interface EmscriptenModule {
|
|
9
|
+
FS: EmscriptenFs;
|
|
10
|
+
// Functions provided by emscripten, see https://emscripten.org/docs/api_reference/index.html.
|
|
11
|
+
callMain(args: readonly string[]): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ModuleOptions {
|
|
15
|
+
noInitialRun: boolean;
|
|
16
|
+
printErr: (line: string) => void;
|
|
17
|
+
onExit: (code: number) => void;
|
|
18
|
+
// The transport, called by emscripten/library.js.
|
|
19
|
+
/**
|
|
20
|
+
* Resolves to the next message for the server. It may run inside another call into the
|
|
21
|
+
* module, so it must not call into the module itself, and it must never reject.
|
|
22
|
+
*/
|
|
23
|
+
lspNextMessage: () => Promise<string>;
|
|
24
|
+
/** Receives one message from the server, from a microtask. */
|
|
25
|
+
lspOnMessage: (body: string) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ModuleFactory = (options: ModuleOptions) => Promise<EmscriptenModule>;
|
package/src/fs.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeding the emscripten in-memory filesystem.
|
|
3
|
+
*
|
|
4
|
+
* This runs once the module factory has resolved, and before `callMain`,
|
|
5
|
+
* so the workspace is already on disk by the time the server's VFS scans it.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The subset of emscripten's `FS` module this package relies on.
|
|
10
|
+
*
|
|
11
|
+
* https://emscripten.org/docs/api_reference/Filesystem-API.html#new-file-system-wasmfs
|
|
12
|
+
*/
|
|
13
|
+
export interface EmscriptenFs {
|
|
14
|
+
mkdirTree(path: string): void;
|
|
15
|
+
writeFile(path: string, data: string | Uint8Array): void;
|
|
16
|
+
unlink(path: string): void;
|
|
17
|
+
chdir(path: string): void;
|
|
18
|
+
cwd(): string;
|
|
19
|
+
analyzePath(path: string): { exists: boolean };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Writes one file, creating its parent directories first. */
|
|
23
|
+
export function writeFile(fs: EmscriptenFs, path: string, contents: string | Uint8Array): void {
|
|
24
|
+
const slash = path.lastIndexOf("/");
|
|
25
|
+
if (slash > 0) fs.mkdirTree(path.slice(0, slash));
|
|
26
|
+
fs.writeFile(path, contents);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Populates `root` with `files`, whose keys are paths relative to it. */
|
|
30
|
+
export function seedWorkspace(
|
|
31
|
+
fs: EmscriptenFs,
|
|
32
|
+
root: string,
|
|
33
|
+
files: Record<string, string | Uint8Array>,
|
|
34
|
+
): void {
|
|
35
|
+
fs.mkdirTree(root);
|
|
36
|
+
for (const [relative, contents] of Object.entries(files)) {
|
|
37
|
+
writeFile(fs, `${root}/${relative.replace(/^\/+/, "")}`, contents);
|
|
38
|
+
}
|
|
39
|
+
}
|