typescript-virtual-container 1.1.1-b → 1.1.1-c

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 (185) hide show
  1. package/.vscode/settings.json +1 -1
  2. package/dist/SSHClient/index.d.ts +138 -0
  3. package/dist/SSHClient/index.d.ts.map +1 -0
  4. package/dist/SSHClient/index.js +216 -0
  5. package/dist/SSHMimic/exec.d.ts +4 -0
  6. package/dist/SSHMimic/exec.d.ts.map +1 -0
  7. package/dist/SSHMimic/exec.js +21 -0
  8. package/dist/SSHMimic/executor.d.ts +9 -0
  9. package/dist/SSHMimic/executor.d.ts.map +1 -0
  10. package/dist/SSHMimic/executor.js +131 -0
  11. package/dist/SSHMimic/hostKey.d.ts +2 -0
  12. package/dist/SSHMimic/hostKey.d.ts.map +1 -0
  13. package/dist/SSHMimic/hostKey.js +17 -0
  14. package/dist/SSHMimic/index.d.ts +39 -0
  15. package/dist/SSHMimic/index.d.ts.map +1 -0
  16. package/dist/SSHMimic/index.js +113 -0
  17. package/dist/SSHMimic/loginFormat.d.ts +2 -0
  18. package/dist/SSHMimic/loginFormat.d.ts.map +1 -0
  19. package/dist/SSHMimic/loginFormat.js +10 -0
  20. package/dist/SSHMimic/prompt.d.ts +2 -0
  21. package/dist/SSHMimic/prompt.d.ts.map +1 -0
  22. package/dist/SSHMimic/prompt.js +9 -0
  23. package/dist/VirtualFileSystem/archive.d.ts +5 -0
  24. package/dist/VirtualFileSystem/archive.d.ts.map +1 -0
  25. package/dist/VirtualFileSystem/archive.js +56 -0
  26. package/dist/VirtualFileSystem/index.d.ts +131 -0
  27. package/dist/VirtualFileSystem/index.d.ts.map +1 -0
  28. package/dist/VirtualFileSystem/index.js +355 -0
  29. package/dist/VirtualFileSystem/internalTypes.d.ts +18 -0
  30. package/dist/VirtualFileSystem/internalTypes.d.ts.map +1 -0
  31. package/dist/VirtualFileSystem/internalTypes.js +0 -0
  32. package/dist/VirtualFileSystem/path.d.ts +9 -0
  33. package/dist/VirtualFileSystem/path.d.ts.map +1 -0
  34. package/dist/VirtualFileSystem/path.js +49 -0
  35. package/dist/VirtualFileSystem/snapshot.d.ts +5 -0
  36. package/dist/VirtualFileSystem/snapshot.d.ts.map +1 -0
  37. package/dist/VirtualFileSystem/snapshot.js +59 -0
  38. package/dist/VirtualFileSystem/tree.d.ts +3 -0
  39. package/dist/VirtualFileSystem/tree.d.ts.map +1 -0
  40. package/dist/VirtualFileSystem/tree.js +19 -0
  41. package/dist/VirtualShell/index.d.ts +86 -0
  42. package/dist/VirtualShell/index.d.ts.map +1 -0
  43. package/dist/VirtualShell/index.js +129 -0
  44. package/dist/VirtualShell/shell.d.ts +5 -0
  45. package/dist/VirtualShell/shell.d.ts.map +1 -0
  46. package/dist/VirtualShell/shell.js +473 -0
  47. package/dist/VirtualShell/shellParser.d.ts +4 -0
  48. package/dist/VirtualShell/shellParser.d.ts.map +1 -0
  49. package/dist/VirtualShell/shellParser.js +207 -0
  50. package/dist/VirtualUserManager/index.d.ts +168 -0
  51. package/dist/VirtualUserManager/index.d.ts.map +1 -0
  52. package/dist/VirtualUserManager/index.js +375 -0
  53. package/dist/commands/adduser.d.ts +3 -0
  54. package/dist/commands/adduser.d.ts.map +1 -0
  55. package/dist/commands/adduser.js +18 -0
  56. package/dist/commands/cat.d.ts +3 -0
  57. package/dist/commands/cat.d.ts.map +1 -0
  58. package/dist/commands/cat.js +15 -0
  59. package/dist/commands/cd.d.ts +3 -0
  60. package/dist/commands/cd.d.ts.map +1 -0
  61. package/dist/commands/cd.js +17 -0
  62. package/dist/commands/clear.d.ts +3 -0
  63. package/dist/commands/clear.d.ts.map +1 -0
  64. package/dist/commands/clear.js +5 -0
  65. package/dist/commands/command-helpers.d.ts +23 -0
  66. package/dist/commands/command-helpers.d.ts.map +1 -0
  67. package/dist/commands/command-helpers.js +139 -0
  68. package/dist/commands/curl.d.ts +3 -0
  69. package/dist/commands/curl.d.ts.map +1 -0
  70. package/dist/commands/curl.js +44 -0
  71. package/dist/commands/deluser.d.ts +3 -0
  72. package/dist/commands/deluser.d.ts.map +1 -0
  73. package/dist/commands/deluser.js +15 -0
  74. package/dist/commands/echo.d.ts +3 -0
  75. package/dist/commands/echo.d.ts.map +1 -0
  76. package/dist/commands/echo.js +22 -0
  77. package/dist/commands/env.d.ts +3 -0
  78. package/dist/commands/env.d.ts.map +1 -0
  79. package/dist/commands/env.js +18 -0
  80. package/dist/commands/exit.d.ts +3 -0
  81. package/dist/commands/exit.d.ts.map +1 -0
  82. package/dist/commands/exit.js +5 -0
  83. package/dist/commands/export.d.ts +3 -0
  84. package/dist/commands/export.d.ts.map +1 -0
  85. package/dist/commands/export.js +34 -0
  86. package/dist/commands/grep.d.ts +3 -0
  87. package/dist/commands/grep.d.ts.map +1 -0
  88. package/dist/commands/grep.js +69 -0
  89. package/dist/commands/help.d.ts +3 -0
  90. package/dist/commands/help.d.ts.map +1 -0
  91. package/dist/commands/help.js +7 -0
  92. package/dist/commands/helpers.d.ts +26 -0
  93. package/dist/commands/helpers.d.ts.map +1 -0
  94. package/dist/commands/helpers.js +160 -0
  95. package/dist/commands/hostname.d.ts +3 -0
  96. package/dist/commands/hostname.d.ts.map +1 -0
  97. package/dist/commands/hostname.js +5 -0
  98. package/dist/commands/htop.d.ts +3 -0
  99. package/dist/commands/htop.d.ts.map +1 -0
  100. package/dist/commands/htop.js +10 -0
  101. package/dist/commands/index.d.ts +8 -0
  102. package/dist/commands/index.d.ts.map +1 -0
  103. package/dist/commands/index.js +212 -0
  104. package/dist/commands/ls.d.ts +3 -0
  105. package/dist/commands/ls.d.ts.map +1 -0
  106. package/dist/commands/ls.js +47 -0
  107. package/dist/commands/mkdir.d.ts +3 -0
  108. package/dist/commands/mkdir.d.ts.map +1 -0
  109. package/dist/commands/mkdir.js +21 -0
  110. package/dist/commands/nano.d.ts +3 -0
  111. package/dist/commands/nano.d.ts.map +1 -0
  112. package/dist/commands/nano.js +27 -0
  113. package/dist/commands/neofetch.d.ts +3 -0
  114. package/dist/commands/neofetch.d.ts.map +1 -0
  115. package/dist/commands/neofetch.js +32 -0
  116. package/dist/commands/pwd.d.ts +3 -0
  117. package/dist/commands/pwd.d.ts.map +1 -0
  118. package/dist/commands/pwd.js +5 -0
  119. package/dist/commands/rm.d.ts +3 -0
  120. package/dist/commands/rm.d.ts.map +1 -0
  121. package/dist/commands/rm.js +29 -0
  122. package/dist/commands/set.d.ts +7 -0
  123. package/dist/commands/set.d.ts.map +1 -0
  124. package/dist/commands/set.js +64 -0
  125. package/dist/commands/sh.d.ts +4 -0
  126. package/dist/commands/sh.d.ts.map +1 -0
  127. package/dist/commands/sh.js +45 -0
  128. package/dist/commands/su.d.ts +3 -0
  129. package/dist/commands/su.d.ts.map +1 -0
  130. package/dist/commands/su.js +24 -0
  131. package/dist/commands/sudo.d.ts +3 -0
  132. package/dist/commands/sudo.d.ts.map +1 -0
  133. package/dist/commands/sudo.js +47 -0
  134. package/dist/commands/touch.d.ts +3 -0
  135. package/dist/commands/touch.d.ts.map +1 -0
  136. package/dist/commands/touch.js +18 -0
  137. package/dist/commands/tree.d.ts +3 -0
  138. package/dist/commands/tree.d.ts.map +1 -0
  139. package/dist/commands/tree.js +11 -0
  140. package/dist/commands/unset.d.ts +3 -0
  141. package/dist/commands/unset.d.ts.map +1 -0
  142. package/dist/commands/unset.js +15 -0
  143. package/dist/commands/wget.d.ts +3 -0
  144. package/dist/commands/wget.d.ts.map +1 -0
  145. package/dist/commands/wget.js +113 -0
  146. package/dist/commands/who.d.ts +3 -0
  147. package/dist/commands/who.d.ts.map +1 -0
  148. package/dist/commands/who.js +15 -0
  149. package/dist/commands/whoami.d.ts +3 -0
  150. package/dist/commands/whoami.d.ts.map +1 -0
  151. package/dist/commands/whoami.js +5 -0
  152. package/dist/index.d.ts +11 -0
  153. package/dist/index.d.ts.map +1 -0
  154. package/dist/index.js +7 -0
  155. package/dist/modules/neofetch.d.ts +19 -0
  156. package/dist/modules/neofetch.d.ts.map +1 -0
  157. package/dist/modules/neofetch.js +284 -0
  158. package/dist/modules/shellInteractive.d.ts +6 -0
  159. package/dist/modules/shellInteractive.d.ts.map +1 -0
  160. package/dist/modules/shellInteractive.js +26 -0
  161. package/dist/modules/shellRuntime.d.ts +11 -0
  162. package/dist/modules/shellRuntime.d.ts.map +1 -0
  163. package/dist/modules/shellRuntime.js +52 -0
  164. package/dist/standalone.d.ts +2 -0
  165. package/dist/standalone.d.ts.map +1 -0
  166. package/dist/standalone.js +25 -0
  167. package/dist/types/commands.d.ts +89 -0
  168. package/dist/types/commands.d.ts.map +1 -0
  169. package/dist/types/commands.js +0 -0
  170. package/dist/types/pipeline.d.ts +23 -0
  171. package/dist/types/pipeline.d.ts.map +1 -0
  172. package/dist/types/pipeline.js +0 -0
  173. package/dist/types/streams.d.ts +32 -0
  174. package/dist/types/streams.d.ts.map +1 -0
  175. package/dist/types/streams.js +0 -0
  176. package/dist/types/vfs.d.ts +71 -0
  177. package/dist/types/vfs.d.ts.map +1 -0
  178. package/dist/types/vfs.js +0 -0
  179. package/package.json +4 -2
  180. package/src/VirtualShell/shell.ts +3 -3
  181. package/src/commands/neofetch.ts +1 -1
  182. package/{modules → src/modules}/neofetch.ts +56 -51
  183. package/{modules → src/modules}/shellInteractive.ts +16 -4
  184. package/tsconfig.json +19 -8
  185. /package/{modules → src/modules}/shellRuntime.ts +0 -0
