typescript-virtual-container 1.4.8 → 1.5.0

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.
Files changed (173) hide show
  1. package/.vscode/settings.json +1 -1
  2. package/README.md +118 -80
  3. package/builds/self-standalone.mjs +1768 -0
  4. package/builds/standalone-wo-sftp.js +735 -268
  5. package/builds/standalone.cjs +734 -267
  6. package/builds/web-full-api.min.js +0 -1
  7. package/builds/web.min.js +0 -1
  8. package/bun.lock +3 -3
  9. package/dist/Honeypot/index.d.ts +6 -0
  10. package/dist/Honeypot/index.d.ts.map +1 -1
  11. package/dist/Honeypot/index.js +20 -0
  12. package/dist/Honeypot/index.js.map +1 -1
  13. package/dist/SSHMimic/exec.js +2 -2
  14. package/dist/SSHMimic/exec.js.map +1 -1
  15. package/dist/SSHMimic/index.d.ts +6 -5
  16. package/dist/SSHMimic/index.d.ts.map +1 -1
  17. package/dist/SSHMimic/index.js +8 -6
  18. package/dist/SSHMimic/index.js.map +1 -1
  19. package/dist/SSHMimic/sftp.d.ts +1 -0
  20. package/dist/SSHMimic/sftp.d.ts.map +1 -1
  21. package/dist/SSHMimic/sftp.js +4 -3
  22. package/dist/SSHMimic/sftp.js.map +1 -1
  23. package/dist/VirtualFileSystem/index.d.ts +14 -0
  24. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  25. package/dist/VirtualFileSystem/index.js +51 -3
  26. package/dist/VirtualFileSystem/index.js.map +1 -1
  27. package/dist/VirtualFileSystem/internalTypes.d.ts +4 -0
  28. package/dist/VirtualFileSystem/internalTypes.d.ts.map +1 -1
  29. package/dist/VirtualFileSystem/journal.d.ts +1 -0
  30. package/dist/VirtualFileSystem/journal.d.ts.map +1 -1
  31. package/dist/VirtualFileSystem/journal.js +11 -5
  32. package/dist/VirtualFileSystem/journal.js.map +1 -1
  33. package/dist/VirtualShell/idleManager.d.ts +6 -2
  34. package/dist/VirtualShell/idleManager.d.ts.map +1 -1
  35. package/dist/VirtualShell/idleManager.js +10 -6
  36. package/dist/VirtualShell/idleManager.js.map +1 -1
  37. package/dist/VirtualShell/index.d.ts +13 -1
  38. package/dist/VirtualShell/index.d.ts.map +1 -1
  39. package/dist/VirtualShell/index.js +10 -5
  40. package/dist/VirtualShell/index.js.map +1 -1
  41. package/dist/VirtualShell/shell.js +12 -12
  42. package/dist/VirtualShell/shell.js.map +1 -1
  43. package/dist/VirtualUserManager/index.d.ts +4 -2
  44. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  45. package/dist/VirtualUserManager/index.js +8 -12
  46. package/dist/VirtualUserManager/index.js.map +1 -1
  47. package/dist/commands/apt.js +3 -3
  48. package/dist/commands/apt.js.map +1 -1
  49. package/dist/commands/cd.d.ts.map +1 -1
  50. package/dist/commands/cd.js +2 -1
  51. package/dist/commands/cd.js.map +1 -1
  52. package/dist/commands/command-helpers.d.ts +5 -2
  53. package/dist/commands/command-helpers.d.ts.map +1 -1
  54. package/dist/commands/command-helpers.js.map +1 -1
  55. package/dist/commands/helpers.d.ts +1 -1
  56. package/dist/commands/helpers.d.ts.map +1 -1
  57. package/dist/commands/helpers.js +3 -2
  58. package/dist/commands/helpers.js.map +1 -1
  59. package/dist/commands/index.d.ts +1 -1
  60. package/dist/commands/index.d.ts.map +1 -1
  61. package/dist/commands/index.js +1 -1
  62. package/dist/commands/index.js.map +1 -1
  63. package/dist/commands/lsb-release.js +1 -1
  64. package/dist/commands/lsb-release.js.map +1 -1
  65. package/dist/commands/runtime.d.ts +2 -0
  66. package/dist/commands/runtime.d.ts.map +1 -1
  67. package/dist/commands/runtime.js +5 -1
  68. package/dist/commands/runtime.js.map +1 -1
  69. package/dist/index.d.ts +5 -2
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js.map +1 -1
  72. package/dist/modules/linuxRootfs.d.ts +9 -5
  73. package/dist/modules/linuxRootfs.d.ts.map +1 -1
  74. package/dist/modules/linuxRootfs.js +1079 -148
  75. package/dist/modules/linuxRootfs.js.map +1 -1
  76. package/dist/self-standalone.js +22 -12
  77. package/dist/self-standalone.js.map +1 -1
  78. package/docs/assets/hierarchy.js +1 -1
  79. package/docs/assets/navigation.js +1 -1
  80. package/docs/assets/search.js +1 -1
  81. package/docs/classes/HoneyPot.html +9 -9
  82. package/docs/classes/IdleManager.html +12 -9
  83. package/docs/classes/SshClient.html +18 -18
  84. package/docs/classes/VirtualFileSystem.html +34 -42
  85. package/docs/classes/VirtualPackageManager.html +13 -13
  86. package/docs/classes/VirtualSftpServer.html +3 -3
  87. package/docs/classes/VirtualShell.html +33 -29
  88. package/docs/classes/VirtualSshServer.html +12 -7
  89. package/docs/classes/VirtualUserManager.html +30 -30
  90. package/docs/functions/assertDiff.html +2 -2
  91. package/docs/functions/diffSnapshots.html +2 -2
  92. package/docs/functions/formatDiff.html +2 -2
  93. package/docs/functions/getArg.html +3 -3
  94. package/docs/functions/getFlag.html +2 -2
  95. package/docs/functions/ifFlag.html +2 -2
  96. package/docs/hierarchy.html +1 -1
  97. package/docs/index.html +11 -9
  98. package/docs/interfaces/AuditLogEntry.html +3 -3
  99. package/docs/interfaces/CommandContext.html +13 -13
  100. package/docs/interfaces/CommandResult.html +13 -13
  101. package/docs/interfaces/ExecStream.html +6 -6
  102. package/docs/interfaces/HoneyPotStats.html +5 -3
  103. package/docs/interfaces/IdleManagerOptions.html +3 -3
  104. package/docs/interfaces/InstalledPackage.html +11 -11
  105. package/docs/interfaces/NanoEditorSession.html +5 -5
  106. package/docs/interfaces/PackageDefinition.html +14 -14
  107. package/docs/interfaces/PackageFile.html +5 -5
  108. package/docs/interfaces/PasswordChallenge.html +16 -0
  109. package/docs/interfaces/RemoveOptions.html +3 -3
  110. package/docs/interfaces/ShellEnv.html +4 -4
  111. package/docs/interfaces/ShellModule.html +8 -8
  112. package/docs/interfaces/ShellProperties.html +5 -5
  113. package/docs/interfaces/ShellStream.html +7 -7
  114. package/docs/interfaces/SudoChallenge.html +9 -9
  115. package/docs/interfaces/VfsBaseNode.html +7 -7
  116. package/docs/interfaces/VfsDiff.html +6 -6
  117. package/docs/interfaces/VfsDiffEntry.html +4 -4
  118. package/docs/interfaces/VfsDiffModified.html +6 -6
  119. package/docs/interfaces/VfsDirectoryNode.html +8 -8
  120. package/docs/interfaces/VfsFileNode.html +9 -9
  121. package/docs/interfaces/VfsOptions.html +6 -6
  122. package/docs/interfaces/VfsSnapshot.html +3 -3
  123. package/docs/interfaces/VfsSnapshotBaseNode.html +4 -4
  124. package/docs/interfaces/VfsSnapshotDirectoryNode.html +5 -5
  125. package/docs/interfaces/VfsSnapshotFileNode.html +6 -6
  126. package/docs/interfaces/VirtualActiveSession.html +12 -0
  127. package/docs/interfaces/VirtualSftpServerOptions.html +7 -0
  128. package/docs/interfaces/VirtualShellVfsLike.html +15 -0
  129. package/docs/interfaces/VirtualShellVfsOptions.html +3 -0
  130. package/docs/interfaces/WriteFileOptions.html +4 -4
  131. package/docs/modules.html +1 -1
  132. package/docs/types/ArgParseOptions.html +4 -0
  133. package/docs/types/CommandMode.html +2 -2
  134. package/docs/types/CommandOutcome.html +2 -2
  135. package/docs/types/IdleState.html +1 -1
  136. package/docs/types/VfsNodeStats.html +2 -2
  137. package/docs/types/VfsNodeType.html +2 -2
  138. package/docs/types/VfsPersistenceMode.html +2 -2
  139. package/docs/types/VfsSnapshotNode.html +2 -2
  140. package/examples/web.min.js +0 -1
  141. package/package.json +11 -11
  142. package/scripts/generate-manuals-bundle.mjs +1 -1
  143. package/scripts/publish-package.sh +1 -1
  144. package/src/Honeypot/index.ts +24 -0
  145. package/src/SSHMimic/exec.ts +2 -2
  146. package/src/SSHMimic/index.ts +9 -6
  147. package/src/SSHMimic/sftp.ts +10 -3
  148. package/src/VirtualFileSystem/index.ts +46 -3
  149. package/src/VirtualFileSystem/internalTypes.ts +4 -0
  150. package/src/VirtualFileSystem/journal.ts +11 -5
  151. package/src/VirtualShell/idleManager.ts +10 -6
  152. package/src/VirtualShell/index.ts +18 -3
  153. package/src/VirtualShell/shell.ts +12 -12
  154. package/src/VirtualUserManager/index.ts +15 -13
  155. package/src/commands/apt.ts +3 -3
  156. package/src/commands/cd.ts +2 -1
  157. package/src/commands/command-helpers.ts +5 -1
  158. package/src/commands/helpers.ts +3 -2
  159. package/src/commands/index.ts +1 -1
  160. package/src/commands/lsb-release.ts +1 -1
  161. package/src/commands/runtime.ts +6 -1
  162. package/src/index.ts +5 -1
  163. package/src/modules/linuxRootfs.ts +1293 -207
  164. package/src/self-standalone.ts +26 -12
  165. package/tests/new-features.test.ts +2 -2
  166. package/tests/sftp.test.ts +13 -13
  167. package/typedoc.json +45 -0
  168. package/builds/self-standalone.js +0 -1300
  169. package/builds/self-standalone.js.map +0 -7
  170. package/builds/standalone-wo-sftp.js.map +0 -7
  171. package/builds/standalone.cjs.map +0 -7
  172. package/builds/web-full-api.min.js.map +0 -7
  173. package/builds/web.min.js.map +0 -7
