Skip to content

Commit edc1e84

Browse files
committed
feat(web-host): prepareVirtualFs
1 parent 4785441 commit edc1e84

File tree

6 files changed

+262
-1
lines changed

6 files changed

+262
-1
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env node --experimental-strip-types --no-warnings
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
import { program } from "commander";
5+
6+
type VirtualFs =
7+
| Record<
8+
string,
9+
| {
10+
dir: Record<string, VirtualFs>;
11+
}
12+
| {
13+
source: string;
14+
}
15+
| Record<string, never>
16+
>
17+
| Record<string, any>;
18+
19+
function makeVirtualFs(filepath: string, acc: VirtualFs) {
20+
fs.readdirSync(filepath).forEach((file) => {
21+
const fullPath = path.join(filepath, file);
22+
if (fs.statSync(fullPath).isDirectory()) {
23+
acc[file] = { dir: {} };
24+
makeVirtualFs(fullPath, acc[file].dir);
25+
} else {
26+
if (file === "README.rust.md") {
27+
return;
28+
}
29+
if (file === "README.browser.md") {
30+
file = "README.md";
31+
}
32+
acc[file] = { source: fs.readFileSync(fullPath, "utf-8") };
33+
}
34+
});
35+
}
36+
37+
function prepareFilesystem({ filepath }: { filepath: string }): VirtualFs {
38+
const workspaceRoot = path.join(import.meta.dirname, "..", "..", "..");
39+
const targetDir = filepath.startsWith("/")
40+
? filepath
41+
: path.join(workspaceRoot, filepath);
42+
if (!fs.existsSync(targetDir)) {
43+
throw new Error(`Path ${filepath} does not exist`);
44+
}
45+
const virtualFs: VirtualFs = { dir: {} };
46+
makeVirtualFs(targetDir, virtualFs.dir);
47+
return virtualFs;
48+
}
49+
50+
function assertPathIsString(path: string): asserts path is string {
51+
if (typeof path !== "string") {
52+
throw new Error("Path must be a string");
53+
}
54+
}
55+
56+
function run() {
57+
program
58+
.description("Prepare wasm files for the web host")
59+
.requiredOption("-p, --path <path>", "Path to the filesystem to prepare")
60+
.option("-f, --format <format>", "Format to output the filesystem", "json")
61+
.action((options) => {
62+
const { path: filepath } = options;
63+
assertPathIsString(filepath);
64+
const virtualFs = prepareFilesystem({ filepath });
65+
if (options.format === "json") {
66+
console.log(JSON.stringify(virtualFs, null, 2));
67+
} else if (options.format === "ts") {
68+
console.log(
69+
`// THIS FILE IS GENERATED BY THE prepareVirtualFs COMMAND, DO NOT EDIT IT MANUALLY
70+
71+
// It is meant to be used for mounting a virtual filesystem in the browser
72+
// interacting with @bytecodealliance/preview2-shim/filesystem , the shim for wasi:filesystem
73+
//
74+
// The \`fs\` calls like \`read\`, \`readDir\` ... in rust or other languages will be redirected to this virtual filesystem
75+
// and will interact as if the filesystem was a real one.
76+
77+
export function makeVirtualFs() { return ${JSON.stringify(virtualFs, null, 2)}; }`,
78+
);
79+
}
80+
});
81+
82+
program.parse();
83+
}
84+
85+
run();

