-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrepl.ts
More file actions
108 lines (88 loc) · 2.72 KB
/
Copy pathrepl.ts
File metadata and controls
108 lines (88 loc) · 2.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import path from 'node:path';
import * as readline from 'node:readline';
import type { Sprite } from '@fly/sprites';
/**
* Options parameter for the @see tinySpritesRepl function.
*/
export interface ISpritesReplOptions {
/** The current working directory. */
cwd: string | undefined;
}
type CommandResult = {
exitCode: number;
stdout: string;
stderr: string;
};
class ShellState {
#cwd: string;
#sprite: Sprite;
private constructor(cwd: string, sprite: Sprite) {
this.#cwd = cwd;
this.#sprite = sprite;
}
static create(sprite: Sprite, cwd: string): ShellState {
return new ShellState(cwd, sprite);
}
/** Execute a shell command. Handles `cd` gracefully enough. */
async run(cmd: string): Promise<CommandResult | null> {
const trimmed: string = cmd.trim();
if (!trimmed) {
return null;
}
const splitCommand = trimmed.split(/\s+/);
if (splitCommand[0] === 'cd') {
const target = splitCommand[1] ?? this.#cwd;
this.#cwd = path.posix.isAbsolute(target) ? target : path.posix.join(this.#cwd, target);
return null;
}
// const { exitCode, stdout, stderr } = await this.#sprite.exec(`cd ${this.#cwd} && ${trimmed}`);
const { exitCode, stdout, stderr } = await this.#sprite.execFile('sh', ['-c', `cd ${this.#cwd} && ${trimmed}`]);
return {
exitCode: exitCode,
stdout: String(stdout),
stderr: String(stderr),
};
}
}
function question(rl: readline.Interface, prompt: string): Promise<string | null> {
return new Promise((resolve) => {
rl.once('close', () => resolve(null));
rl.question(prompt, (answer) => resolve(answer));
});
}
/**
* Spawns the REPL so that users can use it.
*
* This is a tiny REPL and is not a full shell, so it has an incredibly limited subset of regular shell commands.
*/
export default async function tinySpritesRepl(sprite: Sprite, options: ISpritesReplOptions | undefined): Promise<void> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const shell = ShellState.create(sprite, options?.cwd ?? '/');
console.log(`Connected to ${options?.cwd ?? '/'}. Type "exit" or Ctrl+C to quit.\n`);
while (true) {
const input = await question(rl, '$ ');
if (input === null) {
break;
}
if (input.trim() === 'exit') {
break;
}
const result = await shell.run(input);
if (result === null) {
continue;
}
const { exitCode, stdout, stderr } = result;
if (stdout) {
process.stdout.write(stdout);
}
if (stderr) {
process.stderr.write(stderr);
}
if (exitCode !== null && exitCode !== 0) {
console.error(`The process exited with a non-zero exit code: ${exitCode}`);
}
}
}