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