typescript-virtual-container 1.5.3 → 1.5.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.
Files changed (371) hide show
  1. package/README.md +44 -532
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/SSHMimic/executor.js +23 -5
  4. package/dist/VirtualPackageManager/index.js +10 -0
  5. package/dist/VirtualShell/shell.js +158 -11
  6. package/dist/commands/basename.d.ts +13 -0
  7. package/dist/commands/basename.js +45 -0
  8. package/dist/commands/bc.d.ts +2 -0
  9. package/dist/commands/bc.js +28 -0
  10. package/dist/commands/file.d.ts +8 -0
  11. package/dist/commands/file.js +57 -0
  12. package/dist/commands/fun.d.ts +32 -0
  13. package/dist/commands/fun.js +172 -0
  14. package/dist/commands/ip.d.ts +7 -0
  15. package/dist/commands/ip.js +52 -0
  16. package/dist/commands/jobs.d.ts +4 -0
  17. package/dist/commands/jobs.js +27 -0
  18. package/dist/commands/last.d.ts +13 -0
  19. package/dist/commands/last.js +68 -0
  20. package/dist/commands/manuals-bundle.js +598 -6
  21. package/dist/commands/registry.js +30 -2
  22. package/dist/commands/runtime.js +24 -3
  23. package/dist/commands/set.js +20 -0
  24. package/dist/commands/sh.js +74 -1
  25. package/dist/commands/tput.d.ts +13 -0
  26. package/dist/commands/tput.js +76 -0
  27. package/dist/commands/w.d.ts +7 -0
  28. package/dist/commands/w.js +38 -0
  29. package/dist/utils/expand.d.ts +12 -0
  30. package/dist/utils/expand.js +87 -1
  31. package/package.json +9 -3
  32. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
  33. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
  34. package/.github/dependabot.yml +0 -27
  35. package/.github/pull_request_template.md +0 -21
  36. package/.github/workflows/create-pull-request.yml +0 -85
  37. package/.github/workflows/publish.yml +0 -25
  38. package/.github/workflows/test-battery.yml +0 -102
  39. package/.vscode/settings.json +0 -20
  40. package/CODE_OF_CONDUCT.md +0 -39
  41. package/CONTRIBUTING.md +0 -59
  42. package/HONEYPOT.md +0 -358
  43. package/SECURITY.md +0 -33
  44. package/benchmark-results.txt +0 -40
  45. package/benchmark-virtualshell.ts +0 -88
  46. package/biome.json +0 -37
  47. package/build.js +0 -22
  48. package/builds/fortune-nyx-v1.5.3-directbash-k6.1.0.mjs +0 -1764
  49. package/builds/fortune-nyx-v1.5.3-ssh-nosftp.js +0 -1764
  50. package/builds/fortune-nyx-v1.5.3-ssh.cjs +0 -1765
  51. package/builds/fortune-nyx-v1.5.3-web.min.js +0 -17036
  52. package/bun.lock +0 -244
  53. package/docs/.nojekyll +0 -1
  54. package/docs/app.js +0 -1751
  55. package/docs/assets/hierarchy.js +0 -1
  56. package/docs/assets/highlight.css +0 -162
  57. package/docs/assets/icons.js +0 -18
  58. package/docs/assets/icons.svg +0 -1
  59. package/docs/assets/main.js +0 -60
  60. package/docs/assets/navigation.js +0 -1
  61. package/docs/assets/search.js +0 -1
  62. package/docs/assets/style.css +0 -1633
  63. package/docs/classes/HoneyPot.html +0 -31
  64. package/docs/classes/IdleManager.html +0 -162
  65. package/docs/classes/SshClient.html +0 -66
  66. package/docs/classes/VirtualFileSystem.html +0 -279
  67. package/docs/classes/VirtualPackageManager.html +0 -63
  68. package/docs/classes/VirtualSftpServer.html +0 -169
  69. package/docs/classes/VirtualShell.html +0 -285
  70. package/docs/classes/VirtualSshServer.html +0 -182
  71. package/docs/classes/VirtualUserManager.html +0 -276
  72. package/docs/demo.html +0 -82
  73. package/docs/functions/assertDiff.html +0 -6
  74. package/docs/functions/diffSnapshots.html +0 -7
  75. package/docs/functions/formatDiff.html +0 -6
  76. package/docs/functions/getArg.html +0 -13
  77. package/docs/functions/getFlag.html +0 -15
  78. package/docs/functions/ifFlag.html +0 -11
  79. package/docs/hierarchy.html +0 -1
  80. package/docs/index.html +0 -1869
  81. package/docs/interfaces/AuditLogEntry.html +0 -6
  82. package/docs/interfaces/CommandContext.html +0 -22
  83. package/docs/interfaces/CommandResult.html +0 -26
  84. package/docs/interfaces/ExecStream.html +0 -11
  85. package/docs/interfaces/HoneyPotStats.html +0 -16
  86. package/docs/interfaces/IdleManagerOptions.html +0 -7
  87. package/docs/interfaces/InstalledPackage.html +0 -20
  88. package/docs/interfaces/NanoEditorSession.html +0 -8
  89. package/docs/interfaces/PackageDefinition.html +0 -30
  90. package/docs/interfaces/PackageFile.html +0 -8
  91. package/docs/interfaces/PasswordChallenge.html +0 -16
  92. package/docs/interfaces/RemoveOptions.html +0 -4
  93. package/docs/interfaces/ShellEnv.html +0 -6
  94. package/docs/interfaces/ShellModule.html +0 -14
  95. package/docs/interfaces/ShellProperties.html +0 -14
  96. package/docs/interfaces/ShellStream.html +0 -11
  97. package/docs/interfaces/SudoChallenge.html +0 -24
  98. package/docs/interfaces/VfsBaseNode.html +0 -12
  99. package/docs/interfaces/VfsDiff.html +0 -10
  100. package/docs/interfaces/VfsDiffEntry.html +0 -6
  101. package/docs/interfaces/VfsDiffModified.html +0 -10
  102. package/docs/interfaces/VfsDirectoryNode.html +0 -15
  103. package/docs/interfaces/VfsFileNode.html +0 -17
  104. package/docs/interfaces/VfsOptions.html +0 -26
  105. package/docs/interfaces/VfsSnapshot.html +0 -3
  106. package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
  107. package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
  108. package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
  109. package/docs/interfaces/VirtualActiveSession.html +0 -12
  110. package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
  111. package/docs/interfaces/VirtualShellVfsLike.html +0 -15
  112. package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
  113. package/docs/interfaces/WriteFileOptions.html +0 -6
  114. package/docs/media/LICENSE +0 -21
  115. package/docs/modules.html +0 -1
  116. package/docs/types/ArgParseOptions.html +0 -4
  117. package/docs/types/CommandMode.html +0 -2
  118. package/docs/types/CommandOutcome.html +0 -2
  119. package/docs/types/IdleState.html +0 -1
  120. package/docs/types/VfsNodeStats.html +0 -2
  121. package/docs/types/VfsNodeType.html +0 -2
  122. package/docs/types/VfsPersistenceMode.html +0 -5
  123. package/docs/types/VfsSnapshotNode.html +0 -2
  124. package/examples/README.md +0 -288
  125. package/examples/app.js +0 -1751
  126. package/examples/app.ts +0 -299
  127. package/examples/build.js +0 -27
  128. package/examples/demo.html +0 -33
  129. package/examples/honeypot-audit.ts +0 -180
  130. package/examples/honeypot-export.ts +0 -253
  131. package/examples/honeypot-quickstart.ts +0 -110
  132. package/examples/index.html +0 -82
  133. package/examples/server.js +0 -55
  134. package/polyfills/buffer.js +0 -117
  135. package/polyfills/node_child_process/index.js +0 -2
  136. package/polyfills/node_crypto/index.js +0 -167
  137. package/polyfills/node_events/index.js +0 -9
  138. package/polyfills/node_fs/index.js +0 -202
  139. package/polyfills/node_fs/promises.js +0 -4
  140. package/polyfills/node_os/index.js +0 -9
  141. package/polyfills/node_path/index.js +0 -28
  142. package/polyfills/node_vm/index.js +0 -7
  143. package/polyfills/node_zlib/index.js +0 -3
  144. package/polyfills/process.js +0 -14
  145. package/polyfills/ssh2/index.js +0 -75
  146. package/scripts/build-all.mjs +0 -226
  147. package/scripts/build-names.mjs +0 -43
  148. package/scripts/generate-manuals-bundle.mjs +0 -49
  149. package/scripts/postinstall.js +0 -42
  150. package/scripts/publish-package.sh +0 -70
  151. package/src/Honeypot/index.ts +0 -457
  152. package/src/SSHClient/index.ts +0 -270
  153. package/src/SSHMimic/exec.ts +0 -49
  154. package/src/SSHMimic/executor.ts +0 -251
  155. package/src/SSHMimic/hostKey.ts +0 -21
  156. package/src/SSHMimic/index.ts +0 -337
  157. package/src/SSHMimic/loginBanner.ts +0 -36
  158. package/src/SSHMimic/loginFormat.ts +0 -10
  159. package/src/SSHMimic/prompt.ts +0 -14
  160. package/src/SSHMimic/sftp.ts +0 -883
  161. package/src/VirtualFileSystem/binaryPack.ts +0 -258
  162. package/src/VirtualFileSystem/index.ts +0 -1193
  163. package/src/VirtualFileSystem/internalTypes.ts +0 -43
  164. package/src/VirtualFileSystem/journal.ts +0 -171
  165. package/src/VirtualFileSystem/path.ts +0 -74
  166. package/src/VirtualPackageManager/index.ts +0 -996
  167. package/src/VirtualShell/idleManager.ts +0 -137
  168. package/src/VirtualShell/index.ts +0 -475
  169. package/src/VirtualShell/shell.ts +0 -700
  170. package/src/VirtualShell/shellParser.ts +0 -285
  171. package/src/VirtualUserManager/index.ts +0 -758
  172. package/src/bun.d.ts +0 -1
  173. package/src/commands/adduser.ts +0 -103
  174. package/src/commands/alias.ts +0 -69
  175. package/src/commands/apt.ts +0 -233
  176. package/src/commands/awk.ts +0 -168
  177. package/src/commands/base64.ts +0 -29
  178. package/src/commands/cat.ts +0 -52
  179. package/src/commands/cd.ts +0 -25
  180. package/src/commands/chmod.ts +0 -85
  181. package/src/commands/clear.ts +0 -15
  182. package/src/commands/command-helpers.ts +0 -286
  183. package/src/commands/cp.ts +0 -83
  184. package/src/commands/curl.ts +0 -147
  185. package/src/commands/cut.ts +0 -36
  186. package/src/commands/date.ts +0 -30
  187. package/src/commands/declare.ts +0 -49
  188. package/src/commands/deluser.ts +0 -98
  189. package/src/commands/df.ts +0 -23
  190. package/src/commands/diff.ts +0 -43
  191. package/src/commands/dpkg.ts +0 -180
  192. package/src/commands/du.ts +0 -56
  193. package/src/commands/echo.ts +0 -58
  194. package/src/commands/env.ts +0 -23
  195. package/src/commands/exit.ts +0 -18
  196. package/src/commands/export.ts +0 -34
  197. package/src/commands/find.ts +0 -68
  198. package/src/commands/free.ts +0 -47
  199. package/src/commands/grep.ts +0 -116
  200. package/src/commands/groups.ts +0 -19
  201. package/src/commands/gzip.ts +0 -88
  202. package/src/commands/head.ts +0 -52
  203. package/src/commands/help.ts +0 -152
  204. package/src/commands/helpers.ts +0 -234
  205. package/src/commands/history.ts +0 -34
  206. package/src/commands/hostname.ts +0 -14
  207. package/src/commands/htop.ts +0 -20
  208. package/src/commands/id.ts +0 -19
  209. package/src/commands/index.ts +0 -9
  210. package/src/commands/kill.ts +0 -19
  211. package/src/commands/ln.ts +0 -71
  212. package/src/commands/ls.ts +0 -243
  213. package/src/commands/lsb-release.ts +0 -63
  214. package/src/commands/man.ts +0 -31
  215. package/src/commands/manuals/adduser.txt +0 -11
  216. package/src/commands/manuals/apt-cache.txt +0 -12
  217. package/src/commands/manuals/apt.txt +0 -20
  218. package/src/commands/manuals/awk.txt +0 -13
  219. package/src/commands/manuals/cat.txt +0 -14
  220. package/src/commands/manuals/cd.txt +0 -16
  221. package/src/commands/manuals/chmod.txt +0 -16
  222. package/src/commands/manuals/clear.txt +0 -10
  223. package/src/commands/manuals/cp.txt +0 -10
  224. package/src/commands/manuals/curl.txt +0 -20
  225. package/src/commands/manuals/date.txt +0 -14
  226. package/src/commands/manuals/declare.txt +0 -12
  227. package/src/commands/manuals/deluser.txt +0 -10
  228. package/src/commands/manuals/df.txt +0 -10
  229. package/src/commands/manuals/dpkg-query.txt +0 -11
  230. package/src/commands/manuals/dpkg.txt +0 -14
  231. package/src/commands/manuals/du.txt +0 -11
  232. package/src/commands/manuals/echo.txt +0 -11
  233. package/src/commands/manuals/false.txt +0 -10
  234. package/src/commands/manuals/find.txt +0 -11
  235. package/src/commands/manuals/free.txt +0 -12
  236. package/src/commands/manuals/grep.txt +0 -13
  237. package/src/commands/manuals/groups.txt +0 -10
  238. package/src/commands/manuals/gzip.txt +0 -11
  239. package/src/commands/manuals/head.txt +0 -10
  240. package/src/commands/manuals/help.txt +0 -11
  241. package/src/commands/manuals/history.txt +0 -11
  242. package/src/commands/manuals/hostname.txt +0 -10
  243. package/src/commands/manuals/id.txt +0 -10
  244. package/src/commands/manuals/kill.txt +0 -13
  245. package/src/commands/manuals/ls.txt +0 -20
  246. package/src/commands/manuals/lsb_release.txt +0 -14
  247. package/src/commands/manuals/mkdir.txt +0 -10
  248. package/src/commands/manuals/mv.txt +0 -10
  249. package/src/commands/manuals/nano.txt +0 -11
  250. package/src/commands/manuals/neofetch.txt +0 -10
  251. package/src/commands/manuals/node.txt +0 -13
  252. package/src/commands/manuals/npm.txt +0 -13
  253. package/src/commands/manuals/npx.txt +0 -13
  254. package/src/commands/manuals/passwd.txt +0 -11
  255. package/src/commands/manuals/ping.txt +0 -10
  256. package/src/commands/manuals/printf.txt +0 -11
  257. package/src/commands/manuals/ps.txt +0 -10
  258. package/src/commands/manuals/pwd.txt +0 -10
  259. package/src/commands/manuals/python3.txt +0 -13
  260. package/src/commands/manuals/readlink.txt +0 -10
  261. package/src/commands/manuals/return.txt +0 -10
  262. package/src/commands/manuals/rm.txt +0 -10
  263. package/src/commands/manuals/sed.txt +0 -11
  264. package/src/commands/manuals/set.txt +0 -11
  265. package/src/commands/manuals/shift.txt +0 -10
  266. package/src/commands/manuals/sleep.txt +0 -10
  267. package/src/commands/manuals/sort.txt +0 -12
  268. package/src/commands/manuals/source.txt +0 -11
  269. package/src/commands/manuals/ssh.txt +0 -11
  270. package/src/commands/manuals/stat.txt +0 -10
  271. package/src/commands/manuals/su.txt +0 -13
  272. package/src/commands/manuals/sudo.txt +0 -11
  273. package/src/commands/manuals/tail.txt +0 -10
  274. package/src/commands/manuals/tar.txt +0 -19
  275. package/src/commands/manuals/tee.txt +0 -10
  276. package/src/commands/manuals/test.txt +0 -11
  277. package/src/commands/manuals/touch.txt +0 -11
  278. package/src/commands/manuals/tr.txt +0 -10
  279. package/src/commands/manuals/trap.txt +0 -10
  280. package/src/commands/manuals/true.txt +0 -10
  281. package/src/commands/manuals/type.txt +0 -10
  282. package/src/commands/manuals/uname.txt +0 -12
  283. package/src/commands/manuals/uniq.txt +0 -12
  284. package/src/commands/manuals/unset.txt +0 -10
  285. package/src/commands/manuals/uptime.txt +0 -11
  286. package/src/commands/manuals/wc.txt +0 -12
  287. package/src/commands/manuals/wget.txt +0 -12
  288. package/src/commands/manuals/which.txt +0 -10
  289. package/src/commands/manuals/whoami.txt +0 -10
  290. package/src/commands/manuals/xargs.txt +0 -10
  291. package/src/commands/manuals-bundle.ts +0 -898
  292. package/src/commands/mkdir.ts +0 -31
  293. package/src/commands/mv.ts +0 -50
  294. package/src/commands/nano.ts +0 -38
  295. package/src/commands/neofetch.ts +0 -53
  296. package/src/commands/node.ts +0 -341
  297. package/src/commands/npm.ts +0 -132
  298. package/src/commands/passwd.ts +0 -50
  299. package/src/commands/ping.ts +0 -32
  300. package/src/commands/printf.ts +0 -129
  301. package/src/commands/ps.ts +0 -58
  302. package/src/commands/pwd.ts +0 -9
  303. package/src/commands/python.ts +0 -2229
  304. package/src/commands/read.ts +0 -46
  305. package/src/commands/registry.ts +0 -249
  306. package/src/commands/rm.ts +0 -42
  307. package/src/commands/runtime.ts +0 -421
  308. package/src/commands/sed.ts +0 -68
  309. package/src/commands/seq.ts +0 -43
  310. package/src/commands/set.ts +0 -29
  311. package/src/commands/sh.ts +0 -467
  312. package/src/commands/shift.ts +0 -63
  313. package/src/commands/sleep.ts +0 -20
  314. package/src/commands/sort.ts +0 -46
  315. package/src/commands/source.ts +0 -52
  316. package/src/commands/stat.ts +0 -61
  317. package/src/commands/su.ts +0 -72
  318. package/src/commands/sudo.ts +0 -76
  319. package/src/commands/tail.ts +0 -53
  320. package/src/commands/tar.ts +0 -102
  321. package/src/commands/tee.ts +0 -36
  322. package/src/commands/test.ts +0 -137
  323. package/src/commands/touch.ts +0 -28
  324. package/src/commands/tr.ts +0 -70
  325. package/src/commands/tree.ts +0 -20
  326. package/src/commands/true.ts +0 -27
  327. package/src/commands/type.ts +0 -48
  328. package/src/commands/uname.ts +0 -29
  329. package/src/commands/uniq.ts +0 -39
  330. package/src/commands/unset.ts +0 -17
  331. package/src/commands/uptime.ts +0 -54
  332. package/src/commands/wc.ts +0 -55
  333. package/src/commands/wget.ts +0 -148
  334. package/src/commands/which.ts +0 -37
  335. package/src/commands/who.ts +0 -25
  336. package/src/commands/whoami.ts +0 -14
  337. package/src/commands/xargs.ts +0 -31
  338. package/src/index.ts +0 -67
  339. package/src/modules/linuxRootfs.ts +0 -1961
  340. package/src/modules/neofetch.ts +0 -358
  341. package/src/modules/shellInteractive.ts +0 -57
  342. package/src/modules/shellRuntime.ts +0 -76
  343. package/src/self-standalone.ts +0 -542
  344. package/src/standalone-wo-sftp.ts +0 -38
  345. package/src/standalone.ts +0 -72
  346. package/src/types/commands.ts +0 -146
  347. package/src/types/pipeline.ts +0 -52
  348. package/src/types/streams.ts +0 -32
  349. package/src/types/tar-stream.d.ts +0 -38
  350. package/src/types/vfs.ts +0 -98
  351. package/src/utils/expand.ts +0 -491
  352. package/src/utils/perfLogger.ts +0 -72
  353. package/src/utils/tokenize.ts +0 -98
  354. package/src/utils/vfsDiff.ts +0 -275
  355. package/tests/command-helpers.test.ts +0 -116
  356. package/tests/commands-admin-net.test.ts +0 -441
  357. package/tests/commands-advanced.test.ts +0 -456
  358. package/tests/commands-core.test.ts +0 -562
  359. package/tests/commands-missing.test.ts +0 -570
  360. package/tests/commands-specific-units.test.ts +0 -327
  361. package/tests/commands-text-sys.test.ts +0 -445
  362. package/tests/expand.test.ts +0 -170
  363. package/tests/helpers.test.ts +0 -97
  364. package/tests/new-features.test.ts +0 -1036
  365. package/tests/parser-executor.test.ts +0 -37
  366. package/tests/sftp.test.ts +0 -323
  367. package/tests/ssh-exec.test.ts +0 -45
  368. package/tests/test-helper.ts +0 -79
  369. package/tests/users.test.ts +0 -86
  370. package/tsconfig.json +0 -49
  371. package/typedoc.json +0 -47