packages/web-host/clis/prepareWasmFiles.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ const wasmFiles: Array<{ debug: string; release: string }> = [
1616
debug: "target/wasm32-wasip1/debug/plugin_ls.wasm",
1717
release: "target/wasm32-wasip1/release/plugin_ls.wasm",
1818
},
19+
{
20+
debug: "target/wasm32-wasip1/debug/plugin_cat.wasm",
21+
release: "target/wasm32-wasip1/release/plugin_cat.wasm",
22+
},
1923
{
2024
debug: "target/wasm32-wasip1/debug/plugin_weather.wasm",
2125
release: "target/wasm32-wasip1/release/plugin_weather.wasm",

packages/web-host/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
"typecheck": "tsc --noEmit -p tsconfig.app.json",
1818
"prepareWasmFiles:release": "node --experimental-strip-types --no-warnings ./clis/prepareWasmFiles.ts --mode release",
1919
"prepareWasmFiles:debug": "node --experimental-strip-types --no-warnings ./clis/prepareWasmFiles.ts --mode debug",
20+
"prepareVirtualFs": "node --experimental-strip-types --no-warnings ./clis/prepareFilesystem.ts --path fixtures/filesystem --format ts > src/wasm/virtualFs.ts; biome format --write ./src/wasm/virtualFs.ts",
2021
"wasm:transpile:plugin-echo": "jco transpile --no-nodejs-compat --no-namespaced-exports public/plugins/plugin_echo.wasm -o ./src/wasm/generated/plugin_echo/transpiled",
2122
"wasm:transpile:plugin-weather": "jco transpile --no-nodejs-compat --no-namespaced-exports public/plugins/plugin_weather.wasm -o ./src/wasm/generated/plugin_weather/transpiled",
2223
"wasm:transpile:plugin-greet": "jco transpile --no-nodejs-compat --no-namespaced-exports public/plugins/plugin_greet.wasm -o ./src/wasm/generated/plugin_greet/transpiled",
2324
"wasm:transpile:plugin-ls": "jco transpile --no-nodejs-compat --no-namespaced-exports public/plugins/plugin_ls.wasm -o ./src/wasm/generated/plugin_ls/transpiled",
25+
"wasm:transpile:plugin-cat": "jco transpile --no-nodejs-compat --no-namespaced-exports public/plugins/plugin_cat.wasm -o ./src/wasm/generated/plugin_cat/transpiled",
2426
"wasm:transpile:repl-logic-guest": "jco transpile --no-nodejs-compat --no-namespaced-exports public/plugins/repl_logic_guest.wasm -o ./src/wasm/generated/repl_logic_guest/transpiled",
25-
"wasm:transpile": "npm run wasm:transpile:plugin-echo && npm run wasm:transpile:plugin-weather && npm run wasm:transpile:plugin-greet && npm run wasm:transpile:plugin-ls && npm run wasm:transpile:repl-logic-guest",
27+
"wasm:transpile": "npm run wasm:transpile:plugin-echo && npm run wasm:transpile:plugin-weather && npm run wasm:transpile:plugin-greet && npm run wasm:transpile:plugin-ls && npm run wasm:transpile:plugin-cat && npm run wasm:transpile:repl-logic-guest",
2628
"wit-types:host-api": "jco types --world-name host-api --out-dir ./src/types/generated ../../crates/pluginlab/wit",
2729
"wit-types:plugin-api": "jco types --world-name plugin-api --out-dir ./src/types/generated ../../crates/pluginlab/wit",
2830
"wit-types": "npm run wit-types:clean && npm run wit-types:host-api && npm run wit-types:plugin-api && biome format --write ./src/types/generated",

packages/web-host/src/App.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
// @ts-ignore -- private API
2+
import { _setFileData } from "@bytecodealliance/preview2-shim/filesystem";
3+
import { useEffect } from "react";
14
import { HomePage } from "./components/HomePage";
25
import { ReplPage } from "./components/ReplPage";
36
import { useHashNavigation } from "./hooks/navigation";
47
import { WasmProvider } from "./hooks/wasm";
58
import { cn } from "./utils/css";
9+
import { makeVirtualFs } from "./wasm/virtualFs";
610

711
interface HeaderProps extends React.HTMLAttributes<HTMLDivElement> {
812
navigateToHome: () => void;
@@ -66,6 +70,10 @@ const Footer = (props: React.HTMLAttributes<HTMLDivElement>) => {
6670
function App() {
6771
const { currentPage, navigateToRepl, navigateToHome } = useHashNavigation();
6872

73+
useEffect(() => {
74+
_setFileData(makeVirtualFs());
75+
}, []);
76+
6977
return (
7078
<WasmProvider>
7179
<div className="min-h-screen flex flex-col bg-gray-50 text-gray-900">

packages/web-host/src/wasm/engine.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ async function loadPlugins({
3939
import("./generated/plugin_weather/transpiled/plugin_weather.js"),
4040
import("./generated/plugin_greet/transpiled/plugin_greet.js"),
4141
import("./generated/plugin_ls/transpiled/plugin_ls.js"),
42+
import("./generated/plugin_cat/transpiled/plugin_cat.js"),
4243
]).then((plugins) => plugins.map((plugin) => plugin.plugin));
4344

4445
// log the plugins names
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// THIS FILE IS GENERATED BY THE prepareVirtualFs COMMAND, DO NOT EDIT IT MANUALLY
2+
3+
// It is meant to be used for mounting a virtual filesystem in the browser
4+
// interacting with @bytecodealliance/preview2-shim/filesystem , the shim for wasi:filesystem
5+
//
6+
// The `fs` calls like `read`, `readDir` ... in rust or other languages will be redirected to this virtual filesystem
7+
// and will interact as if the filesystem was a real one.
8+
9+
export function makeVirtualFs() {
10+
return {
11+
dir: {
12+
".config": {
13+
source:
14+
"# Hidden configuration file\n# This file contains sensitive configuration data\n# Should be handled carefully in tests\n\n[app]\ndebug = true\nlog_level = info\nmax_connections = 10\n\n[filesystem]\nroot_path = /tmp/filesystem\nallow_hidden = false\nmax_file_size = 1048576\n",
15+
},
16+
".hidden_file": {
17+
source:
18+
"# This is a hidden file\n# It should only be visible when explicitly requested\n# Used for testing hidden file operations\n",
19+
},
20+
"README.md": {
21+
source: "# filesystem\n",
22+
},
23+
data: {
24+
dir: {
25+
processed: {
26+
dir: {
27+
"2024": {
28+
dir: {
29+
".gitkeep": {
30+
source:
31+
"# This file ensures the 2024 directory is tracked in git\n# Year-based nested directory for testing\n",
32+
},
33+
"01": {
34+
dir: {
35+
".gitkeep": {
36+
source:
37+
"# This file ensures the 01 directory is tracked in git\n# Month-based nested directory for testing\n",
38+
},
39+
},
40+
},
41+
"02": {
42+
dir: {
43+
".gitkeep": {
44+
source:
45+
"# This file ensures the 02 directory is tracked in git\n# Another month directory for testing\n",
46+
},
47+
},
48+
},
49+
},
50+
},
51+
},
52+
},
53+
raw: {
54+
dir: {
55+
incoming: {
56+
dir: {
57+
".gitkeep": {
58+
source:
59+
"# This file ensures the incoming directory is tracked in git\n# Raw data nested directory for testing\n",
60+
},
61+
},
62+
},
63+
outgoing: {
64+
dir: {
65+
".gitkeep": {
66+
source:
67+
"# This file ensures the outgoing directory is tracked in git\n# Outgoing data nested directory for testing\n",
68+
},
69+
},
70+
},
71+
},
72+
},
73+
"sample.csv": {
74+
source:
75+
"id,name,age,city,active\n1,Alice,28,New York,true\n2,Bob,32,San Francisco,true\n3,Charlie,25,Chicago,false\n4,Diana,29,Boston,true\n5,Eve,35,Seattle,true\n",
76+
},
77+
"users.yaml": {
78+
source:
79+
"users:\n - id: 1\n name: Alice\n email: alice@example.com\n role: admin\n permissions:\n - read\n - write\n - delete\n - id: 2\n name: Bob\n email: bob@example.com\n role: user\n permissions:\n - read\n - write\n - id: 3\n name: Charlie\n email: charlie@example.com\n role: guest\n permissions:\n - read\n\nsettings:\n max_file_size: 1048576\n allowed_extensions:\n - .txt\n - .md\n - .json\n - .yaml\n - .csv\n",
80+
},
81+
},
82+
},
83+
documents: {
84+
dir: {
85+
"README.md": {
86+
source:
87+
"# Documents\n\nThis is a mock documents directory for testing filesystem operations.\n\n## Contents\n- Various text files\n- Configuration files\n- Sample data\n\nFeel free to modify these files during testing.\n",
88+
},
89+
"config.json": {
90+
source:
91+
'{\n "app": {\n "name": "Test Application",\n "version": "1.0.0",\n "debug": true\n },\n "filesystem": {\n "root": "/tmp/filesystem",\n "max_file_size": 1048576,\n "allowed_extensions": [".txt", ".md", ".json", ".yaml", ".yml"]\n },\n "features": {\n "read": true,\n "write": true,\n "delete": false,\n "create_directories": true\n }\n}\n',
92+
},
93+
"notes.txt": {
94+
source:
95+
"Meeting Notes - 2024-01-15\n\nAgenda:\n1. Project status review\n2. Next sprint planning\n3. Technical debt discussion\n\nAction Items:\n- Update documentation\n- Review pull requests\n- Schedule team meeting\n\nNotes:\nThe new filesystem API is working well. Need to add more test cases.\n",
96+
},
97+
work: {
98+
dir: {
99+
projects: {
100+
dir: {
101+
".gitkeep": {
102+
source:
103+
"# This file ensures the projects directory is tracked in git\n# Nested directory structure for testing\n",
104+
},
105+
alpha: {
106+
dir: {
107+
".gitkeep": {
108+
source:
109+
"# This file ensures the alpha directory is tracked in git\n# Deep nested directory for testing\n",
110+
},
111+
},
112+
},
113+
beta: {
114+
dir: {
115+
".gitkeep": {
116+
source:
117+
"# This file ensures the beta directory is tracked in git\n# Another nested directory for testing\n",
118+
},
119+
},
120+
},
121+
},
122+
},
123+
},
124+
},
125+
},
126+
},
127+
logs: {
128+
dir: {
129+
"app.log": {
130+
source:
131+
"2024-01-15 10:30:15 INFO Application started\n2024-01-15 10:30:16 INFO Loading configuration from config.json\n2024-01-15 10:30:17 INFO Filesystem initialized with root: /tmp/filesystem\n2024-01-15 10:30:18 INFO User authentication successful\n2024-01-15 10:30:19 INFO File read operation: documents/notes.txt\n2024-01-15 10:30:20 INFO File write operation: temp/test.txt\n2024-01-15 10:30:21 WARN Large file detected: backup/archive.tar.gz\n2024-01-15 10:30:22 INFO Directory listing: documents/\n2024-01-15 10:30:23 INFO File delete operation: temp/old_file.txt\n2024-01-15 10:30:24 ERROR Permission denied: /etc/passwd\n2024-01-15 10:30:25 INFO Application shutdown\n",
132+
},
133+
errors: {
134+
dir: {
135+
".gitkeep": {
136+
source:
137+
"# This file ensures the errors directory is tracked in git\n# Error logs nested directory for testing\n",
138+
},
139+
critical: {
140+
dir: {
141+
".gitkeep": {
142+
source:
143+
"# This file ensures the critical directory is tracked in git\n# Critical error logs nested directory for testing\n",
144+
},
145+
},
146+
},
147+
warning: {
148+
dir: {
149+
".gitkeep": {
150+
source:
151+
"# This file ensures the warning directory is tracked in git\n# Warning logs nested directory for testing\n",
152+
},
153+
},
154+
},
155+
},
156+
},
157+
},
158+
},
159+
},
160+
};
161+
}

0 commit comments

Comments
 (0)