@db-lyon/flowkit 0.12.0 → 0.14.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 +12 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/guard/index.d.ts +7 -0
- package/dist/guard/index.d.ts.map +1 -0
- package/dist/guard/index.js +5 -0
- package/dist/guard/index.js.map +1 -0
- package/dist/guard/pipeline.d.ts +22 -0
- package/dist/guard/pipeline.d.ts.map +1 -0
- package/dist/guard/pipeline.js +45 -0
- package/dist/guard/pipeline.js.map +1 -0
- package/dist/guard/registry.d.ts +20 -0
- package/dist/guard/registry.d.ts.map +1 -0
- package/dist/guard/registry.js +33 -0
- package/dist/guard/registry.js.map +1 -0
- package/dist/guard/task-guards.d.ts +89 -0
- package/dist/guard/task-guards.d.ts.map +1 -0
- package/dist/guard/task-guards.js +83 -0
- package/dist/guard/task-guards.js.map +1 -0
- package/dist/guard/types.d.ts +49 -0
- package/dist/guard/types.d.ts.map +1 -0
- package/dist/guard/types.js +38 -0
- package/dist/guard/types.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +6 -0
- package/dist/references.js.map +1 -1
- package/dist/task/shell-task.d.ts +2 -0
- package/dist/task/shell-task.d.ts.map +1 -1
- package/dist/task/shell-task.js +186 -40
- package/dist/task/shell-task.js.map +1 -1
- package/dist/task/shell-termination.d.ts +34 -0
- package/dist/task/shell-termination.d.ts.map +1 -0
- package/dist/task/shell-termination.js +179 -0
- package/dist/task/shell-termination.js.map +1 -0
- package/docs/api-reference.md +139 -1
- package/docs/custom-tasks.md +27 -1
- package/docs/guards.md +133 -0
- package/package.json +4 -2
package/docs/api-reference.md
CHANGED
|
@@ -171,7 +171,8 @@ interface TaskResult {
|
|
|
171
171
|
|
|
172
172
|
### `ShellTask`
|
|
173
173
|
|
|
174
|
-
Built-in task that executes shell commands
|
|
174
|
+
Built-in task that executes shell commands through the platform shell with
|
|
175
|
+
streamed stdout and stderr.
|
|
175
176
|
|
|
176
177
|
```typescript
|
|
177
178
|
class ShellTask extends BaseTask<ShellTaskOptions> {
|
|
@@ -188,11 +189,38 @@ class ShellTask extends BaseTask<ShellTaskOptions> {
|
|
|
188
189
|
| `command` | `string` | (required) | Shell command to execute |
|
|
189
190
|
| `cwd` | `string` | `undefined` | Working directory |
|
|
190
191
|
| `timeout` | `number` | `300000` | Timeout in milliseconds (5 min) |
|
|
192
|
+
| `signal` | `AbortSignal` | `undefined` | Cancels this individual programmatic invocation; not representable in YAML |
|
|
191
193
|
|
|
192
194
|
**Success result:** `data.output` contains trimmed stdout.
|
|
193
195
|
|
|
194
196
|
**Failure result:** `data.exitCode`, `data.stderr`, `data.stdout`.
|
|
195
197
|
|
|
198
|
+
When `signal` is already aborted, no shell process is launched and the task
|
|
199
|
+
returns the normal failed result with `Shell command cancelled`. An abort during
|
|
200
|
+
execution requests termination of the spawned shell process. On Windows,
|
|
201
|
+
Flowkit makes a best-effort `taskkill /T /F` request for that invocation's
|
|
202
|
+
shell PID and falls back to direct shell termination only if that request
|
|
203
|
+
fails or the three-second terminal deadline expires. On POSIX,
|
|
204
|
+
signal-bearing invocations run in a dedicated process group. Flowkit requests
|
|
205
|
+
`SIGTERM`, allows 250ms for cooperative cleanup, and then escalates the group
|
|
206
|
+
to `SIGKILL`. Neither approach guarantees termination of descendants that have
|
|
207
|
+
escaped the managed process tree or group; callers must not treat the returned
|
|
208
|
+
result as proof of complete descendant termination.
|
|
209
|
+
|
|
210
|
+
For signal-bearing invocations, Flowkit waits for the spawned shell to close
|
|
211
|
+
after requesting termination. If the operating system does not report closure
|
|
212
|
+
within one second on POSIX or three seconds on Windows, it returns the
|
|
213
|
+
cancellation or timeout result with the output captured so far; that bounded
|
|
214
|
+
fallback requests force termination and releases Flowkit's Node handles for
|
|
215
|
+
the root shell and any Windows `taskkill` helper, but is not confirmation that
|
|
216
|
+
either process, or any escaped descendant, has
|
|
217
|
+
exited. POSIX
|
|
218
|
+
signal-bearing invocations are in a separate process group, so terminal Ctrl+C
|
|
219
|
+
does not reach them automatically; cancel through the supplied `AbortSignal`.
|
|
220
|
+
|
|
221
|
+
Trailing stdout and stderr fragments are captured once. This corrects the
|
|
222
|
+
duplicate final-partial-line output in earlier releases.
|
|
223
|
+
|
|
196
224
|
---
|
|
197
225
|
|
|
198
226
|
### `TaskRegistry`
|
|
@@ -403,6 +431,116 @@ class MissingDependencyError extends Error {
|
|
|
403
431
|
|
|
404
432
|
---
|
|
405
433
|
|
|
434
|
+
## Guard
|
|
435
|
+
|
|
436
|
+
*Import from `@db-lyon/flowkit` or `@db-lyon/flowkit/guard`*
|
|
437
|
+
|
|
438
|
+
A before/after pipeline around one host operation. See [guards.md](guards.md) for the guide.
|
|
439
|
+
|
|
440
|
+
### `Guard<Ctx, TResult>`
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
interface Guard<Ctx extends GuardContext = GuardContext, TResult = unknown> {
|
|
444
|
+
readonly name: string;
|
|
445
|
+
readonly order?: number; // lower runs first, default 0
|
|
446
|
+
appliesTo?(ctx: Ctx): boolean | Promise<boolean>; // default: always
|
|
447
|
+
before?(ctx: Ctx): Promise<void>; // throw to DENY
|
|
448
|
+
after?(ctx: Ctx, result: TResult): Promise<TResult | void>; // return to replace
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
### `GuardContext`
|
|
455
|
+
|
|
456
|
+
The minimum a host context must provide. Extend it with whatever the operation carries.
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
interface GuardContext {
|
|
460
|
+
meta: Map<string, unknown>; // scratch space shared across guards for one operation
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function guardContextBase(): GuardContext
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
### `lazy(ctx, key, compute)`
|
|
469
|
+
|
|
470
|
+
Wrap a computation so it runs at most once per operation, cached into `ctx.meta` under `key`.
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
function lazy<T>(ctx: GuardContext, key: string, compute: () => T): () => T
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
### `GuardRegistry<Ctx, TResult>`
|
|
479
|
+
|
|
480
|
+
Ordered set of guards. Sorted on registration by `order`, then by name.
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
class GuardRegistry<Ctx extends GuardContext = GuardContext, TResult = unknown> {
|
|
484
|
+
register(guard: Guard<Ctx, TResult>): this;
|
|
485
|
+
registerAll(guards: Iterable<Guard<Ctx, TResult>>): this;
|
|
486
|
+
list(): readonly Guard<Ctx, TResult>[];
|
|
487
|
+
names(): string[];
|
|
488
|
+
get size(): number;
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
### `runGuarded(ctx, registry, invoke)`
|
|
495
|
+
|
|
496
|
+
Run one operation through the pipeline: `before` in order, `invoke`, `after` in reverse.
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
function runGuarded<Ctx extends GuardContext, TResult>(
|
|
500
|
+
ctx: Ctx,
|
|
501
|
+
registry: GuardRegistry<Ctx, TResult>,
|
|
502
|
+
invoke: () => Promise<TResult>,
|
|
503
|
+
): Promise<TResult>
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Applicability resolves once, up front. A `before` throw denies the operation and propagates unchanged. With an empty registry this is exactly `invoke()`.
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
### `discoverTaskGuards(registry, options)`
|
|
511
|
+
|
|
512
|
+
Build a `Guard` for every `guard.<name>.<before|after><Scope?>` task in a `TaskRegistry`.
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
function discoverTaskGuards<Ctx extends GuardContext, TResult = unknown>(
|
|
516
|
+
registry: TaskRegistry,
|
|
517
|
+
options: DiscoverTaskGuardsOptions<Ctx, TResult>,
|
|
518
|
+
): Guard<Ctx, TResult>[]
|
|
519
|
+
|
|
520
|
+
interface DiscoverTaskGuardsOptions<Ctx extends GuardContext, TResult = unknown> {
|
|
521
|
+
scopes?: Record<string, (ctx: Ctx) => boolean | Promise<boolean>>;
|
|
522
|
+
contextFor(ctx: Ctx): TaskContext;
|
|
523
|
+
optionsFor(ctx: Ctx, result?: TResult): Record<string, unknown>;
|
|
524
|
+
onDeny?(info: GuardTaskFailure<Ctx>): Error;
|
|
525
|
+
onError?(info: GuardTaskFailure<Ctx>): Error;
|
|
526
|
+
onAfterFailure?(info: GuardTaskFailure<Ctx>): void;
|
|
527
|
+
logger?: Logger;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
interface GuardTaskFailure<Ctx extends GuardContext> {
|
|
531
|
+
readonly guard: string; // 'p4' for guard.p4.beforeWrite
|
|
532
|
+
readonly phase: string; // 'beforeWrite'
|
|
533
|
+
readonly taskName: string; // 'guard.p4.beforeWrite'
|
|
534
|
+
readonly ctx: Ctx;
|
|
535
|
+
readonly reason: string;
|
|
536
|
+
readonly cause?: Error;
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
Throws at discovery if a task names a scope the host did not register.
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
406
544
|
## Logger
|
|
407
545
|
|
|
408
546
|
*Import from `@db-lyon/flowkit`*
|
package/docs/custom-tasks.md
CHANGED
|
@@ -187,7 +187,8 @@ The module must have either a `default` export or a named export matching the la
|
|
|
187
187
|
|
|
188
188
|
## Built-in: ShellTask
|
|
189
189
|
|
|
190
|
-
`ShellTask` executes shell commands
|
|
190
|
+
`ShellTask` executes shell commands through the platform shell and streams output.
|
|
191
|
+
Register it under any name you like:
|
|
191
192
|
|
|
192
193
|
```typescript
|
|
193
194
|
import { ShellTask } from '@db-lyon/flowkit';
|
|
@@ -221,9 +222,34 @@ tasks:
|
|
|
221
222
|
| `command` | `string` | (required) | The shell command to execute |
|
|
222
223
|
| `cwd` | `string` | `undefined` | Working directory |
|
|
223
224
|
| `timeout` | `number` | `300000` (5 min) | Timeout in milliseconds |
|
|
225
|
+
| `signal` | `AbortSignal` | `undefined` | Cancels one programmatic invocation; cannot be specified in YAML |
|
|
224
226
|
|
|
225
227
|
On success, `result.data.output` contains the trimmed stdout. On failure, `result.data` includes `exitCode`, `stderr`, and `stdout`.
|
|
226
228
|
|
|
229
|
+
Existing YAML consumers need no change. Programmatic callers that need
|
|
230
|
+
cancellation pass an invocation-specific `AbortSignal` in the task options.
|
|
231
|
+
After cancellation, Flowkit waits for the shell to close, with a bounded
|
|
232
|
+
fallback if the operating system does not report closure (one second on POSIX,
|
|
233
|
+
three seconds on Windows). That fallback does not guarantee all descendants
|
|
234
|
+
have exited. At the deadline Flowkit requests force termination and releases
|
|
235
|
+
its Node handles for the root shell and any Windows `taskkill` helper before
|
|
236
|
+
returning. On POSIX,
|
|
237
|
+
signal-bearing invocations use a dedicated process group: Flowkit requests
|
|
238
|
+
`SIGTERM`, waits 250ms for cooperative cleanup, then escalates the group to
|
|
239
|
+
`SIGKILL`. Terminal Ctrl+C is not delivered to that separate group, so use the
|
|
240
|
+
supplied `AbortSignal` for cancellation.
|
|
241
|
+
|
|
242
|
+
On Windows, Flowkit asks `taskkill /T /F` to terminate the shell tree and does
|
|
243
|
+
not kill the shell while that traversal is in progress. If `taskkill` fails or
|
|
244
|
+
the three-second deadline expires, Flowkit requests force termination and
|
|
245
|
+
releases its Node handles for the root and helper. Windows and POSIX descendants that escape the managed process
|
|
246
|
+
tree or process group may still survive; Node does not provide a portable
|
|
247
|
+
guarantee of complete descendant termination.
|
|
248
|
+
|
|
249
|
+
Trailing stdout and stderr fragments are captured once, including when no
|
|
250
|
+
`signal` is supplied. This corrects the duplicate final-partial-line output in
|
|
251
|
+
earlier releases.
|
|
252
|
+
|
|
227
253
|
## Listing registered tasks
|
|
228
254
|
|
|
229
255
|
```typescript
|
package/docs/guards.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Guards
|
|
2
|
+
|
|
3
|
+
A guard is a `before`/`after` pipeline around one host operation. It sits on a seam the host already has (an RPC call, a write, a command dispatch) and may veto that operation, act on it, or observe its result.
|
|
4
|
+
|
|
5
|
+
The pipeline knows nothing about what any guard does. Access policy, source control checkout, audit logging, rate limiting and approval gating are all just guards.
|
|
6
|
+
|
|
7
|
+
Guards are distinct from [`FlowRunnerHooks`](api-reference.md#flow), which fire around flow steps. A hook observes a step; a guard wraps one host operation and can deny it.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { GuardRegistry, runGuarded, guardContextBase, lazy } from '@db-lyon/flowkit/guard';
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## The context
|
|
14
|
+
|
|
15
|
+
Flowkit requires only a scratch map. Everything else belongs to the host.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
interface GuardContext {
|
|
19
|
+
readonly meta: Map<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Extend it with whatever your operation carries, and build it with `guardContextBase()`:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
interface CallContext extends GuardContext {
|
|
27
|
+
readonly method: string;
|
|
28
|
+
readonly params: Record<string, unknown>;
|
|
29
|
+
/** Files this call will modify. Computed on demand. */
|
|
30
|
+
files(): string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function makeCallContext(method: string, params: Record<string, unknown>): CallContext {
|
|
34
|
+
const ctx = { ...guardContextBase(), method, params } as CallContext;
|
|
35
|
+
ctx.files = lazy(ctx, 'files', () => classify(method, params));
|
|
36
|
+
return ctx;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`lazy(ctx, key, compute)` caches into `meta`, so enrichment that is expensive to compute and that most guards never consult costs nothing when it is ignored and is computed once when several guards want it.
|
|
41
|
+
|
|
42
|
+
`meta` is also how guards talk to each other. A `before` hook can stash what it did and the matching `after` hook, or a later guard, can read it back.
|
|
43
|
+
|
|
44
|
+
## Writing a guard
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
interface Guard<Ctx extends GuardContext, TResult> {
|
|
48
|
+
readonly name: string;
|
|
49
|
+
readonly order?: number;
|
|
50
|
+
appliesTo?(ctx: Ctx): boolean | Promise<boolean>;
|
|
51
|
+
before?(ctx: Ctx): Promise<void>;
|
|
52
|
+
after?(ctx: Ctx, result: TResult): Promise<TResult | void>;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- **`before`** runs before the operation. Throw to deny it: the operation never happens and your error propagates unchanged, so the host's own error type survives.
|
|
57
|
+
- **`after`** runs after a successful operation. Return a value to replace the result; return nothing to leave it alone.
|
|
58
|
+
- **`appliesTo`** decides whether the guard participates at all. Default is always.
|
|
59
|
+
- **`order`** sorts the chain, lower first. Ties break by name, so a registry built from an unordered source still runs deterministically.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const sourceControl: Guard<CallContext, unknown> = {
|
|
63
|
+
name: 'source-control',
|
|
64
|
+
order: 10,
|
|
65
|
+
appliesTo: (ctx) => ctx.files().length > 0,
|
|
66
|
+
before: async (ctx) => {
|
|
67
|
+
const denied = await checkout(ctx.files());
|
|
68
|
+
if (denied.length) throw new Error(`locked by another user: ${denied.join(', ')}`);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Running the pipeline
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const guards = new GuardRegistry<CallContext, Result>().registerAll([sourceControl, audit]);
|
|
77
|
+
|
|
78
|
+
async function call(method: string, params: Record<string, unknown>): Promise<Result> {
|
|
79
|
+
return runGuarded(makeCallContext(method, params), guards, () => transport.send(method, params));
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`before` hooks run in registration order, then `invoke`, then `after` hooks in reverse order, so a guard's two halves nest rather than interleave.
|
|
84
|
+
|
|
85
|
+
Applicability resolves once, up front. A guard whose `before` changes the answer to its own `appliesTo` (a source-control guard that checks a file out, making it writable) still gets its `after` half.
|
|
86
|
+
|
|
87
|
+
With an empty registry `runGuarded` is exactly `invoke()`, so it is safe to install on a seam before any guard exists.
|
|
88
|
+
|
|
89
|
+
## Guards from tasks
|
|
90
|
+
|
|
91
|
+
A host that already loads tasks from config or from plugins gets guards for free. Name a task `guard.<name>.<phase>` and `discoverTaskGuards` turns it into a guard. No separate activation concept is needed, because the task registry is already the list of everything the host was given.
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
94
|
+
tasks:
|
|
95
|
+
guard.p4.beforeWrite:
|
|
96
|
+
class_path: ./guards/perforce.js
|
|
97
|
+
guard.audit.after:
|
|
98
|
+
class_path: ./guards/audit.js
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`<phase>` is `before` or `after`, optionally suffixed with a scope the host declared:
|
|
102
|
+
|
|
103
|
+
| Task name | Runs |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `guard.audit.before` | before every operation |
|
|
106
|
+
| `guard.audit.after` | after every successful operation |
|
|
107
|
+
| `guard.p4.beforeWrite` | before an operation the `write` scope claims |
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const guards = discoverTaskGuards<CallContext, Result>(taskRegistry, {
|
|
111
|
+
scopes: { write: (ctx) => ctx.files().length > 0 },
|
|
112
|
+
contextFor: (ctx) => ({ logger, registry: taskRegistry, transport: ctx.transport }),
|
|
113
|
+
optionsFor: (ctx, result) => ({
|
|
114
|
+
method: ctx.method,
|
|
115
|
+
params: ctx.params,
|
|
116
|
+
paths: ctx.files(),
|
|
117
|
+
...(result !== undefined ? { result } : {}),
|
|
118
|
+
}),
|
|
119
|
+
onDeny: (info) => new PolicyError(`blocked (${info.ctx.method}): ${info.reason}`),
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`contextFor` is called per operation, so a guard can be bound to whatever the operation belongs to rather than to a single ambient target. A host driving several connections should hand each guard the one serving the call it is guarding.
|
|
124
|
+
|
|
125
|
+
A scope named by a task but not registered by the host is an error at discovery time. A typo in a plugin's task name surfaces at startup rather than becoming a guard that silently runs on everything.
|
|
126
|
+
|
|
127
|
+
### Denial and failure
|
|
128
|
+
|
|
129
|
+
A `before` guard denies by returning `success: false` or by throwing. Both route to `onDeny`, because `BaseTask.run` turns an exception into a failed result: a guard that crashes denies the operation rather than waving it through, which is the safe direction for the thing standing between a caller and a mutation.
|
|
130
|
+
|
|
131
|
+
`onError` is separate and narrower. It fires only when the guard task cannot be constructed at all, an unresolvable class path or a module that fails to import, where nothing about the operation was evaluated.
|
|
132
|
+
|
|
133
|
+
An `after` guard observes the result and cannot replace it, since a task returns a `TaskResult` rather than the host's result type. A failure is reported through `onAfterFailure` instead of failing an operation that already happened. Reach for a hand-written `Guard` when you need an `after` hook that transforms the result.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@db-lyon/flowkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "YAML-configured task and flow orchestration engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"./config": "./dist/config/index.js",
|
|
11
11
|
"./task": "./dist/task/index.js",
|
|
12
12
|
"./flow": "./dist/flow/index.js",
|
|
13
|
-
"./dag": "./dist/dag/index.js"
|
|
13
|
+
"./dag": "./dist/dag/index.js",
|
|
14
|
+
"./guard": "./dist/guard/index.js"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
16
17
|
"/dist",
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
"build": "tsc -b",
|
|
21
22
|
"prepublishOnly": "tsc -b",
|
|
22
23
|
"test": "vitest run",
|
|
24
|
+
"test:package-api": "npm run build && tsc -p test/tsconfig.public-api.json",
|
|
23
25
|
"test:watch": "vitest"
|
|
24
26
|
},
|
|
25
27
|
"keywords": [
|