Open History to see the last commit
1export interface UpdatesLogEntryLike {
2 readonly timestamp: number;
3 readonly message: string;
4 readonly code: string;
5 readonly level: string;
6}
7
8export interface StartupCrashRecord {
9 readonly timestamp: number;
10 readonly description: string;
11 readonly frames: ReadonlyArray<string>;
12 readonly detail: string;
13}
14
15const FATAL_PREFIX = "ErrorRecovery fatal exception: ";
16const DESCRIPTION_PREFIX = "Description: ";
17const UNHANDLED_PREFIX = "Unhandled JS Exception: ";
18
19export function parseStartupCrashRecords(
20 entries: ReadonlyArray<UpdatesLogEntryLike>,
21): ReadonlyArray<StartupCrashRecord> {
22 const records: StartupCrashRecord[] = [];
23 for (const entry of entries) {
24 if (entry.code !== "JSRuntimeError" || !entry.message.startsWith(FATAL_PREFIX)) continue;
25 const body = entry.message.slice(FATAL_PREFIX.length);
26 const lines = body.split("\n");
27 const descriptionLine = lines.find((line) => line.startsWith(DESCRIPTION_PREFIX));
28 if (descriptionLine === undefined) continue;
29 let description = descriptionLine.slice(DESCRIPTION_PREFIX.length).trim();
30 if (description.startsWith(UNHANDLED_PREFIX)) {
31 description = description.slice(UNHANDLED_PREFIX.length);
32 }
33 const frames = lines
34 .map((line) => line.trim())
35 .filter((line) => line.startsWith("at "))
36 .map(compactFrame);
37 records.push({ timestamp: entry.timestamp, description, frames, detail: body.trim() });
38 }
39 return records.sort((left, right) => right.timestamp - left.timestamp);
40}
41
42function compactFrame(line: string): string {
43 const match = /^at (.+?) \((.*)\)$/.exec(line);
44 if (!match) return line.slice("at ".length);
45 const [, name = "", location = ""] = match;
46 const file = location.slice(location.lastIndexOf("/") + 1);
47 return location === "<anonymous>" ? name : `${name} (${file})`;
48}
49
50export function formatStartupCrashReport(
51 records: ReadonlyArray<StartupCrashRecord>,
52 app: { readonly version: string; readonly build: string },
53): string {
54 const header = `T3 Code ${app.version} (${app.build})`;
55 if (records.length === 0) return `${header}\nNo startup crashes recorded.`;
56 return [
57 header,
58 ...records.map(
59 (record) => `\n--- ${new Date(record.timestamp).toISOString()} ---\n${record.detail}`,
60 ),
61 ].join("\n");
62}