@tui-sandbox/library 9.1.9 → 9.2.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/browser/assets/{index-pw1tOGNt.js → index-BVkLGLTD.js} +1 -1
  3. package/dist/browser/index.html +1 -1
  4. package/dist/src/browser/neovim-client.d.ts +1 -0
  5. package/dist/src/browser/neovim-client.js +6 -1
  6. package/dist/src/browser/neovim-client.js.map +1 -1
  7. package/dist/src/client/terminal-terminal-client.d.ts +3 -1
  8. package/dist/src/client/terminal-terminal-client.js +4 -0
  9. package/dist/src/client/terminal-terminal-client.js.map +1 -1
  10. package/dist/src/server/cypress-support/contents.js +5 -0
  11. package/dist/src/server/cypress-support/contents.js.map +1 -1
  12. package/dist/src/server/neovim/index.js +2 -29
  13. package/dist/src/server/neovim/index.js.map +1 -1
  14. package/dist/src/server/server.d.ts +15 -0
  15. package/dist/src/server/server.js +21 -17
  16. package/dist/src/server/server.js.map +1 -1
  17. package/dist/src/server/terminal/index.d.ts +3 -0
  18. package/dist/src/server/terminal/index.js +11 -0
  19. package/dist/src/server/terminal/index.js.map +1 -1
  20. package/dist/src/server/terminal/runBlockingShellCommand.d.ts +4 -0
  21. package/dist/src/server/terminal/runBlockingShellCommand.js +34 -0
  22. package/dist/src/server/terminal/runBlockingShellCommand.js.map +1 -0
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +1 -1
  25. package/src/browser/neovim-client.ts +7 -1
  26. package/src/client/terminal-terminal-client.ts +7 -2
  27. package/src/server/cypress-support/contents.ts +5 -0
  28. package/src/server/neovim/index.ts +2 -31
  29. package/src/server/server.ts +22 -19
  30. package/src/server/terminal/index.ts +23 -0
  31. package/src/server/terminal/runBlockingShellCommand.ts +45 -0
  32. package/dist/src/server/server.test.d.ts +0 -1
  33. package/dist/src/server/server.test.js +0 -24
  34. package/dist/src/server/server.test.js.map +0 -1
  35. package/src/server/server.test.ts +0 -34
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tui-sandbox/library",
3
- "version": "9.1.9",
3
+ "version": "9.2.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {
@@ -61,6 +61,7 @@ declare global {
61
61
 
62
62
  export type GenericTerminalBrowserApi = {
63
63
  dir: TestDirectory
64
+ runBlockingShellCommand(input: BlockingCommandClientInput): Promise<BlockingShellCommandOutput>
64
65
  }
65
66
 
66
67
  /** Entrypoint for the test runner (cypress) */
@@ -69,5 +70,10 @@ window.startTerminalApplication = async function (
69
70
  ): Promise<GenericTerminalBrowserApi> {
70
71
  const terminal = terminalClient.get()
71
72
  const testDirectory = await terminal.startTerminalApplication(args)
72
- return { dir: testDirectory }
73
+ return {
74
+ dir: testDirectory,
75
+ runBlockingShellCommand(input) {
76
+ return terminal.runBlockingShellCommand(input)
77
+ },
78
+ }
73
79
  }
@@ -1,9 +1,9 @@
1
1
  import { createTRPCClient, httpBatchLink, splitLink, unstable_httpSubscriptionLink } from "@trpc/client"
2
2
  import type { Terminal } from "@xterm/xterm"
3
3
  import "@xterm/xterm/css/xterm.css"
4
- import type { AppRouter } from "../server/server.js"
4
+ import type { AppRouter, BlockingCommandClientInput } from "../server/server.js"
5
5
  import type { StartTerminalGenericArguments } from "../server/terminal/TerminalTestApplication.js"
6
- import type { TestDirectory } from "../server/types.js"
6
+ import type { BlockingShellCommandOutput, TestDirectory } from "../server/types.js"
7
7
  import "./style.css"
8
8
  import { getTabId, startTerminal } from "./websocket-client.js"
9
9
 
@@ -83,4 +83,9 @@ export class TerminalTerminalClient {
83
83
 
84
84
  return testDirectory
85
85
  }
86
+
87
+ public async runBlockingShellCommand(input: BlockingCommandClientInput): Promise<BlockingShellCommandOutput> {
88
+ await this.ready
89
+ return this.trpc.terminal.runBlockingShellCommand.mutate({ ...input, tabId: this.tabId })
90
+ }
86
91
  }
