diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 3d332f5..05990c8 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -2,6 +2,10 @@ module.exports = {
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
+ env: {
+ browser: true,
+ node: true,
+ },
rules: {
"@typescript-eslint/no-this-alias": "off",
},
diff --git a/.gitignore b/.gitignore
index 149320b..c493683 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,8 @@
node_modules/
dist/
typings/
+/test-results/
+/playwright-report/
+/playwright/.cache/
+
+wasmtime/
\ No newline at end of file
diff --git a/README.md b/README.md
index ea591f6..f04ba5b 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,22 @@ $ npm install
$ npm run build
```
+## Running tests
+
+This project uses playwright to run tests in a browser using WebAssembly built from the Wasmtime project.
+
+To clone wasmtime and build the test WebAssembly, run:
+
+```
+$ npm test:build
+```
+
+Once the WebAssembly is built, you can run the tests with:
+
+```
+$ npm test
+```
+
## Running the demo
The demo requires the wasm rustc artifacts and the xterm js package. To get them run:
diff --git a/e2e/harness.html b/e2e/harness.html
new file mode 100644
index 0000000..0c8cc15
--- /dev/null
+++ b/e2e/harness.html
@@ -0,0 +1,8 @@
+
+
+ Test Harness
+
+ Status: Not started
+ Check console for details
+
+
diff --git a/e2e/harness.js b/e2e/harness.js
new file mode 100644
index 0000000..c400bc2
--- /dev/null
+++ b/e2e/harness.js
@@ -0,0 +1,71 @@
+import { WASI, File, PreopenDirectory } from "../dist/index.js";
+
+const stdio = {
+ stdin: undefined,
+ stdout: new File(),
+ stderr: new File(),
+};
+
+const WASI_ENVS = {
+ DEFAULT: [
+ stdio.stdin,
+ stdio.stdout,
+ stdio.stderr,
+ new PreopenDirectory("/", {}),
+ ],
+};
+
+async function main(options) {
+ const wasmReq = await fetch(options.file);
+
+ const args = [options.file];
+ args.push(...(options.args || []));
+
+ const wasi = new WASI(
+ args,
+ (options.env || "").split(","),
+ WASI_ENVS[options.wasi_env] || WASI_ENVS.DEFAULT,
+ {
+ debug: true,
+ },
+ );
+ const module = await WebAssembly.compileStreaming(wasmReq);
+
+ const instance = await WebAssembly.instantiate(module, {
+ wasi_snapshot_preview1: wasi.wasiImport,
+ });
+
+ const status = document.getElementById("status");
+ try {
+ if (options.reactor) {
+ wasi.initialize(instance);
+ }
+ if (options.command) {
+ wasi.start(instance);
+ }
+ printBuffer(stdio, "stdout", "log");
+ printBuffer(stdio, "stderr", "log");
+ status.innerText = "success";
+ } catch (e) {
+ printBuffer(stdio, "stdout", "log");
+ printBuffer(stdio, "stderr", "error");
+ status.innerText = "failure";
+ throw e;
+ }
+}
+
+function printBuffer(stdio, type, logger) {
+ const output = new TextDecoder().decode(stdio[type].data);
+ if (output.trim().length > 0) {
+ console[logger](`${type}`, output);
+ }
+}
+
+main(JSON.parse(decodeURI(location.search.slice(1))))
+ .then(() => {
+ console.log("done");
+ })
+ .catch((e) => {
+ console.error(e);
+ throw e;
+ });
diff --git a/e2e/wasmtime.spec.ts b/e2e/wasmtime.spec.ts
new file mode 100644
index 0000000..4a9291f
--- /dev/null
+++ b/e2e/wasmtime.spec.ts
@@ -0,0 +1,24 @@
+import { test, expect } from "@playwright/test";
+
+const URL = "http://127.0.0.1:3000/e2e/harness.html";
+const WASMDIR = "../wasmtime/artifacts";
+
+const tests = [
+ {
+ file: `${WASMDIR}/preview1_path_open_read_write.wasm`,
+ command: true,
+ args: ["/"],
+ },
+];
+
+for (const testDef of tests) {
+ test(`first ${testDef.file}`, async ({ page }) => {
+ await page.goto(`${URL}?${JSON.stringify(testDef)}`);
+ await page.waitForFunction(
+ () => document.getElementById("status")?.textContent !== "Not started",
+ );
+ expect(await await page.getByTestId("status").textContent()).toBe(
+ "success",
+ );
+ });
+}
diff --git a/package-lock.json b/package-lock.json
index 251b201..72be966 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,11 +9,16 @@
"version": "0.2.15",
"license": "MIT OR Apache-2.0",
"devDependencies": {
+ "@playwright/test": "^1.38.1",
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.37",
+ "@types/node": "^20.8.4",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
+ "chokidar": "^3.5.3",
"eslint": "^8.50.0",
+ "http-server": "^14.1.1",
+ "playwright": "^1.38.1",
"prettier": "^3.0.3",
"typescript": "^4.9.5"
}
@@ -170,6 +175,21 @@
"node": ">= 8"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.38.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz",
+ "integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==",
+ "dev": true,
+ "dependencies": {
+ "playwright": "1.38.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
@@ -451,10 +471,13 @@
}
},
"node_modules/@types/node": {
- "version": "18.14.6",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz",
- "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==",
- "dev": true
+ "version": "20.8.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.4.tgz",
+ "integrity": "sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~5.25.1"
+ }
},
"node_modules/@types/responselike": {
"version": "1.0.0",
@@ -721,6 +744,19 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/arch": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
@@ -756,12 +792,39 @@
"node": ">=8"
}
},
+ "node_modules/async": {
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
+ "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.17.14"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "5.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/basic-auth/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
"node_modules/bin-check": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz",
@@ -926,6 +989,15 @@
"node": ">= 8"
}
},
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -990,6 +1062,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1015,6 +1100,33 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
@@ -1072,6 +1184,15 @@
"node": ">= 0.6"
}
},
+ "node_modules/corser": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
+ "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/cross-spawn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
@@ -1411,6 +1532,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "dev": true
+ },
"node_modules/execa": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
@@ -1630,12 +1757,67 @@
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
"dev": true
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.3",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
+ "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
+ "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/get-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
@@ -1743,6 +1925,15 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
+ "node_modules/has": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
+ "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -1752,12 +1943,98 @@
"node": ">=8"
}
},
+ "node_modules/has-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
+ "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-encoding": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
"dev": true
},
+ "node_modules/http-proxy": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+ "dev": true,
+ "dependencies": {
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/http-server": {
+ "version": "14.1.1",
+ "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
+ "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
+ "dev": true,
+ "dependencies": {
+ "basic-auth": "^2.0.1",
+ "chalk": "^4.1.2",
+ "corser": "^2.0.1",
+ "he": "^1.2.0",
+ "html-encoding-sniffer": "^3.0.0",
+ "http-proxy": "^1.18.1",
+ "mime": "^1.6.0",
+ "minimist": "^1.2.6",
+ "opener": "^1.5.1",
+ "portfinder": "^1.0.28",
+ "secure-compare": "3.0.1",
+ "union": "~0.5.0",
+ "url-join": "^4.0.1"
+ },
+ "bin": {
+ "http-server": "bin/http-server"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
@@ -1780,6 +2057,18 @@
"node": ">=10.17.0"
}
},
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -1850,6 +2139,18 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -1980,6 +2281,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -2033,6 +2340,18 @@
"node": ">=8.6"
}
},
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "dev": true,
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -2072,6 +2391,27 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -2084,6 +2424,15 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/normalize-url": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
@@ -2108,6 +2457,15 @@
"node": ">=4"
}
},
+ "node_modules/object-inspect": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -2132,6 +2490,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/opener": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
+ "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
+ "dev": true,
+ "bin": {
+ "opener": "bin/opener-bin.js"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
@@ -2291,6 +2658,73 @@
"node": ">=0.10.0"
}
},
+ "node_modules/playwright": {
+ "version": "1.38.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz",
+ "integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==",
+ "dev": true,
+ "dependencies": {
+ "playwright-core": "1.38.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.38.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz",
+ "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==",
+ "dev": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/portfinder": {
+ "version": "1.0.32",
+ "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
+ "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==",
+ "dev": true,
+ "dependencies": {
+ "async": "^2.6.4",
+ "debug": "^3.2.7",
+ "mkdirp": "^0.5.6"
+ },
+ "engines": {
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/portfinder/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -2340,6 +2774,21 @@
"node": ">=6"
}
},
+ "node_modules/qs": {
+ "version": "6.11.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
+ "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
+ "dev": true,
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -2402,6 +2851,24 @@
"url": "https://github.com/sponsors/Borewit"
}
},
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true
+ },
"node_modules/resolve-alpn": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
@@ -2497,6 +2964,18 @@
}
]
},
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "node_modules/secure-compare": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
+ "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
+ "dev": true
+ },
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
@@ -2584,6 +3063,20 @@
"node": ">=0.10.0"
}
},
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -2820,6 +3313,24 @@
"node": ">=4.2.0"
}
},
+ "node_modules/undici-types": {
+ "version": "5.25.3",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz",
+ "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==",
+ "dev": true
+ },
+ "node_modules/union": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
+ "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
+ "dev": true,
+ "dependencies": {
+ "qs": "^6.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -2829,12 +3340,30 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/url-join": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
+ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
+ "dev": true
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
+ "node_modules/whatwg-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+ "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "dev": true,
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
diff --git a/package.json b/package.json
index 3bbeaeb..f0c748a 100644
--- a/package.json
+++ b/package.json
@@ -6,8 +6,12 @@
"type": "module",
"scripts": {
"build": "swc src -d dist && tsc --emitDeclarationOnly",
+ "watch": "swc src -w -d dist",
"prepare": "swc src -d dist && tsc --emitDeclarationOnly",
- "check": "tsc --noEmit && prettier src -c && eslint src/"
+ "check": "tsc --noEmit && prettier src -c && eslint src/",
+ "test": "playwright test",
+ "test:build": "./scripts/build-tests.sh",
+ "test:server": "http-server -p 3000"
},
"repository": {
"type": "git",
@@ -26,11 +30,16 @@
"typings": "./typings/index.d.ts",
"homepage": "https://github.com/bjorn3/browser_wasi_shim#readme",
"devDependencies": {
+ "@playwright/test": "^1.38.1",
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.37",
+ "@types/node": "^20.8.4",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
+ "chokidar": "^3.5.3",
"eslint": "^8.50.0",
+ "http-server": "^14.1.1",
+ "playwright": "^1.38.1",
"prettier": "^3.0.3",
"typescript": "^4.9.5"
}
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 0000000..868925b
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,56 @@
+import { defineConfig, devices } from "@playwright/test";
+
+/**
+ * Read environment variables from file.
+ * https://github.com/motdotla/dotenv
+ */
+// require('dotenv').config();
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+export default defineConfig({
+ testDir: "./e2e",
+ /* Run tests in files in parallel */
+ fullyParallel: true,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ /* Retry on CI only */
+ retries: process.env.CI ? 2 : 0,
+ /* Opt out of parallel tests on CI. */
+ workers: process.env.CI ? 1 : undefined,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: "html",
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ // baseURL: 'http://127.0.0.1:3000',
+
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: "on-first-retry",
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: "chromium",
+ use: { ...devices["Desktop Chrome"] },
+ },
+
+ {
+ name: "firefox",
+ use: { ...devices["Desktop Firefox"] },
+ },
+
+ {
+ name: "webkit",
+ use: { ...devices["Desktop Safari"] },
+ },
+ ],
+
+ webServer: {
+ command: "npm run test:server",
+ url: "http://127.0.0.1:3000",
+ // reuseExistingServer: !process.env.CI,
+ },
+});
diff --git a/scripts/build-tests.sh b/scripts/build-tests.sh
new file mode 100755
index 0000000..d4f903f
--- /dev/null
+++ b/scripts/build-tests.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+
+WASMTIME_DIR=$SCRIPT_DIR/../wasmtime
+
+if ! [[ -d wasmtime ]]; then
+ git clone --recurse-submodules https://github.com/bytecodealliance/wasmtime.git --depth=1 $WASMTIME_DIR;
+fi
+
+cargo build --manifest-path $WASMTIME_DIR/crates/test-programs/artifacts/Cargo.toml
+
+TARGET_DIR=$(ls -dt $WASMTIME_DIR/target/debug/build/test-programs-artifacts-* | head -n 1)
+
+if [[ -f $WASMTIME_DIR/artifacts ]]; then
+ $WASMTIME_DIR/artifacts
+fi
+
+ln -s $TARGET_DIR/out/wasm32-wasi/debug $WASMTIME_DIR/artifacts
+
diff --git a/src/fd.ts b/src/fd.ts
index 4a49b64..c297d8b 100644
--- a/src/fd.ts
+++ b/src/fd.ts
@@ -38,10 +38,13 @@ export class Fd {
fd_pread(view8: Uint8Array, iovs: Array, offset: bigint) {
return { ret: -1, nread: 0 };
}
- fd_prestat_get() {
+ fd_prestat_get(): {
+ ret: number;
+ prestat: wasi.Prestat | null;
+ } {
return { ret: -1, prestat: null };
}
- fd_prestat_dir_name() {
+ fd_prestat_dir_name(): { ret: number; prestat_dir_name: Uint8Array | null } {
return { ret: -1, prestat_dir_name: null };
}
fd_pwrite(view8: Uint8Array, iovs: Array, offset: bigint) {
@@ -74,7 +77,10 @@ export class Fd {
path_create_directory(path): number {
return -1;
}
- path_filestat_get(flags, path) {
+ path_filestat_get(
+ flags,
+ path,
+ ): { ret: number; filestat: wasi.Filestat | null } {
return { ret: -1, filestat: null };
}
path_filestat_set_times(flags, path, atim, mtim, fst_flags) {
@@ -84,16 +90,16 @@ export class Fd {
return -1;
}
path_open(
- dirflags,
- path,
- oflags,
- fs_rights_base,
- fs_rights_inheriting,
- fdflags,
- ) {
+ dirflags: number,
+ path: string,
+ oflags: number,
+ fs_rights_base: bigint,
+ fs_rights_inheriting: bigint,
+ fd_flags: number,
+ ): { ret: number; fd_obj: Fd | null } {
return { ret: -1, fd_obj: null };
}
- path_readlink(path) {
+ path_readlink(path): { ret: -1; data: Uint8Array | null } {
return { ret: -1, data: null };
}
path_remove_directory(path): number {
diff --git a/src/fs_core.ts b/src/fs_core.ts
index 4532ee3..d71fded 100644
--- a/src/fs_core.ts
+++ b/src/fs_core.ts
@@ -19,8 +19,8 @@ export class File {
this.readonly = !!options?.readonly;
}
- open(fd_flags: number) {
- const file = new OpenFile(this);
+ open(fd_flags: number, fs_rights_base: bigint) {
+ const file = new OpenFile(this, fs_rights_base);
if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END);
return file;
}
@@ -66,8 +66,8 @@ export class SyncOPFSFile {
this.readonly = !!options?.readonly;
}
- open(fd_flags: number) {
- const file = new OpenSyncOPFSFile(this);
+ open(fd_flags: number, fs_rights_base: bigint) {
+ const file = new OpenSyncOPFSFile(this, fs_rights_base);
if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END);
return file;
}
@@ -115,7 +115,7 @@ export class Directory {
if (entry.contents[component] != undefined) {
entry = entry.contents[component];
} else {
- debug.log(component);
+ debug.log(`component ${component} undefined`);
return null;
}
}
diff --git a/src/fs_fd.ts b/src/fs_fd.ts
index 104958f..f13b6cc 100644
--- a/src/fs_fd.ts
+++ b/src/fs_fd.ts
@@ -6,20 +6,35 @@ import { debug } from "./debug.js";
export class OpenFile extends Fd {
file: File;
file_pos: bigint = 0n;
+ fs_rights_base: bigint = 0n;
- constructor(file: File) {
+ constructor(file: File, fs_rights_base: bigint = 0n) {
super();
this.file = file;
+ this.fs_rights_base = fs_rights_base;
}
fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat | null } {
- return { ret: 0, fdstat: new wasi.Fdstat(wasi.FILETYPE_REGULAR_FILE, 0) };
+ return {
+ ret: 0,
+ fdstat: new wasi.Fdstat(
+ wasi.FILETYPE_REGULAR_FILE,
+ 0,
+ this.fs_rights_base,
+ ),
+ };
}
fd_read(
view8: Uint8Array,
iovs: Array,
): { ret: number; nread: number } {
+ if (
+ (Number(this.fs_rights_base) & wasi.RIGHTS_FD_READ) !==
+ wasi.RIGHTS_FD_READ
+ ) {
+ return { ret: wasi.ERRNO_BADF, nread: 0 };
+ }
let nread = 0;
for (const iovec of iovs) {
if (this.file_pos < this.file.data.byteLength) {
@@ -66,7 +81,11 @@ export class OpenFile extends Fd {
iovs: Array,
): { ret: number; nwritten: number } {
let nwritten = 0;
- if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten };
+ if (
+ this.file.readonly ||
+ (Number(this.fs_rights_base) & wasi.RIGHTS_FD_WRITE) === 0
+ )
+ return { ret: wasi.ERRNO_BADF, nwritten };
for (const iovec of iovs) {
const buffer = view8.slice(iovec.buf, iovec.buf + iovec.buf_len);
if (this.file_pos + BigInt(buffer.byteLength) > this.file.size) {
@@ -94,10 +113,12 @@ export class OpenFile extends Fd {
export class OpenSyncOPFSFile extends Fd {
file: SyncOPFSFile;
position: bigint = 0n;
+ fs_rights_base: bigint = 0n;
- constructor(file: SyncOPFSFile) {
+ constructor(file: SyncOPFSFile, fs_rights_base: bigint = 0n) {
super();
this.file = file;
+ this.fs_rights_base = fs_rights_base;
}
fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat | null } {
@@ -118,6 +139,12 @@ export class OpenSyncOPFSFile extends Fd {
view8: Uint8Array,
iovs: Array,
): { ret: number; nread: number } {
+ if (
+ (Number(this.fs_rights_base) & wasi.RIGHTS_FD_READ) !==
+ wasi.RIGHTS_FD_READ
+ ) {
+ return { ret: wasi.ERRNO_BADF, nread: 0 };
+ }
let nread = 0;
for (const iovec of iovs) {
if (this.position < this.file.handle.getSize()) {
@@ -162,7 +189,11 @@ export class OpenSyncOPFSFile extends Fd {
iovs: Array,
): { ret: number; nwritten: number } {
let nwritten = 0;
- if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten };
+ if (
+ this.file.readonly ||
+ (Number(this.fs_rights_base) & wasi.RIGHTS_FD_WRITE) === 0
+ )
+ return { ret: wasi.ERRNO_BADF, nwritten };
for (const iovec of iovs) {
const buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len);
// don't need to extend file manually, just write
@@ -279,7 +310,11 @@ export class OpenDirectory extends Fd {
const ret = entry.truncate();
if (ret != wasi.ERRNO_SUCCESS) return { ret, fd_obj: null };
}
- return { ret: wasi.ERRNO_SUCCESS, fd_obj: entry.open(fd_flags) };
+
+ return {
+ ret: wasi.ERRNO_SUCCESS,
+ fd_obj: entry.open(fd_flags, fs_rights_base),
+ };
}
path_create_directory(path: string): number {
@@ -292,6 +327,11 @@ export class OpenDirectory extends Fd {
0,
).ret;
}
+
+ path_unlink_file(path: string): number {
+ delete this.dir.contents[path];
+ return wasi.ERRNO_SUCCESS;
+ }
}
export class PreopenDirectory extends OpenDirectory {
diff --git a/src/wasi.ts b/src/wasi.ts
index 12392bd..ab16a89 100644
--- a/src/wasi.ts
+++ b/src/wasi.ts
@@ -7,11 +7,11 @@ export interface Options {
}
export default class WASI {
- args: Array = [];
- env: Array = [];
- fds: Array = [];
+ args: string[] = [];
+ env: string[] = [];
+ fds: (Fd | undefined)[] = [];
inst: { exports: { memory: WebAssembly.Memory } };
- wasiImport: { [key: string]: (...args: Array) => unknown };
+ wasiImport: wasi.WasiPreview1;
/// Start a WASI command
start(instance: {
@@ -19,6 +19,7 @@ export default class WASI {
exports: { memory: WebAssembly.Memory; _start: () => unknown };
}) {
this.inst = instance;
+ debug.log("calling _start");
instance.exports._start();
}
@@ -27,16 +28,22 @@ export default class WASI {
exports: { memory: WebAssembly.Memory; _initialize: () => unknown };
}) {
this.inst = instance;
- instance.exports._initialize();
+ if (instance.exports._initialize) {
+ debug.log("calling _initialize");
+ instance.exports._initialize();
+ } else {
+ debug.log("no _initialize found");
+ }
}
constructor(
- args: Array,
- env: Array,
- fds: Array,
+ args: string[] = [],
+ env: string[] = [],
+ fds: Fd[] = [],
options: Options = {},
) {
debug.enable(options.debug);
+ debug.log("initializing wasi", { args, env, fds });
this.args = args;
this.env = env;
@@ -485,6 +492,8 @@ export default class WASI {
buffer8.slice(path_ptr, path_ptr + path_len),
);
return self.fds[fd].path_create_directory(path);
+ } else {
+ return wasi.ERRNO_BADF;
}
},
path_filestat_get(
@@ -578,7 +587,7 @@ export default class WASI {
const path = new TextDecoder("utf-8").decode(
buffer8.slice(path_ptr, path_ptr + path_len),
);
- debug.log(path);
+ debug.log({ path });
const { ret, fd_obj } = self.fds[fd].path_open(
dirflags,
path,
@@ -591,8 +600,9 @@ export default class WASI {
return ret;
}
// FIXME use first free fd
- self.fds.push(fd_obj);
+ self.fds.push(fd_obj!);
const opened_fd = self.fds.length - 1;
+
buffer.setUint32(opened_fd_ptr, opened_fd, true);
return 0;
} else {
@@ -613,7 +623,7 @@ export default class WASI {
const path = new TextDecoder("utf-8").decode(
buffer8.slice(path_ptr, path_ptr + path_len),
);
- debug.log(path);
+ debug.log({ path });
const { ret, data } = self.fds[fd].path_readlink(path);
if (data != null) {
if (data.length > buf_len) {
diff --git a/src/wasi_defs.ts b/src/wasi_defs.ts
index 91fa7f6..f2a6053 100644
--- a/src/wasi_defs.ts
+++ b/src/wasi_defs.ts
@@ -128,12 +128,8 @@ export class Iovec {
return iovec;
}
- static read_bytes_array(
- view: DataView,
- ptr: number,
- len: number,
- ): Array {
- const iovecs = [];
+ static read_bytes_array(view: DataView, ptr: number, len: number): Iovec[] {
+ const iovecs: Iovec[] = [];
for (let i = 0; i < len; i++) {
iovecs.push(Iovec.read_bytes(view, ptr + 8 * i));
}
@@ -157,7 +153,7 @@ export class Ciovec {
ptr: number,
len: number,
): Array {
- const iovecs = [];
+ const iovecs: Ciovec[] = [];
for (let i = 0; i < len; i++) {
iovecs.push(Ciovec.read_bytes(view, ptr + 8 * i));
}
@@ -236,9 +232,10 @@ export class Fdstat {
fs_rights_base: bigint = 0n;
fs_rights_inherited: bigint = 0n;
- constructor(filetype: number, flags: number) {
+ constructor(filetype: number, flags: number, fs_rights_base = 0n) {
this.fs_filetype = filetype;
this.fs_flags = flags;
+ this.fs_rights_base = fs_rights_base;
}
write_bytes(view: DataView, ptr: number) {
@@ -364,3 +361,156 @@ export class Prestat {
this.inner.write_bytes(view, ptr + 4);
}
}
+
+export interface WasiPreview1 {
+ args_sizes_get(argc: number, argv_buf_size: number): number;
+ args_get(argv: number, argv_buf: number): number;
+
+ environ_sizes_get(environ_count: number, environ_size: number): number;
+ environ_get(environ: number, environ_buf: number): number;
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ clock_res_get(id: number, res_ptr: number): number;
+ clock_time_get(id: number, precision: bigint, time: number): number;
+
+ fd_advise(fd: number, offset: bigint, len: bigint, advice: number): number;
+ fd_allocate(fd: number, offset: bigint, len: bigint): number;
+ fd_close(fd: number): number;
+ fd_datasync(fd: number): number;
+ fd_fdstat_get(fd: number, fdstat_ptr: number): number;
+ fd_fdstat_set_flags(fd: number, flags: number): number;
+ fd_fdstat_set_rights(
+ fd: number,
+ fs_rights_base: bigint,
+ fs_rights_inheriting: bigint,
+ ): number;
+ fd_filestat_get(fd: number, filestat_ptr: number): number;
+ fd_filestat_set_size(fd: number, size: bigint): number;
+ fd_filestat_set_times(
+ fd: number,
+ atim: bigint,
+ mtim: bigint,
+ fst_flags: number,
+ ): number;
+ fd_pread(
+ fd: number,
+ iovs_ptr: number,
+ iovs_len: number,
+ offset: bigint,
+ nread_ptr: number,
+ ): number;
+ fd_prestat_get(fd: number, buf_ptr: number): number;
+
+ fd_prestat_dir_name(
+ fd: number,
+ path_ptr: number,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ path_len: number,
+ ): number;
+ fd_pwrite(
+ fd: number,
+ iovs_ptr: number,
+ iovs_len: number,
+ offset: bigint,
+ nwritten_ptr: number,
+ ): number;
+ fd_read(
+ fd: number,
+ iovs_ptr: number,
+ iovs_len: number,
+ nread_ptr: number,
+ ): number;
+ fd_readdir(
+ fd: number,
+ buf: number,
+ buf_len: number,
+ cookie: bigint,
+ bufused_ptr: number,
+ ): number;
+ fd_renumber(fd: number, to: number);
+ fd_seek(
+ fd: number,
+ offset: bigint,
+ whence: number,
+ offset_out_ptr: number,
+ ): number;
+ fd_sync(fd: number): number;
+ fd_tell(fd: number, offset_ptr: number): number;
+ fd_write(
+ fd: number,
+ iovs_ptr: number,
+ iovs_len: number,
+ nwritten_ptr: number,
+ ): number;
+ path_create_directory(fd: number, path_ptr: number, path_len: number): number;
+ path_filestat_get(
+ fd: number,
+ flags: number,
+ path_ptr: number,
+ path_len: number,
+ filestat_ptr: number,
+ ): number;
+ path_filestat_set_times(
+ fd: number,
+ flags: number,
+ path_ptr: number,
+ path_len: number,
+ atim,
+ mtim,
+ fst_flags,
+ );
+ path_link(
+ old_fd: number,
+ old_flags,
+ old_path_ptr: number,
+ old_path_len: number,
+ new_fd: number,
+ new_path_ptr: number,
+ new_path_len: number,
+ ): number;
+ path_open(
+ fd: number,
+ dirflags,
+ path_ptr: number,
+ path_len: number,
+ oflags,
+ fs_rights_base,
+ fs_rights_inheriting,
+ fd_flags,
+ opened_fd_ptr: number,
+ ): number;
+ path_readlink(
+ fd: number,
+ path_ptr: number,
+ path_len: number,
+ buf_ptr: number,
+ buf_len: number,
+ nread_ptr: number,
+ ): number;
+ path_remove_directory(fd: number, path_ptr: number, path_len: number): number;
+ path_rename(
+ fd: number,
+ old_path_ptr: number,
+ old_path_len: number,
+ new_fd: number,
+ new_path_ptr: number,
+ new_path_len: number,
+ ): number;
+ path_symlink(
+ old_path_ptr: number,
+ old_path_len: number,
+ fd: number,
+ new_path_ptr: number,
+ new_path_len: number,
+ ): number;
+ path_unlink_file(fd: number, path_ptr: number, path_len: number): number;
+ poll_oneoff(in_, out, nsubscriptions);
+ proc_exit(exit_code: number);
+ proc_raise(sig: number);
+ sched_yield();
+ random_get(buf: number, buf_len: number);
+ sock_recv(fd: number, ri_data, ri_flags);
+ sock_send(fd: number, si_data, si_flags);
+ sock_shutdown(fd: number, how);
+ sock_accept(fd: number, flags);
+}
diff --git a/tsconfig.json b/tsconfig.json
index 17ebcea..36488c6 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,7 +12,10 @@
/* Language and Environment */
"target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
- // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ "lib": [
+ "dom",
+ "ESNext"
+ ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */