@nwire/supervisor 0.10.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/LICENSE +21 -0
- package/dist/health.d.ts +12 -0
- package/dist/health.js +24 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/supervisor.d.ts +87 -0
- package/dist/supervisor.js +221 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex Gefter / 200apps Ltd.
|
|
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/dist/health.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health-check helpers for `RunnerSupervisor.start({ healthCheck })`.
|
|
3
|
+
*/
|
|
4
|
+
import type { ManagedProcess } from "./supervisor.js";
|
|
5
|
+
/**
|
|
6
|
+
* Poll a URL until it returns a 2xx response. The supervisor's
|
|
7
|
+
* `healthIntervalMs` drives retry cadence — this helper just performs one
|
|
8
|
+
* attempt and returns a boolean.
|
|
9
|
+
*/
|
|
10
|
+
export declare function httpHealthCheck(urlFactory: (proc: ManagedProcess) => string): (proc: ManagedProcess) => Promise<boolean>;
|
|
11
|
+
/** Wait for `http://localhost:<port>/_nwire/manifest` to respond. */
|
|
12
|
+
export declare function inspectHealthCheck(port: number): (proc: ManagedProcess) => Promise<boolean>;
|
package/dist/health.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health-check helpers for `RunnerSupervisor.start({ healthCheck })`.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Poll a URL until it returns a 2xx response. The supervisor's
|
|
6
|
+
* `healthIntervalMs` drives retry cadence — this helper just performs one
|
|
7
|
+
* attempt and returns a boolean.
|
|
8
|
+
*/
|
|
9
|
+
export function httpHealthCheck(urlFactory) {
|
|
10
|
+
return async (proc) => {
|
|
11
|
+
const url = urlFactory(proc);
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(url);
|
|
14
|
+
return res.ok;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Wait for `http://localhost:<port>/_nwire/manifest` to respond. */
|
|
22
|
+
export function inspectHealthCheck(port) {
|
|
23
|
+
return httpHealthCheck(() => `http://localhost:${port}/_nwire/manifest`);
|
|
24
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nwire/supervisor` — process supervisor for dev tooling.
|
|
3
|
+
*
|
|
4
|
+
* Spawns Nwire wire/app processes, watches them, streams their stdout/stderr
|
|
5
|
+
* into a ring buffer, runs optional health checks, and stops them gracefully
|
|
6
|
+
* (SIGTERM → grace window → SIGKILL). Used by `nwire dev` and Studio's vite
|
|
7
|
+
* dev server; not a foundation primitive — runtime apps do not depend on it.
|
|
8
|
+
*/
|
|
9
|
+
export { RunnerSupervisor, type ManagedProcess, type ProcessStatus, type StartOptions, type LogLine, } from "./supervisor.js";
|
|
10
|
+
export { httpHealthCheck, inspectHealthCheck } from "./health.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nwire/supervisor` — process supervisor for dev tooling.
|
|
3
|
+
*
|
|
4
|
+
* Spawns Nwire wire/app processes, watches them, streams their stdout/stderr
|
|
5
|
+
* into a ring buffer, runs optional health checks, and stops them gracefully
|
|
6
|
+
* (SIGTERM → grace window → SIGKILL). Used by `nwire dev` and Studio's vite
|
|
7
|
+
* dev server; not a foundation primitive — runtime apps do not depend on it.
|
|
8
|
+
*/
|
|
9
|
+
export { RunnerSupervisor, } from "./supervisor.js";
|
|
10
|
+
export { httpHealthCheck, inspectHealthCheck } from "./health.js";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `RunnerSupervisor` — spawns and watches Nwire wire processes.
|
|
3
|
+
*
|
|
4
|
+
* One supervisor manages N child processes. Each process runs
|
|
5
|
+
* `apps/run.ts` with `NWIRE_TOPOLOGY=<name>` set and a port from the
|
|
6
|
+
* topology spec. Stdout/stderr land in a per-process ring buffer + are
|
|
7
|
+
* fanned out to subscribers (Studio's SSE log viewer).
|
|
8
|
+
*
|
|
9
|
+
* Lifecycle:
|
|
10
|
+
* start(topology) → spawn → wait-for-health → 'running'
|
|
11
|
+
* stop(processId) → SIGTERM → drain → 'exited'
|
|
12
|
+
* crash → child exits non-zero unprompted → 'crashed'
|
|
13
|
+
*
|
|
14
|
+
* The supervisor itself does NOT know how to compose topologies. It
|
|
15
|
+
* shells out to a wire entry script (typically under `__wires__/`).
|
|
16
|
+
* That keeps the runner generic — same supervisor can host any Nwire app's
|
|
17
|
+
* entry script.
|
|
18
|
+
*/
|
|
19
|
+
import { EventEmitter } from "node:events";
|
|
20
|
+
export type ProcessStatus = "idle" | "starting" | "running" | "stopping" | "exited" | "crashed";
|
|
21
|
+
export interface LogLine {
|
|
22
|
+
readonly seq: number;
|
|
23
|
+
/** ISO timestamp captured when the line was emitted. */
|
|
24
|
+
readonly ts: string;
|
|
25
|
+
readonly stream: "stdout" | "stderr";
|
|
26
|
+
readonly line: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ManagedProcess {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly topology: string;
|
|
31
|
+
readonly port?: number;
|
|
32
|
+
readonly startedAt: string;
|
|
33
|
+
status: ProcessStatus;
|
|
34
|
+
pid?: number;
|
|
35
|
+
exitCode?: number | null;
|
|
36
|
+
signal?: string | null;
|
|
37
|
+
/** Last seen error message, if status is 'crashed'. */
|
|
38
|
+
errorMessage?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface StartOptions {
|
|
41
|
+
/** Topology name (matches `apps/topologies/<name>.topology.ts`). */
|
|
42
|
+
readonly topology: string;
|
|
43
|
+
/** Optional port override. Forwarded as `PORT=<port>` to the child. */
|
|
44
|
+
readonly port?: number;
|
|
45
|
+
/** Repo root — the cwd the child runs in. Defaults to `process.cwd()`. */
|
|
46
|
+
readonly cwd?: string;
|
|
47
|
+
/** Entry script (relative to cwd). Defaults to `apps/run.ts`. */
|
|
48
|
+
readonly entry?: string;
|
|
49
|
+
/** Command + args to invoke. Defaults to `['pnpm', 'exec', 'vite-node', entry]`. */
|
|
50
|
+
readonly command?: readonly string[];
|
|
51
|
+
/** Additional env vars. Merged into `process.env`. */
|
|
52
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
53
|
+
/** Ring-buffer capacity for stdout/stderr lines. Default 2000. */
|
|
54
|
+
readonly bufferSize?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Optional health check called repeatedly until it returns truthy. When
|
|
57
|
+
* it returns truthy the process transitions to `'running'`. If absent,
|
|
58
|
+
* the process moves to `'running'` immediately after spawn.
|
|
59
|
+
*/
|
|
60
|
+
readonly healthCheck?: (proc: ManagedProcess) => Promise<boolean>;
|
|
61
|
+
/** Health check poll interval (ms). Default 500. */
|
|
62
|
+
readonly healthIntervalMs?: number;
|
|
63
|
+
/** Health check timeout (ms). Default 30_000. */
|
|
64
|
+
readonly healthTimeoutMs?: number;
|
|
65
|
+
}
|
|
66
|
+
export declare class RunnerSupervisor extends EventEmitter {
|
|
67
|
+
private readonly states;
|
|
68
|
+
list(): readonly ManagedProcess[];
|
|
69
|
+
get(id: string): ManagedProcess | undefined;
|
|
70
|
+
recentLogs(id: string, n?: number): readonly LogLine[];
|
|
71
|
+
/**
|
|
72
|
+
* Spawn a child process running the given topology. Returns the
|
|
73
|
+
* ManagedProcess record. Health check runs in the background; subscribe
|
|
74
|
+
* via `on('status', ...)` to observe transitions.
|
|
75
|
+
*/
|
|
76
|
+
start(options: StartOptions): Promise<ManagedProcess>;
|
|
77
|
+
private runHealthCheck;
|
|
78
|
+
/**
|
|
79
|
+
* Send SIGTERM to a managed process; wait until it exits. After
|
|
80
|
+
* `graceMs` (default 5s) escalate to SIGKILL.
|
|
81
|
+
*/
|
|
82
|
+
stop(id: string, graceMs?: number): Promise<void>;
|
|
83
|
+
/** Stop every managed process. */
|
|
84
|
+
stopAll(): Promise<void>;
|
|
85
|
+
/** Drop the record for an exited process. No-op if still running. */
|
|
86
|
+
forget(id: string): void;
|
|
87
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `RunnerSupervisor` — spawns and watches Nwire wire processes.
|
|
3
|
+
*
|
|
4
|
+
* One supervisor manages N child processes. Each process runs
|
|
5
|
+
* `apps/run.ts` with `NWIRE_TOPOLOGY=<name>` set and a port from the
|
|
6
|
+
* topology spec. Stdout/stderr land in a per-process ring buffer + are
|
|
7
|
+
* fanned out to subscribers (Studio's SSE log viewer).
|
|
8
|
+
*
|
|
9
|
+
* Lifecycle:
|
|
10
|
+
* start(topology) → spawn → wait-for-health → 'running'
|
|
11
|
+
* stop(processId) → SIGTERM → drain → 'exited'
|
|
12
|
+
* crash → child exits non-zero unprompted → 'crashed'
|
|
13
|
+
*
|
|
14
|
+
* The supervisor itself does NOT know how to compose topologies. It
|
|
15
|
+
* shells out to a wire entry script (typically under `__wires__/`).
|
|
16
|
+
* That keeps the runner generic — same supervisor can host any Nwire app's
|
|
17
|
+
* entry script.
|
|
18
|
+
*/
|
|
19
|
+
import { spawn } from "node:child_process";
|
|
20
|
+
import { EventEmitter } from "node:events";
|
|
21
|
+
import { randomUUID } from "node:crypto";
|
|
22
|
+
/**
|
|
23
|
+
* Windows toggle — `pnpm`, `vite-node`, and most other CLIs we spawn ship
|
|
24
|
+
* as `.cmd` shims on Windows. `spawn` only resolves `.exe` by default,
|
|
25
|
+
* so without `shell:true` every supervised child ENOENTs on Win.
|
|
26
|
+
*/
|
|
27
|
+
const IS_WIN = process.platform === "win32";
|
|
28
|
+
export class RunnerSupervisor extends EventEmitter {
|
|
29
|
+
states = new Map();
|
|
30
|
+
list() {
|
|
31
|
+
return Array.from(this.states.values()).map((s) => s.proc);
|
|
32
|
+
}
|
|
33
|
+
get(id) {
|
|
34
|
+
return this.states.get(id)?.proc;
|
|
35
|
+
}
|
|
36
|
+
recentLogs(id, n) {
|
|
37
|
+
const state = this.states.get(id);
|
|
38
|
+
if (!state)
|
|
39
|
+
return [];
|
|
40
|
+
const limit = n ?? state.buffer.length;
|
|
41
|
+
return state.buffer.slice(-limit);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Spawn a child process running the given topology. Returns the
|
|
45
|
+
* ManagedProcess record. Health check runs in the background; subscribe
|
|
46
|
+
* via `on('status', ...)` to observe transitions.
|
|
47
|
+
*/
|
|
48
|
+
async start(options) {
|
|
49
|
+
const id = randomUUID();
|
|
50
|
+
const startedAt = new Date().toISOString();
|
|
51
|
+
const entry = options.entry ?? "apps/run.ts";
|
|
52
|
+
const cwd = options.cwd ?? process.cwd();
|
|
53
|
+
const command = options.command ?? ["pnpm", "exec", "vite-node", entry];
|
|
54
|
+
const [cmd, ...args] = command;
|
|
55
|
+
if (!cmd)
|
|
56
|
+
throw new Error("RunnerSupervisor: empty command");
|
|
57
|
+
const env = {
|
|
58
|
+
...process.env,
|
|
59
|
+
NWIRE_TOPOLOGY: options.topology,
|
|
60
|
+
...(options.port ? { PORT: String(options.port) } : {}),
|
|
61
|
+
...(options.env ?? {}),
|
|
62
|
+
};
|
|
63
|
+
const child = spawn(cmd, args, {
|
|
64
|
+
cwd,
|
|
65
|
+
env,
|
|
66
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
67
|
+
shell: IS_WIN,
|
|
68
|
+
});
|
|
69
|
+
const proc = {
|
|
70
|
+
id,
|
|
71
|
+
topology: options.topology,
|
|
72
|
+
port: options.port,
|
|
73
|
+
startedAt,
|
|
74
|
+
status: "starting",
|
|
75
|
+
pid: child.pid,
|
|
76
|
+
};
|
|
77
|
+
const state = {
|
|
78
|
+
proc,
|
|
79
|
+
child,
|
|
80
|
+
buffer: [],
|
|
81
|
+
bufferSize: options.bufferSize ?? 2000,
|
|
82
|
+
seq: 0,
|
|
83
|
+
};
|
|
84
|
+
this.states.set(id, state);
|
|
85
|
+
const capture = (stream) => (chunk) => {
|
|
86
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
87
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
88
|
+
if (!raw)
|
|
89
|
+
continue;
|
|
90
|
+
const line = {
|
|
91
|
+
seq: state.seq++,
|
|
92
|
+
ts: new Date().toISOString(),
|
|
93
|
+
stream,
|
|
94
|
+
line: raw,
|
|
95
|
+
};
|
|
96
|
+
state.buffer.push(line);
|
|
97
|
+
while (state.buffer.length > state.bufferSize)
|
|
98
|
+
state.buffer.shift();
|
|
99
|
+
this.emit("log", id, line);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
child.stdout?.on("data", capture("stdout"));
|
|
103
|
+
child.stderr?.on("data", capture("stderr"));
|
|
104
|
+
child.on("exit", (code, signal) => {
|
|
105
|
+
proc.exitCode = code;
|
|
106
|
+
proc.signal = signal;
|
|
107
|
+
// Distinguish clean stop (we asked for it) from a crash.
|
|
108
|
+
if (proc.status === "stopping") {
|
|
109
|
+
proc.status = "exited";
|
|
110
|
+
}
|
|
111
|
+
else if (code === 0) {
|
|
112
|
+
proc.status = "exited";
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
proc.status = "crashed";
|
|
116
|
+
proc.errorMessage = `child exited with code ${code}${signal ? ` (${signal})` : ""}`;
|
|
117
|
+
}
|
|
118
|
+
this.emit("status", id, proc.status, proc);
|
|
119
|
+
});
|
|
120
|
+
child.on("error", (err) => {
|
|
121
|
+
proc.status = "crashed";
|
|
122
|
+
proc.errorMessage = err.message;
|
|
123
|
+
this.emit("status", id, "crashed", proc);
|
|
124
|
+
});
|
|
125
|
+
this.emit("status", id, "starting", proc);
|
|
126
|
+
// Background health check. Don't await — return the proc record so the
|
|
127
|
+
// caller can subscribe; the status transition fires when health passes.
|
|
128
|
+
void this.runHealthCheck(state, options);
|
|
129
|
+
return proc;
|
|
130
|
+
}
|
|
131
|
+
async runHealthCheck(state, options) {
|
|
132
|
+
if (!options.healthCheck) {
|
|
133
|
+
// No health check: trust the spawn. Transition after one tick so
|
|
134
|
+
// listeners attached synchronously see the change.
|
|
135
|
+
setImmediate(() => {
|
|
136
|
+
if (state.proc.status === "starting") {
|
|
137
|
+
state.proc.status = "running";
|
|
138
|
+
this.emit("status", state.proc.id, "running", state.proc);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const interval = options.healthIntervalMs ?? 500;
|
|
144
|
+
const timeout = options.healthTimeoutMs ?? 30_000;
|
|
145
|
+
const deadline = Date.now() + timeout;
|
|
146
|
+
while (Date.now() < deadline) {
|
|
147
|
+
if (state.proc.status !== "starting")
|
|
148
|
+
return;
|
|
149
|
+
try {
|
|
150
|
+
if (await options.healthCheck(state.proc)) {
|
|
151
|
+
state.proc.status = "running";
|
|
152
|
+
this.emit("status", state.proc.id, "running", state.proc);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// try again
|
|
158
|
+
}
|
|
159
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
160
|
+
}
|
|
161
|
+
if (state.proc.status === "starting") {
|
|
162
|
+
state.proc.status = "crashed";
|
|
163
|
+
state.proc.errorMessage = `health check did not pass within ${timeout}ms`;
|
|
164
|
+
this.emit("status", state.proc.id, "crashed", state.proc);
|
|
165
|
+
// Best-effort kill so we don't leak orphans.
|
|
166
|
+
try {
|
|
167
|
+
state.child.kill("SIGTERM");
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// ignore
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Send SIGTERM to a managed process; wait until it exits. After
|
|
176
|
+
* `graceMs` (default 5s) escalate to SIGKILL.
|
|
177
|
+
*/
|
|
178
|
+
async stop(id, graceMs = 5000) {
|
|
179
|
+
const state = this.states.get(id);
|
|
180
|
+
if (!state)
|
|
181
|
+
return;
|
|
182
|
+
if (state.proc.status === "exited" || state.proc.status === "crashed")
|
|
183
|
+
return;
|
|
184
|
+
state.proc.status = "stopping";
|
|
185
|
+
this.emit("status", id, "stopping", state.proc);
|
|
186
|
+
state.child.kill("SIGTERM");
|
|
187
|
+
const exited = new Promise((resolve) => state.child.once("exit", () => resolve()));
|
|
188
|
+
const grace = new Promise((resolve) => setTimeout(() => resolve("escalate"), graceMs));
|
|
189
|
+
const result = await Promise.race([exited.then(() => "exited"), grace]);
|
|
190
|
+
if (result === "escalate") {
|
|
191
|
+
try {
|
|
192
|
+
// Windows collapses SIGTERM and SIGKILL into the same hard
|
|
193
|
+
// TerminateProcess call — the grace ladder is therefore
|
|
194
|
+
// POSIX-only. On Win we pass no signal so Node picks the
|
|
195
|
+
// platform default (TerminateProcess).
|
|
196
|
+
if (IS_WIN)
|
|
197
|
+
state.child.kill();
|
|
198
|
+
else
|
|
199
|
+
state.child.kill("SIGKILL");
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// already dead
|
|
203
|
+
}
|
|
204
|
+
await exited;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** Stop every managed process. */
|
|
208
|
+
async stopAll() {
|
|
209
|
+
await Promise.all([...this.states.keys()].map((id) => this.stop(id)));
|
|
210
|
+
}
|
|
211
|
+
/** Drop the record for an exited process. No-op if still running. */
|
|
212
|
+
forget(id) {
|
|
213
|
+
const state = this.states.get(id);
|
|
214
|
+
if (!state)
|
|
215
|
+
return;
|
|
216
|
+
if (state.proc.status === "running" || state.proc.status === "starting") {
|
|
217
|
+
throw new Error(`RunnerSupervisor.forget: process ${id} is still ${state.proc.status} — stop it first.`);
|
|
218
|
+
}
|
|
219
|
+
this.states.delete(id);
|
|
220
|
+
}
|
|
221
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nwire/supervisor",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Spawns and watches Nwire wire processes for dev tooling. Used by `nwire dev` and Studio's vite dev server. Generic process supervisor with stdout/stderr ring buffer, health-check polling, SIGTERM-then-SIGKILL graceful stop.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"dev",
|
|
8
|
+
"supervisor",
|
|
9
|
+
"process",
|
|
10
|
+
"nwire"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^5.6.0",
|
|
32
|
+
"vitest": "^4.0.18"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc && node ../../scripts/fix-dist-extensions.mjs dist",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
}
|
|
38
|
+
}
|