@@ -14,9 +14,14 @@ if (typeof SharedArrayBuffer === "undefined") {
1414}
1515
1616const WASM_URL = "/wasm/uutils.wasm" ;
17- // grep ships as its own standalone WASM module (it is not part of the
18- // coreutils multicall binary), loaded lazily alongside it.
19- const GREP_WASM_URL = "/wasm/grep.wasm" ;
17+ // Some utilities ship as their own standalone WASM modules rather than as part
18+ // of the coreutils multicall binary (grep lives in uutils/grep, find in
19+ // uutils/findutils). They are loaded lazily alongside the multicall module and
20+ // each is optional — see loadStandaloneWasm.
21+ const STANDALONE_WASM_URLS = {
22+ grep : "/wasm/grep.wasm" ,
23+ find : "/wasm/find.wasm" ,
24+ } ;
2025const XTERM_CSS = "https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css" ;
2126const XTERM_CSS_INTEGRITY = "sha384-tStR1zLfWgsiXCF3IgfB3lBa8KmBe/lG287CL9WCeKgQYcp1bjb4/+mwN6oti4Co" ;
2227const XTERM_JS = "https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js" ;
@@ -34,6 +39,11 @@ const SAMPLE_FILES = {
3439 "fruits.txt" : "banana\napple\ncherry\ndate\napple\nbanana\ncherry\napple\n" ,
3540 "csv.txt" : "name,age,city\nAlice,30,Paris\nBob,25,London\nCharlie,35,Berlin\nDiana,28,Tokyo\n" ,
3641 "words.txt" : "hello world\nfoo bar baz\nthe quick brown fox\njumps over the lazy dog\n" ,
42+ // Emoji-named files so `find` has something fun (and Unicode!) to match.
43+ "🍎.md" : "# Apple\n" ,
44+ "🍌.md" : "# Banana\n" ,
45+ "🍒.md" : "# Cherry\n" ,
46+ "🥝.md" : "# Kiwi\n" ,
3747} ;
3848
3949// Commands available in the feat_wasm build.
@@ -51,7 +61,7 @@ const FALLBACK_COMMANDS = [
5161 "sha1sum" , "sha224sum" , "sha256sum" , "sha384sum" , "sha512sum" ,
5262 "shred" , "shuf" , "sleep" , "sum" , "tee" , "true" , "truncate" ,
5363 "uname" , "unexpand" , "uniq" , "unlink" , "vdir" , "wc" ,
54- "grep" ,
64+ "grep" , "find" ,
5565] ;
5666const AVAILABLE_COMMANDS =
5767 ( typeof WASM_COMMANDS !== "undefined" && Array . isArray ( WASM_COMMANDS ) && WASM_COMMANDS . length > 0 )
@@ -67,7 +77,9 @@ const LOCALE_SHORTCUTS = {
6777} ;
6878
6979let wasmModule = null ;
70- let grepModule = null ; // standalone grep WASM module (null if unavailable)
80+ // Compiled standalone modules, keyed by command name (e.g. "grep", "find").
81+ // A key is present only once its module has loaded successfully.
82+ const standaloneModules = { } ;
7183let wasiShim = null ;
7284let terminal = null ;
7385let inputBuffer = "" ;
@@ -155,29 +167,32 @@ async function loadWasm() {
155167}
156168
157169/**
158- * Load the standalone grep WASM module. grep is optional: if the binary isn't
159- * present (e.g. local dev without a CI build), this resolves to null and grep
160- * commands report that they're unavailable rather than breaking the terminal.
170+ * Load the optional standalone WASM modules (grep, find, …). Each is optional:
171+ * if a binary isn't present (e.g. local dev without a CI build), its error is
172+ * swallowed and the corresponding command reports that it's unavailable rather
173+ * than breaking the terminal or blocking the coreutils module.
161174 */
162- async function loadGrepWasm ( ) {
163- if ( grepModule ) return grepModule ;
164- try {
165- const { module, size } = await compileWasmModule ( GREP_WASM_URL ) ;
166- grepModule = module ;
167- wasmSize += size ;
168- } catch ( e ) {
169- console . warn ( "grep WASM unavailable:" , e . message ) ;
170- grepModule = null ;
171- }
172- return grepModule ;
175+ async function loadStandaloneWasm ( ) {
176+ await Promise . all (
177+ Object . entries ( STANDALONE_WASM_URLS ) . map ( async ( [ cmd , url ] ) => {
178+ if ( standaloneModules [ cmd ] ) return ;
179+ try {
180+ const { module, size } = await compileWasmModule ( url ) ;
181+ standaloneModules [ cmd ] = module ;
182+ wasmSize += size ;
183+ } catch ( e ) {
184+ console . warn ( `${ cmd } WASM unavailable:` , e . message ) ;
185+ }
186+ } )
187+ ) ;
173188}
174189
175190async function initWasm ( ) {
176191 if ( wasmReady ) return ;
177192 try {
178- // grep is optional and loadGrepWasm swallows its own errors, so it never
179- // blocks the coreutils module from becoming ready.
180- await Promise . all ( [ loadWasiShim ( ) , loadWasm ( ) , loadGrepWasm ( ) ] ) ;
193+ // The standalone modules are optional and loadStandaloneWasm swallows its
194+ // own errors, so they never block the coreutils module from becoming ready.
195+ await Promise . all ( [ loadWasiShim ( ) , loadWasm ( ) , loadStandaloneWasm ( ) ] ) ;
181196 wasmReady = true ;
182197 } catch ( e ) {
183198 // Will fall back to JS implementations
@@ -493,6 +508,7 @@ async function executeCommandLine(line) {
493508 " wc -l fruits.txt\n" +
494509 " seq 1 10 | factor\n" +
495510 " grep -i alice names.txt\n" +
511+ " find . -name '*.md'\n" +
496512 " basename /usr/local/bin/rustc\n" +
497513 " date\n" +
498514 " uname -a\n"
@@ -566,11 +582,11 @@ async function executeCommandLine(line) {
566582 return `uutils: command not found: ${ cmd } \nType 'help' for available commands.\n` ;
567583 }
568584
569- // grep is a separate WASM module rather than part of the coreutils
570- // multicall binary.
571- const isGrep = cmd === "grep" ;
572- if ( isGrep && ! grepModule ) {
573- return "grep is not available in this build.\n" ;
585+ // Some utilities ( grep, find) are separate WASM modules rather than part
586+ // of the coreutils multicall binary.
587+ const isStandalone = cmd in STANDALONE_WASM_URLS ;
588+ if ( isStandalone && ! standaloneModules [ cmd ] ) {
589+ return ` ${ cmd } is not available in this build.\n` ;
574590 }
575591
576592 try {
@@ -586,10 +602,18 @@ async function executeCommandLine(line) {
586602 if ( ! hasPathArg && cwd && [ "ls" , "dir" ] . includes ( cmd ) ) {
587603 resolvedArgs . push ( cwd ) ;
588604 }
589- // grep is invoked directly (argv[0] = "grep"); coreutils utilities go
590- // through the multicall dispatcher (argv = ["coreutils", <util>, ...]).
605+ // find takes its starting paths *before* the expression. When the user
606+ // gives none (e.g. `find -type f`), GNU find defaults to "."; mirror that
607+ // but use the virtual cwd so `cd subdir; find` searches the right place.
608+ if ( cmd === "find" ) {
609+ const hasStartPath = resolvedArgs . length > 1 && ! resolvedArgs [ 1 ] . startsWith ( "-" ) ;
610+ if ( ! hasStartPath ) resolvedArgs . splice ( 1 , 0 , cwd || "." ) ;
611+ }
612+ // Standalone utilities are invoked directly (argv[0] = the command name);
613+ // coreutils utilities go through the multicall dispatcher
614+ // (argv = ["coreutils", <util>, ...]).
591615 let dispatchArgs = resolvedArgs ;
592- if ( isGrep ) {
616+ if ( cmd === "grep" ) {
593617 // browser_wasi_shim reports stdout as a TTY, so grep would emit GNU
594618 // match-highlight escape codes by default. That looks fine in the
595619 // terminal but corrupts piped/redirected output (e.g. `grep x | wc`),
@@ -599,8 +623,8 @@ async function executeCommandLine(line) {
599623 ? resolvedArgs
600624 : [ resolvedArgs [ 0 ] , "--color=never" , ...resolvedArgs . slice ( 1 ) ] ;
601625 }
602- const wasmArgs = isGrep ? dispatchArgs : [ "coreutils" , ...resolvedArgs ] ;
603- const result = await runCommand ( wasmArgs , stdinData , isGrep ? grepModule : wasmModule ) ;
626+ const wasmArgs = isStandalone ? dispatchArgs : [ "coreutils" , ...resolvedArgs ] ;
627+ const result = await runCommand ( wasmArgs , stdinData , isStandalone ? standaloneModules [ cmd ] : wasmModule ) ;
604628 if ( result . stderr ) {
605629 return result . stderr + result . stdout ;
606630 }
@@ -924,7 +948,8 @@ window._uutilsTestInternals = {
924948 get locale ( ) { return currentLocale ; } ,
925949 set locale ( v ) { currentLocale = v ; } ,
926950 get wasmReady ( ) { return wasmReady ; } ,
927- get grepReady ( ) { return grepModule !== null ; } ,
951+ get grepReady ( ) { return ! ! standaloneModules . grep ; } ,
952+ get findReady ( ) { return ! ! standaloneModules . find ; } ,
928953 initWasm,
929954 LOCALE_SHORTCUTS ,
930955 SAMPLE_FILES ,
0 commit comments