typescript-virtual-container 1.2.4 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +868 -1245
- package/benchmark-results.txt +21 -21
- package/dist/SSHMimic/index.d.ts +19 -2
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +116 -20
- package/dist/VirtualFileSystem/index.d.ts +115 -88
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +406 -258
- package/dist/VirtualShell/index.d.ts +3 -4
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +4 -6
- package/dist/VirtualUserManager/index.d.ts +25 -0
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +33 -0
- package/dist/commands/chmod.d.ts +3 -0
- package/dist/commands/chmod.d.ts.map +1 -0
- package/dist/commands/chmod.js +31 -0
- package/dist/commands/cp.d.ts +3 -0
- package/dist/commands/cp.d.ts.map +1 -0
- package/dist/commands/cp.js +68 -0
- package/dist/commands/find.d.ts +3 -0
- package/dist/commands/find.d.ts.map +1 -0
- package/dist/commands/find.js +48 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +61 -35
- package/dist/commands/head.d.ts +3 -0
- package/dist/commands/head.d.ts.map +1 -0
- package/dist/commands/head.js +30 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +25 -35
- package/dist/commands/ln.d.ts +3 -0
- package/dist/commands/ln.d.ts.map +1 -0
- package/dist/commands/ln.js +42 -0
- package/dist/commands/mv.d.ts +3 -0
- package/dist/commands/mv.d.ts.map +1 -0
- package/dist/commands/mv.js +35 -0
- package/dist/commands/tail.d.ts +3 -0
- package/dist/commands/tail.d.ts.map +1 -0
- package/dist/commands/tail.js +33 -0
- package/dist/commands/wc.d.ts +3 -0
- package/dist/commands/wc.d.ts.map +1 -0
- package/dist/commands/wc.js +48 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +5 -2
- package/scripts/publish-package.sh +70 -0
- package/src/SSHMimic/index.ts +143 -28
- package/src/VirtualFileSystem/index.ts +500 -280
- package/src/VirtualShell/index.ts +4 -6
- package/src/VirtualUserManager/index.ts +41 -0
- package/src/commands/chmod.ts +33 -0
- package/src/commands/cp.ts +76 -0
- package/src/commands/find.ts +61 -0
- package/src/commands/grep.ts +54 -38
- package/src/commands/head.ts +35 -0
- package/src/commands/index.ts +25 -43
- package/src/commands/ln.ts +47 -0
- package/src/commands/mv.ts +43 -0
- package/src/commands/tail.ts +37 -0
- package/src/commands/wc.ts +48 -0
- package/src/index.ts +1 -0
- package/standalone.js +62 -52
- package/standalone.js.map +4 -4
- package/tests/bun-test-shim.ts +1 -0
- package/tests/sftp.test.ts +115 -191
- package/tests/users.test.ts +66 -83
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import type { CommandContext, CommandResult } from "../types/commands";
|
|
3
3
|
import type { ShellStream } from "../types/streams";
|
|
4
|
-
import VirtualFileSystem from "../VirtualFileSystem";
|
|
4
|
+
import VirtualFileSystem, { type VfsOptions } from "../VirtualFileSystem";
|
|
5
5
|
import { VirtualUserManager } from "../VirtualUserManager";
|
|
6
6
|
export interface ShellProperties {
|
|
7
7
|
kernel: string;
|
|
@@ -15,7 +15,6 @@ export interface ShellProperties {
|
|
|
15
15
|
* client API.
|
|
16
16
|
*/
|
|
17
17
|
declare class VirtualShell extends EventEmitter {
|
|
18
|
-
basePath: string;
|
|
19
18
|
vfs: VirtualFileSystem;
|
|
20
19
|
users: VirtualUserManager;
|
|
21
20
|
hostname: string;
|
|
@@ -26,9 +25,9 @@ declare class VirtualShell extends EventEmitter {
|
|
|
26
25
|
*
|
|
27
26
|
* @param hostname Virtual hostname used for prompts and idents.
|
|
28
27
|
* @param properties Customizable properties shown in `uname -a` and similar commands.
|
|
29
|
-
* @param
|
|
28
|
+
* @param vfsOptions Optional VFS persistence options (mode, snapshotPath).
|
|
30
29
|
*/
|
|
31
|
-
constructor(hostname: string, properties?: ShellProperties,
|
|
30
|
+
constructor(hostname: string, properties?: ShellProperties, vfsOptions?: VfsOptions);
|
|
32
31
|
/**
|
|
33
32
|
* Ensures initialization is complete before allowing operations.
|
|
34
33
|
* Call this before any authentication or command execution.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,iBAAiB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,iBAAiB,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC1E,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;AAmBD;;;;;GAKG;AACH,cAAM,YAAa,SAAQ,YAAY;IACtC,GAAG,EAAE,iBAAiB,CAAC;IACvB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,CAAC,WAAW,CAAgB;IAEnC;;;;;;OAMG;gBAEF,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,eAAe,EAC5B,UAAU,CAAC,EAAE,UAAU;IAqBxB;;;OAGG;IACU,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/C;;;;;;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;IAMrE;;;;;;;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;IAgBP;;;;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;CAKP;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { createCustomCommand, registerCommand, runCommand } from "../commands";
|
|
3
3
|
import { createPerfLogger } from "../utils/perfLogger";
|
|
4
|
-
import VirtualFileSystem from "../VirtualFileSystem";
|
|
4
|
+
import VirtualFileSystem, {} from "../VirtualFileSystem";
|
|
5
5
|
import { VirtualUserManager } from "../VirtualUserManager";
|
|
6
6
|
import { startShell } from "./shell";
|
|
7
7
|
const defaultShellProperties = {
|
|
@@ -24,7 +24,6 @@ function resolveAutoSudoForNewUsers() {
|
|
|
24
24
|
* client API.
|
|
25
25
|
*/
|
|
26
26
|
class VirtualShell extends EventEmitter {
|
|
27
|
-
basePath = ".";
|
|
28
27
|
vfs;
|
|
29
28
|
users;
|
|
30
29
|
hostname;
|
|
@@ -35,15 +34,14 @@ class VirtualShell extends EventEmitter {
|
|
|
35
34
|
*
|
|
36
35
|
* @param hostname Virtual hostname used for prompts and idents.
|
|
37
36
|
* @param properties Customizable properties shown in `uname -a` and similar commands.
|
|
38
|
-
* @param
|
|
37
|
+
* @param vfsOptions Optional VFS persistence options (mode, snapshotPath).
|
|
39
38
|
*/
|
|
40
|
-
constructor(hostname, properties,
|
|
39
|
+
constructor(hostname, properties, vfsOptions) {
|
|
41
40
|
super();
|
|
42
41
|
perf.mark("constructor");
|
|
43
42
|
this.hostname = hostname;
|
|
44
43
|
this.properties = properties || defaultShellProperties;
|
|
45
|
-
this.
|
|
46
|
-
this.vfs = new VirtualFileSystem(this.basePath);
|
|
44
|
+
this.vfs = new VirtualFileSystem(vfsOptions ?? {});
|
|
47
45
|
this.users = new VirtualUserManager(this.vfs, resolveAutoSudoForNewUsers());
|
|
48
46
|
// Store references to avoid TypeScript "used before assigned" errors
|
|
49
47
|
const vfs = this.vfs;
|
|
@@ -189,5 +189,30 @@ export declare class VirtualUserManager extends EventEmitter {
|
|
|
189
189
|
hashPassword(password: string): string;
|
|
190
190
|
private validateUsername;
|
|
191
191
|
private validatePassword;
|
|
192
|
+
private readonly authorizedKeys;
|
|
193
|
+
/**
|
|
194
|
+
* Adds an SSH public key for a user, enabling public-key authentication.
|
|
195
|
+
*
|
|
196
|
+
* @param username Target user.
|
|
197
|
+
* @param algo Key algorithm (e.g. "ssh-rsa", "ssh-ed25519").
|
|
198
|
+
* @param data Raw key data as a Buffer (the base64-decoded key bytes).
|
|
199
|
+
*/
|
|
200
|
+
addAuthorizedKey(username: string, algo: string, data: Buffer): void;
|
|
201
|
+
/**
|
|
202
|
+
* Removes all authorized keys for a user.
|
|
203
|
+
*
|
|
204
|
+
* @param username Target user.
|
|
205
|
+
*/
|
|
206
|
+
removeAuthorizedKeys(username: string): void;
|
|
207
|
+
/**
|
|
208
|
+
* Returns the list of authorized keys for a user.
|
|
209
|
+
* Returns an empty array when no keys are registered.
|
|
210
|
+
*
|
|
211
|
+
* @param username Target user.
|
|
212
|
+
*/
|
|
213
|
+
getAuthorizedKeys(username: string): Array<{
|
|
214
|
+
algo: string;
|
|
215
|
+
data: Buffer;
|
|
216
|
+
}>;
|
|
192
217
|
}
|
|
193
218
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -505,6 +505,39 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
505
505
|
throw new Error("invalid password");
|
|
506
506
|
}
|
|
507
507
|
}
|
|
508
|
+
authorizedKeys = new Map();
|
|
509
|
+
/**
|
|
510
|
+
* Adds an SSH public key for a user, enabling public-key authentication.
|
|
511
|
+
*
|
|
512
|
+
* @param username Target user.
|
|
513
|
+
* @param algo Key algorithm (e.g. "ssh-rsa", "ssh-ed25519").
|
|
514
|
+
* @param data Raw key data as a Buffer (the base64-decoded key bytes).
|
|
515
|
+
*/
|
|
516
|
+
addAuthorizedKey(username, algo, data) {
|
|
517
|
+
perf.mark("addAuthorizedKey");
|
|
518
|
+
const keys = this.authorizedKeys.get(username) ?? [];
|
|
519
|
+
keys.push({ algo, data });
|
|
520
|
+
this.authorizedKeys.set(username, keys);
|
|
521
|
+
this.emit("key:add", { username, algo });
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Removes all authorized keys for a user.
|
|
525
|
+
*
|
|
526
|
+
* @param username Target user.
|
|
527
|
+
*/
|
|
528
|
+
removeAuthorizedKeys(username) {
|
|
529
|
+
this.authorizedKeys.delete(username);
|
|
530
|
+
this.emit("key:remove", { username });
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Returns the list of authorized keys for a user.
|
|
534
|
+
* Returns an empty array when no keys are registered.
|
|
535
|
+
*
|
|
536
|
+
* @param username Target user.
|
|
537
|
+
*/
|
|
538
|
+
getAuthorizedKeys(username) {
|
|
539
|
+
return this.authorizedKeys.get(username) ?? [];
|
|
540
|
+
}
|
|
508
541
|
}
|
|
509
542
|
function normalizeVfsPath(targetPath) {
|
|
510
543
|
const normalized = path.posix.normalize(targetPath);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chmod.d.ts","sourceRoot":"","sources":["../../src/commands/chmod.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,eAAO,MAAM,YAAY,EAAE,WA6B1B,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { assertPathAccess, resolvePath } from "./helpers";
|
|
2
|
+
export const chmodCommand = {
|
|
3
|
+
name: "chmod",
|
|
4
|
+
params: ["<mode> <file>"],
|
|
5
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
6
|
+
const [modeArg, fileArg] = args;
|
|
7
|
+
if (!modeArg || !fileArg) {
|
|
8
|
+
return { stderr: "chmod: missing operand", exitCode: 1 };
|
|
9
|
+
}
|
|
10
|
+
const filePath = resolvePath(cwd, fileArg);
|
|
11
|
+
try {
|
|
12
|
+
assertPathAccess(authUser, filePath, "chmod");
|
|
13
|
+
if (!shell.vfs.exists(filePath)) {
|
|
14
|
+
return {
|
|
15
|
+
stderr: `chmod: ${fileArg}: No such file or directory`,
|
|
16
|
+
exitCode: 1,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const mode = parseInt(modeArg, 8);
|
|
20
|
+
if (Number.isNaN(mode)) {
|
|
21
|
+
return { stderr: `chmod: invalid mode: ${modeArg}`, exitCode: 1 };
|
|
22
|
+
}
|
|
23
|
+
shell.vfs.chmod(filePath, mode);
|
|
24
|
+
return { exitCode: 0 };
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28
|
+
return { stderr: `chmod: ${msg}`, exitCode: 1 };
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cp.d.ts","sourceRoot":"","sources":["../../src/commands/cp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,eAAO,MAAM,SAAS,EAAE,WAuEvB,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ifFlag } from "./command-helpers";
|
|
2
|
+
import { assertPathAccess, resolvePath } from "./helpers";
|
|
3
|
+
export const cpCommand = {
|
|
4
|
+
name: "cp",
|
|
5
|
+
params: ["[-r] <source> <dest>"],
|
|
6
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
7
|
+
const recursive = ifFlag(args, ["-r", "-R", "--recursive"]);
|
|
8
|
+
const positionals = args.filter((a) => !a.startsWith("-"));
|
|
9
|
+
const [srcArg, destArg] = positionals;
|
|
10
|
+
if (!srcArg || !destArg) {
|
|
11
|
+
return { stderr: "cp: missing operand", exitCode: 1 };
|
|
12
|
+
}
|
|
13
|
+
const srcPath = resolvePath(cwd, srcArg);
|
|
14
|
+
const destPath = resolvePath(cwd, destArg);
|
|
15
|
+
try {
|
|
16
|
+
assertPathAccess(authUser, srcPath, "cp");
|
|
17
|
+
assertPathAccess(authUser, destPath, "cp");
|
|
18
|
+
if (!shell.vfs.exists(srcPath)) {
|
|
19
|
+
return {
|
|
20
|
+
stderr: `cp: ${srcArg}: No such file or directory`,
|
|
21
|
+
exitCode: 1,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const srcStat = shell.vfs.stat(srcPath);
|
|
25
|
+
if (srcStat.type === "directory") {
|
|
26
|
+
if (!recursive) {
|
|
27
|
+
return {
|
|
28
|
+
stderr: `cp: ${srcArg}: is a directory (use -r)`,
|
|
29
|
+
exitCode: 1,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const copyDir = (from, to) => {
|
|
33
|
+
shell.vfs.mkdir(to, 0o755);
|
|
34
|
+
for (const entry of shell.vfs.list(from)) {
|
|
35
|
+
const fromEntry = `${from}/${entry}`;
|
|
36
|
+
const toEntry = `${to}/${entry}`;
|
|
37
|
+
const stat = shell.vfs.stat(fromEntry);
|
|
38
|
+
if (stat.type === "directory") {
|
|
39
|
+
copyDir(fromEntry, toEntry);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const content = shell.vfs.readFileRaw(fromEntry);
|
|
43
|
+
shell.writeFileAsUser(authUser, toEntry, content);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const finalDest = shell.vfs.exists(destPath) &&
|
|
48
|
+
shell.vfs.stat(destPath).type === "directory"
|
|
49
|
+
? `${destPath}/${srcArg.split("/").pop()}`
|
|
50
|
+
: destPath;
|
|
51
|
+
copyDir(srcPath, finalDest);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const finalDest = shell.vfs.exists(destPath) &&
|
|
55
|
+
shell.vfs.stat(destPath).type === "directory"
|
|
56
|
+
? `${destPath}/${srcArg.split("/").pop()}`
|
|
57
|
+
: destPath;
|
|
58
|
+
const content = shell.vfs.readFileRaw(srcPath);
|
|
59
|
+
shell.writeFileAsUser(authUser, finalDest, content);
|
|
60
|
+
}
|
|
61
|
+
return { exitCode: 0 };
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
65
|
+
return { stderr: `cp: ${msg}`, exitCode: 1 };
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../src/commands/find.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,eAAO,MAAM,WAAW,EAAE,WAwDzB,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getFlag } from "./command-helpers";
|
|
2
|
+
import { assertPathAccess, resolvePath } from "./helpers";
|
|
3
|
+
export const findCommand = {
|
|
4
|
+
name: "find",
|
|
5
|
+
params: ["[path] [-name <pattern>] [-type f|d]"],
|
|
6
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
7
|
+
const namePattern = getFlag(args, ["-name"]);
|
|
8
|
+
const typeFilter = getFlag(args, ["-type"]);
|
|
9
|
+
const positionals = args.filter((a) => !a.startsWith("-") && a !== namePattern && a !== typeFilter);
|
|
10
|
+
const rootArg = positionals[0] ?? ".";
|
|
11
|
+
const rootPath = resolvePath(cwd, rootArg);
|
|
12
|
+
try {
|
|
13
|
+
assertPathAccess(authUser, rootPath, "find");
|
|
14
|
+
if (!shell.vfs.exists(rootPath)) {
|
|
15
|
+
return {
|
|
16
|
+
stderr: `find: ${rootArg}: No such file or directory`,
|
|
17
|
+
exitCode: 1,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
23
|
+
return { stderr: `find: ${msg}`, exitCode: 1 };
|
|
24
|
+
}
|
|
25
|
+
const nameRegex = namePattern
|
|
26
|
+
? new RegExp(`^${namePattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".")}$`)
|
|
27
|
+
: null;
|
|
28
|
+
const results = [];
|
|
29
|
+
const walk = (currentPath, display) => {
|
|
30
|
+
const stat = shell.vfs.stat(currentPath);
|
|
31
|
+
const matchesType = !typeFilter ||
|
|
32
|
+
(typeFilter === "f" && stat.type === "file") ||
|
|
33
|
+
(typeFilter === "d" && stat.type === "directory");
|
|
34
|
+
const matchesName = !nameRegex || nameRegex.test(currentPath.split("/").pop() ?? "");
|
|
35
|
+
if (matchesType && matchesName)
|
|
36
|
+
results.push(display);
|
|
37
|
+
if (stat.type === "directory") {
|
|
38
|
+
for (const entry of shell.vfs.list(currentPath)) {
|
|
39
|
+
const full = `${currentPath}/${entry}`;
|
|
40
|
+
const disp = `${display}/${entry}`;
|
|
41
|
+
walk(full, disp);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
walk(rootPath, rootArg);
|
|
46
|
+
return { stdout: results.join("\n"), exitCode: 0 };
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../src/commands/grep.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,eAAO,MAAM,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../src/commands/grep.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,eAAO,MAAM,WAAW,EAAE,WA2FzB,CAAC"}
|
package/dist/commands/grep.js
CHANGED
|
@@ -2,11 +2,15 @@ import { parseArgs } from "./command-helpers";
|
|
|
2
2
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
3
3
|
export const grepCommand = {
|
|
4
4
|
name: "grep",
|
|
5
|
-
params: ["[-i] [-v] <pattern> [file...]"],
|
|
5
|
+
params: ["[-i] [-v] [-n] [-r] <pattern> [file...]"],
|
|
6
6
|
run: ({ authUser, shell, cwd, args, stdin }) => {
|
|
7
|
-
const { flags, positionals } = parseArgs(args, {
|
|
7
|
+
const { flags, positionals } = parseArgs(args, {
|
|
8
|
+
flags: ["-i", "-v", "-n", "-r"],
|
|
9
|
+
});
|
|
8
10
|
const caseInsensitive = flags.has("-i");
|
|
9
11
|
const invertMatch = flags.has("-v");
|
|
12
|
+
const showLineNumbers = flags.has("-n");
|
|
13
|
+
const recursive = flags.has("-r");
|
|
10
14
|
const pattern = positionals[0];
|
|
11
15
|
const files = positionals.slice(1);
|
|
12
16
|
if (!pattern) {
|
|
@@ -14,51 +18,73 @@ export const grepCommand = {
|
|
|
14
18
|
}
|
|
15
19
|
let regex;
|
|
16
20
|
try {
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
// No "g" flag — avoids the stateful lastIndex problem with regex.test()
|
|
22
|
+
const regexFlags = caseInsensitive ? "mi" : "m";
|
|
23
|
+
regex = new RegExp(pattern, regexFlags);
|
|
19
24
|
}
|
|
20
25
|
catch {
|
|
21
26
|
return { stderr: `grep: invalid regex: ${pattern}`, exitCode: 1 };
|
|
22
27
|
}
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const lines = stdin.split("\n");
|
|
29
|
-
for (const line of lines) {
|
|
30
|
-
regex.lastIndex = 0;
|
|
28
|
+
const matchLines = (content, prefix = "") => {
|
|
29
|
+
const lines = content.split("\n");
|
|
30
|
+
const out = [];
|
|
31
|
+
for (let i = 0; i < lines.length; i++) {
|
|
32
|
+
const line = lines[i] ?? "";
|
|
31
33
|
const matches = regex.test(line);
|
|
32
34
|
const shouldInclude = invertMatch ? !matches : matches;
|
|
33
35
|
if (shouldInclude) {
|
|
34
|
-
|
|
36
|
+
const lineLabel = showLineNumbers ? `${i + 1}:` : "";
|
|
37
|
+
out.push(`${prefix}${lineLabel}${line}`);
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
return out;
|
|
41
|
+
};
|
|
42
|
+
const readPaths = (base) => {
|
|
43
|
+
if (!shell.vfs.exists(base))
|
|
44
|
+
return [];
|
|
45
|
+
const stat = shell.vfs.stat(base);
|
|
46
|
+
if (stat.type === "file")
|
|
47
|
+
return [base];
|
|
48
|
+
if (!recursive)
|
|
49
|
+
return [];
|
|
50
|
+
const paths = [];
|
|
51
|
+
const walk = (dir) => {
|
|
52
|
+
for (const entry of shell.vfs.list(dir)) {
|
|
53
|
+
const full = `${dir}/${entry}`;
|
|
54
|
+
const s = shell.vfs.stat(full);
|
|
55
|
+
if (s.type === "file")
|
|
56
|
+
paths.push(full);
|
|
57
|
+
else
|
|
58
|
+
walk(full);
|
|
59
|
+
}
|
|
40
60
|
};
|
|
61
|
+
walk(base);
|
|
62
|
+
return paths;
|
|
63
|
+
};
|
|
64
|
+
const results = [];
|
|
65
|
+
if (files.length === 0) {
|
|
66
|
+
if (!stdin)
|
|
67
|
+
return { stdout: "", exitCode: 1 };
|
|
68
|
+
results.push(...matchLines(stdin));
|
|
41
69
|
}
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
70
|
+
else {
|
|
71
|
+
const resolvedPaths = files.flatMap((f) => {
|
|
72
|
+
const target = resolvePath(cwd, f);
|
|
73
|
+
return readPaths(target).map((p) => ({ file: f, path: p }));
|
|
74
|
+
});
|
|
75
|
+
for (const { file, path: filePath } of resolvedPaths) {
|
|
76
|
+
try {
|
|
77
|
+
assertPathAccess(authUser, filePath, "grep");
|
|
78
|
+
const content = shell.vfs.readFile(filePath);
|
|
79
|
+
const prefix = resolvedPaths.length > 1 ? `${file}:` : "";
|
|
80
|
+
results.push(...matchLines(content, prefix));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return {
|
|
84
|
+
stderr: `grep: ${file}: No such file or directory`,
|
|
85
|
+
exitCode: 1,
|
|
86
|
+
};
|
|
55
87
|
}
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return {
|
|
59
|
-
stderr: `grep: ${file}: No such file or directory`,
|
|
60
|
-
exitCode: 1,
|
|
61
|
-
};
|
|
62
88
|
}
|
|
63
89
|
}
|
|
64
90
|
return {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../../src/commands/head.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,eAAO,MAAM,WAAW,EAAE,WA8BzB,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getFlag } from "./command-helpers";
|
|
2
|
+
import { assertPathAccess, resolvePath } from "./helpers";
|
|
3
|
+
export const headCommand = {
|
|
4
|
+
name: "head",
|
|
5
|
+
params: ["[-n <lines>] [file...]"],
|
|
6
|
+
run: ({ authUser, shell, cwd, args, stdin }) => {
|
|
7
|
+
const nArg = getFlag(args, ["-n"]);
|
|
8
|
+
const n = typeof nArg === "string" ? parseInt(nArg, 10) : 10;
|
|
9
|
+
const positionals = args.filter((a) => !a.startsWith("-") && a !== nArg);
|
|
10
|
+
const take = (content) => content.split("\n").slice(0, n).join("\n");
|
|
11
|
+
if (positionals.length === 0) {
|
|
12
|
+
return { stdout: take(stdin ?? ""), exitCode: 0 };
|
|
13
|
+
}
|
|
14
|
+
const results = [];
|
|
15
|
+
for (const file of positionals) {
|
|
16
|
+
const filePath = resolvePath(cwd, file);
|
|
17
|
+
try {
|
|
18
|
+
assertPathAccess(authUser, filePath, "head");
|
|
19
|
+
results.push(take(shell.vfs.readFile(filePath)));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return {
|
|
23
|
+
stderr: `head: ${file}: No such file or directory`,
|
|
24
|
+
exitCode: 1,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { stdout: results.join("\n"), exitCode: 0 };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EACX,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EACX,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,MAAM,mBAAmB,CAAC;AA0G3B,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAgBzD;AAED,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,EAChB,GAAG,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GAClE,WAAW,CAEb;AAED,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAG1C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAGnE;AAgDD,wBAAsB,UAAU,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,YAAY,EACnB,KAAK,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,aAAa,CAAC,CAqDxB"}
|