@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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.0](https://github.com/mikavilpas/tui-sandbox/compare/library-v1.3.0...library-v2.0.0) (2024-09-24)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * add public client and server APIs ([#46](https://github.com/mikavilpas/tui-sandbox/issues/46))
9
+
10
+ ### Features
11
+
12
+ * add public client and server APIs ([#46](https://github.com/mikavilpas/tui-sandbox/issues/46)) ([3f13f53](https://github.com/mikavilpas/tui-sandbox/commit/3f13f5386f31de9bb5bf6fc099e2e404261d31b0))
13
+
14
+ ## [1.3.0](https://github.com/mikavilpas/tui-sandbox/compare/library-v1.2.0...library-v1.3.0) (2024-09-23)
15
+
16
+
17
+ ### Features
18
+
19
+ * add license (MIT) to library package ([#42](https://github.com/mikavilpas/tui-sandbox/issues/42)) ([8c72eb1](https://github.com/mikavilpas/tui-sandbox/commit/8c72eb13e17c5e6838220a5a16da0d44a6aba792))
20
+ * allow test dirs that have no contents ([#40](https://github.com/mikavilpas/tui-sandbox/issues/40)) ([863e9f0](https://github.com/mikavilpas/tui-sandbox/commit/863e9f0731b1565bf5f9afdb6834275598196f22))
21
+
3
22
  ## [1.2.0](https://github.com/mikavilpas/tui-sandbox/compare/library-v1.1.0...library-v1.2.0) (2024-09-22)
4
23
 
5
24
 
@@ -0,0 +1 @@
1
+ export { NeovimClient } from "./neovim-client";
@@ -0,0 +1,2 @@
1
+ // This is the public client api. Semantic versioning will be applied to this.
2
+ export { NeovimClient } from "./neovim-client";
@@ -0,0 +1,12 @@
1
+ import "@xterm/xterm/css/xterm.css";
2
+ import type { StartNeovimGenericArguments } from "../server/neovim/NeovimApplication.ts";
3
+ import type { TestDirectory } from "../server/types.ts";
4
+ import "./style.css";
5
+ export declare class NeovimClient {
6
+ private readonly ready;
7
+ private readonly tabId;
8
+ private readonly terminal;
9
+ private readonly trpc;
10
+ constructor(app: HTMLElement);
11
+ startNeovim(args: StartNeovimGenericArguments): Promise<TestDirectory>;
12
+ }
@@ -0,0 +1,58 @@
1
+ import { createTRPCClient, createWSClient, wsLink } from "@trpc/client";
2
+ import "@xterm/xterm/css/xterm.css";
3
+ import "./style.css";
4
+ import { getTabId, startTerminal } from "./websocket-client.js";
5
+ export class NeovimClient {
6
+ ready;
7
+ tabId;
8
+ terminal;
9
+ trpc;
10
+ constructor(app) {
11
+ const wsClient = createWSClient({ url: `ws://localhost:3000`, WebSocket });
12
+ const trpc = createTRPCClient({
13
+ links: [wsLink({ client: wsClient })],
14
+ });
15
+ this.trpc = trpc;
16
+ this.tabId = getTabId();
17
+ const tabId = this.tabId;
18
+ const terminal = startTerminal(app, {
19
+ onMouseEvent(data) {
20
+ void trpc.neovim.sendStdin.mutate({ tabId, data }).catch((error) => {
21
+ console.error(`Error sending mouse event`, error);
22
+ });
23
+ },
24
+ onKeyPress(event) {
25
+ void trpc.neovim.sendStdin.mutate({ tabId, data: event.key });
26
+ },
27
+ });
28
+ this.terminal = terminal;
29
+ // start listening to Neovim stdout - this will take some (short) amount of
30
+ // time to complete
31
+ this.ready = new Promise(resolve => {
32
+ console.log("Subscribing to Neovim stdout");
33
+ trpc.neovim.onStdout.subscribe({ client: tabId }, {
34
+ onStarted() {
35
+ resolve();
36
+ },
37
+ onData(data) {
38
+ terminal.write(data);
39
+ },
40
+ onError(err) {
41
+ console.error(`Error from Neovim`, err);
42
+ },
43
+ });
44
+ });
45
+ }
46
+ async startNeovim(args) {
47
+ await this.ready;
48
+ const neovim = await this.trpc.neovim.start.mutate({
49
+ startNeovimArguments: args,
50
+ tabId: this.tabId,
51
+ terminalDimensions: {
52
+ cols: this.terminal.cols,
53
+ rows: this.terminal.rows,
54
+ },
55
+ });
56
+ return neovim;
57
+ }
58
+ }
@@ -1,7 +1,5 @@
1
1
  import { Terminal } from "@xterm/xterm";
2
2
  import "@xterm/xterm/css/xterm.css";
3
- import type { StartNeovimGenericArguments } from "../server/neovim/NeovimApplication.ts";
4
- import type { TestDirectory } from "../server/types.ts";
5
3
  import type { TabId } from "../server/utilities/tabId.ts";
6
4
  import "./style.css";
7
5
  export type StartTerminalOptions = {
@@ -15,11 +13,3 @@ export declare function startTerminal(app: HTMLElement, options: StartTerminalOp
15
13
  /** An identifier unique to a browser tab, so that each tab can have its own
16
14
  * unique session that persists across page reloads. */
17
15
  export declare function getTabId(): TabId;
18
- export declare class NeovimClient {
19
- private readonly ready;
20
- private readonly tabId;
21
- private readonly terminal;
22
- private readonly trpc;
23
- constructor(app: HTMLElement);
24
- startNeovim(args: StartNeovimGenericArguments): Promise<TestDirectory>;
25
- }
@@ -1,5 +1,4 @@
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";
@@ -71,57 +70,3 @@ export function getTabId() {
71
70
  }
72
71
  return { tabId };
73
72
  }
74
- export class NeovimClient {
75
- ready;
76
- tabId;
77
- terminal;
78
- trpc;
79
- constructor(app) {
80
- const wsClient = createWSClient({ url: `ws://localhost:3000`, WebSocket });
81
- const trpc = createTRPCClient({
82
- links: [wsLink({ client: wsClient })],
83
- });
84
- this.trpc = trpc;
85
- this.tabId = getTabId();
86
- const tabId = this.tabId;
87
- const terminal = startTerminal(app, {
88
- onMouseEvent(data) {
89
- void trpc.neovim.sendStdin.mutate({ tabId, data }).catch((error) => {
90
- console.error(`Error sending mouse event`, error);
91
- });
92
- },
93
- onKeyPress(event) {
94
- void trpc.neovim.sendStdin.mutate({ tabId, data: event.key });
95
- },
96
- });
97
- this.terminal = terminal;
98
- // start listening to Neovim stdout - this will take some (short) amount of
99
- // time to complete
100
- this.ready = new Promise(resolve => {
101
- console.log("Subscribing to Neovim stdout");
102
- trpc.neovim.onStdout.subscribe({ client: tabId }, {
103
- onStarted() {
104
- resolve();
105
- },
106
- onData(data) {
107
- terminal.write(data);
108
- },
109
- onError(err) {
110
- console.error(`Error from Neovim`, err);
111
- },
112
- });
113
- });
114
- }
115
- async startNeovim(args) {
116
- await this.ready;
117
- const neovim = await this.trpc.neovim.start.mutate({
118
- startNeovimArguments: args,
119
- tabId: this.tabId,
120
- terminalDimensions: {
121
- cols: this.terminal.cols,
122
- rows: this.terminal.rows,
123
- },
124
- });
125
- return neovim;
126
- }
127
- }
@@ -0,0 +1,8 @@
1
+ import type { AnyTRPCRouter } from "@trpc/server";
2
+ import "core-js/proposals/async-explicit-resource-management";
3
+ import type { TestServerConfig } from "./updateTestdirectorySchemaFile";
4
+ export declare class TestServer {
5
+ private readonly port;
6
+ constructor(port: number);
7
+ startAndRun<TRouter extends AnyTRPCRouter>(appRouter: TRouter, config: TestServerConfig): Promise<void>;
8
+ }
@@ -0,0 +1,49 @@
1
+ import { applyWSSHandler } from "@trpc/server/adapters/ws";
2
+ import "core-js/proposals/async-explicit-resource-management";
3
+ import { once } from "events";
4
+ import { WebSocketServer } from "ws";
5
+ import { createContext } from "./connection/trpc";
6
+ import { updateTestdirectorySchemaFile } from "./updateTestdirectorySchemaFile";
7
+ export class TestServer {
8
+ port;
9
+ constructor(port) {
10
+ this.port = port;
11
+ }
12
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
13
+ async startAndRun(appRouter, config) {
14
+ console.log("🚀 Server starting");
15
+ await updateTestdirectorySchemaFile(config);
16
+ const wss = new WebSocketServer({ port: this.port });
17
+ const handler = applyWSSHandler({
18
+ wss,
19
+ router: appRouter,
20
+ createContext,
21
+ // Enable heartbeat messages to keep connection open (disabled by default)
22
+ keepAlive: {
23
+ enabled: true,
24
+ // server ping message interval in milliseconds
25
+ pingMs: 30_000,
26
+ // connection is terminated if pong message is not received in this many milliseconds
27
+ pongWaitMs: 5000,
28
+ },
29
+ });
30
+ wss.on("connection", socket => {
31
+ console.log(`➕➕ Connection (${wss.clients.size})`);
32
+ socket.once("close", () => {
33
+ console.log(`➖➖ Connection (${wss.clients.size})`);
34
+ });
35
+ });
36
+ console.log(`✅ WebSocket Server listening on ws://localhost:${this.port}`);
37
+ await Promise.race([once(process, "SIGTERM"), once(process, "SIGINT")]);
38
+ console.log("Shutting down...");
39
+ handler.broadcastReconnectNotification();
40
+ wss.close(error => {
41
+ if (error) {
42
+ console.error("Error closing WebSocket server", error);
43
+ process.exit(1);
44
+ }
45
+ console.log("WebSocket server closed");
46
+ process.exit(0);
47
+ });
48
+ }
49
+ }
@@ -28,13 +28,12 @@ export function convertDree(root) {
28
28
  stem: root.extension ? root.name.slice(0, -root.extension.length) : root.name,
29
29
  };
30
30
  }
31
- assert(root.children, `Expected children for directory node ${root.name}`);
32
31
  const node = {
33
32
  name: root.name,
34
33
  type: root.type,
35
34
  contents: {},
36
35
  };
37
- for (const child of root.children) {
36
+ for (const child of root.children || []) {
38
37
  node.contents[child.name] = convertDree(child);
39
38
  }
40
39
  return node;
@@ -1,8 +1,4 @@
1
- import type { AnyRouter } from "@trpc/server";
2
1
  import "core-js/proposals/async-explicit-resource-management";
3
- import type { TestServerConfig } from "./updateTestdirectorySchemaFile";
4
- export declare class TestServer {
5
- private readonly port;
6
- constructor(port: number);
7
- startAndRun<TRouter extends AnyRouter>(appRouter: TRouter, config: TestServerConfig): Promise<void>;
8
- }
2
+ export { startTestServer } from "./server";
3
+ export { updateTestdirectorySchemaFile } from "./updateTestdirectorySchemaFile";
4
+ export type { TestServerConfig } from "./updateTestdirectorySchemaFile";
@@ -1,49 +1,4 @@
1
- import { applyWSSHandler } from "@trpc/server/adapters/ws";
2
1
  import "core-js/proposals/async-explicit-resource-management";
3
- import { once } from "events";
4
- import { WebSocketServer } from "ws";
5
- import { createContext } from "./connection/trpc";
6
- import { updateTestdirectorySchemaFile } from "./updateTestdirectorySchemaFile";
7
- export class TestServer {
8
- port;
9
- constructor(port) {
10
- this.port = port;
11
- }
12
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
13
- async startAndRun(appRouter, config) {
14
- console.log("🚀 Server starting");
15
- await updateTestdirectorySchemaFile(config);
16
- const wss = new WebSocketServer({ port: this.port });
17
- const handler = applyWSSHandler({
18
- wss,
19
- router: appRouter,
20
- createContext,
21
- // Enable heartbeat messages to keep connection open (disabled by default)
22
- keepAlive: {
23
- enabled: true,
24
- // server ping message interval in milliseconds
25
- pingMs: 30_000,
26
- // connection is terminated if pong message is not received in this many milliseconds
27
- pongWaitMs: 5000,
28
- },
29
- });
30
- wss.on("connection", socket => {
31
- console.log(`➕➕ Connection (${wss.clients.size})`);
32
- socket.once("close", () => {
33
- console.log(`➖➖ Connection (${wss.clients.size})`);
34
- });
35
- });
36
- console.log(`✅ WebSocket Server listening on ws://localhost:${this.port}`);
37
- await Promise.race([once(process, "SIGTERM"), once(process, "SIGINT")]);
38
- console.log("Shutting down...");
39
- handler.broadcastReconnectNotification();
40
- wss.close(error => {
41
- if (error) {
42
- console.error("Error closing WebSocket server", error);
43
- process.exit(1);
44
- }
45
- console.log("WebSocket server closed");
46
- process.exit(0);
47
- });
48
- }
49
- }
2
+ // This is the public server api. Semantic versioning will be applied to this.
3
+ export { startTestServer } from "./server";
4
+ export { updateTestdirectorySchemaFile } from "./updateTestdirectorySchemaFile";
@@ -1,3 +1,4 @@
1
1
  import type { TestDirectory } from "../../types";
2
2
  import type { TestServerConfig } from "../../updateTestdirectorySchemaFile";
3
3
  export declare function createTempDir(config: TestServerConfig): Promise<TestDirectory>;
4
+ export type TestDirsPath = "testdirs";
@@ -0,0 +1,29 @@
1
+ import fs, { rmdirSync } from "fs";
2
+ import nodePath from "path";
3
+ import { expect, it } from "vitest";
4
+ import { createTempDir } from "./createTempDir";
5
+ class TempDirectory {
6
+ path;
7
+ constructor(path) {
8
+ this.path = path;
9
+ }
10
+ static create() {
11
+ const tmp = fs.mkdtempSync("test-temp-dir-");
12
+ return new TempDirectory(tmp);
13
+ }
14
+ [Symbol.dispose]() {
15
+ rmdirSync(this.path, { recursive: true });
16
+ }
17
+ }
18
+ it("should create a temp dir with no contents", async () => {
19
+ // typically the user will want to have contents, but this should not be an error
20
+ await using dir = TempDirectory.create();
21
+ const result = await createTempDir({
22
+ testEnvironmentPath: dir.path,
23
+ outputFilePath: nodePath.join(dir.path, "MyTestDirectory.ts"),
24
+ });
25
+ expect(result.contents).toEqual({});
26
+ expect(result.testEnvironmentPath).toEqual(dir.path);
27
+ expect(result.testEnvironmentPath.startsWith("test-temp-dir-")).toBeTruthy();
28
+ expect(result.testEnvironmentPathRelative.startsWith("testdirs")).toBeTruthy();
29
+ });
@@ -1,5 +1,5 @@
1
1
  import type { inferRouterInputs } from "@trpc/server";
2
- import { TestServer } from ".";
2
+ import { TestServer } from "./TestServer";
3
3
  import type { TestServerConfig } from "./updateTestdirectorySchemaFile";
4
4
  /** Stack for managing resources that need to be disposed of when the server
5
5
  * shuts down */
@@ -35,7 +35,7 @@ declare function createAppRouter(config: TestServerConfig): import("@trpc/server
35
35
  };
36
36
  output: import("./types").TestDirectory;
37
37
  }>;
38
- onStdout: import("@trpc/server").TRPCSubscriptionProcedure<{
38
+ onStdout: import("@trpc/server/unstable-core-do-not-import").LegacyObservableSubscriptionProcedure<{
39
39
  input: {
40
40
  client: {
41
41
  tabId: string;
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
- import { TestServer } from ".";
3
2
  import { trpc } from "./connection/trpc";
4
3
  import * as neovim from "./neovim";
4
+ import { TestServer } from "./TestServer";
5
5
  import { tabIdSchema } from "./utilities/tabId";
6
6
  /** Stack for managing resources that need to be disposed of when the server
7
7
  * shuts down */