11declare const __BUILD_COMMIT__ : string | undefined ;
22declare const __BUILD_DATE__ : string | undefined ;
33
4+ import { access , readFile , writeFile } from "node:fs/promises" ;
45import os from "node:os" ;
6+ import path from "node:path" ;
57import {
68 app ,
79 clipboard ,
810 dialog ,
911 Menu ,
1012 type MenuItemConstructorOptions ,
1113} from "electron" ;
14+ import log from "electron-log/main" ;
1215import { container } from "./di/container.js" ;
1316import { MAIN_TOKENS } from "./di/tokens.js" ;
1417import type { AgentService } from "./services/agent/service.js" ;
1518import type { UIService } from "./services/ui/service.js" ;
1619import type { UpdatesService } from "./services/updates/service.js" ;
1720
21+ function getSystemInfo ( ) : string {
22+ const commit = __BUILD_COMMIT__ ?? "dev" ;
23+ const buildDate = __BUILD_DATE__ ?? "dev" ;
24+ return [
25+ `Version: ${ app . getVersion ( ) } ` ,
26+ `Commit: ${ commit } ` ,
27+ `Date: ${ buildDate } ` ,
28+ `Electron: ${ process . versions . electron } ` ,
29+ `Chromium: ${ process . versions . chrome } ` ,
30+ `Node.js: ${ process . versions . node } ` ,
31+ `V8: ${ process . versions . v8 } ` ,
32+ `OS: ${ process . platform } ${ process . arch } ${ os . release ( ) } ` ,
33+ ] . join ( "\n" ) ;
34+ }
35+
1836export function buildApplicationMenu ( ) : void {
1937 const template : MenuItemConstructorOptions [ ] = [
2038 buildAppMenu ( ) ,
@@ -34,18 +52,7 @@ function buildAppMenu(): MenuItemConstructorOptions {
3452 {
3553 label : "About Twig" ,
3654 click : ( ) => {
37- const commit = __BUILD_COMMIT__ ?? "dev" ;
38- const buildDate = __BUILD_DATE__ ?? "dev" ;
39- const info = [
40- `Version: ${ app . getVersion ( ) } ` ,
41- `Commit: ${ commit } ` ,
42- `Date: ${ buildDate } ` ,
43- `Electron: ${ process . versions . electron } ` ,
44- `Chromium: ${ process . versions . chrome } ` ,
45- `Node.js: ${ process . versions . node } ` ,
46- `V8: ${ process . versions . v8 } ` ,
47- `OS: ${ process . platform } ${ process . arch } ${ os . release ( ) } ` ,
48- ] . join ( "\n" ) ;
55+ const info = getSystemInfo ( ) ;
4956
5057 dialog
5158 . showMessageBox ( {
@@ -109,6 +116,52 @@ function buildFileMenu(): MenuItemConstructorOptions {
109116 {
110117 label : "Developer" ,
111118 submenu : [
119+ {
120+ label : "Export application logs" ,
121+ click : async ( ) => {
122+ const logPath = log . transports . file . getFile ( ) . path ;
123+
124+ try {
125+ await access ( logPath ) ;
126+ } catch {
127+ dialog . showMessageBox ( {
128+ type : "warning" ,
129+ title : "No Logs Found" ,
130+ message : "No log file exists yet." ,
131+ detail : `Expected location: ${ logPath } ` ,
132+ } ) ;
133+ return ;
134+ }
135+
136+ const timestamp = new Date ( )
137+ . toISOString ( )
138+ . replace ( / [: .] / g, "-" )
139+ . slice ( 0 , 19 ) ;
140+ const defaultName = `twig-logs-${ timestamp } .log` ;
141+ const { filePath, canceled } = await dialog . showSaveDialog ( {
142+ title : "Export Logs" ,
143+ defaultPath : path . join ( app . getPath ( "desktop" ) , defaultName ) ,
144+ filters : [ { name : "Log Files" , extensions : [ "log" ] } ] ,
145+ } ) ;
146+ if ( canceled || ! filePath ) return ;
147+
148+ const logContent = await readFile ( logPath , "utf-8" ) ;
149+ const header = [
150+ "=" . repeat ( 60 ) ,
151+ " Twig Log Export" ,
152+ "=" . repeat ( 60 ) ,
153+ "" ,
154+ getSystemInfo ( ) ,
155+ "" ,
156+ `Exported: ${ new Date ( ) . toISOString ( ) } ` ,
157+ "" ,
158+ "=" . repeat ( 60 ) ,
159+ "" ,
160+ ] . join ( "\n" ) ;
161+
162+ await writeFile ( filePath , header + logContent , "utf-8" ) ;
163+ } ,
164+ } ,
112165 {
113166 label : "Clear application storage" ,
114167 click : ( ) => {
0 commit comments