@columenlabs/lumen-ship 0.1.168 → 0.1.170
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 +28 -0
- package/dist/behavior.d.ts +99 -0
- package/dist/behavior.js +249 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -105,6 +105,34 @@ lumen-ship check parts <folders> # the kit's part pages: hint-text, text-a
|
|
|
105
105
|
|
|
106
106
|
Run each from a package folder in its `lint` script; run `parts` from the repo root with the app folders (`lumen-ship check parts apps/*/web/src`). `magic-numbers` reads the repo's `scripts/magic-numbers.baseline` (or `LUMEN_SHIP_BASELINE`): one `<folder> <count>` a line, lowered in the change that removes one. `parts` reads `scripts/parts.baseline` (or `LUMEN_SHIP_PARTS_BASELINE`): one `<folder> <rule> <count>` a line; `--write-baseline` writes today's counts on a first run. Each rule is explained, with what to write instead, at https://lumen.columenlabs.com/parts/README.md.
|
|
107
107
|
|
|
108
|
+
## Behavior check
|
|
109
|
+
|
|
110
|
+
`@columenlabs/lumen-ship/behavior` checks how an app's Durable Objects act over time, without naming any bug. A sanity run drives the app in the local runtime on a simulated clock, records every wake, and holds every store to the same vital signs.
|
|
111
|
+
|
|
112
|
+
- `simClock()` replaces the global `Date` with a clock the run moves forward. `SimAlarms` keeps each store's alarm instead of the runtime, so nothing fires on real time. The run jumps the clock to the earliest alarm, fires it, and repeats, so a simulated day takes seconds.
|
|
113
|
+
- `judge(wakes, { from, to, stopped }, limits?)` reads the recorded `Wake`s and returns `numbers` (for a diff against main) and `broken` (every vital sign broken, in plain words). Pure: no runtime, no I/O.
|
|
114
|
+
|
|
115
|
+
| Vital sign | Broken when (defaults in `VITALS`) |
|
|
116
|
+
|---|---|
|
|
117
|
+
| Idle wakes | over 12 wakes in one hour wrote nothing and called nothing |
|
|
118
|
+
| Quick idle | over 5 idle wakes in one hour set the next alarm under 60 s ahead |
|
|
119
|
+
| Wake rate | over 120 wakes in one hour, working or not |
|
|
120
|
+
| Alarm in the past | a wake set an alarm before its own time |
|
|
121
|
+
| Rows per wake | one wake read or wrote over 10k rows |
|
|
122
|
+
| Rows per day | one store read over 800k or wrote over 40k rows in a day |
|
|
123
|
+
| Settles | the last 6 hours still woke over 12 an hour |
|
|
124
|
+
| Errors | a wake threw or logged an error |
|
|
125
|
+
| Runaway | the run stopped at 2,000 wakes before the day ended |
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { judge, type Wake } from '@columenlabs/lumen-ship/behavior';
|
|
129
|
+
|
|
130
|
+
const { numbers, broken } = judge(wakes, { from, to, stopped });
|
|
131
|
+
if (broken.length) console.log(broken.map((b) => `${b.rule}: ${b.detail}`).join('\n'));
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Give it varied starting states, not failure cases: a returning user's store, the same store 30 days later, a first visit. A loop, a runaway read or stuck work then breaks a vital sign by itself, whatever caused it.
|
|
135
|
+
|
|
108
136
|
## Versions
|
|
109
137
|
|
|
110
138
|
0.x: any minor may break things until 1.0. Every merge to Lumen's `main` publishes the next patch of every Lumen package; see the [releases](https://github.com/columenlabs/lumen/releases).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavior check: does a store act like a healthy one over a simulated day?
|
|
3
|
+
*
|
|
4
|
+
* A sanity run drives an app's Durable Objects on a simulated clock (`simClock`, `SimAlarms`): it
|
|
5
|
+
* jumps from one alarm to the next, fires it, and records each wake as a `Wake`. `judge` then holds
|
|
6
|
+
* every store to the same vital signs, whatever the app and whatever the cause:
|
|
7
|
+
*
|
|
8
|
+
* idle wakes a wake that writes nothing and calls nothing; at most `idlePerHour` an hour
|
|
9
|
+
* quick idle an idle wake that sets its next alarm under `quickMs` ahead; at most
|
|
10
|
+
* `quickIdlePerHour` an hour (a store that keeps checking and finds nothing)
|
|
11
|
+
* wake rate at most `wakesPerHour` wakes an hour, working or not
|
|
12
|
+
* rows per wake at most `wakeRead` / `wakeWrite` rows (the free tier's 10k a step)
|
|
13
|
+
* rows per day at most `dayRead` / `dayWrite` rows for one store over the day
|
|
14
|
+
* settles the last `settleHours` hours wake no more than `idlePerHour` an hour
|
|
15
|
+
* errors a wake that throws or logs an error
|
|
16
|
+
* runaway the run stopped at `maxWakes` wakes for one state (the driver says so)
|
|
17
|
+
*
|
|
18
|
+
* No list of known bugs: a loop, a runaway read or stuck work breaks a vital sign by itself.
|
|
19
|
+
* Pure: no runtime, no I/O, so the same code judges main and the PR.
|
|
20
|
+
*/
|
|
21
|
+
/** One wake of one store, as the driver saw it. Times are the simulated clock, in ms. */
|
|
22
|
+
export type Wake = {
|
|
23
|
+
/** The store: its class and object id, `Store:ab12…`. */
|
|
24
|
+
store: string;
|
|
25
|
+
/** The simulated time the wake started. */
|
|
26
|
+
at: number;
|
|
27
|
+
/** What it read, wrote and called out to (network and bindings) while it ran. */
|
|
28
|
+
rowsRead: number;
|
|
29
|
+
rowsWritten: number;
|
|
30
|
+
calls: number;
|
|
31
|
+
/** Errors: the wake threw, or logged an error while it ran. */
|
|
32
|
+
errors: number;
|
|
33
|
+
/** The alarm it left set, or null for none. */
|
|
34
|
+
next: number | null;
|
|
35
|
+
/** The parts of the store that were due (a person store's modules), when the driver knows. */
|
|
36
|
+
parts?: string[];
|
|
37
|
+
};
|
|
38
|
+
export type Vitals = {
|
|
39
|
+
idlePerHour: number;
|
|
40
|
+
quickIdlePerHour: number;
|
|
41
|
+
quickMs: number;
|
|
42
|
+
wakesPerHour: number;
|
|
43
|
+
wakeRead: number;
|
|
44
|
+
wakeWrite: number;
|
|
45
|
+
dayRead: number;
|
|
46
|
+
dayWrite: number;
|
|
47
|
+
settleHours: number;
|
|
48
|
+
maxWakes: number;
|
|
49
|
+
};
|
|
50
|
+
/** Defaults for a small personal system on the free plan. An app may tighten any of them. */
|
|
51
|
+
export declare const VITALS: Vitals;
|
|
52
|
+
export type Broken = {
|
|
53
|
+
store: string;
|
|
54
|
+
rule: string;
|
|
55
|
+
detail: string;
|
|
56
|
+
};
|
|
57
|
+
export type Judged = {
|
|
58
|
+
/** Numbers for the run's diff against main: totals and the worst hour of any store. */
|
|
59
|
+
numbers: Record<string, number>;
|
|
60
|
+
/** Every vital sign broken, worst first per rule. Empty is healthy. */
|
|
61
|
+
broken: Broken[];
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Judges the wakes of one simulated span (`from` to `to`, ms). `stopped` is true when the driver
|
|
65
|
+
* hit `maxWakes` before `to`: that alone is a broken vital sign.
|
|
66
|
+
*/
|
|
67
|
+
export declare function judge(wakes: Wake[], span: {
|
|
68
|
+
from: number;
|
|
69
|
+
to: number;
|
|
70
|
+
stopped?: boolean;
|
|
71
|
+
}, limits?: Partial<Vitals>): Judged;
|
|
72
|
+
/**
|
|
73
|
+
* Alarms kept by the sanity run instead of the runtime, one per store. The runtime's own alarm is
|
|
74
|
+
* never set while the clock is simulated, so nothing fires on real time.
|
|
75
|
+
*/
|
|
76
|
+
export declare class SimAlarms {
|
|
77
|
+
private at;
|
|
78
|
+
set(store: string, at: number): void;
|
|
79
|
+
get(store: string): number | null;
|
|
80
|
+
delete(store: string): void;
|
|
81
|
+
/** The earliest alarm at or before `until`, or null. */
|
|
82
|
+
next(until: number): {
|
|
83
|
+
store: string;
|
|
84
|
+
at: number;
|
|
85
|
+
} | null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A clock that runs from real time plus an offset the driver moves forward. Installs itself as the
|
|
89
|
+
* global `Date` (so `Date.now()` and `new Date()` everywhere read it) and never runs backwards.
|
|
90
|
+
*/
|
|
91
|
+
export declare function simClock(g?: {
|
|
92
|
+
Date: DateConstructor;
|
|
93
|
+
}): {
|
|
94
|
+
now: () => number;
|
|
95
|
+
/** Moves the clock to `t` if that is ahead; never back. */
|
|
96
|
+
advanceTo(t: number): void;
|
|
97
|
+
/** Puts the real `Date` back. */
|
|
98
|
+
restore(): void;
|
|
99
|
+
};
|
package/dist/behavior.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavior check: does a store act like a healthy one over a simulated day?
|
|
3
|
+
*
|
|
4
|
+
* A sanity run drives an app's Durable Objects on a simulated clock (`simClock`, `SimAlarms`): it
|
|
5
|
+
* jumps from one alarm to the next, fires it, and records each wake as a `Wake`. `judge` then holds
|
|
6
|
+
* every store to the same vital signs, whatever the app and whatever the cause:
|
|
7
|
+
*
|
|
8
|
+
* idle wakes a wake that writes nothing and calls nothing; at most `idlePerHour` an hour
|
|
9
|
+
* quick idle an idle wake that sets its next alarm under `quickMs` ahead; at most
|
|
10
|
+
* `quickIdlePerHour` an hour (a store that keeps checking and finds nothing)
|
|
11
|
+
* wake rate at most `wakesPerHour` wakes an hour, working or not
|
|
12
|
+
* rows per wake at most `wakeRead` / `wakeWrite` rows (the free tier's 10k a step)
|
|
13
|
+
* rows per day at most `dayRead` / `dayWrite` rows for one store over the day
|
|
14
|
+
* settles the last `settleHours` hours wake no more than `idlePerHour` an hour
|
|
15
|
+
* errors a wake that throws or logs an error
|
|
16
|
+
* runaway the run stopped at `maxWakes` wakes for one state (the driver says so)
|
|
17
|
+
*
|
|
18
|
+
* No list of known bugs: a loop, a runaway read or stuck work breaks a vital sign by itself.
|
|
19
|
+
* Pure: no runtime, no I/O, so the same code judges main and the PR.
|
|
20
|
+
*/
|
|
21
|
+
/** Defaults for a small personal system on the free plan. An app may tighten any of them. */
|
|
22
|
+
export const VITALS = {
|
|
23
|
+
idlePerHour: 12,
|
|
24
|
+
quickIdlePerHour: 5,
|
|
25
|
+
quickMs: 60_000,
|
|
26
|
+
wakesPerHour: 120,
|
|
27
|
+
wakeRead: 10_000,
|
|
28
|
+
wakeWrite: 10_000,
|
|
29
|
+
dayRead: 800_000,
|
|
30
|
+
dayWrite: 40_000,
|
|
31
|
+
settleHours: 6,
|
|
32
|
+
maxWakes: 2_000,
|
|
33
|
+
};
|
|
34
|
+
const HOUR = 3_600_000;
|
|
35
|
+
const idle = (w) => w.rowsWritten === 0 && w.calls === 0 && w.errors === 0;
|
|
36
|
+
const short = (store) => store.replace(/:([0-9a-f]{6})[0-9a-f]+$/, ':$1');
|
|
37
|
+
const partsOf = (ws) => {
|
|
38
|
+
const n = new Map();
|
|
39
|
+
for (const w of ws)
|
|
40
|
+
for (const p of w.parts ?? [])
|
|
41
|
+
n.set(p, (n.get(p) ?? 0) + 1);
|
|
42
|
+
const top = [...n].sort((a, b) => b[1] - a[1])[0];
|
|
43
|
+
return top ? ` (mostly ${top[0]})` : '';
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Judges the wakes of one simulated span (`from` to `to`, ms). `stopped` is true when the driver
|
|
47
|
+
* hit `maxWakes` before `to`: that alone is a broken vital sign.
|
|
48
|
+
*/
|
|
49
|
+
export function judge(wakes, span, limits = {}) {
|
|
50
|
+
const v = { ...VITALS, ...limits };
|
|
51
|
+
const hours = Math.max(1, Math.ceil((span.to - span.from) / HOUR));
|
|
52
|
+
const broken = [];
|
|
53
|
+
const numbers = {
|
|
54
|
+
wakes: wakes.length,
|
|
55
|
+
idleWakes: 0,
|
|
56
|
+
quickIdleWakes: 0,
|
|
57
|
+
rowsRead: 0,
|
|
58
|
+
rowsWritten: 0,
|
|
59
|
+
calls: 0,
|
|
60
|
+
errors: 0,
|
|
61
|
+
stores: 0,
|
|
62
|
+
maxWakesHour: 0,
|
|
63
|
+
maxIdleHour: 0,
|
|
64
|
+
maxRowsReadWake: 0,
|
|
65
|
+
maxRowsWrittenWake: 0,
|
|
66
|
+
lastHoursWakes: 0,
|
|
67
|
+
};
|
|
68
|
+
const byStore = new Map();
|
|
69
|
+
for (const w of wakes) {
|
|
70
|
+
const list = byStore.get(w.store) ?? [];
|
|
71
|
+
list.push(w);
|
|
72
|
+
byStore.set(w.store, list);
|
|
73
|
+
}
|
|
74
|
+
numbers.stores = byStore.size;
|
|
75
|
+
const settleFrom = span.to - v.settleHours * HOUR;
|
|
76
|
+
for (const [store, ws] of byStore) {
|
|
77
|
+
const name = short(store);
|
|
78
|
+
const perHour = new Array(hours).fill(0);
|
|
79
|
+
const idleHour = new Array(hours).fill(0);
|
|
80
|
+
const quickHour = new Array(hours).fill(0);
|
|
81
|
+
let read = 0;
|
|
82
|
+
let written = 0;
|
|
83
|
+
let errors = 0;
|
|
84
|
+
let last = 0;
|
|
85
|
+
let past = 0;
|
|
86
|
+
let worstRead;
|
|
87
|
+
let worstWrite;
|
|
88
|
+
for (const w of ws) {
|
|
89
|
+
const h = Math.min(hours - 1, Math.max(0, Math.floor((w.at - span.from) / HOUR)));
|
|
90
|
+
perHour[h] = (perHour[h] ?? 0) + 1;
|
|
91
|
+
read += w.rowsRead;
|
|
92
|
+
written += w.rowsWritten;
|
|
93
|
+
errors += w.errors;
|
|
94
|
+
numbers.calls += w.calls;
|
|
95
|
+
if (idle(w)) {
|
|
96
|
+
idleHour[h] = (idleHour[h] ?? 0) + 1;
|
|
97
|
+
numbers.idleWakes++;
|
|
98
|
+
if (w.next !== null && w.next - w.at < v.quickMs) {
|
|
99
|
+
quickHour[h] = (quickHour[h] ?? 0) + 1;
|
|
100
|
+
numbers.quickIdleWakes++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (w.next !== null && w.next < w.at)
|
|
104
|
+
past++;
|
|
105
|
+
if (w.at >= settleFrom)
|
|
106
|
+
last++;
|
|
107
|
+
if (!worstRead || w.rowsRead > worstRead.rowsRead)
|
|
108
|
+
worstRead = w;
|
|
109
|
+
if (!worstWrite || w.rowsWritten > worstWrite.rowsWritten)
|
|
110
|
+
worstWrite = w;
|
|
111
|
+
}
|
|
112
|
+
numbers.rowsRead += read;
|
|
113
|
+
numbers.rowsWritten += written;
|
|
114
|
+
numbers.errors += errors;
|
|
115
|
+
const maxW = Math.max(...perHour);
|
|
116
|
+
const maxI = Math.max(...idleHour);
|
|
117
|
+
const maxQ = Math.max(...quickHour);
|
|
118
|
+
numbers.maxWakesHour = Math.max(numbers.maxWakesHour, maxW);
|
|
119
|
+
numbers.maxIdleHour = Math.max(numbers.maxIdleHour, maxI);
|
|
120
|
+
numbers.maxRowsReadWake = Math.max(numbers.maxRowsReadWake, worstRead?.rowsRead ?? 0);
|
|
121
|
+
numbers.maxRowsWrittenWake = Math.max(numbers.maxRowsWrittenWake, worstWrite?.rowsWritten ?? 0);
|
|
122
|
+
numbers.lastHoursWakes = Math.max(numbers.lastHoursWakes, last);
|
|
123
|
+
const why = partsOf(ws);
|
|
124
|
+
if (maxI > v.idlePerHour)
|
|
125
|
+
broken.push({
|
|
126
|
+
store,
|
|
127
|
+
rule: 'idle wakes',
|
|
128
|
+
detail: `${name}: ${maxI} wakes in one hour did nothing (limit ${v.idlePerHour})${why}`,
|
|
129
|
+
});
|
|
130
|
+
if (maxQ > v.quickIdlePerHour)
|
|
131
|
+
broken.push({
|
|
132
|
+
store,
|
|
133
|
+
rule: 'quick idle',
|
|
134
|
+
detail: `${name}: ${maxQ} idle wakes in one hour set the next under ${v.quickMs / 1000} s (limit ${v.quickIdlePerHour})${why}`,
|
|
135
|
+
});
|
|
136
|
+
if (maxW > v.wakesPerHour)
|
|
137
|
+
broken.push({
|
|
138
|
+
store,
|
|
139
|
+
rule: 'wake rate',
|
|
140
|
+
detail: `${name}: ${maxW} wakes in one hour (limit ${v.wakesPerHour})${why}`,
|
|
141
|
+
});
|
|
142
|
+
if (past)
|
|
143
|
+
broken.push({
|
|
144
|
+
store,
|
|
145
|
+
rule: 'alarm in the past',
|
|
146
|
+
detail: `${name}: ${past} wakes set an alarm before their own time${why}`,
|
|
147
|
+
});
|
|
148
|
+
if (worstRead && worstRead.rowsRead > v.wakeRead)
|
|
149
|
+
broken.push({
|
|
150
|
+
store,
|
|
151
|
+
rule: 'rows per wake',
|
|
152
|
+
detail: `${name}: one wake read ${worstRead.rowsRead} rows (limit ${v.wakeRead})${partsOf([worstRead])}`,
|
|
153
|
+
});
|
|
154
|
+
if (worstWrite && worstWrite.rowsWritten > v.wakeWrite)
|
|
155
|
+
broken.push({
|
|
156
|
+
store,
|
|
157
|
+
rule: 'rows per wake',
|
|
158
|
+
detail: `${name}: one wake wrote ${worstWrite.rowsWritten} rows (limit ${v.wakeWrite})${partsOf([worstWrite])}`,
|
|
159
|
+
});
|
|
160
|
+
const days = Math.max(1, (span.to - span.from) / (24 * HOUR));
|
|
161
|
+
if (read / days > v.dayRead)
|
|
162
|
+
broken.push({
|
|
163
|
+
store,
|
|
164
|
+
rule: 'rows per day',
|
|
165
|
+
detail: `${name}: read ${Math.round(read / days)} rows a day (limit ${v.dayRead})${why}`,
|
|
166
|
+
});
|
|
167
|
+
if (written / days > v.dayWrite)
|
|
168
|
+
broken.push({
|
|
169
|
+
store,
|
|
170
|
+
rule: 'rows per day',
|
|
171
|
+
detail: `${name}: wrote ${Math.round(written / days)} rows a day (limit ${v.dayWrite})${why}`,
|
|
172
|
+
});
|
|
173
|
+
if (span.to - span.from > v.settleHours * HOUR && last > v.idlePerHour * v.settleHours)
|
|
174
|
+
broken.push({
|
|
175
|
+
store,
|
|
176
|
+
rule: 'settles',
|
|
177
|
+
detail: `${name}: still ${last} wakes in the last ${v.settleHours} h (limit ${v.idlePerHour * v.settleHours})${why}`,
|
|
178
|
+
});
|
|
179
|
+
if (errors)
|
|
180
|
+
broken.push({ store, rule: 'errors', detail: `${name}: ${errors} wake errors${why}` });
|
|
181
|
+
}
|
|
182
|
+
if (span.stopped)
|
|
183
|
+
broken.unshift({
|
|
184
|
+
store: '',
|
|
185
|
+
rule: 'runaway',
|
|
186
|
+
detail: `stopped at ${v.maxWakes} wakes, ${Math.round((wakes.at(-1)?.at ?? span.from) - span.from) / 1000} simulated seconds in${partsOf(wakes)}`,
|
|
187
|
+
});
|
|
188
|
+
return { numbers, broken };
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Alarms kept by the sanity run instead of the runtime, one per store. The runtime's own alarm is
|
|
192
|
+
* never set while the clock is simulated, so nothing fires on real time.
|
|
193
|
+
*/
|
|
194
|
+
export class SimAlarms {
|
|
195
|
+
at = new Map();
|
|
196
|
+
set(store, at) {
|
|
197
|
+
this.at.set(store, at);
|
|
198
|
+
}
|
|
199
|
+
get(store) {
|
|
200
|
+
return this.at.get(store) ?? null;
|
|
201
|
+
}
|
|
202
|
+
delete(store) {
|
|
203
|
+
this.at.delete(store);
|
|
204
|
+
}
|
|
205
|
+
/** The earliest alarm at or before `until`, or null. */
|
|
206
|
+
next(until) {
|
|
207
|
+
let best = null;
|
|
208
|
+
for (const [store, at] of this.at)
|
|
209
|
+
if (at <= until && (!best || at < best.at))
|
|
210
|
+
best = { store, at };
|
|
211
|
+
return best;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* A clock that runs from real time plus an offset the driver moves forward. Installs itself as the
|
|
216
|
+
* global `Date` (so `Date.now()` and `new Date()` everywhere read it) and never runs backwards.
|
|
217
|
+
*/
|
|
218
|
+
export function simClock(g = globalThis) {
|
|
219
|
+
const Real = g.Date;
|
|
220
|
+
let offset = 0;
|
|
221
|
+
const now = () => Real.now() + offset;
|
|
222
|
+
class SimDate extends Real {
|
|
223
|
+
// biome-ignore lint/suspicious/noExplicitAny: Date's constructor takes any of its overloads.
|
|
224
|
+
constructor(...a) {
|
|
225
|
+
if (a.length === 0)
|
|
226
|
+
super(now());
|
|
227
|
+
// @ts-expect-error: spread into Date's overloads.
|
|
228
|
+
else
|
|
229
|
+
super(...a);
|
|
230
|
+
}
|
|
231
|
+
static now() {
|
|
232
|
+
return now();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
g.Date = SimDate;
|
|
236
|
+
return {
|
|
237
|
+
now,
|
|
238
|
+
/** Moves the clock to `t` if that is ahead; never back. */
|
|
239
|
+
advanceTo(t) {
|
|
240
|
+
const d = t - now();
|
|
241
|
+
if (d > 0)
|
|
242
|
+
offset += d;
|
|
243
|
+
},
|
|
244
|
+
/** Puts the real `Date` back. */
|
|
245
|
+
restore() {
|
|
246
|
+
g.Date = Real;
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ship",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Lumen's shipping kit: release notes, the What's new changelog and
|
|
3
|
+
"version": "0.1.170",
|
|
4
|
+
"description": "Lumen's shipping kit: release notes, the What's new changelog, lint guards and the behavior check.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"./releases": {
|
|
15
15
|
"types": "./dist/releases.d.ts",
|
|
16
16
|
"default": "./dist/releases.js"
|
|
17
|
+
},
|
|
18
|
+
"./behavior": {
|
|
19
|
+
"types": "./dist/behavior.d.ts",
|
|
20
|
+
"default": "./dist/behavior.js"
|
|
17
21
|
}
|
|
18
22
|
},
|
|
19
23
|
"bin": {
|