@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 CHANGED
@@ -1,11 +1,14 @@
1
1
  import * as freestyle_sandboxes from 'freestyle-sandboxes';
2
- import { VmWith, VmWithInstance, CreateVmOptions } from 'freestyle-sandboxes';
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
- configure(existingConfig: CreateVmOptions): CreateVmOptions | Promise<CreateVmOptions>;
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<any>;
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, VmTemplate, VmWithInstance } from 'freestyle-sandboxes';
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: config.command ?? "bash -l",
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
- configure(existingConfig) {
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
- curl -fsSL -o /usr/local/bin/ttyd "https://github.com/tsl0922/ttyd/releases/download/\${TTYD_VERSION}/ttyd.x86_64"
28
- chmod +x /usr/local/bin/ttyd
29
- /usr/local/bin/ttyd --version
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
- const config = {
67
- template: new VmTemplate({
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
- return this.compose(existingConfig, config);
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 devLogs = new VmTtyd({
7
+ const devPty = new VmPtySession({
8
+ sessionId: "npm-dev",
9
+ });
10
+
11
+ const devCommandTtyd = new VmTtyd({
7
12
  port: 3010,
8
- command: "tmux attach -t dev-server",
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: "tmux new -s dev-server npm run dev",
32
+ devCommand: "npm run dev",
33
+ devCommandPty: devPty,
51
34
  }),
52
35
  },
53
36
  });
54
37
 
55
- const { vm, vmId } = await freestyle.vms.create({
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("npx freestyle-sandboxes vm ssh " + vmId);
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.5",
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"
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
- VmTemplate,
3
- type CreateVmOptions,
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: config.command ?? "bash -l",
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
- override configure(
69
- existingConfig: CreateVmOptions,
70
- ): CreateVmOptions | Promise<CreateVmOptions> {
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
- curl -fsSL -o /usr/local/bin/ttyd "https://github.com/tsl0922/ttyd/releases/download/\${TTYD_VERSION}/ttyd.x86_64"
77
- chmod +x /usr/local/bin/ttyd
78
- /usr/local/bin/ttyd --version
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
- const config: CreateVmOptions = {
129
- template: new VmTemplate({
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
- return this.compose(existingConfig, config);
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<any>;
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<any>;
203
+ instance: VmWebTerminalInstance<TtydConfig[]>;
177
204
  }) {
178
205
  this.port = port;
179
206
  this.command = command;