Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Plugins are sandboxed by default - they cannot access the filesystem or network
Plugins like `ls` or `cat` can interact with the filesystem using the primitives of the languages they are written in.

- on the CLI, a folder from the disk is mounted via the `--dir` flag
- on the browser, a virtual filesystem is mounted, the I/O operations are forwarded via the `@bytecodealliance/preview2-shim/filesystem` shim, which shims the `wasi:filesystem` filesystem interface
- on the browser, a virtual filesystem is mounted, the I/O operations are forwarded via a [local fork](./packages/web-host/overrides/@bytecodealliance/preview2-shim) of `@bytecodealliance/preview2-shim/filesystem` shim, which shims the `wasi:filesystem` filesystem interface

<p align="center"><a href="https://topheman.github.io/webassembly-component-model-experiments/"><img src="./packages/web-host/public/wasi.png" alt="Demo" /></a></p>
<p align="center">
Expand Down Expand Up @@ -410,6 +410,13 @@ pluginlab\
- formating the rust code
- typechecking the TypeScript code

### Local fork of `@bytecodealliance/preview2-shim`

- The original [`@bytecodealliance/preview2-shim`](https://github.com/bytecodealliance/jco/tree/main/packages/preview2-shim) for the **browser** doesn't support **WRITE** operations on the filesystem
- A fork was created in [`packages/web-host/overrides/@bytecodealliance/preview2-shim`](./packages/web-host/overrides/@bytecodealliance/preview2-shim) to fix this issue

Everything is described in [PR#15 Support plugin-tee in the web host](https://github.com/topheman/webassembly-component-model-experiments/pull/15) (must read 😉).

## Resources

### Optional tools
Expand Down
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"!**/node_modules",
"!**/dist",
"!**/build",
"!**/package.json"
"!**/package.json",
"!packages/web-host/overrides/**"
]
},
"formatter": {
Expand Down
2 changes: 1 addition & 1 deletion lint-staged.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

export default {
'packages/**': 'biome check --write',
'packages/*/!(overrides)/**': 'biome check --write',
'packages/plugin-echo/**/*.ts': () => 'npm run typecheck --workspace=packages/plugin-echo',
'packages/web-host/**/*.{ts,tsx}': () => 'npm run typecheck --workspace=packages/web-host',
// `cargo fmt doesn't accept files
Expand Down
24 changes: 13 additions & 11 deletions packages/web-host/clis/prepareFilesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ function assertPathIsString(path: string): asserts path is string {
}
}

function template(data: VirtualFs): string {
return `// THIS FILE IS GENERATED BY THE prepareVirtualFs COMMAND, DO NOT EDIT IT MANUALLY

// It is meant to be used for mounting a virtual filesystem in the browser
// interacting with @bytecodealliance/preview2-shim/filesystem , the shim for wasi:filesystem
//
// The \`fs\` calls like \`read\`, \`readDir\` ... in rust or other languages will be redirected to this virtual filesystem
// and will interact as if the filesystem was a real one.

export function makeVirtualFs() { return ${JSON.stringify(data, null, 2)}; }`;
}

function run() {
program
.description("Prepare wasm files for the web host")
Expand All @@ -65,17 +77,7 @@ function run() {
if (options.format === "json") {
console.log(JSON.stringify(virtualFs, null, 2));
} else if (options.format === "ts") {
console.log(
`// THIS FILE IS GENERATED BY THE prepareVirtualFs COMMAND, DO NOT EDIT IT MANUALLY

// It is meant to be used for mounting a virtual filesystem in the browser
// interacting with @bytecodealliance/preview2-shim/filesystem , the shim for wasi:filesystem
//
// The \`fs\` calls like \`read\`, \`readDir\` ... in rust or other languages will be redirected to this virtual filesystem
// and will interact as if the filesystem was a real one.

export function makeVirtualFs() { return ${JSON.stringify(virtualFs, null, 2)}; }`,
);
console.log(template(virtualFs));
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Preview2 Shim

WASI Preview2 implementations for Node.js & browsers.

Node.js support is fully tested and conformant against the Wasmtime test suite.

Browser support is considered experimental, and not currently suitable for production applications.

# License

This project is licensed under the Apache 2.0 license with the LLVM exception.
See [LICENSE](LICENSE) for more details.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this project by you, as defined in the Apache-2.0 license,
shall be licensed as above, without any additional terms or conditions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { _setCwd as fsSetCwd } from './filesystem.js';
import { streams } from './io.js';
const { InputStream, OutputStream } = streams;

const symbolDispose = Symbol.dispose ?? Symbol.for('dispose');

let _env = [], _args = [], _cwd = "/";
export function _setEnv (envObj) {
_env = Object.entries(envObj);
}
export function _setArgs (args) {
_args = args;
}

export function _setCwd (cwd) {
fsSetCwd(_cwd = cwd);
}

export const environment = {
getEnvironment () {
return _env;
},
getArguments () {
return _args;
},
initialCwd () {
return _cwd;
}
};

class ComponentExit extends Error {
constructor(code) {
super(`Component exited ${code === 0 ? 'successfully' : 'with error'}`);
this.exitError = true;
this.code = code;
}
}

export const exit = {
exit (status) {
throw new ComponentExit(status.tag === 'err' ? 1 : 0);
},
exitWithCode (code) {
throw new ComponentExit(code);
}
};

/**
* @param {import('../common/io.js').InputStreamHandler} handler
*/
export function _setStdin (handler) {
stdinStream.handler = handler;
}
/**
* @param {import('../common/io.js').OutputStreamHandler} handler
*/
export function _setStderr (handler) {
stderrStream.handler = handler;
}
/**
* @param {import('../common/io.js').OutputStreamHandler} handler
*/
export function _setStdout (handler) {
stdoutStream.handler = handler;
}

const stdinStream = new InputStream({
blockingRead (_len) {
// TODO
},
subscribe () {
// TODO
},
[symbolDispose] () {
// TODO
}
});
let textDecoder = new TextDecoder();
const stdoutStream = new OutputStream({
write (contents) {
if (contents[contents.length - 1] == 10) {
// console.log already appends a new line
contents = contents.subarray(0, contents.length - 1);
}
console.log(textDecoder.decode(contents));
},
blockingFlush () {
},
[symbolDispose] () {
}
});
const stderrStream = new OutputStream({
write (contents) {
if (contents[contents.length - 1] == 10) {
// console.error already appends a new line
contents = contents.subarray(0, contents.length - 1);
}
console.error(textDecoder.decode(contents));
},
blockingFlush () {
},
[symbolDispose] () {

}
});

export const stdin = {
InputStream,
getStdin () {
return stdinStream;
}
};

export const stdout = {
OutputStream,
getStdout () {
return stdoutStream;
}
};

export const stderr = {
OutputStream,
getStderr () {
return stderrStream;
}
};

class TerminalInput {}
class TerminalOutput {}

const terminalStdoutInstance = new TerminalOutput();
const terminalStderrInstance = new TerminalOutput();
const terminalStdinInstance = new TerminalInput();

export const terminalInput = {
TerminalInput
};

export const terminalOutput = {
TerminalOutput
};

export const terminalStderr = {
TerminalOutput,
getTerminalStderr () {
return terminalStderrInstance;
}
};

export const terminalStdin = {
TerminalInput,
getTerminalStdin () {
return terminalStdinInstance;
}
};

export const terminalStdout = {
TerminalOutput,
getTerminalStdout () {
return terminalStdoutInstance;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const monotonicClock = {
resolution() {
// usually we dont get sub-millisecond accuracy in the browser
// Note: is there a better way to determine this?
return 1e6;
},
now () {
// performance.now() is in milliseconds, but we want nanoseconds
return BigInt(Math.floor(performance.now() * 1e6));
},
subscribeInstant (instant) {
instant = BigInt(instant);
const now = this.now();
if (instant <= now)
return this.subscribeDuration(0);
return this.subscribeDuration(instant - now);
},
subscribeDuration (_duration) {
_duration = BigInt(_duration);
console.log(`[monotonic-clock] subscribe`);
}
};

export const wallClock = {
now() {
let now = Date.now(); // in milliseconds
const seconds = BigInt(Math.floor(now / 1e3));
const nanoseconds = (now % 1e3) * 1e6;
return { seconds, nanoseconds };
},
resolution() {
return { seconds: 0n, nanoseconds: 1e6 };
}
};
Loading
Loading