@@ -1,2 +1,2 @@
1
- <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsNodeType | typescript-virtual-container</title><meta name="description" content="Documentation for typescript-virtual-container"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">typescript-virtual-container</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">VfsNodeType</a></li></ul><h1>Type Alias VfsNodeType</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">VfsNodeType</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">&quot;file&quot;</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;directory&quot;</span></div><div class="tsd-comment tsd-typography"><p>Supported virtual node kinds.</p>
2
- </div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/d6b2c55e130d43f4ac8acc8d9d91e80481fa4c36/src/types/vfs.ts#L2">src/types/vfs.ts:2</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">typescript-virtual-container</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsNodeType | typescript-virtual-container - v1.5.0</title><meta name="description" content="Documentation for typescript-virtual-container"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">typescript-virtual-container - v1.5.0</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">VfsNodeType</a></li></ul><h1>Type Alias VfsNodeType</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">VfsNodeType</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">&quot;file&quot;</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;directory&quot;</span></div><div class="tsd-comment tsd-typography"><p>Supported virtual node kinds.</p>
2
+ </div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/52880451442508494c6f496513aae8ae8d1600b8/src/types/vfs.ts#L2">src/types/vfs.ts:2</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">typescript-virtual-container - v1.5.0</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
@@ -1,5 +1,5 @@
1
- <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsPersistenceMode | typescript-virtual-container</title><meta name="description" content="Documentation for typescript-virtual-container"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">typescript-virtual-container</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">VfsPersistenceMode</a></li></ul><h1>Type Alias VfsPersistenceMode</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">VfsPersistenceMode</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">&quot;memory&quot;</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;fs&quot;</span></div><div class="tsd-comment tsd-typography"><p>&quot;memory&quot; — pure in-memory, no disk I/O (default).</p>
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsPersistenceMode | typescript-virtual-container - v1.5.0</title><meta name="description" content="Documentation for typescript-virtual-container"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">typescript-virtual-container - v1.5.0</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">VfsPersistenceMode</a></li></ul><h1>Type Alias VfsPersistenceMode</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">VfsPersistenceMode</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">&quot;memory&quot;</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;fs&quot;</span></div><div class="tsd-comment tsd-typography"><p>&quot;memory&quot; — pure in-memory, no disk I/O (default).</p>
2
2
  <p>&quot;fs&quot; — mirrors the VFS tree to a directory on the host filesystem.
