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' ;
2
3
import {
3
4
app ,
4
5
BrowserWindow ,
5
6
contentTracing ,
6
- ipcMain ,
7
7
Event as ElectronEvent ,
8
+ ipcMain ,
8
9
} 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' ;
14
17
import { 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' ;
17
18
import {
18
19
ElectronMainApplication as TheiaElectronMainApplication ,
19
20
ElectronMainExecutionParams ,
20
21
} 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' ;
28
23
import { 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' ;
33
31
import { Sketch } from '../../common/protocol' ;
34
32
import {
35
33
AppInfo ,
@@ -39,9 +37,71 @@ import {
39
37
CHANNEL_SHOW_PLOTTER_WINDOW ,
40
38
isShowPlotterWindowParams ,
41
39
} 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' ;
42
43
43
44
app . commandLine . appendSwitch ( 'disable-http-cache' ) ;
44
45
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
+
45
105
interface WorkspaceOptions {
46
106
file : string ;
47
107
x : number ;
@@ -185,7 +245,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
185
245
186
246
private attachFileAssociations ( cwd : string ) : void {
187
247
// OSX: register open-file event
188
- if ( os . isOSX ) {
248
+ if ( isOSX ) {
189
249
app . on ( 'open-file' , async ( event , path ) => {
190
250
event . preventDefault ( ) ;
191
251
const resolvedPath = await this . resolvePath ( path , cwd ) ;
@@ -495,9 +555,14 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
495
555
) ;
496
556
console . log ( `Starting backend process. PID: ${ backendProcess . pid } ` ) ;
497
557
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
+ }
501
566
} ) ;
502
567
backendProcess . on ( 'error' , ( error ) => {
503
568
reject ( error ) ;
@@ -703,7 +768,7 @@ class InterruptWorkspaceRestoreError extends Error {
703
768
async function updateFrontendApplicationConfigFromPackageJson (
704
769
config : FrontendApplicationConfig
705
770
) : Promise < FrontendApplicationConfig > {
706
- if ( environment . electron . isDevMode ( ) ) {
771
+ if ( ! isProductionMode ) {
707
772
console . debug (
708
773
'Skipping frontend application configuration customizations. Running in dev mode.'
709
774
) ;
@@ -777,3 +842,9 @@ function updateAppInfo(
777
842
} ) ;
778
843
return toUpdate ;
779
844
}
845
+
846
+ function isAddressInfo ( arg : unknown ) : arg is Pick < AddressInfo , 'port' > {
847
+ // Cannot do the type-guard on all properties, but the port is sufficient as the address is always `localhost`.
848
+ // For example, the `family` might be absent if the address is IPv6.
849
+ return isObject < AddressInfo > ( arg ) && typeof arg . port === 'number' ;
850
+ }
0 commit comments