@lovelaces-io/storyteller 0.1.0 → 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 +72 -73
- package/dist/index.cjs +227 -176
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -36
- package/dist/index.d.ts +119 -36
- package/dist/index.js +225 -174
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
package/dist/index.js
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
// src/audiences/consoleAudience.ts
|
|
2
|
+
function consoleAudience() {
|
|
3
|
+
return {
|
|
4
|
+
name: "console",
|
|
5
|
+
hear: (event) => {
|
|
6
|
+
const prefix = "Storyteller";
|
|
7
|
+
const style = event.level === "Information" ? "color:#16a34a;font-weight:600" : event.level === "Warning" ? "color:#f59e0b;font-weight:600" : "color:#dc2626;font-weight:600";
|
|
8
|
+
const header = `${prefix}: ${event.title}`;
|
|
9
|
+
console.groupCollapsed(`%c${header}`, style);
|
|
10
|
+
const payload = JSON.stringify(event, null, 2);
|
|
11
|
+
if (event.level === "Information") {
|
|
12
|
+
console.log(header, payload);
|
|
13
|
+
} else if (event.level === "Warning") {
|
|
14
|
+
console.warn(header, payload);
|
|
15
|
+
} else {
|
|
16
|
+
console.error(header, payload);
|
|
17
|
+
}
|
|
18
|
+
console.groupEnd();
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
1
23
|
// src/utils.ts
|
|
2
24
|
var ANSI = {
|
|
3
25
|
reset: "\x1B[0m",
|
|
@@ -5,18 +27,21 @@ var ANSI = {
|
|
|
5
27
|
yellow: "\x1B[33m",
|
|
6
28
|
red: "\x1B[38;2;250;128;114m",
|
|
7
29
|
grayLight: "\x1B[37m",
|
|
8
|
-
grayDark: "\x1B[
|
|
30
|
+
grayDark: "\x1B[90m"
|
|
9
31
|
};
|
|
10
32
|
function getLevelColor(level) {
|
|
11
|
-
if (level === "
|
|
12
|
-
if (level === "
|
|
33
|
+
if (level === "Information") return ANSI.green;
|
|
34
|
+
if (level === "Warning") return ANSI.yellow;
|
|
13
35
|
return ANSI.red;
|
|
14
36
|
}
|
|
15
37
|
function formatOrigin(origin) {
|
|
16
38
|
if (!origin?.where) return;
|
|
17
39
|
if (typeof origin.where === "string") return origin.where;
|
|
18
40
|
const whereRecord = origin.where;
|
|
19
|
-
const
|
|
41
|
+
const priorityKeys = ["app", "service", "page", "component"];
|
|
42
|
+
const priorityParts = priorityKeys.filter((key) => whereRecord[key] != null).map((key) => String(whereRecord[key]));
|
|
43
|
+
const extraParts = Object.entries(whereRecord).filter(([key, value]) => !priorityKeys.includes(key) && value != null).map(([_, value]) => String(value));
|
|
44
|
+
const parts = [...priorityParts, ...extraParts];
|
|
20
45
|
return parts.length ? parts.join(" / ") : void 0;
|
|
21
46
|
}
|
|
22
47
|
function colorizeJsonSections(json, colors) {
|
|
@@ -44,27 +69,144 @@ function countBrackets(line) {
|
|
|
44
69
|
return openCount - closeCount;
|
|
45
70
|
}
|
|
46
71
|
|
|
47
|
-
// src/
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
// src/formatting.ts
|
|
73
|
+
function formatStory(story, options = {}) {
|
|
74
|
+
const {
|
|
75
|
+
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
76
|
+
locale = "en-US",
|
|
77
|
+
detail = "normal",
|
|
78
|
+
noteLimit = 50,
|
|
79
|
+
showData = true,
|
|
80
|
+
colors = true
|
|
81
|
+
} = options;
|
|
82
|
+
const dateTimeFormatter = new Intl.DateTimeFormat(locale, {
|
|
83
|
+
timeZone: timezone,
|
|
84
|
+
year: "numeric",
|
|
85
|
+
month: "short",
|
|
86
|
+
day: "2-digit",
|
|
87
|
+
hour: "numeric",
|
|
88
|
+
minute: "2-digit",
|
|
89
|
+
second: "2-digit"
|
|
90
|
+
});
|
|
91
|
+
const timeFormatter = new Intl.DateTimeFormat(locale, {
|
|
92
|
+
timeZone: timezone,
|
|
93
|
+
hour: "numeric",
|
|
94
|
+
minute: "2-digit",
|
|
95
|
+
second: "2-digit"
|
|
96
|
+
});
|
|
97
|
+
const orderedNotes = [...story.notes].sort(
|
|
98
|
+
(noteA, noteB) => Date.parse(noteA.timestamp) - Date.parse(noteB.timestamp)
|
|
99
|
+
);
|
|
100
|
+
const durationMs = story.durationMs ?? calculateNoteDuration(orderedNotes);
|
|
101
|
+
const duration = durationMs != null ? formatDuration(durationMs) : void 0;
|
|
102
|
+
const slicedNotes = orderedNotes.slice(0, noteLimit);
|
|
103
|
+
const reportNotes = slicedNotes.map((note) => ({
|
|
104
|
+
timestamp: note.timestamp,
|
|
105
|
+
when: timeFormatter.format(new Date(note.timestamp)),
|
|
106
|
+
note: note.note,
|
|
107
|
+
text: formatNoteText(note, detail),
|
|
108
|
+
...note.who ? { who: note.who } : {},
|
|
109
|
+
...note.what ? { what: note.what } : {},
|
|
110
|
+
...note.where ? { where: note.where } : {},
|
|
111
|
+
...note.error ? { error: note.error } : {}
|
|
112
|
+
}));
|
|
113
|
+
const data = {
|
|
114
|
+
title: story.title,
|
|
115
|
+
level: story.level,
|
|
116
|
+
when: dateTimeFormatter.format(new Date(story.timestamp)),
|
|
117
|
+
...durationMs != null ? { durationMs } : {},
|
|
118
|
+
...duration ? { duration } : {},
|
|
119
|
+
...story.origin ? { origin: story.origin } : {},
|
|
120
|
+
notes: reportNotes,
|
|
121
|
+
...story.error ? { error: story.error } : {}
|
|
67
122
|
};
|
|
123
|
+
const lines = buildReportText(story, data, reportNotes, orderedNotes, {
|
|
124
|
+
colors,
|
|
125
|
+
detail,
|
|
126
|
+
showData,
|
|
127
|
+
duration
|
|
128
|
+
});
|
|
129
|
+
return { text: lines.join("\n"), data };
|
|
130
|
+
}
|
|
131
|
+
function buildReportText(story, data, reportNotes, orderedNotes, options) {
|
|
132
|
+
const levelColor = getLevelColor(story.level);
|
|
133
|
+
const label = (text) => options.colors ? `${levelColor}${text}${ANSI.reset}` : text;
|
|
134
|
+
const originLabel = formatOrigin(story.origin);
|
|
135
|
+
const lines = [];
|
|
136
|
+
lines.push(`${label("Story")}: ${story.title}`);
|
|
137
|
+
lines.push(`${label("Level")}: ${story.level}`);
|
|
138
|
+
lines.push(`${label("Time")}: ${data.when}${options.duration ? ` (${options.duration})` : ""}`);
|
|
139
|
+
if (originLabel) {
|
|
140
|
+
lines.push(`${label("Origin")}: ${originLabel}`);
|
|
141
|
+
}
|
|
142
|
+
if (story.error) {
|
|
143
|
+
const errorLine = [story.error.name, story.error.message].filter(Boolean).join(": ");
|
|
144
|
+
if (errorLine) lines.push(`${label("Error")}: ${errorLine}`);
|
|
145
|
+
}
|
|
146
|
+
if (options.detail !== "brief" && reportNotes.length) {
|
|
147
|
+
lines.push(`${label("Notes")}:`);
|
|
148
|
+
for (const note of reportNotes) {
|
|
149
|
+
lines.push(` ${note.when} \u2014 ${note.text}`);
|
|
150
|
+
}
|
|
151
|
+
if (orderedNotes.length > reportNotes.length) {
|
|
152
|
+
lines.push(` \u2026 (${orderedNotes.length - reportNotes.length} more)`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (options.showData) {
|
|
156
|
+
lines.push(`${label("Data")}:`);
|
|
157
|
+
const json = JSON.stringify(data, null, 2);
|
|
158
|
+
if (options.colors) {
|
|
159
|
+
const colored = colorizeJsonSections(json, {
|
|
160
|
+
base: ANSI.grayLight,
|
|
161
|
+
notes: ANSI.grayDark,
|
|
162
|
+
reset: ANSI.reset
|
|
163
|
+
});
|
|
164
|
+
lines.push(...colored);
|
|
165
|
+
} else {
|
|
166
|
+
lines.push(...json.split("\n"));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return lines;
|
|
170
|
+
}
|
|
171
|
+
function calculateNoteDuration(notes) {
|
|
172
|
+
if (notes.length <= 1) return void 0;
|
|
173
|
+
const startTime = Date.parse(notes[0].timestamp);
|
|
174
|
+
const endTime = Date.parse(notes[notes.length - 1].timestamp);
|
|
175
|
+
return Number.isFinite(startTime) && Number.isFinite(endTime) ? Math.max(0, endTime - startTime) : void 0;
|
|
176
|
+
}
|
|
177
|
+
function formatDuration(milliseconds) {
|
|
178
|
+
if (milliseconds < 1e3) return `${milliseconds}ms`;
|
|
179
|
+
const seconds = milliseconds / 1e3;
|
|
180
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
181
|
+
const minutes = Math.floor(seconds / 60);
|
|
182
|
+
const remainingSeconds = Math.round(seconds % 60).toString().padStart(2, "0");
|
|
183
|
+
return `${minutes}:${remainingSeconds}m`;
|
|
184
|
+
}
|
|
185
|
+
var summarizeStory = formatStory;
|
|
186
|
+
function formatNoteText(note, verbosity) {
|
|
187
|
+
if (verbosity !== "full") return note.note;
|
|
188
|
+
const details = [];
|
|
189
|
+
const what = note.what;
|
|
190
|
+
const where = note.where;
|
|
191
|
+
if (typeof what === "string") {
|
|
192
|
+
details.push(`what=${what}`);
|
|
193
|
+
} else if (what) {
|
|
194
|
+
for (const [key, value] of Object.entries(what)) {
|
|
195
|
+
if (value != null) details.push(`${key}=${String(value)}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (typeof where === "string") {
|
|
199
|
+
details.push(`where=${where}`);
|
|
200
|
+
} else if (where) {
|
|
201
|
+
for (const [key, value] of Object.entries(where)) {
|
|
202
|
+
if (value != null) details.push(`${key}=${String(value)}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (note.error) {
|
|
206
|
+
const errorLine = [note.error.name, note.error.message].filter(Boolean).join(": ");
|
|
207
|
+
if (errorLine) details.push(`error=${errorLine}`);
|
|
208
|
+
}
|
|
209
|
+
return details.length ? `${note.note} (${details.join(" ")})` : note.note;
|
|
68
210
|
}
|
|
69
211
|
|
|
70
212
|
// src/storyteller.ts
|
|
@@ -88,6 +230,14 @@ var AudienceRegistry = class {
|
|
|
88
230
|
getOnly(names) {
|
|
89
231
|
return names.map((name) => this.members.get(name)).filter(Boolean);
|
|
90
232
|
}
|
|
233
|
+
/** Check if an audience member is registered by name */
|
|
234
|
+
has(name) {
|
|
235
|
+
return this.members.has(name);
|
|
236
|
+
}
|
|
237
|
+
/** List the names of all registered audience members */
|
|
238
|
+
names() {
|
|
239
|
+
return [...this.members.keys()];
|
|
240
|
+
}
|
|
91
241
|
};
|
|
92
242
|
var Storyteller = class {
|
|
93
243
|
audience = new AudienceRegistry();
|
|
@@ -98,7 +248,17 @@ var Storyteller = class {
|
|
|
98
248
|
this.audience.add(consoleAudience());
|
|
99
249
|
options?.audiences?.forEach((audience) => this.audience.add(audience));
|
|
100
250
|
}
|
|
101
|
-
/**
|
|
251
|
+
/**
|
|
252
|
+
* Add a timestamped note to the current story.
|
|
253
|
+
* @param text - What happened
|
|
254
|
+
* @param data - Optional context: who did it, what was involved, where it happened, any error
|
|
255
|
+
* @returns `this` for chaining
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```ts
|
|
259
|
+
* story.note("Card charged", { what: { amount: "$42" }, where: "stripe" });
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
102
262
|
note(text, data = {}) {
|
|
103
263
|
this.notes.push({
|
|
104
264
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -115,13 +275,13 @@ var Storyteller = class {
|
|
|
115
275
|
this.notes = [];
|
|
116
276
|
return this;
|
|
117
277
|
}
|
|
118
|
-
/**
|
|
278
|
+
/** Preview the current notes as a formatted report without emitting or clearing them */
|
|
119
279
|
summarize(options = {}) {
|
|
120
280
|
const {
|
|
121
281
|
title = "Story preview",
|
|
122
|
-
level = "
|
|
282
|
+
level = "Information",
|
|
123
283
|
error,
|
|
124
|
-
...
|
|
284
|
+
...reportOptions
|
|
125
285
|
} = options;
|
|
126
286
|
const event = {
|
|
127
287
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -131,23 +291,23 @@ var Storyteller = class {
|
|
|
131
291
|
notes: [...this.notes],
|
|
132
292
|
...error ? { error: normalizeError(error) } : {}
|
|
133
293
|
};
|
|
134
|
-
return
|
|
294
|
+
return formatStory(event, reportOptions);
|
|
135
295
|
}
|
|
136
|
-
/**
|
|
296
|
+
/** Tell a success story — everything went well */
|
|
137
297
|
tell(title) {
|
|
138
298
|
return this.createDelivery("tell", title);
|
|
139
299
|
}
|
|
140
|
-
/**
|
|
300
|
+
/** Tell a cautionary story — something was off but it's handled */
|
|
141
301
|
warn(title) {
|
|
142
302
|
return this.createDelivery("warn", title);
|
|
143
303
|
}
|
|
144
|
-
/**
|
|
304
|
+
/** Tell an error story — something broke. Pass the error for automatic normalization. */
|
|
145
305
|
oops(title, error) {
|
|
146
306
|
return this.createDelivery("oops", title, error);
|
|
147
307
|
}
|
|
148
308
|
/** Build a story event and schedule delivery, returning a handle to override the audience list */
|
|
149
|
-
createDelivery(
|
|
150
|
-
const event = this.buildEvent(
|
|
309
|
+
createDelivery(method, title, error) {
|
|
310
|
+
const event = this.buildEvent(method, title, error);
|
|
151
311
|
let delivered = false;
|
|
152
312
|
let defaultCancelled = false;
|
|
153
313
|
queueMicrotask(() => {
|
|
@@ -165,21 +325,26 @@ var Storyteller = class {
|
|
|
165
325
|
};
|
|
166
326
|
}
|
|
167
327
|
/** Assemble the story event from current notes and clear notes for the next story */
|
|
168
|
-
buildEvent(
|
|
328
|
+
buildEvent(method, title, error) {
|
|
169
329
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
170
|
-
const
|
|
330
|
+
const level = methodToLevel(method);
|
|
331
|
+
const sortedNotes = [...this.notes].sort(
|
|
332
|
+
(noteA, noteB) => Date.parse(noteA.timestamp) - Date.parse(noteB.timestamp)
|
|
333
|
+
);
|
|
171
334
|
this.notes = [];
|
|
335
|
+
const durationMs = calculateNoteDuration2(sortedNotes).durationMs;
|
|
172
336
|
const event = {
|
|
173
337
|
timestamp: now,
|
|
174
338
|
level,
|
|
175
339
|
title,
|
|
176
340
|
...this.origin ? { origin: this.origin } : {},
|
|
177
|
-
notes:
|
|
341
|
+
notes: sortedNotes,
|
|
342
|
+
...durationMs != null ? { durationMs } : {},
|
|
178
343
|
...error ? { error: normalizeError(error) } : {}
|
|
179
344
|
};
|
|
180
345
|
const eventWithSummary = event;
|
|
181
346
|
Object.defineProperty(eventWithSummary, "summarize", {
|
|
182
|
-
value: (options) =>
|
|
347
|
+
value: (options) => formatStory(event, options),
|
|
183
348
|
enumerable: false
|
|
184
349
|
});
|
|
185
350
|
return eventWithSummary;
|
|
@@ -192,6 +357,11 @@ var Storyteller = class {
|
|
|
192
357
|
);
|
|
193
358
|
}
|
|
194
359
|
};
|
|
360
|
+
function methodToLevel(method) {
|
|
361
|
+
if (method === "tell") return "Information";
|
|
362
|
+
if (method === "warn") return "Warning";
|
|
363
|
+
return "Error";
|
|
364
|
+
}
|
|
195
365
|
function normalizeError(rawError) {
|
|
196
366
|
if (rawError instanceof Error) {
|
|
197
367
|
const normalized = {
|
|
@@ -209,7 +379,7 @@ function normalizeError(rawError) {
|
|
|
209
379
|
}
|
|
210
380
|
return { message: String(rawError) };
|
|
211
381
|
}
|
|
212
|
-
function
|
|
382
|
+
function calculateNoteDuration2(notes) {
|
|
213
383
|
if (notes.length <= 1) {
|
|
214
384
|
return {
|
|
215
385
|
durationMs: void 0
|
|
@@ -221,125 +391,6 @@ function calculateNoteDuration(notes) {
|
|
|
221
391
|
durationMs: Number.isFinite(startTime) && Number.isFinite(endTime) ? Math.max(0, endTime - startTime) : void 0
|
|
222
392
|
};
|
|
223
393
|
}
|
|
224
|
-
function summarizeStory(story, options = {}) {
|
|
225
|
-
const {
|
|
226
|
-
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
227
|
-
locale = "en-US",
|
|
228
|
-
verbosity = "normal",
|
|
229
|
-
maxNotes = 50,
|
|
230
|
-
showData = true,
|
|
231
|
-
colorize = true
|
|
232
|
-
} = options;
|
|
233
|
-
const dateTimeFormatter = new Intl.DateTimeFormat(locale, {
|
|
234
|
-
timeZone: timezone,
|
|
235
|
-
year: "numeric",
|
|
236
|
-
month: "short",
|
|
237
|
-
day: "2-digit",
|
|
238
|
-
hour: "numeric",
|
|
239
|
-
minute: "2-digit",
|
|
240
|
-
second: "2-digit"
|
|
241
|
-
});
|
|
242
|
-
const timeFormatter = new Intl.DateTimeFormat(locale, {
|
|
243
|
-
timeZone: timezone,
|
|
244
|
-
hour: "numeric",
|
|
245
|
-
minute: "2-digit",
|
|
246
|
-
second: "2-digit"
|
|
247
|
-
});
|
|
248
|
-
const orderedNotes = [...story.notes].sort(
|
|
249
|
-
(noteA, noteB) => Date.parse(noteA.timestamp) - Date.parse(noteB.timestamp)
|
|
250
|
-
);
|
|
251
|
-
const noteTiming = calculateNoteDuration(orderedNotes);
|
|
252
|
-
const originLabel = formatOrigin(story.origin);
|
|
253
|
-
const duration = noteTiming.durationMs != null ? formatDuration(noteTiming.durationMs) : void 0;
|
|
254
|
-
const slicedNotes = orderedNotes.slice(0, maxNotes);
|
|
255
|
-
const summaryNotes = slicedNotes.map((note) => ({
|
|
256
|
-
timestamp: note.timestamp,
|
|
257
|
-
when: timeFormatter.format(new Date(note.timestamp)),
|
|
258
|
-
note: note.note,
|
|
259
|
-
text: formatNoteText(note, verbosity),
|
|
260
|
-
...note.who ? { who: note.who } : {},
|
|
261
|
-
...note.what ? { what: note.what } : {},
|
|
262
|
-
...note.where ? { where: note.where } : {},
|
|
263
|
-
...note.error ? { error: note.error } : {}
|
|
264
|
-
}));
|
|
265
|
-
const data = {
|
|
266
|
-
title: story.title,
|
|
267
|
-
level: story.level,
|
|
268
|
-
when: dateTimeFormatter.format(new Date(story.timestamp)),
|
|
269
|
-
...noteTiming.durationMs != null ? { durationMs: noteTiming.durationMs } : {},
|
|
270
|
-
...duration ? { duration } : {},
|
|
271
|
-
...story.origin ? { origin: story.origin } : {},
|
|
272
|
-
notes: summaryNotes,
|
|
273
|
-
...story.error ? { error: story.error } : {}
|
|
274
|
-
};
|
|
275
|
-
const levelColor = getLevelColor(story.level);
|
|
276
|
-
const label = (text) => colorize ? `${levelColor}${text}${ANSI.reset}` : text;
|
|
277
|
-
const lines = [];
|
|
278
|
-
lines.push(`${label("Story")}: ${story.title}`);
|
|
279
|
-
lines.push(`${label("Level")}: ${story.level}`);
|
|
280
|
-
lines.push(`${label("Time")}: ${data.when}${duration ? ` (${duration})` : ""}`);
|
|
281
|
-
if (originLabel) {
|
|
282
|
-
lines.push(`${label("Origin")}: ${originLabel}`);
|
|
283
|
-
}
|
|
284
|
-
if (story.error) {
|
|
285
|
-
const errorLine = [story.error.name, story.error.message].filter(Boolean).join(": ");
|
|
286
|
-
if (errorLine) lines.push(`${label("Error")}: ${errorLine}`);
|
|
287
|
-
}
|
|
288
|
-
if (verbosity !== "brief" && summaryNotes.length) {
|
|
289
|
-
lines.push(`${label("Notes")}:`);
|
|
290
|
-
for (const note of summaryNotes) {
|
|
291
|
-
lines.push(` ${note.when} \u2014 ${note.text}`);
|
|
292
|
-
}
|
|
293
|
-
if (orderedNotes.length > summaryNotes.length) {
|
|
294
|
-
lines.push(` \u2026 (${orderedNotes.length - summaryNotes.length} more)`);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if (showData) {
|
|
298
|
-
lines.push(`${label("Data")}:`);
|
|
299
|
-
const json = JSON.stringify(data, null, 2);
|
|
300
|
-
if (colorize) {
|
|
301
|
-
const colored = colorizeJsonSections(json, {
|
|
302
|
-
base: ANSI.grayLight,
|
|
303
|
-
notes: ANSI.grayDark,
|
|
304
|
-
reset: ANSI.reset
|
|
305
|
-
});
|
|
306
|
-
lines.push(...colored);
|
|
307
|
-
} else {
|
|
308
|
-
lines.push(...json.split("\n"));
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return { text: lines.join("\n"), data };
|
|
312
|
-
}
|
|
313
|
-
function formatDuration(milliseconds) {
|
|
314
|
-
if (milliseconds < 1e3) return `${milliseconds}ms`;
|
|
315
|
-
const seconds = milliseconds / 1e3;
|
|
316
|
-
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
317
|
-
const minutes = Math.floor(seconds / 60);
|
|
318
|
-
const remainingSeconds = Math.round(seconds % 60).toString().padStart(2, "0");
|
|
319
|
-
return `${minutes}:${remainingSeconds}m`;
|
|
320
|
-
}
|
|
321
|
-
function formatNoteText(note, verbosity) {
|
|
322
|
-
if (verbosity !== "full") return note.note;
|
|
323
|
-
const details = [];
|
|
324
|
-
const what = note.what;
|
|
325
|
-
const where = note.where;
|
|
326
|
-
if (typeof what === "string") {
|
|
327
|
-
details.push(`what=${what}`);
|
|
328
|
-
} else if (what) {
|
|
329
|
-
if (what.field) details.push(`field=${String(what.field)}`);
|
|
330
|
-
if (what.status) details.push(`status=${String(what.status)}`);
|
|
331
|
-
}
|
|
332
|
-
if (typeof where === "string") {
|
|
333
|
-
details.push(`where=${where}`);
|
|
334
|
-
} else if (where) {
|
|
335
|
-
if (where.component) details.push(`component=${String(where.component)}`);
|
|
336
|
-
}
|
|
337
|
-
if (note.error) {
|
|
338
|
-
const errorLine = [note.error.name, note.error.message].filter(Boolean).join(": ");
|
|
339
|
-
if (errorLine) details.push(`error=${errorLine}`);
|
|
340
|
-
}
|
|
341
|
-
return details.length ? `${note.note} (${details.join(" ")})` : note.note;
|
|
342
|
-
}
|
|
343
394
|
|
|
344
395
|
// src/useStoryteller.ts
|
|
345
396
|
var sharedInstance;
|
|
@@ -355,7 +406,7 @@ function useStoryteller(options = {}) {
|
|
|
355
406
|
function dbAudience(insert) {
|
|
356
407
|
return {
|
|
357
408
|
name: "db",
|
|
358
|
-
accepts: (event) => event.level === "
|
|
409
|
+
accepts: (event) => event.level === "Warning" || event.level === "Error",
|
|
359
410
|
hear: async (event) => {
|
|
360
411
|
await insert(event);
|
|
361
412
|
}
|
|
@@ -367,10 +418,10 @@ function writeStoryReport(stories, options = {}) {
|
|
|
367
418
|
const {
|
|
368
419
|
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
369
420
|
locale = "en-US",
|
|
370
|
-
|
|
371
|
-
|
|
421
|
+
detail = "normal",
|
|
422
|
+
noteLimit = 50,
|
|
372
423
|
showData = true,
|
|
373
|
-
|
|
424
|
+
colors = true
|
|
374
425
|
} = options;
|
|
375
426
|
if (!stories.length) {
|
|
376
427
|
return "Storyteller Report\n\n(no stories)\n";
|
|
@@ -407,17 +458,17 @@ function writeStoryReport(stories, options = {}) {
|
|
|
407
458
|
for (const [day, dayStories] of storiesByDay) {
|
|
408
459
|
lines.push(day);
|
|
409
460
|
for (const story of dayStories) {
|
|
410
|
-
const
|
|
461
|
+
const report = formatStory(story, {
|
|
411
462
|
timezone,
|
|
412
463
|
locale,
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
464
|
+
detail,
|
|
465
|
+
noteLimit,
|
|
466
|
+
colors
|
|
416
467
|
});
|
|
417
|
-
const { data } =
|
|
468
|
+
const { data } = report;
|
|
418
469
|
const originLabel = formatOrigin(story.origin);
|
|
419
470
|
const levelColor = getLevelColor(story.level);
|
|
420
|
-
const label = (text) =>
|
|
471
|
+
const label = (text) => colors ? `${levelColor}${text}${ANSI.reset}` : text;
|
|
421
472
|
const duration = data.duration ? ` (${data.duration})` : "";
|
|
422
473
|
lines.push(`${label("Story")}: ${story.title}`);
|
|
423
474
|
lines.push(`${label("Level")}: ${story.level}`);
|
|
@@ -432,10 +483,10 @@ function writeStoryReport(stories, options = {}) {
|
|
|
432
483
|
].filter(Boolean).join(": ");
|
|
433
484
|
if (errorLine) lines.push(`${label("Error")}: ${errorLine}`);
|
|
434
485
|
}
|
|
435
|
-
if (
|
|
486
|
+
if (detail !== "brief" && data.notes.length) {
|
|
436
487
|
lines.push(` ${label("Notes")}:`);
|
|
437
|
-
for (const
|
|
438
|
-
lines.push(` ${
|
|
488
|
+
for (const reportNote of data.notes) {
|
|
489
|
+
lines.push(` ${reportNote.when} \u2014 ${reportNote.text}`);
|
|
439
490
|
}
|
|
440
491
|
if (story.notes.length > data.notes.length) {
|
|
441
492
|
lines.push(
|
|
@@ -446,7 +497,7 @@ function writeStoryReport(stories, options = {}) {
|
|
|
446
497
|
if (showData) {
|
|
447
498
|
lines.push(`${label("Data")}:`);
|
|
448
499
|
const json = JSON.stringify(data, null, 2);
|
|
449
|
-
if (
|
|
500
|
+
if (colors) {
|
|
450
501
|
const colored = colorizeJsonSections(json, {
|
|
451
502
|
base: ANSI.grayLight,
|
|
452
503
|
notes: ANSI.grayDark,
|
|
@@ -465,11 +516,11 @@ function writeStoryReport(stories, options = {}) {
|
|
|
465
516
|
export {
|
|
466
517
|
ANSI,
|
|
467
518
|
Storyteller,
|
|
468
|
-
colorizeJsonSections,
|
|
469
519
|
consoleAudience,
|
|
470
|
-
countBrackets,
|
|
471
520
|
dbAudience,
|
|
521
|
+
formatDuration,
|
|
472
522
|
formatOrigin,
|
|
523
|
+
formatStory,
|
|
473
524
|
getLevelColor,
|
|
474
525
|
summarizeStory,
|
|
475
526
|
useStoryteller,
|