3
3
  <code>snapshotPath</code> must be set to the directory where the binary
4
4
  snapshot file will be read/written (<code>vfs-snapshot.vfsb</code>).</p>
5
- </div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/d6b2c55e130d43f4ac8acc8d9d91e80481fa4c36/src/VirtualFileSystem/index.ts#L36">src/VirtualFileSystem/index.ts:36</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">typescript-virtual-container</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
5
+ </div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/52880451442508494c6f496513aae8ae8d1600b8/src/VirtualFileSystem/index.ts#L36">src/VirtualFileSystem/index.ts:36</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">typescript-virtual-container - v1.5.0</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
@@ -1,2 +1,2 @@
1
- <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsSnapshotNode | typescript-virtual-container</title><meta name="description" content="Documentation for typescript-virtual-container"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">typescript-virtual-container</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">VfsSnapshotNode</a></li></ul><h1>Type Alias VfsSnapshotNode</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">VfsSnapshotNode</span><span class="tsd-signature-symbol">:</span> <a href="../interfaces/VfsSnapshotFileNode.html" class="tsd-signature-type tsd-kind-interface">VfsSnapshotFileNode</a> <span class="tsd-signature-symbol">|</span> <a href="../interfaces/VfsSnapshotDirectoryNode.html" class="tsd-signature-type tsd-kind-interface">VfsSnapshotDirectoryNode</a></div><div class="tsd-comment tsd-typography"><p>Union of serialized snapshot node variants.</p>
2
- </div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/d6b2c55e130d43f4ac8acc8d9d91e80481fa4c36/src/types/vfs.ts#L76">src/types/vfs.ts:76</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">typescript-virtual-container</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsSnapshotNode | typescript-virtual-container - v1.5.0</title><meta name="description" content="Documentation for typescript-virtual-container"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">typescript-virtual-container - v1.5.0</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">VfsSnapshotNode</a></li></ul><h1>Type Alias VfsSnapshotNode</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">VfsSnapshotNode</span><span class="tsd-signature-symbol">:</span> <a href="../interfaces/VfsSnapshotFileNode.html" class="tsd-signature-type tsd-kind-interface">VfsSnapshotFileNode</a> <span class="tsd-signature-symbol">|</span> <a href="../interfaces/VfsSnapshotDirectoryNode.html" class="tsd-signature-type tsd-kind-interface">VfsSnapshotDirectoryNode</a></div><div class="tsd-comment tsd-typography"><p>Union of serialized snapshot node variants.</p>
2
+ </div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/52880451442508494c6f496513aae8ae8d1600b8/src/types/vfs.ts#L76">src/types/vfs.ts:76</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">typescript-virtual-container - v1.5.0</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
@@ -10,4 +10,3 @@ var R=Object.defineProperty;var F=(o,e,t)=>e in o?R(o,e,{enumerable:!0,configura
10
10
  `),this.vfs.exists("/etc/hosts")||this.vfs.writeFile("/etc/hosts",`127.0.0.1 localhost
11
11
  ::1 localhost
12
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
- //# 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.8",
7
+ "version": "1.5.0",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
@@ -28,24 +28,24 @@
28
28
  "test-battery": "bun test tests/",
29
29
  "test-salve": "for f in tests/*.test.ts; do echo \"\\n🧪 Testing $f...\"; bun test \"$f\" --timeout 10000; sleep 0.25; done",
30
30
  "build": "tsc --project tsconfig.json",
31
- "deploy:npm": "npm publish --access public",
31
+ "deploy:npm": "bun publish --access public",
32
32
  "bench": "rm -rf .benchmark-shells/ && bun benchmark-virtualshell.ts",
33
- "standalone-build:wo-sftp": "bunx esbuild src/standalone-wo-sftp.ts --bundle --platform=node --target=node18 --outfile=builds/standalone-wo-sftp.js --tree-shaking=true --minify --sourcemap --banner:js='#!/usr/bin/env node'",
34
- "web-build": "bunx esbuild src/web.ts --bundle --platform=browser --format=esm --target=es2020 --outfile=builds/web.min.js --tree-shaking=true --minify --sourcemap",
35
- "web-build-iife": "bunx esbuild src/web.ts --bundle --platform=browser --format=iife --target=es2020 --outfile=builds/web-iife.min.js --tree-shaking=true --minify --sourcemap --global-name=WebShellLib",
33
+ "standalone-build:wo-sftp": "bunx esbuild src/standalone-wo-sftp.ts --bundle --platform=node --target=node18 --outfile=builds/standalone-wo-sftp.js --tree-shaking=true --minify --banner:js='#!/usr/bin/env node'",
34
+ "web-build": "bunx esbuild src/web.ts --bundle --platform=browser --format=esm --target=es2020 --outfile=builds/web.min.js --tree-shaking=true --minify",
35
+ "web-build-iife": "bunx esbuild src/web.ts --bundle --platform=browser --format=iife --target=es2020 --outfile=builds/web-iife.min.js --tree-shaking=true --minify --global-name=WebShellLib",
36
36
  "example-build": "bun run web-build && cp builds/web.min.js examples/web.min.js",
37
- "example-serve": "cd examples && node server.js",
38
- "web-full-build": "bunx esbuild src/web-api.ts --bundle --platform=browser --format=esm --target=es2020 --outfile=builds/web-full-api.min.js --tree-shaking=true --minify --sourcemap --alias:node:events=./polyfills/node_events/index.js --alias:node:path=./polyfills/node_path/index.js --alias:node:os=./polyfills/node_os/index.js --alias:node:fs=./polyfills/node_fs/index.js --alias:node:fs/promises=./polyfills/node_fs/promises.js --alias:node:crypto=./polyfills/node_crypto/index.js --alias:node:child_process=./polyfills/node_child_process/index.js --alias:node:zlib=./polyfills/node_zlib/index.js --alias:node:vm=./polyfills/node_vm/index.js",
37
+ "example-serve": "cd examples && bun server.js",
38
+ "web-full-build": "bunx esbuild src/web-api.ts --bundle --platform=browser --format=esm --target=es2020 --outfile=builds/web-full-api.min.js --tree-shaking=true --minify --alias:node:events=./polyfills/node_events/index.js --alias:node:path=./polyfills/node_path/index.js --alias:node:os=./polyfills/node_os/index.js --alias:node:fs=./polyfills/node_fs/index.js --alias:node:fs/promises=./polyfills/node_fs/promises.js --alias:node:crypto=./polyfills/node_crypto/index.js --alias:node:child_process=./polyfills/node_child_process/index.js --alias:node:zlib=./polyfills/node_zlib/index.js --alias:node:vm=./polyfills/node_vm/index.js",
39
39
  "publish-package": "bash ./scripts/publish-package.sh",
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'",
41
- "standalone-build": "bunx esbuild src/standalone.ts --bundle --platform=node --target=node18 --outfile=builds/standalone.cjs --tree-shaking=true --minify --sourcemap --banner:js='#!/usr/bin/env node'",
40
+ "self-standalone-build": "bunx esbuild src/self-standalone.ts --bundle --platform=node --format=esm --target=node18 --outfile=builds/self-standalone.mjs --tree-shaking=true --minify --banner:js='#!/usr/bin/env node'",
41
+ "standalone-build": "bunx esbuild src/standalone.ts --bundle --platform=node --target=node18 --outfile=builds/standalone.cjs --tree-shaking=true --minify --banner:js='#!/usr/bin/env node'",
42
42
  "build-all": "bun run generate-manuals && 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
43
  "publish-doc": "bunx typedoc && bunx gh-pages -d docs",
44
- "generate-manuals": "node scripts/generate-manuals-bundle.mjs"
44
+ "generate-manuals": "bun scripts/generate-manuals-bundle.mjs"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@biomejs/biome": "^2.4.15",
48
- "@types/bun": "^1.3.13",
48
+ "@types/bun": "^1.3.14",
49
49
  "@types/node": "^25.7.0",
50
50
  "@types/ssh2": "^1.15.5",
51
51
  "gh-pages": "^6.3.0",
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  /**
3
3
  * scripts/generate-manuals-bundle.mjs
4
4
  *
@@ -11,7 +11,7 @@ if [[ -n "$(git status --porcelain)" ]]; then
11
11
  exit 1
12
12
  fi
13
13
 
14
- VERSION="$(node -p "require('./package.json').version")"
14
+ VERSION="$(bun -e "console.log(require('./package.json').version)")"
15
15
  TAG="v${VERSION}"
16
16
 
17
17
  if [[ -z "${VERSION}" ]]; then
@@ -43,6 +43,8 @@ export interface HoneyPotStats {
43
43
  userDeleted: number;
44
44
  clientConnects: number;
45
45
  clientDisconnects: number;
46
+ shellFreezes: number;
47
+ shellThaws: number;
46
48
  }
47
49
 
48
50
  const perf: PerfLogger = createPerfLogger("HoneyPot");
@@ -68,9 +70,13 @@ export class HoneyPot {
68
70
  userDeleted: 0,
69
71
  clientConnects: 0,
70
72
  clientDisconnects: 0,
73
+ shellFreezes: 0,
74
+ shellThaws: 0,
71
75
  };
72
76
 
73
77
  private maxLogSize: number;
78
+ /** Reference kept so VFS events can ping the shell's idle manager. */
79
+ private _shell: VirtualShell | null = null;
74
80
 
75
81
  /**
76
82
  * Creates a new HoneyPot instance.
@@ -99,6 +105,7 @@ export class HoneyPot {
99
105
  sftp?: SftpMimic,
100
106
  ): void {
101
107
  perf.mark("attach");
108
+ this._shell = shell;
102
109
  this.attachVirtualShell(shell);
103
110
  this.attachVirtualFileSystem(vfs);
104
111
  this.attachVirtualUserManager(users);
@@ -130,23 +137,38 @@ export class HoneyPot {
130
137
  this.log("VirtualShell", "session:start", data);
131
138
  },
132
139
  );
140
+
141
+ (shell as EventEmitter).on("shell:freeze", () => {
142
+ this.stats.shellFreezes++;
143
+ this.log("VirtualShell", "shell:freeze", {});
144
+ });
145
+
146
+ (shell as EventEmitter).on("shell:thaw", () => {
147
+ this.stats.shellThaws++;
148
+ this.log("VirtualShell", "shell:thaw", {});
149
+ });
133
150
  }
134
151
 
135
152
  /**
136
153
  * Attaches to VirtualFileSystem events.
154
+ * Also pings the shell's idle manager so SFTP/direct VFS activity
155
+ * counts as activity and prevents spurious freezes.
137
156
  */
138
157
  private attachVirtualFileSystem(vfs: VirtualFileSystem): void {
139
158
  (vfs as EventEmitter).on("file:read", (data: Record<string, unknown>) => {
140
159
  this.stats.fileReads++;
160
+ this._shell?.pingIdle();
141
161
  this.log("VirtualFileSystem", "file:read", data);
142
162
  });
143
163
 
144
164
  (vfs as EventEmitter).on("file:write", (data: Record<string, unknown>) => {
145
165
  this.stats.fileWrites++;
166
+ this._shell?.pingIdle();
146
167
  this.log("VirtualFileSystem", "file:write", data);
147
168
  });
148
169
 
149
170
  (vfs as EventEmitter).on("dir:create", (data: Record<string, unknown>) => {
171
+ this._shell?.pingIdle();
150
172
  this.log("VirtualFileSystem", "dir:create", data);
151
173
  });
152
174
 
@@ -354,6 +376,8 @@ export class HoneyPot {
354
376
  userDeleted: 0,
355
377
  clientConnects: 0,
356
378
  clientDisconnects: 0,
379
+ shellFreezes: 0,
380
+ shellThaws: 0,
357
381
  };
358
382
  }
359
383
 
@@ -1,4 +1,4 @@
1
- import { makeDefaultEnv, runCommand } from "../commands";
1
+ import { makeDefaultEnv, runCommand, userHome } from "../commands";
2
2
  import type { ExecStream } from "../types/streams";
3
3
  import type { VirtualShell } from "../VirtualShell";
4
4
 
@@ -22,7 +22,7 @@ export function runExec(
22
22
  authUser,
23
23
  hostname,
24
24
  "exec",
25
- `/home/${authUser}`,
25
+ userHome(authUser),
26
26
  shell,
27
27
  undefined,
28
28
  makeDefaultEnv(authUser, hostname),
@@ -1,6 +1,7 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import { Server as SshServer } from "ssh2";
3
3
  import { VirtualShell } from "../VirtualShell";
4
+ import { userHome } from "../commands";
4
5
  import { createPerfLogger, type PerfLogger } from "../utils/perfLogger";
5
6
  import { runExec } from "./exec";
6
7
  import { loadOrCreateHostKey } from "./hostKey";
@@ -26,6 +27,7 @@ const DEV = !!process.env.DEV_MODE;
26
27
  const devLog = DEV ? console.log.bind(console) : () => {};
27
28
 
28
29
 
30
+ /** @internal */
29
31
  interface RateLimitEntry {
30
32
  attempts: number;
31
33
  lockedUntil: number;
@@ -45,11 +47,12 @@ class SshMimic extends EventEmitter {
45
47
  /**
46
48
  * Creates a new SSH mimic server instance.
47
49
  *
48
- * @param port TCP port to bind on localhost.
49
- * @param hostname Virtual hostname used for the SSH ident and default shell label.
50
- * @param shell Optional preconfigured virtual shell instance to reuse.
51
- * @param maxAuthAttempts Max failed attempts per IP before lockout (default: 5).
52
- * @param lockoutDurationMs Lockout window in ms after exceeding attempts (default: 60 000).
50
+ * @param options - Configuration object for the SSH server.
51
+ * @param options.port - TCP port to bind on localhost.
52
+ * @param options.hostname - Virtual hostname used for the SSH ident and default shell label.
53
+ * @param options.shell - Optional preconfigured virtual shell instance to reuse.
54
+ * @param options.maxAuthAttempts - Max failed attempts per IP before lockout (default: 5).
55
+ * @param options.lockoutDurationMs - Lockout window in ms after exceeding attempts (default: 60 000).
53
56
  */
54
57
  constructor({
55
58
  port,
@@ -102,7 +105,7 @@ class SshMimic extends EventEmitter {
102
105
  // ── Home directory bootstrap ─────────────────────────────────────────────
103
106
 
104
107
  private ensureHomeDir(authUser: string): void {
105
- const homePath = `/home/${authUser}`;
108
+ const homePath = userHome(authUser);
106
109
  if (!this.shell.vfs.exists(homePath)) {
107
110
  this.shell.vfs.mkdir(homePath, 0o755);
108
111
  this.shell.vfs.writeFile(
@@ -7,6 +7,7 @@ import type { VfsNodeStats } from "../types/vfs";
7
7
  import type { PerfLogger } from "../utils/perfLogger";
8
8
  import { createPerfLogger } from "../utils/perfLogger";
9
9
  import type VirtualFileSystem from "../VirtualFileSystem";
10
+ import { userHome } from "../commands";
10
11
  import { VirtualShell } from "../VirtualShell";
11
12
  import type { VirtualUserManager } from "../VirtualUserManager";
12
13
  import { loadOrCreateHostKey } from "./hostKey";
@@ -41,6 +42,7 @@ const OPEN_MODE = {
41
42
 
42
43
  const perf: PerfLogger = createPerfLogger("SftpMimic");
43
44
 
45
+ /** @internal */
44
46
  interface SftpFileHandle {
45
47
  type: "file";
46
48
  path: string;
@@ -48,6 +50,7 @@ interface SftpFileHandle {
48
50
  buffer: Buffer;
49
51
  }
50
52
 
53
+ /** @internal */
51
54
  interface SftpDirHandle {
52
55
  type: "dir";
53
56
  path: string;
@@ -55,8 +58,10 @@ interface SftpDirHandle {
55
58
  index: number;
56
59
  }
57
60
 
61
+ /** @internal */
58
62
  type SftpHandle = SftpFileHandle | SftpDirHandle;
59
63
 
64
+ /** @internal */
60
65
  interface SftpAttributes {
61
66
  mode: number;
62
67
  uid: number;
@@ -66,6 +71,7 @@ interface SftpAttributes {
66
71
  mtime: number | Date;
67
72
  }
68
73
 
74
+ /** @internal */
69
75
  interface SftpServerStream {
70
76
  on(
71
77
  event: "OPEN",
@@ -139,6 +145,7 @@ interface SftpServerStream {
139
145
  ): void;
140
146
  }
141
147
 
148
+ /** Options for {@link SftpMimic} constructor. */
142
149
  export interface SftpMimicOptions {
143
150
  port: number;
144
151
  hostname?: string;
@@ -240,7 +247,7 @@ export class SftpMimic extends EventEmitter {
240
247
  this.getVfs().mkdir(homeRoot, 0o755);
241
248
  }
242
249
 
243
- const homePath = `/home/${authUser}`;
250
+ const homePath = userHome(authUser);
244
251
  if (!this.getVfs().exists(homePath)) {
245
252
  this.getVfs().mkdir(homePath, 0o755);
246
253
  this.getVfs().writeFile(
@@ -378,7 +385,7 @@ export class SftpMimic extends EventEmitter {
378
385
  * This is standard SFTP behavior where the "working directory" is always the home.
379
386
  */
380
387
  private resolveRequestPath(requestPath: string, authUser: string): string {
381
- const homePath = `/home/${authUser}`;
388
+ const homePath = userHome(authUser);
382
389
 
383
390
  // Empty path or "." → resolve to home directory
384
391
  if (!requestPath || requestPath === ".") {
@@ -402,7 +409,7 @@ export class SftpMimic extends EventEmitter {
402
409
  * @returns true if path is within home, false if traversal attempt detected
403
410
  */
404
411
  private isPathWithinHome(targetPath: string, authUser: string): boolean {
405
- const homePath = `/home/${authUser}`;
412
+ const homePath = userHome(authUser);
406
413
  const normalized = path.posix.normalize(targetPath);
407
414
 
408
415
  // Allow access to home directory itself
@@ -134,10 +134,11 @@ class VirtualFileSystem extends EventEmitter {
134
134
  this.journalFile = path.resolve(options.snapshotPath, "vfs-journal.bin");
135
135
  this.evictionThreshold = options.evictionThresholdBytes ?? 64 * 1024; // 64 KB default
136
136
  this.flushAfterNWrites = options.flushAfterNWrites ?? 500;
137
- const intervalMs = options.flushIntervalMs ?? 30_000;
137
+ const intervalMs = options.flushIntervalMs ?? 1_000;
138
138
  if (intervalMs > 0) {
139
139
  this._flushTimer = setInterval(() => {
140
- if (this._dirty) void this._autoFlush();
140
+ const dirty = this._dirty;
141
+ if (dirty) void this._autoFlush();
141
142
  }, intervalMs);
142
143
  // Don't block process exit on this timer
143
144
  if (typeof this._flushTimer === "object" && this._flushTimer !== null && "unref" in this._flushTimer) {
@@ -303,7 +304,8 @@ class VirtualFileSystem extends EventEmitter {
303
304
 
304
305
  const dir = path.dirname(this.snapshotFile);
305
306
  fsSync.mkdirSync(dir, { recursive: true });
306
- const binary = encodeVfs(this.root);
307
+ const root = this.root;
308
+ const binary = encodeVfs(root);
307
309
  fsSync.writeFileSync(this.snapshotFile, binary);
308
310
  // Checkpoint complete — truncate the journal (entries are now in the snapshot)
309
311
  if (this.journalFile) truncateJournal(this.journalFile);
@@ -382,6 +384,47 @@ class VirtualFileSystem extends EventEmitter {
382
384
  try { this.root = root; } finally { this._replayMode = prev; }
383
385
  }
384
386
 
387
+ /**
388
+ * Merge a static rootfs tree into the existing live tree.
389
+ * Used by `bootstrapLinuxRootfs` when a persisted snapshot already exists,
390
+ * to layer in missing system files without overwriting user data.
391
+ *
392
+ * Rules:
393
+ * - Directories: recurse — never overwrite a live dir with an empty one.
394
+ * - Files/stubs: only written if the path does NOT yet exist in the live tree.
395
+ * This ensures user-created files always win over static defaults.
396
+ *
397
+ * @internal
398
+ */
399
+ public mergeRootTree(incoming: InternalDirectoryNode): void {
400
+ const prev = this._replayMode;
401
+ this._replayMode = true;
402
+ try { this._mergeDir(this.root, incoming); } finally { this._replayMode = prev; }
403
+ }
404
+
405
+ private _mergeDir(live: InternalDirectoryNode, incoming: InternalDirectoryNode): void {
406
+ for (const [name, node] of Object.entries(incoming.children)) {
407
+ const existing = live.children[name];
408
+ if (node.type === "directory") {
409
+ if (!existing) {
410
+ // Dir doesn't exist yet — add it
411
+ live.children[name] = node;
412
+ live._childCount++;
413
+ } else if (existing.type === "directory") {
414
+ // Both dirs — recurse
415
+ this._mergeDir(existing, node);
416
+ }
417
+ // existing is a file where dir expected — leave user file alone
418
+ } else {
419
+ // File or stub — only add if not already present
420
+ if (!existing) {
421
+ live.children[name] = node;
422
+ live._childCount++;
423
+ }
424
+ }
425
+ }
426
+ }
427
+
385
428
  /** Serialise current tree to VFSB binary. Used for the static rootfs cache. */
386
429
  public encodeBinary(): Buffer {
387
430
  return encodeVfs(this.root);
@@ -1,3 +1,4 @@
1
+ /** @internal */
1
2
  export type InternalNode = InternalFileNode | InternalStubNode | InternalDirectoryNode;
2
3
 
3
4
  interface InternalBaseNode {
@@ -9,6 +10,7 @@ interface InternalBaseNode {
9
10
  updatedAt: number;
10
11
  }
11
12
 
13
+ /** @internal */
12
14
  export interface InternalFileNode extends InternalBaseNode {
13
15
  type: "file";
14
16
  content: Buffer;
@@ -23,6 +25,7 @@ export interface InternalFileNode extends InternalBaseNode {
23
25
  * Lazy stub — stores static rootfs file content as a plain string.
24
26
  * No Buffer allocation until the file is actually read or written.
25
27
  * On first write, promoted to a real InternalFileNode.
28
+ * @internal
26
29
  */
27
30
  export interface InternalStubNode extends InternalBaseNode {
28
31
  type: "stub";
@@ -30,6 +33,7 @@ export interface InternalStubNode extends InternalBaseNode {
30
33
  stubContent: string;
31
34
  }
32
35
 
36
+ /** @internal */
33
37
  export interface InternalDirectoryNode extends InternalBaseNode {
34
38
  type: "directory";
35
39
  /** Null-prototype object — avoids Map overhead (~40% less RAM per entry). */
@@ -32,6 +32,7 @@ export const JournalOp = {
32
32
 
33
33
  export type JournalOp = typeof JournalOp[keyof typeof JournalOp];
34
34
 
35
+ /** @internal */
35
36
  export interface JournalEntry {
36
37
  op: JournalOp;
37
38
  path: string;
@@ -141,11 +142,16 @@ export function decodeJournal(buf: Buffer): JournalEntry[] {
141
142
  /** Append a single entry to the journal file (O_APPEND, atomic write). */
142
143
  export function appendJournalEntry(journalPath: string, entry: JournalEntry): void {
143
144
  const buf = encodeEntry(entry);
144
- const fd = fsSync.openSync(journalPath, fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_APPEND);
145
- try {
146
- fsSync.writeSync(fd, buf);
147
- } finally {
148
- fsSync.closeSync(fd);
145
+ console.log(`[Journal] Appending entry: op=${entry.op} path=${entry.path} ${entry.content ? `content_len=${entry.content.length}` : ""}${entry.mode ? ` mode=${entry.mode.toString(8)}` : ""}${entry.dest ? ` dest=${entry.dest}` : ""}`);
146
+ if (fsSync.existsSync(journalPath)) {
147
+ const fd = fsSync.openSync(journalPath, fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_APPEND);
148
+ try {
149
+ fsSync.writeSync(fd, buf);
150
+ } finally {
151
+ fsSync.closeSync(fd);
152
+ }
153
+ } else {
154
+ fsSync.writeFileSync(journalPath, buf);
149
155
  }
150
156
  }
151
157
 
@@ -78,16 +78,20 @@ export class IdleManager extends EventEmitter {
78
78
  clearInterval(this._checkTimer);
79
79
  this._checkTimer = null;
80
80
  }
81
- if (this._state === "frozen") await this._thaw();
81
+ if (this._state === "frozen") this._thaw();
82
82
  }
83
83
 
84
84
  /**
85
- * Signal activity — resets the idle clock and thaws if frozen.
85
+ * Signal activity — resets the idle clock and thaws synchronously if frozen.
86
86
  * Call this before every exec / keypress / session event.
87
+ *
88
+ * Thaw is intentionally synchronous: decodeVfs() is a pure CPU operation
89
+ * (~0.07 ms) with no I/O, so it is safe to block briefly on the hot path.
90
+ * This guarantees the VFS tree is fully restored before any command runs.
87
91
  */
88
- public async ping(): Promise<void> {
92
+ public ping(): void {
89
93
  this._lastActivity = Date.now();
90
- if (this._state === "frozen") await this._thaw();
94
+ if (this._state === "frozen") this._thaw();
91
95
  }
92
96
 
93
97
  /** Current idle state. */
@@ -121,9 +125,9 @@ export class IdleManager extends EventEmitter {
121
125
  this.emit("freeze");
122
126
  }
123
127
 
124
- private async _thaw(): Promise<void> {
128
+ private _thaw(): void {
125
129
  if (this._state !== "frozen" || !this._frozenBuffer) return;
126
- // Reconstruct the tree from the frozen buffer (~0.07 ms for typical rootfs)
130
+ // Reconstruct the tree from the frozen buffer (~0.07 ms pure CPU, no I/O)
127
131
  const root = decodeVfs(this._frozenBuffer);
128
132
  this.vfs.importRootTree(root);
129
133
  this._frozenBuffer = null;