@hona/strobe 0.0.0-stage → 0.2.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/README.md CHANGED
@@ -1,3 +1,35 @@
1
- # Temporary Holding Version
1
+ # @hona/strobe
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [strobe](https://github.com/Hona/strobe): measure and explain performance. This package is the `strobe` command
4
+ and the types and helpers for its bench files, so a project pins one strobe version for everyone who runs its
5
+ benches, people, agents and CI alike.
6
+
7
+ ```sh
8
+ bun add -d @hona/strobe
9
+ ```
10
+
11
+ ```ts
12
+ import { bench, web } from "@hona/strobe"
13
+
14
+ export default bench({
15
+ targets: { app: web({ url: "/" }) },
16
+ cases: {
17
+ "open settings": {
18
+ primary: "main.work",
19
+ run: (ctx) => ctx.page.click("[data-action=settings]", { until: "[role=dialog]" }),
20
+ reset: (ctx) => ctx.page.press("Escape", { until: "idle" }),
21
+ },
22
+ },
23
+ })
24
+ ```
25
+
26
+ ```sh
27
+ bunx strobe bench perf/settings.bench.ts --check # each case once: does it work?
28
+ bunx strobe bench perf/settings.bench.ts --base git:main # is this branch faster?
29
+ ```
30
+
31
+ - **Platforms:** the binary comes from `@hona/strobe-<platform>-<arch>`, installed with this package for your
32
+ machine only. Windows x64 so far.
33
+ - **One SDK per binary:** strobe runs a bench file with the SDK it was built with, so the types you import and
34
+ the runner always match.
35
+ - **Old name:** `@hona/strobe-bench` re-exports this package. Import `@hona/strobe` instead.
package/bin/strobe.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ // Runs this platform's strobe binary, from the @hona/strobe-<platform>-<arch> package npm installed beside this
3
+ // one (an optional dependency: each machine installs only its own). STROBE_BINARY points at another build.
4
+ import { spawnSync } from "node:child_process"
5
+ import { createRequire } from "node:module"
6
+ import { dirname, join } from "node:path"
7
+
8
+ const target = `${process.platform}-${process.arch}`
9
+ let exe = process.env.STROBE_BINARY
10
+ if (!exe) {
11
+ try {
12
+ const require = createRequire(import.meta.url)
13
+ const dir = dirname(require.resolve(`@hona/strobe-${target}/package.json`))
14
+ exe = join(dir, process.platform === "win32" ? "strobe.exe" : "strobe")
15
+ } catch {
16
+ console.error(`strobe: no build for ${target} is installed (@hona/strobe-${target}). strobe ships for win32-x64 so far.`)
17
+ process.exit(1)
18
+ }
19
+ }
20
+ const r = spawnSync(exe, process.argv.slice(2), { stdio: "inherit" })
21
+ if (r.error) {
22
+ console.error(`strobe: ${r.error.message}`)
23
+ process.exit(1)
24
+ }
25
+ if (r.signal) process.kill(process.pid, r.signal)
26
+ process.exit(r.status ?? 1)
@@ -0,0 +1,367 @@
1
+ /** A visible element: a selector, or one whose text contains `text` (or is exactly it, trimmed). */
2
+ export type Selector = string | {
3
+ selector: string;
4
+ text?: string;
5
+ exact?: boolean;
6
+ };
7
+ /** When an input call resolves: `"idle"` (no DOM change or long frame for 300 ms), `"frame"` (after next paint), or a visible element. */
8
+ export type Until = "idle" | "frame" | Selector;
9
+ export interface Options {
10
+ until?: Until;
11
+ /** Milliseconds before the call fails the block. Default 30000. */
12
+ timeout?: number;
13
+ /** Native targets: the surface (window or overlay) by its key; default the app's main window. */
14
+ window?: string;
15
+ }
16
+ /**
17
+ * Drives the target. On a native target the app's bridge does it (its UI toolkit built with strobe support):
18
+ * `click`, `hover`, `move`, `type`, `press`, `scroll`, `wheel`, `waitFor`, `idle`, `wait`, `window` and `snapshot`
19
+ * work there, with selectors as the toolkit's element keys (`"nav-history"`, `"slider/knob"`), `role=button`,
20
+ * `text=General`, and a `:focus` suffix. `goto`, `eval`, `down` and `up` are page-only. Without the bridge a native
21
+ * target takes `wait` only.
22
+ */
23
+ export interface Page {
24
+ /** Opens `url`; a relative path resolves against the current page. */
25
+ goto(url: string, opts?: Options): Promise<void>;
26
+ click(selector: string, opts?: Options): Promise<void>;
27
+ /** Moves the pointer onto `selector` without a click: reveals hover-only controls, or parks the pointer. */
28
+ hover(selector: string, opts?: Options): Promise<void>;
29
+ /** Moves the pointer to a point in the viewport (CSS pixels), such as away from anything that reacts to hover. */
30
+ move(x: number, y: number, opts?: Options): Promise<void>;
31
+ /** Focuses `selector` (clicking it if focus is elsewhere), then sends one key event per character. */
32
+ type(selector: string, text: string, opts?: Options & {
33
+ delay?: number;
34
+ }): Promise<void>;
35
+ /** A key or chord: `"Enter"`, `"Control+a"`, `"ControlOrMeta+Shift+P"`. */
36
+ press(key: string, opts?: Options): Promise<void>;
37
+ /** Presses one key and holds it across other calls until `up`: a game's `"w"`, or `"Shift"` (held modifiers mark later keys). */
38
+ down(key: string, opts?: Options): Promise<void>;
39
+ /** Lets go of a key `down` pressed. */
40
+ up(key: string, opts?: Options): Promise<void>;
41
+ /**
42
+ * The page's pixels as `name`, for `--pixels`: an untimed block per variant captures each snapshot and strobe
43
+ * compares base with head. Elsewhere it does nothing. Snapshot a state that does not depend on time (a still
44
+ * page, an app's deterministic step), or the two builds differ for reasons of their own.
45
+ */
46
+ snapshot(name: string): Promise<void>;
47
+ scroll(selector: string, delta: {
48
+ by?: number;
49
+ byX?: number;
50
+ speed?: number;
51
+ }, opts?: Options): Promise<void>;
52
+ /** One mouse-wheel event of `dx`, `dy` (pixels; + is down and right) over `selector`, or the window's centre with `""`. */
53
+ wheel(selector: string, delta: {
54
+ dx?: number;
55
+ dy?: number;
56
+ }, opts?: Options): Promise<void>;
57
+ /** Native targets: shows, hides, focuses or closes a window (`opts.window`, default the main one), through the app's own path. */
58
+ window(action: "show" | "hide" | "focus" | "close", opts?: Options): Promise<void>;
59
+ /** Resolves when a visible element matches `selector` (holding `text`, if given). Re-checked on every DOM and focus change, so `:focus` works. */
60
+ waitFor(selector: string, opts?: {
61
+ text?: string;
62
+ exact?: boolean;
63
+ timeout?: number;
64
+ }): Promise<void>;
65
+ /** Resolves when nothing in the DOM changed and no frame ran long for 300 ms. */
66
+ idle(opts?: {
67
+ timeout?: number;
68
+ }): Promise<void>;
69
+ wait(ms: number): Promise<void>;
70
+ /** Runs `fn` in the page. Only in `prepare`, `setup` and `reset`: it costs main-thread time. */
71
+ eval<A extends unknown[], T>(fn: (...args: A) => T, ...args: A): Promise<Awaited<T>>;
72
+ }
73
+ export interface Context<P> {
74
+ page: Page;
75
+ /** The case this block runs: a block always belongs to one case. */
76
+ case: string;
77
+ /** Marks a named span inside a run; every metric can be sliced by it. */
78
+ phase<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
79
+ /** Adds `n` (default 1) to the `count.<name>` metric of the current run. Outside a run it counts nothing. */
80
+ count(name: string, n?: number): void;
81
+ /** Calls an export of a `script()` target's module in its process and returns what it returned (JSON). */
82
+ call<T = unknown>(name: string, ...args: unknown[]): Promise<T>;
83
+ params: P;
84
+ target: string;
85
+ variant: "base" | "head";
86
+ /** The URL this block opened (empty for app, command and script targets). */
87
+ url: string;
88
+ /** The variant's folder: a git ref's worktree, or the directory it names. */
89
+ root: string;
90
+ /** What the target's `prepare` returned for this block. */
91
+ prepared: any;
92
+ }
93
+ /** A request a `mock` handler answers. */
94
+ export interface MockRequest {
95
+ url: string;
96
+ method: string;
97
+ headers: Record<string, string>;
98
+ /** The request body as text, if it had one. */
99
+ body?: string;
100
+ json<T = any>(): T;
101
+ }
102
+ /** A mocked response: `body` as text, bytes, or any other value as JSON. */
103
+ export interface MockResponse {
104
+ status?: number;
105
+ headers?: Record<string, string>;
106
+ body?: string | Uint8Array | unknown;
107
+ }
108
+ /** Answers a request, or returns `undefined` to let it through to the network. */
109
+ export type MockHandler = (request: MockRequest) => MockResponse | undefined | Promise<MockResponse | undefined>;
110
+ export interface PrepareContext<P> {
111
+ /** The case this block runs, so one file can give each case its own fixture. */
112
+ case: string;
113
+ params: P;
114
+ target: string;
115
+ variant: "base" | "head";
116
+ /** The URL strobe opens after `prepare`, such as the variant's server; mock its API under `new URL(url).origin`. */
117
+ url: string;
118
+ /** The variant's folder: a git ref's worktree, or the directory it names. */
119
+ root: string;
120
+ /** Playwright, connected to this block's browser. Only on targets with a debug port. */
121
+ playwright: {
122
+ browser: any;
123
+ context: any;
124
+ page: any;
125
+ };
126
+ /**
127
+ * Answers requests whose URL matches `pattern` (`*` is any run of characters) in this block's page.
128
+ * Unlike Playwright's `page.route`, the browser's HTTP cache stays on: scripts and styles a reload
129
+ * fetches come from the cache, as for a person. Later calls add patterns and are asked first; a
130
+ * handler that returns `undefined` passes the request to earlier ones, as Playwright's routes do.
131
+ */
132
+ mock(pattern: string, handler: MockHandler): Promise<void>;
133
+ }
134
+ /** What a launch's `before` hook gets: the launch has not started yet. */
135
+ export interface LaunchContext<P> {
136
+ case: string;
137
+ params: P;
138
+ target: string;
139
+ variant: "base" | "head";
140
+ /** The variant's folder: a git ref's worktree, or the directory it names. */
141
+ root: string;
142
+ /** The private home the app launches in (its `HOME`, `APPDATA`, XDG folders); null for web targets. Ready before the hook: seed and profile files are in. */
143
+ home: string | null;
144
+ /** App and exe targets: the whole environment the app launches with, to start what it expects to find (a background service) as it would. */
145
+ env: Record<string, string> | null;
146
+ /** App and exe targets: the executable this variant launches. */
147
+ exe: string | null;
148
+ }
149
+ /** A point in a launch: the first line matching `match` in `in`, timed from process spawn. */
150
+ export interface Milestone {
151
+ /** Text the line contains, or a regular expression it matches. A group named `at` holding epoch ms (`/entry: (?<at>\d{13})/`) is the time, for marks an app writes into its log; otherwise the time strobe read the line. */
152
+ match: string | RegExp;
153
+ /** `"output"` (stdout and stderr, the default), `"page"` (the page's `performance.mark` names), or a glob of files under the launch's home. */
154
+ in?: "output" | "page" | (string & {});
155
+ }
156
+ /** A native app's launch ends on its main window: `visible`, its pixels first `painted` (changed from its first frame), or `settled` (their last change, once they hold for a second). */
157
+ export interface WindowUntil {
158
+ window: "visible" | "painted" | "settled";
159
+ }
160
+ /** A native launch ends on one of its own milestones (an output or log line): no window needed. */
161
+ export interface MilestoneUntil {
162
+ milestone: string;
163
+ }
164
+ export interface Launch<P> {
165
+ /** The launch is ready when this is visible in any of its windows. The primary is `launch.ready`. A native target without its bridge times to a window condition instead. */
166
+ until: Selector | WindowUntil | MilestoneUntil;
167
+ /** Named points on the way, each a metric `launch.<name>`. One a case declares must appear in every launch. */
168
+ milestones?: Record<string, Milestone>;
169
+ /** `fresh` (default): a new home every launch. `warm`: one home per series and variant, kept across its launches, so caches and state from earlier launches are there. */
170
+ profile?: "fresh" | "warm";
171
+ /** Before every launch, never timed: delete caches for a cold start, start or stop a service, seed the home. */
172
+ before?(ctx: LaunchContext<P>): unknown;
173
+ /** Starts the main process with `--inspect` and reads Node's own bootstrap timing after the launch (`launch.node.*`). App and exe targets. */
174
+ inspect?: boolean;
175
+ /** Times `launch.painted` and `launch.settled` from the window's pixels (Windows; default on for apps). */
176
+ screen?: boolean;
177
+ /** Milliseconds before a launch that never shows `until` fails. Default 120000. */
178
+ timeout?: number;
179
+ }
180
+ interface CaseBase {
181
+ /** The metric this case is about, declared before the run. */
182
+ primary: string;
183
+ /** Extra secondary metrics to report: any catalog metric, `count.<name>`, `net.requests{<url glob>}`, `layout.shift`, `launch.<milestone>`, `js.heap.retained` (scripts). */
184
+ metrics?: string[];
185
+ /** The targets this case runs on. Default: every target. */
186
+ targets?: string[];
187
+ }
188
+ /** A case of runs inside an open target: measured `run`, unmeasured `reset`. */
189
+ export interface RunCase<P> extends CaseBase {
190
+ /** Measured. */
191
+ run(ctx: Context<P>): unknown;
192
+ /** Puts the app back between runs. Never measured. */
193
+ reset?(ctx: Context<P>): unknown;
194
+ launch?: never;
195
+ }
196
+ /** A case that measures the launch itself: one cold launch per block. */
197
+ export interface LaunchCase<P> extends CaseBase {
198
+ launch: Launch<P>;
199
+ run?: never;
200
+ reset?: never;
201
+ }
202
+ export type Case<P> = RunCase<P> | LaunchCase<P>;
203
+ /** The first page a block opens: a path, or a function of what `prepare` returned. */
204
+ export type FirstPage<P> = string | ((ctx: {
205
+ case: string;
206
+ prepared: any;
207
+ params: P;
208
+ }) => string | Promise<string>);
209
+ interface Hooks<P> {
210
+ prepare?: (ctx: PrepareContext<P>) => unknown;
211
+ /** When the block closes, for what `prepare` started (a server, a temporary folder). */
212
+ teardown?: (ctx: PrepareContext<P>) => unknown;
213
+ }
214
+ export type Target<P> = ({
215
+ kind: "web";
216
+ url: FirstPage<P>;
217
+ viewport?: Viewport;
218
+ scale?: number;
219
+ } & Hooks<P>) | ({
220
+ kind: "url";
221
+ href: string;
222
+ viewport?: Viewport;
223
+ scale?: number;
224
+ } & Hooks<P>) | {
225
+ kind: "app";
226
+ id: string;
227
+ home?: string;
228
+ viewport?: Viewport;
229
+ } | {
230
+ kind: "exe";
231
+ path: string;
232
+ app?: string;
233
+ home?: string;
234
+ viewport?: Viewport;
235
+ } | ({
236
+ kind: "command";
237
+ } & CommandOptions) | ({
238
+ kind: "script";
239
+ } & ScriptOptions) | ({
240
+ kind: "native";
241
+ app?: string;
242
+ path?: string;
243
+ } & NativeOptions) | ({
244
+ kind: "headless";
245
+ app?: string;
246
+ path?: string;
247
+ } & HeadlessOptions);
248
+ export interface Viewport {
249
+ width: number;
250
+ height: number;
251
+ }
252
+ export interface CommandOptions {
253
+ /** The command line, run by the shell in `cwd`, such as `bun run dev:desktop`. */
254
+ run: string;
255
+ /** Relative to the variant's folder. Default: the folder itself. */
256
+ cwd?: string;
257
+ /** Extra environment; `{home}` (the launch's private home) and `{port}` (a free port) are filled in. */
258
+ env?: Record<string, string>;
259
+ /** Environment variables to remove; `PREFIX_*` removes a family. */
260
+ unset?: string[];
261
+ /** The text before the DevTools endpoint in the command's output. Default `"DevTools listening on"`. */
262
+ attach?: string;
263
+ /** Milliseconds the command may take to print its endpoint. Default 600000. */
264
+ timeout?: number;
265
+ /** Run once per variant before its first block, in its folder. */
266
+ build?: string;
267
+ viewport?: Viewport;
268
+ }
269
+ export interface ScriptOptions {
270
+ /**
271
+ * The harness module whose exports cases call (`ctx.call`), relative to the bench file. strobe
272
+ * runs the same file for both variants, placed at the same path in a git ref's worktree, so its
273
+ * relative imports reach that variant's code. Keep the harness in one file.
274
+ */
275
+ entry: string;
276
+ /** Default `"bun"`. */
277
+ runtime?: "bun" | "node";
278
+ env?: Record<string, string>;
279
+ /** Run once per variant before its first block, in its folder. */
280
+ build?: string;
281
+ }
282
+ export interface NativeOptions {
283
+ /** A seed folder copied into each launch's fresh private home (`{param}` placeholders allowed), relative to the bench file. */
284
+ home?: string;
285
+ /** The main window's client area in device-independent pixels, where the app lets strobe size it. */
286
+ viewport?: Viewport;
287
+ /** Extra arguments after the profile's; `{home}` and `{port}` are filled in. */
288
+ args?: string[];
289
+ /** Extra environment after the profile's; `{home}` and `{port}` are filled in. */
290
+ env?: Record<string, string>;
291
+ /**
292
+ * The app's bridge (`STROBE_BRIDGE`): by default strobe offers it and uses it once a page call, a `ui.*` or frame
293
+ * metric, or `--pixels` needs it. `true` requires it in every block; `false` measures the app from outside only.
294
+ */
295
+ bridge?: boolean;
296
+ /**
297
+ * `false`: the app shows no window (a tray app). strobe neither waits for one nor places or checks it, launches end
298
+ * on a milestone (`until: { milestone }`), and cases whose metrics are all CPU, memory, IO, counters and milestones
299
+ * run while the session is locked.
300
+ */
301
+ window?: false;
302
+ }
303
+ export type HeadlessOptions = Omit<NativeOptions, "viewport" | "bridge" | "window">;
304
+ /** A parameter's values: all run, or `run` by default and `more` with `--params all`. */
305
+ export type ParamValues = readonly unknown[] | {
306
+ readonly run: readonly unknown[];
307
+ readonly more?: readonly unknown[];
308
+ };
309
+ type Values<T> = {
310
+ [K in keyof T]: T[K] extends readonly (infer V)[] ? V : T[K] extends {
311
+ run: readonly (infer V)[];
312
+ more: readonly (infer W)[];
313
+ } ? V | W : T[K] extends {
314
+ run: readonly (infer V)[];
315
+ } ? V : never;
316
+ };
317
+ export interface Bench<Params extends Record<string, ParamValues>> {
318
+ targets: Record<string, Target<Values<Params>>>;
319
+ /** Every combination is its own series. */
320
+ params?: Params;
321
+ /** Once per block, after load. Never measured. */
322
+ setup?(ctx: Context<Values<Params>>): unknown;
323
+ /** Once per block, when it closes. Never measured. */
324
+ teardown?(ctx: Context<Values<Params>>): unknown;
325
+ cases: Record<string, Case<Values<Params>>>;
326
+ }
327
+ /** The project's production build, served per variant from `strobe.json`, in a strobe-launched Chromium. */
328
+ export declare function web<P = any>(opts?: {
329
+ url?: FirstPage<P>;
330
+ viewport?: Viewport;
331
+ scale?: number;
332
+ } & Hooks<P>): Target<P>;
333
+ /** A fixed page; the same for both variants unless a variant is `url:<href>`. */
334
+ export declare function url<P = any>(href: string, opts?: {
335
+ viewport?: Viewport;
336
+ scale?: number;
337
+ } & Hooks<P>): Target<P>;
338
+ /** An installed Electron app, by its profile in strobe.json `apps`, in a fresh isolated home seeded from `home`. */
339
+ export declare function app<P = any>(id: string, opts?: {
340
+ home?: string;
341
+ viewport?: Viewport;
342
+ }): Target<P>;
343
+ /** A packaged Electron app by path; `app` borrows a profile from strobe.json `apps` for it. */
344
+ export declare function exe<P = any>(path: string, opts?: {
345
+ app?: string;
346
+ home?: string;
347
+ viewport?: Viewport;
348
+ }): Target<P>;
349
+ /** An app started by a command in the variant's folder (a dev server that launches it), attached through the DevTools endpoint it prints. */
350
+ export declare function command<P = any>(run: string, opts?: Omit<CommandOptions, "run">): Target<P>;
351
+ /** A Bun or Node module of the variant, loaded in a fresh process per block; cases call its exports with `ctx.call`. */
352
+ export declare function script<P = any>(entry: string, opts?: Omit<ScriptOptions, "entry">): Target<P>;
353
+ /**
354
+ * A native app (no browser engine): by its profile in strobe.json `apps` (`kind: "native"`), or an executable path.
355
+ * Each block launches it in a fresh isolated home and measures it from outside: CPU in cycles, thread wakes
356
+ * (`cpu.wakes`), IO, GPU, memory, and for launch cases its window. A directory or git-ref variant runs strobe.json's
357
+ * `build` and launches its `output`.
358
+ */
359
+ export declare function native<P = any>(appOrPath: string, opts?: NativeOptions): Target<P>;
360
+ /**
361
+ * The same native app in its toolkit's headless mode: offscreen, on a virtual clock, driven through its bridge. The
362
+ * app profile's `headless` object holds the `args` and `env` that select it. Only `ui.*`, `count.*`, `cpu.*` and
363
+ * `--pixels` are measured there: no display, no presentation, no launch.
364
+ */
365
+ export declare function headless<P = any>(appOrPath: string, opts?: HeadlessOptions): Target<P>;
366
+ export declare function bench<const Params extends Record<string, ParamValues> = {}>(spec: Bench<Params>): Bench<Params>;
367
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,41 @@
1
+ // src/index.ts
2
+ function web(opts = {}) {
3
+ return { kind: "web", url: opts.url ?? "/", prepare: opts.prepare, teardown: opts.teardown, viewport: opts.viewport, scale: opts.scale };
4
+ }
5
+ function url(href, opts = {}) {
6
+ return { kind: "url", href, ...opts };
7
+ }
8
+ function app(id, opts = {}) {
9
+ return { kind: "app", id, ...opts };
10
+ }
11
+ function exe(path, opts = {}) {
12
+ return { kind: "exe", path, ...opts };
13
+ }
14
+ function command(run, opts = {}) {
15
+ return { kind: "command", run, ...opts };
16
+ }
17
+ function script(entry, opts = {}) {
18
+ return { kind: "script", entry, ...opts };
19
+ }
20
+ function native(appOrPath, opts = {}) {
21
+ const path = /[\\/]|\.exe$/i.test(appOrPath);
22
+ return { kind: "native", ...path ? { path: appOrPath } : { app: appOrPath }, ...opts };
23
+ }
24
+ function headless(appOrPath, opts = {}) {
25
+ const path = /[\\/]|\.exe$/i.test(appOrPath);
26
+ return { kind: "headless", ...path ? { path: appOrPath } : { app: appOrPath }, ...opts };
27
+ }
28
+ function bench(spec) {
29
+ return spec;
30
+ }
31
+ export {
32
+ app,
33
+ bench,
34
+ command,
35
+ exe,
36
+ headless,
37
+ native,
38
+ script,
39
+ url,
40
+ web
41
+ };
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "@hona/strobe",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Measure and explain performance: the strobe CLI and the types and helpers for its bench files",
5
+ "type": "module",
6
+ "types": "./dist/index.d.ts",
7
+ "bin": {
8
+ "strobe": "bin/strobe.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "files": ["dist", "bin", "README.md"],
19
+ "sideEffects": false,
20
+ "scripts": {
21
+ "build": "bun build src/index.ts --outdir dist --target node --format esm && tsc -p tsconfig.json"
22
+ },
23
+ "optionalDependencies": {
24
+ "@hona/strobe-win32-x64": "0.2.0"
25
+ },
26
+ "devDependencies": {
27
+ "typescript": "^5.9.0"
28
+ },
29
+ "keywords": ["benchmark", "performance", "strobe", "playwright", "electron", "chromium"],
30
+ "license": "MIT",
31
+ "repository": { "type": "git", "url": "git+https://github.com/Hona/strobe.git" },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ }
35
+ }