typescript-virtual-container 1.4.3 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +2 -1
- package/README.md +137 -19
- package/benchmark-results.txt +21 -21
- package/builds/self-standalone.js +322 -266
- package/builds/self-standalone.js.map +4 -4
- package/builds/standalone-wo-sftp.js +268 -212
- package/builds/standalone-wo-sftp.js.map +4 -4
- package/builds/standalone.js +268 -212
- package/builds/standalone.js.map +4 -4
- package/builds/web-full-api.min.js +5 -5
- package/builds/web-full-api.min.js.map +3 -3
- package/builds/web.min.js +5 -5
- package/builds/web.min.js.map +3 -3
- package/bun.lock +101 -6
- package/dist/Honeypot/index.js +1 -0
- package/dist/Honeypot/index.js.map +1 -0
- package/dist/SSHClient/index.js +1 -0
- package/dist/SSHClient/index.js.map +1 -0
- package/dist/SSHMimic/exec.js +1 -0
- package/dist/SSHMimic/exec.js.map +1 -0
- package/dist/SSHMimic/executor.js +1 -0
- package/dist/SSHMimic/executor.js.map +1 -0
- package/dist/SSHMimic/hostKey.js +1 -0
- package/dist/SSHMimic/hostKey.js.map +1 -0
- package/dist/SSHMimic/index.js +1 -0
- package/dist/SSHMimic/index.js.map +1 -0
- package/dist/SSHMimic/loginBanner.js +1 -0
- package/dist/SSHMimic/loginBanner.js.map +1 -0
- package/dist/SSHMimic/loginFormat.js +1 -0
- package/dist/SSHMimic/loginFormat.js.map +1 -0
- package/dist/SSHMimic/prompt.js +1 -0
- package/dist/SSHMimic/prompt.js.map +1 -0
- package/dist/SSHMimic/sftp.js +1 -0
- package/dist/SSHMimic/sftp.js.map +1 -0
- package/dist/VirtualFileSystem/binaryPack.js +1 -0
- package/dist/VirtualFileSystem/binaryPack.js.map +1 -0
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +1 -0
- package/dist/VirtualFileSystem/index.js.map +1 -0
- package/dist/VirtualFileSystem/internalTypes.js +1 -0
- package/dist/VirtualFileSystem/internalTypes.js.map +1 -0
- package/dist/VirtualFileSystem/path.js +1 -0
- package/dist/VirtualFileSystem/path.js.map +1 -0
- package/dist/VirtualPackageManager/index.js +1 -0
- package/dist/VirtualPackageManager/index.js.map +1 -0
- package/dist/VirtualShell/index.js +1 -0
- package/dist/VirtualShell/index.js.map +1 -0
- package/dist/VirtualShell/shell.js +1 -0
- package/dist/VirtualShell/shell.js.map +1 -0
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualShell/shellParser.js +3 -1
- package/dist/VirtualShell/shellParser.js.map +1 -0
- package/dist/VirtualUserManager/index.js +1 -0
- package/dist/VirtualUserManager/index.js.map +1 -0
- package/dist/commands/adduser.js +1 -0
- package/dist/commands/adduser.js.map +1 -0
- package/dist/commands/alias.js +1 -0
- package/dist/commands/alias.js.map +1 -0
- package/dist/commands/apt.js +1 -0
- package/dist/commands/apt.js.map +1 -0
- package/dist/commands/awk.d.ts.map +1 -1
- package/dist/commands/awk.js +2 -2
- package/dist/commands/awk.js.map +1 -0
- package/dist/commands/base64.js +1 -0
- package/dist/commands/base64.js.map +1 -0
- package/dist/commands/cat.js +1 -0
- package/dist/commands/cat.js.map +1 -0
- package/dist/commands/cd.js +3 -2
- package/dist/commands/cd.js.map +1 -0
- package/dist/commands/chmod.js +1 -0
- package/dist/commands/chmod.js.map +1 -0
- package/dist/commands/clear.js +1 -0
- package/dist/commands/clear.js.map +1 -0
- package/dist/commands/command-helpers.js +1 -0
- package/dist/commands/command-helpers.js.map +1 -0
- package/dist/commands/cp.js +1 -0
- package/dist/commands/cp.js.map +1 -0
- package/dist/commands/curl.js +1 -0
- package/dist/commands/curl.js.map +1 -0
- package/dist/commands/cut.js +1 -0
- package/dist/commands/cut.js.map +1 -0
- package/dist/commands/date.js +1 -0
- package/dist/commands/date.js.map +1 -0
- package/dist/commands/declare.js +1 -0
- package/dist/commands/declare.js.map +1 -0
- package/dist/commands/deluser.js +1 -0
- package/dist/commands/deluser.js.map +1 -0
- package/dist/commands/df.js +1 -0
- package/dist/commands/df.js.map +1 -0
- package/dist/commands/diff.js +1 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/dpkg.js +1 -0
- package/dist/commands/dpkg.js.map +1 -0
- package/dist/commands/du.js +1 -0
- package/dist/commands/du.js.map +1 -0
- package/dist/commands/echo.js +1 -0
- package/dist/commands/echo.js.map +1 -0
- package/dist/commands/env.js +1 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/exit.js +1 -0
- package/dist/commands/exit.js.map +1 -0
- package/dist/commands/export.js +1 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/find.js +1 -0
- package/dist/commands/find.js.map +1 -0
- package/dist/commands/free.js +1 -0
- package/dist/commands/free.js.map +1 -0
- package/dist/commands/grep.js +1 -0
- package/dist/commands/grep.js.map +1 -0
- package/dist/commands/groups.js +1 -0
- package/dist/commands/groups.js.map +1 -0
- package/dist/commands/gzip.js +1 -0
- package/dist/commands/gzip.js.map +1 -0
- package/dist/commands/head.js +1 -0
- package/dist/commands/head.js.map +1 -0
- package/dist/commands/help.js +1 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/helpers.d.ts.map +1 -1
- package/dist/commands/helpers.js +4 -0
- package/dist/commands/helpers.js.map +1 -0
- package/dist/commands/history.js +1 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/hostname.js +1 -0
- package/dist/commands/hostname.js.map +1 -0
- package/dist/commands/htop.js +1 -0
- package/dist/commands/htop.js.map +1 -0
- package/dist/commands/id.js +1 -0
- package/dist/commands/id.js.map +1 -0
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/kill.js +1 -0
- package/dist/commands/kill.js.map +1 -0
- package/dist/commands/ln.js +1 -0
- package/dist/commands/ln.js.map +1 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +171 -37
- package/dist/commands/ls.js.map +1 -0
- package/dist/commands/lsb-release.js +1 -0
- package/dist/commands/lsb-release.js.map +1 -0
- package/dist/commands/man.js +1 -0
- package/dist/commands/man.js.map +1 -0
- package/dist/commands/mkdir.js +1 -0
- package/dist/commands/mkdir.js.map +1 -0
- package/dist/commands/mv.js +1 -0
- package/dist/commands/mv.js.map +1 -0
- package/dist/commands/nano.js +1 -0
- package/dist/commands/nano.js.map +1 -0
- package/dist/commands/neofetch.js +1 -0
- package/dist/commands/neofetch.js.map +1 -0
- package/dist/commands/node.js +1 -0
- package/dist/commands/node.js.map +1 -0
- package/dist/commands/npm.js +1 -0
- package/dist/commands/npm.js.map +1 -0
- package/dist/commands/passwd.js +1 -0
- package/dist/commands/passwd.js.map +1 -0
- package/dist/commands/ping.js +1 -0
- package/dist/commands/ping.js.map +1 -0
- package/dist/commands/printf.js +1 -0
- package/dist/commands/printf.js.map +1 -0
- package/dist/commands/ps.js +1 -0
- package/dist/commands/ps.js.map +1 -0
- package/dist/commands/pwd.js +1 -0
- package/dist/commands/pwd.js.map +1 -0
- package/dist/commands/python.js +1 -0
- package/dist/commands/python.js.map +1 -0
- package/dist/commands/read.js +1 -0
- package/dist/commands/read.js.map +1 -0
- package/dist/commands/registry.js +1 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/rm.js +1 -0
- package/dist/commands/rm.js.map +1 -0
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +37 -0
- package/dist/commands/runtime.js.map +1 -0
- package/dist/commands/sed.js +1 -0
- package/dist/commands/sed.js.map +1 -0
- package/dist/commands/seq.js +1 -0
- package/dist/commands/seq.js.map +1 -0
- package/dist/commands/set.js +1 -0
- package/dist/commands/set.js.map +1 -0
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +9 -4
- package/dist/commands/sh.js.map +1 -0
- package/dist/commands/shift.js +1 -0
- package/dist/commands/shift.js.map +1 -0
- package/dist/commands/sleep.js +1 -0
- package/dist/commands/sleep.js.map +1 -0
- package/dist/commands/sort.js +1 -0
- package/dist/commands/sort.js.map +1 -0
- package/dist/commands/source.js +1 -0
- package/dist/commands/source.js.map +1 -0
- package/dist/commands/stat.js +1 -0
- package/dist/commands/stat.js.map +1 -0
- package/dist/commands/su.js +1 -0
- package/dist/commands/su.js.map +1 -0
- package/dist/commands/sudo.js +1 -0
- package/dist/commands/sudo.js.map +1 -0
- package/dist/commands/tail.js +1 -0
- package/dist/commands/tail.js.map +1 -0
- package/dist/commands/tar.js +1 -0
- package/dist/commands/tar.js.map +1 -0
- package/dist/commands/tee.js +1 -0
- package/dist/commands/tee.js.map +1 -0
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +1 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/touch.js +1 -0
- package/dist/commands/touch.js.map +1 -0
- package/dist/commands/tr.js +1 -0
- package/dist/commands/tr.js.map +1 -0
- package/dist/commands/tree.js +1 -0
- package/dist/commands/tree.js.map +1 -0
- package/dist/commands/true.js +1 -0
- package/dist/commands/true.js.map +1 -0
- package/dist/commands/type.js +1 -0
- package/dist/commands/type.js.map +1 -0
- package/dist/commands/uname.js +1 -0
- package/dist/commands/uname.js.map +1 -0
- package/dist/commands/uniq.js +1 -0
- package/dist/commands/uniq.js.map +1 -0
- package/dist/commands/unset.js +1 -0
- package/dist/commands/unset.js.map +1 -0
- package/dist/commands/uptime.js +1 -0
- package/dist/commands/uptime.js.map +1 -0
- package/dist/commands/wc.js +2 -1
- package/dist/commands/wc.js.map +1 -0
- package/dist/commands/wget.js +1 -0
- package/dist/commands/wget.js.map +1 -0
- package/dist/commands/which.js +1 -0
- package/dist/commands/which.js.map +1 -0
- package/dist/commands/who.js +1 -0
- package/dist/commands/who.js.map +1 -0
- package/dist/commands/whoami.js +1 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/commands/xargs.js +1 -0
- package/dist/commands/xargs.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/linuxRootfs.d.ts +35 -17
- package/dist/modules/linuxRootfs.d.ts.map +1 -1
- package/dist/modules/linuxRootfs.js +332 -152
- package/dist/modules/linuxRootfs.js.map +1 -0
- package/dist/modules/neofetch.js +1 -0
- package/dist/modules/neofetch.js.map +1 -0
- package/dist/modules/shellInteractive.js +1 -0
- package/dist/modules/shellInteractive.js.map +1 -0
- package/dist/modules/shellRuntime.js +1 -0
- package/dist/modules/shellRuntime.js.map +1 -0
- package/dist/self-standalone.js +1 -0
- package/dist/self-standalone.js.map +1 -0
- package/dist/standalone-wo-sftp.js +1 -0
- package/dist/standalone-wo-sftp.js.map +1 -0
- package/dist/standalone.js +1 -0
- package/dist/standalone.js.map +1 -0
- package/dist/types/commands.d.ts +1 -1
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/commands.js +1 -0
- package/dist/types/commands.js.map +1 -0
- package/dist/types/pipeline.js +1 -0
- package/dist/types/pipeline.js.map +1 -0
- package/dist/types/streams.js +1 -0
- package/dist/types/streams.js.map +1 -0
- package/dist/types/vfs.js +1 -0
- package/dist/types/vfs.js.map +1 -0
- package/dist/utils/expand.d.ts +2 -2
- package/dist/utils/expand.d.ts.map +1 -1
- package/dist/utils/expand.js +336 -124
- package/dist/utils/expand.js.map +1 -0
- package/dist/utils/perfLogger.js +1 -0
- package/dist/utils/perfLogger.js.map +1 -0
- package/dist/utils/tokenize.js +1 -0
- package/dist/utils/tokenize.js.map +1 -0
- package/dist/utils/vfsDiff.js +1 -0
- package/dist/utils/vfsDiff.js.map +1 -0
- package/dist/web-api.js +1 -0
- package/dist/web-api.js.map +1 -0
- package/dist/web-full.js +1 -0
- package/dist/web-full.js.map +1 -0
- package/dist/web.js +1 -0
- package/dist/web.js.map +1 -0
- package/docs/.nojekyll +1 -0
- package/docs/assets/hierarchy.js +1 -0
- package/docs/assets/highlight.css +162 -0
- package/docs/assets/icons.js +18 -0
- package/docs/assets/icons.svg +1 -0
- package/docs/assets/main.js +60 -0
- package/docs/assets/navigation.js +1 -0
- package/docs/assets/search.js +1 -0
- package/docs/assets/style.css +1633 -0
- package/docs/classes/HoneyPot.html +31 -0
- package/docs/classes/SshClient.html +66 -0
- package/docs/classes/VirtualFileSystem.html +262 -0
- package/docs/classes/VirtualPackageManager.html +63 -0
- package/docs/classes/VirtualSftpServer.html +169 -0
- package/docs/classes/VirtualShell.html +265 -0
- package/docs/classes/VirtualSshServer.html +177 -0
- package/docs/classes/VirtualUserManager.html +276 -0
- package/docs/docs/.nojekyll +1 -0
- package/docs/docs/assets/hierarchy.js +1 -0
- package/docs/docs/assets/highlight.css +162 -0
- package/docs/docs/assets/icons.js +18 -0
- package/docs/docs/assets/icons.svg +1 -0
- package/docs/docs/assets/main.js +60 -0
- package/docs/docs/assets/navigation.js +1 -0
- package/docs/docs/assets/search.js +1 -0
- package/docs/docs/assets/style.css +1633 -0
- package/docs/docs/hierarchy.html +1 -0
- package/docs/docs/index.html +1842 -0
- package/docs/docs/media/LICENSE +21 -0
- package/docs/docs/modules.html +1 -0
- package/docs/functions/assertDiff.html +6 -0
- package/docs/functions/diffSnapshots.html +7 -0
- package/docs/functions/formatDiff.html +6 -0
- package/docs/functions/getArg.html +13 -0
- package/docs/functions/getFlag.html +15 -0
- package/docs/functions/ifFlag.html +11 -0
- package/docs/hierarchy.html +1 -0
- package/docs/index.html +1842 -0
- package/docs/interfaces/AuditLogEntry.html +6 -0
- package/docs/interfaces/CommandContext.html +22 -0
- package/docs/interfaces/CommandResult.html +26 -0
- package/docs/interfaces/ExecStream.html +11 -0
- package/docs/interfaces/HoneyPotStats.html +14 -0
- package/docs/interfaces/InstalledPackage.html +20 -0
- package/docs/interfaces/NanoEditorSession.html +8 -0
- package/docs/interfaces/PackageDefinition.html +30 -0
- package/docs/interfaces/PackageFile.html +8 -0
- package/docs/interfaces/RemoveOptions.html +4 -0
- package/docs/interfaces/ShellEnv.html +6 -0
- package/docs/interfaces/ShellModule.html +14 -0
- package/docs/interfaces/ShellProperties.html +14 -0
- package/docs/interfaces/ShellStream.html +11 -0
- package/docs/interfaces/SudoChallenge.html +24 -0
- package/docs/interfaces/VfsBaseNode.html +12 -0
- package/docs/interfaces/VfsDiff.html +10 -0
- package/docs/interfaces/VfsDiffEntry.html +6 -0
- package/docs/interfaces/VfsDiffModified.html +10 -0
- package/docs/interfaces/VfsDirectoryNode.html +15 -0
- package/docs/interfaces/VfsFileNode.html +17 -0
- package/docs/interfaces/VfsOptions.html +12 -0
- package/docs/interfaces/VfsSnapshot.html +3 -0
- package/docs/interfaces/VfsSnapshotBaseNode.html +8 -0
- package/docs/interfaces/VfsSnapshotDirectoryNode.html +10 -0
- package/docs/interfaces/VfsSnapshotFileNode.html +12 -0
- package/docs/interfaces/WriteFileOptions.html +6 -0
- package/docs/media/LICENSE +21 -0
- package/docs/modules.html +1 -0
- package/docs/types/CommandMode.html +2 -0
- package/docs/types/CommandOutcome.html +2 -0
- package/docs/types/VfsNodeStats.html +2 -0
- package/docs/types/VfsNodeType.html +2 -0
- package/docs/types/VfsPersistenceMode.html +5 -0
- package/docs/types/VfsSnapshotNode.html +2 -0
- package/examples/web.min.js +5 -5
- package/package.json +7 -4
- package/src/VirtualFileSystem/index.ts +11 -9
- package/src/VirtualShell/shellParser.ts +3 -2
- package/src/bun.d.ts +1 -0
- package/src/commands/awk.ts +1 -2
- package/src/commands/cd.ts +2 -2
- package/src/commands/helpers.ts +3 -0
- package/src/commands/ls.ts +210 -41
- package/src/commands/runtime.ts +56 -3
- package/src/commands/sh.ts +7 -4
- package/src/commands/test.ts +4 -2
- package/src/commands/wc.ts +1 -1
- package/src/modules/linuxRootfs.ts +420 -231
- package/src/types/commands.ts +1 -1
- package/src/utils/expand.ts +256 -76
- package/tests/command-helpers.test.ts +80 -0
- package/tests/commands-admin-net.test.ts +441 -0
- package/tests/commands-advanced.test.ts +456 -0
- package/tests/commands-core.test.ts +562 -0
- package/tests/commands-missing.test.ts +570 -0
- package/tests/commands-specific-units.test.ts +327 -0
- package/tests/commands-text-sys.test.ts +445 -0
- package/tests/expand.test.ts +170 -0
- package/tests/helpers.test.ts +75 -0
- package/tests/test-helper.ts +79 -0
- package/tsconfig.json +3 -0
- package/typedoc.json +8 -0
- package/tests/bun-test-shim.ts +0 -9
package/examples/web.min.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
var k=Object.defineProperty;var O=(o,e,t)=>e in o?k(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var u=(o,e,t)=>O(o,typeof e!="symbol"?e+"":e,t);function x(o){let e=[],t="",r=!1,n="",s=0;for(;s<o.length;){let i=o[s],a=o[s+1];if((i==='"'||i==="'")&&!r){r=!0,n=i,s++;continue}if(r&&i===n){r=!1,n="",s++;continue}if(r){t+=i,s++;continue}if(i===" "){t&&(e.push(t),t=""),s++;continue}if((i===">"||i==="<")&&!r){t&&(e.push(t),t=""),i===">"&&a===">"?(e.push(">>"),s+=2):(e.push(i),s++);continue}t+=i,s++}return t&&e.push(t),e}function w(o){let e=o.trim();if(!e)return{statements:[],isValid:!0};try{return{statements:z(e),isValid:!0}}catch(t){return{statements:[],isValid:!1,error:t.message}}}function z(o){let e=A(o),t=[];for(let r of e){let s={pipeline:{commands:R(r.text.trim()),isValid:!0}};r.op&&(s.op=r.op),t.push(s)}return t}function A(o){let e=[],t="",r=0,n=!1,s="",i=0,a=c=>{t.trim()&&e.push({text:t,op:c}),t=""};for(;i<o.length;){let c=o[i],d=o.slice(i,i+2);if((c==='"'||c==="'")&&!n){n=!0,s=c,t+=c,i++;continue}if(n&&c===s){n=!1,t+=c,i++;continue}if(n){t+=c,i++;continue}if(c==="("){r++,t+=c,i++;continue}if(c===")"){r--,t+=c,i++;continue}if(r>0){t+=c,i++;continue}if(d==="&&"){a("&&"),i+=2;continue}if(d==="||"){a("||"),i+=2;continue}if(c===";"){a(";"),i++;continue}t+=c,i++}return a(),e}function R(o){return F(o).map(L)}function F(o){let e=[],t="",r=!1,n="";for(let i=0;i<o.length;i++){let a=o[i];if((a==='"'||a==="'")&&!r){r=!0,n=a,t+=a;continue}if(r&&a===n){r=!1,t+=a;continue}if(r){t+=a;continue}if(a==="|"&&o[i+1]!=="|"){if(!t.trim())throw new Error("Syntax error near unexpected token '|'");e.push(t.trim()),t=""}else t+=a}let s=t.trim();if(!s&&e.length>0)throw new Error("Syntax error near unexpected token '|'");return s&&e.push(s),e}function L(o){let e=x(o);if(e.length===0)return{name:"",args:[]};let t=[],r,n,s=!1,i=0;for(;i<e.length;){let c=e[i];if(c==="<"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after <");r=e[i],i++}else if(c===">>"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after >>");n=e[i],s=!0,i++}else if(c===">"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after >");n=e[i],s=!1,i++}else t.push(c),i++}return{name:(t[0]??"").toLowerCase(),args:t.slice(1),inputFile:r,outputFile:n,appendOutput:s}}function I(o,e){let t=o.replace(/\b([A-Za-z_][A-Za-z0-9_]*)\b/g,(r,n)=>{let s=e[n];return s!==void 0&&s!==""?s:"0"});if(!/^[\d\s+\-*/%()^!&|<>=,. ]+$/.test(t))return NaN;try{let r=Function(`"use strict"; return (${t.replace(/\*\*/g,"**")});`)();return typeof r=="number"?Math.trunc(r):NaN}catch{return NaN}}function M(o,e){let t=[],r=0;for(;r<o.length;){let n=o.indexOf("'",r);if(n===-1){t.push(e(o.slice(r)));break}t.push(e(o.slice(r,n)));let s=o.indexOf("'",n+1);if(s===-1){t.push(o.slice(n));break}t.push(o.slice(n,s+1)),r=s+1}return t.join("")}function p(o,e,t=0,r){let n=r??e.HOME??"/home/user";return M(o,s=>{let i=s;return i=i.replace(/(^|[\s:])~(\/|$)/g,(a,c,d)=>`${c}${n}${d}`),i=i.replace(/\$\?/g,String(t)),i=i.replace(/\$\$/g,"1"),i=i.replace(/\$#/g,"0"),i=i.replace(/\$\(\(([^)]+(?:\([^)]*\)[^)]*)*)\)\)/g,(a,c)=>{let d=I(c,e);return Number.isNaN(d)?"0":String(d)}),i=i.replace(/\$\{#([A-Za-z_][A-Za-z0-9_]*)\}/g,(a,c)=>String((e[c]??"").length)),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):-([^}]*)\}/g,(a,c,d)=>e[c]!==void 0&&e[c]!==""?e[c]:d),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):=([^}]*)\}/g,(a,c,d)=>((e[c]===void 0||e[c]==="")&&(e[c]=d),e[c])),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):\+([^}]*)\}/g,(a,c,d)=>e[c]!==void 0&&e[c]!==""?d:""),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,(a,c)=>e[c]??""),i=i.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g,(a,c)=>e[c]??""),i})}async function C(o,e,t,r){if(o.includes("$(")){let n="",s=!1,i=0;for(;i<o.length;){let a=o[i];if(a==="'"&&!s){s=!0,n+=a,i++;continue}if(a==="'"&&s){s=!1,n+=a,i++;continue}if(!s&&a==="$"&&o[i+1]==="("){if(o[i+2]==="("){n+=a,i++;continue}let c=0,d=i+1;for(;d<o.length;){if(o[d]==="(")c++;else if(o[d]===")"&&(c--,c===0))break;d++}let g=o.slice(i+2,d).trim(),P=(await r(g)).replace(/\n$/,"");n+=P,i=d+1;continue}n+=a,i++}o=n}return p(o,e,t)}var _=new TextEncoder,B=new TextDecoder;function j(o){let e="";for(let t of o)e+=String.fromCharCode(t);return btoa(e)}function N(o){let e=atob(o),t=new Uint8Array(e.length);for(let r=0;r<e.length;r+=1)t[r]=e.charCodeAt(r);return t}function l(o,e="/"){let r=(o.startsWith("/")?o:`${e}/${o}`).split("/"),n=[];for(let s of r)if(!(!s||s===".")){if(s===".."){n.pop();continue}n.push(s)}return`/${n.join("/")}`||"/"}function h(o){let e=l(o);if(e==="/")return"/";let t=e.split("/").filter(Boolean);return t.pop(),t.length>0?`/${t.join("/")}`:"/"}function f(o){let e=l(o);return e==="/"?"/":e.split("/").filter(Boolean).at(-1)??"/"}function D(o){return o.type==="file"?{...o,contentBase64:o.contentBase64}:{...o,children:o.children.map(e=>D(e))}}function W(o,e){let t=new Date().toISOString();return{type:"directory",name:o,mode:e,createdAt:t,updatedAt:t,children:[]}}function T(o,e,t){let r=new Date().toISOString();return{type:"file",name:o,mode:t,createdAt:r,updatedAt:r,contentBase64:j(e)}}function b(o,e){return o.children.find(t=>t.name===e)}function m(o,e){let t=o.children.findIndex(r=>r.name===e.name);if(t===-1){o.children.push(e);return}o.children[t]=e}function S(o,e){o.children=o.children.filter(t=>t.name!==e)}function $(o){return l(o).split("/").filter(Boolean)}var V=globalThis;function E(o){return new Promise((e,t)=>{o.addEventListener("success",()=>e(o.result)),o.addEventListener("error",()=>t(o.error))})}var y=class{constructor(e={}){u(this,"databaseName");u(this,"storeName");u(this,"key");u(this,"root");this.databaseName=e.databaseName??"typescript-virtual-container-web",this.storeName=e.storeName??"snapshots",this.key=e.key??"current",this.root=W("",493)}async openDatabase(){return new Promise((e,t)=>{let r=V.indexedDB;if(!r){t(new Error("IndexedDB is not available in this environment"));return}let n=r.open(this.databaseName,1);n.addEventListener("upgradeneeded",()=>{let s=n.result;s.objectStoreNames.contains(this.storeName)||s.createObjectStore(this.storeName)}),n.addEventListener("success",()=>e(n.result)),n.addEventListener("error",()=>t(n.error))})}async readSnapshot(){let e=await this.openDatabase();try{let n=e.transaction(this.storeName,"readonly").objectStore(this.storeName).get(this.key),s=await E(n);return s?JSON.parse(s):null}finally{e.close()}}async writeSnapshot(e){let t=await this.openDatabase();try{let r=t.transaction(this.storeName,"readwrite"),n=r.objectStore(this.storeName);await E(n.put(JSON.stringify(e),this.key)),await new Promise((s,i)=>{r.addEventListener("complete",()=>s()),r.addEventListener("error",()=>i(r.error)),r.addEventListener("abort",()=>i(r.error))})}finally{t.close()}}serializeNode(e){return e.type==="file"?{...e}:{...e,children:e.children.map(t=>this.serializeNode(t))}}deserializeNode(e){return e.type==="file"?{...e}:{...e,children:e.children.map(t=>this.deserializeNode(t))}}getNode(e){let t=l(e);if(t==="/")return this.root;let r=$(t),n=this.root;for(let s of r){if(n.type!=="directory")throw new Error(`Not a directory: ${t}`);let i=b(n,s);if(!i)throw new Error(`No such file or directory: ${t}`);n=i}return n}ensureDirectory(e,t){let r=l(e);if(r==="/")return this.root;let n=$(r),s=this.root;for(let i of n){let a=b(s,i);if(!a){let c=W(i,t);m(s,c),s=c;continue}if(a.type!=="directory")throw new Error(`Cannot create directory '${r}': path is a file.`);s=a}return s}removeNode(e,t){let r=l(e);if(r==="/")throw new Error("Cannot remove root directory");let n=this.getNode(h(r));if(n.type!=="directory")throw new Error(`Not a directory: ${h(r)}`);let s=f(r),i=b(n,s);if(!i)throw new Error(`No such file or directory: ${r}`);if(i.type==="directory"&&i.children.length>0&&!t)throw new Error(`Cannot remove '${r}': directory not empty.`);S(n,s)}copyNode(e){return e.type==="file"?{...e,contentBase64:e.contentBase64}:{...e,children:e.children.map(t=>this.copyNode(t))}}async restoreMirror(){let e=await this.readSnapshot();e&&(this.root=this.deserializeNode(e.root))}async flushMirror(){await this.writeSnapshot({root:this.serializeNode(this.root)})}exists(e){try{return this.getNode(e),!0}catch{return!1}}list(e){let t=this.getNode(e);if(t.type!=="directory")throw new Error(`Not a directory: ${e}`);return t.children.map(r=>r.name).sort((r,n)=>r.localeCompare(n))}stat(e){let t=this.getNode(e);return t.type==="file"?{type:"file",mode:t.mode,size:N(t.contentBase64).byteLength,name:t.name}:{type:"directory",mode:t.mode,size:0,name:t.name}}readFile(e){let t=this.getNode(e);if(t.type!=="file")throw new Error(`Is a directory: ${e}`);return B.decode(N(t.contentBase64))}writeFile(e,t,r=420){let n=l(e),s=this.ensureDirectory(h(n),493),i=typeof t=="string"?_.encode(t):t,a=T(f(n),i,r);m(s,a)}mkdir(e,t=493){this.ensureDirectory(e,t)}touch(e){this.exists(e)||this.writeFile(e,"")}move(e,t){let r=this.getNode(e),n=this.getNode(h(e)),s=this.ensureDirectory(h(t),493);if(n.type!=="directory")throw new Error(`Not a directory: ${h(e)}`);S(n,f(e));let i=D(r);i.name=f(t),m(s,i)}copy(e,t){let r=this.getNode(e),n=this.ensureDirectory(h(t),493),s=this.copyNode(r);s.name=f(t),m(n,s)}remove(e,t={}){this.removeNode(e,t.recursive??!1)}exportSnapshot(){return{root:this.serializeNode(this.root)}}importSnapshot(e){this.root=this.deserializeNode(e.root)}},v=class{constructor(e,t={}){u(this,"hostname");u(this,"vfs");u(this,"env");u(this,"cwd");u(this,"commands",new Map);u(this,"initialized",!1);this.hostname=e,this.cwd=t.cwd??"/home/root",this.env={vars:{PATH:"/usr/bin:/bin",HOME:"/home/root",USER:"root",LOGNAME:"root",SHELL:"/bin/sh",HOSTNAME:e,PWD:this.cwd},lastExitCode:0},this.vfs=new y(t.vfs),this.registerBuiltins()}register(e){this.commands.set(e.name.toLowerCase(),e);for(let t of e.aliases??[])this.commands.set(t.toLowerCase(),e)}registerBuiltins(){this.register({name:"help",description:"List available web commands",params:[],run:()=>({stdout:`${this.listCommands().join(`
|
|
1
|
+
var R=Object.defineProperty;var F=(o,e,t)=>e in o?R(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var m=(o,e,t)=>F(o,typeof e!="symbol"?e+"":e,t);function S(o){let e=[],t="",r=!1,n="",s=0;for(;s<o.length;){let i=o[s],a=o[s+1];if((i==='"'||i==="'")&&!r){r=!0,n=i,s++;continue}if(r&&i===n){r=!1,n="",s++;continue}if(r){t+=i,s++;continue}if(i===" "){t&&(e.push(t),t=""),s++;continue}if(!r&&i==="2"&&a===">"){let c=o.slice(s+1);if(c.startsWith(">>&1")||c.startsWith(">> &1")){t&&(e.push(t),t=""),e.push("2>>&1"),s+=5;continue}if(c.startsWith(">&1")){t&&(e.push(t),t=""),e.push("2>&1"),s+=4;continue}if(c.startsWith(">>")){t&&(e.push(t),t=""),e.push("2>>"),s+=3;continue}if(c.startsWith(">")){t&&(e.push(t),t=""),e.push("2>"),s+=2;continue}}if((i===">"||i==="<")&&!r){t&&(e.push(t),t=""),i===">"&&a===">"?(e.push(">>"),s+=2):(e.push(i),s++);continue}t+=i,s++}return t&&e.push(t),e}function $(o){let e=o.trim();if(!e)return{statements:[],isValid:!0};try{return{statements:L(e),isValid:!0}}catch(t){return{statements:[],isValid:!1,error:t.message}}}function L(o){let e=I(o),t=[];for(let r of e){let s={pipeline:{commands:M(r.text.trim()),isValid:!0}};r.op&&(s.op=r.op),t.push(s)}return t}function I(o){let e=[],t="",r=0,n=!1,s="",i=0,a=c=>{t.trim()&&e.push({text:t,op:c}),t=""};for(;i<o.length;){let c=o[i],d=o.slice(i,i+2);if((c==='"'||c==="'")&&!n){n=!0,s=c,t+=c,i++;continue}if(n&&c===s){n=!1,t+=c,i++;continue}if(n){t+=c,i++;continue}if(c==="("){r++,t+=c,i++;continue}if(c===")"){r--,t+=c,i++;continue}if(r>0){t+=c,i++;continue}if(d==="&&"){a("&&"),i+=2;continue}if(d==="||"){a("||"),i+=2;continue}if(c===";"){a(";"),i++;continue}t+=c,i++}return a(),e}function M(o){return B(o).map(_)}function B(o){let e=[],t="",r=!1,n="";for(let i=0;i<o.length;i++){let a=o[i];if((a==='"'||a==="'")&&!r){r=!0,n=a,t+=a;continue}if(r&&a===n){r=!1,t+=a;continue}if(r){t+=a;continue}if(a==="|"&&o[i+1]!=="|"){if(!t.trim())throw new Error("Syntax error near unexpected token '|'");e.push(t.trim()),t=""}else t+=a}let s=t.trim();if(!s&&e.length>0)throw new Error("Syntax error near unexpected token '|'");return s&&e.push(s),e}function _(o){let e=S(o);if(e.length===0)return{name:"",args:[]};let t=[],r,n,s=!1,i=0,a,c=!1,d=!1;for(;i<e.length;){let f=e[i];if(f==="<"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after <");r=e[i],i++}else if(f===">>"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after >>");n=e[i],s=!0,i++}else if(f===">"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after >");n=e[i],s=!1,i++}else if(f==="2>&1")d=!0,i++;else if(f==="2>>"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after 2>>");a=e[i],c=!0,i++}else if(f==="2>"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after 2>");a=e[i],c=!1,i++}else t.push(f),i++}let h=t[0]??"";return{name:/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.test(h)?h:h.toLowerCase(),args:t.slice(1),inputFile:r,outputFile:n,appendOutput:s,stderrFile:a,stderrAppend:c,stderrToStdout:d}}function T(o,e){let t=[],r=0;for(;r<o.length;){let n=o[r];if(/\s/.test(n)){r++;continue}if(n==="+"){t.push({type:"plus"}),r++;continue}if(n==="-"){t.push({type:"minus"}),r++;continue}if(n==="*"){if(o[r+1]==="*"){t.push({type:"pow"}),r+=2;continue}t.push({type:"mul"}),r++;continue}if(n==="/"){t.push({type:"div"}),r++;continue}if(n==="%"){t.push({type:"mod"}),r++;continue}if(n==="("){t.push({type:"lparen"}),r++;continue}if(n===")"){t.push({type:"rparen"}),r++;continue}if(/\d/.test(n)){let s=r+1;for(;s<o.length&&/\d/.test(o[s]);)s++;t.push({type:"number",value:Number(o.slice(r,s))}),r=s;continue}if(/[A-Za-z_]/.test(n)){let s=r+1;for(;s<o.length&&/[A-Za-z0-9_]/.test(o[s]);)s++;let i=o.slice(r,s),a=e[i],c=a===void 0||a===""?0:Number(a);t.push({type:"number",value:Number.isFinite(c)?c:0}),r=s;continue}return[]}return t}function j(o,e){let t=o.trim();if(t.length===0||t.length>1024)return NaN;let r=T(t,e);if(r.length===0)return NaN;let n=0,s=()=>r[n],i=()=>r[n++],a=()=>{let l=i();if(!l)return NaN;if(l.type==="number")return l.value;if(l.type==="lparen"){let p=g();return r[n]?.type!=="rparen"?NaN:(n++,p)}return NaN},c=()=>{let l=s();return l?.type==="plus"?(i(),c()):l?.type==="minus"?(i(),-c()):a()},d=()=>{let l=c();for(;s()?.type==="pow";){i();let p=c();l=l**p}return l},h=()=>{let l=d();for(;;){let p=s();if(p?.type==="mul"){i(),l*=d();continue}if(p?.type==="div"){i();let x=d();l=x===0?NaN:l/x;continue}if(p?.type==="mod"){i();let x=d();l=x===0?NaN:l%x;continue}return l}},g=()=>{let l=h();for(;;){let p=s();if(p?.type==="plus"){i(),l+=h();continue}if(p?.type==="minus"){i(),l-=h();continue}return l}},f=g();return!Number.isFinite(f)||n!==r.length?NaN:Math.trunc(f)}function V(o,e){let t=[],r=0;for(;r<o.length;){let n=o.indexOf("'",r);if(n===-1){t.push(e(o.slice(r)));break}t.push(e(o.slice(r,n)));let s=o.indexOf("'",n+1);if(s===-1){t.push(o.slice(n));break}t.push(o.slice(n,s+1)),r=s+1}return t.join("")}function U(o,e){let t="",r=0;for(;r<o.length;){if(o[r]==="$"&&o[r+1]==="("&&o[r+2]==="("){let n=r+3,s=0;for(;n<o.length;){let i=o[n];if(i==="(")s++;else if(i===")"){if(s>0)s--;else if(o[n+1]===")"){let a=o.slice(r+3,n),c=j(a,e);t+=Number.isNaN(c)?"0":String(c),r=n+2;break}}n++}if(n>=o.length){t+=o.slice(r);break}continue}t+=o[r],r++}return t}function w(o,e,t=0,r){let n=r??e.HOME??"/home/user";return V(o,s=>{let i=s;return i=i.replace(/(^|[\s:])~(\/|$)/g,(a,c,d)=>`${c}${n}${d}`),i=i.replace(/\$\?/g,String(t)),i=i.replace(/\$\$/g,"1"),i=i.replace(/\$#/g,"0"),i=U(i,e),i=i.replace(/\$\{#([A-Za-z_][A-Za-z0-9_]*)\}/g,(a,c)=>String((e[c]??"").length)),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):-([^}]*)\}/g,(a,c,d)=>e[c]!==void 0&&e[c]!==""?e[c]:d),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):=([^}]*)\}/g,(a,c,d)=>((e[c]===void 0||e[c]==="")&&(e[c]=d),e[c])),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):\+([^}]*)\}/g,(a,c,d)=>e[c]!==void 0&&e[c]!==""?d:""),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,(a,c)=>e[c]??""),i=i.replace(/\$([A-Za-z_][A-Za-z0-9_]*|\d+)/g,(a,c)=>e[c]??""),i})}async function E(o,e,t,r){let n="__shellExpandDepth",i=Number(e[n]??"0");if(i>=8)return w(o,e,t);e[n]=String(i+1);try{if(o.includes("$(")){let a="",c=!1,d=0;for(;d<o.length;){let h=o[d];if(h==="'"&&!c){c=!0,a+=h,d++;continue}if(h==="'"&&c){c=!1,a+=h,d++;continue}if(!c&&h==="$"&&o[d+1]==="("){if(o[d+2]==="("){a+=h,d++;continue}let g=0,f=d+1;for(;f<o.length;){if(o[f]==="(")g++;else if(o[f]===")"&&(g--,g===0))break;f++}let l=o.slice(d+2,f).trim(),p=(await r(l)).replace(/\n$/,"");a+=p,d=f+1;continue}a+=h,d++}o=a}return w(o,e,t)}finally{i<=0?delete e[n]:e[n]=String(i)}}var Z=new TextEncoder,q=new TextDecoder;function H(o){let e="";for(let t of o)e+=String.fromCharCode(t);return btoa(e)}function D(o){let e=atob(o),t=new Uint8Array(e.length);for(let r=0;r<e.length;r+=1)t[r]=e.charCodeAt(r);return t}function u(o,e="/"){let r=(o.startsWith("/")?o:`${e}/${o}`).split("/"),n=[];for(let s of r)if(!(!s||s===".")){if(s===".."){n.pop();continue}n.push(s)}return`/${n.join("/")}`||"/"}function y(o){let e=u(o);if(e==="/")return"/";let t=e.split("/").filter(Boolean);return t.pop(),t.length>0?`/${t.join("/")}`:"/"}function b(o){let e=u(o);return e==="/"?"/":e.split("/").filter(Boolean).at(-1)??"/"}function O(o){return o.type==="file"?{...o,contentBase64:o.contentBase64}:{...o,children:o.children.map(e=>O(e))}}function k(o,e){let t=new Date().toISOString();return{type:"directory",name:o,mode:e,createdAt:t,updatedAt:t,children:[]}}function Q(o,e,t){let r=new Date().toISOString();return{type:"file",name:o,mode:t,createdAt:r,updatedAt:r,contentBase64:H(e)}}function C(o,e){return o.children.find(t=>t.name===e)}function v(o,e){let t=o.children.findIndex(r=>r.name===e.name);if(t===-1){o.children.push(e);return}o.children[t]=e}function A(o,e){o.children=o.children.filter(t=>t.name!==e)}function z(o){return u(o).split("/").filter(Boolean)}var G=globalThis;function P(o){return new Promise((e,t)=>{o.addEventListener("success",()=>e(o.result)),o.addEventListener("error",()=>t(o.error))})}var N=class{constructor(e={}){m(this,"databaseName");m(this,"storeName");m(this,"key");m(this,"root");this.databaseName=e.databaseName??"typescript-virtual-container-web",this.storeName=e.storeName??"snapshots",this.key=e.key??"current",this.root=k("",493)}async openDatabase(){return new Promise((e,t)=>{let r=G.indexedDB;if(!r){t(new Error("IndexedDB is not available in this environment"));return}let n=r.open(this.databaseName,1);n.addEventListener("upgradeneeded",()=>{let s=n.result;s.objectStoreNames.contains(this.storeName)||s.createObjectStore(this.storeName)}),n.addEventListener("success",()=>e(n.result)),n.addEventListener("error",()=>t(n.error))})}async readSnapshot(){let e=await this.openDatabase();try{let n=e.transaction(this.storeName,"readonly").objectStore(this.storeName).get(this.key),s=await P(n);return s?JSON.parse(s):null}finally{e.close()}}async writeSnapshot(e){let t=await this.openDatabase();try{let r=t.transaction(this.storeName,"readwrite"),n=r.objectStore(this.storeName);await P(n.put(JSON.stringify(e),this.key)),await new Promise((s,i)=>{r.addEventListener("complete",()=>s()),r.addEventListener("error",()=>i(r.error)),r.addEventListener("abort",()=>i(r.error))})}finally{t.close()}}serializeNode(e){return e.type==="file"?{...e}:{...e,children:e.children.map(t=>this.serializeNode(t))}}deserializeNode(e){return e.type==="file"?{...e}:{...e,children:e.children.map(t=>this.deserializeNode(t))}}getNode(e){let t=u(e);if(t==="/")return this.root;let r=z(t),n=this.root;for(let s of r){if(n.type!=="directory")throw new Error(`Not a directory: ${t}`);let i=C(n,s);if(!i)throw new Error(`No such file or directory: ${t}`);n=i}return n}ensureDirectory(e,t){let r=u(e);if(r==="/")return this.root;let n=z(r),s=this.root;for(let i of n){let a=C(s,i);if(!a){let c=k(i,t);v(s,c),s=c;continue}if(a.type!=="directory")throw new Error(`Cannot create directory '${r}': path is a file.`);s=a}return s}removeNode(e,t){let r=u(e);if(r==="/")throw new Error("Cannot remove root directory");let n=this.getNode(y(r));if(n.type!=="directory")throw new Error(`Not a directory: ${y(r)}`);let s=b(r),i=C(n,s);if(!i)throw new Error(`No such file or directory: ${r}`);if(i.type==="directory"&&i.children.length>0&&!t)throw new Error(`Cannot remove '${r}': directory not empty.`);A(n,s)}copyNode(e){return e.type==="file"?{...e,contentBase64:e.contentBase64}:{...e,children:e.children.map(t=>this.copyNode(t))}}async restoreMirror(){let e=await this.readSnapshot();e&&(this.root=this.deserializeNode(e.root))}async flushMirror(){await this.writeSnapshot({root:this.serializeNode(this.root)})}exists(e){try{return this.getNode(e),!0}catch{return!1}}list(e){let t=this.getNode(e);if(t.type!=="directory")throw new Error(`Not a directory: ${e}`);return t.children.map(r=>r.name).sort((r,n)=>r.localeCompare(n))}stat(e){let t=this.getNode(e);return t.type==="file"?{type:"file",mode:t.mode,size:D(t.contentBase64).byteLength,name:t.name}:{type:"directory",mode:t.mode,size:0,name:t.name}}readFile(e){let t=this.getNode(e);if(t.type!=="file")throw new Error(`Is a directory: ${e}`);return q.decode(D(t.contentBase64))}writeFile(e,t,r=420){let n=u(e),s=this.ensureDirectory(y(n),493),i=typeof t=="string"?Z.encode(t):t,a=Q(b(n),i,r);v(s,a)}mkdir(e,t=493){this.ensureDirectory(e,t)}touch(e){this.exists(e)||this.writeFile(e,"")}move(e,t){let r=this.getNode(e),n=this.getNode(y(e)),s=this.ensureDirectory(y(t),493);if(n.type!=="directory")throw new Error(`Not a directory: ${y(e)}`);A(n,b(e));let i=O(r);i.name=b(t),v(s,i)}copy(e,t){let r=this.getNode(e),n=this.ensureDirectory(y(t),493),s=this.copyNode(r);s.name=b(t),v(n,s)}remove(e,t={}){this.removeNode(e,t.recursive??!1)}exportSnapshot(){return{root:this.serializeNode(this.root)}}importSnapshot(e){this.root=this.deserializeNode(e.root)}},W=class{constructor(e,t={}){m(this,"hostname");m(this,"vfs");m(this,"env");m(this,"cwd");m(this,"commands",new Map);m(this,"initialized",!1);this.hostname=e,this.cwd=t.cwd??"/home/root",this.env={vars:{PATH:"/usr/bin:/bin",HOME:"/home/root",USER:"root",LOGNAME:"root",SHELL:"/bin/sh",HOSTNAME:e,PWD:this.cwd},lastExitCode:0},this.vfs=new N(t.vfs),this.registerBuiltins()}register(e){this.commands.set(e.name.toLowerCase(),e);for(let t of e.aliases??[])this.commands.set(t.toLowerCase(),e)}registerBuiltins(){this.register({name:"help",description:"List available web commands",params:[],run:()=>({stdout:`${this.listCommands().join(`
|
|
2
2
|
`)}
|
|
3
3
|
`,exitCode:0})}),this.register({name:"pwd",description:"Print current directory",params:[],run:()=>({stdout:`${this.cwd}
|
|
4
|
-
`,exitCode:0})}),this.register({name:"cd",description:"Change current directory",params:["[dir]"],run:({args:e})=>{let t=e[0]?
|
|
4
|
+
`,exitCode:0})}),this.register({name:"cd",description:"Change current directory",params:["[dir]"],run:({args:e})=>{let t=e[0]?u(e[0],this.cwd):"/home/root";return!this.vfs.exists(t)||this.vfs.stat(t).type!=="directory"?{stderr:`cd: no such file or directory: ${t}`,exitCode:1}:(this.cwd=t,this.env.vars.PWD=t,{exitCode:0,nextCwd:t})}}),this.register({name:"echo",description:"Display text",params:["[-n] [-e] [text...]"],run:({args:e,stdin:t})=>{let r=e.includes("-n"),n=e.filter(a=>a!=="-n"&&a!=="-e"&&a!=="-E"),s=n.length>0?n.join(" "):t??"",i=w(s,this.env.vars,this.env.lastExitCode,this.env.vars.HOME);return{stdout:r?i:`${i}
|
|
5
5
|
`,exitCode:0}}}),this.register({name:"env",description:"Print environment variables",params:[],run:()=>({stdout:`${Object.entries(this.env.vars).map(([e,t])=>`${e}=${t}`).join(`
|
|
6
6
|
`)}
|
|
7
|
-
`,exitCode:0})}),this.register({name:"export",description:"Set environment variables",params:["KEY=VALUE..."],run:({args:e})=>{for(let t of e){let r=t.indexOf("=");if(r===-1)continue;let n=t.slice(0,r).trim(),s=t.slice(r+1);n&&(this.env.vars[n]=s)}return{exitCode:0}}}),this.register({name:"unset",description:"Unset environment variables",params:["NAME..."],run:({args:e})=>{for(let t of e)delete this.env.vars[t];return{exitCode:0}}}),this.register({name:"mkdir",description:"Create directories",params:["[-p] dir..."],run:async({args:e})=>{let t=e.filter(r=>r!=="-p");for(let r of t)this.vfs.mkdir(
|
|
8
|
-
`,exitCode:0}}}),this.register({name:"tee",description:"Read from stdin and write to files",params:["[-a] file..."],run:async({args:e,stdin:t})=>{let r=e.includes("-a"),n=e.filter(i=>i!=="-a"),s=t??"";for(let i of n){let a=
|
|
7
|
+
`,exitCode:0})}),this.register({name:"export",description:"Set environment variables",params:["KEY=VALUE..."],run:({args:e})=>{for(let t of e){let r=t.indexOf("=");if(r===-1)continue;let n=t.slice(0,r).trim(),s=t.slice(r+1);n&&(this.env.vars[n]=s)}return{exitCode:0}}}),this.register({name:"unset",description:"Unset environment variables",params:["NAME..."],run:({args:e})=>{for(let t of e)delete this.env.vars[t];return{exitCode:0}}}),this.register({name:"mkdir",description:"Create directories",params:["[-p] dir..."],run:async({args:e})=>{let t=e.filter(r=>r!=="-p");for(let r of t)this.vfs.mkdir(u(r,this.cwd));return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"touch",description:"Create files",params:["file..."],run:async({args:e})=>{for(let t of e)this.vfs.touch(u(t,this.cwd));return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"rm",description:"Remove files or directories",params:["[-r] [-f] path..."],run:async({args:e})=>{let t=e.includes("-r"),r=e.filter(n=>n!=="-r"&&n!=="-f");for(let n of r)this.vfs.remove(u(n,this.cwd),{recursive:t});return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"cp",description:"Copy files or directories",params:["[-r] source destination"],run:async({args:e})=>{let t=e.includes("-r"),r=e.filter(s=>s!=="-r");if(r.length<2)return{stderr:"cp: missing destination file operand",exitCode:1};let n=u(r.at(-1),this.cwd);for(let s of r.slice(0,-1)){let i=u(s,this.cwd);if(!t&&this.vfs.stat(i).type==="directory")return{stderr:`cp: -r not specified; omitting directory '${i}'`,exitCode:1};this.vfs.copy(i,n)}return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"mv",description:"Move or rename files",params:["source destination"],run:async({args:e})=>{if(e.length<2)return{stderr:"mv: missing destination file operand",exitCode:1};let t=u(e[0],this.cwd),r=u(e[1],this.cwd);return this.vfs.move(t,r),await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"cat",description:"Concatenate files",params:["[file...]"],run:({args:e,stdin:t})=>{if(e.length===0)return{stdout:t??"",exitCode:0};let r="";for(let n of e)r+=this.vfs.readFile(u(n,this.cwd));return{stdout:r,exitCode:0}}}),this.register({name:"ls",description:"List files",params:["[path]"],run:({args:e})=>{let t=u(e[0]??".",this.cwd);return{stdout:`${this.vfs.list(t).join(" ")}
|
|
8
|
+
`,exitCode:0}}}),this.register({name:"tee",description:"Read from stdin and write to files",params:["[-a] file..."],run:async({args:e,stdin:t})=>{let r=e.includes("-a"),n=e.filter(i=>i!=="-a"),s=t??"";for(let i of n){let a=u(i,this.cwd);if(r&&this.vfs.exists(a)){let c=this.vfs.readFile(a);this.vfs.writeFile(a,`${c}${s}`)}else this.vfs.writeFile(a,s)}return await this.vfs.flushMirror(),{stdout:s,exitCode:0}}}),this.register({name:"curl",description:"Fetch a URL and optionally write to a file",params:["[-o file] URL"],run:async({args:e})=>{let t=e.indexOf("-o"),r=t!==-1?e[t+1]:void 0,s=e.filter((c,d)=>c!=="-o"&&d!==t+1).at(-1);if(!s)return{stderr:"curl: missing URL",exitCode:2};let i=await fetch(s),a=await i.text();return r?(this.vfs.writeFile(u(r,this.cwd),a),await this.vfs.flushMirror(),{exitCode:i.ok?0:1}):{stdout:a,exitCode:i.ok?0:1}}}),this.register({name:"wget",description:"Fetch a URL and optionally write to a file",params:["[-O file] URL"],run:async({args:e})=>{let t=e.indexOf("-O"),r=t!==-1?e[t+1]:void 0,s=e.filter((d,h)=>d!=="-O"&&h!==t+1).at(-1);if(!s)return{stderr:"wget: missing URL",exitCode:2};let i=await fetch(s),a=await i.text(),c=r??b(new URL(s).pathname||"index.html");return this.vfs.writeFile(u(c,this.cwd),a),await this.vfs.flushMirror(),{exitCode:i.ok?0:1}}}),this.register({name:"true",description:"Return success",params:[],run:()=>({exitCode:0})}),this.register({name:"false",description:"Return failure",params:[],run:()=>({exitCode:1})})}listCommands(){let e=new Map;for(let t of this.commands.values())e.set(t.name,t);return Array.from(e.values()).sort((t,r)=>t.name.localeCompare(r.name)).map(t=>`${t.name}${t.params.length>0?` ${t.params.join(" ")}`:""}`)}resolveCommand(e){return this.commands.get(e.toLowerCase())}async ensureInitialized(){this.initialized||(await this.vfs.restoreMirror(),this.vfs.exists("/home")||this.vfs.mkdir("/home"),this.vfs.exists("/home/root")||(this.vfs.mkdir("/home/root"),this.vfs.writeFile("/home/root/README.txt",`Welcome to ${this.hostname}
|
|
9
9
|
`)),this.vfs.exists("/tmp")||this.vfs.mkdir("/tmp"),this.vfs.exists("/etc")||this.vfs.mkdir("/etc"),this.vfs.exists("/etc/hostname")||this.vfs.writeFile("/etc/hostname",`${this.hostname}
|
|
10
10
|
`),this.vfs.exists("/etc/hosts")||this.vfs.writeFile("/etc/hosts",`127.0.0.1 localhost
|
|
11
11
|
::1 localhost
|
|
12
|
-
`),this.initialized=!0)}getCurrentWorkingDirectory(){return this.cwd}async executeCommandLine(e,t=!0){await this.ensureInitialized();let r=e.trim();if(!r)return{exitCode:0};let n=await
|
|
12
|
+
`),this.initialized=!0)}getCurrentWorkingDirectory(){return this.cwd}async executeCommandLine(e,t=!0){await this.ensureInitialized();let r=e.trim();if(!r)return{exitCode:0};let n=await E(r,this.env.vars,this.env.lastExitCode,a=>this.executeCommandLine(a,!1).then(c=>c.stdout??"")),s=$(n),i=await this.executeStatements(s.statements);return this.env.lastExitCode=i.exitCode??0,t&&await this.vfs.flushMirror(),i}async executeStatements(e){let t={exitCode:0},r=0;for(;r<e.length;){let n=e[r];if(t=await this.executePipeline(n.pipeline.commands),this.env.lastExitCode=t.exitCode??0,t.closeSession||t.switchUser)return t;let s=n.op;if(!(!s||s===";")){if(s==="&&"){if((t.exitCode??0)!==0)for(;r<e.length&&e[r]?.op==="&&";)r+=1}else if(s==="||"&&(t.exitCode??0)===0)for(;r<e.length&&e[r]?.op==="||";)r+=1}r+=1}return t}async executePipeline(e){return e.length===0?{exitCode:0}:e.length===1?this.executeSingleCommandWithRedirections(e[0]):this.executePipelineChain(e)}async executeSingleCommandWithRedirections(e){let t;if(e.inputFile){let n=u(e.inputFile,this.cwd);try{t=this.vfs.readFile(n)}catch{return{stderr:`${e.inputFile}: No such file or directory`,exitCode:1}}}let r=await this.executeCommand(e.name,e.args,t);if(e.outputFile){let n=u(e.outputFile,this.cwd),s=r.stdout??"";if(e.appendOutput&&this.vfs.exists(n)){let i=this.vfs.readFile(n);this.vfs.writeFile(n,`${i}${s}`)}else this.vfs.writeFile(n,s);return{...r,stdout:""}}return r}async executePipelineChain(e){let t="",r=0;for(let n=0;n<e.length;n+=1){let s=e[n];if(n===0&&s.inputFile){let a=u(s.inputFile,this.cwd);try{t=this.vfs.readFile(a)}catch{return{stderr:`${s.inputFile}: No such file or directory`,exitCode:1}}}let i=await this.executeCommand(s.name,s.args,t);t=i.stdout??"",r=i.exitCode??0}return{stdout:t,exitCode:r}}async executeCommand(e,t,r){let n=this.resolveCommand(e);if(!n)return{stderr:`${e}: command not found`,exitCode:127};let i={args:t.map(a=>w(a,this.env.vars,this.env.lastExitCode,this.env.vars.HOME)),stdin:r,cwd:this.cwd,env:this.env,rawInput:`${e} ${t.join(" ")}`.trim(),shell:this};try{let a=await n.run(i);return a.nextCwd&&(this.cwd=a.nextCwd,this.env.vars.PWD=a.nextCwd),a}catch(a){return{stderr:a instanceof Error?a.message:String(a),exitCode:1}}}};function ie(o="typescript-vm",e={}){return new W(o,e)}export{N as IndexedDbMirrorVfs,W as WebShell,ie as createWebShell};
|
|
13
13
|
//# sourceMappingURL=web.min.js.map
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "1.4.
|
|
7
|
+
"version": "1.4.5",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
"check": "bunx --bun @biomejs/biome check ./src",
|
|
25
25
|
"lint": "bunx --bun @biomejs/biome lint ./src",
|
|
26
26
|
"lint:write": "bunx --bun @biomejs/biome lint --write ./src",
|
|
27
|
-
"test": "
|
|
27
|
+
"test": "bun run test-salve",
|
|
28
28
|
"test-battery": "bun test tests/",
|
|
29
|
+
"test-salve": "for f in tests/*.test.ts; do echo \"\\n🧪 Testing $f...\"; bun test \"$f\" --timeout 10000; sleep 0.25; done",
|
|
29
30
|
"build": "tsc --project tsconfig.json",
|
|
30
31
|
"deploy:npm": "npm publish --access public",
|
|
31
32
|
"bench": "rm -rf .benchmark-shells/ && bun benchmark-virtualshell.ts",
|
|
@@ -38,13 +39,15 @@
|
|
|
38
39
|
"publish-package": "bash ./scripts/publish-package.sh",
|
|
39
40
|
"self-standalone-build": "bunx esbuild src/self-standalone.ts --bundle --platform=node --format=esm --target=node18 --outfile=builds/self-standalone.js --tree-shaking=true --minify --sourcemap --banner:js='#!/usr/bin/env node'",
|
|
40
41
|
"standalone-build": "bunx esbuild src/standalone.ts --bundle --platform=node --target=node18 --outfile=builds/standalone.js --tree-shaking=true --minify --sourcemap",
|
|
41
|
-
"build-all": "bun run self-standalone-build && bun run standalone-build && bun run standalone-build:wo-sftp && bun run web-build && bun run web-full-build && bun run example-build"
|
|
42
|
+
"build-all": "bun run self-standalone-build && bun run standalone-build && bun run standalone-build:wo-sftp && bun run web-build && bun run web-full-build && bun run example-build",
|
|
43
|
+
"publish-doc": "npx typedoc && npx gh-pages -d docs"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
44
46
|
"@biomejs/biome": "^2.4.15",
|
|
45
47
|
"@types/bun": "^1.3.13",
|
|
46
|
-
"@types/node": "^25.
|
|
48
|
+
"@types/node": "^25.7.0",
|
|
47
49
|
"@types/ssh2": "^1.15.5",
|
|
50
|
+
"gh-pages": "^6.3.0",
|
|
48
51
|
"typescript": "^6.0.3"
|
|
49
52
|
},
|
|
50
53
|
"peerDependencies": {
|
|
@@ -2,15 +2,10 @@ import { EventEmitter } from "node:events";
|
|
|
2
2
|
import * as fsSync from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
5
|
-
import type {
|
|
6
|
-
InternalDirectoryNode,
|
|
7
|
-
InternalFileNode,
|
|
8
|
-
InternalNode,
|
|
9
|
-
} from "./internalTypes";
|
|
10
|
-
import { decodeVfs, encodeVfs, isBinarySnapshot } from "./binaryPack";
|
|
11
|
-
import { getNode, getParentDirectory, normalizePath } from "./path";
|
|
12
5
|
import type {
|
|
13
6
|
RemoveOptions,
|
|
7
|
+
VfsDirectoryNode,
|
|
8
|
+
VfsFileNode,
|
|
14
9
|
VfsNodeStats,
|
|
15
10
|
VfsSnapshot,
|
|
16
11
|
VfsSnapshotDirectoryNode,
|
|
@@ -18,6 +13,13 @@ import type {
|
|
|
18
13
|
VfsSnapshotNode,
|
|
19
14
|
WriteFileOptions,
|
|
20
15
|
} from "../types/vfs";
|
|
16
|
+
import { decodeVfs, encodeVfs, isBinarySnapshot } from "./binaryPack";
|
|
17
|
+
import type {
|
|
18
|
+
InternalDirectoryNode,
|
|
19
|
+
InternalFileNode,
|
|
20
|
+
InternalNode,
|
|
21
|
+
} from "./internalTypes";
|
|
22
|
+
import { getNode, getParentDirectory, normalizePath } from "./path";
|
|
21
23
|
|
|
22
24
|
// ── Persistence options ───────────────────────────────────────────────────────
|
|
23
25
|
|
|
@@ -460,7 +462,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
460
462
|
createdAt: hst.birthtime,
|
|
461
463
|
updatedAt: now,
|
|
462
464
|
childrenCount: fsSync.readdirSync(m.fullHostPath).length,
|
|
463
|
-
} satisfies
|
|
465
|
+
} satisfies VfsDirectoryNode;
|
|
464
466
|
}
|
|
465
467
|
return {
|
|
466
468
|
type: "file",
|
|
@@ -471,7 +473,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
471
473
|
updatedAt: now,
|
|
472
474
|
compressed: false,
|
|
473
475
|
size: hst.size,
|
|
474
|
-
} satisfies
|
|
476
|
+
} satisfies VfsFileNode;
|
|
475
477
|
}
|
|
476
478
|
const normalized = normalizePath(targetPath);
|
|
477
479
|
const node = getNode(this.root, normalized);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
LogicalOp,
|
|
2
3
|
Pipeline,
|
|
3
4
|
PipelineCommand,
|
|
4
5
|
Script,
|
|
5
6
|
Statement,
|
|
6
|
-
LogicalOp,
|
|
7
7
|
} from "../types/pipeline";
|
|
8
8
|
import { tokenizeCommand } from "../utils/tokenize";
|
|
9
9
|
|
|
@@ -274,7 +274,8 @@ function parseCommandWithRedirections(token: string): PipelineCommand {
|
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
const
|
|
277
|
+
const rawName = cmdParts[0] ?? "";
|
|
278
|
+
const name = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.test(rawName) ? rawName : rawName.toLowerCase();
|
|
278
279
|
return {
|
|
279
280
|
name, args: cmdParts.slice(1),
|
|
280
281
|
inputFile, outputFile, appendOutput,
|
package/src/bun.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="@types/bun" />
|
package/src/commands/awk.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { getFlag } from "./command-helpers";
|
|
3
|
-
import {
|
|
3
|
+
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Minimal awk-like pattern scanner.
|
|
@@ -92,7 +92,6 @@ export const awkCommand: ShellModule = {
|
|
|
92
92
|
// Arithmetic NR+1, NF-1
|
|
93
93
|
const arith = expr.replace(/\bNR\b/g, String(nr)).replace(/\bNF\b/g, String(nf));
|
|
94
94
|
if (/^[\d\s+\-*/()]+$/.test(arith)) {
|
|
95
|
-
// biome-ignore lint/security/noGlobalEval: safe arithmetic — input contains only digits and operators after variable substitution
|
|
96
95
|
try { return String(Function(`"use strict"; return (${arith});`)()); } catch {} }
|
|
97
96
|
return expr.replace(/"/g, "");
|
|
98
97
|
};
|
package/src/commands/cd.ts
CHANGED
|
@@ -11,8 +11,8 @@ export const cdCommand: ShellModule = {
|
|
|
11
11
|
description: "Change directory",
|
|
12
12
|
category: "navigation",
|
|
13
13
|
params: ["[path]"],
|
|
14
|
-
run: ({ authUser, shell, cwd, args
|
|
15
|
-
const target = resolvePath(cwd, args[0] ??
|
|
14
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
15
|
+
const target = resolvePath(cwd, args[0] ?? `~`);
|
|
16
16
|
assertPathAccess(authUser, target, "cd");
|
|
17
17
|
const stats = shell.vfs.stat(target);
|
|
18
18
|
if (stats.type !== "directory") {
|
package/src/commands/helpers.ts
CHANGED
|
@@ -32,6 +32,9 @@ export function resolvePath(cwd: string, inputPath: string): string {
|
|
|
32
32
|
if (!inputPath || inputPath.trim() === "") {
|
|
33
33
|
return cwd;
|
|
34
34
|
}
|
|
35
|
+
if (inputPath.startsWith("~")) {
|
|
36
|
+
return path.posix.normalize(`/home/${inputPath.slice(1)}`);
|
|
37
|
+
}
|
|
35
38
|
return inputPath.startsWith("/")
|
|
36
39
|
? path.posix.normalize(inputPath)
|
|
37
40
|
: path.posix.normalize(path.posix.join(cwd, inputPath));
|
package/src/commands/ls.ts
CHANGED
|
@@ -1,74 +1,243 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { getArg, ifFlag } from "./command-helpers";
|
|
3
|
-
import { assertPathAccess,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
3
|
+
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
|
+
|
|
5
|
+
// ─── ANSI color codes (matches GNU ls --color=auto / LS_COLORS defaults) ────
|
|
6
|
+
|
|
7
|
+
const RESET = "\x1b[0m";
|
|
8
|
+
|
|
9
|
+
// Entry-type colors
|
|
10
|
+
const C_DIR = "\x1b[1;34m"; // bold blue — directory
|
|
11
|
+
const C_LINK = "\x1b[1;36m"; // bold cyan — symlink
|
|
12
|
+
const C_EXEC = "\x1b[1;32m"; // bold green — executable file
|
|
13
|
+
const C_NORMAL = ""; // no color — regular file
|
|
14
|
+
|
|
15
|
+
// Special-mode backgrounds (GNU ls corner cases)
|
|
16
|
+
const C_STICKY_WX = "\x1b[30;42m"; // black on green — dir sticky + world-writable (/tmp)
|
|
17
|
+
const C_STICKY = "\x1b[37;44m"; // white on blue — dir sticky, NOT world-writable
|
|
18
|
+
const C_OTHER_WX = "\x1b[34;42m"; // blue on green — dir world-writable, not sticky
|
|
19
|
+
|
|
20
|
+
// ─── helpers ────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
function colorize(name: string, color: string): string {
|
|
23
|
+
return color ? `${color}${name}${RESET}` : name;
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
function entryColor(mode: number, type: "file" | "directory", isLink: boolean): string {
|
|
27
|
+
if (isLink) return C_LINK;
|
|
28
|
+
if (type === "directory") {
|
|
29
|
+
const sticky = !!(mode & 0o1000);
|
|
30
|
+
const worldW = !!(mode & 0o002);
|
|
31
|
+
if (sticky && worldW) return C_STICKY_WX;
|
|
32
|
+
if (sticky) return C_STICKY;
|
|
33
|
+
if (worldW) return C_OTHER_WX;
|
|
34
|
+
return C_DIR;
|
|
35
|
+
}
|
|
36
|
+
if (mode & 0o111) return C_EXEC;
|
|
37
|
+
return C_NORMAL;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── permissions string ──────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
function formatPermissions(mode: number, type: "file" | "directory", isLink: boolean): string {
|
|
43
|
+
let ft: string;
|
|
44
|
+
if (isLink) ft = "l";
|
|
45
|
+
else if (type === "directory") ft = "d";
|
|
46
|
+
else ft = "-";
|
|
47
|
+
|
|
48
|
+
const r = (bit: number) => (mode & bit ? "r" : "-");
|
|
49
|
+
const w = (bit: number) => (mode & bit ? "w" : "-");
|
|
50
|
+
|
|
51
|
+
const xOwner = (() => {
|
|
52
|
+
const exec = !!(mode & 0o100);
|
|
53
|
+
if (mode & 0o4000) return exec ? "s" : "S";
|
|
54
|
+
return exec ? "x" : "-";
|
|
55
|
+
})();
|
|
56
|
+
const xGroup = (() => {
|
|
57
|
+
const exec = !!(mode & 0o010);
|
|
58
|
+
if (mode & 0o2000) return exec ? "s" : "S";
|
|
59
|
+
return exec ? "x" : "-";
|
|
60
|
+
})();
|
|
61
|
+
const xOther = (() => {
|
|
62
|
+
const exec = !!(mode & 0o001);
|
|
63
|
+
if (type === "directory" && (mode & 0o1000)) return exec ? "t" : "T";
|
|
64
|
+
return exec ? "x" : "-";
|
|
65
|
+
})();
|
|
66
|
+
|
|
67
|
+
return `${ft}${r(0o400)}${w(0o200)}${xOwner}${r(0o040)}${w(0o020)}${xGroup}${r(0o004)}${w(0o002)}${xOther}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── date formatting (GNU ls + French locale) ────────────────────────────────
|
|
71
|
+
|
|
72
|
+
const MONTHS_EN = [
|
|
73
|
+
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
74
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
|
75
|
+
];
|
|
76
|
+
|
|
25
77
|
function formatDate(date: Date): string {
|
|
26
|
-
|
|
78
|
+
const now = new Date();
|
|
79
|
+
const sixMo = 6 * 30 * 24 * 3600 * 1000;
|
|
80
|
+
const recent = Math.abs(now.getTime() - date.getTime()) < sixMo;
|
|
81
|
+
const day = String(date.getDate()).padStart(2, " ");
|
|
82
|
+
const month = MONTHS_EN[date.getMonth()] ?? "";
|
|
83
|
+
if (recent) {
|
|
84
|
+
const hh = String(date.getHours()).padStart(2, "0");
|
|
85
|
+
const mm = String(date.getMinutes()).padStart(2, "0");
|
|
86
|
+
return `${day} ${month.padEnd(3)} ${hh}:${mm}`;
|
|
87
|
+
}
|
|
88
|
+
return `${day} ${month.padEnd(3)} ${date.getFullYear()}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── symlink target ──────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
function readlinkTarget(vfs: { readFile: (p: string) => string }, path: string): string {
|
|
94
|
+
try { return vfs.readFile(path); } catch { return "?"; }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── short listing ───────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
function shortListing(
|
|
100
|
+
vfs: {
|
|
101
|
+
stat: (p: string) => { mode: number; type: "file" | "directory" };
|
|
102
|
+
isSymlink: (p: string) => boolean;
|
|
103
|
+
},
|
|
104
|
+
dir: string,
|
|
105
|
+
items: string[],
|
|
106
|
+
): string {
|
|
107
|
+
const base = dir === "/" ? "" : dir;
|
|
108
|
+
return items.map((name) => {
|
|
109
|
+
const childPath = `${base}/${name}`;
|
|
110
|
+
const isLink = vfs.isSymlink(childPath);
|
|
111
|
+
let stat: { mode: number; type: "file" | "directory" };
|
|
112
|
+
try { stat = vfs.stat(childPath); } catch { return name; }
|
|
113
|
+
const color = entryColor(stat.mode, stat.type, isLink);
|
|
114
|
+
return colorize(name, color);
|
|
115
|
+
}).join(" ");
|
|
27
116
|
}
|
|
28
117
|
|
|
118
|
+
// ─── long listing ────────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
type VfsStat = {
|
|
121
|
+
mode: number;
|
|
122
|
+
type: "file" | "directory";
|
|
123
|
+
updatedAt: Date;
|
|
124
|
+
size?: number;
|
|
125
|
+
childrenCount?: number;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
function longListing(
|
|
129
|
+
vfs: {
|
|
130
|
+
stat: (p: string) => VfsStat;
|
|
131
|
+
isSymlink: (p: string) => boolean;
|
|
132
|
+
readFile: (p: string) => string;
|
|
133
|
+
},
|
|
134
|
+
dir: string,
|
|
135
|
+
items: string[],
|
|
136
|
+
): string {
|
|
137
|
+
const base = dir === "/" ? "" : dir;
|
|
138
|
+
|
|
139
|
+
type Row = { perms: string; nlink: string; size: string; date: string; label: string };
|
|
140
|
+
|
|
141
|
+
const rows: Row[] = items.map((name) => {
|
|
142
|
+
const childPath = `${base}/${name}`;
|
|
143
|
+
const isLink = vfs.isSymlink(childPath);
|
|
144
|
+
let stat: VfsStat;
|
|
145
|
+
try { stat = vfs.stat(childPath); } catch {
|
|
146
|
+
return {
|
|
147
|
+
perms: "----------", nlink: "1", size: "0",
|
|
148
|
+
date: formatDate(new Date()), label: name,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const mode = isLink ? 0o120777 : stat.mode;
|
|
153
|
+
const perms = formatPermissions(mode, stat.type, isLink);
|
|
154
|
+
|
|
155
|
+
// nlink: dirs = children + 2 (. and ..), files/links = 1
|
|
156
|
+
const nlink = stat.type === "directory"
|
|
157
|
+
? String((stat.childrenCount ?? 0) + 2)
|
|
158
|
+
: "1";
|
|
159
|
+
|
|
160
|
+
// size: links → target path length, files → bytes, dirs → children×4096
|
|
161
|
+
const rawSize = isLink
|
|
162
|
+
? readlinkTarget(vfs, childPath).length
|
|
163
|
+
: stat.type === "file"
|
|
164
|
+
? (stat.size ?? 0)
|
|
165
|
+
: (stat.childrenCount ?? 0) * 4096;
|
|
166
|
+
const size = String(rawSize);
|
|
167
|
+
|
|
168
|
+
const date = formatDate(stat.updatedAt);
|
|
169
|
+
const color = entryColor(mode, stat.type, isLink);
|
|
170
|
+
|
|
171
|
+
const label = isLink
|
|
172
|
+
? `${colorize(name, color)} -> ${readlinkTarget(vfs, childPath)}`
|
|
173
|
+
: colorize(name, color);
|
|
174
|
+
|
|
175
|
+
return { perms, nlink, size, date, label };
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const wNlink = Math.max(...rows.map((r) => r.nlink.length));
|
|
179
|
+
const wSize = Math.max(...rows.map((r) => r.size.length));
|
|
180
|
+
const owner = "root";
|
|
181
|
+
const group = "root";
|
|
182
|
+
|
|
183
|
+
const total = items.length * 8;
|
|
184
|
+
const lines = rows.map((r) =>
|
|
185
|
+
`${r.perms} ${r.nlink.padStart(wNlink)} ${owner} ${group} ${r.size.padStart(wSize)} ${r.date} ${r.label}`,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return `total ${total}\n${lines.join("\n")}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ─── command ─────────────────────────────────────────────────────────────────
|
|
192
|
+
|
|
29
193
|
export const lsCommand: ShellModule = {
|
|
30
194
|
name: "ls",
|
|
31
195
|
description: "List directory contents",
|
|
32
196
|
category: "navigation",
|
|
33
197
|
params: ["[-la] [path]"],
|
|
34
198
|
run: ({ authUser, shell, cwd, args }) => {
|
|
35
|
-
const longFormat = ifFlag(args, ["-l", "--long"]);
|
|
36
|
-
const showHidden = ifFlag(args, ["-a", "--all"]);
|
|
199
|
+
const longFormat = ifFlag(args, ["-l", "--long", "-la", "-al"]);
|
|
200
|
+
const showHidden = ifFlag(args, ["-a", "--all", "-la", "-al"]);
|
|
201
|
+
|
|
37
202
|
const targetArg = getArg(args, 0, {
|
|
38
203
|
flags: ["-l", "--long", "-a", "--all", "-la", "-al"],
|
|
39
204
|
});
|
|
40
205
|
const target = resolvePath(cwd, targetArg ?? cwd);
|
|
41
206
|
assertPathAccess(authUser, target, "ls");
|
|
42
207
|
|
|
43
|
-
//
|
|
208
|
+
// Single file or symlink
|
|
44
209
|
if (shell.vfs.exists(target)) {
|
|
45
|
-
const st
|
|
46
|
-
|
|
210
|
+
const st = shell.vfs.stat(target);
|
|
211
|
+
const isLink = shell.vfs.isSymlink(target);
|
|
212
|
+
if (st.type === "file" || isLink) {
|
|
213
|
+
const name = target.split("/").pop() ?? target;
|
|
214
|
+
const color = entryColor(isLink ? 0o120777 : st.mode, st.type, isLink);
|
|
47
215
|
if (longFormat) {
|
|
48
|
-
const
|
|
49
|
-
const size
|
|
216
|
+
const mode = isLink ? 0o120777 : st.mode;
|
|
217
|
+
const size = isLink
|
|
218
|
+
? readlinkTarget(shell.vfs, target).length
|
|
219
|
+
: (st as { size?: number }).size ?? 0;
|
|
220
|
+
const perms = formatPermissions(mode, st.type, isLink);
|
|
221
|
+
const label = isLink
|
|
222
|
+
? `${colorize(name, color)} -> ${readlinkTarget(shell.vfs, target)}`
|
|
223
|
+
: colorize(name, color);
|
|
50
224
|
return {
|
|
51
|
-
stdout: `${
|
|
225
|
+
stdout: `${perms} 1 root root ${size} ${formatDate(st.updatedAt)} ${label}\n`,
|
|
52
226
|
exitCode: 0,
|
|
53
227
|
};
|
|
54
228
|
}
|
|
55
|
-
return { stdout:
|
|
229
|
+
return { stdout: `${colorize(name, color)}\n`, exitCode: 0 };
|
|
56
230
|
}
|
|
57
231
|
}
|
|
58
232
|
|
|
59
233
|
const items = shell.vfs
|
|
60
234
|
.list(target)
|
|
61
235
|
.filter((name) => showHidden || !name.startsWith("."));
|
|
236
|
+
|
|
62
237
|
const rendered = longFormat
|
|
63
|
-
? items
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const size = stat.type === "file" ? stat.size : stat.childrenCount;
|
|
68
|
-
return `${formatPermissions(stat.mode, stat.type === "directory")} 1 ${size} ${formatDate(stat.updatedAt)} ${name}${stat.type === "directory" ? "/" : ""}`;
|
|
69
|
-
})
|
|
70
|
-
.join("\n")
|
|
71
|
-
: joinListWithType(target, items, (p) => shell.vfs.stat(p));
|
|
72
|
-
return { stdout: rendered, exitCode: 0 };
|
|
238
|
+
? longListing(shell.vfs, target, items)
|
|
239
|
+
: shortListing(shell.vfs, target, items);
|
|
240
|
+
|
|
241
|
+
return { stdout: `${rendered}\n`, exitCode: 0 };
|
|
73
242
|
},
|
|
74
|
-
};
|
|
243
|
+
};
|
package/src/commands/runtime.ts
CHANGED
|
@@ -3,9 +3,9 @@ import { executeStatements } from "../SSHMimic/executor";
|
|
|
3
3
|
import type { VirtualShell } from "../VirtualShell";
|
|
4
4
|
import { parseScript } from "../VirtualShell/shellParser";
|
|
5
5
|
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
CommandMode,
|
|
7
|
+
CommandResult,
|
|
8
|
+
ShellEnv,
|
|
9
9
|
} from "../types/commands";
|
|
10
10
|
import { expandAsync, expandBraces } from "../utils/expand";
|
|
11
11
|
import { tokenizeCommand } from "../utils/tokenize";
|
|
@@ -77,6 +77,44 @@ export async function runCommandDirect(
|
|
|
77
77
|
stdin: string | undefined,
|
|
78
78
|
env: ShellEnv,
|
|
79
79
|
): Promise<CommandResult> {
|
|
80
|
+
const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
81
|
+
const invocation = [name, ...args];
|
|
82
|
+
let assignCount = 0;
|
|
83
|
+
while (assignCount < invocation.length && assignRe.test(invocation[assignCount]!)) {
|
|
84
|
+
assignCount += 1;
|
|
85
|
+
}
|
|
86
|
+
if (assignCount > 0) {
|
|
87
|
+
const assignments = invocation.slice(0, assignCount).map((token) => token.match(assignRe)!);
|
|
88
|
+
const remaining = invocation.slice(assignCount);
|
|
89
|
+
const restored: Array<[string, string | undefined]> = [];
|
|
90
|
+
for (const [, key, value] of assignments) {
|
|
91
|
+
restored.push([key!, env.vars[key!]]);
|
|
92
|
+
env.vars[key!] = value!;
|
|
93
|
+
}
|
|
94
|
+
if (remaining.length === 0) {
|
|
95
|
+
return { exitCode: 0 };
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const result = await runCommandDirect(
|
|
99
|
+
remaining[0]!,
|
|
100
|
+
remaining.slice(1),
|
|
101
|
+
authUser,
|
|
102
|
+
hostname,
|
|
103
|
+
mode,
|
|
104
|
+
cwd,
|
|
105
|
+
shell,
|
|
106
|
+
stdin,
|
|
107
|
+
env,
|
|
108
|
+
);
|
|
109
|
+
return result;
|
|
110
|
+
} finally {
|
|
111
|
+
for (const [key, value] of restored) {
|
|
112
|
+
if (value === undefined) delete env.vars[key];
|
|
113
|
+
else env.vars[key] = value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
80
118
|
const aliasVal = env.vars[`__alias_${name}`];
|
|
81
119
|
if (aliasVal) {
|
|
82
120
|
return runCommand(
|
|
@@ -250,6 +288,21 @@ export async function runCommand(
|
|
|
250
288
|
);
|
|
251
289
|
|
|
252
290
|
const parts = tokenizeCommand(expanded.trim());
|
|
291
|
+
if (parts.length === 0) return { exitCode: 0 };
|
|
292
|
+
const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
293
|
+
if (assignRe.test(parts[0]!)) {
|
|
294
|
+
return runCommandDirect(
|
|
295
|
+
parts[0]!,
|
|
296
|
+
parts.slice(1),
|
|
297
|
+
authUser,
|
|
298
|
+
hostname,
|
|
299
|
+
mode,
|
|
300
|
+
cwd,
|
|
301
|
+
shell,
|
|
302
|
+
stdin,
|
|
303
|
+
shellEnv,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
253
306
|
const commandName = parts[0]?.toLowerCase() ?? "";
|
|
254
307
|
// Apply brace expansion to each arg token
|
|
255
308
|
const args = parts.slice(1).flatMap(expandBraces);
|