2g 0.0.1-0.wip → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/LICENSE.md +22 -0
- package/README.md +323 -0
- package/api/package.json +17 -0
- package/bin/cli.js +19 -0
- package/cli/package.json +17 -0
- package/dist/2g-api.d.ts +52 -0
- package/dist/2g-api.js +129 -0
- package/dist/2g-api.js.map +1 -0
- package/dist/2g-api.mjs +121 -0
- package/dist/2g-api.mjs.map +1 -0
- package/dist/2g-cli.d.ts +4 -0
- package/dist/2g-cli.js +11 -0
- package/dist/2g-cli.js.map +1 -0
- package/dist/2g-cli.mjs +5 -0
- package/dist/2g-cli.mjs.map +1 -0
- package/dist/2g.d.ts +37 -0
- package/dist/2g.js +1184 -0
- package/dist/2g.js.map +1 -0
- package/dist/2g.mjs +1180 -0
- package/dist/2g.mjs.map +1 -0
- package/dist/chunks/cli-chunk.js +807 -0
- package/dist/chunks/cli-chunk.js.map +1 -0
- package/dist/chunks/cli-chunk.mjs +801 -0
- package/dist/chunks/cli-chunk.mjs.map +1 -0
- package/dist/chunks/index-chunk.js +176 -0
- package/dist/chunks/index-chunk.js.map +1 -0
- package/dist/chunks/index-chunk.mjs +176 -0
- package/dist/chunks/index-chunk.mjs.map +1 -0
- package/dist/chunks/pretty-chunk.js +77 -0
- package/dist/chunks/pretty-chunk.js.map +1 -0
- package/dist/chunks/pretty-chunk.mjs +79 -0
- package/dist/chunks/pretty-chunk.mjs.map +1 -0
- package/dist/chunks/sessionSockets-chunk.js +348 -0
- package/dist/chunks/sessionSockets-chunk.js.map +1 -0
- package/dist/chunks/sessionSockets-chunk.mjs +332 -0
- package/dist/chunks/sessionSockets-chunk.mjs.map +1 -0
- package/dist/chunks/tap-chunk.js +287 -0
- package/dist/chunks/tap-chunk.js.map +1 -0
- package/dist/chunks/tap-chunk.mjs +287 -0
- package/dist/chunks/tap-chunk.mjs.map +1 -0
- package/dist/chunks/types-chunk.d.ts +127 -0
- package/package.json +96 -11
- package/.editorconfig +0 -9
- package/bsconfig.json +0 -25
- package/build_script.re +0 -12
- package/example/Example.re +0 -56
- package/include/glad/glad.c +0 -11881
- package/include/glad/glad.h +0 -19876
- package/include/glad/khrplatform.h +0 -290
- package/include/platform/package.json +0 -12
- package/include/platform/postinstall.js +0 -45
- package/include/platform/src/g2platform.ml +0 -3
- package/include/platform/src/g2platform.re +0 -3
- package/include/sokol/sokol.c +0 -2
- package/include/sokol/sokol.h +0 -11
- package/include/sokol/sokol_app.h +0 -7074
- package/include/sokol/sokol_gfx.h +0 -10557
- package/src/g2.c +0 -237
- package/src/g2.re +0 -25
package/LICENSE.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Phil Pluckthun,
|
|
4
|
+
Copyright (c) 650 Industries, Inc. (aka Expo),
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h2>2g</h2>
|
|
3
|
+
<strong>Low-overhead structured event logs for agents and developers</strong>
|
|
4
|
+
<br />
|
|
5
|
+
<br />
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
`2g` is a structured event logger for command-line tools. It writes JSONL logs
|
|
9
|
+
that can be replayed, tailed, or exported to trace formats.
|
|
10
|
+
|
|
11
|
+
In short, `2g`,
|
|
12
|
+
|
|
13
|
+
- keeps logging cheap enough to leave enabled
|
|
14
|
+
- writes bounded session logs to the system temporary directory
|
|
15
|
+
- forwards worker and child-process events into the same session
|
|
16
|
+
- lets tools and agents tap into running commands
|
|
17
|
+
- derives typed event payloads through TypeScript declaration merging
|
|
18
|
+
|
|
19
|
+
## Implementation
|
|
20
|
+
|
|
21
|
+
The goal of `2g` is to replace ad-hoc `debug` logging with structured events
|
|
22
|
+
that are always recorded, without instrumented code paths paying for it. A disabled logging
|
|
23
|
+
call is a single property check, and an enabled call serializes straight into a batched
|
|
24
|
+
file stream — there is no interchange format, no transport negotiation, and no
|
|
25
|
+
per-event allocation beyond the payload the caller already builds.
|
|
26
|
+
|
|
27
|
+
This has been built with the explicit purpose in mind to:
|
|
28
|
+
|
|
29
|
+
- Instrument CLI processes that spawn workers and child processes, unifying their events
|
|
30
|
+
into one session log
|
|
31
|
+
- Keep a bounded, self-cleaning record of recent sessions that tooling and agents can
|
|
32
|
+
discover and replay after the fact
|
|
33
|
+
- Tap into live processes without those processes knowing or slowing down
|
|
34
|
+
|
|
35
|
+
As such, sessions write newline-framed JSONL through a single batched fd writer, rotate
|
|
36
|
+
across fixed-size segments, and broadcast to live subscribers over a local socket. Child
|
|
37
|
+
processes and workers inherit the session over an IPC socket published through the
|
|
38
|
+
environment, so the whole process tree agrees on one destination.
|
|
39
|
+
|
|
40
|
+
The package ships two entrypoints so consumers don't load the producer graph and vice
|
|
41
|
+
versa:
|
|
42
|
+
|
|
43
|
+
- `2g` — the producer surface instrumented processes import
|
|
44
|
+
- `2g/api` — the consumption surface for tooling that reads sessions
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
Install logging once when a CLI command starts, then create loggers in any package:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { installEventLogger, events } from '2g';
|
|
52
|
+
|
|
53
|
+
installEventLogger({ command: 'expo start -p web', version: '1.0.0' });
|
|
54
|
+
|
|
55
|
+
const log = events('metro');
|
|
56
|
+
|
|
57
|
+
log('ready', { port: 8081 });
|
|
58
|
+
|
|
59
|
+
const end = log.span('bundle', { platform: 'ios' });
|
|
60
|
+
end('bundle', { cached: false });
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Tap into the session from another terminal:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
2g tap "expo start" --filter metro:* --tail
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Behavior
|
|
70
|
+
|
|
71
|
+
### Activation
|
|
72
|
+
|
|
73
|
+
`installEventLogger` resolves its destination in precedence order:
|
|
74
|
+
|
|
75
|
+
1. An explicit `LOG_EVENTS` target — an fd number or a file path, from the environment or
|
|
76
|
+
passed directly
|
|
77
|
+
2. A parent 2g process, inherited through the environment; events forward into the
|
|
78
|
+
parent's session over IPC
|
|
79
|
+
3. A session in the system temporary directory, when an options object is passed
|
|
80
|
+
4. Otherwise logging stays inactive and all calls are no-ops
|
|
81
|
+
|
|
82
|
+
Calling `installEventLogger()` with no arguments activates logging only when `LOG_EVENTS`
|
|
83
|
+
or a parent process is present — the right idiom for child processes, workers, and
|
|
84
|
+
libraries. Passing `session: false` keeps this env-only activation while still providing
|
|
85
|
+
options. There is no uninstall: logging is installed once per process and torn down on
|
|
86
|
+
exit.
|
|
87
|
+
|
|
88
|
+
When `LOG_EVENTS` targets stdout, `console` is transparently redirected to stderr so
|
|
89
|
+
event output stays machine-readable.
|
|
90
|
+
|
|
91
|
+
### Sessions
|
|
92
|
+
|
|
93
|
+
Sessions live under `event-log-<uid>` in the system temporary directory. Each
|
|
94
|
+
session directory holds rotating JSONL segments (`0.jsonl` is current), a `meta.json`
|
|
95
|
+
describing the process, and local sockets for live subscribers and child IPC. With the
|
|
96
|
+
defaults of 3 segments × 512 KiB, a session retains roughly the last 12k events.
|
|
97
|
+
|
|
98
|
+
Stale sessions are cleaned opportunistically: exited sessions are removed after 7 days,
|
|
99
|
+
and only the newest 100 exited sessions are kept. An unwritable temporary directory
|
|
100
|
+
disables session capture without affecting the host process.
|
|
101
|
+
|
|
102
|
+
### Debug events
|
|
103
|
+
|
|
104
|
+
`events.debug(category)` creates a logger for chatty, debug-level events. Debug events
|
|
105
|
+
carry `_l: 1` on the wire. Session output drops them at the emit site — keeping the
|
|
106
|
+
rotation budget for normal history — unless `LOG_DEBUG` is set or `installEventLogger`
|
|
107
|
+
is passed `debug: true`; explicit `LOG_EVENTS` targets record them. `tap` and `export`
|
|
108
|
+
skip debug events unless `--debug` (CLI) or `debug: true` (API) is passed.
|
|
109
|
+
|
|
110
|
+
`LOG_DEBUG` also prints matching events to stderr in a readable format while structured
|
|
111
|
+
logging continues unchanged:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
LOG_DEBUG=metro:* expo start
|
|
115
|
+
LOG_DEBUG=* expo export
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Deferred payload helpers
|
|
119
|
+
|
|
120
|
+
`log.path(absolutePath)` logs paths relative to the log target, and `log.error(error)`
|
|
121
|
+
serializes an error to `{ name, message, code, stack, cause }`. Both return
|
|
122
|
+
`Serialized<T>` wrappers — `{ toJSON(): T }` — that only do their work when an event is
|
|
123
|
+
written, so disabled loggers skip `path.relative` calls and `error.stack`
|
|
124
|
+
materialization entirely. Payloads accept `Serialized<T>` wherever their declared types
|
|
125
|
+
expect `T`, so `EventRegistry` declarations keep using the wire shapes.
|
|
126
|
+
|
|
127
|
+
### Typed events
|
|
128
|
+
|
|
129
|
+
Extend `EventRegistry` to type event payloads; keys are `category:kind` and payloads
|
|
130
|
+
merge across packages through declaration merging:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
declare module '2g' {
|
|
134
|
+
interface EventRegistry {
|
|
135
|
+
'metro:ready': { port: number };
|
|
136
|
+
'metro:bundle': { platform?: string; cached?: boolean };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Unknown event names are uncallable, reserved wire fields (`_e`, `_t`, `_d`, `_l`, `_w`)
|
|
142
|
+
are rejected in payload types, and payloads with no required keys become optional
|
|
143
|
+
arguments. `2g typegen` merges the registry declarations of a whole project into
|
|
144
|
+
one schema.
|
|
145
|
+
|
|
146
|
+
## CLI
|
|
147
|
+
|
|
148
|
+
Use the CLI to find sessions, replay logs, or export traces:
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
2g ps --json
|
|
152
|
+
2g tap "expo start" --filter metro:* --tail
|
|
153
|
+
2g export "expo start" --format chrome-trace -o trace.json
|
|
154
|
+
2g export "expo start" --format opentelemetry -o otel.json
|
|
155
|
+
2g typegen --project tsconfig.json --format dts
|
|
156
|
+
2g clean --json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Selectors match a session by PID, command, session directory, or working directory.
|
|
160
|
+
`tap` and `export` replay retained history first. `--tail` appends live events; an event
|
|
161
|
+
written in the instant `--tail` attaches may appear twice at the seam.
|
|
162
|
+
|
|
163
|
+
Open Chrome traces in [ui.perfetto.dev](https://ui.perfetto.dev) or via Chrome DevTools →
|
|
164
|
+
Performance → "Load profile" (renders as a plain flame chart); `chrome://tracing` is
|
|
165
|
+
deprecated. For a near-live view of a running command, tail the export:
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
2g export "expo start" --tail -o trace.json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Testing
|
|
172
|
+
|
|
173
|
+
Capture a subprocess's events in integration tests by handing it a pipe as its
|
|
174
|
+
`LOG_EVENTS` target:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { spawn } from 'node:child_process';
|
|
178
|
+
import { captureEvents } from '2g/api';
|
|
179
|
+
|
|
180
|
+
const capture = captureEvents({ filter: 'metro:*' });
|
|
181
|
+
const child = spawn(
|
|
182
|
+
'expo',
|
|
183
|
+
['export'],
|
|
184
|
+
capture.spawnOptions({ env: process.env })
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const events = await capture.attach(child).collect();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`spawnOptions` appends a pipe to `stdio` and points `LOG_EVENTS` at it; `attach`
|
|
191
|
+
consumes the pipe and parses events, and can also be iterated with `for await` for live
|
|
192
|
+
consumption. Iteration ends when the pipe closes, so every event the child wrote is
|
|
193
|
+
received.
|
|
194
|
+
|
|
195
|
+
> [!IMPORTANT]
|
|
196
|
+
> The child's events are flushed on natural exit. Child code that calls `process.exit()`
|
|
197
|
+
> should `await flushEventLogger()` first or trailing events may be lost.
|
|
198
|
+
|
|
199
|
+
## API Reference
|
|
200
|
+
|
|
201
|
+
### `2g`
|
|
202
|
+
|
|
203
|
+
The producer entry that instrumented processes import.
|
|
204
|
+
|
|
205
|
+
#### `events(category: string) => EventLogger`
|
|
206
|
+
|
|
207
|
+
Returns a typed logger for `category`. The logger is callable directly and carries
|
|
208
|
+
helpers:
|
|
209
|
+
|
|
210
|
+
- `log(event, data?)`: writes a `category:event` line with the payload
|
|
211
|
+
- `log.span(event, data?)`: starts a span; returns an `end(event, data?)` function that
|
|
212
|
+
writes a single event carrying the duration as `_d`
|
|
213
|
+
- `log.path(target)`: returns a `Serialized<string>` of the path relative to the log
|
|
214
|
+
target
|
|
215
|
+
- `log.error(error)`: returns a `Serialized<SerializedError>` of
|
|
216
|
+
`{ name, message, code, stack, cause }`, with cause chains serialized recursively
|
|
217
|
+
- `log.category`: the category string
|
|
218
|
+
|
|
219
|
+
Span kinds ending in `:started`, `:done`, or `:failed` are naming conventions; trace
|
|
220
|
+
exporters strip the suffix from displayed names.
|
|
221
|
+
|
|
222
|
+
#### `events.debug(category: string) => EventLogger`
|
|
223
|
+
|
|
224
|
+
Identical to `events(category)`, but events are debug-level (`_l: 1`) and subject to
|
|
225
|
+
debug gating.
|
|
226
|
+
|
|
227
|
+
#### `installEventLogger(target?: string | number | InstallEventLoggerOptions) => void`
|
|
228
|
+
|
|
229
|
+
- Accepts an explicit target (fd number or file path) or an options object
|
|
230
|
+
|
|
231
|
+
Installs logging for the process following the activation precedence above. All options
|
|
232
|
+
are optional:
|
|
233
|
+
|
|
234
|
+
| Option | Default | Description |
|
|
235
|
+
| ---------------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
236
|
+
| `command` | argv | Command line recorded in session metadata |
|
|
237
|
+
| `version` | — | Tool version recorded in session metadata and the init event |
|
|
238
|
+
| `maxSegments` | `3` | Rotated JSONL segments kept per session |
|
|
239
|
+
| `maxSegmentSize` | 512 KiB | Segment size that triggers rotation |
|
|
240
|
+
| `session` | `true` | Allow the session fall-through; `false` keeps env-only activation |
|
|
241
|
+
| `debug` | varies | Record debug events; defaults to `true` on explicit targets and to `LOG_DEBUG` presence on session and IPC output |
|
|
242
|
+
|
|
243
|
+
#### `flushEventLogger() => Promise<void>`
|
|
244
|
+
|
|
245
|
+
Drains buffered events to the active destination. Resolves immediately when logging is
|
|
246
|
+
inactive.
|
|
247
|
+
|
|
248
|
+
#### `getEventLoggerInfo() => EventLoggerInfo | null`
|
|
249
|
+
|
|
250
|
+
Returns the active destination — `{ destination, isUserVisibleOutput }` plus a `file`,
|
|
251
|
+
`fd`, or `sessionDir` — or `null` when logging is inactive. `isUserVisibleOutput` is
|
|
252
|
+
`true` when events write to stdout or stderr.
|
|
253
|
+
|
|
254
|
+
#### `interface Serialized<T>`
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
interface Serialized<T> {
|
|
258
|
+
toJSON(): T;
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
A deferred value that serializes to `T` when an event is written. Payload types accept
|
|
263
|
+
`Serialized<T>` wherever `T` is declared, so user code can defer its own expensive
|
|
264
|
+
values the same way.
|
|
265
|
+
|
|
266
|
+
### `2g/api`
|
|
267
|
+
|
|
268
|
+
The consumption entry for tooling that reads sessions.
|
|
269
|
+
|
|
270
|
+
#### `list(options?: ListSessionsOptions) => Promise<ListedSession[]>`
|
|
271
|
+
|
|
272
|
+
- **Parameters**
|
|
273
|
+
- `selector?`: matches by PID, command, session directory, or working directory
|
|
274
|
+
|
|
275
|
+
Returns known sessions, newest first. Each `ListedSession` carries `pid`, `command`,
|
|
276
|
+
`cwd`, `startedAt`, `alive`, and `sessionDir`.
|
|
277
|
+
|
|
278
|
+
#### `resolveSession(selector?: string) => Promise<ListedSession>`
|
|
279
|
+
|
|
280
|
+
Resolves a selector to exactly one session, preferring the only alive match. Throws when
|
|
281
|
+
nothing matches or the selector is ambiguous.
|
|
282
|
+
|
|
283
|
+
#### `tap(sessionDir: string, options?: TapOptions) => AsyncIterable<ParsedEvent>`
|
|
284
|
+
|
|
285
|
+
Replays a session's retained history and optionally follows live events.
|
|
286
|
+
|
|
287
|
+
| Option | Default | Description |
|
|
288
|
+
| ------------- | ------- | --------------------------------------------------------------- |
|
|
289
|
+
| `since` | — | Replay from a duration (`'5m'`), Unix time, ISO date, or `Date` |
|
|
290
|
+
| `follow` | `false` | Follow live events after replaying history |
|
|
291
|
+
| `filter` | — | `category[:kind]` patterns; `*` wildcards, comma-separated |
|
|
292
|
+
| `spans` | `false` | Only yield events carrying a span duration |
|
|
293
|
+
| `debug` | `false` | Include debug-level events |
|
|
294
|
+
| `signal` | — | `AbortSignal` that stops following |
|
|
295
|
+
| `timeout` | — | Stop following after an absolute duration in milliseconds |
|
|
296
|
+
| `idleTimeout` | — | Stop following after this many milliseconds without a new event |
|
|
297
|
+
|
|
298
|
+
#### `captureEvents(options?: CaptureOptions) => EventCapture`
|
|
299
|
+
|
|
300
|
+
- **Parameters**
|
|
301
|
+
- `filter?` and `debug?`: as in `TapOptions`
|
|
302
|
+
|
|
303
|
+
Returns an `EventCapture` for receiving a subprocess's events over an inherited pipe:
|
|
304
|
+
|
|
305
|
+
- `capture.spawnOptions(options?)`: returns spawn options with a `'pipe'` slot appended
|
|
306
|
+
to `stdio` and `LOG_EVENTS` pointing at it, preserving the caller's `env` and `stdio`
|
|
307
|
+
- `capture.attach(child)`: consumes the child's pipe; the capture is an
|
|
308
|
+
`AsyncIterable<ParsedEvent>` that ends when the pipe closes
|
|
309
|
+
- `capture.collect()`: iterates to the end and returns all events
|
|
310
|
+
|
|
311
|
+
#### `interface ParsedEvent`
|
|
312
|
+
|
|
313
|
+
The wire format of a parsed JSONL event; all other properties are payload fields.
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
interface ParsedEvent {
|
|
317
|
+
_e: string; // "category:kind"
|
|
318
|
+
_t: number; // wall-clock timestamp in milliseconds
|
|
319
|
+
_d?: number; // span duration in milliseconds
|
|
320
|
+
_l?: number; // level; 1 marks debug events
|
|
321
|
+
_w?: string; // originating worker or child process
|
|
322
|
+
}
|
|
323
|
+
```
|
package/api/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "2g-api",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"main": "../dist/2g-api.js",
|
|
6
|
+
"module": "../dist/2g-api.mjs",
|
|
7
|
+
"types": "../dist/2g-api.d.ts",
|
|
8
|
+
"source": "../src/api.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "../dist/2g-api.d.ts",
|
|
12
|
+
"import": "../dist/2g-api.mjs",
|
|
13
|
+
"require": "../dist/2g-api.js",
|
|
14
|
+
"source": "../src/api.ts"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
(async function main() {
|
|
4
|
+
let cli;
|
|
5
|
+
try {
|
|
6
|
+
cli = await import('../dist/2g-cli.mjs');
|
|
7
|
+
} catch {
|
|
8
|
+
cli = require('../dist/2g-cli.js');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
process.exitCode = await cli.main(process.argv.slice(2));
|
|
13
|
+
} catch (error) {
|
|
14
|
+
process.stderr.write(
|
|
15
|
+
`${error instanceof Error ? error.message : String(error)}\n`
|
|
16
|
+
);
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
}
|
|
19
|
+
})();
|
package/cli/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "2g-cli",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"main": "../dist/2g-cli.js",
|
|
6
|
+
"module": "../dist/2g-cli.mjs",
|
|
7
|
+
"types": "../dist/2g-cli.d.ts",
|
|
8
|
+
"source": "../src/cli.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "../dist/2g-cli.d.ts",
|
|
12
|
+
"import": "../dist/2g-cli.mjs",
|
|
13
|
+
"require": "../dist/2g-cli.js",
|
|
14
|
+
"source": "../src/cli.ts"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
package/dist/2g-api.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { S as SessionMeta, P as ParsedEvent } from './chunks/types-chunk.js';
|
|
2
|
+
import { StdioOptions, ChildProcess } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
interface EventFilterOptions {
|
|
5
|
+
since?: number | string | Date;
|
|
6
|
+
filter?: string | string[];
|
|
7
|
+
spans?: boolean;
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ListedSession {
|
|
12
|
+
id: string;
|
|
13
|
+
pid: number;
|
|
14
|
+
formatVersion: number;
|
|
15
|
+
alive: boolean;
|
|
16
|
+
startedAt: number;
|
|
17
|
+
command: string;
|
|
18
|
+
cwd: string;
|
|
19
|
+
version?: string;
|
|
20
|
+
origin?: SessionMeta['origin'];
|
|
21
|
+
sessionDir: string;
|
|
22
|
+
}
|
|
23
|
+
interface ListSessionsOptions {
|
|
24
|
+
selector?: string;
|
|
25
|
+
}
|
|
26
|
+
interface TapOptions extends EventFilterOptions {
|
|
27
|
+
follow?: boolean;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
timeout?: number;
|
|
30
|
+
idleTimeout?: number;
|
|
31
|
+
}
|
|
32
|
+
declare function listSessions(options?: ListSessionsOptions): Promise<ListedSession[]>;
|
|
33
|
+
declare function resolveSession(selector?: string): Promise<ListedSession>;
|
|
34
|
+
declare function tap(sessionDir: string, options?: TapOptions): AsyncIterable<ParsedEvent>;
|
|
35
|
+
|
|
36
|
+
type CaptureOptions = Pick<TapOptions, 'debug' | 'filter'>;
|
|
37
|
+
interface CaptureSpawnOptions {
|
|
38
|
+
env?: NodeJS.ProcessEnv;
|
|
39
|
+
stdio?: StdioOptions;
|
|
40
|
+
}
|
|
41
|
+
declare class EventCapture implements AsyncIterable<ParsedEvent> {
|
|
42
|
+
#private;
|
|
43
|
+
constructor(options?: CaptureOptions);
|
|
44
|
+
spawnOptions<T extends CaptureSpawnOptions>(options?: T): T & Required<CaptureSpawnOptions>;
|
|
45
|
+
attach(child: ChildProcess): this;
|
|
46
|
+
collect(): Promise<ParsedEvent[]>;
|
|
47
|
+
[Symbol.asyncIterator](): AsyncIterator<ParsedEvent>;
|
|
48
|
+
}
|
|
49
|
+
declare function captureEvents(options?: CaptureOptions): EventCapture;
|
|
50
|
+
|
|
51
|
+
export { EventCapture, ParsedEvent, captureEvents, listSessions as list, resolveSession, tap };
|
|
52
|
+
export type { CaptureOptions, CaptureSpawnOptions, ListSessionsOptions, ListedSession, TapOptions };
|
package/dist/2g-api.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Object.defineProperty(exports, "__esModule", {
|
|
2
|
+
value: !0
|
|
3
|
+
});
|
|
4
|
+
|
|
5
|
+
var t = require("./chunks/tap-chunk.js");
|
|
6
|
+
|
|
7
|
+
var e = require("./chunks/sessionSockets-chunk.js");
|
|
8
|
+
|
|
9
|
+
class EventCapture {
|
|
10
|
+
#t;
|
|
11
|
+
#e;
|
|
12
|
+
#s;
|
|
13
|
+
#i=!1;
|
|
14
|
+
#n=!1;
|
|
15
|
+
#r=[];
|
|
16
|
+
#o=[];
|
|
17
|
+
constructor(t = {}) {
|
|
18
|
+
this.#t = t;
|
|
19
|
+
this.#e = e.compileEventFilter(t.filter);
|
|
20
|
+
}
|
|
21
|
+
spawnOptions(t) {
|
|
22
|
+
const s = Array.isArray(t?.stdio) ? [ ...t.stdio ] : null != t?.stdio ? [ t.stdio, t.stdio, t.stdio ] : [];
|
|
23
|
+
while (s.length < 3) {
|
|
24
|
+
s.push("inherit");
|
|
25
|
+
}
|
|
26
|
+
this.#s = s.length;
|
|
27
|
+
s.push("pipe");
|
|
28
|
+
return {
|
|
29
|
+
...t,
|
|
30
|
+
stdio: s,
|
|
31
|
+
env: {
|
|
32
|
+
...t?.env ?? process.env,
|
|
33
|
+
[e.LOG_EVENTS_ENV]: `${this.#s}`
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
attach(t) {
|
|
38
|
+
if (null == this.#s) {
|
|
39
|
+
throw new Error("captureEvents: spawnOptions() must create the pipe before attach()");
|
|
40
|
+
}
|
|
41
|
+
if (this.#i) {
|
|
42
|
+
throw new Error("captureEvents: a child is already attached");
|
|
43
|
+
}
|
|
44
|
+
const e = t.stdio[this.#s];
|
|
45
|
+
if (!e || "function" != typeof e.setEncoding) {
|
|
46
|
+
throw new Error(`captureEvents: child stdio[${this.#s}] is not a readable pipe`);
|
|
47
|
+
}
|
|
48
|
+
this.#i = !0;
|
|
49
|
+
let s = "";
|
|
50
|
+
e.setEncoding("utf8");
|
|
51
|
+
e.on("data", t => {
|
|
52
|
+
const e = s ? s + t : t;
|
|
53
|
+
let i = 0;
|
|
54
|
+
let n;
|
|
55
|
+
while (-1 !== (n = e.indexOf("\n", i))) {
|
|
56
|
+
const t = e.slice(i, n);
|
|
57
|
+
i = n + 1;
|
|
58
|
+
if (t) {
|
|
59
|
+
this.#h(t);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
s = i ? e.slice(i) : e;
|
|
63
|
+
});
|
|
64
|
+
const finish = () => {
|
|
65
|
+
if (this.#n) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.#n = !0;
|
|
69
|
+
if (s) {
|
|
70
|
+
this.#h(s);
|
|
71
|
+
}
|
|
72
|
+
this.#a(null);
|
|
73
|
+
};
|
|
74
|
+
e.on("end", finish);
|
|
75
|
+
e.on("close", finish);
|
|
76
|
+
e.on("error", finish);
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
async collect() {
|
|
80
|
+
const t = [];
|
|
81
|
+
for await (const e of this) {
|
|
82
|
+
t.push(e);
|
|
83
|
+
}
|
|
84
|
+
return t;
|
|
85
|
+
}
|
|
86
|
+
async* [Symbol.asyncIterator]() {
|
|
87
|
+
let t;
|
|
88
|
+
while (t = await this.#c()) {
|
|
89
|
+
yield t;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
#h(t) {
|
|
93
|
+
const s = e.parseEventLine(t, this.#t, this.#e);
|
|
94
|
+
if (s) {
|
|
95
|
+
this.#a(s);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
#a(t) {
|
|
99
|
+
const e = this.#o.shift();
|
|
100
|
+
if (e) {
|
|
101
|
+
e(t);
|
|
102
|
+
} else if (t) {
|
|
103
|
+
this.#r.push(t);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
#c() {
|
|
107
|
+
if (this.#r.length) {
|
|
108
|
+
return Promise.resolve(this.#r.shift());
|
|
109
|
+
}
|
|
110
|
+
if (this.#n) {
|
|
111
|
+
return Promise.resolve(null);
|
|
112
|
+
}
|
|
113
|
+
if (!this.#i) {
|
|
114
|
+
throw new Error("captureEvents: attach() a child before iterating");
|
|
115
|
+
}
|
|
116
|
+
return new Promise(t => this.#o.push(t));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
exports.list = t.listSessions;
|
|
121
|
+
|
|
122
|
+
exports.resolveSession = t.resolveSession;
|
|
123
|
+
|
|
124
|
+
exports.tap = t.tap;
|
|
125
|
+
|
|
126
|
+
exports.captureEvents = function captureEvents(t) {
|
|
127
|
+
return new EventCapture(t);
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=2g-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"2g-api.js","sources":["../src/capture.ts"],"sourcesContent":["import type { ChildProcess, StdioOptions } from 'node:child_process';\nimport type { Readable } from 'node:stream';\n\nimport { LOG_EVENTS_ENV } from './constants';\nimport type { TapOptions } from './tap';\nimport type { ParsedEvent } from './types';\nimport { compileEventFilter, parseEventLine } from './utils/eventFilter';\n\nexport type CaptureOptions = Pick<TapOptions, 'debug' | 'filter'>;\n\nexport interface CaptureSpawnOptions {\n env?: NodeJS.ProcessEnv;\n stdio?: StdioOptions;\n}\n\nexport class EventCapture implements AsyncIterable<ParsedEvent> {\n #options: CaptureOptions;\n #eventFilter: RegExp | null;\n #index: number | undefined;\n #attached = false;\n #done = false;\n #events: ParsedEvent[] = [];\n #waiters: Array<(event: ParsedEvent | null) => void> = [];\n\n constructor(options: CaptureOptions = {}) {\n this.#options = options;\n this.#eventFilter = compileEventFilter(options.filter);\n }\n\n spawnOptions<T extends CaptureSpawnOptions>(\n options?: T\n ): T & Required<CaptureSpawnOptions> {\n const stdio: Extract<StdioOptions, readonly unknown[]> = Array.isArray(\n options?.stdio\n )\n ? [...options.stdio]\n : options?.stdio != null\n ? [options.stdio, options.stdio, options.stdio]\n : [];\n while (stdio.length < 3) stdio.push('inherit');\n this.#index = stdio.length;\n stdio.push('pipe');\n return {\n ...(options as T),\n stdio,\n env: {\n ...(options?.env ?? process.env),\n [LOG_EVENTS_ENV]: `${this.#index}`,\n },\n };\n }\n\n attach(child: ChildProcess): this {\n if (this.#index == null)\n throw new Error(\n 'captureEvents: spawnOptions() must create the pipe before attach()'\n );\n if (this.#attached)\n throw new Error('captureEvents: a child is already attached');\n const stream = child.stdio[this.#index] as Readable | null | undefined;\n if (!stream || typeof stream.setEncoding !== 'function')\n throw new Error(\n `captureEvents: child stdio[${this.#index}] is not a readable pipe`\n );\n this.#attached = true;\n\n let pending = '';\n stream.setEncoding('utf8');\n stream.on('data', (chunk: string) => {\n const data = pending ? pending + chunk : chunk;\n let start = 0;\n let end: number;\n while ((end = data.indexOf('\\n', start)) !== -1) {\n const line = data.slice(start, end);\n start = end + 1;\n if (line) this.#ingest(line);\n }\n pending = start ? data.slice(start) : data;\n });\n\n const finish = () => {\n if (this.#done) return;\n this.#done = true;\n if (pending) this.#ingest(pending);\n this.#notify(null);\n };\n stream.on('end', finish);\n stream.on('close', finish);\n stream.on('error', finish);\n return this;\n }\n\n async collect(): Promise<ParsedEvent[]> {\n const events: ParsedEvent[] = [];\n for await (const event of this) events.push(event);\n return events;\n }\n\n async *[Symbol.asyncIterator](): AsyncIterator<ParsedEvent> {\n let event: ParsedEvent | null;\n while ((event = await this.#next())) yield event;\n }\n\n #ingest(line: string) {\n const event = parseEventLine(line, this.#options, this.#eventFilter);\n if (event) this.#notify(event);\n }\n\n #notify(event: ParsedEvent | null) {\n const waiter = this.#waiters.shift();\n if (waiter) waiter(event);\n else if (event) this.#events.push(event);\n }\n\n #next(): Promise<ParsedEvent | null> {\n if (this.#events.length) return Promise.resolve(this.#events.shift()!);\n if (this.#done) return Promise.resolve(null);\n if (!this.#attached)\n throw new Error('captureEvents: attach() a child before iterating');\n return new Promise(resolve => this.#waiters.push(resolve));\n }\n}\n\nexport function captureEvents(options?: CaptureOptions): EventCapture {\n return new EventCapture(options);\n}\n"],"names":["EventCapture","options","eventFilter","index","attached","done","events","waiters","constructor","this","compileEventFilter","filter","spawnOptions","stdio","Array","isArray","length","push","env","process","LOG_EVENTS_ENV","attach","child","Error","stream","setEncoding","pending","on","chunk","data","start","end","indexOf","line","slice","ingest","finish","notify","collect","event","Symbol","asyncIterator","next","parseEventLine","waiter","shift","Promise","resolve","captureEvents"],"mappings":";;;;;;;;AAeO,MAAMA;EACXC;EACAC;EACAC;EACAC,IAAY;EACZC,IAAQ;EACRC,GAAyB;EACzBC,GAAuD;EAEvDC,WAAAA,CAAYP,IAA0B;IACpCQ,MAAKR,IAAWA;IAChBQ,MAAKP,IAAeQ,qBAAmBT,EAAQU;AACjD;EAEAC,YAAAA,CACEX;IAEA,MAAMY,IAAmDC,MAAMC,QAC7Dd,GAASY,SAEP,KAAIZ,EAAQY,UACM,QAAlBZ,GAASY,QACP,EAACZ,EAAQY,OAAOZ,EAAQY,OAAOZ,EAAQY,UACvC;IACN,OAAOA,EAAMG,SAAS;MAAGH,EAAMI,KAAK;;IACpCR,MAAKN,IAASU,EAAMG;IACpBH,EAAMI,KAAK;IACX,OAAO;SACDhB;MACJY;MACAK,KAAK;WACCjB,GAASiB,OAAOC,QAAQD;QAC5B,CAACE,mBAAiB,GAAGX,MAAKN;;;AAGhC;EAEAkB,MAAAA,CAAOC;IACL,IAAmB,QAAfb,MAAKN;MACP,MAAM,IAAIoB,MACR;;IAEJ,IAAId,MAAKL;MACP,MAAM,IAAImB,MAAM;;IAClB,MAAMC,IAASF,EAAMT,MAAMJ,MAAKN;IAChC,KAAKqB,KAAwC,qBAAvBA,EAAOC;MAC3B,MAAM,IAAIF,MACR,8BAA8Bd,MAAKN;;IAEvCM,MAAKL,KAAY;IAEjB,IAAIsB,IAAU;IACdF,EAAOC,YAAY;IACnBD,EAAOG,GAAG,QAASC;MACjB,MAAMC,IAAOH,IAAUA,IAAUE,IAAQA;MACzC,IAAIE,IAAQ;MACZ,IAAIC;MACJ,eAAQA,IAAMF,EAAKG,QAAQ,MAAMF,KAAgB;QAC/C,MAAMG,IAAOJ,EAAKK,MAAMJ,GAAOC;QAC/BD,IAAQC,IAAM;QACd,IAAIE;UAAMxB,MAAK0B,EAAQF;;AACzB;MACAP,IAAUI,IAAQD,EAAKK,MAAMJ,KAASD;;IAGxC,MAAMO,SAASA;MACb,IAAI3B,MAAKJ;QAAO;;MAChBI,MAAKJ,KAAQ;MACb,IAAIqB;QAASjB,MAAK0B,EAAQT;;MAC1BjB,MAAK4B,EAAQ;;IAEfb,EAAOG,GAAG,OAAOS;IACjBZ,EAAOG,GAAG,SAASS;IACnBZ,EAAOG,GAAG,SAASS;IACnB,OAAO3B;AACT;EAEA,aAAM6B;IACJ,MAAMhC,IAAwB;IAC9B,WAAW,MAAMiC,KAAS9B;MAAMH,EAAOW,KAAKsB;;IAC5C,OAAOjC;AACT;EAEA,QAAQkC,OAAOC;IACb,IAAIF;IACJ,OAAQA,UAAc9B,MAAKiC;YAAgBH;;AAC7C;EAEA,EAAAJ,CAAQF;IACN,MAAMM,IAAQI,EAAAA,eAAeV,GAAMxB,MAAKR,GAAUQ,MAAKP;IACvD,IAAIqC;MAAO9B,MAAK4B,EAAQE;;AAC1B;EAEA,EAAAF,CAAQE;IACN,MAAMK,IAASnC,MAAKF,EAASsC;IAC7B,IAAID;MAAQA,EAAOL;WACd,IAAIA;MAAO9B,MAAKH,EAAQW,KAAKsB;;AACpC;EAEA,EAAAG;IACE,IAAIjC,MAAKH,EAAQU;MAAQ,OAAO8B,QAAQC,QAAQtC,MAAKH,EAAQuC;;IAC7D,IAAIpC,MAAKJ;MAAO,OAAOyC,QAAQC,QAAQ;;IACvC,KAAKtC,MAAKL;MACR,MAAM,IAAImB,MAAM;;IAClB,OAAO,IAAIuB,QAAQC,KAAWtC,MAAKF,EAASU,KAAK8B;AACnD;;;;;;;;;wBAGK,SAASC,cAAc/C;EAC5B,OAAO,IAAID,aAAaC;AAC1B"}
|