1- import { inject , injectable } from '@theia/core/shared/inversify' ;
1+ import type { FrontendApplicationConfig } from '@theia/application-package/lib/application-props' ;
2+ import { environment } from '@theia/application-package/lib/environment' ;
23import {
34 app ,
45 BrowserWindow ,
56 contentTracing ,
6- ipcMain ,
77 Event as ElectronEvent ,
8+ ipcMain ,
89} from '@theia/core/electron-shared/electron' ;
9- import { fork } from 'node:child_process' ;
10- import { AddressInfo } from 'node:net' ;
11- import { join , isAbsolute , resolve } from 'node:path' ;
12- import { promises as fs , rm , rmSync } from 'node:fs' ;
13- import type { MaybePromise , Mutable } from '@theia/core/lib/common/types' ;
10+ import {
11+ Disposable ,
12+ DisposableCollection ,
13+ } from '@theia/core/lib/common/disposable' ;
14+ import { isOSX } from '@theia/core/lib/common/os' ;
15+ import { Deferred } from '@theia/core/lib/common/promise-util' ;
16+ import { isObject , MaybePromise , Mutable } from '@theia/core/lib/common/types' ;
1417import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token' ;
15- import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props' ;
16- import { environment } from '@theia/application-package/lib/environment' ;
1718import {
1819 ElectronMainApplication as TheiaElectronMainApplication ,
1920 ElectronMainExecutionParams ,
2021} from '@theia/core/lib/electron-main/electron-main-application' ;
21- import { URI } from '@theia/core/shared/vscode-uri' ;
22- import { Deferred } from '@theia/core/lib/common/promise-util' ;
23- import * as os from '@theia/core/lib/common/os' ;
24- import { TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/theia-electron-window' ;
25- import { IsTempSketch } from '../../node/is-temp-sketch' ;
26- import { ErrnoException } from '../../node/utils/errors' ;
27- import { isAccessibleSketchPath } from '../../node/sketches-service-impl' ;
22+ import type { TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/theia-electron-window' ;
2823import { FileUri } from '@theia/core/lib/node/file-uri' ;
29- import {
30- Disposable ,
31- DisposableCollection ,
32- } from '@theia/core/lib/common/disposable' ;
24+ import { inject , injectable } from '@theia/core/shared/inversify' ;
25+ import { URI } from '@theia/core/shared/vscode-uri' ;
26+ import { log as logToFile , setup as setupFileLog } from 'node-log-rotate' ;
27+ import { fork } from 'node:child_process' ;
28+ import { promises as fs , rm , rmSync } from 'node:fs' ;
29+ import type { AddressInfo } from 'node:net' ;
30+ import { isAbsolute , join , resolve } from 'node:path' ;
3331import { Sketch } from '../../common/protocol' ;
3432import {
3533 AppInfo ,
@@ -39,9 +37,71 @@ import {
3937 CHANNEL_SHOW_PLOTTER_WINDOW ,
4038 isShowPlotterWindowParams ,
4139} from '../../electron-common/electron-arduino' ;
40+ import { IsTempSketch } from '../../node/is-temp-sketch' ;
41+ import { isAccessibleSketchPath } from '../../node/sketches-service-impl' ;
42+ import { ErrnoException } from '../../node/utils/errors' ;
4243
4344app . commandLine . appendSwitch ( 'disable-http-cache' ) ;
4445
46+ const consoleLogFunctionNames = [
47+ 'log' ,
48+ 'trace' ,
49+ 'debug' ,
50+ 'info' ,
51+ 'warn' ,
52+ 'error' ,
53+ ] as const ;
54+ type ConsoleLogSeverity = ( typeof consoleLogFunctionNames ) [ number ] ;
55+ interface ConsoleLogParams {
56+ readonly severity : ConsoleLogSeverity ;
57+ readonly message : string ;
58+ }
59+ function isConsoleLogParams ( arg : unknown ) : arg is ConsoleLogParams {
60+ return (
61+ isObject < ConsoleLogParams > ( arg ) &&
62+ typeof arg . message === 'string' &&
63+ typeof arg . severity === 'string' &&
64+ consoleLogFunctionNames . includes ( arg . severity as ConsoleLogSeverity )
65+ ) ;
66+ }
67+
68+ // Patch for on Linux when `XDG_CONFIG_HOME` is not available, `node-log-rotate` creates the folder with `undefined` name.
69+ // See https://github.com/lemon-sour/node-log-rotate/issues/23 and https://github.com/arduino/arduino-ide/issues/394.
70+ // If the IDE2 is running on Linux, and the `XDG_CONFIG_HOME` variable is not available, set it to avoid the `undefined` folder.
71+ // From the specs: https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
72+ // "If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used."
73+ function enableFileLogger ( ) {
74+ const os = require ( 'os' ) ;
75+ const util = require ( 'util' ) ;
76+ if ( os . platform ( ) === 'linux' && ! process . env [ 'XDG_CONFIG_HOME' ] ) {
77+ const { join } = require ( 'path' ) ;
78+ const home = process . env [ 'HOME' ] ;
79+ const xdgConfigHome = home
80+ ? join ( home , '.config' )
81+ : join ( os . homedir ( ) , '.config' ) ;
82+ process . env [ 'XDG_CONFIG_HOME' ] = xdgConfigHome ;
83+ }
84+ setupFileLog ( {
85+ appName : 'Arduino IDE' ,
86+ maxSize : 10 * 1024 * 1024 ,
87+ } ) ;
88+ for ( const name of consoleLogFunctionNames ) {
89+ const original = console [ name ] ;
90+ console [ name ] = function ( ) {
91+ // eslint-disable-next-line prefer-rest-params
92+ const messages = Object . values ( arguments ) ;
93+ const message = util . format ( ...messages ) ;
94+ original ( message ) ;
95+ logToFile ( message ) ;
96+ } ;
97+ }
98+ }
99+
100+ const isProductionMode = ! environment . electron . isDevMode ( ) ;
101+ if ( isProductionMode ) {
102+ enableFileLogger ( ) ;
103+ }
104+
45105interface WorkspaceOptions {
46106 file : string ;
47107 x : number ;
@@ -185,7 +245,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
185245
186246 private attachFileAssociations ( cwd : string ) : void {
187247 // OSX: register open-file event
188- if ( os . isOSX ) {
248+ if ( isOSX ) {
189249 app . on ( 'open-file' , async ( event , path ) => {
190250 event . preventDefault ( ) ;
191251 const resolvedPath = await this . resolvePath ( path , cwd ) ;
@@ -495,9 +555,14 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
495555 ) ;
496556 console . log ( `Starting backend process. PID: ${ backendProcess . pid } ` ) ;
497557 return new Promise ( ( resolve , reject ) => {
498- // The backend server main file is also supposed to send the resolved http(s) server port via IPC.
499- backendProcess . on ( 'message' , ( address : AddressInfo ) => {
500- resolve ( address . port ) ;
558+ // The forked backend process sends the resolved http(s) server port via IPC, and forwards the log messages.
559+ backendProcess . on ( 'message' , ( arg : unknown ) => {
560+ if ( isConsoleLogParams ( arg ) ) {
561+ const { message, severity } = arg ;
562+ console [ severity ] ( message ) ;
563+ } else if ( isAddressInfo ( arg ) ) {
564+ resolve ( arg . port ) ;
565+ }
501566 } ) ;
502567 backendProcess . on ( 'error' , ( error ) => {
503568 reject ( error ) ;
@@ -703,7 +768,7 @@ class InterruptWorkspaceRestoreError extends Error {
703768async function updateFrontendApplicationConfigFromPackageJson (
704769 config : FrontendApplicationConfig
705770) : Promise < FrontendApplicationConfig > {
706- if ( environment . electron . isDevMode ( ) ) {
771+ if ( ! isProductionMode ) {
707772 console . debug (
708773 'Skipping frontend application configuration customizations. Running in dev mode.'
709774 ) ;
@@ -777,3 +842,7 @@ function updateAppInfo(
777842 } ) ;
778843 return toUpdate ;
779844}
845+
846+ function isAddressInfo ( arg : unknown ) : arg is Pick < AddressInfo , 'port' > {
847+ return isObject < AddressInfo > ( arg ) && typeof arg . port === 'number' ; // `family` might be
848+ }
0 commit comments