@tui-sandbox/library 9.0.0 → 9.1.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 +14 -0
- package/dist/browser/assets/{index-_30KjjEK.js → index-pw1tOGNt.js} +7 -7
- package/dist/browser/index.html +1 -1
- package/dist/src/browser/neovim-client.d.ts +5 -0
- package/dist/src/browser/neovim-client.js +17 -6
- package/dist/src/client/index.d.ts +2 -1
- package/dist/src/client/index.js +2 -1
- package/dist/src/client/{terminal-client.d.ts → neovim-terminal-client.d.ts} +1 -1
- package/dist/src/client/{terminal-client.js → neovim-terminal-client.js} +3 -3
- package/dist/src/client/terminal-terminal-client.d.ts +14 -0
- package/dist/src/client/terminal-terminal-client.js +72 -0
- package/dist/src/server/connection/trpc.d.ts +6 -14
- package/dist/src/server/cypress-support/contents.js +23 -1
- package/dist/src/server/dirtree/index.test.js +2 -0
- package/dist/src/server/neovim/NeovimApplication.d.ts +1 -1
- package/dist/src/server/neovim/environment/createTempDir.test.js +6 -5
- package/dist/src/server/neovim/index.js +6 -1
- package/dist/src/server/server.d.ts +46 -6
- package/dist/src/server/server.js +24 -0
- package/dist/src/server/terminal/TerminalTestApplication.d.ts +22 -0
- package/dist/src/server/terminal/TerminalTestApplication.js +60 -0
- package/dist/src/server/terminal/index.d.ts +12 -0
- package/dist/src/server/terminal/index.js +40 -0
- package/dist/src/server/types.d.ts +5 -0
- package/dist/src/server/utilities/TerminalApplication.d.ts +1 -0
- package/dist/src/server/utilities/TerminalApplication.js +10 -5
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/browser/neovim-client.ts +26 -6
- package/src/client/index.ts +2 -1
- package/src/client/{terminal-client.ts → neovim-terminal-client.ts} +3 -3
- package/src/client/terminal-terminal-client.ts +86 -0
- package/src/server/cypress-support/contents.ts +23 -1
- package/src/server/dirtree/index.test.ts +2 -0
- package/src/server/neovim/NeovimApplication.ts +3 -3
- package/src/server/neovim/environment/createTempDir.test.ts +6 -5
- package/src/server/neovim/index.ts +6 -1
- package/src/server/server.ts +34 -0
- package/src/server/terminal/TerminalTestApplication.ts +98 -0
- package/src/server/terminal/index.ts +62 -0
- package/src/server/types.ts +13 -0
- package/src/server/utilities/TerminalApplication.ts +10 -8
package/dist/browser/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<title>tui-sandbox integration tests</title>
|
|
6
|
-
<script type="module" crossorigin src="/assets/index-
|
|
6
|
+
<script type="module" crossorigin src="/assets/index-pw1tOGNt.js"></script>
|
|
7
7
|
<link rel="stylesheet" crossorigin href="/assets/index-D6fBrqAi.css">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BlockingCommandClientInput, ExCommandClientInput, LuaCodeClientInput } from "../server/server.js";
|
|
2
|
+
import type { StartTerminalGenericArguments } from "../server/terminal/TerminalTestApplication.js";
|
|
2
3
|
import type { BlockingShellCommandOutput, RunExCommandOutput, RunLuaCodeOutput, StartNeovimGenericArguments, TestDirectory } from "../server/types.js";
|
|
3
4
|
export type GenericNeovimBrowserApi = {
|
|
4
5
|
runBlockingShellCommand(input: BlockingCommandClientInput): Promise<BlockingShellCommandOutput>;
|
|
@@ -9,5 +10,9 @@ export type GenericNeovimBrowserApi = {
|
|
|
9
10
|
declare global {
|
|
10
11
|
interface Window {
|
|
11
12
|
startNeovim(startArguments?: StartNeovimGenericArguments): Promise<GenericNeovimBrowserApi>;
|
|
13
|
+
startTerminalApplication(args: StartTerminalGenericArguments): Promise<GenericTerminalBrowserApi>;
|
|
12
14
|
}
|
|
13
15
|
}
|
|
16
|
+
export type GenericTerminalBrowserApi = {
|
|
17
|
+
dir: TestDirectory;
|
|
18
|
+
};
|
|
@@ -1,27 +1,38 @@
|
|
|
1
|
-
import { TerminalClient } from "../client/index.js";
|
|
1
|
+
import { TerminalClient as NeovimTerminalClient } from "../client/index.js";
|
|
2
|
+
import { TerminalTerminalClient } from "../client/terminal-terminal-client.js";
|
|
3
|
+
import { Lazy } from "../server/utilities/Lazy.js";
|
|
2
4
|
const app = document.querySelector("#app");
|
|
3
5
|
if (!app) {
|
|
4
6
|
throw new Error("No app element found");
|
|
5
7
|
}
|
|
6
|
-
|
|
8
|
+
// limitation: right now only one client can be used in the same test
|
|
9
|
+
const neovimClient = new Lazy(() => new NeovimTerminalClient(app));
|
|
10
|
+
const terminalClient = new Lazy(() => new TerminalTerminalClient(app));
|
|
7
11
|
/** Entrypoint for the test runner (cypress) */
|
|
8
12
|
window.startNeovim = async function (startArgs) {
|
|
9
|
-
const
|
|
13
|
+
const neovim = neovimClient.get();
|
|
14
|
+
const testDirectory = await neovim.startNeovim({
|
|
10
15
|
additionalEnvironmentVariables: startArgs?.additionalEnvironmentVariables,
|
|
11
16
|
filename: startArgs?.filename ?? "initial-file.txt",
|
|
12
17
|
startupScriptModifications: startArgs?.startupScriptModifications ?? [],
|
|
13
18
|
});
|
|
14
19
|
const neovimBrowserApi = {
|
|
15
20
|
runBlockingShellCommand(input) {
|
|
16
|
-
return
|
|
21
|
+
return neovim.runBlockingShellCommand(input);
|
|
17
22
|
},
|
|
18
23
|
runLuaCode(input) {
|
|
19
|
-
return
|
|
24
|
+
return neovim.runLuaCode(input);
|
|
20
25
|
},
|
|
21
26
|
runExCommand(input) {
|
|
22
|
-
return
|
|
27
|
+
return neovim.runExCommand(input);
|
|
23
28
|
},
|
|
24
29
|
dir: testDirectory,
|
|
25
30
|
};
|
|
26
31
|
return neovimBrowserApi;
|
|
27
32
|
};
|
|
33
|
+
/** Entrypoint for the test runner (cypress) */
|
|
34
|
+
window.startTerminalApplication = async function (args) {
|
|
35
|
+
const terminal = terminalClient.get();
|
|
36
|
+
const testDirectory = await terminal.startTerminalApplication(args);
|
|
37
|
+
return { dir: testDirectory };
|
|
38
|
+
};
|
package/dist/src/client/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
// This is the public client api. Semantic versioning will be applied to this.
|
|
2
2
|
export { rgbify } from "./color-utilities.js";
|
|
3
|
-
export { TerminalClient } from "./terminal-client.js";
|
|
3
|
+
export { NeovimTerminalClient as TerminalClient } from "./neovim-terminal-client.js";
|
|
4
|
+
export { TerminalTerminalClient } from "./terminal-terminal-client.js";
|
|
@@ -4,7 +4,7 @@ import type { BlockingShellCommandOutput, RunExCommandOutput, RunLuaCodeOutput,
|
|
|
4
4
|
import "./style.css";
|
|
5
5
|
/** Manages the terminal state in the browser as well as the (browser's)
|
|
6
6
|
* connection to the server side terminal application api. */
|
|
7
|
-
export declare class
|
|
7
|
+
export declare class NeovimTerminalClient {
|
|
8
8
|
private readonly ready;
|
|
9
9
|
private readonly tabId;
|
|
10
10
|
private readonly terminal;
|
|
@@ -4,7 +4,7 @@ import "./style.css";
|
|
|
4
4
|
import { getTabId, startTerminal } from "./websocket-client.js";
|
|
5
5
|
/** Manages the terminal state in the browser as well as the (browser's)
|
|
6
6
|
* connection to the server side terminal application api. */
|
|
7
|
-
export class
|
|
7
|
+
export class NeovimTerminalClient {
|
|
8
8
|
ready;
|
|
9
9
|
tabId;
|
|
10
10
|
terminal;
|
|
@@ -40,7 +40,7 @@ export class TerminalClient {
|
|
|
40
40
|
// start listening to Neovim stdout - this will take some (short) amount of
|
|
41
41
|
// time to complete
|
|
42
42
|
this.ready = new Promise(resolve => {
|
|
43
|
-
console.log("Subscribing to
|
|
43
|
+
console.log("Subscribing to stdout");
|
|
44
44
|
trpc.neovim.onStdout.subscribe({ client: tabId }, {
|
|
45
45
|
onStarted() {
|
|
46
46
|
resolve();
|
|
@@ -49,7 +49,7 @@ export class TerminalClient {
|
|
|
49
49
|
terminal.write(data);
|
|
50
50
|
},
|
|
51
51
|
onError(err) {
|
|
52
|
-
console.error(`Error from
|
|
52
|
+
console.error(`Error from the application`, err);
|
|
53
53
|
},
|
|
54
54
|
});
|
|
55
55
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "@xterm/xterm/css/xterm.css";
|
|
2
|
+
import type { StartTerminalGenericArguments } from "../server/terminal/TerminalTestApplication.js";
|
|
3
|
+
import type { TestDirectory } from "../server/types.js";
|
|
4
|
+
import "./style.css";
|
|
5
|
+
/** Manages the terminal state in the browser as well as the (browser's)
|
|
6
|
+
* connection to the server side terminal application api. */
|
|
7
|
+
export declare class TerminalTerminalClient {
|
|
8
|
+
private readonly ready;
|
|
9
|
+
private readonly tabId;
|
|
10
|
+
private readonly terminal;
|
|
11
|
+
private readonly trpc;
|
|
12
|
+
constructor(app: HTMLElement);
|
|
13
|
+
startTerminalApplication(args: StartTerminalGenericArguments): Promise<TestDirectory>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createTRPCClient, httpBatchLink, splitLink, unstable_httpSubscriptionLink } from "@trpc/client";
|
|
2
|
+
import "@xterm/xterm/css/xterm.css";
|
|
3
|
+
import "./style.css";
|
|
4
|
+
import { getTabId, startTerminal } from "./websocket-client.js";
|
|
5
|
+
/** Manages the terminal state in the browser as well as the (browser's)
|
|
6
|
+
* connection to the server side terminal application api. */
|
|
7
|
+
export class TerminalTerminalClient {
|
|
8
|
+
ready;
|
|
9
|
+
tabId;
|
|
10
|
+
terminal;
|
|
11
|
+
trpc;
|
|
12
|
+
constructor(app) {
|
|
13
|
+
const trpc = createTRPCClient({
|
|
14
|
+
links: [
|
|
15
|
+
splitLink({
|
|
16
|
+
condition: operation => operation.type === "subscription",
|
|
17
|
+
true: unstable_httpSubscriptionLink({
|
|
18
|
+
url: "/trpc",
|
|
19
|
+
}),
|
|
20
|
+
false: httpBatchLink({
|
|
21
|
+
url: "/trpc",
|
|
22
|
+
}),
|
|
23
|
+
}),
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
this.trpc = trpc;
|
|
27
|
+
this.tabId = getTabId();
|
|
28
|
+
const tabId = this.tabId;
|
|
29
|
+
const terminal = startTerminal(app, {
|
|
30
|
+
onMouseEvent(data) {
|
|
31
|
+
void trpc.terminal.sendStdin.mutate({ tabId, data }).catch((error) => {
|
|
32
|
+
console.error(`Error sending mouse event`, error);
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
onKeyPress(event) {
|
|
36
|
+
void trpc.terminal.sendStdin.mutate({ tabId, data: event.key });
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
this.terminal = terminal;
|
|
40
|
+
// start listening to Neovim stdout - this will take some (short) amount of
|
|
41
|
+
// time to complete
|
|
42
|
+
this.ready = new Promise(resolve => {
|
|
43
|
+
console.log("Subscribing to stdout");
|
|
44
|
+
trpc.terminal.onStdout.subscribe({ client: tabId }, {
|
|
45
|
+
onStarted() {
|
|
46
|
+
resolve();
|
|
47
|
+
},
|
|
48
|
+
onData(data) {
|
|
49
|
+
terminal.write(data);
|
|
50
|
+
},
|
|
51
|
+
onError(err) {
|
|
52
|
+
console.error(`Error from the application`, err);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async startTerminalApplication(args) {
|
|
58
|
+
await this.ready;
|
|
59
|
+
const testDirectory = await this.trpc.terminal.start.mutate({
|
|
60
|
+
tabId: this.tabId,
|
|
61
|
+
startTerminalArguments: {
|
|
62
|
+
additionalEnvironmentVariables: args.additionalEnvironmentVariables,
|
|
63
|
+
commandToRun: args.commandToRun,
|
|
64
|
+
terminalDimensions: {
|
|
65
|
+
cols: this.terminal.cols,
|
|
66
|
+
rows: this.terminal.rows,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
return testDirectory;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -7,20 +7,12 @@ export declare const trpc: {
|
|
|
7
7
|
}>;
|
|
8
8
|
procedure: import("@trpc/server/unstable-core-do-not-import").ProcedureBuilder<object, object, object, typeof import("@trpc/server/unstable-core-do-not-import").unsetMarker, typeof import("@trpc/server/unstable-core-do-not-import").unsetMarker, typeof import("@trpc/server/unstable-core-do-not-import").unsetMarker, typeof import("@trpc/server/unstable-core-do-not-import").unsetMarker, false>;
|
|
9
9
|
middleware: <$ContextOverrides>(fn: import("@trpc/server/unstable-core-do-not-import").MiddlewareFunction<object, object, object, $ContextOverrides, unknown>) => import("@trpc/server/unstable-core-do-not-import").MiddlewareBuilder<object, object, $ContextOverrides, unknown>;
|
|
10
|
-
router: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}, TInput>;
|
|
17
|
-
<TInput extends import("@trpc/server/unstable-core-do-not-import").CreateRouterOptions>(input: TInput): import("@trpc/server/unstable-core-do-not-import").BuiltRouter<{
|
|
18
|
-
ctx: object;
|
|
19
|
-
meta: object;
|
|
20
|
-
errorShape: import("@trpc/server/unstable-core-do-not-import").DefaultErrorShape;
|
|
21
|
-
transformer: false;
|
|
22
|
-
}, import("@trpc/server/unstable-core-do-not-import").DecorateCreateRouterOptions<TInput>>;
|
|
23
|
-
};
|
|
10
|
+
router: <TInput extends import("@trpc/server/unstable-core-do-not-import").CreateRouterOptions>(input: TInput) => import("@trpc/server/unstable-core-do-not-import").BuiltRouter<{
|
|
11
|
+
ctx: object;
|
|
12
|
+
meta: object;
|
|
13
|
+
errorShape: import("@trpc/server/unstable-core-do-not-import").DefaultErrorShape;
|
|
14
|
+
transformer: false;
|
|
15
|
+
}, import("@trpc/server/unstable-core-do-not-import").DecorateCreateRouterOptions<TInput>>;
|
|
24
16
|
mergeRouters: typeof import("@trpc/server/unstable-core-do-not-import").mergeRouters;
|
|
25
17
|
createCallerFactory: <TRecord extends import("@trpc/server").RouterRecord>(router: Pick<import("@trpc/server/unstable-core-do-not-import").Router<{
|
|
26
18
|
ctx: object;
|
|
@@ -8,7 +8,10 @@ export async function createCypressSupportFileContents() {
|
|
|
8
8
|
//
|
|
9
9
|
// This file is autogenerated by tui-sandbox. Do not edit it directly.
|
|
10
10
|
//
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
GenericNeovimBrowserApi,
|
|
13
|
+
GenericTerminalBrowserApi,
|
|
14
|
+
} from "@tui-sandbox/library/dist/src/browser/neovim-client"
|
|
12
15
|
import type {
|
|
13
16
|
BlockingCommandClientInput,
|
|
14
17
|
ExCommandClientInput,
|
|
@@ -21,9 +24,19 @@ import type {
|
|
|
21
24
|
StartNeovimGenericArguments,
|
|
22
25
|
TestDirectory,
|
|
23
26
|
} from "@tui-sandbox/library/dist/src/server/types"
|
|
27
|
+
import type { StartTerminalGenericArguments } from "@tui-sandbox/library/src/server/terminal/TerminalTestApplication"
|
|
24
28
|
import type { OverrideProperties } from "type-fest"
|
|
25
29
|
import type { MyTestDirectory, MyTestDirectoryFile } from "../../MyTestDirectory"
|
|
26
30
|
|
|
31
|
+
export type TerminalTestApplicationContext = {
|
|
32
|
+
/** Types text into the terminal, making the terminal application receive the
|
|
33
|
+
* keystrokes as input. Requires the application to be running. */
|
|
34
|
+
typeIntoTerminal(text: string, options?: Partial<Cypress.TypeOptions>): void
|
|
35
|
+
|
|
36
|
+
/** The test directory, providing type-safe access to its file and directory structure */
|
|
37
|
+
dir: TestDirectory<MyTestDirectory>
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
/** The api that can be used in tests after a Neovim instance has been started. */
|
|
28
41
|
export type NeovimContext = {
|
|
29
42
|
/** Types text into the terminal, making the terminal application receive
|
|
@@ -91,6 +104,14 @@ Cypress.Commands.add("startNeovim", (startArguments?: MyStartNeovimServerArgumen
|
|
|
91
104
|
})
|
|
92
105
|
})
|
|
93
106
|
|
|
107
|
+
Cypress.Commands.add("startTerminalApplication", (args: StartTerminalGenericArguments) => {
|
|
108
|
+
cy.window().then(async win => {
|
|
109
|
+
const api: GenericTerminalBrowserApi = await win.startTerminalApplication(args)
|
|
110
|
+
|
|
111
|
+
return api
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
94
115
|
Cypress.Commands.add("typeIntoTerminal", (text: string, options?: Partial<Cypress.TypeOptions>) => {
|
|
95
116
|
// the syntax for keys is described here:
|
|
96
117
|
// https://docs.cypress.io/api/commands/type
|
|
@@ -110,6 +131,7 @@ declare global {
|
|
|
110
131
|
namespace Cypress {
|
|
111
132
|
interface Chainable {
|
|
112
133
|
startNeovim(args?: MyStartNeovimServerArguments): Chainable<NeovimContext>
|
|
134
|
+
startTerminalApplication(args: StartTerminalGenericArguments): Chainable<TerminalTestApplicationContext>
|
|
113
135
|
|
|
114
136
|
/** Types text into the terminal, making the terminal application receive
|
|
115
137
|
* the keystrokes as input. Requires neovim to be running. */
|
|
@@ -32,6 +32,7 @@ describe("dirtree", () => {
|
|
|
32
32
|
name: z.literal("test-environment/"),
|
|
33
33
|
type: z.literal("directory"),
|
|
34
34
|
contents: z.object({
|
|
35
|
+
".bashrc": z.object({ name: z.literal(".bashrc"), type: z.literal("file") }),
|
|
35
36
|
".config": z.object({
|
|
36
37
|
name: z.literal(".config/"),
|
|
37
38
|
type: z.literal("directory"),
|
|
@@ -120,6 +121,7 @@ describe("dirtree", () => {
|
|
|
120
121
|
export type MyDirectoryTree = MyDirectoryTreeContentsSchemaType["contents"]
|
|
121
122
|
|
|
122
123
|
export const testDirectoryFiles = z.enum([
|
|
124
|
+
".bashrc",
|
|
123
125
|
".config/.gitkeep",
|
|
124
126
|
".config/nvim/init.lua",
|
|
125
127
|
".config/nvim/prepare.lua",
|
|
@@ -24,7 +24,7 @@ type ResettableState = {
|
|
|
24
24
|
socketPath: string;
|
|
25
25
|
client: Lazy<Promise<NeovimApiClient>>;
|
|
26
26
|
};
|
|
27
|
-
export declare class NeovimApplication {
|
|
27
|
+
export declare class NeovimApplication implements AsyncDisposable {
|
|
28
28
|
private readonly testEnvironmentPath;
|
|
29
29
|
readonly application: DisposableSingleApplication;
|
|
30
30
|
state: ResettableState | undefined;
|
|
@@ -50,7 +50,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
import fs
|
|
53
|
+
import fs from "fs";
|
|
54
54
|
import nodePath from "path";
|
|
55
55
|
import { expect, it } from "vitest";
|
|
56
56
|
import { createTempDir } from "./createTempDir.js";
|
|
@@ -62,10 +62,11 @@ class TempDirectory {
|
|
|
62
62
|
}
|
|
63
63
|
static create() {
|
|
64
64
|
const tmp = fs.mkdtempSync("test-temp-dir-");
|
|
65
|
-
|
|
65
|
+
const absolutePath = nodePath.resolve(tmp);
|
|
66
|
+
return new TempDirectory(absolutePath);
|
|
66
67
|
}
|
|
67
68
|
[Symbol.dispose]() {
|
|
68
|
-
rmdirSync(this.path, { recursive: true });
|
|
69
|
+
fs.rmdirSync(this.path, { recursive: true, maxRetries: 5 });
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
it("should create a temp dir with no contents", async () => {
|
|
@@ -79,8 +80,8 @@ it("should create a temp dir with no contents", async () => {
|
|
|
79
80
|
});
|
|
80
81
|
expect(result.contents).toEqual({});
|
|
81
82
|
expect(result.testEnvironmentPath).toEqual(dir.path);
|
|
82
|
-
expect(result.testEnvironmentPath.
|
|
83
|
-
expect(result.testEnvironmentPathRelative.
|
|
83
|
+
expect(result.testEnvironmentPath.includes("test-temp-dir-")).toBeTruthy();
|
|
84
|
+
expect(result.testEnvironmentPathRelative.includes("testdirs")).toBeTruthy();
|
|
84
85
|
}
|
|
85
86
|
catch (e_1) {
|
|
86
87
|
env_1.error = e_1;
|
|
@@ -78,11 +78,16 @@ export async function installDependencies(testEnvironmentPath, config) {
|
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
console.log(`🚀 Running Neovim prepareFilePath ${prepareFilePath}...`);
|
|
81
|
+
let output = "";
|
|
81
82
|
app.events.on("stdout", data => {
|
|
82
|
-
|
|
83
|
+
assert(data);
|
|
84
|
+
assert(typeof data === "string");
|
|
85
|
+
output += data;
|
|
83
86
|
});
|
|
84
87
|
await app.startNextAndKillCurrent(testDirectory, { filename: "empty.txt", headlessCmd: `lua dofile("${prepareFilePath}")` }, { cols: 80, rows: 24 });
|
|
85
88
|
await app.application.untilExit();
|
|
89
|
+
console.log(`🚀 Neovim installDependencies output:`);
|
|
90
|
+
console.log(output);
|
|
86
91
|
}
|
|
87
92
|
catch (e_1) {
|
|
88
93
|
env_1.error = e_1;
|
|
@@ -98,27 +98,67 @@ export declare function createAppRouter(config: DirectoriesConfig): Promise<impo
|
|
|
98
98
|
errorShape: import("@trpc/server/unstable-core-do-not-import").DefaultErrorShape;
|
|
99
99
|
transformer: false;
|
|
100
100
|
}, import("@trpc/server/unstable-core-do-not-import").DecorateCreateRouterOptions<{
|
|
101
|
+
terminal: import("@trpc/server/unstable-core-do-not-import").BuiltRouter<{
|
|
102
|
+
ctx: object;
|
|
103
|
+
meta: object;
|
|
104
|
+
errorShape: import("@trpc/server/unstable-core-do-not-import").DefaultErrorShape;
|
|
105
|
+
transformer: false;
|
|
106
|
+
}, import("@trpc/server/unstable-core-do-not-import").DecorateCreateRouterOptions<{
|
|
107
|
+
onStdout: import("@trpc/server").TRPCSubscriptionProcedure<{
|
|
108
|
+
input: {
|
|
109
|
+
client: {
|
|
110
|
+
tabId: string;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
output: AsyncIterable<string, void, unknown>;
|
|
114
|
+
}>;
|
|
115
|
+
start: import("@trpc/server").TRPCMutationProcedure<{
|
|
116
|
+
input: {
|
|
117
|
+
tabId: {
|
|
118
|
+
tabId: string;
|
|
119
|
+
};
|
|
120
|
+
startTerminalArguments: {
|
|
121
|
+
commandToRun: string[];
|
|
122
|
+
terminalDimensions: {
|
|
123
|
+
cols: number;
|
|
124
|
+
rows: number;
|
|
125
|
+
};
|
|
126
|
+
additionalEnvironmentVariables?: Record<string, string> | undefined;
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
output: void;
|
|
130
|
+
}>;
|
|
131
|
+
sendStdin: import("@trpc/server").TRPCMutationProcedure<{
|
|
132
|
+
input: {
|
|
133
|
+
data: string;
|
|
134
|
+
tabId: {
|
|
135
|
+
tabId: string;
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
output: void;
|
|
139
|
+
}>;
|
|
140
|
+
}>>;
|
|
101
141
|
neovim: import("@trpc/server/unstable-core-do-not-import").BuiltRouter<{
|
|
102
142
|
ctx: object;
|
|
103
143
|
meta: object;
|
|
104
144
|
errorShape: import("@trpc/server/unstable-core-do-not-import").DefaultErrorShape;
|
|
105
145
|
transformer: false;
|
|
106
|
-
}, {
|
|
146
|
+
}, import("@trpc/server/unstable-core-do-not-import").DecorateCreateRouterOptions<{
|
|
107
147
|
start: import("@trpc/server").TRPCMutationProcedure<{
|
|
108
148
|
input: {
|
|
109
149
|
tabId: {
|
|
110
150
|
tabId: string;
|
|
111
151
|
};
|
|
112
152
|
startNeovimArguments: {
|
|
113
|
-
filename: string | {
|
|
114
|
-
openInVerticalSplits: string[];
|
|
115
|
-
};
|
|
116
153
|
terminalDimensions: {
|
|
117
154
|
cols: number;
|
|
118
155
|
rows: number;
|
|
119
156
|
};
|
|
120
|
-
|
|
157
|
+
filename: string | {
|
|
158
|
+
openInVerticalSplits: string[];
|
|
159
|
+
};
|
|
121
160
|
additionalEnvironmentVariables?: Record<string, string> | undefined;
|
|
161
|
+
startupScriptModifications?: string[] | undefined;
|
|
122
162
|
};
|
|
123
163
|
};
|
|
124
164
|
output: import("./types.js").TestDirectory;
|
|
@@ -174,7 +214,7 @@ export declare function createAppRouter(config: DirectoriesConfig): Promise<impo
|
|
|
174
214
|
};
|
|
175
215
|
output: import("./types.js").RunExCommandOutput;
|
|
176
216
|
}>;
|
|
177
|
-
}
|
|
217
|
+
}>>;
|
|
178
218
|
}>>>;
|
|
179
219
|
export type AppRouter = Awaited<ReturnType<typeof createAppRouter>>;
|
|
180
220
|
export type RouterInput = inferRouterInputs<AppRouter>;
|
|
@@ -2,6 +2,7 @@ import "core-js/proposals/async-explicit-resource-management.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { trpc } from "./connection/trpc.js";
|
|
4
4
|
import * as neovim from "./neovim/index.js";
|
|
5
|
+
import * as terminal from "./terminal/index.js";
|
|
5
6
|
import { TestServer } from "./TestServer.js";
|
|
6
7
|
import { applicationAvailable } from "./utilities/applicationAvailable.js";
|
|
7
8
|
import { tabIdSchema } from "./utilities/tabId.js";
|
|
@@ -29,6 +30,29 @@ export async function createAppRouter(config) {
|
|
|
29
30
|
throw new Error("Neovim is not installed. Please install Neovim (nvim).");
|
|
30
31
|
}
|
|
31
32
|
const appRouter = trpc.router({
|
|
33
|
+
terminal: trpc.router({
|
|
34
|
+
onStdout: trpc.procedure.input(z.object({ client: tabIdSchema })).subscription(options => {
|
|
35
|
+
return terminal.initializeStdout(options.input, options.signal, config.testEnvironmentPath);
|
|
36
|
+
}),
|
|
37
|
+
start: trpc.procedure
|
|
38
|
+
.input(z.object({
|
|
39
|
+
tabId: tabIdSchema,
|
|
40
|
+
startTerminalArguments: z.object({
|
|
41
|
+
commandToRun: z.array(z.string()),
|
|
42
|
+
additionalEnvironmentVariables: z.record(z.string()).optional(),
|
|
43
|
+
terminalDimensions: z.object({
|
|
44
|
+
cols: z.number(),
|
|
45
|
+
rows: z.number(),
|
|
46
|
+
}),
|
|
47
|
+
}),
|
|
48
|
+
}))
|
|
49
|
+
.mutation(options => {
|
|
50
|
+
return terminal.start(options.input.startTerminalArguments.terminalDimensions, options.input.startTerminalArguments.commandToRun, options.input.tabId, config);
|
|
51
|
+
}),
|
|
52
|
+
sendStdin: trpc.procedure.input(z.object({ tabId: tabIdSchema, data: z.string() })).mutation(options => {
|
|
53
|
+
return terminal.sendStdin(options.input);
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
32
56
|
neovim: trpc.router({
|
|
33
57
|
start: trpc.procedure
|
|
34
58
|
.input(z.object({
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import EventEmitter from "events";
|
|
2
|
+
import type { TerminalDimensions } from "../neovim/NeovimApplication.js";
|
|
3
|
+
import type { TestDirectory } from "../types.js";
|
|
4
|
+
import { DisposableSingleApplication } from "../utilities/DisposableSingleApplication.js";
|
|
5
|
+
type ResettableState = {
|
|
6
|
+
testDirectory: TestDirectory;
|
|
7
|
+
};
|
|
8
|
+
export type StartTerminalGenericArguments = {
|
|
9
|
+
commandToRun: string[];
|
|
10
|
+
additionalEnvironmentVariables?: Record<string, string> | undefined;
|
|
11
|
+
};
|
|
12
|
+
export default class TerminalTestApplication implements AsyncDisposable {
|
|
13
|
+
private readonly testEnvironmentPath;
|
|
14
|
+
readonly application: DisposableSingleApplication;
|
|
15
|
+
state: ResettableState | undefined;
|
|
16
|
+
readonly events: EventEmitter;
|
|
17
|
+
constructor(testEnvironmentPath: string, application?: DisposableSingleApplication);
|
|
18
|
+
startNextAndKillCurrent(testDirectory: TestDirectory, startArgs: StartTerminalGenericArguments, terminalDimensions: TerminalDimensions): Promise<void>;
|
|
19
|
+
getEnvironmentVariables(testDirectory: TestDirectory, additionalEnvironmentVariables?: Record<string, string>): NodeJS.ProcessEnv;
|
|
20
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import { exec } from "child_process";
|
|
3
|
+
import EventEmitter from "events";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { DisposableSingleApplication } from "../utilities/DisposableSingleApplication.js";
|
|
6
|
+
import { TerminalApplication } from "../utilities/TerminalApplication.js";
|
|
7
|
+
export default class TerminalTestApplication {
|
|
8
|
+
testEnvironmentPath;
|
|
9
|
+
application;
|
|
10
|
+
state;
|
|
11
|
+
events;
|
|
12
|
+
constructor(testEnvironmentPath, application = new DisposableSingleApplication()) {
|
|
13
|
+
this.testEnvironmentPath = testEnvironmentPath;
|
|
14
|
+
this.application = application;
|
|
15
|
+
this.events = new EventEmitter();
|
|
16
|
+
}
|
|
17
|
+
async startNextAndKillCurrent(testDirectory, startArgs, terminalDimensions) {
|
|
18
|
+
await this[Symbol.asyncDispose]();
|
|
19
|
+
assert(this.state === undefined, "TerminalTestApplication state should be undefined after disposing so that no previous state is reused.");
|
|
20
|
+
const command = startArgs.commandToRun[0];
|
|
21
|
+
assert(command, "No command to run was provided.");
|
|
22
|
+
// TODO could check if the command is executable
|
|
23
|
+
const terminalArguments = startArgs.commandToRun.slice(1);
|
|
24
|
+
const stdout = this.events;
|
|
25
|
+
await this.application.startNextAndKillCurrent(async () => {
|
|
26
|
+
const env = this.getEnvironmentVariables(testDirectory, startArgs.additionalEnvironmentVariables);
|
|
27
|
+
return TerminalApplication.start({
|
|
28
|
+
command,
|
|
29
|
+
args: terminalArguments,
|
|
30
|
+
cwd: this.testEnvironmentPath,
|
|
31
|
+
env: env,
|
|
32
|
+
dimensions: terminalDimensions,
|
|
33
|
+
onStdoutOrStderr(data) {
|
|
34
|
+
data;
|
|
35
|
+
stdout.emit("stdout", data);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
const processId = this.application.processId();
|
|
40
|
+
assert(processId !== undefined, "TerminalApplication was started without a process ID. This is a bug - please open an issue.");
|
|
41
|
+
this.state = { testDirectory };
|
|
42
|
+
console.log(`🚀 Started Terminal instance ${processId}`);
|
|
43
|
+
}
|
|
44
|
+
getEnvironmentVariables(testDirectory, additionalEnvironmentVariables) {
|
|
45
|
+
return {
|
|
46
|
+
...process.env,
|
|
47
|
+
HOME: testDirectory.rootPathAbsolute,
|
|
48
|
+
XDG_CONFIG_HOME: join(testDirectory.rootPathAbsolute, ".config"),
|
|
49
|
+
XDG_DATA_HOME: join(testDirectory.testEnvironmentPath, ".repro", "data"),
|
|
50
|
+
...additionalEnvironmentVariables,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async [Symbol.asyncDispose]() {
|
|
54
|
+
await this.application[Symbol.asyncDispose]();
|
|
55
|
+
if (!this.state)
|
|
56
|
+
return;
|
|
57
|
+
exec(`rm -rf ${this.state.testDirectory.rootPathAbsolute}`);
|
|
58
|
+
this.state = undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "core-js/proposals/async-explicit-resource-management.js";
|
|
2
|
+
import type { TerminalDimensions } from "../neovim/NeovimApplication.js";
|
|
3
|
+
import type { DirectoriesConfig } from "../updateTestdirectorySchemaFile.js";
|
|
4
|
+
import type { TabId } from "../utilities/tabId.js";
|
|
5
|
+
export declare function start(terminalDimensions: TerminalDimensions, commandToRun: string[], tabId: TabId, config: DirectoriesConfig): Promise<void>;
|
|
6
|
+
export declare function initializeStdout(options: {
|
|
7
|
+
client: TabId;
|
|
8
|
+
}, signal: AbortSignal | undefined, testEnvironmentPath: string): Promise<AsyncGenerator<string, void, unknown>>;
|
|
9
|
+
export declare function sendStdin(options: {
|
|
10
|
+
tabId: TabId;
|
|
11
|
+
data: string;
|
|
12
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import "core-js/proposals/async-explicit-resource-management.js";
|
|
3
|
+
import { prepareNewTestDirectory } from "../neovim/index.js";
|
|
4
|
+
import { convertEventEmitterToAsyncGenerator } from "../utilities/generator.js";
|
|
5
|
+
import { Lazy } from "../utilities/Lazy.js";
|
|
6
|
+
import TerminalTestApplication from "./TerminalTestApplication.js";
|
|
7
|
+
const terminals = new Map();
|
|
8
|
+
const resources = new Lazy(() => {
|
|
9
|
+
return new AsyncDisposableStack();
|
|
10
|
+
});
|
|
11
|
+
export async function start(terminalDimensions, commandToRun, tabId, config) {
|
|
12
|
+
const app = terminals.get(tabId.tabId);
|
|
13
|
+
assert(app, `Terminal with tabId ${tabId.tabId} not found.`);
|
|
14
|
+
const testDirectory = await prepareNewTestDirectory(config);
|
|
15
|
+
await app.startNextAndKillCurrent(testDirectory, { commandToRun }, terminalDimensions);
|
|
16
|
+
}
|
|
17
|
+
export async function initializeStdout(options, signal, testEnvironmentPath) {
|
|
18
|
+
const tabId = options.client.tabId;
|
|
19
|
+
const app = terminals.get(tabId) ?? new TerminalTestApplication(testEnvironmentPath);
|
|
20
|
+
if (terminals.get(tabId) === undefined) {
|
|
21
|
+
terminals.set(tabId, app);
|
|
22
|
+
resources.get().adopt(app, async (a) => {
|
|
23
|
+
await a[Symbol.asyncDispose]();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const stdout = convertEventEmitterToAsyncGenerator(app.events, "stdout");
|
|
27
|
+
signal?.addEventListener("abort", () => {
|
|
28
|
+
void app[Symbol.asyncDispose]().finally(() => {
|
|
29
|
+
terminals.delete(tabId);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
return stdout;
|
|
33
|
+
}
|
|
34
|
+
export async function sendStdin(options) {
|
|
35
|
+
const tabId = options.tabId.tabId;
|
|
36
|
+
const app = terminals.get(tabId);
|
|
37
|
+
assert(app !== undefined, `Terminal instance for clientId not found - cannot send stdin. Maybe it's not started yet?`);
|
|
38
|
+
assert(app.application, `Terminal application not found for client id ${options.tabId.tabId}. Maybe it's not started yet?`);
|
|
39
|
+
await app.application.write(options.data);
|
|
40
|
+
}
|
|
@@ -26,6 +26,11 @@ export type TestDirectory<TContents extends object = object> = {
|
|
|
26
26
|
testEnvironmentPathRelative: string;
|
|
27
27
|
contents: TContents;
|
|
28
28
|
};
|
|
29
|
+
export type TestEnvironmentCommonEnvironmentVariables = {
|
|
30
|
+
HOME: string;
|
|
31
|
+
XDG_CONFIG_HOME: string;
|
|
32
|
+
XDG_DATA_HOME: string;
|
|
33
|
+
};
|
|
29
34
|
export type { StartNeovimGenericArguments } from "../server/neovim/NeovimApplication.js";
|
|
30
35
|
export type BlockingShellCommandOutput = {
|
|
31
36
|
type: "success";
|