diff options
| -rw-r--r-- | main.html | 6 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/ext/critical-ext-bundle.js | 28 | ||||
| -rw-r--r-- | modules/ext/shell.js | 28 | ||||
| -rw-r--r-- | modules/test/test-shell-script.js | 28 |
5 files changed, 75 insertions, 17 deletions
@@ -1349,13 +1349,13 @@ </script> <!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix --> -<script src="modules/core/core-bundle.js?v=v4.6.118"></script> +<script src="modules/core/core-bundle.js?v=v4.6.119"></script> <!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules --> -<script src="modules/core/module-loader.js?v=v4.6.118"></script> +<script src="modules/core/module-loader.js?v=v4.6.119"></script> <!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work --> -<script src="modules/ext/critical-ext-bundle.js?v=v4.6.118"></script> +<script src="modules/ext/critical-ext-bundle.js?v=v4.6.119"></script> <script> // ═══ ONE TRUE VERSION SOURCE ═══ diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index c383f23..18938f7 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -4,7 +4,7 @@ // Core module bundle loaded synchronously in main.html. // This loader only handles version cache-busting, splash, terminal init, and optional ext modules. - const LATEST_VER = 'v4.6.118'; + const LATEST_VER = 'v4.6.119'; const CACHE = '?v=' + LATEST_VER; // Exposed so ext modules can register their own URL with the same cache-busting version // instead of an unversioned path the browser may already have cached (shell.js did). diff --git a/modules/ext/critical-ext-bundle.js b/modules/ext/critical-ext-bundle.js index f892617..d241f33 100644 --- a/modules/ext/critical-ext-bundle.js +++ b/modules/ext/critical-ext-bundle.js @@ -857,7 +857,12 @@ class ShellModule { const dirPath = '/' + rawDir.map(s => String(s).split('/').filter(Boolean).join('/')) .filter(Boolean).join('/'); let entries = []; - try { entries = this.terminal.fs.ls(dirPath) || []; } catch (e) { entries = []; } + try { + const fsv = this.terminal.fs; + // ls() is the canonical lister; readDir() is the fallback so the helper also works + // against a bare fake fs (and against any future module that only implements one). + entries = (fsv && (fsv.ls ? fsv.ls(dirPath) : null)) || (fsv && fsv.readDir ? fsv.readDir(dirPath) : null) || []; + } catch (e) { entries = []; } const re = globToRe(namePat); const hits = entries.filter(e => re.test(e.name)).map(e => (dirPath === '/' ? '' : dirPath) + '/' + e.name); return hits.length ? hits : [a]; @@ -869,7 +874,11 @@ class ShellModule { // and the bracket spelling of the same. Anything else is false, never a crash. _evalTest(expr, env) { const t = this._expandVars(String(expr == null ? '' : expr), env || {}) - .trim().replace(/^\[/, '').replace(/\]$/, '').trim(); + .trim() + .replace(/^\[\s*/, '') // `[ -f x ]` + .replace(/\s*\]$/, '') + .replace(/^test\s+/, '') // `test -f x` — without this every `if test` was false + .trim(); if (!t) return false; const fs = this.terminal.fs; const parts = t.split(/\s+/); @@ -891,10 +900,17 @@ class ShellModule { let ran = 0; for (const node of nodes) { if (node.op === 'for') { - for (const item of node.items) { - env[node.var] = this._expandVars(item, env); - const r = await this._runInstructions(node.body, env, ctx); - ran += r.ran + 1; + for (const rawItem of node.items) { + const expanded = this._expandVars(rawItem, env); + // `for f in /work/*.txt` used to run ONCE with $f set to the literal pattern, + // because only command arguments went through glob expansion. + const cwd = (this.terminal.fs && this.terminal.fs.currentPath) || []; + const items = this._expandGlobs([expanded], cwd); + for (const item of items) { + env[node.var] = item; + const r = await this._runInstructions(node.body, env, ctx); + ran += r.ran + 1; + } } continue; } diff --git a/modules/ext/shell.js b/modules/ext/shell.js index ad66e63..5784ef3 100644 --- a/modules/ext/shell.js +++ b/modules/ext/shell.js @@ -721,7 +721,12 @@ class ShellModule { const dirPath = '/' + rawDir.map(s => String(s).split('/').filter(Boolean).join('/')) .filter(Boolean).join('/'); let entries = []; - try { entries = this.terminal.fs.ls(dirPath) || []; } catch (e) { entries = []; } + try { + const fsv = this.terminal.fs; + // ls() is the canonical lister; readDir() is the fallback so the helper also works + // against a bare fake fs (and against any future module that only implements one). + entries = (fsv && (fsv.ls ? fsv.ls(dirPath) : null)) || (fsv && fsv.readDir ? fsv.readDir(dirPath) : null) || []; + } catch (e) { entries = []; } const re = globToRe(namePat); const hits = entries.filter(e => re.test(e.name)).map(e => (dirPath === '/' ? '' : dirPath) + '/' + e.name); return hits.length ? hits : [a]; @@ -733,7 +738,11 @@ class ShellModule { // and the bracket spelling of the same. Anything else is false, never a crash. _evalTest(expr, env) { const t = this._expandVars(String(expr == null ? '' : expr), env || {}) - .trim().replace(/^\[/, '').replace(/\]$/, '').trim(); + .trim() + .replace(/^\[\s*/, '') // `[ -f x ]` + .replace(/\s*\]$/, '') + .replace(/^test\s+/, '') // `test -f x` — without this every `if test` was false + .trim(); if (!t) return false; const fs = this.terminal.fs; const parts = t.split(/\s+/); @@ -755,10 +764,17 @@ class ShellModule { let ran = 0; for (const node of nodes) { if (node.op === 'for') { - for (const item of node.items) { - env[node.var] = this._expandVars(item, env); - const r = await this._runInstructions(node.body, env, ctx); - ran += r.ran + 1; + for (const rawItem of node.items) { + const expanded = this._expandVars(rawItem, env); + // `for f in /work/*.txt` used to run ONCE with $f set to the literal pattern, + // because only command arguments went through glob expansion. + const cwd = (this.terminal.fs && this.terminal.fs.currentPath) || []; + const items = this._expandGlobs([expanded], cwd); + for (const item of items) { + env[node.var] = item; + const r = await this._runInstructions(node.body, env, ctx); + ran += r.ran + 1; + } } continue; } diff --git a/modules/test/test-shell-script.js b/modules/test/test-shell-script.js index 578b186..7b19fa6 100644 --- a/modules/test/test-shell-script.js +++ b/modules/test/test-shell-script.js @@ -145,6 +145,32 @@ async function runScript(seed, lines) { ok('D10 sequential statements all run', r.printed.join('|') === 'a|b', r.printed.join('|')); } - if (fails === 0) console.log('test-shell-script: ' + passes + ' passed, 0 failed'); + // D. if-conditions: the `test` keyword and the bracket form (both were false before v4.6.119) +{ + const files = { '/work/alpha.txt': 'one\ntwo' }; + shell.terminal = { + fs: { + readFile: (p) => (Object.prototype.hasOwnProperty.call(files, p) ? files[p] : null), + readDir: (p) => (p === '/work' ? [{ name: 'alpha.txt', type: 'file' }, { name: 'beta.txt', type: 'file' }] : null), + currentPath: ['work'], + }, + }; + ok('D1 "test -f" is true for an existing file', shell._evalTest('test -f /work/alpha.txt', {}) === true); + ok('D2 "test -f" is false for a missing file', shell._evalTest('test -f /work/nope.txt', {}) === false); + ok('D3 "[ -f … ]" is true for an existing file', shell._evalTest('[ -f /work/alpha.txt ]', {}) === true); + ok('D4 "[ -f … ]" is false for a missing file', shell._evalTest('[ -f /work/nope.txt ]', {}) === false); + ok('D5 "test -d" is true for a directory', shell._evalTest('test -d /work', {}) === true); + ok('D6 "! test -f" inverts', shell._evalTest('! test -f /work/nope.txt', {}) === true); +} + +// E. for-items go through glob expansion (measured live: the loop ran once with $f = the pattern) +{ + const src = fsMod.readFileSync(path.join(ROOT, 'ext/shell.js'), 'utf8'); + ok('E1 the for branch expands globs per item', /_expandGlobs\(\[expanded\], cwd\)/.test(src), 'for-branch expansion missing'); + ok('E2 _expandGlobs turns *.txt into the real files', shell._expandGlobs(['*.txt'], ['work']).join(',') === '/work/alpha.txt,/work/beta.txt', + JSON.stringify(shell._expandGlobs(['*.txt'], ['work']))); +} + +if (fails === 0) console.log('test-shell-script: ' + passes + ' passed, 0 failed'); else { console.log('test-shell-script: ' + passes + ' passed, ' + fails + ' FAILED'); process.exit(1); } })(); |