@@ -1,275 +0,0 @@
1
- /**
2
- * vfsDiff.ts
3
- *
4
- * Snapshot diff tooling for `VirtualFileSystem`.
5
- *
6
- * Compares two VFS snapshots and returns structured diff results suitable
7
- * for test assertions, audit logging, and deployment verification.
8
- *
9
- * @example
10
- * ```ts
11
- * import { diffSnapshots, formatDiff } from "typescript-virtual-container/utils/vfsDiff";
12
- *
13
- * const before = shell.vfs.toSnapshot();
14
- * await client.exec("npm install && mkdir -p /app");
15
- * const after = shell.vfs.toSnapshot();
16
- *
17
- * const diff = diffSnapshots(before, after);
18
- * console.log(formatDiff(diff));
19
- *
20
- * // Test assertions
21
- * expect(diff.added).toContain("/app");
22
- * expect(diff.modified).toContain("/etc/hosts");
23
- * expect(diff.removed).not.toContain("/tmp/needed-file");
24
- * ```
25
- */
26
-
27
- import type {
28
- VfsSnapshot,
29
- VfsSnapshotNode,
30
- VfsSnapshotFileNode,
31
- VfsSnapshotDirectoryNode,
32
- } from "../types/vfs";
33
-
34
- // ─── types ────────────────────────────────────────────────────────────────────
35
-
36
- /** A single changed file entry in a diff result. */
37
- export interface VfsDiffEntry {
38
- /** Absolute VFS path of the changed node. */
39
- path: string;
40
- /** Node type — `"file"` or `"directory"`. */
41
- type: "file" | "directory";
42
- }
43
-
44
- /** A modified file entry — includes before/after content for files. */
45
- export interface VfsDiffModified extends VfsDiffEntry {
46
- type: "file";
47
- /** Content before the change (decoded from base64). */
48
- before: string;
49
- /** Content after the change (decoded from base64). */
50
- after: string;
51
- }
52
-
53
- /** Full result of a snapshot diff operation. */
54
- export interface VfsDiff {
55
- /** Paths present in `after` but not in `before`. */
56
- added: VfsDiffEntry[];
57
- /** Paths present in `before` but not in `after`. */
58
- removed: VfsDiffEntry[];
59
- /** Files whose content or mode changed between snapshots. */
60
- modified: VfsDiffModified[];
61
- /** True when there are no differences. */
62
- clean: boolean;
63
- }
64
-
65
- // ─── internal helpers ─────────────────────────────────────────────────────────
66
-
67
- function flattenSnapshot(
68
- node: VfsSnapshotNode,
69
- prefix: string,
70
- out: Map<string, VfsSnapshotNode>,
71
- ): void {
72
- const path = prefix === "" ? "/" : prefix;
73
- out.set(path, node);
74
- if (node.type === "directory") {
75
- for (const child of (node as VfsSnapshotDirectoryNode).children ?? []) {
76
- flattenSnapshot(child, `${prefix}/${child.name}`, out);
77
- }
78
- }
79
- }
80
-
81
- function decodeContent(node: VfsSnapshotFileNode): string {
82
- try {
83
- return Buffer.from(node.contentBase64, "base64").toString("utf8");
84
- } catch {
85
- return node.contentBase64;
86
- }
87
- }
88
-
89
- // ─── public API ───────────────────────────────────────────────────────────────
90
-
91
- /**
92
- * Compute the diff between two VFS snapshots.
93
- *
94
- * @param before Snapshot taken before the operation.
95
- * @param after Snapshot taken after the operation.
96
- * @param options Optional filtering options.
97
- * @returns A structured `VfsDiff` result.
98
- */
99
- export function diffSnapshots(
100
- before: VfsSnapshot,
101
- after: VfsSnapshot,
102
- options: {
103
- /** Glob-style path prefixes to ignore (e.g. `["/proc", "/var/log"]`). */
104
- ignore?: string[];
105
- } = {},
106
- ): VfsDiff {
107
- const ignorePrefixes = options.ignore ?? [];
108
-
109
- const shouldIgnore = (path: string): boolean =>
110
- ignorePrefixes.some(
111
- (prefix) => path === prefix || path.startsWith(`${prefix}/`),
112
- );
113
-
114
- const beforeMap = new Map<string, VfsSnapshotNode>();
115
- const afterMap = new Map<string, VfsSnapshotNode>();
116
-
117
- flattenSnapshot(before.root, "", beforeMap);
118
- flattenSnapshot(after.root, "", afterMap);
119
-
120
- const added: VfsDiffEntry[] = [];
121
- const removed: VfsDiffEntry[] = [];
122
- const modified: VfsDiffModified[] = [];
123
-
124
- // Added — in after, not in before
125
- for (const [path, node] of afterMap) {
126
- if (shouldIgnore(path)) continue;
127
- if (!beforeMap.has(path)) {
128
- added.push({ path, type: node.type });
129
- }
130
- }
131
-
132
- // Removed — in before, not in after
133
- for (const [path, node] of beforeMap) {
134
- if (shouldIgnore(path)) continue;
135
- if (!afterMap.has(path)) {
136
- removed.push({ path, type: node.type });
137
- }
138
- }
139
-
140
- // Modified — in both, but content or mode changed
141
- for (const [path, afterNode] of afterMap) {
142
- if (shouldIgnore(path)) continue;
143
- const beforeNode = beforeMap.get(path);
144
- if (!beforeNode) continue; // already in added
145
- if (afterNode.type !== beforeNode.type) continue; // type change = add+remove
146
-
147
- if (afterNode.type === "file" && beforeNode.type === "file") {
148
- const beforeContent = decodeContent(beforeNode as VfsSnapshotFileNode);
149
- const afterContent = decodeContent(afterNode as VfsSnapshotFileNode);
150
- const modeChanged = afterNode.mode !== beforeNode.mode;
151
- if (beforeContent !== afterContent || modeChanged) {
152
- modified.push({
153
- path,
154
- type: "file",
155
- before: beforeContent,
156
- after: afterContent,
157
- });
158
- }
159
- }
160
- }
161
-
162
- // Sort all arrays for determinism
163
- const sortByPath = (a: VfsDiffEntry, b: VfsDiffEntry) =>
164
- a.path.localeCompare(b.path);
165
-
166
- added.sort(sortByPath);
167
- removed.sort(sortByPath);
168
- modified.sort(sortByPath);
169
-
170
- return {
171
- added,
172
- removed,
173
- modified,
174
- clean: added.length === 0 && removed.length === 0 && modified.length === 0,
175
- };
176
- }
177
-
178
- /**
179
- * Format a `VfsDiff` as a human-readable string similar to `git diff --stat`.
180
- *
181
- * @param diff Result from `diffSnapshots`.
182
- * @param options Formatting options.
183
- */
184
- export function formatDiff(
185
- diff: VfsDiff,
186
- options: {
187
- /** Show file content changes inline. Default: false. */
188
- showContent?: boolean;
189
- /** Max chars of content to show per change. Default: 120. */
190
- maxContentChars?: number;
191
- } = {},
192
- ): string {
193
- if (diff.clean) return "(no changes)";
194
-
195
- const { showContent = false, maxContentChars = 120 } = options;
196
- const lines: string[] = [];
197
-
198
- for (const entry of diff.added) {
199
- lines.push(`+ ${entry.path} [${entry.type}]`);
200
- }
201
-
202
- for (const entry of diff.removed) {
203
- lines.push(`- ${entry.path} [${entry.type}]`);
204
- }
205
-
206
- for (const entry of diff.modified) {
207
- lines.push(`~ ${entry.path} [modified]`);
208
- if (showContent) {
209
- const before = entry.before.slice(0, maxContentChars);
210
- const after = entry.after.slice(0, maxContentChars);
211
- lines.push(
212
- ` before: ${JSON.stringify(before)}${entry.before.length > maxContentChars ? "…" : ""}`,
213
- );
214
- lines.push(
215
- ` after: ${JSON.stringify(after)}${entry.after.length > maxContentChars ? "…" : ""}`,
216
- );
217
- }
218
- }
219
-
220
- const summary = [
221
- diff.added.length > 0 ? `${diff.added.length} added` : "",
222
- diff.removed.length > 0 ? `${diff.removed.length} removed` : "",
223
- diff.modified.length > 0 ? `${diff.modified.length} modified` : "",
224
- ]
225
- .filter(Boolean)
226
- .join(", ");
227
-
228
- lines.push(`\n${summary}`);
229
- return lines.join("\n");
230
- }
231
-
232
- /**
233
- * Assert that a diff contains specific paths, throwing on mismatch.
234
- * Designed for use in test suites.
235
- *
236
- * @param diff Result from `diffSnapshots`.
237
- * @param expect Expected paths in each category.
238
- * @throws When any expectation is not met.
239
- */
240
- export function assertDiff(
241
- diff: VfsDiff,
242
- expect: {
243
- added?: string[];
244
- removed?: string[];
245
- modified?: string[];
246
- },
247
- ): void {
248
- const addedPaths = diff.added.map((e) => e.path);
249
- const removedPaths = diff.removed.map((e) => e.path);
250
- const modifiedPaths = diff.modified.map((e) => e.path);
251
-
252
- for (const path of expect.added ?? []) {
253
- if (!addedPaths.includes(path)) {
254
- throw new Error(
255
- `assertDiff: expected "${path}" to be added, but it was not.\nAdded: ${JSON.stringify(addedPaths)}`,
256
- );
257
- }
258
- }
259
-
260
- for (const path of expect.removed ?? []) {
261
- if (!removedPaths.includes(path)) {
262
- throw new Error(
263
- `assertDiff: expected "${path}" to be removed, but it was not.\nRemoved: ${JSON.stringify(removedPaths)}`,
264
- );
265
- }
266
- }
267
-
268
- for (const path of expect.modified ?? []) {
269
- if (!modifiedPaths.includes(path)) {
270
- throw new Error(
271
- `assertDiff: expected "${path}" to be modified, but it was not.\nModified: ${JSON.stringify(modifiedPaths)}`,
272
- );
273
- }
274
- }
275
- }
@@ -1,116 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { getArg, getFlag, ifFlag } from "../src/commands/command-helpers";
3
-
4
- describe("command-helpers", () => {
5
- test("ifFlag detects plain and inline flag forms", () => {
6
- expect(ifFlag(["-l", "docs"], ["-l", "--long"])).toBe(true);
7
- expect(ifFlag(["--user=root", "whoami"], "--user")).toBe(true);
8
- expect(ifFlag(["docs"], ["-l", "--long"])).toBe(false);
9
- });
10
-
11
- test("ifFlag with multiple flags checks all", () => {
12
- expect(ifFlag(["-l", "-h"], ["-l", "--long"])).toBe(true);
13
- expect(ifFlag(["-h"], ["-l", "--long"])).toBe(false);
14
- expect(ifFlag(["-l"], ["-l", "--long"])).toBe(true);
15
- });
16
-
17
- test("ifFlag empty args returns false", () => {
18
- expect(ifFlag([], ["-l", "--long"])).toBe(false);
19
- });
20
-
21
- test("ifFlag single flag array", () => {
22
- expect(ifFlag(["-n"], "-n")).toBe(true);
23
- expect(ifFlag(["-m"], "-n")).toBe(false);
24
- });
25
-
26
- test("getFlag returns value for adjacent and inline forms", () => {
27
- expect(getFlag(["-u", "root", "id"], ["-u", "--user"])).toBe("root");
28
- expect(getFlag(["--user=alice", "id"], ["-u", "--user"])).toBe("alice");
29
- expect(getFlag(["-i", "whoami"], "-i")).toBe("whoami");
30
- expect(getFlag(["-o"], "-o")).toBe(true);
31
- expect(getFlag(["pwd"], ["-u", "--user"])).toBeUndefined();
32
- });
33
-
34
- test("getFlag with multiple flag aliases", () => {
35
- expect(getFlag(["-u", "john"], ["-u", "--user", "-U"])).toBe("john");
36
- expect(getFlag(["--user=mary"], ["-u", "--user"])).toBe("mary");
37
- expect(getFlag(["-U", "admin"], ["-u", "--user", "-U"])).toBe("admin");
38
- });
39
-
40
- test("getFlag inline with equals", () => {
41
- expect(getFlag(["--option=value"], "--option")).toBe("value");
42
- expect(getFlag(["--opt=123"], "--opt")).toBe("123");
43
- });
44
-
45
- test("getFlag first occurrence", () => {
46
- expect(getFlag(["-u", "first", "-u", "second"], "-u")).toBe("first");
47
- });
48
-
49
- test("getFlag with boolean flag", () => {
50
- expect(getFlag(["-v"], "-v")).toBe(true);
51
- expect(getFlag(["-v", "file.txt"], "-v")).toBe("file.txt");
52
- });
53
-
54
- test("getArg skips bool and value flags", () => {
55
- const args = ["-i", "-u", "root", "sh", "-c", "whoami"];
56
- const options = { flags: ["-i"], flagsWithValue: ["-u"] };
57
-
58
- expect(getArg(args, 0, options)).toBe("sh");
59
- expect(getArg(args, 1, options)).toBe("-c");
60
- expect(getArg(args, 2, options)).toBe("whoami");
61
- expect(getArg(args, 3, options)).toBeUndefined();
62
- });
63
-
64
- test("getArg with no flags", () => {
65
- const args = ["file1", "file2", "file3"];
66
- const options = { flags: [], flagsWithValue: [] };
67
-
68
- expect(getArg(args, 0, options)).toBe("file1");
69
- expect(getArg(args, 1, options)).toBe("file2");
70
- expect(getArg(args, 2, options)).toBe("file3");
71
- });
72
-
73
- test("getArg empty args", () => {
74
- const args: string[] = [];
75
- const options = { flags: ["-n"] };
76
-
77
- expect(getArg(args, 0, options)).toBeUndefined();
78
- });
79
-
80
- test("getArg skips values for flagsWithValue", () => {
81
- const args = ["-d", ":", "-f", "1", "input.txt"];
82
- const options = { flagsWithValue: ["-d", "-f"] };
83
-
84
- expect(getArg(args, 0, options)).toBe("input.txt");
85
- });
86
-
87
- test("getArg keeps tokens after -- as positional", () => {
88
- const args = ["-n", "--", "-n", "hello"];
89
- const options = { flags: ["-n"] };
90
-
91
- expect(getArg(args, 0, options)).toBe("-n");
92
- expect(getArg(args, 1, options)).toBe("hello");
93
- });
94
-
95
- test("getArg -- blocks all flag processing", () => {
96
- const args = ["-a", "--", "-a", "-b", "-c"];
97
- const options = { flags: ["-a", "-b", "-c"] };
98
-
99
- expect(getArg(args, 0, options)).toBe("-a");
100
- expect(getArg(args, 1, options)).toBe("-b");
101
- expect(getArg(args, 2, options)).toBe("-c");
102
- });
103
-
104
- test("getArg with mixed flags and positionals", () => {
105
- const args = ["-v", "file1", "-o", "output.txt", "file2"];
106
- const options = { flags: ["-v"], flagsWithValue: ["-o"] };
107
-
108
- expect(getArg(args, 0, options)).toBe("file1");
109
- expect(getArg(args, 1, options)).toBe("file2");
110
- });
111
-
112
- test("getArg at end of args", () => {
113
- const args = ["a", "b", "c"];
114
- expect(getArg(args, 5, {})).toBeUndefined();
115
- });
116
- });