Playground: remove JS fallback, require WASM for all commands

Remove the jsFallback function and its tests. Commands now require
the WASM binary to be loaded. If WASM is still loading, a "please
wait" message is shown instead of running JS approximations.
This commit is contained in:
Sylvestre Ledru
2026-04-04 12:58:14 +02:00
parent b00263738c
commit 7b8c877eb0
2 changed files with 30 additions and 227 deletions
+29 -123
View File
@@ -511,38 +511,35 @@ async function executeCommandLine(line) {
const cmd = args[0];
// Try WASM execution for commands in the WASM build
if (wasmReady && AVAILABLE_COMMANDS.includes(cmd)) {
try {
// Resolve relative paths using the virtual cwd
const resolvedArgs = args.map((arg, i) => {
if (i === 0) return arg; // command name
if (arg.startsWith("-")) return arg; // flag
return resolvePath(arg);
});
// If the command takes a default path (like ls) and no path args
// were given, add the cwd so it lists the right directory
const hasPathArg = resolvedArgs.slice(1).some(a => !a.startsWith("-"));
if (!hasPathArg && cwd && ["ls", "dir"].includes(cmd)) {
resolvedArgs.push(cwd);
}
const wasmArgs = ["coreutils", ...resolvedArgs];
const result = await runCommand(wasmArgs, stdinData);
if (result.stderr) {
return result.stderr + result.stdout;
}
stdinData = result.stdout;
} catch (e) {
return `Error running '${cmd}': ${e.message}\n`;
if (!wasmReady) {
return "WASM binary is still loading, please wait...\n";
}
if (!AVAILABLE_COMMANDS.includes(cmd)) {
return `uutils: command not found: ${cmd}\nType 'help' for available commands.\n`;
}
try {
// Resolve relative paths using the virtual cwd
const resolvedArgs = args.map((arg, i) => {
if (i === 0) return arg; // command name
if (arg.startsWith("-")) return arg; // flag
return resolvePath(arg);
});
// If the command takes a default path (like ls) and no path args
// were given, add the cwd so it lists the right directory
const hasPathArg = resolvedArgs.slice(1).some(a => !a.startsWith("-"));
if (!hasPathArg && cwd && ["ls", "dir"].includes(cmd)) {
resolvedArgs.push(cwd);
}
} else {
// JS fallback for commands not in the WASM build or when WASM isn't ready
const result = jsFallback(args, stdinData);
if (result !== null) {
stdinData = result;
} else {
return `uutils: command not found: ${cmd}\nType 'help' for available commands.\n`;
const wasmArgs = ["coreutils", ...resolvedArgs];
const result = await runCommand(wasmArgs, stdinData);
if (result.stderr) {
return result.stderr + result.stdout;
}
stdinData = result.stdout;
} catch (e) {
return `Error running '${cmd}': ${e.message}\n`;
}
// Handle output redirection: > file or >> file
@@ -555,95 +552,6 @@ async function executeCommandLine(line) {
return stdinData;
}
/**
* JS fallback implementations for when WASM is not loaded.
*/
function jsFallback(args, stdinData) {
const cmd = args[0];
const fileArgs = args.slice(1).filter((a) => !a.startsWith("-"));
const flags = args.slice(1).filter((a) => a.startsWith("-"));
let input = stdinData;
for (const f of fileArgs) {
if (SAMPLE_FILES[f]) input += SAMPLE_FILES[f];
}
switch (cmd) {
case "ls": {
if (flags.includes("--version")) {
return "ls: not available in WASM build (requires platform-specific syscalls)\nUse 'ls' to list sample files, or try 'echo --version' to verify WASM is working.\n";
}
return Object.keys(SAMPLE_FILES).join(" ") + "\n";
}
case "cat": {
const files = args.slice(1).filter((a) => !a.startsWith("-"));
let out = stdinData;
for (const f of files) {
if (SAMPLE_FILES[f]) out += SAMPLE_FILES[f];
else return `cat: ${f}: No such file or directory\n`;
}
return out || stdinData;
}
case "echo":
return args.slice(1).join(" ") + "\n";
case "seq": {
const nums = fileArgs.map(Number);
let start = 1, end = 1, step = 1;
if (nums.length === 1) end = nums[0];
else if (nums.length === 2) { start = nums[0]; end = nums[1]; }
else if (nums.length === 3) { start = nums[0]; step = nums[1]; end = nums[2]; }
const result = [];
for (let i = start; step > 0 ? i <= end : i >= end; i += step) result.push(String(i));
return result.join("\n") + "\n";
}
case "factor": {
const lines = input ? input.trimEnd().split("\n") : fileArgs;
return lines.map((n) => {
n = parseInt(n.toString().trim());
if (isNaN(n) || n < 2) return `${n}:`;
const factors = [];
let x = n;
for (let d = 2; d * d <= x; d++) {
while (x % d === 0) { factors.push(d); x /= d; }
}
if (x > 1) factors.push(x);
return `${n}: ${factors.join(" ")}`;
}).join("\n") + "\n";
}
case "basename": {
if (fileArgs.length > 0) {
const parts = fileArgs[0].split("/");
return parts[parts.length - 1] + "\n";
}
return "\n";
}
case "wc": {
const lines = input.trimEnd().split("\n");
const allFlags = flags.join(" ");
const showL = allFlags.includes("-l");
const showW = allFlags.includes("-w");
const showC = allFlags.includes("-c");
const showAll = !showL && !showW && !showC;
const parts = [];
if (showL || showAll) parts.push(lines.length);
if (showW || showAll) parts.push(input.trim().split(/\s+/).length);
if (showC || showAll) parts.push(new TextEncoder().encode(input).length);
return (showAll ? " " : "") + parts.join(" ") + "\n";
}
default:
if (AVAILABLE_COMMANDS.includes(cmd)) {
return `(WASM binary loading... JS fallback: '${cmd}' not yet implemented)\n`;
}
return null;
}
}
function writeToTerminal(text) {
if (!terminal) return;
@@ -890,9 +798,8 @@ async function initPlayground(containerId) {
terminal.writeln("Sample data files: names.txt, numbers.txt, fruits.txt, csv.txt, words.txt");
} catch (e) {
terminal.writeln(" \x1b[1;31mfailed\x1b[0m");
terminal.writeln("Using JS fallback. Some commands may be limited.");
terminal.writeln("");
terminal.writeln("Type \x1b[1;32mhelp\x1b[0m for available commands.");
terminal.writeln("Failed to load WASM binary. Commands are not available.");
terminal.writeln("Try reloading the page.");
}
// Run command(s) from URL ?cmd= parameter if present
@@ -939,7 +846,6 @@ window.setLocale = setLocale;
// Expose internals for testing
window._uutilsTestInternals = {
parseCommandLine,
jsFallback,
executeCommandLine,
resolvePath,
lookupDir,
+1 -104
View File
@@ -20,7 +20,7 @@
<script>
(async function() {
const T = window._uutilsTestInternals;
const { parseCommandLine, jsFallback, executeCommandLine, resolvePath, lookupDir, getPersistentDir, readVirtualFile, writeVirtualFile, SAMPLE_FILES, AVAILABLE_COMMANDS } = T;
const { parseCommandLine, executeCommandLine, resolvePath, lookupDir, getPersistentDir, readVirtualFile, writeVirtualFile, SAMPLE_FILES, AVAILABLE_COMMANDS } = T;
let passed = 0;
let failed = 0;
@@ -142,109 +142,6 @@ async function runTests() {
assert("pipe + redirect: stdout", p[1].stdout, "out.txt");
assertDeep("pipe + redirect: second args", p[1].args, ["head", "-3"]);
// ===== jsFallback: echo =====
section("jsFallback: echo");
assert("echo simple",
jsFallback(["echo", "hello"], ""),
"hello\n");
assert("echo multiple words",
jsFallback(["echo", "hello", "world"], ""),
"hello world\n");
assert("echo empty",
jsFallback(["echo"], ""),
"\n");
// ===== jsFallback: cat =====
section("jsFallback: cat");
assert("cat existing file",
jsFallback(["cat", "names.txt"], ""),
SAMPLE_FILES["names.txt"]);
assert("cat missing file",
jsFallback(["cat", "nope.txt"], ""),
"cat: nope.txt: No such file or directory\n");
assert("cat with stdin passthrough",
jsFallback(["cat"], "piped input"),
"piped input");
// ===== jsFallback: ls =====
section("jsFallback: ls");
assert("ls in AVAILABLE_COMMANDS (WASM)",
AVAILABLE_COMMANDS.includes("ls"), true);
assert("ls fallback still works",
jsFallback(["ls"], ""),
Object.keys(SAMPLE_FILES).join(" ") + "\n");
// ===== jsFallback: seq =====
section("jsFallback: seq");
assert("seq 1 to 5",
jsFallback(["seq", "5"], ""),
"1\n2\n3\n4\n5\n");
assert("seq 3 to 6",
jsFallback(["seq", "3", "6"], ""),
"3\n4\n5\n6\n");
assert("seq with step",
jsFallback(["seq", "1", "2", "7"], ""),
"1\n3\n5\n7\n");
// ===== jsFallback: factor =====
section("jsFallback: factor");
assert("factor 12",
jsFallback(["factor"], "12"),
"12: 2 2 3\n");
assert("factor prime",
jsFallback(["factor"], "17"),
"17: 17\n");
assert("factor multiple lines",
jsFallback(["factor"], "6\n10"),
"6: 2 3\n10: 2 5\n");
// ===== jsFallback: basename =====
section("jsFallback: basename");
assert("basename path",
jsFallback(["basename", "/usr/local/bin/rustc"], ""),
"rustc\n");
assert("basename simple",
jsFallback(["basename", "file.txt"], ""),
"file.txt\n");
// ===== jsFallback: wc =====
section("jsFallback: wc");
assert("wc -l",
jsFallback(["wc", "-l", "names.txt"], ""),
"8\n");
assert("wc -w",
jsFallback(["wc", "-w", "names.txt"], ""),
"8\n");
// ===== jsFallback: unknown command =====
section("jsFallback: unknown");
assert("unknown command returns null",
jsFallback(["nonexistent"], ""),
null);
assert("WASM command without WASM returns loading message",
jsFallback(["date"], "").includes("WASM binary loading"),
true);
// ===== executeCommandLine: builtins =====
section("executeCommandLine: builtins");