starskiff 0.0.0 → 0.4.2
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 +21 -0
- package/README.md +327 -0
- package/dist/Instance.d.ts +89 -0
- package/dist/Instance.d.ts.map +1 -0
- package/dist/Instance.js +206 -0
- package/dist/Instance.js.map +1 -0
- package/dist/accounts.d.ts +26 -0
- package/dist/accounts.d.ts.map +1 -0
- package/dist/accounts.js +38 -0
- package/dist/accounts.js.map +1 -0
- package/dist/cosmos.d.ts +265 -0
- package/dist/cosmos.d.ts.map +1 -0
- package/dist/cosmos.js +359 -0
- package/dist/cosmos.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/instances/evmd.d.ts +68 -0
- package/dist/instances/evmd.d.ts.map +1 -0
- package/dist/instances/evmd.js +106 -0
- package/dist/instances/evmd.js.map +1 -0
- package/dist/instances/gaiad.d.ts +46 -0
- package/dist/instances/gaiad.d.ts.map +1 -0
- package/dist/instances/gaiad.js +35 -0
- package/dist/instances/gaiad.js.map +1 -0
- package/dist/instances/hermes.d.ts +52 -0
- package/dist/instances/hermes.d.ts.map +1 -0
- package/dist/instances/hermes.js +457 -0
- package/dist/instances/hermes.js.map +1 -0
- package/dist/instances/simd.d.ts +43 -0
- package/dist/instances/simd.d.ts.map +1 -0
- package/dist/instances/simd.js +21 -0
- package/dist/instances/simd.js.map +1 -0
- package/dist/instances/wasmd.d.ts +45 -0
- package/dist/instances/wasmd.d.ts.map +1 -0
- package/dist/instances/wasmd.js +23 -0
- package/dist/instances/wasmd.js.map +1 -0
- package/dist/instances/xplad.d.ts +55 -0
- package/dist/instances/xplad.d.ts.map +1 -0
- package/dist/instances/xplad.js +86 -0
- package/dist/instances/xplad.js.map +1 -0
- package/dist/ports.d.ts +31 -0
- package/dist/ports.d.ts.map +1 -0
- package/dist/ports.js +45 -0
- package/dist/ports.js.map +1 -0
- package/dist/process.d.ts +34 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +92 -0
- package/dist/process.js.map +1 -0
- package/dist/utils.d.ts +15 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +33 -0
- package/dist/utils.js.map +1 -0
- package/package.json +50 -7
- package/src/Instance.ts +288 -0
- package/src/accounts.ts +49 -0
- package/src/cosmos.ts +601 -0
- package/src/index.ts +7 -0
- package/src/instances/evmd.ts +126 -0
- package/src/instances/gaiad.ts +40 -0
- package/src/instances/hermes.ts +580 -0
- package/src/instances/simd.ts +26 -0
- package/src/instances/wasmd.ts +28 -0
- package/src/instances/xplad.ts +107 -0
- package/src/ports.ts +63 -0
- package/src/process.ts +137 -0
- package/src/utils.ts +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Wonhee Lee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# starskiff
|
|
2
|
+
|
|
3
|
+
> [!WARNING]
|
|
4
|
+
> This project is under active development. APIs may change without notice until v1.0.
|
|
5
|
+
|
|
6
|
+
Real Cosmos SDK nodes as ephemeral test instances — spawned as child processes, no Docker, no Kubernetes.
|
|
7
|
+
|
|
8
|
+
starskiff is the lightweight skiff to [Starship](https://github.com/cosmology-tech/starship)'s heavy vessel: where Starship stands up multi-chain environments on Kubernetes, starskiff boots a single real chain node in seconds — for integration tests and CI. Not a mock; the actual Go binary.
|
|
9
|
+
|
|
10
|
+
Inspired by [prool](https://github.com/wevm/prool) (test instances for Ethereum).
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- Spawn real Cosmos SDK nodes as child processes
|
|
15
|
+
- No Docker, no Kubernetes — just a Go binary
|
|
16
|
+
- Genesis account injection with mnemonic recovery
|
|
17
|
+
- Full lifecycle management (start/stop/restart)
|
|
18
|
+
- Compatible with [@cosmjs/stargate](https://github.com/cosmos/cosmjs) and [@cosmjs/cosmwasm-stargate](https://github.com/cosmos/cosmjs) for testing
|
|
19
|
+
- Extensible to any Cosmos SDK chain binary via `cosmosBase`
|
|
20
|
+
|
|
21
|
+
## Instances
|
|
22
|
+
|
|
23
|
+
| Instance | Binary | Modules | Use case |
|
|
24
|
+
| ------------------ | ------- | ----------------------------------------------- | ---------------------------- |
|
|
25
|
+
| `Instance.simd()` | `simd` | bank, staking, gov, mint | Basic Cosmos SDK testing |
|
|
26
|
+
| `Instance.wasmd()` | `wasmd` | bank, staking, gov, mint, **IBC**, **CosmWasm** | Contract deploy/execute, IBC |
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add -D starskiff
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Prerequisites
|
|
35
|
+
|
|
36
|
+
Install the binary for the instance you need. Requires [Go](https://go.dev/dl/) >= 1.25.
|
|
37
|
+
|
|
38
|
+
**simd** (Cosmos SDK simapp):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git clone --depth 1 https://github.com/cosmos/cosmos-sdk.git /tmp/cosmos-sdk
|
|
42
|
+
cd /tmp/cosmos-sdk/simapp && go build -o ~/go/bin/simd ./simd/
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**wasmd** (CosmWasm — includes IBC):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone --depth 1 https://github.com/CosmWasm/wasmd.git /tmp/wasmd
|
|
49
|
+
cd /tmp/wasmd && go build -o ~/go/bin/wasmd ./cmd/wasmd/
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Prebuilt binaries (CI)
|
|
53
|
+
|
|
54
|
+
Prebuilt `linux/amd64` binaries are available in [GitHub Releases](https://github.com/2wheeh/starskiff/releases/tag/binaries/latest) for CI environments:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Download and install (e.g. in GitHub Actions)
|
|
58
|
+
gh release download "binaries/latest" --repo 2wheeh/starskiff --pattern "*.gz" --dir /tmp
|
|
59
|
+
gunzip -c /tmp/simd-linux-amd64.gz > /usr/local/bin/simd
|
|
60
|
+
gunzip -c /tmp/wasmd-linux-amd64.gz > /usr/local/bin/wasmd
|
|
61
|
+
chmod +x /usr/local/bin/simd /usr/local/bin/wasmd
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> For local development on macOS/Windows, build from source using the instructions above.
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Basic (simd)
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { Instance } from 'starskiff';
|
|
72
|
+
|
|
73
|
+
const instance = Instance.simd({
|
|
74
|
+
chainId: 'test-1',
|
|
75
|
+
denom: 'stake',
|
|
76
|
+
accounts: [
|
|
77
|
+
{
|
|
78
|
+
mnemonic: 'abandon abandon abandon ...',
|
|
79
|
+
coins: '1000000000stake',
|
|
80
|
+
name: 'alice',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await instance.start();
|
|
86
|
+
|
|
87
|
+
// Connect with cosmjs
|
|
88
|
+
import { StargateClient } from '@cosmjs/stargate';
|
|
89
|
+
const client = await StargateClient.connect(instance.rpcUrl);
|
|
90
|
+
const balance = await client.getBalance(address, 'stake');
|
|
91
|
+
|
|
92
|
+
await instance.stop();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Test accounts
|
|
96
|
+
|
|
97
|
+
Skip deriving your own dev mnemonic — `testAccounts` ships a handful of
|
|
98
|
+
well-known, publicly-documented BIP39 mnemonics with addresses pre-derived
|
|
99
|
+
for the `cosmos` bech32 prefix:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { Instance, testAccounts } from 'starskiff';
|
|
103
|
+
|
|
104
|
+
const [alice, bob] = testAccounts;
|
|
105
|
+
|
|
106
|
+
const instance = Instance.simd({
|
|
107
|
+
chainId: 'test-1',
|
|
108
|
+
accounts: [{ mnemonic: alice.mnemonic, coins: '1000000000stake', name: alice.name }],
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
These mnemonics are public — never fund them with real value. Addresses
|
|
113
|
+
assume the `cosmos` prefix; chains configured with a different `prefix`
|
|
114
|
+
(e.g. `osmo`, `xpla`) need addresses re-derived from the same mnemonic.
|
|
115
|
+
|
|
116
|
+
### CosmWasm (wasmd)
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { Instance } from 'starskiff';
|
|
120
|
+
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
|
|
121
|
+
import { GasPrice } from '@cosmjs/stargate';
|
|
122
|
+
|
|
123
|
+
const instance = Instance.wasmd({
|
|
124
|
+
chainId: 'wasm-test-1',
|
|
125
|
+
accounts: [{ mnemonic: '...', coins: '1000000000stake', name: 'alice' }],
|
|
126
|
+
});
|
|
127
|
+
await instance.start();
|
|
128
|
+
|
|
129
|
+
const client = await SigningCosmWasmClient.connectWithSigner(instance.rpcUrl, wallet, {
|
|
130
|
+
gasPrice: GasPrice.fromString('0stake'),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Upload, instantiate, execute
|
|
134
|
+
const { codeId } = await client.upload(address, wasmBytecode, 'auto');
|
|
135
|
+
const { contractAddress } = await client.instantiate(address, codeId, initMsg, 'label', 'auto');
|
|
136
|
+
const result = await client.execute(address, contractAddress, executeMsg, 'auto');
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### vitest globalSetup (provide/inject)
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
// test/global-setup.ts
|
|
143
|
+
import type { TestProject } from 'vitest/node';
|
|
144
|
+
import { Instance } from 'starskiff';
|
|
145
|
+
|
|
146
|
+
export default async function setup({ provide }: TestProject) {
|
|
147
|
+
const instance = Instance.wasmd({
|
|
148
|
+
chainId: 'test-1',
|
|
149
|
+
accounts: [{ mnemonic: '...', coins: '1000000000stake' }],
|
|
150
|
+
});
|
|
151
|
+
await instance.start();
|
|
152
|
+
|
|
153
|
+
provide('rpcUrl', instance.rpcUrl);
|
|
154
|
+
|
|
155
|
+
return () => instance.stop();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
declare module 'vitest' {
|
|
159
|
+
export interface ProvidedContext {
|
|
160
|
+
rpcUrl: string;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
// vitest.config.ts
|
|
167
|
+
export default defineConfig({
|
|
168
|
+
test: {
|
|
169
|
+
globalSetup: './test/global-setup.ts',
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
// test/bank.test.ts
|
|
176
|
+
import { inject } from 'vitest';
|
|
177
|
+
|
|
178
|
+
const rpcUrl = inject('rpcUrl');
|
|
179
|
+
|
|
180
|
+
it('queries balance', async () => {
|
|
181
|
+
const client = await StargateClient.connect(rpcUrl);
|
|
182
|
+
const balance = await client.getBalance(address, 'stake');
|
|
183
|
+
// ...
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Multi-chain
|
|
188
|
+
|
|
189
|
+
Hand-assigning ports gets tedious (and error-prone) once you run more than one
|
|
190
|
+
chain — use `findFreePorts()` instead:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { findFreePorts, Instance } from 'starskiff';
|
|
194
|
+
|
|
195
|
+
const [ports1, ports2] = await Promise.all([findFreePorts(), findFreePorts()]);
|
|
196
|
+
|
|
197
|
+
const chain1 = Instance.wasmd({ chainId: 'wasm-1', ...ports1 });
|
|
198
|
+
const chain2 = Instance.wasmd({ chainId: 'wasm-2', ...ports2 });
|
|
199
|
+
|
|
200
|
+
await Promise.all([chain1.start(), chain2.start()]);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Each `findFreePorts()` call returns a `PortSet` of distinct, currently-free
|
|
204
|
+
ports (`rpcPort`, `grpcPort`, `apiPort`, `p2pPort`, `grpcWebPort`,
|
|
205
|
+
`pprofPort`). Pass `{ evm: true }` to also grab an `evmPort` for EVM-enabled
|
|
206
|
+
chains (e.g. `xplad`, `evmd`). Note the TOCTOU caveat: a port is free at grab
|
|
207
|
+
time, but nothing stops another process from binding it before the instance
|
|
208
|
+
actually starts — rare, but possible under heavy concurrent test runs.
|
|
209
|
+
|
|
210
|
+
### Custom chain binary
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { Instance, cosmosBase } from 'starskiff';
|
|
214
|
+
|
|
215
|
+
// Any Cosmos SDK binary works — same init/genesis/start flow
|
|
216
|
+
const gaiad = Instance.define(params => cosmosBase({ binary: 'gaiad', name: 'gaiad', ...params }));
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## API
|
|
220
|
+
|
|
221
|
+
### `Instance.simd(parameters?, options?)`
|
|
222
|
+
|
|
223
|
+
Creates a simd (Cosmos SDK simapp) instance.
|
|
224
|
+
|
|
225
|
+
### `Instance.wasmd(parameters?, options?)`
|
|
226
|
+
|
|
227
|
+
Creates a wasmd (CosmWasm + IBC) instance. Same parameters as simd.
|
|
228
|
+
|
|
229
|
+
### Parameters
|
|
230
|
+
|
|
231
|
+
Shared by all instances (`CosmosChainParameters`):
|
|
232
|
+
|
|
233
|
+
| Parameter | Type | Default | Description |
|
|
234
|
+
| ------------------ | ----------------- | ----------------- | ------------------ |
|
|
235
|
+
| `binary` | `string` | instance-specific | Path to binary |
|
|
236
|
+
| `chainId` | `string` | `"starskiff-1"` | Chain ID |
|
|
237
|
+
| `denom` | `string` | `"stake"` | Default denom |
|
|
238
|
+
| `accounts` | `CosmosAccount[]` | `[]` | Genesis accounts |
|
|
239
|
+
| `minimumGasPrices` | `string` | `"0{denom}"` | Minimum gas prices |
|
|
240
|
+
| `rpcPort` | `number` | `26657` | CometBFT RPC port |
|
|
241
|
+
| `grpcPort` | `number` | `9090` | gRPC port |
|
|
242
|
+
| `apiPort` | `number` | `1317` | REST API port |
|
|
243
|
+
| `p2pPort` | `number` | `26656` | P2P port |
|
|
244
|
+
| `grpcWebPort` | `number` | `9091` | gRPC-Web port |
|
|
245
|
+
| `pprofPort` | `number` | `6060` | pprof port |
|
|
246
|
+
|
|
247
|
+
### Instance options (second argument)
|
|
248
|
+
|
|
249
|
+
| Option | Type | Default | Description |
|
|
250
|
+
| --------------- | -------- | ------- | ---------------------------------- |
|
|
251
|
+
| `messageBuffer` | `number` | `20` | Max messages to store in-memory |
|
|
252
|
+
| `timeout` | `number` | `60000` | Start/stop timeout in milliseconds |
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
const instance = Instance.wasmd({ chainId: 'test-1' }, { timeout: 30_000 });
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Instance methods
|
|
259
|
+
|
|
260
|
+
| Method | Description |
|
|
261
|
+
| --------------------- | ---------------------------------------------------------------------- |
|
|
262
|
+
| `start()` | Start the instance. Returns a stop function. |
|
|
263
|
+
| `stop()` | Stop the instance and cleanup temp directory. |
|
|
264
|
+
| `restart()` | Stop then start. |
|
|
265
|
+
| `on(event, handler)` | Listen to events (`message`, `stdout`, `stderr`, `listening`, `exit`). |
|
|
266
|
+
| `off(event, handler)` | Remove event listener. |
|
|
267
|
+
|
|
268
|
+
### Instance properties
|
|
269
|
+
|
|
270
|
+
| Property | Type | Description |
|
|
271
|
+
| ---------- | -------- | ----------------------------------------------------------------------- |
|
|
272
|
+
| `status` | `string` | `idle` / `starting` / `started` / `stopping` / `stopped` / `restarting` |
|
|
273
|
+
| `host` | `string` | Host (default: `localhost`) |
|
|
274
|
+
| `port` | `number` | RPC port |
|
|
275
|
+
| `name` | `string` | Instance name |
|
|
276
|
+
| `messages` | `object` | `.get()` returns buffered messages, `.clear()` clears them |
|
|
277
|
+
|
|
278
|
+
### URL getters
|
|
279
|
+
|
|
280
|
+
Cosmos instances (`simd`, `wasmd`, `xplad`, `evmd`, and anything built on
|
|
281
|
+
`cosmosBase`) also expose ready-to-use endpoint URLs — no more manually
|
|
282
|
+
templating `http://localhost:${instance.port}`:
|
|
283
|
+
|
|
284
|
+
| Property | Type | Description |
|
|
285
|
+
| --------- | -------- | ---------------------------------------------------------|
|
|
286
|
+
| `rpcUrl` | `string` | `http://{host}:{port}` — CometBFT RPC endpoint |
|
|
287
|
+
| `grpcUrl` | `string` | `http://{host}:{grpcPort}` — gRPC endpoint |
|
|
288
|
+
| `apiUrl` | `string` | `http://{host}:{apiPort}` — REST (Cosmos SDK API) endpoint |
|
|
289
|
+
| `evmUrl` | `string` | `http://{host}:{evmPort}` — EVM JSON-RPC endpoint (EVM instances only, e.g. `xplad`, `evmd`) |
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
const client = await StargateClient.connect(instance.rpcUrl);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Testing strategies
|
|
296
|
+
|
|
297
|
+
| Strategy | Isolation | Speed | Use case |
|
|
298
|
+
| ------------------------ | --------- | --------- | ------------------------------------------- |
|
|
299
|
+
| **Account isolation** | Practical | Fast | Most tests — each test uses unique accounts |
|
|
300
|
+
| **Suite-level instance** | Full | ~5s setup | Tests that modify chain-wide state |
|
|
301
|
+
| **Shared instance** | None | Fastest | Read-only queries, smoke tests |
|
|
302
|
+
|
|
303
|
+
Recommended: fund multiple accounts in genesis, assign each test its own account(s).
|
|
304
|
+
|
|
305
|
+
## Why not Starship?
|
|
306
|
+
|
|
307
|
+
| | Starship | starskiff |
|
|
308
|
+
| ------------ | -------------------------- | -------------------- |
|
|
309
|
+
| Infra | Kubernetes + Helm + Docker | None (child process) |
|
|
310
|
+
| Startup | 2-5 min | 3-5 sec |
|
|
311
|
+
| Dependencies | K8s cluster | Go binary |
|
|
312
|
+
| State reset | Helm redeploy (minutes) | kill + restart (~3s) |
|
|
313
|
+
| Best for | Production simulation | Dev/test |
|
|
314
|
+
|
|
315
|
+
## TODO
|
|
316
|
+
|
|
317
|
+
- [ ] `cosmosEvmBase` — EVM JSON-RPC port support for EVM-enabled chains (e.g. xpla, evmos)
|
|
318
|
+
- [ ] `starskiff.config.ts` — config-based chain + relayer declaration
|
|
319
|
+
- [ ] `starskiff/vitest` — vitest plugin (automatic setup/teardown via `vitestPlugin(config)`)
|
|
320
|
+
- [ ] `starskiff/playwright` — playwright plugin (`playwrightPlugin(config)`)
|
|
321
|
+
- [ ] Automatic port allocation (avoid port conflicts in parallel tests)
|
|
322
|
+
- [x] `findFreePorts()` utility for direct `Instance` users
|
|
323
|
+
- [ ] `starskiff/setup-binaries` GitHub Action for CI binary setup
|
|
324
|
+
|
|
325
|
+
## License
|
|
326
|
+
|
|
327
|
+
MIT
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type Emitter } from 'mitt';
|
|
2
|
+
import type { EventTypes } from './process.js';
|
|
3
|
+
export { simd } from './instances/simd.js';
|
|
4
|
+
export { wasmd } from './instances/wasmd.js';
|
|
5
|
+
export { gaiad } from './instances/gaiad.js';
|
|
6
|
+
export { xplad } from './instances/xplad.js';
|
|
7
|
+
export { evmd } from './instances/evmd.js';
|
|
8
|
+
export { hermes } from './instances/hermes.js';
|
|
9
|
+
export type InstanceStatus = 'idle' | 'starting' | 'started' | 'stopping' | 'stopped' | 'restarting';
|
|
10
|
+
/** A managed instance with lifecycle control and event emitting. */
|
|
11
|
+
export type Instance = {
|
|
12
|
+
/** Host the instance is running on. */
|
|
13
|
+
host: string;
|
|
14
|
+
/** Name of the instance (e.g. "simd"). */
|
|
15
|
+
name: string;
|
|
16
|
+
/** RPC port the instance is listening on. */
|
|
17
|
+
port: number;
|
|
18
|
+
/** Current lifecycle status. */
|
|
19
|
+
status: InstanceStatus;
|
|
20
|
+
/** In-memory message buffer for debugging. */
|
|
21
|
+
messages: {
|
|
22
|
+
/** Clear all buffered messages. */
|
|
23
|
+
clear(): void;
|
|
24
|
+
/** Get all buffered messages. */
|
|
25
|
+
get(): string[];
|
|
26
|
+
};
|
|
27
|
+
/** Start the instance. Returns a stop function. */
|
|
28
|
+
start(): Promise<() => void>;
|
|
29
|
+
/** Stop the instance and clean up resources. */
|
|
30
|
+
stop(): Promise<void>;
|
|
31
|
+
/** Stop then start the instance. */
|
|
32
|
+
restart(): Promise<void>;
|
|
33
|
+
/** Subscribe to instance events (message, stdout, stderr, listening, exit). */
|
|
34
|
+
on: Emitter<EventTypes>['on'];
|
|
35
|
+
/** Unsubscribe from instance events. */
|
|
36
|
+
off: Emitter<EventTypes>['off'];
|
|
37
|
+
};
|
|
38
|
+
export type InstanceOptions = {
|
|
39
|
+
/** Number of messages to store in-memory. @default 20 */
|
|
40
|
+
messageBuffer?: number;
|
|
41
|
+
/** Timeout (in milliseconds) for starting and stopping. @default 60_000 */
|
|
42
|
+
timeout?: number;
|
|
43
|
+
};
|
|
44
|
+
export type InstanceStartOptions = {
|
|
45
|
+
port?: number | undefined;
|
|
46
|
+
};
|
|
47
|
+
export type InstanceStartContext = {
|
|
48
|
+
emitter: Emitter<EventTypes>;
|
|
49
|
+
setEndpoint?(endpoint: {
|
|
50
|
+
host?: string;
|
|
51
|
+
port?: number;
|
|
52
|
+
}): void;
|
|
53
|
+
status: InstanceStatus;
|
|
54
|
+
};
|
|
55
|
+
export type InstanceStopContext = {
|
|
56
|
+
emitter: Emitter<EventTypes>;
|
|
57
|
+
status: InstanceStatus;
|
|
58
|
+
};
|
|
59
|
+
type DefineFnResult = {
|
|
60
|
+
name: string;
|
|
61
|
+
host: string;
|
|
62
|
+
port: number;
|
|
63
|
+
start(options: InstanceStartOptions, context: InstanceStartContext): Promise<void>;
|
|
64
|
+
stop(context: InstanceStopContext): Promise<void>;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Creates an instance definition.
|
|
68
|
+
*
|
|
69
|
+
* Takes a factory function that returns the instance's name, host, port,
|
|
70
|
+
* and start/stop implementations. Returns a callable that creates
|
|
71
|
+
* managed instances with lifecycle control.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const simd = Instance.define((params?: { chainId?: string }) => ({
|
|
76
|
+
* name: 'simd',
|
|
77
|
+
* host: 'localhost',
|
|
78
|
+
* port: 26657,
|
|
79
|
+
* async start(opts, ctx) { ... },
|
|
80
|
+
* async stop(ctx) { ... },
|
|
81
|
+
* }))
|
|
82
|
+
*
|
|
83
|
+
* const instance = simd({ chainId: 'test-1' })
|
|
84
|
+
* instance.chainId // string — extra field preserved
|
|
85
|
+
* await instance.start()
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function define<P = undefined, R extends DefineFnResult = DefineFnResult>(fn: (parameters: P) => R): (...args: P extends undefined ? [options?: InstanceOptions] : [parameters: P, options?: InstanceOptions]) => Omit<R, keyof DefineFnResult> & Instance;
|
|
89
|
+
//# sourceMappingURL=Instance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Instance.d.ts","sourceRoot":"","sources":["../src/Instance.ts"],"names":[],"mappings":"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"}
|
package/dist/Instance.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import mitt, {} from 'mitt';
|
|
2
|
+
export { simd } from './instances/simd.js';
|
|
3
|
+
export { wasmd } from './instances/wasmd.js';
|
|
4
|
+
export { gaiad } from './instances/gaiad.js';
|
|
5
|
+
export { xplad } from './instances/xplad.js';
|
|
6
|
+
export { evmd } from './instances/evmd.js';
|
|
7
|
+
export { hermes } from './instances/hermes.js';
|
|
8
|
+
/**
|
|
9
|
+
* Creates an instance definition.
|
|
10
|
+
*
|
|
11
|
+
* Takes a factory function that returns the instance's name, host, port,
|
|
12
|
+
* and start/stop implementations. Returns a callable that creates
|
|
13
|
+
* managed instances with lifecycle control.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const simd = Instance.define((params?: { chainId?: string }) => ({
|
|
18
|
+
* name: 'simd',
|
|
19
|
+
* host: 'localhost',
|
|
20
|
+
* port: 26657,
|
|
21
|
+
* async start(opts, ctx) { ... },
|
|
22
|
+
* async stop(ctx) { ... },
|
|
23
|
+
* }))
|
|
24
|
+
*
|
|
25
|
+
* const instance = simd({ chainId: 'test-1' })
|
|
26
|
+
* instance.chainId // string — extra field preserved
|
|
27
|
+
* await instance.start()
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function define(fn) {
|
|
31
|
+
return (...[parametersOrOptions, options_]) => {
|
|
32
|
+
const isInstanceOptions = (v) => v != null && typeof v === 'object' && ('messageBuffer' in v || 'timeout' in v);
|
|
33
|
+
// When P = undefined: (options?) → first arg is options
|
|
34
|
+
// When P is defined: (params, options?) → first arg is params, second is options
|
|
35
|
+
const parameters = (options_ !== undefined || !isInstanceOptions(parametersOrOptions)
|
|
36
|
+
? parametersOrOptions
|
|
37
|
+
: undefined);
|
|
38
|
+
const options = options_ ?? (isInstanceOptions(parametersOrOptions) ? parametersOrOptions : {});
|
|
39
|
+
const raw = fn(parameters);
|
|
40
|
+
const { name, start, stop } = raw;
|
|
41
|
+
let host = raw.host;
|
|
42
|
+
let port = raw.port;
|
|
43
|
+
const { messageBuffer = 20, timeout = 60_000 } = options;
|
|
44
|
+
let startResolver = Promise.withResolvers();
|
|
45
|
+
let stopResolver = Promise.withResolvers();
|
|
46
|
+
let restartResolver = Promise.withResolvers();
|
|
47
|
+
const emitter = mitt();
|
|
48
|
+
let messages = [];
|
|
49
|
+
let status = 'idle';
|
|
50
|
+
let restarting = false;
|
|
51
|
+
const onMessage = (message) => {
|
|
52
|
+
messages.push(message);
|
|
53
|
+
if (messages.length > messageBuffer)
|
|
54
|
+
messages.shift();
|
|
55
|
+
};
|
|
56
|
+
const onListening = () => { status = 'started'; };
|
|
57
|
+
const onExit = () => { status = 'stopped'; };
|
|
58
|
+
const self = {
|
|
59
|
+
get host() { return host; },
|
|
60
|
+
name,
|
|
61
|
+
get port() { return port; },
|
|
62
|
+
get status() {
|
|
63
|
+
if (restarting)
|
|
64
|
+
return 'restarting';
|
|
65
|
+
return status;
|
|
66
|
+
},
|
|
67
|
+
messages: {
|
|
68
|
+
clear() { messages = []; },
|
|
69
|
+
get() { return [...messages]; },
|
|
70
|
+
},
|
|
71
|
+
async start() {
|
|
72
|
+
if (status === 'starting')
|
|
73
|
+
return startResolver.promise;
|
|
74
|
+
if (status !== 'idle' && status !== 'stopped')
|
|
75
|
+
throw new Error(`Instance "${name}" is not in an idle or stopped state. Status: ${status}`);
|
|
76
|
+
let startTimer;
|
|
77
|
+
// Guards the in-flight start(...) call's .then/.catch below: once the
|
|
78
|
+
// timeout fires, status/resolvers have already been reset (and a
|
|
79
|
+
// retry may be in flight), so a late settle must not touch them.
|
|
80
|
+
let timedOut = false;
|
|
81
|
+
if (typeof timeout === 'number') {
|
|
82
|
+
startTimer = setTimeout(() => {
|
|
83
|
+
timedOut = true;
|
|
84
|
+
status = 'idle';
|
|
85
|
+
self.messages.clear();
|
|
86
|
+
emitter.off('message', onMessage);
|
|
87
|
+
emitter.off('listening', onListening);
|
|
88
|
+
emitter.off('exit', onExit);
|
|
89
|
+
startResolver.reject(new Error(`Instance "${name}" failed to start in time.`));
|
|
90
|
+
startResolver = Promise.withResolvers();
|
|
91
|
+
// Best-effort: the child may still be running post-timeout, so
|
|
92
|
+
// ask the underlying stop to tear it down. Never let this throw —
|
|
93
|
+
// the instance must remain retryable regardless of the outcome.
|
|
94
|
+
try {
|
|
95
|
+
void stop({ emitter, status: self.status }).catch(() => { });
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// ignore
|
|
99
|
+
}
|
|
100
|
+
}, timeout);
|
|
101
|
+
}
|
|
102
|
+
emitter.on('message', onMessage);
|
|
103
|
+
emitter.on('listening', onListening);
|
|
104
|
+
emitter.on('exit', onExit);
|
|
105
|
+
status = 'starting';
|
|
106
|
+
start({ port }, {
|
|
107
|
+
emitter,
|
|
108
|
+
setEndpoint(endpoint) {
|
|
109
|
+
if (endpoint.host)
|
|
110
|
+
host = endpoint.host;
|
|
111
|
+
if (endpoint.port)
|
|
112
|
+
port = endpoint.port;
|
|
113
|
+
},
|
|
114
|
+
status: self.status,
|
|
115
|
+
})
|
|
116
|
+
.then(() => {
|
|
117
|
+
if (startTimer)
|
|
118
|
+
clearTimeout(startTimer);
|
|
119
|
+
if (timedOut)
|
|
120
|
+
return;
|
|
121
|
+
status = 'started';
|
|
122
|
+
stopResolver = Promise.withResolvers();
|
|
123
|
+
startResolver.resolve(self.stop.bind(self));
|
|
124
|
+
})
|
|
125
|
+
.catch((error) => {
|
|
126
|
+
if (startTimer)
|
|
127
|
+
clearTimeout(startTimer);
|
|
128
|
+
if (timedOut)
|
|
129
|
+
return;
|
|
130
|
+
status = 'idle';
|
|
131
|
+
self.messages.clear();
|
|
132
|
+
emitter.off('message', onMessage);
|
|
133
|
+
emitter.off('listening', onListening);
|
|
134
|
+
emitter.off('exit', onExit);
|
|
135
|
+
startResolver.reject(error);
|
|
136
|
+
startResolver = Promise.withResolvers();
|
|
137
|
+
});
|
|
138
|
+
return startResolver.promise;
|
|
139
|
+
},
|
|
140
|
+
async stop() {
|
|
141
|
+
if (status === 'stopping')
|
|
142
|
+
return stopResolver.promise;
|
|
143
|
+
if (status === 'starting')
|
|
144
|
+
throw new Error(`Instance "${name}" is starting.`);
|
|
145
|
+
let stopTimer;
|
|
146
|
+
if (typeof timeout === 'number') {
|
|
147
|
+
stopTimer = setTimeout(() => {
|
|
148
|
+
stopResolver.reject(new Error(`Instance "${name}" failed to stop in time.`));
|
|
149
|
+
}, timeout);
|
|
150
|
+
}
|
|
151
|
+
status = 'stopping';
|
|
152
|
+
stop({
|
|
153
|
+
emitter,
|
|
154
|
+
status: self.status,
|
|
155
|
+
})
|
|
156
|
+
.then((...args) => {
|
|
157
|
+
if (stopTimer)
|
|
158
|
+
clearTimeout(stopTimer);
|
|
159
|
+
status = 'stopped';
|
|
160
|
+
self.messages.clear();
|
|
161
|
+
emitter.off('message', onMessage);
|
|
162
|
+
emitter.off('listening', onListening);
|
|
163
|
+
emitter.off('exit', onExit);
|
|
164
|
+
startResolver = Promise.withResolvers();
|
|
165
|
+
stopResolver.resolve(...args);
|
|
166
|
+
})
|
|
167
|
+
.catch((error) => {
|
|
168
|
+
if (stopTimer)
|
|
169
|
+
clearTimeout(stopTimer);
|
|
170
|
+
status = 'started';
|
|
171
|
+
stopResolver.reject(error);
|
|
172
|
+
stopResolver = Promise.withResolvers();
|
|
173
|
+
});
|
|
174
|
+
return stopResolver.promise;
|
|
175
|
+
},
|
|
176
|
+
async restart() {
|
|
177
|
+
if (restarting)
|
|
178
|
+
return restartResolver.promise;
|
|
179
|
+
restarting = true;
|
|
180
|
+
self.stop()
|
|
181
|
+
.then(() => self.start())
|
|
182
|
+
.then(() => {
|
|
183
|
+
restarting = false;
|
|
184
|
+
restartResolver.resolve();
|
|
185
|
+
restartResolver = Promise.withResolvers();
|
|
186
|
+
})
|
|
187
|
+
.catch((error) => {
|
|
188
|
+
restarting = false;
|
|
189
|
+
restartResolver.reject(error);
|
|
190
|
+
restartResolver = Promise.withResolvers();
|
|
191
|
+
});
|
|
192
|
+
return restartResolver.promise;
|
|
193
|
+
},
|
|
194
|
+
on: emitter.on.bind(emitter),
|
|
195
|
+
off: emitter.off.bind(emitter),
|
|
196
|
+
};
|
|
197
|
+
const knownKeys = new Set(['name', 'host', 'port', 'start', 'stop']);
|
|
198
|
+
const extra = {};
|
|
199
|
+
for (const key of Object.keys(raw)) {
|
|
200
|
+
if (!knownKeys.has(key))
|
|
201
|
+
extra[key] = raw[key];
|
|
202
|
+
}
|
|
203
|
+
return Object.assign(self, extra);
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=Instance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Instance.js","sourceRoot":"","sources":["../src/Instance.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,EAAE,EAA8B,MAAM,MAAM,CAAA;AAGvD,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAqE9C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,MAAM,CACpB,EAAwB;IAExB,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,QAAQ,CAAQ,EAAE,EAAE;QACnD,MAAM,iBAAiB,GAAG,CAAC,CAAM,EAAwB,EAAE,CACzD,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,eAAe,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,CAAC,CAAA;QAEhF,wDAAwD;QACxD,iFAAiF;QACjF,MAAM,UAAU,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,CAAC;YACnF,CAAC,CAAC,mBAAmB;YACrB,CAAC,CAAC,SAAS,CAAM,CAAA;QACnB,MAAM,OAAO,GAAoB,QAAQ,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAEhH,MAAM,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,CAAA;QAC1B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,GAAG,CAAA;QACjC,IAAI,IAAI,GAAG,GAAG,CAAC,IAAI,CAAA;QACnB,IAAI,IAAI,GAAG,GAAG,CAAC,IAAI,CAAA;QACnB,MAAM,EAAE,aAAa,GAAG,EAAE,EAAE,OAAO,GAAG,MAAM,EAAE,GAAG,OAAO,CAAA;QAExD,IAAI,aAAa,GAAG,OAAO,CAAC,aAAa,EAAc,CAAA;QACvD,IAAI,YAAY,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;QAChD,IAAI,eAAe,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;QAEnD,MAAM,OAAO,GAAG,IAAI,EAAc,CAAA;QAElC,IAAI,QAAQ,GAAa,EAAE,CAAA;QAC3B,IAAI,MAAM,GAAmB,MAAM,CAAA;QACnC,IAAI,UAAU,GAAG,KAAK,CAAA;QAEtB,MAAM,SAAS,GAAoB,CAAC,OAAO,EAAE,EAAE;YAC7C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACtB,IAAI,QAAQ,CAAC,MAAM,GAAG,aAAa;gBAAE,QAAQ,CAAC,KAAK,EAAE,CAAA;QACvD,CAAC,CAAA;QACD,MAAM,WAAW,GAAG,GAAG,EAAE,GAAG,MAAM,GAAG,SAAS,CAAA,CAAC,CAAC,CAAA;QAChD,MAAM,MAAM,GAAG,GAAG,EAAE,GAAG,MAAM,GAAG,SAAS,CAAA,CAAC,CAAC,CAAA;QAE3C,MAAM,IAAI,GAAG;YACX,IAAI,IAAI,KAAK,OAAO,IAAI,CAAA,CAAC,CAAC;YAC1B,IAAI;YACJ,IAAI,IAAI,KAAK,OAAO,IAAI,CAAA,CAAC,CAAC;YAC1B,IAAI,MAAM;gBACR,IAAI,UAAU;oBAAE,OAAO,YAAY,CAAA;gBACnC,OAAO,MAAM,CAAA;YACf,CAAC;YACD,QAAQ,EAAE;gBACR,KAAK,KAAK,QAAQ,GAAG,EAAE,CAAA,CAAC,CAAC;gBACzB,GAAG,KAAK,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAA,CAAC,CAAC;aAC/B;YAED,KAAK,CAAC,KAAK;gBACT,IAAI,MAAM,KAAK,UAAU;oBAAE,OAAO,aAAa,CAAC,OAAO,CAAA;gBACvD,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,SAAS;oBAC3C,MAAM,IAAI,KAAK,CAAC,aAAa,IAAI,iDAAiD,MAAM,EAAE,CAAC,CAAA;gBAE7F,IAAI,UAAqD,CAAA;gBACzD,sEAAsE;gBACtE,iEAAiE;gBACjE,iEAAiE;gBACjE,IAAI,QAAQ,GAAG,KAAK,CAAA;gBACpB,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;oBAChC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;wBAC3B,QAAQ,GAAG,IAAI,CAAA;wBACf,MAAM,GAAG,MAAM,CAAA;wBACf,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;wBACrB,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;wBACjC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;wBACrC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;wBAC3B,aAAa,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,aAAa,IAAI,4BAA4B,CAAC,CAAC,CAAA;wBAC9E,aAAa,GAAG,OAAO,CAAC,aAAa,EAAc,CAAA;wBACnD,+DAA+D;wBAC/D,kEAAkE;wBAClE,gEAAgE;wBAChE,IAAI,CAAC;4BACH,KAAK,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;wBAC7D,CAAC;wBAAC,MAAM,CAAC;4BACP,SAAS;wBACX,CAAC;oBACH,CAAC,EAAE,OAAO,CAAC,CAAA;gBACb,CAAC;gBAED,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;gBAChC,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;gBACpC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;gBAE1B,MAAM,GAAG,UAAU,CAAA;gBACnB,KAAK,CACH,EAAE,IAAI,EAAE,EACR;oBACE,OAAO;oBACP,WAAW,CAAC,QAAQ;wBAClB,IAAI,QAAQ,CAAC,IAAI;4BAAE,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAA;wBACvC,IAAI,QAAQ,CAAC,IAAI;4BAAE,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAA;oBACzC,CAAC;oBACD,MAAM,EAAE,IAAI,CAAC,MAAM;iBACpB,CACF;qBACE,IAAI,CAAC,GAAG,EAAE;oBACT,IAAI,UAAU;wBAAE,YAAY,CAAC,UAAU,CAAC,CAAA;oBACxC,IAAI,QAAQ;wBAAE,OAAM;oBACpB,MAAM,GAAG,SAAS,CAAA;oBAClB,YAAY,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;oBAC5C,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;gBAC7C,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;oBACf,IAAI,UAAU;wBAAE,YAAY,CAAC,UAAU,CAAC,CAAA;oBACxC,IAAI,QAAQ;wBAAE,OAAM;oBACpB,MAAM,GAAG,MAAM,CAAA;oBACf,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;oBACrB,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;oBACjC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;oBACrC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;oBAC3B,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;oBAC3B,aAAa,GAAG,OAAO,CAAC,aAAa,EAAc,CAAA;gBACrD,CAAC,CAAC,CAAA;gBAEJ,OAAO,aAAa,CAAC,OAAO,CAAA;YAC9B,CAAC;YAED,KAAK,CAAC,IAAI;gBACR,IAAI,MAAM,KAAK,UAAU;oBAAE,OAAO,YAAY,CAAC,OAAO,CAAA;gBACtD,IAAI,MAAM,KAAK,UAAU;oBAAE,MAAM,IAAI,KAAK,CAAC,aAAa,IAAI,gBAAgB,CAAC,CAAA;gBAE7E,IAAI,SAAoD,CAAA;gBACxD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;oBAChC,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;wBAC1B,YAAY,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,aAAa,IAAI,2BAA2B,CAAC,CAAC,CAAA;oBAC9E,CAAC,EAAE,OAAO,CAAC,CAAA;gBACb,CAAC;gBAED,MAAM,GAAG,UAAU,CAAA;gBACnB,IAAI,CAAC;oBACH,OAAO;oBACP,MAAM,EAAE,IAAI,CAAC,MAAM;iBACpB,CAAC;qBACC,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE;oBAChB,IAAI,SAAS;wBAAE,YAAY,CAAC,SAAS,CAAC,CAAA;oBACtC,MAAM,GAAG,SAAS,CAAA;oBAClB,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;oBACrB,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;oBACjC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;oBACrC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;oBAC3B,aAAa,GAAG,OAAO,CAAC,aAAa,EAAc,CAAA;oBACnD,YAAY,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAA;gBAC/B,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;oBACf,IAAI,SAAS;wBAAE,YAAY,CAAC,SAAS,CAAC,CAAA;oBACtC,MAAM,GAAG,SAAS,CAAA;oBAClB,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;oBAC1B,YAAY,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;gBAC9C,CAAC,CAAC,CAAA;gBAEJ,OAAO,YAAY,CAAC,OAAO,CAAA;YAC7B,CAAC;YAED,KAAK,CAAC,OAAO;gBACX,IAAI,UAAU;oBAAE,OAAO,eAAe,CAAC,OAAO,CAAA;gBAE9C,UAAU,GAAG,IAAI,CAAA;gBAEjB,IAAI,CAAC,IAAI,EAAE;qBACR,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;qBACxB,IAAI,CAAC,GAAG,EAAE;oBACT,UAAU,GAAG,KAAK,CAAA;oBAClB,eAAe,CAAC,OAAO,EAAE,CAAA;oBACzB,eAAe,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;gBACjD,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;oBACf,UAAU,GAAG,KAAK,CAAA;oBAClB,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;oBAC7B,eAAe,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;gBACjD,CAAC,CAAC,CAAA;gBAEJ,OAAO,eAAe,CAAC,OAAO,CAAA;YAChC,CAAC;YAED,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC;YAC5B,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;SACZ,CAAA;QAEpB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;QACpE,MAAM,KAAK,GAA4B,EAAE,CAAA;QACzC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,KAAK,CAAC,GAAG,CAAC,GAAI,GAA+B,CAAC,GAAG,CAAC,CAAA;QAC7E,CAAC;QAED,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAA6C,CAAA;IAC/E,CAAC,CAAA;AACH,CAAC"}
|