prool 0.0.25 → 0.1.1
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/{LICENSE → LICENSE.md} +1 -1
- package/README.md +46 -163
- package/{_lib/instance.d.ts → dist/Instance.d.ts} +28 -25
- package/dist/Instance.d.ts.map +1 -0
- package/{_lib/instance.js → dist/Instance.js} +9 -4
- package/dist/Instance.js.map +1 -0
- package/{_lib/pool.d.ts → dist/Pool.d.ts} +15 -13
- package/dist/Pool.d.ts.map +1 -0
- package/{_lib/pool.js → dist/Pool.js} +3 -3
- package/dist/Pool.js.map +1 -0
- package/{_lib/server.d.ts → dist/Server.d.ts} +7 -10
- package/dist/Server.d.ts.map +1 -0
- package/{_lib/server.js → dist/Server.js} +8 -9
- package/dist/Server.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/instances/alto.d.ts +452 -0
- package/dist/instances/alto.d.ts.map +1 -0
- package/{_lib → dist}/instances/alto.js +4 -4
- package/dist/instances/alto.js.map +1 -0
- package/dist/instances/anvil.d.ts +458 -0
- package/dist/instances/anvil.d.ts.map +1 -0
- package/{_lib → dist}/instances/anvil.js +4 -4
- package/dist/instances/anvil.js.map +1 -0
- package/dist/instances/tempo.d.ts +189 -0
- package/dist/instances/tempo.d.ts.map +1 -0
- package/dist/instances/tempo.js +200 -0
- package/dist/instances/tempo.js.map +1 -0
- package/{_lib → dist/internal}/utils.d.ts +5 -5
- package/dist/internal/utils.d.ts.map +1 -0
- package/{_lib → dist/internal}/utils.js +37 -7
- package/dist/internal/utils.js.map +1 -0
- package/{_lib → dist}/processes/execa.d.ts +11 -10
- package/dist/processes/execa.d.ts.map +1 -0
- package/{_lib → dist}/processes/execa.js +1 -1
- package/dist/processes/execa.js.map +1 -0
- package/package.json +17 -16
- package/{instance.test.ts → src/Instance.test.ts} +12 -12
- package/{instance.ts → src/Instance.ts} +51 -42
- package/{pool.test.ts → src/Pool.test.ts} +41 -55
- package/{pool.ts → src/Pool.ts} +21 -19
- package/{server.test.ts → src/Server.test.ts} +26 -149
- package/{server.ts → src/Server.ts} +9 -11
- package/src/index.ts +3 -0
- package/{instances → src/instances}/alto.test.ts +8 -11
- package/src/instances/alto.ts +289 -0
- package/{instances → src/instances}/anvil.test.ts +5 -6
- package/src/instances/anvil.ts +306 -0
- package/{instances/silius.test.ts → src/instances/tempo.test.ts} +20 -44
- package/src/instances/tempo.ts +322 -0
- package/{utils.test.ts → src/internal/utils.test.ts} +20 -6
- package/{utils.ts → src/internal/utils.ts} +47 -17
- package/{processes → src/processes}/execa.test.ts +6 -11
- package/{processes → src/processes}/execa.ts +19 -15
- package/src/processes/index.ts +1 -0
- package/CHANGELOG.md +0 -154
- package/_lib/exports/index.d.ts +0 -5
- package/_lib/exports/index.d.ts.map +0 -1
- package/_lib/exports/index.js +0 -5
- package/_lib/exports/index.js.map +0 -1
- package/_lib/exports/instances.d.ts +0 -7
- package/_lib/exports/instances.d.ts.map +0 -1
- package/_lib/exports/instances.js +0 -7
- package/_lib/exports/instances.js.map +0 -1
- package/_lib/exports/processes.d.ts +0 -2
- package/_lib/exports/processes.d.ts.map +0 -1
- package/_lib/exports/processes.js +0 -2
- package/_lib/exports/processes.js.map +0 -1
- package/_lib/instance.d.ts.map +0 -1
- package/_lib/instance.js.map +0 -1
- package/_lib/instances/alto.d.ts +0 -449
- package/_lib/instances/alto.d.ts.map +0 -1
- package/_lib/instances/alto.js.map +0 -1
- package/_lib/instances/anvil.d.ts +0 -455
- package/_lib/instances/anvil.d.ts.map +0 -1
- package/_lib/instances/anvil.js.map +0 -1
- package/_lib/instances/rundler.d.ts +0 -604
- package/_lib/instances/rundler.d.ts.map +0 -1
- package/_lib/instances/rundler.js +0 -86
- package/_lib/instances/rundler.js.map +0 -1
- package/_lib/instances/silius.d.ts +0 -224
- package/_lib/instances/silius.d.ts.map +0 -1
- package/_lib/instances/silius.js +0 -117
- package/_lib/instances/silius.js.map +0 -1
- package/_lib/instances/stackup.d.ts +0 -86
- package/_lib/instances/stackup.d.ts.map +0 -1
- package/_lib/instances/stackup.js +0 -89
- package/_lib/instances/stackup.js.map +0 -1
- package/_lib/pool.d.ts.map +0 -1
- package/_lib/pool.js.map +0 -1
- package/_lib/processes/execa.d.ts.map +0 -1
- package/_lib/processes/execa.js.map +0 -1
- package/_lib/server.d.ts.map +0 -1
- package/_lib/server.js.map +0 -1
- package/_lib/utils.d.ts.map +0 -1
- package/_lib/utils.js.map +0 -1
- package/exports/index.test.ts +0 -16
- package/exports/index.ts +0 -29
- package/exports/instances.test.ts +0 -15
- package/exports/instances.ts +0 -13
- package/exports/processes.test.ts +0 -10
- package/exports/processes.ts +0 -8
- package/instances/alto.ts +0 -280
- package/instances/anvil.ts +0 -304
- package/instances/package.json +0 -5
- package/instances/rundler.test.ts +0 -111
- package/instances/rundler.ts +0 -451
- package/instances/silius.ts +0 -347
- package/instances/stackup.test.ts +0 -106
- package/instances/stackup.ts +0 -162
- package/processes/package.json +0 -5
- package/tsconfig.build.tsbuildinfo +0 -1
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import * as os from 'node:os'
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
import {
|
|
4
|
+
GenericContainer,
|
|
5
|
+
type StartedTestContainer,
|
|
6
|
+
Wait,
|
|
7
|
+
} from 'testcontainers'
|
|
8
|
+
import * as Instance from '../Instance.js'
|
|
9
|
+
import { deepAssign, toArgs } from '../internal/utils.js'
|
|
10
|
+
import { execa } from '../processes/execa.js'
|
|
11
|
+
|
|
12
|
+
export function command(parameters: tempo.Parameters): string[] {
|
|
13
|
+
const { blockMaxTransactions, blockTime, mnemonic, port, ...rest } =
|
|
14
|
+
parameters
|
|
15
|
+
|
|
16
|
+
const datadir = path.join(os.tmpdir(), '.prool', `tempo.${port}`)
|
|
17
|
+
const defaultParameters = {
|
|
18
|
+
authrpc: {
|
|
19
|
+
port: port! + 30,
|
|
20
|
+
},
|
|
21
|
+
datadir,
|
|
22
|
+
dev: [
|
|
23
|
+
true,
|
|
24
|
+
{
|
|
25
|
+
blockTime: blockTime ?? '50ms',
|
|
26
|
+
...(blockMaxTransactions ? { blockMaxTransactions } : {}),
|
|
27
|
+
...(mnemonic ? { mnemonic } : {}),
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
engine: {
|
|
31
|
+
disablePrecompileCache: true,
|
|
32
|
+
legacyStateRoot: true,
|
|
33
|
+
},
|
|
34
|
+
faucet: {
|
|
35
|
+
address: [
|
|
36
|
+
'0x20c0000000000000000000000000000000000000',
|
|
37
|
+
'0x20c0000000000000000000000000000000000001',
|
|
38
|
+
'0x20c0000000000000000000000000000000000002',
|
|
39
|
+
'0x20c0000000000000000000000000000000000003',
|
|
40
|
+
],
|
|
41
|
+
amount: '1000000000000',
|
|
42
|
+
enabled: true,
|
|
43
|
+
privateKey:
|
|
44
|
+
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
|
|
45
|
+
},
|
|
46
|
+
http: {
|
|
47
|
+
addr: '0.0.0.0',
|
|
48
|
+
api: 'all',
|
|
49
|
+
corsdomain: '*',
|
|
50
|
+
port: port!,
|
|
51
|
+
},
|
|
52
|
+
port: port! + 10,
|
|
53
|
+
ws: {
|
|
54
|
+
port: port! + 20,
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return [
|
|
59
|
+
'node',
|
|
60
|
+
...toArgs(deepAssign(defaultParameters, rest), {
|
|
61
|
+
arraySeparator: null,
|
|
62
|
+
}),
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Defines a Tempo instance.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const instance = Instance.tempo({ port: 8545 })
|
|
72
|
+
* await instance.start()
|
|
73
|
+
* // ...
|
|
74
|
+
* await instance.stop()
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export const tempo = Instance.define((parameters?: tempo.Parameters) => {
|
|
78
|
+
const { binary = 'tempo', log: log_, ...args } = parameters || {}
|
|
79
|
+
|
|
80
|
+
const log = (() => {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(log_ as string)
|
|
83
|
+
} catch {
|
|
84
|
+
return log_
|
|
85
|
+
}
|
|
86
|
+
})()
|
|
87
|
+
const RUST_LOG = log && typeof log !== 'boolean' ? log : ''
|
|
88
|
+
|
|
89
|
+
const name = 'tempo'
|
|
90
|
+
const process = execa({ name })
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
_internal: {
|
|
94
|
+
args,
|
|
95
|
+
get process() {
|
|
96
|
+
return process._internal.process
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
host: args.host ?? 'localhost',
|
|
100
|
+
name,
|
|
101
|
+
port: args.port ?? 8545,
|
|
102
|
+
async start({ port = args.port }, options) {
|
|
103
|
+
return await process.start(
|
|
104
|
+
($) =>
|
|
105
|
+
$({
|
|
106
|
+
env: {
|
|
107
|
+
RUST_LOG,
|
|
108
|
+
},
|
|
109
|
+
})`${[binary, ...command({ ...args, port })]}`,
|
|
110
|
+
{
|
|
111
|
+
...options,
|
|
112
|
+
// Resolve when the process is listening via "RPC HTTP server started" message.
|
|
113
|
+
resolver({ process, reject, resolve }) {
|
|
114
|
+
process.stdout.on('data', (data) => {
|
|
115
|
+
const message = data.toString()
|
|
116
|
+
if (log) console.log(message)
|
|
117
|
+
if (message.includes('RPC HTTP server started')) resolve()
|
|
118
|
+
if (message.includes('shutting down')) reject('shutting down')
|
|
119
|
+
})
|
|
120
|
+
process.stderr.on('data', (data) => {
|
|
121
|
+
const message = data.toString()
|
|
122
|
+
if (log) console.error(message)
|
|
123
|
+
})
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
},
|
|
128
|
+
async stop() {
|
|
129
|
+
await process.stop()
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
export declare namespace tempo {
|
|
135
|
+
export type Parameters = {
|
|
136
|
+
/**
|
|
137
|
+
* Path or alias to the Tempo binary.
|
|
138
|
+
*/
|
|
139
|
+
binary?: string | undefined
|
|
140
|
+
/**
|
|
141
|
+
* Interval between blocks.
|
|
142
|
+
*/
|
|
143
|
+
blockTime?: string | undefined
|
|
144
|
+
/**
|
|
145
|
+
* How many transactions to mine per block
|
|
146
|
+
*/
|
|
147
|
+
blockMaxTransactions?: number | undefined
|
|
148
|
+
/**
|
|
149
|
+
* Path to a configuration file.
|
|
150
|
+
*/
|
|
151
|
+
config?: string | undefined
|
|
152
|
+
/**
|
|
153
|
+
* The chain this node is running.
|
|
154
|
+
* Possible values are either a built-in chain or the path to a chain specification file.
|
|
155
|
+
*
|
|
156
|
+
* Built-in chains:
|
|
157
|
+
* - testnet
|
|
158
|
+
*
|
|
159
|
+
* @default "testnet"
|
|
160
|
+
*/
|
|
161
|
+
chain?: string | undefined
|
|
162
|
+
/**
|
|
163
|
+
* The path to the data dir for all reth files and subdirectories.
|
|
164
|
+
*/
|
|
165
|
+
datadir?: string | undefined
|
|
166
|
+
/**
|
|
167
|
+
* Faucet options.
|
|
168
|
+
*/
|
|
169
|
+
faucet?:
|
|
170
|
+
| {
|
|
171
|
+
/**
|
|
172
|
+
* Target token addresses for the faucet to be funding with
|
|
173
|
+
*/
|
|
174
|
+
addresses?: string[] | undefined
|
|
175
|
+
/**
|
|
176
|
+
* Amount for each faucet funding transaction
|
|
177
|
+
*/
|
|
178
|
+
amount?: bigint | undefined
|
|
179
|
+
/**
|
|
180
|
+
* Faucet funding mnemonic
|
|
181
|
+
*/
|
|
182
|
+
privateKey?: string | undefined
|
|
183
|
+
}
|
|
184
|
+
| undefined
|
|
185
|
+
/**
|
|
186
|
+
* Rust log level configuration (sets RUST_LOG environment variable).
|
|
187
|
+
* Can be a log level or a custom filter string.
|
|
188
|
+
*/
|
|
189
|
+
log?:
|
|
190
|
+
| 'trace'
|
|
191
|
+
| 'debug'
|
|
192
|
+
| 'info'
|
|
193
|
+
| 'warn'
|
|
194
|
+
| 'error'
|
|
195
|
+
| (string & {})
|
|
196
|
+
| boolean
|
|
197
|
+
| undefined
|
|
198
|
+
/**
|
|
199
|
+
* Host the server will listen on.
|
|
200
|
+
*/
|
|
201
|
+
host?: string | undefined
|
|
202
|
+
/**
|
|
203
|
+
* Derive dev accounts from a fixed mnemonic instead of random ones.
|
|
204
|
+
* @default "test test test test test test test test test test test junk"
|
|
205
|
+
*/
|
|
206
|
+
mnemonic?: string | undefined
|
|
207
|
+
/**
|
|
208
|
+
* Port the server will listen on.
|
|
209
|
+
*/
|
|
210
|
+
port?: number | undefined
|
|
211
|
+
} & Record<string, unknown>
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Defines a Tempo instance.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* const instance = Instance.tempoDocker({ port: 8545 })
|
|
220
|
+
* await instance.start()
|
|
221
|
+
* // ...
|
|
222
|
+
* await instance.stop()
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
export const tempoDocker = Instance.define(
|
|
226
|
+
(parameters?: tempoDocker.Parameters) => {
|
|
227
|
+
const {
|
|
228
|
+
containerName = `tempo.${crypto.randomUUID()}`,
|
|
229
|
+
image = 'ghcr.io/tempoxyz/tempo:latest',
|
|
230
|
+
log: log_,
|
|
231
|
+
...args
|
|
232
|
+
} = parameters || {}
|
|
233
|
+
|
|
234
|
+
const log = (() => {
|
|
235
|
+
try {
|
|
236
|
+
return JSON.parse(log_ as string)
|
|
237
|
+
} catch {
|
|
238
|
+
return log_
|
|
239
|
+
}
|
|
240
|
+
})()
|
|
241
|
+
const RUST_LOG = log && typeof log !== 'boolean' ? log : ''
|
|
242
|
+
|
|
243
|
+
const name = 'tempo'
|
|
244
|
+
let container: StartedTestContainer | undefined
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
_internal: {
|
|
248
|
+
args,
|
|
249
|
+
},
|
|
250
|
+
host: args.host ?? 'localhost',
|
|
251
|
+
name,
|
|
252
|
+
port: args.port ?? 8545,
|
|
253
|
+
async start({ port = args.port }, { emitter }) {
|
|
254
|
+
const promise = Promise.withResolvers<void>()
|
|
255
|
+
|
|
256
|
+
const c = new GenericContainer(image)
|
|
257
|
+
.withPlatform('linux/x86_64')
|
|
258
|
+
.withNetworkMode('host')
|
|
259
|
+
.withExtraHosts([
|
|
260
|
+
{ host: 'host.docker.internal', ipAddress: 'host-gateway' },
|
|
261
|
+
{ host: 'localhost', ipAddress: 'host-gateway' },
|
|
262
|
+
])
|
|
263
|
+
.withName(containerName)
|
|
264
|
+
.withEnvironment({ RUST_LOG })
|
|
265
|
+
.withCommand(command({ ...args, port }))
|
|
266
|
+
.withWaitStrategy(Wait.forLogMessage(/RPC HTTP server started/))
|
|
267
|
+
.withLogConsumer((stream) => {
|
|
268
|
+
stream.on('data', (data) => {
|
|
269
|
+
const message = data.toString()
|
|
270
|
+
emitter.emit('message', message)
|
|
271
|
+
emitter.emit('stdout', message)
|
|
272
|
+
if (log) console.log(message)
|
|
273
|
+
if (message.includes('shutting down'))
|
|
274
|
+
promise.reject(new Error(`Failed to start: ${message}`))
|
|
275
|
+
})
|
|
276
|
+
stream.on('error', (error) => {
|
|
277
|
+
if (log) console.error(error.message)
|
|
278
|
+
emitter.emit('message', error.message)
|
|
279
|
+
emitter.emit('stderr', error.message)
|
|
280
|
+
promise.reject(new Error(`Failed to start: ${error.message}`))
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
.withStartupTimeout(10_000)
|
|
284
|
+
|
|
285
|
+
c.start()
|
|
286
|
+
.then((c) => {
|
|
287
|
+
container = c
|
|
288
|
+
promise.resolve()
|
|
289
|
+
})
|
|
290
|
+
.catch(promise.reject)
|
|
291
|
+
|
|
292
|
+
return promise.promise
|
|
293
|
+
},
|
|
294
|
+
async stop() {
|
|
295
|
+
if (!container) return
|
|
296
|
+
await container.stop()
|
|
297
|
+
container = undefined
|
|
298
|
+
},
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
export declare namespace tempoDocker {
|
|
304
|
+
export type Parameters = Omit<tempo.Parameters, 'binary'> & {
|
|
305
|
+
/**
|
|
306
|
+
* Name of the container.
|
|
307
|
+
*/
|
|
308
|
+
containerName?: string | undefined
|
|
309
|
+
/**
|
|
310
|
+
* Docker image to use.
|
|
311
|
+
*/
|
|
312
|
+
image?: string | undefined
|
|
313
|
+
/**
|
|
314
|
+
* Host the server will listen on.
|
|
315
|
+
*/
|
|
316
|
+
host?: string | undefined
|
|
317
|
+
/**
|
|
318
|
+
* Port the server will listen on.
|
|
319
|
+
*/
|
|
320
|
+
port?: number | undefined
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -30,10 +30,12 @@ test.each([
|
|
|
30
30
|
[[{ foo: 0 }], ['--foo', '0']],
|
|
31
31
|
[[{ foo: 1 }], ['--foo', '1']],
|
|
32
32
|
[[{ foo: 1n }], ['--foo', '1']],
|
|
33
|
+
[[{ foo: 1n }], ['--foo', '1']],
|
|
33
34
|
[[{ foo: 'bar', baz: 1 }], ['--foo', 'bar', '--baz', '1']],
|
|
34
35
|
[[{ fooBar: 'test' }], ['--foo-bar', 'test']],
|
|
35
36
|
[[{ foo: ['bar', 'baz'] }], ['--foo', 'bar,baz']],
|
|
36
37
|
[[{ foo: { barBaz: 'test' } }], ['--foo.bar-baz', 'test']],
|
|
38
|
+
[[{ foo: [true, { barBaz: 'test' }] }], ['--foo', '--foo.bar-baz', 'test']],
|
|
37
39
|
[[{ foo: { barBaz: ['test', 'test2'] } }], ['--foo.bar-baz', 'test,test2']],
|
|
38
40
|
[
|
|
39
41
|
[{ fooBar: 'test' }, { casing: 'snake' }],
|
|
@@ -43,12 +45,24 @@ test.each([
|
|
|
43
45
|
[{ foo: { barBaz: 'test' } }, { casing: 'snake' }],
|
|
44
46
|
['--foo.bar_baz', 'test'],
|
|
45
47
|
],
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
[
|
|
49
|
+
[{ foo: ['bar', 'baz'] }, { arraySeparator: null }],
|
|
50
|
+
['--foo', 'bar', 'baz'],
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
[{ foo: { bar: ['baz', 'qux'] } }, { arraySeparator: null }],
|
|
54
|
+
['--foo.bar', 'baz', 'qux'],
|
|
55
|
+
],
|
|
56
|
+
[
|
|
57
|
+
[{ foo: ['bar', 'baz'] }, { arraySeparator: null }],
|
|
58
|
+
['--foo', 'bar', 'baz'],
|
|
59
|
+
],
|
|
60
|
+
] as [Parameters<typeof toArgs>, string[]][])('toArgs(%o) -> %o', ([
|
|
61
|
+
input,
|
|
62
|
+
options,
|
|
63
|
+
], expected) => {
|
|
64
|
+
expect(toArgs(input, options)).toEqual(expected)
|
|
65
|
+
})
|
|
52
66
|
|
|
53
67
|
test.each([
|
|
54
68
|
['foo', '--foo'],
|
|
@@ -30,7 +30,7 @@ export function extractPath(request: string): ExtractPathReturnType {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const ansiColorRegex =
|
|
33
|
-
// biome-ignore lint/suspicious/noControlCharactersInRegex:
|
|
33
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: _
|
|
34
34
|
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -50,23 +50,26 @@ export function stripColors(message: string) {
|
|
|
50
50
|
* @returns The command line arguments.
|
|
51
51
|
*/
|
|
52
52
|
export function toArgs(
|
|
53
|
-
obj:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
| bigint
|
|
61
|
-
| undefined
|
|
62
|
-
},
|
|
63
|
-
options: { casing: 'kebab' | 'snake' } = { casing: 'kebab' },
|
|
64
|
-
) {
|
|
65
|
-
const { casing } = options
|
|
53
|
+
obj: Record<string, unknown>,
|
|
54
|
+
options: {
|
|
55
|
+
arraySeparator?: string | null | undefined
|
|
56
|
+
casing?: 'kebab' | 'snake' | undefined
|
|
57
|
+
} = {},
|
|
58
|
+
): string[] {
|
|
59
|
+
const { arraySeparator = ',', casing = 'kebab' } = options
|
|
66
60
|
return Object.entries(obj).flatMap(([key, value]) => {
|
|
67
61
|
if (value === undefined) return []
|
|
68
62
|
|
|
69
|
-
if (Array.isArray(value))
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
if (value[0] === true)
|
|
65
|
+
return [toFlagCase(key), ...toArgs({ [key]: value[1] }, options)]
|
|
66
|
+
const arrayValue = (() => {
|
|
67
|
+
if (arraySeparator === null) return value
|
|
68
|
+
return value.join(arraySeparator)
|
|
69
|
+
})()
|
|
70
|
+
|
|
71
|
+
return [toFlagCase(key), arrayValue].flat()
|
|
72
|
+
}
|
|
70
73
|
|
|
71
74
|
if (typeof value === 'object' && value !== null) {
|
|
72
75
|
return Object.entries(value).flatMap(([subKey, subValue]) => {
|
|
@@ -75,7 +78,7 @@ export function toArgs(
|
|
|
75
78
|
`${key}.${subKey}`,
|
|
76
79
|
casing === 'kebab' ? '-' : '_',
|
|
77
80
|
)
|
|
78
|
-
return [flag
|
|
81
|
+
return toArgs({ [flag.slice(2)]: subValue }, options)
|
|
79
82
|
})
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -84,7 +87,7 @@ export function toArgs(
|
|
|
84
87
|
if (value === false) return [flag, 'false']
|
|
85
88
|
if (value === true) return [flag]
|
|
86
89
|
|
|
87
|
-
const stringified = value
|
|
90
|
+
const stringified = value?.toString() ?? ''
|
|
88
91
|
if (stringified === '') return [flag]
|
|
89
92
|
|
|
90
93
|
return [flag, stringified]
|
|
@@ -106,3 +109,30 @@ export function toFlagCase(str: string, separator = '-') {
|
|
|
106
109
|
}
|
|
107
110
|
return `--${keys.join('.')}`
|
|
108
111
|
}
|
|
112
|
+
|
|
113
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
114
|
+
return Object.prototype.toString.call(value) === '[object Object]'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function deepAssign(
|
|
118
|
+
target: Record<string, unknown>,
|
|
119
|
+
...sources: Record<string, unknown>[]
|
|
120
|
+
): Record<string, unknown> {
|
|
121
|
+
for (const source of sources) {
|
|
122
|
+
if (!isPlainObject(source)) continue
|
|
123
|
+
|
|
124
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
125
|
+
const srcVal = source[key as keyof typeof source]
|
|
126
|
+
const tgtVal = target[key as keyof typeof target]
|
|
127
|
+
|
|
128
|
+
// If both are plain objects, merge recursively
|
|
129
|
+
if (isPlainObject(srcVal) && isPlainObject(tgtVal)) {
|
|
130
|
+
deepAssign(tgtVal, srcVal)
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
// Otherwise, assign a deep clone of the source value
|
|
134
|
+
;(target as any)[key] = structuredClone(srcVal)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return target
|
|
138
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from 'eventemitter3'
|
|
2
|
+
import getPort from 'get-port'
|
|
2
3
|
import { afterEach, expect, test } from 'vitest'
|
|
3
|
-
import {
|
|
4
|
+
import { execa } from './execa.js'
|
|
4
5
|
|
|
5
|
-
const processes:
|
|
6
|
+
const processes: execa.ReturnType[] = []
|
|
6
7
|
function createProcess() {
|
|
7
8
|
const process = execa({ name: 'foo' })
|
|
8
9
|
processes.push(process)
|
|
@@ -82,14 +83,7 @@ test('start (error)', async () => {
|
|
|
82
83
|
process.stderr.on('data', reject)
|
|
83
84
|
},
|
|
84
85
|
}),
|
|
85
|
-
).rejects.
|
|
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
|
-
`)
|
|
86
|
+
).rejects.toThrowError('Failed to start process "foo"')
|
|
93
87
|
await expect(resolvers.message.promise).resolves.toBeDefined()
|
|
94
88
|
await expect(resolvers.stderr.promise).resolves.toBeDefined()
|
|
95
89
|
})
|
|
@@ -128,7 +122,8 @@ test('behavior: exit when status is starting', async () => {
|
|
|
128
122
|
emitter.on('exit', resolvers.exit.resolve)
|
|
129
123
|
|
|
130
124
|
// Invalid argument
|
|
131
|
-
await
|
|
125
|
+
const port = await getPort()
|
|
126
|
+
await process.start(($) => $`anvil --port ${port}`, {
|
|
132
127
|
emitter,
|
|
133
128
|
status: 'starting',
|
|
134
129
|
resolver({ process, resolve }) {
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import type { SignalConstants } from 'node:os'
|
|
2
|
-
import {
|
|
3
|
-
import type
|
|
4
|
-
import { stripColors } from '../utils.js'
|
|
2
|
+
import { execa as exec, type ResultPromise } from 'execa'
|
|
3
|
+
import type * as Instance from '../Instance.js'
|
|
4
|
+
import { stripColors } from '../internal/utils.js'
|
|
5
5
|
|
|
6
6
|
export type Process_internal = ResultPromise<{ cleanup: true; reject: false }>
|
|
7
7
|
|
|
8
|
-
export type ExecaStartOptions =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type ExecaParameters = { name: string }
|
|
8
|
+
export type ExecaStartOptions =
|
|
9
|
+
Instance.define.InstanceStartOptions_internal & {
|
|
10
|
+
resolver(options: {
|
|
11
|
+
process: Process_internal
|
|
12
|
+
reject(data: string): Promise<void>
|
|
13
|
+
resolve(): void
|
|
14
|
+
}): void
|
|
15
|
+
}
|
|
17
16
|
|
|
18
|
-
export type
|
|
17
|
+
export type Process = {
|
|
19
18
|
_internal: {
|
|
20
19
|
process: Process_internal
|
|
21
20
|
}
|
|
@@ -26,9 +25,8 @@ export type ExecaProcess = {
|
|
|
26
25
|
): Promise<void>
|
|
27
26
|
stop(signal?: keyof SignalConstants | number): Promise<void>
|
|
28
27
|
}
|
|
29
|
-
export type ExecaReturnType = ExecaProcess
|
|
30
28
|
|
|
31
|
-
export function execa(parameters:
|
|
29
|
+
export function execa(parameters: execa.Parameters): execa.ReturnType {
|
|
32
30
|
const { name } = parameters
|
|
33
31
|
|
|
34
32
|
const errorMessages: string[] = []
|
|
@@ -112,3 +110,9 @@ export function execa(parameters: ExecaParameters): ExecaReturnType {
|
|
|
112
110
|
},
|
|
113
111
|
}
|
|
114
112
|
}
|
|
113
|
+
|
|
114
|
+
export declare namespace execa {
|
|
115
|
+
export type Parameters = { name: string }
|
|
116
|
+
|
|
117
|
+
export type ReturnType = Process
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as execa from './execa.js'
|