@@ -35,6 +35,10 @@ export type TerminalTestApplicationContext = {
35
35
  * keystrokes as input. Requires the application to be running. */
36
36
  typeIntoTerminal(text: string, options?: Partial<Cypress.TypeOptions>): void
37
37
 
38
+ /** Runs a shell command in a blocking manner, waiting for the command to
39
+ * finish before returning. Requires the terminal to be running. */
40
+ runBlockingShellCommand(input: BlockingCommandClientInput): Cypress.Chainable<BlockingShellCommandOutput>
41
+
38
42
  /** The test directory, providing type-safe access to its file and directory structure */
39
43
  dir: TestDirectory<MyTestDirectory>
40
44
  }
@@ -168,6 +172,7 @@ afterEach(async () => {
168
172
  await Promise.race([timeout, testNeovim.runExCommand({ command: "messages" })])
169
173
  } finally {
170
174
  clearTimeout(timeoutId) // Ensure the timeout is cleared
175
+ testNeovim = undefined
171
176
  }
172
177
  })
173
178
  `
@@ -1,10 +1,9 @@
1
1
  import assert from "assert"
2
- import { exec } from "child_process"
3
2
  import "core-js/proposals/async-explicit-resource-management.js"
4
3
  import { access } from "fs/promises"
5
4
  import path from "path"
6
- import util from "util"
7
5
  import type { BlockingCommandInput, ExCommandInput, LuaCodeInput } from "../server.js"
6
+ import { executeBlockingShellCommand } from "../terminal/runBlockingShellCommand.js"
8
7
  import type {
9
8
  BlockingShellCommandOutput,
10
9
  RunExCommandOutput,
@@ -124,36 +123,8 @@ export async function runBlockingShellCommand(
124
123
  const testDirectory = neovim.state?.testDirectory
125
124
  assert(testDirectory, `Test directory not found for client id ${input.tabId.tabId}. Maybe neovim's not started yet?`)
126
125
 
127
- const execPromise = util.promisify(exec)
128
126
  const env = neovim.getEnvironmentVariables(testDirectory, input.envOverrides)
129
- const processPromise = execPromise(input.command, {
130
- signal: signal,
131
- shell: input.shell,
132
- uid: input.uid,
133
- gid: input.gid,
134
- cwd: input.cwd ?? env["HOME"],
135
- env,
136
- })
137
-
138
- try {
139
- const result = await processPromise
140
- console.log(
141
- `Successfully ran shell blockingCommand (${input.command}) with stdout: ${result.stdout}, stderr: ${result.stderr}`
142
- )
143
- return {
144
- type: "success",
145
- stdout: result.stdout,
146
- stderr: result.stderr,
147
- } satisfies BlockingShellCommandOutput
148
- } catch (e) {
149
- console.warn(`Error running shell blockingCommand (${input.command})`, e)
150
- if (allowFailure) {
151
- return {
152
- type: "failed",
153
- }
154
- }
155
- throw new Error(`Error running shell blockingCommand (${input.command})`, { cause: e })
156
- }
127
+ return executeBlockingShellCommand(input, signal, allowFailure, env)
157
128
  }
158
129
 
