@tui-sandbox/library 1.2.0 → 2.0.0
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/CHANGELOG.md +19 -0
- package/dist/src/client/index.d.ts +1 -0
- package/dist/src/client/index.js +2 -0
- package/dist/src/client/neovim-client.d.ts +12 -0
- package/dist/src/client/neovim-client.js +58 -0
- package/dist/src/client/websocket-client.d.ts +0 -10
- package/dist/src/client/websocket-client.js +0 -55
- package/dist/src/server/TestServer.d.ts +8 -0
- package/dist/src/server/TestServer.js +49 -0
- package/dist/src/server/dirtree/index.js +1 -2
- package/dist/src/server/index.d.ts +3 -7
- package/dist/src/server/index.js +3 -48
- package/dist/src/server/neovim/environment/createTempDir.d.ts +1 -0
- package/dist/src/server/neovim/environment/createTempDir.test.d.ts +1 -0
- package/dist/src/server/neovim/environment/createTempDir.test.js +29 -0
- package/dist/src/server/server.d.ts +2 -2
- package/dist/src/server/server.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/src/client/index.ts +3 -0
- package/src/client/neovim-client.ts +73 -0
- package/src/client/style.css +1 -1
- package/src/client/websocket-client.ts +0 -69
- package/src/server/TestServer.ts +54 -0
- package/src/server/dirtree/index.ts +1 -2
- package/src/server/index.ts +4 -51
- package/src/server/neovim/environment/createTempDir.test.ts +34 -0
- package/src/server/neovim/environment/createTempDir.ts +4 -2
- package/src/server/server.ts +1 -1
- /package/src/{public → client/public}/DejaVuSansMNerdFontMono-Regular.ttf +0 -0
package/src/client/style.css
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { flavors } from "@catppuccin/palette"
|
|
2
|
-
import { createTRPCClient, createWSClient, wsLink } from "@trpc/client"
|
|
3
2
|
import { FitAddon } from "@xterm/addon-fit"
|
|
4
3
|
import { Terminal } from "@xterm/xterm"
|
|
5
4
|
import "@xterm/xterm/css/xterm.css"
|
|
6
5
|
import z from "zod"
|
|
7
|
-
import type { StartNeovimGenericArguments } from "../server/neovim/NeovimApplication.ts"
|
|
8
|
-
import type { AppRouter } from "../server/server.ts"
|
|
9
|
-
import type { TestDirectory } from "../server/types.ts"
|
|
10
6
|
import type { TabId } from "../server/utilities/tabId.ts"
|
|
11
7
|
import "./style.css"
|
|
12
8
|
import { validateMouseEvent } from "./validateMouseEvent"
|
|
@@ -90,68 +86,3 @@ export function getTabId(): TabId {
|
|
|
90
86
|
|
|
91
87
|
return { tabId }
|
|
92
88
|
}
|
|
93
|
-
|
|
94
|
-
export class NeovimClient {
|
|
95
|
-
private readonly ready: Promise<void>
|
|
96
|
-
private readonly tabId: { tabId: string }
|
|
97
|
-
private readonly terminal: Terminal
|
|
98
|
-
private readonly trpc: ReturnType<typeof createTRPCClient<AppRouter>>
|
|
99
|
-
|
|
100
|
-
constructor(app: HTMLElement) {
|
|
101
|
-
const wsClient = createWSClient({ url: `ws://localhost:3000`, WebSocket })
|
|
102
|
-
const trpc = createTRPCClient<AppRouter>({
|
|
103
|
-
links: [wsLink({ client: wsClient })],
|
|
104
|
-
})
|
|
105
|
-
this.trpc = trpc
|
|
106
|
-
|
|
107
|
-
this.tabId = getTabId()
|
|
108
|
-
const tabId = this.tabId
|
|
109
|
-
|
|
110
|
-
const terminal = startTerminal(app, {
|
|
111
|
-
onMouseEvent(data: string) {
|
|
112
|
-
void trpc.neovim.sendStdin.mutate({ tabId, data }).catch((error: unknown) => {
|
|
113
|
-
console.error(`Error sending mouse event`, error)
|
|
114
|
-
})
|
|
115
|
-
},
|
|
116
|
-
onKeyPress(event) {
|
|
117
|
-
void trpc.neovim.sendStdin.mutate({ tabId, data: event.key })
|
|
118
|
-
},
|
|
119
|
-
})
|
|
120
|
-
this.terminal = terminal
|
|
121
|
-
|
|
122
|
-
// start listening to Neovim stdout - this will take some (short) amount of
|
|
123
|
-
// time to complete
|
|
124
|
-
this.ready = new Promise<void>(resolve => {
|
|
125
|
-
console.log("Subscribing to Neovim stdout")
|
|
126
|
-
trpc.neovim.onStdout.subscribe(
|
|
127
|
-
{ client: tabId },
|
|
128
|
-
{
|
|
129
|
-
onStarted() {
|
|
130
|
-
resolve()
|
|
131
|
-
},
|
|
132
|
-
onData(data: string) {
|
|
133
|
-
terminal.write(data)
|
|
134
|
-
},
|
|
135
|
-
onError(err: unknown) {
|
|
136
|
-
console.error(`Error from Neovim`, err)
|
|
137
|
-
},
|
|
138
|
-
}
|
|
139
|
-
)
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
public async startNeovim(args: StartNeovimGenericArguments): Promise<TestDirectory> {
|
|
144
|
-
await this.ready
|
|
145
|
-
|
|
146
|
-
const neovim = await this.trpc.neovim.start.mutate({
|
|
147
|
-
startNeovimArguments: args,
|
|
148
|
-
tabId: this.tabId,
|
|
149
|
-
terminalDimensions: {
|
|
150
|
-
cols: this.terminal.cols,
|
|
151
|
-
rows: this.terminal.rows,
|
|
152
|
-
},
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
return neovim
|
|
156
|
-
}
|
|
157
|
-
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AnyTRPCRouter } from "@trpc/server"
|
|
2
|
+
import { applyWSSHandler } from "@trpc/server/adapters/ws"
|
|
3
|
+
import "core-js/proposals/async-explicit-resource-management"
|
|
4
|
+
import { once } from "events"
|
|
5
|
+
import { WebSocketServer } from "ws"
|
|
6
|
+
import { createContext } from "./connection/trpc"
|
|
7
|
+
import type { TestServerConfig } from "./updateTestdirectorySchemaFile"
|
|
8
|
+
import { updateTestdirectorySchemaFile } from "./updateTestdirectorySchemaFile"
|
|
9
|
+
|
|
10
|
+
export class TestServer {
|
|
11
|
+
public constructor(private readonly port: number) {}
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
|
|
14
|
+
public async startAndRun<TRouter extends AnyTRPCRouter>(appRouter: TRouter, config: TestServerConfig): Promise<void> {
|
|
15
|
+
console.log("🚀 Server starting")
|
|
16
|
+
|
|
17
|
+
await updateTestdirectorySchemaFile(config)
|
|
18
|
+
|
|
19
|
+
const wss = new WebSocketServer({ port: this.port })
|
|
20
|
+
const handler = applyWSSHandler<TRouter>({
|
|
21
|
+
wss,
|
|
22
|
+
router: appRouter,
|
|
23
|
+
createContext,
|
|
24
|
+
// Enable heartbeat messages to keep connection open (disabled by default)
|
|
25
|
+
keepAlive: {
|
|
26
|
+
enabled: true,
|
|
27
|
+
// server ping message interval in milliseconds
|
|
28
|
+
pingMs: 30_000,
|
|
29
|
+
// connection is terminated if pong message is not received in this many milliseconds
|
|
30
|
+
pongWaitMs: 5000,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
wss.on("connection", socket => {
|
|
35
|
+
console.log(`➕➕ Connection (${wss.clients.size})`)
|
|
36
|
+
socket.once("close", () => {
|
|
37
|
+
console.log(`➖➖ Connection (${wss.clients.size})`)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
console.log(`✅ WebSocket Server listening on ws://localhost:${this.port}`)
|
|
41
|
+
|
|
42
|
+
await Promise.race([once(process, "SIGTERM"), once(process, "SIGINT")])
|
|
43
|
+
console.log("Shutting down...")
|
|
44
|
+
handler.broadcastReconnectNotification()
|
|
45
|
+
wss.close(error => {
|
|
46
|
+
if (error) {
|
|
47
|
+
console.error("Error closing WebSocket server", error)
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
console.log("WebSocket server closed")
|
|
51
|
+
process.exit(0)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -54,13 +54,12 @@ export function convertDree(root: Dree): TreeNode {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
assert(root.children, `Expected children for directory node ${root.name}`)
|
|
58
57
|
const node: DirectoryNode = {
|
|
59
58
|
name: root.name,
|
|
60
59
|
type: root.type,
|
|
61
60
|
contents: {},
|
|
62
61
|
}
|
|
63
|
-
for (const child of root.children) {
|
|
62
|
+
for (const child of root.children || []) {
|
|
64
63
|
node.contents[child.name] = convertDree(child)
|
|
65
64
|
}
|
|
66
65
|
|
package/src/server/index.ts
CHANGED
|
@@ -1,54 +1,7 @@
|
|
|
1
|
-
import type { AnyRouter } from "@trpc/server"
|
|
2
|
-
import { applyWSSHandler } from "@trpc/server/adapters/ws"
|
|
3
1
|
import "core-js/proposals/async-explicit-resource-management"
|
|
4
|
-
import { once } from "events"
|
|
5
|
-
import { WebSocketServer } from "ws"
|
|
6
|
-
import { createContext } from "./connection/trpc"
|
|
7
|
-
import type { TestServerConfig } from "./updateTestdirectorySchemaFile"
|
|
8
|
-
import { updateTestdirectorySchemaFile } from "./updateTestdirectorySchemaFile"
|
|
9
2
|
|
|
10
|
-
|
|
11
|
-
public constructor(private readonly port: number) {}
|
|
3
|
+
// This is the public server api. Semantic versioning will be applied to this.
|
|
12
4
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
await updateTestdirectorySchemaFile(config)
|
|
18
|
-
|
|
19
|
-
const wss = new WebSocketServer({ port: this.port })
|
|
20
|
-
const handler = applyWSSHandler<TRouter>({
|
|
21
|
-
wss,
|
|
22
|
-
router: appRouter,
|
|
23
|
-
createContext,
|
|
24
|
-
// Enable heartbeat messages to keep connection open (disabled by default)
|
|
25
|
-
keepAlive: {
|
|
26
|
-
enabled: true,
|
|
27
|
-
// server ping message interval in milliseconds
|
|
28
|
-
pingMs: 30_000,
|
|
29
|
-
// connection is terminated if pong message is not received in this many milliseconds
|
|
30
|
-
pongWaitMs: 5000,
|
|
31
|
-
},
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
wss.on("connection", socket => {
|
|
35
|
-
console.log(`➕➕ Connection (${wss.clients.size})`)
|
|
36
|
-
socket.once("close", () => {
|
|
37
|
-
console.log(`➖➖ Connection (${wss.clients.size})`)
|
|
38
|
-
})
|
|
39
|
-
})
|
|
40
|
-
console.log(`✅ WebSocket Server listening on ws://localhost:${this.port}`)
|
|
41
|
-
|
|
42
|
-
await Promise.race([once(process, "SIGTERM"), once(process, "SIGINT")])
|
|
43
|
-
console.log("Shutting down...")
|
|
44
|
-
handler.broadcastReconnectNotification()
|
|
45
|
-
wss.close(error => {
|
|
46
|
-
if (error) {
|
|
47
|
-
console.error("Error closing WebSocket server", error)
|
|
48
|
-
process.exit(1)
|
|
49
|
-
}
|
|
50
|
-
console.log("WebSocket server closed")
|
|
51
|
-
process.exit(0)
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
}
|
|
5
|
+
export { startTestServer } from "./server"
|
|
6
|
+
export { updateTestdirectorySchemaFile } from "./updateTestdirectorySchemaFile"
|
|
7
|
+
export type { TestServerConfig } from "./updateTestdirectorySchemaFile"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs, { rmdirSync } from "fs"
|
|
2
|
+
import nodePath from "path"
|
|
3
|
+
import { expect, it } from "vitest"
|
|
4
|
+
import type { TestDirsPath } from "./createTempDir"
|
|
5
|
+
import { createTempDir } from "./createTempDir"
|
|
6
|
+
|
|
7
|
+
type TestTempDirPrefix = "test-temp-dir-"
|
|
8
|
+
|
|
9
|
+
class TempDirectory implements Disposable {
|
|
10
|
+
constructor(public readonly path: string) {}
|
|
11
|
+
|
|
12
|
+
public static create(): TempDirectory {
|
|
13
|
+
const tmp = fs.mkdtempSync("test-temp-dir-" satisfies TestTempDirPrefix)
|
|
14
|
+
return new TempDirectory(tmp)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
[Symbol.dispose](): void {
|
|
18
|
+
rmdirSync(this.path, { recursive: true })
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
it("should create a temp dir with no contents", async () => {
|
|
23
|
+
// typically the user will want to have contents, but this should not be an error
|
|
24
|
+
await using dir = TempDirectory.create()
|
|
25
|
+
const result = await createTempDir({
|
|
26
|
+
testEnvironmentPath: dir.path,
|
|
27
|
+
outputFilePath: nodePath.join(dir.path, "MyTestDirectory.ts"),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
expect(result.contents).toEqual({})
|
|
31
|
+
expect(result.testEnvironmentPath).toEqual(dir.path)
|
|
32
|
+
expect(result.testEnvironmentPath.startsWith("test-temp-dir-" satisfies TestTempDirPrefix)).toBeTruthy()
|
|
33
|
+
expect(result.testEnvironmentPathRelative.startsWith("testdirs" satisfies TestDirsPath)).toBeTruthy()
|
|
34
|
+
})
|
|
@@ -14,7 +14,7 @@ export async function createTempDir(config: TestServerConfig): Promise<TestDirec
|
|
|
14
14
|
const dir = await createUniqueDirectory(config.testEnvironmentPath)
|
|
15
15
|
|
|
16
16
|
readdirSync(config.testEnvironmentPath).forEach(entry => {
|
|
17
|
-
if (entry === "testdirs") return
|
|
17
|
+
if (entry === ("testdirs" satisfies TestDirsPath)) return
|
|
18
18
|
if (entry === ".repro") return
|
|
19
19
|
|
|
20
20
|
execSync(`cp -a '${path.join(config.testEnvironmentPath, entry)}' ${dir}/`)
|
|
@@ -37,8 +37,10 @@ export async function createTempDir(config: TestServerConfig): Promise<TestDirec
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export type TestDirsPath = "testdirs"
|
|
41
|
+
|
|
40
42
|
async function createUniqueDirectory(testEnvironmentPath: string): Promise<string> {
|
|
41
|
-
const testdirs = path.join(testEnvironmentPath, "testdirs")
|
|
43
|
+
const testdirs = path.join(testEnvironmentPath, "testdirs" satisfies TestDirsPath)
|
|
42
44
|
try {
|
|
43
45
|
await access(testdirs, constants.F_OK)
|
|
44
46
|
} catch {
|
package/src/server/server.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { inferRouterInputs } from "@trpc/server"
|
|
2
2
|
import { z } from "zod"
|
|
3
|
-
import { TestServer } from "."
|
|
4
3
|
import { trpc } from "./connection/trpc"
|
|
5
4
|
import * as neovim from "./neovim"
|
|
5
|
+
import { TestServer } from "./TestServer"
|
|
6
6
|
import type { TestServerConfig } from "./updateTestdirectorySchemaFile"
|
|
7
7
|
import { tabIdSchema } from "./utilities/tabId"
|
|
8
8
|
|
|
File without changes
|