@freestyle-sh/with-ttyd 0.0.5 → 0.0.6
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/dist/index.d.ts +9 -3
- package/dist/index.js +35 -13
- package/examples/basic.ts +19 -30
- package/package.json +3 -2
- package/src/index.ts +50 -23
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import * as freestyle_sandboxes from 'freestyle-sandboxes';
|
|
2
|
-
import { VmWith, VmWithInstance,
|
|
2
|
+
import { VmWith, VmWithInstance, VmSpec } from 'freestyle-sandboxes';
|
|
3
|
+
import { VmPtySessionLike } from '@freestyle-sh/with-pty';
|
|
3
4
|
|
|
4
5
|
type TtydConfig = {
|
|
5
6
|
/** Port to run ttyd on (default: auto-assigned starting at 7682) */
|
|
6
7
|
port?: number;
|
|
7
8
|
/** Shell or command to run (default: /bin/bash) */
|
|
8
9
|
command?: string;
|
|
10
|
+
/** Attach ttyd to a PTY session */
|
|
11
|
+
pty?: VmPtySessionLike;
|
|
9
12
|
/** User to run terminal as (default: current user) */
|
|
10
13
|
user?: string;
|
|
11
14
|
/** Working directory (default: user home) */
|
|
@@ -34,8 +37,11 @@ type ResolvedTerminalConfig = {
|
|
|
34
37
|
};
|
|
35
38
|
declare class VmWebTerminal<T extends TtydConfig[] = TtydConfig[]> extends VmWith<VmWebTerminalInstance<T>> {
|
|
36
39
|
private resolvedTerminals;
|
|
40
|
+
private ptySessions;
|
|
37
41
|
constructor(terminals: T | TtydConfig);
|
|
38
|
-
|
|
42
|
+
private composeTtydSpec;
|
|
43
|
+
configureSnapshotSpec(spec: VmSpec): VmSpec;
|
|
44
|
+
configureSpec(spec: VmSpec): VmSpec;
|
|
39
45
|
createInstance(): VmWebTerminalInstance<T>;
|
|
40
46
|
installServiceName(): string;
|
|
41
47
|
}
|
|
@@ -46,7 +52,7 @@ declare class WebTerminal {
|
|
|
46
52
|
constructor({ port, command, instance, }: {
|
|
47
53
|
port: number;
|
|
48
54
|
command: string;
|
|
49
|
-
instance: VmWebTerminalInstance<
|
|
55
|
+
instance: VmWebTerminalInstance<TtydConfig[]>;
|
|
50
56
|
});
|
|
51
57
|
/** Expose this terminal publicly via Freestyle routing */
|
|
52
58
|
route({ domain }: {
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
import { VmWith,
|
|
1
|
+
import { VmWith, VmSpec, VmWithInstance } from 'freestyle-sandboxes';
|
|
2
2
|
|
|
3
3
|
class VmWebTerminal extends VmWith {
|
|
4
4
|
resolvedTerminals;
|
|
5
|
+
ptySessions;
|
|
5
6
|
constructor(terminals) {
|
|
6
7
|
super();
|
|
7
8
|
const terminalList = Array.isArray(terminals) ? terminals : [terminals];
|
|
9
|
+
this.ptySessions = terminalList.map((config) => config.pty).filter((value) => value !== void 0);
|
|
8
10
|
let nextPort = 7682;
|
|
9
11
|
this.resolvedTerminals = terminalList.map((config) => {
|
|
12
|
+
if (config.pty && config.command) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"VmWebTerminal config cannot include both pty and command. Use one or the other."
|
|
15
|
+
);
|
|
16
|
+
}
|
|
10
17
|
const port = config.port ?? nextPort++;
|
|
18
|
+
const resolvedCommand = config.pty ? config.pty.attachCommand(config.readOnly ?? false) : config.command ?? "bash -l";
|
|
11
19
|
return {
|
|
12
20
|
port,
|
|
13
|
-
command:
|
|
21
|
+
command: resolvedCommand,
|
|
14
22
|
user: config.user ?? "root",
|
|
15
23
|
cwd: config.cwd ?? "/root",
|
|
16
24
|
credential: config.credential,
|
|
@@ -19,15 +27,23 @@ class VmWebTerminal extends VmWith {
|
|
|
19
27
|
};
|
|
20
28
|
});
|
|
21
29
|
}
|
|
22
|
-
|
|
30
|
+
composeTtydSpec(spec) {
|
|
31
|
+
const uniquePtySessions = Array.from(new Set(this.ptySessions));
|
|
32
|
+
let composed = spec;
|
|
33
|
+
for (const pty of uniquePtySessions) {
|
|
34
|
+
composed = pty.applyToSpec(composed);
|
|
35
|
+
}
|
|
23
36
|
const installScript = `#!/bin/bash
|
|
24
|
-
set -e
|
|
37
|
+
set -e
|
|
25
38
|
|
|
26
|
-
TTYD_VERSION="1.7.7"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/
|
|
30
|
-
|
|
39
|
+
TTYD_VERSION="1.7.7"
|
|
40
|
+
mkdir -p /usr/local/bin
|
|
41
|
+
tmpfile="$(mktemp)"
|
|
42
|
+
curl -fsSL -o "$tmpfile" "https://github.com/tsl0922/ttyd/releases/download/\${TTYD_VERSION}/ttyd.x86_64"
|
|
43
|
+
mv "$tmpfile" /usr/local/bin/ttyd
|
|
44
|
+
chmod +x /usr/local/bin/ttyd
|
|
45
|
+
/usr/local/bin/ttyd --version
|
|
46
|
+
`;
|
|
31
47
|
const services = this.resolvedTerminals.map((t) => {
|
|
32
48
|
const args = [`/usr/local/bin/ttyd`, `-p ${t.port}`];
|
|
33
49
|
if (t.credential) {
|
|
@@ -63,8 +79,9 @@ chmod +x /usr/local/bin/ttyd
|
|
|
63
79
|
requires: ["systemd-sysusers.service"]
|
|
64
80
|
};
|
|
65
81
|
});
|
|
66
|
-
|
|
67
|
-
|
|
82
|
+
return this.composeSpecs(
|
|
83
|
+
composed,
|
|
84
|
+
new VmSpec({
|
|
68
85
|
additionalFiles: {
|
|
69
86
|
"/opt/install-ttyd.sh": { content: installScript }
|
|
70
87
|
},
|
|
@@ -81,8 +98,13 @@ chmod +x /usr/local/bin/ttyd
|
|
|
81
98
|
]
|
|
82
99
|
}
|
|
83
100
|
})
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
configureSnapshotSpec(spec) {
|
|
104
|
+
return this.composeTtydSpec(spec);
|
|
105
|
+
}
|
|
106
|
+
configureSpec(spec) {
|
|
107
|
+
return this.composeTtydSpec(spec);
|
|
86
108
|
}
|
|
87
109
|
createInstance() {
|
|
88
110
|
return new VmWebTerminalInstance(this, this.resolvedTerminals);
|
package/examples/basic.ts
CHANGED
|
@@ -2,10 +2,15 @@ import "dotenv/config";
|
|
|
2
2
|
import { freestyle, VmSpec } from "freestyle-sandboxes";
|
|
3
3
|
import { VmWebTerminal as VmTtyd } from "../src/index.ts";
|
|
4
4
|
import { VmDevServer } from "../../with-dev-server/src/index.ts";
|
|
5
|
+
import { VmPtySession } from "../../with-pty/src/index.ts";
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const devPty = new VmPtySession({
|
|
8
|
+
sessionId: "npm-dev",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const devCommandTtyd = new VmTtyd({
|
|
7
12
|
port: 3010,
|
|
8
|
-
|
|
13
|
+
pty: devPty,
|
|
9
14
|
readOnly: true,
|
|
10
15
|
cwd: "/root/repo",
|
|
11
16
|
});
|
|
@@ -17,44 +22,26 @@ const otherTerminals = new VmTtyd({
|
|
|
17
22
|
|
|
18
23
|
const domain = `${crypto.randomUUID()}.style.dev`;
|
|
19
24
|
|
|
20
|
-
const innerDeps = new VmSpec({
|
|
21
|
-
discriminator: "inner-deps",
|
|
22
|
-
with: {
|
|
23
|
-
devLogs: devLogs,
|
|
24
|
-
otherTerminals: otherTerminals,
|
|
25
|
-
},
|
|
26
|
-
aptDeps: ["tmux"],
|
|
27
|
-
// systemd: {
|
|
28
|
-
// services: [
|
|
29
|
-
// {
|
|
30
|
-
// name: "install-tmux",
|
|
31
|
-
// mode: "oneshot",
|
|
32
|
-
// bash: "apt-get update && apt-get install -y tmux",
|
|
33
|
-
// timeoutSec: 120,
|
|
34
|
-
// },
|
|
35
|
-
// ],
|
|
36
|
-
// },
|
|
37
|
-
additionalFiles: {
|
|
38
|
-
"/root/.tmux.conf": {
|
|
39
|
-
content: `set -g mouse on`,
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
25
|
const snapshot = new VmSpec({
|
|
45
|
-
snapshot: innerDeps,
|
|
46
26
|
with: {
|
|
27
|
+
devCommandTtyd: devCommandTtyd,
|
|
28
|
+
otherTerminals: otherTerminals,
|
|
47
29
|
devServer: new VmDevServer({
|
|
48
30
|
templateRepo: "https://github.com/freestyle-sh/freestyle-next",
|
|
49
31
|
workdir: "/root/repo",
|
|
50
|
-
devCommand: "
|
|
32
|
+
devCommand: "npm run dev",
|
|
33
|
+
devCommandPty: devPty,
|
|
51
34
|
}),
|
|
52
35
|
},
|
|
53
36
|
});
|
|
54
37
|
|
|
55
|
-
const {
|
|
38
|
+
const { vmId, vm } = await freestyle.vms.create({
|
|
56
39
|
snapshot: snapshot,
|
|
57
40
|
domains: [
|
|
41
|
+
{
|
|
42
|
+
domain: domain,
|
|
43
|
+
vmPort: 3000,
|
|
44
|
+
},
|
|
58
45
|
{
|
|
59
46
|
domain: "dev-logs-" + domain,
|
|
60
47
|
vmPort: 3010,
|
|
@@ -66,7 +53,9 @@ const { vm, vmId } = await freestyle.vms.create({
|
|
|
66
53
|
],
|
|
67
54
|
});
|
|
68
55
|
|
|
69
|
-
console.log
|
|
56
|
+
await vm.devServer.getLogs().then(console.log);
|
|
70
57
|
|
|
58
|
+
console.log("npx freestyle-sandboxes vm ssh " + vmId);
|
|
59
|
+
console.log(`Dev server available at: https://${domain}`);
|
|
71
60
|
console.log(`Terminal available at: https://dev-logs-${domain}`);
|
|
72
61
|
console.log(`Other terminals available at: https://other-terminals-${domain}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freestyle-sh/with-ttyd",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Web terminal for freestyle sandboxes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"typescript": "^5.8.3"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"freestyle-sandboxes": "^0.1.
|
|
27
|
+
"freestyle-sandboxes": "^0.1.28",
|
|
28
|
+
"@freestyle-sh/with-pty": "^0.0.2"
|
|
28
29
|
},
|
|
29
30
|
"scripts": {
|
|
30
31
|
"build": "pkgroll"
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
VmWith,
|
|
5
|
-
VmWithInstance,
|
|
6
|
-
Freestyle,
|
|
7
|
-
} from "freestyle-sandboxes";
|
|
1
|
+
import { VmSpec, VmWith, VmWithInstance } from "freestyle-sandboxes";
|
|
2
|
+
import type { VmPtySessionLike } from "@freestyle-sh/with-pty";
|
|
3
|
+
import type { Freestyle } from "freestyle-sandboxes";
|
|
8
4
|
|
|
9
5
|
// ============================================================================
|
|
10
6
|
// Configuration Types
|
|
@@ -15,6 +11,8 @@ export type TtydConfig = {
|
|
|
15
11
|
port?: number;
|
|
16
12
|
/** Shell or command to run (default: /bin/bash) */
|
|
17
13
|
command?: string;
|
|
14
|
+
/** Attach ttyd to a PTY session */
|
|
15
|
+
pty?: VmPtySessionLike;
|
|
18
16
|
/** User to run terminal as (default: current user) */
|
|
19
17
|
user?: string;
|
|
20
18
|
/** Working directory (default: user home) */
|
|
@@ -45,17 +43,31 @@ export class VmWebTerminal<
|
|
|
45
43
|
T extends TtydConfig[] = TtydConfig[],
|
|
46
44
|
> extends VmWith<VmWebTerminalInstance<T>> {
|
|
47
45
|
private resolvedTerminals: ResolvedTerminalConfig[];
|
|
46
|
+
private ptySessions: VmPtySessionLike[];
|
|
48
47
|
|
|
49
48
|
constructor(terminals: T | TtydConfig) {
|
|
50
49
|
super();
|
|
51
50
|
const terminalList = Array.isArray(terminals) ? terminals : [terminals];
|
|
51
|
+
this.ptySessions = terminalList
|
|
52
|
+
.map((config) => config.pty)
|
|
53
|
+
.filter((value): value is VmPtySessionLike => value !== undefined);
|
|
52
54
|
// Resolve config once with defaults
|
|
53
55
|
let nextPort = 7682;
|
|
54
56
|
this.resolvedTerminals = terminalList.map((config) => {
|
|
57
|
+
if (config.pty && config.command) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
"VmWebTerminal config cannot include both pty and command. Use one or the other.",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
55
63
|
const port = config.port ?? nextPort++;
|
|
64
|
+
const resolvedCommand = config.pty
|
|
65
|
+
? config.pty.attachCommand(config.readOnly ?? false)
|
|
66
|
+
: (config.command ?? "bash -l");
|
|
67
|
+
|
|
56
68
|
return {
|
|
57
69
|
port,
|
|
58
|
-
command:
|
|
70
|
+
command: resolvedCommand,
|
|
59
71
|
user: config.user ?? "root",
|
|
60
72
|
cwd: config.cwd ?? "/root",
|
|
61
73
|
credential: config.credential,
|
|
@@ -65,18 +77,26 @@ export class VmWebTerminal<
|
|
|
65
77
|
});
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
private composeTtydSpec(spec: VmSpec): VmSpec {
|
|
81
|
+
const uniquePtySessions = Array.from(new Set(this.ptySessions));
|
|
82
|
+
let composed = spec;
|
|
83
|
+
|
|
84
|
+
for (const pty of uniquePtySessions) {
|
|
85
|
+
composed = pty.applyToSpec(composed) as VmSpec;
|
|
86
|
+
}
|
|
87
|
+
|
|
71
88
|
// Generate install script
|
|
72
89
|
const installScript = `#!/bin/bash
|
|
73
|
-
set -e
|
|
90
|
+
set -e
|
|
74
91
|
|
|
75
|
-
TTYD_VERSION="1.7.7"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
/
|
|
79
|
-
|
|
92
|
+
TTYD_VERSION="1.7.7"
|
|
93
|
+
mkdir -p /usr/local/bin
|
|
94
|
+
tmpfile="$(mktemp)"
|
|
95
|
+
curl -fsSL -o "$tmpfile" "https://github.com/tsl0922/ttyd/releases/download/\${TTYD_VERSION}/ttyd.x86_64"
|
|
96
|
+
mv "$tmpfile" /usr/local/bin/ttyd
|
|
97
|
+
chmod +x /usr/local/bin/ttyd
|
|
98
|
+
/usr/local/bin/ttyd --version
|
|
99
|
+
`;
|
|
80
100
|
|
|
81
101
|
// Generate systemd service for each terminal
|
|
82
102
|
const services = this.resolvedTerminals.map((t) => {
|
|
@@ -125,8 +145,9 @@ chmod +x /usr/local/bin/ttyd
|
|
|
125
145
|
};
|
|
126
146
|
});
|
|
127
147
|
|
|
128
|
-
|
|
129
|
-
|
|
148
|
+
return this.composeSpecs(
|
|
149
|
+
composed,
|
|
150
|
+
new VmSpec({
|
|
130
151
|
additionalFiles: {
|
|
131
152
|
"/opt/install-ttyd.sh": { content: installScript },
|
|
132
153
|
},
|
|
@@ -143,9 +164,15 @@ chmod +x /usr/local/bin/ttyd
|
|
|
143
164
|
],
|
|
144
165
|
},
|
|
145
166
|
}),
|
|
146
|
-
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
override configureSnapshotSpec(spec: VmSpec): VmSpec {
|
|
171
|
+
return this.composeTtydSpec(spec);
|
|
172
|
+
}
|
|
147
173
|
|
|
148
|
-
|
|
174
|
+
override configureSpec(spec: VmSpec): VmSpec {
|
|
175
|
+
return this.composeTtydSpec(spec);
|
|
149
176
|
}
|
|
150
177
|
|
|
151
178
|
createInstance(): VmWebTerminalInstance<T> {
|
|
@@ -164,7 +191,7 @@ chmod +x /usr/local/bin/ttyd
|
|
|
164
191
|
export class WebTerminal {
|
|
165
192
|
readonly port: number;
|
|
166
193
|
readonly command: string;
|
|
167
|
-
private instance: VmWebTerminalInstance<
|
|
194
|
+
private instance: VmWebTerminalInstance<TtydConfig[]>;
|
|
168
195
|
|
|
169
196
|
constructor({
|
|
170
197
|
port,
|
|
@@ -173,7 +200,7 @@ export class WebTerminal {
|
|
|
173
200
|
}: {
|
|
174
201
|
port: number;
|
|
175
202
|
command: string;
|
|
176
|
-
instance: VmWebTerminalInstance<
|
|
203
|
+
instance: VmWebTerminalInstance<TtydConfig[]>;
|
|
177
204
|
}) {
|
|
178
205
|
this.port = port;
|
|
179
206
|
this.command = command;
|