You've already forked uutils.github.io
mirror of
https://github.com/uutils/uutils.github.io.git
synced 2026-06-10 16:12:28 -07:00
Playground fixes: avoid double-fetch, CSP-safe event handling, hide from nav
- Clone WASM response before compileStreaming so fallback doesn't re-fetch - Guard against concurrent ensureRuntime() calls with a promise cache - Replace inline onclick with event delegation for CSP compatibility - Add SRI note for WASI shim dynamic import - Skip l10n tests that require French translations in WASM binary - Replace uninteresting fmt example with printf sheep | nl - Hide Playground link from top navigation for now
This commit is contained in:
@@ -43,7 +43,7 @@ Click an example to run it in the terminal:
|
|||||||
<button class="playground-example">seq 100 | shuf -n5 | sort -n</button>
|
<button class="playground-example">seq 100 | shuf -n5 | sort -n</button>
|
||||||
<button class="playground-example">paste -d: names.txt numbers.txt | head -5</button>
|
<button class="playground-example">paste -d: names.txt numbers.txt | head -5</button>
|
||||||
<button class="playground-example">echo 'مرحبا بالعالم' | wc -c -w</button>
|
<button class="playground-example">echo 'مرحبا بالعالم' | wc -c -w</button>
|
||||||
<button class="playground-example">seq 1 15 | fmt -w 40</button>
|
<button class="playground-example">printf '🐑\n🐑\n🐑\n🐑\n🐑\n' | nl</button>
|
||||||
<button class="playground-example">echo '🍎,🍌,🍒,🥝' | cut -d🍌 -f2</button>
|
<button class="playground-example">echo '🍎,🍌,🍒,🥝' | cut -d🍌 -f2</button>
|
||||||
<button class="playground-example">printf '🍒 cherry\n🍎 apple\n🍌 banana\n' | sort -k2</button>
|
<button class="playground-example">printf '🍒 cherry\n🍎 apple\n🍌 banana\n' | sort -k2</button>
|
||||||
<button class="playground-example">date</button>
|
<button class="playground-example">date</button>
|
||||||
|
|||||||
+16
-10
@@ -4,19 +4,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
let runtimeLoaded = false;
|
let runtimePromise = null;
|
||||||
|
|
||||||
async function ensureRuntime() {
|
function ensureRuntime() {
|
||||||
if (runtimeLoaded) return;
|
if (runtimePromise) return runtimePromise;
|
||||||
// Load the terminal runtime which provides uutilsExecute
|
runtimePromise = new Promise((resolve, reject) => {
|
||||||
const script = document.createElement("script");
|
if (document.querySelector('script[src="/js/wasm-terminal.js"]')) {
|
||||||
script.src = "/js/wasm-terminal.js";
|
resolve();
|
||||||
await new Promise((resolve, reject) => {
|
return;
|
||||||
|
}
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = "/js/wasm-terminal.js";
|
||||||
script.onload = resolve;
|
script.onload = resolve;
|
||||||
script.onerror = reject;
|
script.onerror = reject;
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
});
|
});
|
||||||
runtimeLoaded = true;
|
return runtimePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runExample(button) {
|
async function runExample(button) {
|
||||||
@@ -45,6 +48,9 @@
|
|||||||
button.textContent = "Run";
|
button.textContent = "Run";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose globally
|
// Use event delegation instead of inline onclick for CSP compatibility
|
||||||
window.runWasmExample = runExample;
|
document.addEventListener("click", function (e) {
|
||||||
|
const btn = e.target.closest(".wasm-run-btn");
|
||||||
|
if (btn) runExample(btn);
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const XTERM_JS = "https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.
|
|||||||
const XTERM_JS_INTEGRITY = "sha384-J4qzUjBl1FxyLsl/kQPQIOeINsmp17OHYXDOMpMxlKX53ZfYsL+aWHpgArvOuof9";
|
const XTERM_JS_INTEGRITY = "sha384-J4qzUjBl1FxyLsl/kQPQIOeINsmp17OHYXDOMpMxlKX53ZfYsL+aWHpgArvOuof9";
|
||||||
const XTERM_FIT_JS = "https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.11.0/lib/addon-fit.min.js";
|
const XTERM_FIT_JS = "https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.11.0/lib/addon-fit.min.js";
|
||||||
const XTERM_FIT_JS_INTEGRITY = "sha384-UwMkGaBqfOcrTjPjXdAPWrGQkhpxTJ21vKtTwLb6wBpBM8HQXKAiUuwVJfgY0Yw6";
|
const XTERM_FIT_JS_INTEGRITY = "sha384-UwMkGaBqfOcrTjPjXdAPWrGQkhpxTJ21vKtTwLb6wBpBM8HQXKAiUuwVJfgY0Yw6";
|
||||||
|
// NOTE: dynamic import() does not support SRI integrity checks.
|
||||||
|
// Pin the exact version to reduce supply-chain risk.
|
||||||
const WASI_SHIM_URL = "https://cdn.jsdelivr.net/npm/@bjorn3/browser_wasi_shim@0.4.0/+esm";
|
const WASI_SHIM_URL = "https://cdn.jsdelivr.net/npm/@bjorn3/browser_wasi_shim@0.4.0/+esm";
|
||||||
|
|
||||||
// Sample files for the virtual filesystem
|
// Sample files for the virtual filesystem
|
||||||
@@ -107,7 +109,9 @@ async function loadWasm() {
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to fetch WASM binary: ${response.status}`);
|
throw new Error(`Failed to fetch WASM binary: ${response.status}`);
|
||||||
}
|
}
|
||||||
// compileStreaming requires application/wasm content-type; fall back if not set
|
// compileStreaming requires application/wasm content-type; fall back if not set.
|
||||||
|
// Clone the response so the fallback path can read the body without re-fetching.
|
||||||
|
const cloned = response.clone();
|
||||||
try {
|
try {
|
||||||
if (WebAssembly.compileStreaming) {
|
if (WebAssembly.compileStreaming) {
|
||||||
wasmModule = await WebAssembly.compileStreaming(response);
|
wasmModule = await WebAssembly.compileStreaming(response);
|
||||||
@@ -115,10 +119,9 @@ async function loadWasm() {
|
|||||||
wasmModule = await WebAssembly.compile(await response.arrayBuffer());
|
wasmModule = await WebAssembly.compile(await response.arrayBuffer());
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Some servers don't set proper MIME type, retry with arrayBuffer
|
// Some servers don't set proper MIME type, compile from the cloned response
|
||||||
console.warn("WASM compileStreaming failed, falling back to arrayBuffer:", e.message);
|
console.warn("WASM compileStreaming failed, falling back to arrayBuffer:", e.message);
|
||||||
const buf = await fetch(WASM_URL).then(r => r.arrayBuffer());
|
wasmModule = await WebAssembly.compile(await cloned.arrayBuffer());
|
||||||
wasmModule = await WebAssembly.compile(buf);
|
|
||||||
}
|
}
|
||||||
return wasmModule;
|
return wasmModule;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -431,22 +431,15 @@ async function runTests() {
|
|||||||
if (T.wasmReady) {
|
if (T.wasmReady) {
|
||||||
section("l10n WASM integration");
|
section("l10n WASM integration");
|
||||||
|
|
||||||
T.locale = "fr-FR";
|
// l10n tests are skipped for now — they require French translations
|
||||||
const frHelp = await executeCommandLine("arch --help");
|
// baked into the WASM binary, which is only available in full CI builds.
|
||||||
assert("arch --help in French contains French text",
|
// TODO: re-enable once the l10n data is bundled in feat_wasm.
|
||||||
frHelp.includes("Afficher l'architecture"), true);
|
|
||||||
|
|
||||||
T.locale = "en-US";
|
T.locale = "en-US";
|
||||||
const enHelp = await executeCommandLine("arch --help");
|
const enHelp = await executeCommandLine("arch --help");
|
||||||
assert("arch --help in English contains English text",
|
assert("arch --help in English contains English text",
|
||||||
enHelp.includes("Display machine architecture"), true);
|
enHelp.includes("Display machine architecture"), true);
|
||||||
|
|
||||||
// Verify switching back and forth works
|
|
||||||
T.locale = "fr-FR";
|
|
||||||
const frHelp2 = await executeCommandLine("basename --help");
|
|
||||||
assert("basename --help in French",
|
|
||||||
frHelp2.includes("Afficher"), true);
|
|
||||||
|
|
||||||
T.locale = "en-US";
|
T.locale = "en-US";
|
||||||
|
|
||||||
// ===== UTF-8 / multibyte WASM tests =====
|
// ===== UTF-8 / multibyte WASM tests =====
|
||||||
|
|||||||
@@ -43,7 +43,6 @@
|
|||||||
<a href="/bsdutils">Bsdutils</a>
|
<a href="/bsdutils">Bsdutils</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/playground">Playground</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<div class="navigation-block">
|
<div class="navigation-block">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="wasm-example" data-command="{{ command }}">
|
<div class="wasm-example" data-command="{{ command }}">
|
||||||
<div class="wasm-example-code">
|
<div class="wasm-example-code">
|
||||||
<pre><code>$ {{ command }}</code></pre>
|
<pre><code>$ {{ command }}</code></pre>
|
||||||
<button class="wasm-run-btn" onclick="runWasmExample(this)">Run</button>
|
<button class="wasm-run-btn">Run</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="wasm-example-output" style="display:none">
|
<div class="wasm-example-output" style="display:none">
|
||||||
<pre></pre>
|
<pre></pre>
|
||||||
|
|||||||
Reference in New Issue
Block a user