@@ -0,0 +1,355 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { gunzipSync, gzipSync } from "node:zlib";
4
+ import { archiveExists, createTarBuffer, readSnapshotFromTar } from "./archive";
5
+ import { getNode, getParentDirectory, normalizePath, splitPath } from "./path";
6
+ import { applySnapshot, createSnapshot } from "./snapshot";
7
+ import { renderTree } from "./tree";
8
+ /**
9
+ * In-memory virtual filesystem with tar.gz mirror persistence.
10
+ *
11
+ * Paths are normalized to POSIX-like absolute paths. Use
12
+ * {@link VirtualFileSystem.restoreMirror} on startup and
13
+ * {@link VirtualFileSystem.flushMirror} to persist pending changes.
14
+ */
15
+ class VirtualFileSystem {
16
+ root;
17
+ archivePath;
18
+ dirty = false;
19
+ computeNodeUsageBytes(node) {
20
+ if (node.type === "file") {
21
+ return node.content.length;
22
+ }
23
+ let total = 0;
24
+ for (const child of node.children.values()) {
25
+ total += this.computeNodeUsageBytes(child);
26
+ }
27
+ return total;
28
+ }
29
+ /**
30
+ * Creates a virtual filesystem instance.
31
+ *
32
+ * @param baseDir Base directory used to resolve mirror archive location.
33
+ */
34
+ constructor(baseDir = process.cwd()) {
35
+ const now = new Date();
36
+ this.archivePath = path.resolve(baseDir, ".vfs", "mirror.tar.gz");
37
+ this.root = {
38
+ type: "directory",
39
+ name: "",
40
+ mode: 0o755,
41
+ createdAt: now,
42
+ updatedAt: now,
43
+ children: new Map(),
44
+ };
45
+ }
46
+ /**
47
+ * Restores filesystem state from mirror archive.
48
+ *
49
+ * If archive does not exist or cannot be read, creates fresh mirror file.
50
+ */
51
+ async restoreMirror() {
52
+ await fs.mkdir(path.dirname(this.archivePath), { recursive: true });
53
+ try {
54
+ const compressed = await fs.readFile(this.archivePath);
55
+ const tarBuffer = gunzipSync(compressed);
56
+ const snapshot = await readSnapshotFromTar(tarBuffer);
57
+ applySnapshot(this.root, snapshot);
58
+ this.dirty = false;
59
+ return;
60
+ }
61
+ catch {
62
+ console.warn(`No valid mirror archive found at '${this.archivePath}'. Starting with empty filesystem.`);
63
+ await this.flushMirror();
64
+ }
65
+ }
66
+ /**
67
+ * Persists current filesystem state to mirror archive.
68
+ *
69
+ * No-op when nothing changed and archive already exists.
70
+ */
71
+ async flushMirror() {
72
+ if (!this.dirty && (await archiveExists(this.archivePath))) {
73
+ return;
74
+ }
75
+ await fs.mkdir(path.dirname(this.archivePath), { recursive: true });
76
+ const snapshotJson = JSON.stringify(createSnapshot(this.root), null, 2);
77
+ const tarBuffer = await createTarBuffer(snapshotJson);
78
+ const compressed = gzipSync(tarBuffer);
79
+ await fs.writeFile(this.archivePath, compressed);
80
+ this.dirty = false;
81
+ }
82
+ /**
83
+ * Creates directory and any missing parent directories.
84
+ *
85
+ * @param targetPath Absolute or relative path to directory.
86
+ * @param mode POSIX-like mode bits for new directories.
87
+ */
88
+ mkdir(targetPath, mode = 0o755) {
89
+ const normalized = normalizePath(targetPath);
90
+ const parts = splitPath(normalized);
91
+ let current = this.root;
92
+ for (const part of parts) {
93
+ const existing = current.children.get(part);
94
+ if (!existing) {
95
+ const now = new Date();
96
+ const nextDir = {
97
+ type: "directory",
98
+ name: part,
99
+ mode,
100
+ createdAt: now,
101
+ updatedAt: now,
102
+ children: new Map(),
103
+ };
104
+ current.children.set(part, nextDir);
105
+ current.updatedAt = now;
106
+ this.dirty = true;
107
+ current = nextDir;
108
+ continue;
109
+ }
110
+ if (existing.type !== "directory") {
111
+ throw new Error(`Cannot create directory '${normalized}': '${part}' is a file.`);
112
+ }
113
+ current = existing;
114
+ }
115
+ }
116
+ /**
117
+ * Writes UTF-8 text or binary content into file.
118
+ *
119
+ * Parent directories are created when missing.
120
+ *
121
+ * @param targetPath Destination file path.
122
+ * @param content File content as string or Buffer.
123
+ * @param options Optional write behavior (mode, compression).
124
+ */
125
+ writeFile(targetPath, content, options = {}) {
126
+ const normalized = normalizePath(targetPath);
127
+ const { parent, name } = getParentDirectory(this.root, normalized, true, (pathToCreate) => this.mkdir(pathToCreate));
128
+ const now = new Date();
129
+ const rawContent = Buffer.isBuffer(content)
130
+ ? content
131
+ : Buffer.from(content, "utf8");
132
+ const shouldCompress = options.compress ?? false;
133
+ const storedContent = shouldCompress ? gzipSync(rawContent) : rawContent;
134
+ const existing = parent.children.get(name);
135
+ if (existing && existing.type === "directory") {
136
+ throw new Error(`Cannot write file '${normalized}': path is a directory.`);
137
+ }
138
+ const createdAt = existing?.type === "file" ? existing.createdAt : now;
139
+ const mode = options.mode ?? (existing?.type === "file" ? existing.mode : 0o644);
140
+ parent.children.set(name, {
141
+ type: "file",
142
+ name,
143
+ mode,
144
+ createdAt,
145
+ updatedAt: now,
146
+ content: storedContent,
147
+ compressed: shouldCompress,
148
+ });
149
+ parent.updatedAt = now;
150
+ this.dirty = true;
151
+ }
152
+ /**
153
+ * Reads file content as UTF-8 text.
154
+ *
155
+ * Compressed files are transparently decompressed.
156
+ *
157
+ * @param targetPath Path to file.
158
+ * @returns UTF-8 string content.
159
+ */
160
+ readFile(targetPath) {
161
+ const node = getNode(this.root, targetPath);
162
+ if (node.type !== "file") {
163
+ throw new Error(`Cannot read '${targetPath}': not a file.`);
164
+ }
165
+ const raw = node.compressed ? gunzipSync(node.content) : node.content;
166
+ return raw.toString("utf8");
167
+ }
168
+ /**
169
+ * Checks whether node exists at path.
170
+ *
171
+ * @param targetPath Node path.
172
+ * @returns True when file or directory exists.
173
+ */
174
+ exists(targetPath) {
175
+ try {
176
+ getNode(this.root, targetPath);
177
+ return true;
178
+ }
179
+ catch {
180
+ return false;
181
+ }
182
+ }
183
+ /**
184
+ * Updates mode bits for file or directory.
185
+ *
186
+ * @param targetPath Node path.
187
+ * @param mode New POSIX-like mode.
188
+ */
189
+ chmod(targetPath, mode) {
190
+ const node = getNode(this.root, targetPath);
191
+ node.mode = mode;
192
+ node.updatedAt = new Date();
193
+ this.dirty = true;
194
+ }
195
+ /**
196
+ * Returns metadata for file or directory.
197
+ *
198
+ * @param targetPath Node path.
199
+ * @returns Typed stat object based on node type.
200
+ */
201
+ stat(targetPath) {
202
+ const normalized = normalizePath(targetPath);
203
+ const node = getNode(this.root, normalized);
204
+ if (node.type === "file") {
205
+ return {
206
+ type: "file",
207
+ name: node.name,
208
+ path: normalized,
209
+ mode: node.mode,
210
+ createdAt: node.createdAt,
211
+ updatedAt: node.updatedAt,
212
+ compressed: node.compressed,
213
+ size: node.content.length,
214
+ };
215
+ }
216
+ return {
217
+ type: "directory",
218
+ name: node.name,
219
+ path: normalized,
220
+ mode: node.mode,
221
+ createdAt: node.createdAt,
222
+ updatedAt: node.updatedAt,
223
+ childrenCount: node.children.size,
224
+ };
225
+ }
226
+ /**
227
+ * Lists direct children names of directory.
228
+ *
229
+ * @param dirPath Directory path, defaults to root.
230
+ * @returns Sorted child names.
231
+ */
232
+ list(dirPath = "/") {
233
+ const node = getNode(this.root, dirPath);
234
+ if (node.type !== "directory") {
235
+ throw new Error(`Cannot list '${dirPath}': not a directory.`);
236
+ }
237
+ return Array.from(node.children.keys()).sort();
238
+ }
239
+ /**
240
+ * Renders ASCII tree view of directory hierarchy.
241
+ *
242
+ * @param dirPath Directory path, defaults to root.
243
+ * @returns Multi-line tree string.
244
+ */
245
+ tree(dirPath = "/") {
246
+ const node = getNode(this.root, dirPath);
247
+ if (node.type !== "directory") {
248
+ throw new Error(`Cannot render tree for '${dirPath}': not a directory.`);
249
+ }
250
+ const rootLabel = dirPath === "/" ? "/" : path.posix.basename(normalizePath(dirPath));
251
+ return renderTree(node, rootLabel);
252
+ }
253
+ /**
254
+ * Computes total stored file bytes under a path.
255
+ *
256
+ * File usage is based on in-memory stored bytes, including compressed
257
+ * payload size when files are marked as compressed.
258
+ *
259
+ * @param targetPath File or directory path to measure, defaults to root.
260
+ * @returns Total byte usage for file content under target path.
261
+ */
262
+ getUsageBytes(targetPath = "/") {
263
+ const node = getNode(this.root, targetPath);
264
+ return this.computeNodeUsageBytes(node);
265
+ }
266
+ /**
267
+ * Compresses file content with gzip and flags node as compressed.
268
+ *
269
+ * @param targetPath Path to file.
270
+ */
271
+ compressFile(targetPath) {
272
+ const node = getNode(this.root, targetPath);
273
+ if (node.type !== "file") {
274
+ throw new Error(`Cannot compress '${targetPath}': not a file.`);
275
+ }
276
+ if (!node.compressed) {
277
+ node.content = gzipSync(node.content);
278
+ node.compressed = true;
279
+ node.updatedAt = new Date();
280
+ this.dirty = true;
281
+ }
282
+ }
283
+ /**
284
+ * Decompresses gzip-compressed file content.
285
+ *
286
+ * @param targetPath Path to file.
287
+ */
288
+ decompressFile(targetPath) {
289
+ const node = getNode(this.root, targetPath);
290
+ if (node.type !== "file") {
291
+ throw new Error(`Cannot decompress '${targetPath}': not a file.`);
292
+ }
293
+ if (node.compressed) {
294
+ node.content = gunzipSync(node.content);
295
+ node.compressed = false;
296
+ node.updatedAt = new Date();
297
+ this.dirty = true;
298
+ }
299
+ }
300
+ /**
301
+ * Removes file or directory node.
302
+ *
303
+ * @param targetPath Path to remove.
304
+ * @param options Removal options, including recursive delete.
305
+ */
306
+ remove(targetPath, options = {}) {
307
+ const normalized = normalizePath(targetPath);
308
+ if (normalized === "/") {
309
+ throw new Error("Cannot remove root directory.");
310
+ }
311
+ const { parent, name } = getParentDirectory(this.root, normalized, false, () => undefined);
312
+ const node = parent.children.get(name);
313
+ if (!node) {
314
+ throw new Error(`Path '${normalized}' does not exist.`);
315
+ }
316
+ if (node.type === "directory" &&
317
+ node.children.size > 0 &&
318
+ !options.recursive) {
319
+ throw new Error(`Directory '${normalized}' is not empty. Use recursive option.`);
320
+ }
321
+ parent.children.delete(name);
322
+ parent.updatedAt = new Date();
323
+ this.dirty = true;
324
+ }
325
+ /**
326
+ * Moves or renames node to destination path.
327
+ *
328
+ * @param fromPath Existing source path.
329
+ * @param toPath Destination path.
330
+ */
331
+ move(fromPath, toPath) {
332
+ const fromNormalized = normalizePath(fromPath);
333
+ const toNormalized = normalizePath(toPath);
334
+ if (fromNormalized === "/" || toNormalized === "/") {
335
+ throw new Error("Cannot move root directory.");
336
+ }
337
+ const { parent: fromParent, name: fromName } = getParentDirectory(this.root, fromNormalized, false, () => undefined);
338
+ const node = fromParent.children.get(fromName);
339
+ if (!node) {
340
+ throw new Error(`Path '${fromNormalized}' does not exist.`);
341
+ }
342
+ const { parent: toParent, name: toName } = getParentDirectory(this.root, toNormalized, true, (pathToCreate) => this.mkdir(pathToCreate));
343
+ if (toParent.children.has(toName)) {
344
+ throw new Error(`Destination '${toNormalized}' already exists.`);
345
+ }
346
+ fromParent.children.delete(fromName);
347
+ node.name = toName;
348
+ node.updatedAt = new Date();
349
+ toParent.children.set(toName, node);
350
+ fromParent.updatedAt = new Date();
351
+ toParent.updatedAt = new Date();
352
+ this.dirty = true;
353
+ }
354
+ }
355
+ export default VirtualFileSystem;
@@ -0,0 +1,18 @@
1
+ export type InternalNode = InternalFileNode | InternalDirectoryNode;
2
+ interface InternalBaseNode {
3
+ name: string;
4
+ mode: number;
5
+ createdAt: Date;
6
+ updatedAt: Date;
7
+ }
8
+ export interface InternalFileNode extends InternalBaseNode {
9
+ type: "file";
10
+ content: Buffer;
11
+ compressed: boolean;
12
+ }
13
+ export interface InternalDirectoryNode extends InternalBaseNode {
14
+ type: "directory";
15
+ children: Map<string, InternalNode>;
16
+ }
17
+ export {};
18
+ //# sourceMappingURL=internalTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internalTypes.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/internalTypes.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,qBAAqB,CAAC;AAEpE,UAAU,gBAAgB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC9D,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACpC"}
File without changes
@@ -0,0 +1,9 @@
1
+ import type { InternalDirectoryNode, InternalNode } from "./internalTypes";
2
+ export declare function normalizePath(rawPath: string): string;
3
+ export declare function splitPath(normalizedPath: string): string[];
4
+ export declare function getNode(root: InternalDirectoryNode, targetPath: string): InternalNode;
5
+ export declare function getParentDirectory(root: InternalDirectoryNode, targetPath: string, createIfMissing: boolean, createPath: (pathToCreate: string) => void): {
6
+ parent: InternalDirectoryNode;
7
+ name: string;
8
+ };
9
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/path.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE3E,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASrD;AAED,wBAAgB,SAAS,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAE1D;AAED,wBAAgB,OAAO,CACtB,IAAI,EAAE,qBAAqB,EAC3B,UAAU,EAAE,MAAM,GAChB,YAAY,CAsBd;AAED,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,qBAAqB,EAC3B,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,OAAO,EACxB,UAAU,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,GACxC;IAAE,MAAM,EAAE,qBAAqB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAuBjD"}
@@ -0,0 +1,49 @@
1
+ import * as path from "node:path";
2
+ export function normalizePath(rawPath) {
3
+ if (!rawPath || rawPath.trim() === "") {
4
+ return "/";
5
+ }
6
+ const normalized = path.posix.normalize(rawPath.startsWith("/") ? rawPath : `/${rawPath}`);
7
+ return normalized === "" ? "/" : normalized;
8
+ }
9
+ export function splitPath(normalizedPath) {
10
+ return normalizedPath.split("/").filter(Boolean);
11
+ }
12
+ export function getNode(root, targetPath) {
13
+ const normalized = normalizePath(targetPath);
14
+ if (normalized === "/") {
15
+ return root;
16
+ }
17
+ const parts = splitPath(normalized);
18
+ let current = root;
19
+ for (const part of parts) {
20
+ if (current.type !== "directory") {
21
+ throw new Error(`Path '${normalized}' does not exist.`);
22
+ }
23
+ const next = current.children.get(part);
24
+ if (!next) {
25
+ throw new Error(`Path '${normalized}' does not exist.`);
26
+ }
27
+ current = next;
28
+ }
29
+ return current;
30
+ }
31
+ export function getParentDirectory(root, targetPath, createIfMissing, createPath) {
32
+ const normalized = normalizePath(targetPath);
33
+ if (normalized === "/") {
34
+ throw new Error("Root path has no parent directory.");
35
+ }
36
+ const parentPath = path.posix.dirname(normalized);
37
+ const name = path.posix.basename(normalized);
38
+ if (!name) {
39
+ throw new Error(`Invalid path '${targetPath}'.`);
40
+ }
41
+ if (createIfMissing) {
42
+ createPath(parentPath);
43
+ }
44
+ const parentNode = getNode(root, parentPath);
45
+ if (parentNode.type !== "directory") {
46
+ throw new Error(`Parent path '${parentPath}' is not a directory.`);
47
+ }
48
+ return { parent: parentNode, name };
49
+ }
@@ -0,0 +1,5 @@
1
+ import type { VfsSnapshot } from "../types/vfs";
2
+ import type { InternalDirectoryNode } from "./internalTypes";
3
+ export declare function createSnapshot(root: InternalDirectoryNode): VfsSnapshot;
4
+ export declare function applySnapshot(rootTarget: InternalDirectoryNode, snapshot: VfsSnapshot): void;
5
+ //# sourceMappingURL=snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,WAAW,EAGX,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,qBAAqB,EAAgB,MAAM,iBAAiB,CAAC;AAgE3E,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,WAAW,CAEvE;AAED,wBAAgB,aAAa,CAC5B,UAAU,EAAE,qBAAqB,EACjC,QAAQ,EAAE,WAAW,GACnB,IAAI,CAON"}
@@ -0,0 +1,59 @@
1
+ function serializeNode(node) {
2
+ if (node.type === "file") {
3
+ return {
4
+ type: "file",
5
+ name: node.name,
6
+ mode: node.mode,
7
+ createdAt: node.createdAt.toISOString(),
8
+ updatedAt: node.updatedAt.toISOString(),
9
+ compressed: node.compressed,
10
+ contentBase64: node.content.toString("base64"),
11
+ };
12
+ }
13
+ return serializeDirectory(node);
14
+ }
15
+ function serializeDirectory(node) {
16
+ return {
17
+ type: "directory",
18
+ name: node.name,
19
+ mode: node.mode,
20
+ createdAt: node.createdAt.toISOString(),
21
+ updatedAt: node.updatedAt.toISOString(),
22
+ children: Array.from(node.children.values()).map((child) => serializeNode(child)),
23
+ };
24
+ }
25
+ function deserializeNode(node) {
26
+ if (node.type === "file") {
27
+ return {
28
+ type: "file",
29
+ name: node.name,
30
+ mode: node.mode,
31
+ createdAt: new Date(node.createdAt),
32
+ updatedAt: new Date(node.updatedAt),
33
+ content: Buffer.from(node.contentBase64, "base64"),
34
+ compressed: node.compressed,
35
+ };
36
+ }
37
+ return deserializeDirectory(node);
38
+ }
39
+ function deserializeDirectory(node) {
40
+ return {
41
+ type: "directory",
42
+ name: node.name,
43
+ mode: node.mode,
44
+ createdAt: new Date(node.createdAt),
45
+ updatedAt: new Date(node.updatedAt),
46
+ children: new Map(node.children.map((child) => [child.name, deserializeNode(child)])),
47
+ };
48
+ }
49
+ export function createSnapshot(root) {
50
+ return { root: serializeDirectory(root) };
51
+ }
52
+ export function applySnapshot(rootTarget, snapshot) {
53
+ const root = deserializeDirectory(snapshot.root);
54
+ rootTarget.name = root.name;
55
+ rootTarget.mode = root.mode;
56
+ rootTarget.createdAt = root.createdAt;
57
+ rootTarget.updatedAt = root.updatedAt;
58
+ rootTarget.children = root.children;
59
+ }
@@ -0,0 +1,3 @@
1
+ import type { InternalDirectoryNode } from "./internalTypes";
2
+ export declare function renderTree(node: InternalDirectoryNode, rootLabel: string): string;
3
+ //# sourceMappingURL=tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AA0B7D,wBAAgB,UAAU,CACzB,IAAI,EAAE,qBAAqB,EAC3B,SAAS,EAAE,MAAM,GACf,MAAM,CAIR"}
@@ -0,0 +1,19 @@
1
+ function walkTree(node, indent, lines) {
2
+ const entries = Array.from(node.children.entries()).sort(([a], [b]) => a.localeCompare(b));
3
+ entries.forEach(([name, child], index) => {
4
+ const isLast = index === entries.length - 1;
5
+ const branch = isLast ? "`-- " : "|-- ";
6
+ const nextIndent = indent + (isLast ? " " : "| ");
7
+ if (child.type === "file") {
8
+ lines.push(`${indent}${branch}${name}${child.compressed ? " [gz]" : ""}`);
9
+ return;
10
+ }
11
+ lines.push(`${indent}${branch}${name}/`);
12
+ walkTree(child, nextIndent, lines);
13
+ });
14
+ }
15
+ export function renderTree(node, rootLabel) {
16
+ const lines = [rootLabel];
17
+ walkTree(node, "", lines);
18
+ return lines.join("\n");
19
+ }
@@ -0,0 +1,86 @@
1
+ import type { CommandContext, CommandResult } from "../types/commands";
2
+ import type { ShellStream } from "../types/streams";
3
+ import VirtualFileSystem from "../VirtualFileSystem";
4
+ import { VirtualUserManager } from "../VirtualUserManager";
5
+ export interface ShellProperties {
6
+ kernel: string;
7
+ os: string;
8
+ arch: string;
9
+ }
10
+ /**
11
+ * Coordinates the virtual filesystem, user manager, and command runtime.
12
+ *
13
+ * Instances are used both by the SSH server facade and by the programmatic
14
+ * client API.
15
+ */
16
+ declare class VirtualShell {
17
+ basePath: string;
18
+ vfs: VirtualFileSystem;
19
+ users: VirtualUserManager;
20
+ hostname: string;
21
+ properties: ShellProperties;
22
+ /**
23
+ * Creates a new virtual shell instance.
24
+ *
25
+ * @param hostname Virtual hostname used for prompts and idents.
26
+ * @param properties Customizable properties shown in `uname -a` and similar commands.
27
+ * @param basePath Optional base path for the virtual filesystem (defaults to process.cwd()).
28
+ */
29
+ constructor(hostname: string, properties?: ShellProperties, basePath?: string);
30
+ /**
31
+ * Registers a new command in the shell runtime.
32
+ *
33
+ * @param name Case-insensitive command name (no spaces).
34
+ * @param params List of parameter names for help text (no validation).
35
+ * @param callback Function invoked with command context on execution.
36
+ */
37
+ addCommand(name: string, params: string[], callback: (ctx: CommandContext) => CommandResult | Promise<CommandResult>): void;
38
+ /**
39
+ * Executes a command line string in the context of this shell instance.
40
+ *
41
+ * @param rawInput
42
+ * @param authUser
43
+ * @param cwd
44
+ */
45
+ executeCommand(rawInput: string, authUser: string, cwd: string): void;
46
+ /**
47
+ * Starts an interactive session with the shell.
48
+ *
49
+ * @param stream The stream for the interactive session.
50
+ * @param authUser The authenticated user for the session.
51
+ * @param sessionId The ID of the session.
52
+ * @param remoteAddress The address of the remote client.
53
+ */
54
+ startInteractiveSession(stream: ShellStream, authUser: string, sessionId: string | null, remoteAddress: string, terminalSize: {
55
+ cols: number;
56
+ rows: number;
57
+ }): void;
58
+ /**
59
+ * Returns virtual filesystem instance after server started.
60
+ *
61
+ * @returns VirtualFileSystem or null when not started.
62
+ */
63
+ getVfs(): VirtualFileSystem | null;
64
+ /**
65
+ * Returns user manager instance after server started.
66
+ *
67
+ * @returns VirtualUserManager or null when not started.
68
+ */
69
+ getUsers(): VirtualUserManager | null;
70
+ /**
71
+ * Returns hostname shown in prompts and idents.
72
+ *
73
+ * @returns Configured hostname label.
74
+ */
75
+ getHostname(): string;
76
+ /**
77
+ * Writes a file on behalf of a user with quota enforcement.
78
+ *
79
+ * @param authUser User performing the write.
80
+ * @param targetPath Destination path.
81
+ * @param content File content.
82
+ */
83
+ writeFileAsUser(authUser: string, targetPath: string, content: string | Buffer): void;
84
+ }
85
+ export { VirtualShell };
86
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,iBAAiB,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACb;AA8BD;;;;;GAKG;AACH,cAAM,YAAY;IACjB,QAAQ,EAAE,MAAM,CAAO;IACvB,GAAG,EAAE,iBAAiB,CAAC;IACvB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,eAAe,CAAC;IAE5B;;;;;;OAMG;gBAEF,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,eAAe,EAC5B,QAAQ,CAAC,EAAE,MAAM;IAqBlB;;;;;;OAMG;IACH,UAAU,CACT,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,EAChB,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GACvE,IAAI;IASP;;;;;;OAMG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAIrE;;;;;;;OAOG;IAEH,uBAAuB,CACtB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC1C,IAAI;IAcP;;;;OAIG;IACI,MAAM,IAAI,iBAAiB,GAAG,IAAI;IAIzC;;;;OAIG;IACI,QAAQ,IAAI,kBAAkB,GAAG,IAAI;IAI5C;;;;OAIG;IACI,WAAW,IAAI,MAAM;IAI5B;;;;;;OAMG;IACI,eAAe,CACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,GACtB,IAAI;CAIP;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}