typescript-virtual-container 1.5.2 → 1.5.4

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 (364) hide show
  1. package/README.md +43 -23
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/SSHMimic/executor.js +23 -5
  4. package/dist/commands/basename.d.ts +13 -0
  5. package/dist/commands/basename.js +45 -0
  6. package/dist/commands/file.d.ts +8 -0
  7. package/dist/commands/file.js +57 -0
  8. package/dist/commands/fun.d.ts +32 -0
  9. package/dist/commands/fun.js +172 -0
  10. package/dist/commands/ifconfig.d.ts +7 -0
  11. package/dist/commands/ifconfig.js +52 -0
  12. package/dist/commands/last.d.ts +13 -0
  13. package/dist/commands/last.js +68 -0
  14. package/dist/commands/manuals-bundle.js +598 -6
  15. package/dist/commands/registry.js +24 -2
  16. package/dist/commands/runtime.js +159 -106
  17. package/dist/commands/sh.js +5 -0
  18. package/dist/commands/tput.d.ts +13 -0
  19. package/dist/commands/tput.js +76 -0
  20. package/dist/commands/w.d.ts +7 -0
  21. package/dist/commands/w.js +38 -0
  22. package/dist/utils/expand.d.ts +12 -0
  23. package/dist/utils/expand.js +84 -0
  24. package/package.json +9 -3
  25. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
  26. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
  27. package/.github/dependabot.yml +0 -27
  28. package/.github/pull_request_template.md +0 -21
  29. package/.github/workflows/create-pull-request.yml +0 -85
  30. package/.github/workflows/publish.yml +0 -25
  31. package/.github/workflows/test-battery.yml +0 -102
  32. package/.vscode/settings.json +0 -20
  33. package/CODE_OF_CONDUCT.md +0 -39
  34. package/CONTRIBUTING.md +0 -59
  35. package/HONEYPOT.md +0 -358
  36. package/SECURITY.md +0 -33
  37. package/benchmark-results.txt +0 -40
  38. package/benchmark-virtualshell.ts +0 -88
  39. package/biome.json +0 -37
  40. package/build.js +0 -22
  41. package/builds/fortune-nyx-v1.5.1-directbash-k6.1.0.mjs +0 -1768
  42. package/builds/fortune-nyx-v1.5.1-ssh-nosftp.js +0 -1768
  43. package/builds/fortune-nyx-v1.5.1-ssh.cjs +0 -1769
  44. package/builds/fortune-nyx-v1.5.1-web.min.js +0 -17022
  45. package/bun.lock +0 -244
  46. package/docs/.nojekyll +0 -1
  47. package/docs/app.js +0 -1755
  48. package/docs/assets/hierarchy.js +0 -1
  49. package/docs/assets/highlight.css +0 -162
  50. package/docs/assets/icons.js +0 -18
  51. package/docs/assets/icons.svg +0 -1
  52. package/docs/assets/main.js +0 -60
  53. package/docs/assets/navigation.js +0 -1
  54. package/docs/assets/search.js +0 -1
  55. package/docs/assets/style.css +0 -1633
  56. package/docs/classes/HoneyPot.html +0 -31
  57. package/docs/classes/IdleManager.html +0 -162
  58. package/docs/classes/SshClient.html +0 -66
  59. package/docs/classes/VirtualFileSystem.html +0 -279
  60. package/docs/classes/VirtualPackageManager.html +0 -63
  61. package/docs/classes/VirtualSftpServer.html +0 -169
  62. package/docs/classes/VirtualShell.html +0 -285
  63. package/docs/classes/VirtualSshServer.html +0 -182
  64. package/docs/classes/VirtualUserManager.html +0 -276
  65. package/docs/demo.html +0 -82
  66. package/docs/functions/assertDiff.html +0 -6
  67. package/docs/functions/diffSnapshots.html +0 -7
  68. package/docs/functions/formatDiff.html +0 -6
  69. package/docs/functions/getArg.html +0 -13
  70. package/docs/functions/getFlag.html +0 -15
  71. package/docs/functions/ifFlag.html +0 -11
  72. package/docs/hierarchy.html +0 -1
  73. package/docs/index.html +0 -1869
  74. package/docs/interfaces/AuditLogEntry.html +0 -6
  75. package/docs/interfaces/CommandContext.html +0 -22
  76. package/docs/interfaces/CommandResult.html +0 -26
  77. package/docs/interfaces/ExecStream.html +0 -11
  78. package/docs/interfaces/HoneyPotStats.html +0 -16
  79. package/docs/interfaces/IdleManagerOptions.html +0 -7
  80. package/docs/interfaces/InstalledPackage.html +0 -20
  81. package/docs/interfaces/NanoEditorSession.html +0 -8
  82. package/docs/interfaces/PackageDefinition.html +0 -30
  83. package/docs/interfaces/PackageFile.html +0 -8
  84. package/docs/interfaces/PasswordChallenge.html +0 -16
  85. package/docs/interfaces/RemoveOptions.html +0 -4
  86. package/docs/interfaces/ShellEnv.html +0 -6
  87. package/docs/interfaces/ShellModule.html +0 -14
  88. package/docs/interfaces/ShellProperties.html +0 -14
  89. package/docs/interfaces/ShellStream.html +0 -11
  90. package/docs/interfaces/SudoChallenge.html +0 -24
  91. package/docs/interfaces/VfsBaseNode.html +0 -12
  92. package/docs/interfaces/VfsDiff.html +0 -10
  93. package/docs/interfaces/VfsDiffEntry.html +0 -6
  94. package/docs/interfaces/VfsDiffModified.html +0 -10
  95. package/docs/interfaces/VfsDirectoryNode.html +0 -15
  96. package/docs/interfaces/VfsFileNode.html +0 -17
  97. package/docs/interfaces/VfsOptions.html +0 -26
  98. package/docs/interfaces/VfsSnapshot.html +0 -3
  99. package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
  100. package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
  101. package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
  102. package/docs/interfaces/VirtualActiveSession.html +0 -12
  103. package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
  104. package/docs/interfaces/VirtualShellVfsLike.html +0 -15
  105. package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
  106. package/docs/interfaces/WriteFileOptions.html +0 -6
  107. package/docs/media/LICENSE +0 -21
  108. package/docs/modules.html +0 -1
  109. package/docs/types/ArgParseOptions.html +0 -4
  110. package/docs/types/CommandMode.html +0 -2
  111. package/docs/types/CommandOutcome.html +0 -2
  112. package/docs/types/IdleState.html +0 -1
  113. package/docs/types/VfsNodeStats.html +0 -2
  114. package/docs/types/VfsNodeType.html +0 -2
  115. package/docs/types/VfsPersistenceMode.html +0 -5
  116. package/docs/types/VfsSnapshotNode.html +0 -2
  117. package/examples/README.md +0 -288
  118. package/examples/app.js +0 -1755
  119. package/examples/app.ts +0 -299
  120. package/examples/build.js +0 -27
  121. package/examples/demo.html +0 -33
  122. package/examples/honeypot-audit.ts +0 -180
  123. package/examples/honeypot-export.ts +0 -253
  124. package/examples/honeypot-quickstart.ts +0 -110
  125. package/examples/index.html +0 -82
  126. package/examples/server.js +0 -55
  127. package/polyfills/buffer.js +0 -117
  128. package/polyfills/node_child_process/index.js +0 -2
  129. package/polyfills/node_crypto/index.js +0 -167
  130. package/polyfills/node_events/index.js +0 -9
  131. package/polyfills/node_fs/index.js +0 -202
  132. package/polyfills/node_fs/promises.js +0 -4
  133. package/polyfills/node_os/index.js +0 -9
  134. package/polyfills/node_path/index.js +0 -28
  135. package/polyfills/node_vm/index.js +0 -7
  136. package/polyfills/node_zlib/index.js +0 -3
  137. package/polyfills/process.js +0 -14
  138. package/polyfills/ssh2/index.js +0 -75
  139. package/scripts/build-all.mjs +0 -226
  140. package/scripts/build-names.mjs +0 -43
  141. package/scripts/generate-manuals-bundle.mjs +0 -49
  142. package/scripts/postinstall.js +0 -42
  143. package/scripts/publish-package.sh +0 -70
  144. package/src/Honeypot/index.ts +0 -457
  145. package/src/SSHClient/index.ts +0 -270
  146. package/src/SSHMimic/exec.ts +0 -49
  147. package/src/SSHMimic/executor.ts +0 -251
  148. package/src/SSHMimic/hostKey.ts +0 -21
  149. package/src/SSHMimic/index.ts +0 -337
  150. package/src/SSHMimic/loginBanner.ts +0 -36
  151. package/src/SSHMimic/loginFormat.ts +0 -10
  152. package/src/SSHMimic/prompt.ts +0 -14
  153. package/src/SSHMimic/sftp.ts +0 -883
  154. package/src/VirtualFileSystem/binaryPack.ts +0 -258
  155. package/src/VirtualFileSystem/index.ts +0 -1193
  156. package/src/VirtualFileSystem/internalTypes.ts +0 -43
  157. package/src/VirtualFileSystem/journal.ts +0 -171
  158. package/src/VirtualFileSystem/path.ts +0 -74
  159. package/src/VirtualPackageManager/index.ts +0 -1006
  160. package/src/VirtualShell/idleManager.ts +0 -137
  161. package/src/VirtualShell/index.ts +0 -475
  162. package/src/VirtualShell/shell.ts +0 -700
  163. package/src/VirtualShell/shellParser.ts +0 -285
  164. package/src/VirtualUserManager/index.ts +0 -758
  165. package/src/bun.d.ts +0 -1
  166. package/src/commands/adduser.ts +0 -103
  167. package/src/commands/alias.ts +0 -69
  168. package/src/commands/apt.ts +0 -233
  169. package/src/commands/awk.ts +0 -168
  170. package/src/commands/base64.ts +0 -29
  171. package/src/commands/cat.ts +0 -52
  172. package/src/commands/cd.ts +0 -25
  173. package/src/commands/chmod.ts +0 -85
  174. package/src/commands/clear.ts +0 -15
  175. package/src/commands/command-helpers.ts +0 -286
  176. package/src/commands/cp.ts +0 -83
  177. package/src/commands/curl.ts +0 -147
  178. package/src/commands/cut.ts +0 -36
  179. package/src/commands/date.ts +0 -30
  180. package/src/commands/declare.ts +0 -49
  181. package/src/commands/deluser.ts +0 -98
  182. package/src/commands/df.ts +0 -23
  183. package/src/commands/diff.ts +0 -43
  184. package/src/commands/dpkg.ts +0 -180
  185. package/src/commands/du.ts +0 -56
  186. package/src/commands/echo.ts +0 -58
  187. package/src/commands/env.ts +0 -23
  188. package/src/commands/exit.ts +0 -18
  189. package/src/commands/export.ts +0 -34
  190. package/src/commands/find.ts +0 -68
  191. package/src/commands/free.ts +0 -47
  192. package/src/commands/grep.ts +0 -116
  193. package/src/commands/groups.ts +0 -19
  194. package/src/commands/gzip.ts +0 -88
  195. package/src/commands/head.ts +0 -52
  196. package/src/commands/help.ts +0 -152
  197. package/src/commands/helpers.ts +0 -234
  198. package/src/commands/history.ts +0 -34
  199. package/src/commands/hostname.ts +0 -14
  200. package/src/commands/htop.ts +0 -20
  201. package/src/commands/id.ts +0 -19
  202. package/src/commands/index.ts +0 -9
  203. package/src/commands/kill.ts +0 -19
  204. package/src/commands/ln.ts +0 -71
  205. package/src/commands/ls.ts +0 -243
  206. package/src/commands/lsb-release.ts +0 -63
  207. package/src/commands/man.ts +0 -31
  208. package/src/commands/manuals/adduser.txt +0 -11
  209. package/src/commands/manuals/apt-cache.txt +0 -12
  210. package/src/commands/manuals/apt.txt +0 -20
  211. package/src/commands/manuals/awk.txt +0 -13
  212. package/src/commands/manuals/cat.txt +0 -14
  213. package/src/commands/manuals/cd.txt +0 -16
  214. package/src/commands/manuals/chmod.txt +0 -16
  215. package/src/commands/manuals/clear.txt +0 -10
  216. package/src/commands/manuals/cp.txt +0 -10
  217. package/src/commands/manuals/curl.txt +0 -20
  218. package/src/commands/manuals/date.txt +0 -14
  219. package/src/commands/manuals/declare.txt +0 -12
  220. package/src/commands/manuals/deluser.txt +0 -10
  221. package/src/commands/manuals/df.txt +0 -10
  222. package/src/commands/manuals/dpkg-query.txt +0 -11
  223. package/src/commands/manuals/dpkg.txt +0 -14
  224. package/src/commands/manuals/du.txt +0 -11
  225. package/src/commands/manuals/echo.txt +0 -11
  226. package/src/commands/manuals/false.txt +0 -10
  227. package/src/commands/manuals/find.txt +0 -11
  228. package/src/commands/manuals/free.txt +0 -12
  229. package/src/commands/manuals/grep.txt +0 -13
  230. package/src/commands/manuals/groups.txt +0 -10
  231. package/src/commands/manuals/gzip.txt +0 -11
  232. package/src/commands/manuals/head.txt +0 -10
  233. package/src/commands/manuals/help.txt +0 -11
  234. package/src/commands/manuals/history.txt +0 -11
  235. package/src/commands/manuals/hostname.txt +0 -10
  236. package/src/commands/manuals/id.txt +0 -10
  237. package/src/commands/manuals/kill.txt +0 -13
  238. package/src/commands/manuals/ls.txt +0 -20
  239. package/src/commands/manuals/lsb_release.txt +0 -14
  240. package/src/commands/manuals/mkdir.txt +0 -10
  241. package/src/commands/manuals/mv.txt +0 -10
  242. package/src/commands/manuals/nano.txt +0 -11
  243. package/src/commands/manuals/neofetch.txt +0 -10
  244. package/src/commands/manuals/node.txt +0 -13
  245. package/src/commands/manuals/npm.txt +0 -13
  246. package/src/commands/manuals/npx.txt +0 -13
  247. package/src/commands/manuals/passwd.txt +0 -11
  248. package/src/commands/manuals/ping.txt +0 -10
  249. package/src/commands/manuals/printf.txt +0 -11
  250. package/src/commands/manuals/ps.txt +0 -10
  251. package/src/commands/manuals/pwd.txt +0 -10
  252. package/src/commands/manuals/python3.txt +0 -13
  253. package/src/commands/manuals/readlink.txt +0 -10
  254. package/src/commands/manuals/return.txt +0 -10
  255. package/src/commands/manuals/rm.txt +0 -10
  256. package/src/commands/manuals/sed.txt +0 -11
  257. package/src/commands/manuals/set.txt +0 -11
  258. package/src/commands/manuals/shift.txt +0 -10
  259. package/src/commands/manuals/sleep.txt +0 -10
  260. package/src/commands/manuals/sort.txt +0 -12
  261. package/src/commands/manuals/source.txt +0 -11
  262. package/src/commands/manuals/ssh.txt +0 -11
  263. package/src/commands/manuals/stat.txt +0 -10
  264. package/src/commands/manuals/su.txt +0 -13
  265. package/src/commands/manuals/sudo.txt +0 -11
  266. package/src/commands/manuals/tail.txt +0 -10
  267. package/src/commands/manuals/tar.txt +0 -19
  268. package/src/commands/manuals/tee.txt +0 -10
  269. package/src/commands/manuals/test.txt +0 -11
  270. package/src/commands/manuals/touch.txt +0 -11
  271. package/src/commands/manuals/tr.txt +0 -10
  272. package/src/commands/manuals/trap.txt +0 -10
  273. package/src/commands/manuals/true.txt +0 -10
  274. package/src/commands/manuals/type.txt +0 -10
  275. package/src/commands/manuals/uname.txt +0 -12
  276. package/src/commands/manuals/uniq.txt +0 -12
  277. package/src/commands/manuals/unset.txt +0 -10
  278. package/src/commands/manuals/uptime.txt +0 -11
  279. package/src/commands/manuals/wc.txt +0 -12
  280. package/src/commands/manuals/wget.txt +0 -12
  281. package/src/commands/manuals/which.txt +0 -10
  282. package/src/commands/manuals/whoami.txt +0 -10
  283. package/src/commands/manuals/xargs.txt +0 -10
  284. package/src/commands/manuals-bundle.ts +0 -898
  285. package/src/commands/mkdir.ts +0 -31
  286. package/src/commands/mv.ts +0 -50
  287. package/src/commands/nano.ts +0 -38
  288. package/src/commands/neofetch.ts +0 -53
  289. package/src/commands/node.ts +0 -341
  290. package/src/commands/npm.ts +0 -132
  291. package/src/commands/passwd.ts +0 -50
  292. package/src/commands/ping.ts +0 -32
  293. package/src/commands/printf.ts +0 -129
  294. package/src/commands/ps.ts +0 -58
  295. package/src/commands/pwd.ts +0 -9
  296. package/src/commands/python.ts +0 -2229
  297. package/src/commands/read.ts +0 -46
  298. package/src/commands/registry.ts +0 -249
  299. package/src/commands/rm.ts +0 -42
  300. package/src/commands/runtime.ts +0 -378
  301. package/src/commands/sed.ts +0 -68
  302. package/src/commands/seq.ts +0 -43
  303. package/src/commands/set.ts +0 -29
  304. package/src/commands/sh.ts +0 -467
  305. package/src/commands/shift.ts +0 -63
  306. package/src/commands/sleep.ts +0 -20
  307. package/src/commands/sort.ts +0 -46
  308. package/src/commands/source.ts +0 -52
  309. package/src/commands/stat.ts +0 -61
  310. package/src/commands/su.ts +0 -72
  311. package/src/commands/sudo.ts +0 -76
  312. package/src/commands/tail.ts +0 -53
  313. package/src/commands/tar.ts +0 -102
  314. package/src/commands/tee.ts +0 -36
  315. package/src/commands/test.ts +0 -137
  316. package/src/commands/touch.ts +0 -28
  317. package/src/commands/tr.ts +0 -70
  318. package/src/commands/tree.ts +0 -20
  319. package/src/commands/true.ts +0 -27
  320. package/src/commands/type.ts +0 -48
  321. package/src/commands/uname.ts +0 -29
  322. package/src/commands/uniq.ts +0 -39
  323. package/src/commands/unset.ts +0 -17
  324. package/src/commands/uptime.ts +0 -54
  325. package/src/commands/wc.ts +0 -55
  326. package/src/commands/wget.ts +0 -148
  327. package/src/commands/which.ts +0 -37
  328. package/src/commands/who.ts +0 -25
  329. package/src/commands/whoami.ts +0 -14
  330. package/src/commands/xargs.ts +0 -31
  331. package/src/index.ts +0 -67
  332. package/src/modules/linuxRootfs.ts +0 -1961
  333. package/src/modules/neofetch.ts +0 -358
  334. package/src/modules/shellInteractive.ts +0 -57
  335. package/src/modules/shellRuntime.ts +0 -76
  336. package/src/self-standalone.ts +0 -542
  337. package/src/standalone-wo-sftp.ts +0 -38
  338. package/src/standalone.ts +0 -72
  339. package/src/types/commands.ts +0 -146
  340. package/src/types/pipeline.ts +0 -52
  341. package/src/types/streams.ts +0 -32
  342. package/src/types/tar-stream.d.ts +0 -38
  343. package/src/types/vfs.ts +0 -98
  344. package/src/utils/expand.ts +0 -491
  345. package/src/utils/perfLogger.ts +0 -72
  346. package/src/utils/tokenize.ts +0 -98
  347. package/src/utils/vfsDiff.ts +0 -275
  348. package/tests/command-helpers.test.ts +0 -116
  349. package/tests/commands-admin-net.test.ts +0 -441
  350. package/tests/commands-advanced.test.ts +0 -456
  351. package/tests/commands-core.test.ts +0 -562
  352. package/tests/commands-missing.test.ts +0 -570
  353. package/tests/commands-specific-units.test.ts +0 -327
  354. package/tests/commands-text-sys.test.ts +0 -445
  355. package/tests/expand.test.ts +0 -170
  356. package/tests/helpers.test.ts +0 -97
  357. package/tests/new-features.test.ts +0 -1036
  358. package/tests/parser-executor.test.ts +0 -37
  359. package/tests/sftp.test.ts +0 -323
  360. package/tests/ssh-exec.test.ts +0 -45
  361. package/tests/test-helper.ts +0 -79
  362. package/tests/users.test.ts +0 -86
  363. package/tsconfig.json +0 -49
  364. 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
- });