prool 0.0.3 → 0.0.5
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 +15 -0
- package/_lib/exports/index.d.ts +1 -1
- package/_lib/exports/processes.d.ts +2 -0
- package/_lib/exports/processes.d.ts.map +1 -0
- package/_lib/exports/processes.js +2 -0
- package/_lib/exports/processes.js.map +1 -0
- package/_lib/instance.d.ts +9 -6
- package/_lib/instance.d.ts.map +1 -1
- package/_lib/instance.js +22 -3
- package/_lib/instance.js.map +1 -1
- package/_lib/instances/anvil.d.ts +1 -5
- package/_lib/instances/anvil.d.ts.map +1 -1
- package/_lib/instances/anvil.js +18 -44
- package/_lib/instances/anvil.js.map +1 -1
- package/_lib/instances/stackup.d.ts +86 -0
- package/_lib/instances/stackup.d.ts.map +1 -0
- package/_lib/instances/stackup.js +89 -0
- package/_lib/instances/stackup.js.map +1 -0
- package/_lib/pool.d.ts +7 -6
- package/_lib/pool.d.ts.map +1 -1
- package/_lib/pool.js +60 -53
- package/_lib/pool.js.map +1 -1
- package/_lib/processes/execa.d.ts +27 -0
- package/_lib/processes/execa.d.ts.map +1 -0
- package/_lib/processes/execa.js +63 -0
- package/_lib/processes/execa.js.map +1 -0
- package/_lib/server.d.ts +3 -3
- package/_lib/server.d.ts.map +1 -1
- package/_lib/server.js +42 -34
- package/_lib/server.js.map +1 -1
- package/_lib/utils.d.ts +4 -3
- package/_lib/utils.d.ts.map +1 -1
- package/_lib/utils.js +2 -2
- package/_lib/utils.js.map +1 -1
- package/exports/index.ts +2 -2
- package/exports/processes.test.ts +10 -0
- package/exports/processes.ts +8 -0
- package/instance.test.ts +34 -2
- package/instance.ts +46 -13
- package/instances/anvil.test.ts +5 -3
- package/instances/anvil.ts +21 -49
- package/instances/stackup.test.ts +106 -0
- package/instances/stackup.ts +162 -0
- package/package.json +6 -2
- package/pool.test.ts +203 -138
- package/pool.ts +83 -66
- package/processes/execa.test.ts +143 -0
- package/processes/execa.ts +100 -0
- package/server.test.ts +162 -85
- package/server.ts +46 -38
- package/tsconfig.build.tsbuildinfo +1 -1
- package/utils.ts +11 -9
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { EventEmitter } from 'eventemitter3'
|
|
2
|
+
import { afterEach, expect, test } from 'vitest'
|
|
3
|
+
import { type ExecaProcess, execa } from './execa.js'
|
|
4
|
+
|
|
5
|
+
const processes: ExecaProcess[] = []
|
|
6
|
+
function createProcess() {
|
|
7
|
+
const process = execa({ name: 'foo' })
|
|
8
|
+
processes.push(process)
|
|
9
|
+
return process
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
for (const process of processes) await process.stop().catch(() => {})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('default', async () => {
|
|
17
|
+
const process = createProcess()
|
|
18
|
+
expect(process).toMatchInlineSnapshot(`
|
|
19
|
+
{
|
|
20
|
+
"_internal": {
|
|
21
|
+
"process": undefined,
|
|
22
|
+
},
|
|
23
|
+
"name": "foo",
|
|
24
|
+
"start": [Function],
|
|
25
|
+
"stop": [Function],
|
|
26
|
+
}
|
|
27
|
+
`)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('start', async () => {
|
|
31
|
+
const emitter = new EventEmitter<any>()
|
|
32
|
+
const process = createProcess()
|
|
33
|
+
|
|
34
|
+
const resolvers = {
|
|
35
|
+
listening: Promise.withResolvers<void>(),
|
|
36
|
+
message: Promise.withResolvers<void>(),
|
|
37
|
+
stdout: Promise.withResolvers<void>(),
|
|
38
|
+
}
|
|
39
|
+
emitter.on('listening', resolvers.listening.resolve)
|
|
40
|
+
emitter.on('message', resolvers.message.resolve)
|
|
41
|
+
emitter.on('stdout', resolvers.stdout.resolve)
|
|
42
|
+
|
|
43
|
+
await process.start(($) => $`anvil --port 1337`, {
|
|
44
|
+
emitter,
|
|
45
|
+
status: 'idle',
|
|
46
|
+
resolver({ process, resolve }) {
|
|
47
|
+
process.stdout.on('data', (data) => {
|
|
48
|
+
const message = data.toString()
|
|
49
|
+
if (message.includes('Listening on')) resolve()
|
|
50
|
+
})
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
expect(process._internal.process).toBeDefined()
|
|
54
|
+
await expect(resolvers.listening.promise).resolves.toBeUndefined()
|
|
55
|
+
await expect(resolvers.message.promise).resolves.toBeDefined()
|
|
56
|
+
await expect(resolvers.stdout.promise).resolves.toBeDefined()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('start (error)', async () => {
|
|
60
|
+
const emitter = new EventEmitter<any>()
|
|
61
|
+
const process = createProcess()
|
|
62
|
+
|
|
63
|
+
const resolvers = {
|
|
64
|
+
listening: Promise.withResolvers<void>(),
|
|
65
|
+
message: Promise.withResolvers<void>(),
|
|
66
|
+
stderr: Promise.withResolvers<void>(),
|
|
67
|
+
}
|
|
68
|
+
emitter.on('listening', resolvers.listening.resolve)
|
|
69
|
+
emitter.on('message', resolvers.message.resolve)
|
|
70
|
+
emitter.on('stderr', resolvers.stderr.resolve)
|
|
71
|
+
|
|
72
|
+
// Invalid argument
|
|
73
|
+
await expect(() =>
|
|
74
|
+
process.start(($) => $`anvil --lol`, {
|
|
75
|
+
emitter,
|
|
76
|
+
status: 'idle',
|
|
77
|
+
resolver({ process, reject, resolve }) {
|
|
78
|
+
process.stdout.on('data', (data) => {
|
|
79
|
+
const message = data.toString()
|
|
80
|
+
if (message.includes('Listening on')) resolve()
|
|
81
|
+
})
|
|
82
|
+
process.stderr.on('data', reject)
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
|
86
|
+
[Error: Failed to start process "foo": error: unexpected argument '--lol' found
|
|
87
|
+
|
|
88
|
+
Usage: anvil [OPTIONS] [COMMAND]
|
|
89
|
+
|
|
90
|
+
For more information, try '--help'.
|
|
91
|
+
]
|
|
92
|
+
`)
|
|
93
|
+
await expect(resolvers.message.promise).resolves.toBeDefined()
|
|
94
|
+
await expect(resolvers.stderr.promise).resolves.toBeDefined()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('behavior: exit', async () => {
|
|
98
|
+
const emitter = new EventEmitter<any>()
|
|
99
|
+
const process = createProcess()
|
|
100
|
+
|
|
101
|
+
const resolvers = {
|
|
102
|
+
exit: Promise.withResolvers<void>(),
|
|
103
|
+
}
|
|
104
|
+
emitter.on('exit', resolvers.exit.resolve)
|
|
105
|
+
|
|
106
|
+
// Invalid argument
|
|
107
|
+
await process.start(($) => $`anvil --port 1338`, {
|
|
108
|
+
emitter,
|
|
109
|
+
status: 'idle',
|
|
110
|
+
resolver({ process, resolve }) {
|
|
111
|
+
process.stdout.on('data', (data) => {
|
|
112
|
+
const message = data.toString()
|
|
113
|
+
if (message.includes('Listening on')) resolve()
|
|
114
|
+
})
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
process._internal.process.kill()
|
|
118
|
+
await expect(resolvers.exit.promise).resolves.toBeDefined()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('behavior: exit when status is starting', async () => {
|
|
122
|
+
const emitter = new EventEmitter<any>()
|
|
123
|
+
const process = createProcess()
|
|
124
|
+
|
|
125
|
+
const resolvers = {
|
|
126
|
+
exit: Promise.withResolvers<void>(),
|
|
127
|
+
}
|
|
128
|
+
emitter.on('exit', resolvers.exit.resolve)
|
|
129
|
+
|
|
130
|
+
// Invalid argument
|
|
131
|
+
await process.start(($) => $`anvil`, {
|
|
132
|
+
emitter,
|
|
133
|
+
status: 'starting',
|
|
134
|
+
resolver({ process, resolve }) {
|
|
135
|
+
process.stdout.on('data', (data) => {
|
|
136
|
+
const message = data.toString()
|
|
137
|
+
if (message.includes('Listening on')) resolve()
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
process._internal.process.kill()
|
|
142
|
+
await expect(resolvers.exit.promise).resolves.toBeDefined()
|
|
143
|
+
})
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type ResultPromise, execa as exec } from 'execa'
|
|
2
|
+
import type { InstanceStartOptions_internal } from '../instance.js'
|
|
3
|
+
import { stripColors } from '../utils.js'
|
|
4
|
+
|
|
5
|
+
export type Process_internal = ResultPromise<{ cleanup: true; reject: false }>
|
|
6
|
+
|
|
7
|
+
export type ExecaStartOptions = InstanceStartOptions_internal & {
|
|
8
|
+
resolver(options: {
|
|
9
|
+
process: Process_internal
|
|
10
|
+
reject(data: string): Promise<void>
|
|
11
|
+
resolve(): void
|
|
12
|
+
}): void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ExecaParameters = { name: string }
|
|
16
|
+
|
|
17
|
+
export type ExecaProcess = {
|
|
18
|
+
_internal: {
|
|
19
|
+
process: Process_internal
|
|
20
|
+
}
|
|
21
|
+
name: string
|
|
22
|
+
start(
|
|
23
|
+
command: (x: typeof exec) => void,
|
|
24
|
+
options: ExecaStartOptions,
|
|
25
|
+
): Promise<void>
|
|
26
|
+
stop(): Promise<void>
|
|
27
|
+
}
|
|
28
|
+
export type ExecaReturnType = ExecaProcess
|
|
29
|
+
|
|
30
|
+
export function execa(parameters: ExecaParameters): ExecaReturnType {
|
|
31
|
+
const { name } = parameters
|
|
32
|
+
|
|
33
|
+
let process: Process_internal
|
|
34
|
+
|
|
35
|
+
async function stop() {
|
|
36
|
+
const killed = process.kill()
|
|
37
|
+
if (!killed) return
|
|
38
|
+
return new Promise((resolve) => process.on('close', resolve))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
_internal: {
|
|
43
|
+
get process() {
|
|
44
|
+
return process
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
name,
|
|
48
|
+
start(command, { emitter, resolver, status }) {
|
|
49
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>()
|
|
50
|
+
|
|
51
|
+
process = command(
|
|
52
|
+
exec({
|
|
53
|
+
cleanup: true,
|
|
54
|
+
reject: false,
|
|
55
|
+
}) as any,
|
|
56
|
+
) as unknown as Process_internal
|
|
57
|
+
|
|
58
|
+
resolver({
|
|
59
|
+
process,
|
|
60
|
+
async reject(data) {
|
|
61
|
+
await stop()
|
|
62
|
+
reject(
|
|
63
|
+
new Error(`Failed to start process "${name}": ${data.toString()}`),
|
|
64
|
+
)
|
|
65
|
+
},
|
|
66
|
+
resolve() {
|
|
67
|
+
emitter.emit('listening')
|
|
68
|
+
return resolve()
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
process.stdout.on('data', (data) => {
|
|
73
|
+
const message = stripColors(data.toString())
|
|
74
|
+
emitter.emit('message', message)
|
|
75
|
+
emitter.emit('stdout', message)
|
|
76
|
+
})
|
|
77
|
+
process.stderr.on('data', async (data) => {
|
|
78
|
+
const message = stripColors(data.toString())
|
|
79
|
+
emitter.emit('message', message)
|
|
80
|
+
emitter.emit('stderr', message)
|
|
81
|
+
})
|
|
82
|
+
process.on('close', () => process.removeAllListeners())
|
|
83
|
+
process.on('exit', (code, signal) => {
|
|
84
|
+
emitter.emit('exit', code, signal)
|
|
85
|
+
|
|
86
|
+
if (!code) {
|
|
87
|
+
process.removeAllListeners()
|
|
88
|
+
if (status === 'starting')
|
|
89
|
+
reject(new Error(`Failed to start process "${name}": exited.`))
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return promise
|
|
94
|
+
},
|
|
95
|
+
async stop() {
|
|
96
|
+
process.removeAllListeners()
|
|
97
|
+
await stop()
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
}
|
package/server.test.ts
CHANGED
|
@@ -1,116 +1,123 @@
|
|
|
1
|
-
import
|
|
1
|
+
import getPort from 'get-port'
|
|
2
|
+
import { beforeAll, describe, expect, test } from 'vitest'
|
|
2
3
|
import { type MessageEvent, WebSocket } from 'ws'
|
|
4
|
+
import { stackupOptions } from '../test/utils.js'
|
|
3
5
|
import { anvil } from './instances/anvil.js'
|
|
6
|
+
import { stackup } from './instances/stackup.js'
|
|
4
7
|
import { createServer } from './server.js'
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
'instance: $instance.name',
|
|
8
|
-
({ instance }) => {
|
|
9
|
-
test('default', async () => {
|
|
10
|
-
const pool = createServer({
|
|
11
|
-
instance,
|
|
12
|
-
})
|
|
13
|
-
expect(pool).toBeDefined()
|
|
9
|
+
const port = await getPort()
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
expect(pool.address()).toBeDefined()
|
|
11
|
+
beforeAll(() => anvil({ port }).start())
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
describe.each([
|
|
14
|
+
{ instance: anvil() },
|
|
15
|
+
{
|
|
16
|
+
instance: stackup(stackupOptions({ port })),
|
|
17
|
+
},
|
|
18
|
+
])('instance: $instance.name', ({ instance }) => {
|
|
19
|
+
test('default', async () => {
|
|
20
|
+
const server = createServer({
|
|
21
|
+
instance,
|
|
22
|
+
})
|
|
23
|
+
expect(server).toBeDefined()
|
|
24
|
+
|
|
25
|
+
await server.start()
|
|
26
|
+
expect(server.address()).toBeDefined()
|
|
27
|
+
|
|
28
|
+
// Stop via instance method.
|
|
29
|
+
await server.stop()
|
|
30
|
+
expect(server.address()).toBeNull()
|
|
31
|
+
|
|
32
|
+
const stop = await server.start()
|
|
33
|
+
expect(server.address()).toBeDefined()
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
// Stop via return value.
|
|
36
|
+
await stop()
|
|
37
|
+
expect(server.address()).toBeNull()
|
|
38
|
+
})
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
test('args: port', async () => {
|
|
41
|
+
const server = createServer({
|
|
42
|
+
instance,
|
|
43
|
+
port: 3000,
|
|
28
44
|
})
|
|
45
|
+
expect(server).toBeDefined()
|
|
29
46
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
})
|
|
35
|
-
expect(pool).toBeDefined()
|
|
47
|
+
const stop = await server.start()
|
|
48
|
+
expect(server.address()?.port).toBe(3000)
|
|
49
|
+
await stop()
|
|
50
|
+
})
|
|
36
51
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
52
|
+
test('args: host', async () => {
|
|
53
|
+
const server = createServer({
|
|
54
|
+
instance,
|
|
55
|
+
host: 'localhost',
|
|
56
|
+
port: 3000,
|
|
40
57
|
})
|
|
58
|
+
expect(server).toBeDefined()
|
|
41
59
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
expect(pool.address()?.address).toBe('::1')
|
|
52
|
-
expect(pool.address()?.port).toBe(3000)
|
|
53
|
-
await stop()
|
|
60
|
+
const stop = await server.start()
|
|
61
|
+
expect(server.address()?.address).toBe('::1')
|
|
62
|
+
expect(server.address()?.port).toBe(3000)
|
|
63
|
+
await stop()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('request: /healthcheck', async () => {
|
|
67
|
+
const server = createServer({
|
|
68
|
+
instance,
|
|
54
69
|
})
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
const stop = await server.start()
|
|
72
|
+
const { port } = server.address()!
|
|
73
|
+
const response = await fetch(`http://localhost:${port}/healthcheck`)
|
|
74
|
+
expect(response.status).toBe(200)
|
|
60
75
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const response = await fetch(`http://localhost:${port}/healthcheck`)
|
|
64
|
-
expect(response.status).toBe(200)
|
|
76
|
+
await stop()
|
|
77
|
+
})
|
|
65
78
|
|
|
66
|
-
|
|
79
|
+
test('request: /start + /stop', async () => {
|
|
80
|
+
const server = createServer({
|
|
81
|
+
instance,
|
|
67
82
|
})
|
|
68
83
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
84
|
+
const stop = await server.start()
|
|
85
|
+
const { port } = server.address()!
|
|
86
|
+
const response = await fetch(`http://localhost:${port}/1/start`)
|
|
87
|
+
expect(response.status).toBe(200)
|
|
73
88
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
expect(response.status).toBe(200)
|
|
89
|
+
const json = (await response.json()) as any
|
|
90
|
+
expect(json.host).toBeDefined()
|
|
91
|
+
expect(json.port).toBeDefined()
|
|
78
92
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
const response_err = await fetch(`http://localhost:${port}/1/start`)
|
|
94
|
+
expect(response_err.status).toBe(400)
|
|
95
|
+
expect(await response_err.json()).toEqual({
|
|
96
|
+
message: `Instance "${instance.name}" is not in an idle or stopped state. Status: started`,
|
|
97
|
+
})
|
|
82
98
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
expect(await response_err.json()).toEqual({
|
|
86
|
-
message: `Instance "${instance.name}" is not in an idle or stopped state. Status: started`,
|
|
87
|
-
})
|
|
99
|
+
const response_stop = await fetch(`http://localhost:${port}/1/stop`)
|
|
100
|
+
expect(response_stop.status).toBe(200)
|
|
88
101
|
|
|
89
|
-
|
|
90
|
-
|
|
102
|
+
const response_2 = await fetch(`http://localhost:${port}/1/start`)
|
|
103
|
+
expect(response_2.status).toBe(200)
|
|
91
104
|
|
|
92
|
-
|
|
93
|
-
|
|
105
|
+
await stop()
|
|
106
|
+
})
|
|
94
107
|
|
|
95
|
-
|
|
108
|
+
test('request: /restart', async () => {
|
|
109
|
+
const server = createServer({
|
|
110
|
+
instance,
|
|
96
111
|
})
|
|
97
112
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const stop = await server.start()
|
|
104
|
-
const { port } = server.address()!
|
|
105
|
-
const ws = new WebSocket(`ws://localhost:${port}/1`)
|
|
106
|
-
await new Promise((resolve) => ws.addEventListener('open', resolve))
|
|
107
|
-
ws.send('test')
|
|
108
|
-
await new Promise((resolve) => ws.addEventListener('message', resolve))
|
|
113
|
+
const stop = await server.start()
|
|
114
|
+
const { port } = server.address()!
|
|
115
|
+
const response = await fetch(`http://localhost:${port}/1/restart`)
|
|
116
|
+
expect(response.status).toBe(200)
|
|
109
117
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
)
|
|
118
|
+
await stop()
|
|
119
|
+
})
|
|
120
|
+
})
|
|
114
121
|
|
|
115
122
|
describe("instance: 'anvil'", () => {
|
|
116
123
|
test('request: /{id}', async () => {
|
|
@@ -224,6 +231,76 @@ describe("instance: 'anvil'", () => {
|
|
|
224
231
|
await stop()
|
|
225
232
|
})
|
|
226
233
|
|
|
234
|
+
test('request: /restart', async () => {
|
|
235
|
+
const server = createServer({
|
|
236
|
+
instance: anvil(),
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
const stop = await server.start()
|
|
240
|
+
const { port } = server.address()!
|
|
241
|
+
|
|
242
|
+
// Mine block number
|
|
243
|
+
await fetch(`http://localhost:${port}/1`, {
|
|
244
|
+
body: JSON.stringify({
|
|
245
|
+
method: 'anvil_mine',
|
|
246
|
+
params: ['0x69', '0x0'],
|
|
247
|
+
id: 0,
|
|
248
|
+
jsonrpc: '2.0',
|
|
249
|
+
}),
|
|
250
|
+
headers: {
|
|
251
|
+
'Content-Type': 'application/json',
|
|
252
|
+
},
|
|
253
|
+
method: 'POST',
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
// Check block numbers
|
|
257
|
+
expect(
|
|
258
|
+
await fetch(`http://localhost:${port}/1`, {
|
|
259
|
+
body: JSON.stringify({
|
|
260
|
+
method: 'eth_blockNumber',
|
|
261
|
+
id: 0,
|
|
262
|
+
jsonrpc: '2.0',
|
|
263
|
+
}),
|
|
264
|
+
headers: {
|
|
265
|
+
'Content-Type': 'application/json',
|
|
266
|
+
},
|
|
267
|
+
method: 'POST',
|
|
268
|
+
}).then((x) => x.json()),
|
|
269
|
+
).toMatchInlineSnapshot(`
|
|
270
|
+
{
|
|
271
|
+
"id": 0,
|
|
272
|
+
"jsonrpc": "2.0",
|
|
273
|
+
"result": "0x69",
|
|
274
|
+
}
|
|
275
|
+
`)
|
|
276
|
+
|
|
277
|
+
// Restart
|
|
278
|
+
await fetch(`http://localhost:${port}/1/restart`)
|
|
279
|
+
|
|
280
|
+
// Check block numbers
|
|
281
|
+
expect(
|
|
282
|
+
await fetch(`http://localhost:${port}/1`, {
|
|
283
|
+
body: JSON.stringify({
|
|
284
|
+
method: 'eth_blockNumber',
|
|
285
|
+
id: 0,
|
|
286
|
+
jsonrpc: '2.0',
|
|
287
|
+
}),
|
|
288
|
+
headers: {
|
|
289
|
+
'Content-Type': 'application/json',
|
|
290
|
+
},
|
|
291
|
+
method: 'POST',
|
|
292
|
+
}).then((x) => x.json()),
|
|
293
|
+
).toMatchInlineSnapshot(`
|
|
294
|
+
{
|
|
295
|
+
"id": 0,
|
|
296
|
+
"jsonrpc": "2.0",
|
|
297
|
+
"result": "0x0",
|
|
298
|
+
}
|
|
299
|
+
`)
|
|
300
|
+
|
|
301
|
+
await stop()
|
|
302
|
+
})
|
|
303
|
+
|
|
227
304
|
test('request: /messages', async () => {
|
|
228
305
|
const server = createServer({
|
|
229
306
|
instance: anvil(),
|
package/server.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { extractPath } from './utils.js'
|
|
|
12
12
|
|
|
13
13
|
const { createProxyServer } = httpProxy
|
|
14
14
|
|
|
15
|
-
export type
|
|
15
|
+
export type CreateServerParameters = DefinePoolParameters<number> &
|
|
16
16
|
(
|
|
17
17
|
| {
|
|
18
18
|
/** Host to run the server on. */
|
|
@@ -26,7 +26,7 @@ export type createServerParameters = DefinePoolParameters &
|
|
|
26
26
|
}
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
-
export type
|
|
29
|
+
export type CreateServerReturnType = Omit<
|
|
30
30
|
Server<typeof IncomingMessage, typeof ServerResponse>,
|
|
31
31
|
'address'
|
|
32
32
|
> & {
|
|
@@ -36,8 +36,8 @@ export type createServerReturnType = Omit<
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export function createServer(
|
|
39
|
-
parameters:
|
|
40
|
-
):
|
|
39
|
+
parameters: CreateServerParameters,
|
|
40
|
+
): CreateServerReturnType {
|
|
41
41
|
const { host = '::', instance, limit, port } = parameters
|
|
42
42
|
|
|
43
43
|
const pool = definePool({ instance, limit })
|
|
@@ -56,44 +56,46 @@ export function createServer(
|
|
|
56
56
|
|
|
57
57
|
const { id, path } = extractPath(url)
|
|
58
58
|
|
|
59
|
-
if (typeof id === 'number'
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return
|
|
59
|
+
if (typeof id === 'number') {
|
|
60
|
+
if (path === '/') {
|
|
61
|
+
const { host, port } = pool.get(id) || (await pool.start(id))
|
|
62
|
+
return proxy.web(request, response, {
|
|
63
|
+
target: `http://${host}:${port}`,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
if (path === '/start') {
|
|
67
|
+
const { host, port } = await pool.start(id)
|
|
68
|
+
return done(response, 200, { host, port })
|
|
69
|
+
}
|
|
70
|
+
if (path === '/stop') {
|
|
71
|
+
await pool.stop(id)
|
|
72
|
+
return done(response, 200)
|
|
73
|
+
}
|
|
74
|
+
if (path === '/restart') {
|
|
75
|
+
await pool.restart(id)
|
|
76
|
+
return done(response, 200)
|
|
77
|
+
}
|
|
78
|
+
if (path === '/messages') {
|
|
79
|
+
const messages = pool.get(id)?.messages.get() || []
|
|
80
|
+
return done(response, 200, messages)
|
|
81
|
+
}
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
if (path === '/healthcheck')
|
|
86
|
-
response.writeHead(200, { 'Content-Type': 'application/json' }).end()
|
|
87
|
-
return
|
|
88
|
-
}
|
|
84
|
+
if (path === '/healthcheck') return done(response, 200)
|
|
89
85
|
|
|
90
|
-
response
|
|
91
|
-
return
|
|
86
|
+
return done(response, 404)
|
|
92
87
|
} catch (error) {
|
|
93
|
-
response
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
return done(response, 400, { message: (error as Error).message })
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
proxy.on('proxyReq', (proxyReq, req) => {
|
|
93
|
+
;(req as any)._proxyReq = proxyReq
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
proxy.on('error', (err, req) => {
|
|
97
|
+
if (req.socket.destroyed && (err as any).code === 'ECONNRESET') {
|
|
98
|
+
;(req as any)._proxyReq.abort()
|
|
97
99
|
}
|
|
98
100
|
})
|
|
99
101
|
|
|
@@ -135,3 +137,9 @@ export function createServer(
|
|
|
135
137
|
},
|
|
136
138
|
})
|
|
137
139
|
}
|
|
140
|
+
|
|
141
|
+
function done(res: ServerResponse, statusCode: number, json?: unknown) {
|
|
142
|
+
return res
|
|
143
|
+
.writeHead(statusCode, { 'Content-Type': 'application/json' })
|
|
144
|
+
.end(json ? JSON.stringify(json) : undefined)
|
|
145
|
+
}
|