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.
Files changed (52) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/_lib/exports/index.d.ts +1 -1
  3. package/_lib/exports/processes.d.ts +2 -0
  4. package/_lib/exports/processes.d.ts.map +1 -0
  5. package/_lib/exports/processes.js +2 -0
  6. package/_lib/exports/processes.js.map +1 -0
  7. package/_lib/instance.d.ts +9 -6
  8. package/_lib/instance.d.ts.map +1 -1
  9. package/_lib/instance.js +22 -3
  10. package/_lib/instance.js.map +1 -1
  11. package/_lib/instances/anvil.d.ts +1 -5
  12. package/_lib/instances/anvil.d.ts.map +1 -1
  13. package/_lib/instances/anvil.js +18 -44
  14. package/_lib/instances/anvil.js.map +1 -1
  15. package/_lib/instances/stackup.d.ts +86 -0
  16. package/_lib/instances/stackup.d.ts.map +1 -0
  17. package/_lib/instances/stackup.js +89 -0
  18. package/_lib/instances/stackup.js.map +1 -0
  19. package/_lib/pool.d.ts +7 -6
  20. package/_lib/pool.d.ts.map +1 -1
  21. package/_lib/pool.js +60 -53
  22. package/_lib/pool.js.map +1 -1
  23. package/_lib/processes/execa.d.ts +27 -0
  24. package/_lib/processes/execa.d.ts.map +1 -0
  25. package/_lib/processes/execa.js +63 -0
  26. package/_lib/processes/execa.js.map +1 -0
  27. package/_lib/server.d.ts +3 -3
  28. package/_lib/server.d.ts.map +1 -1
  29. package/_lib/server.js +42 -34
  30. package/_lib/server.js.map +1 -1
  31. package/_lib/utils.d.ts +4 -3
  32. package/_lib/utils.d.ts.map +1 -1
  33. package/_lib/utils.js +2 -2
  34. package/_lib/utils.js.map +1 -1
  35. package/exports/index.ts +2 -2
  36. package/exports/processes.test.ts +10 -0
  37. package/exports/processes.ts +8 -0
  38. package/instance.test.ts +34 -2
  39. package/instance.ts +46 -13
  40. package/instances/anvil.test.ts +5 -3
  41. package/instances/anvil.ts +21 -49
  42. package/instances/stackup.test.ts +106 -0
  43. package/instances/stackup.ts +162 -0
  44. package/package.json +6 -2
  45. package/pool.test.ts +203 -138
  46. package/pool.ts +83 -66
  47. package/processes/execa.test.ts +143 -0
  48. package/processes/execa.ts +100 -0
  49. package/server.test.ts +162 -85
  50. package/server.ts +46 -38
  51. package/tsconfig.build.tsbuildinfo +1 -1
  52. 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 { describe, expect, test } from 'vitest'
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
- describe.each([{ instance: anvil() }])(
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
- await pool.start()
16
- expect(pool.address()).toBeDefined()
11
+ beforeAll(() => anvil({ port }).start())
17
12
 
18
- // Stop via instance method.
19
- await pool.stop()
20
- expect(pool.address()).toBeNull()
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
- const stop = await pool.start()
23
- expect(pool.address()).toBeDefined()
35
+ // Stop via return value.
36
+ await stop()
37
+ expect(server.address()).toBeNull()
38
+ })
24
39
 
25
- // Stop via return value.
26
- await stop()
27
- expect(pool.address()).toBeNull()
40
+ test('args: port', async () => {
41
+ const server = createServer({
42
+ instance,
43
+ port: 3000,
28
44
  })
45
+ expect(server).toBeDefined()
29
46
 
30
- test('args: port', async () => {
31
- const pool = createServer({
32
- instance,
33
- port: 3000,
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
- const stop = await pool.start()
38
- expect(pool.address()?.port).toBe(3000)
39
- await stop()
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
- test('args: host', async () => {
43
- const pool = createServer({
44
- instance,
45
- host: 'localhost',
46
- port: 3000,
47
- })
48
- expect(pool).toBeDefined()
49
-
50
- const stop = await pool.start()
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
- test('request: /healthcheck', async () => {
57
- const server = createServer({
58
- instance,
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
- const stop = await server.start()
62
- const { port } = server.address()!
63
- const response = await fetch(`http://localhost:${port}/healthcheck`)
64
- expect(response.status).toBe(200)
76
+ await stop()
77
+ })
65
78
 
66
- await stop()
79
+ test('request: /start + /stop', async () => {
80
+ const server = createServer({
81
+ instance,
67
82
  })
68
83
 
69
- test('request: /start + /stop', async () => {
70
- const server = createServer({
71
- instance,
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
- const stop = await server.start()
75
- const { port } = server.address()!
76
- const response = await fetch(`http://localhost:${port}/1/start`)
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
- const json = (await response.json()) as any
80
- expect(json.host).toBeDefined()
81
- expect(json.port).toBeDefined()
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
- const response_err = await fetch(`http://localhost:${port}/1/start`)
84
- expect(response_err.status).toBe(400)
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
- const response_stop = await fetch(`http://localhost:${port}/1/stop`)
90
- expect(response_stop.status).toBe(200)
102
+ const response_2 = await fetch(`http://localhost:${port}/1/start`)
103
+ expect(response_2.status).toBe(200)
91
104
 
92
- const response_2 = await fetch(`http://localhost:${port}/1/start`)
93
- expect(response_2.status).toBe(200)
105
+ await stop()
106
+ })
94
107
 
95
- await stop()
108
+ test('request: /restart', async () => {
109
+ const server = createServer({
110
+ instance,
96
111
  })
97
112
 
98
- test('ws', async () => {
99
- const server = createServer({
100
- instance,
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
- await stop()
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 createServerParameters = DefinePoolParameters &
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 createServerReturnType = Omit<
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: createServerParameters,
40
- ): createServerReturnType {
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' && path === '/') {
60
- const { host, port } = pool.get(id) || (await pool.start(id))
61
- return proxy.web(request, response, {
62
- target: `http://${host}:${port}`,
63
- })
64
- }
65
- if (typeof id === 'number' && path === '/start') {
66
- const { host, port } = await pool.start(id)
67
- response
68
- .writeHead(200, { 'Content-Type': 'application/json' })
69
- .end(JSON.stringify({ host, port }))
70
- return
71
- }
72
- if (typeof id === 'number' && path === '/stop') {
73
- await pool.stop(id)
74
- response.writeHead(200, { 'Content-Type': 'application/json' }).end()
75
- return
76
- }
77
- if (typeof id === 'number' && path === '/messages') {
78
- const messages = pool.get(id)?.messages.get() || []
79
- response
80
- .writeHead(200, { 'Content-Type': 'application/json' })
81
- .end(JSON.stringify(messages))
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.writeHead(404, { 'Content-Type': 'application/json' }).end()
91
- return
86
+ return done(response, 404)
92
87
  } catch (error) {
93
- response
94
- .writeHead(400, { 'Content-Type': 'application/json' })
95
- .end(JSON.stringify({ message: (error as Error).message }))
96
- return
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
+ }