159
130
  export async function runLuaCode(options: LuaCodeInput): Promise<RunLuaCodeOutput> {
@@ -7,7 +7,6 @@ import * as neovim from "./neovim/index.js"
7
7
  import * as terminal from "./terminal/index.js"
8
8
  import { TestServer } from "./TestServer.js"
9
9
  import type { DirectoriesConfig, TestServerConfig } from "./updateTestdirectorySchemaFile.js"
10
- import { applicationAvailable } from "./utilities/applicationAvailable.js"
11
10
  import { tabIdSchema } from "./utilities/tabId.js"
12
11
 
13
12
  const blockingCommandInputSchema = z.object({
@@ -41,10 +40,6 @@ export type ExCommandInput = z.infer<typeof exCommandInputSchema>
41
40
  /** @private */
42
41
  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
43
42
  export async function createAppRouter(config: DirectoriesConfig) {
44
- if (!(await applicationAvailable("nvim"))) {
45
- throw new Error("Neovim is not installed. Please install Neovim (nvim).")
46
- }
47
-
48
43
  const appRouter = trpc.router({
49
44
  terminal: trpc.router({
50
45
  onStdout: trpc.procedure.input(z.object({ client: tabIdSchema })).subscription(options => {
@@ -77,6 +72,10 @@ export async function createAppRouter(config: DirectoriesConfig) {
77
72
  sendStdin: trpc.procedure.input(z.object({ tabId: tabIdSchema, data: z.string() })).mutation(options => {
78
73
  return terminal.sendStdin(options.input)
79
74
  }),
75
+
76
+ runBlockingShellCommand: trpc.procedure.input(blockingCommandInputSchema).mutation(async options => {
77
+ return terminal.runBlockingShellCommand(options.signal, options.input, options.input.allowFailure ?? false)
78
+ }),
80
79
  }),
81
80
 
82
81
  neovim: trpc.router({
@@ -136,23 +135,27 @@ export type AppRouter = Awaited<ReturnType<typeof createAppRouter>>
136
135
  export type RouterInput = inferRouterInputs<AppRouter>
137
136
 
138
137
  export async function startTestServer(config: TestServerConfig): Promise<TestServer> {
139
- const testServer = new TestServer({
140
- port: config.port,
141
- })
142
- const appRouter = await createAppRouter(config.directories)
143
-
144
- const neovimTask: Promise<void> = neovim
145
- .installDependencies(config.directories.testEnvironmentPath, config.directories)
146
- .catch((err: unknown) => {
147
- console.error("Error installing neovim dependencies", err)
138
+ try {
139
+ const testServer = new TestServer({
140
+ port: config.port,
148
141
  })
142
+ const appRouter = await createAppRouter(config.directories)
143
+
144
+ const neovimTask: Promise<void> = neovim
145
+ .installDependencies(config.directories.testEnvironmentPath, config.directories)
146
+ .catch((err: unknown) => {
147
+ console.error("Error installing neovim dependencies", err)
148
+ // suppress the error because neovim is optional - other applications
149
+ // can still be tested
150
+ })
151
+
152
+ const startServerTask = testServer.startAndRun(appRouter)
149
153
 
150
- const startServerTask = testServer.startAndRun(appRouter)
154
+ await Promise.all([neovimTask, startServerTask])
151
155
 
152
- await Promise.all([neovimTask, startServerTask]).catch((err: unknown) => {
156
+ return testServer
157
+ } catch (err: unknown) {
153
158
  console.error("Error starting test server", err)
154
159
  throw err
155
- })
156
-
157
- return testServer
160
+ }
158
161
  }
@@ -2,10 +2,13 @@ import assert from "assert"
2
2
  import "core-js/proposals/async-explicit-resource-management.js"
3
3
  import type { TerminalDimensions } from "../neovim/NeovimApplication.js"
4
4
  import { prepareNewTestDirectory } from "../neovim/prepareNewTestDirectory.js"
5
+ import type { BlockingCommandInput } from "../server.js"
6
+ import type { BlockingShellCommandOutput } from "../types.js"
5
7
  import type { DirectoriesConfig } from "../updateTestdirectorySchemaFile.js"
6
8
  import { convertEventEmitterToAsyncGenerator } from "../utilities/generator.js"
7
9
  import { Lazy } from "../utilities/Lazy.js"
8
10
  import type { TabId } from "../utilities/tabId.js"
11
+ import { executeBlockingShellCommand } from "./runBlockingShellCommand.js"
9
12
  import TerminalTestApplication from "./TerminalTestApplication.js"
10
13
 
11
14
  const terminals = new Map<TabId["tabId"], TerminalTestApplication>()
@@ -60,3 +63,23 @@ export async function sendStdin(options: { tabId: TabId; data: string }): Promis
60
63
 
61
64
  await app.application.write(options.data)
62
65
  }
66
+
67
+ export async function runBlockingShellCommand(
68
+ signal: AbortSignal | undefined,
69
+ input: BlockingCommandInput,
70
+ allowFailure: boolean
71
+ ): Promise<BlockingShellCommandOutput> {
72
+ const tabId = input.tabId.tabId
73
+ const app = terminals.get(tabId)
74
+ assert(app !== undefined, `Terminal instance for clientId not found - cannot send stdin. Maybe it's not started yet?`)
75
+ assert(
76
+ app.application,
77
+ `Terminal application not found for client id ${input.tabId.tabId}. Maybe it's not started yet?`
78
+ )
79
+
80
+ const testDirectory = app.state?.testDirectory
81
+ assert(testDirectory, `Test directory not found for client id ${input.tabId.tabId}. Maybe neovim's not started yet?`)
82
+
83
+ const env = app.getEnvironmentVariables(testDirectory, input.envOverrides)
84
+ return executeBlockingShellCommand(input, signal, allowFailure, env)
85
+ }
@@ -0,0 +1,45 @@
1
+ import { exec } from "child_process"
2
+ import "core-js/proposals/async-explicit-resource-management.js"
3
+ import util from "util"
4
+ import type { BlockingCommandInput } from "../server.js"
5
+ import type { BlockingShellCommandOutput } from "../types.js"
6
+
7
+ export async function executeBlockingShellCommand(
8
+ input: BlockingCommandInput,
9
+ signal: AbortSignal | undefined,
10
+ allowFailure: boolean,
11
+ env: NodeJS.ProcessEnv
12
+ ): Promise<BlockingShellCommandOutput> {
13
+ const execPromise = util.promisify(exec)
14
+
15
+ const cwd = input.cwd ?? env["HOME"]
16
+
17
+ const processPromise = execPromise(input.command, {
18
+ signal: signal,
19
+ shell: input.shell,
20
+ uid: input.uid,
21
+ gid: input.gid,
22
+ cwd: cwd,
23
+ env,
24
+ })
25
+
26
+ try {
27
+ const result = await processPromise
28
+ console.log(
29
+ `Successfully ran shell blockingCommand (${input.command}) with stdout: ${result.stdout}, stderr: ${result.stderr}`
30
+ )
31
+ return {
32
+ type: "success",
33
+ stdout: result.stdout,
34
+ stderr: result.stderr,
35
+ } satisfies BlockingShellCommandOutput
36
+ } catch (e) {
37
+ console.warn(`Error running shell blockingCommand (${input.command})`, e)
38
+ if (allowFailure) {
39
+ return {
40
+ type: "failed",
41
+ }
42
+ }
43
+ throw new Error(`Error running shell blockingCommand (${input.command})`, { cause: e })
44
+ }
45
+ }
@@ -1 +0,0 @@
1
- export {};
@@ -1,24 +0,0 @@
1
- import { createAppRouter } from "./server.js";
2
- import { applicationAvailable } from "./utilities/applicationAvailable.js";
3
- vi.mock("./utilities/applicationAvailable");
4
- const mocked = {
5
- applicationAvailable: vi.mocked(applicationAvailable),
6
- };
7
- describe("Neovim server", () => {
8
- it("complains when neovim is not installed", async () => {
9
- await expect(createAppRouter({
10
- outputFilePath: "outputFilePath",
11
- testEnvironmentPath: "testEnvironmentPath",
12
- })).rejects.toThrow("Neovim is not installed. Please install Neovim (nvim).");
13
- expect(mocked.applicationAvailable).toHaveBeenCalledWith("nvim");
14
- });
15
- it("creates a router when neovim is installed", async () => {
16
- mocked.applicationAvailable.mockResolvedValue("nvim");
17
- await expect(createAppRouter({
18
- outputFilePath: "outputFilePath",
19
- testEnvironmentPath: "testEnvironmentPath",
20
- })).resolves.toBeDefined();
21
- expect(mocked.applicationAvailable).toHaveBeenCalledWith("nvim");
22
- });
23
- });
24
- //# sourceMappingURL=server.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server.test.js","sourceRoot":"","sources":["../../../src/server/server.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAA;AAE1E,EAAE,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAA;AAE3C,MAAM,MAAM,GAAG;IACb,oBAAoB,EAAE,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC;CACtD,CAAA;AAED,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,MAAM,CACV,eAAe,CAAC;YACd,cAAc,EAAE,gBAAgB;YAChC,mBAAmB,EAAE,qBAAqB;SAC3C,CAAC,CACH,CAAC,OAAO,CAAC,OAAO,CAAC,wDAAwD,CAAC,CAAA;QAE3E,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAA;IAClE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAA;QAErD,MAAM,MAAM,CACV,eAAe,CAAC;YACd,cAAc,EAAE,gBAAgB;YAChC,mBAAmB,EAAE,qBAAqB;SAC3C,CAAC,CACH,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAA;QAExB,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAA;IAClE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -1,34 +0,0 @@
1
- import { createAppRouter } from "./server.js"
2
- import { applicationAvailable } from "./utilities/applicationAvailable.js"
3
-
4
- vi.mock("./utilities/applicationAvailable")
5
-
6
- const mocked = {
7
- applicationAvailable: vi.mocked(applicationAvailable),
8
- }
9
-
10
- describe("Neovim server", () => {
11
- it("complains when neovim is not installed", async () => {
12
- await expect(
13
- createAppRouter({
14
- outputFilePath: "outputFilePath",
15
- testEnvironmentPath: "testEnvironmentPath",
16
- })
17
- ).rejects.toThrow("Neovim is not installed. Please install Neovim (nvim).")
18
-
19
- expect(mocked.applicationAvailable).toHaveBeenCalledWith("nvim")
20
- })
21
-
22
- it("creates a router when neovim is installed", async () => {
23
- mocked.applicationAvailable.mockResolvedValue("nvim")
24
-
25
- await expect(
26
- createAppRouter({
27
- outputFilePath: "outputFilePath",
28
- testEnvironmentPath: "testEnvironmentPath",
29
- })
30
- ).resolves.toBeDefined()
31
-
32
- expect(mocked.applicationAvailable).toHaveBeenCalledWith("nvim")
33
- })
34
- })