Skip to content

Commit 948c4d4

Browse files
authored
feat: build watcher host (#96)
1 parent cc46d0f commit 948c4d4

File tree

4 files changed

+187
-0
lines changed

4 files changed

+187
-0
lines changed

build/webpack/ForgeWebpackPlugin.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import mainConfig from './webpack.main.config'
1515
import rendererConfig from './webpack.renderer.config'
1616
import nodeConfig from './webpack.node.config'
1717
import { extHostConfig, workerHostConfig } from './webpack.ext-host.config'
18+
import { watcherHostConfig } from './webpack.watcher-host.config'
1819
import webviewConfig from './webpack.webview.config'
1920

2021
const d = debug('electron-forge:plugin:webpack');
@@ -145,6 +146,16 @@ export class WebpackPlugin extends PluginBase<WebpackPluginConfig> {
145146
timer: { ...PRESET_TIMER },
146147
},
147148
},
149+
{
150+
title: 'Compiling wathcer host code',
151+
task: async () => {
152+
const tab = logger.createTab('Watcher Host')
153+
await this.compile(watcherHostConfig, 'watcher-host', 'watcher-host', false, tab)
154+
},
155+
rendererOptions: {
156+
timer: { ...PRESET_TIMER },
157+
},
158+
},
148159
{
149160
title: 'Compiling webview process code',
150161
task: async () => {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import path from 'node:path';
2+
import { createConfig, webpackDir } from './webpack.base.config';
3+
import { asarDeps } from '../deps';
4+
5+
const srcDir = path.resolve('src/bootstrap/watcher-host');
6+
const outDir = path.join(webpackDir, 'watcher-host');
7+
8+
export const watcherHostConfig = createConfig(() => ({
9+
entry: srcDir,
10+
output: {
11+
filename: 'index.js',
12+
path: outDir,
13+
},
14+
externals: [
15+
({ request }, callback) => {
16+
if (asarDeps.includes(request!)) {
17+
return callback(null, 'commonjs ' + request);
18+
}
19+
callback();
20+
},
21+
],
22+
target: 'node',
23+
}));

src/bootstrap/node/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
import '@/core/common/asar'
33
import * as net from 'node:net';
4+
import path from 'node:path';
45
import mri from 'mri'
56
import { IServerAppOpts, ServerApp, ConstructorOf, NodeModule } from '@opensumi/ide-core-node';
67
import { ServerCommonModule } from '@opensumi/ide-core-node';
@@ -49,6 +50,7 @@ async function startServer() {
4950
showBuiltinExtensions: true,
5051
extensionDir: process.env.IDE_EXTENSIONS_PATH!,
5152
},
53+
watcherHost: path.join(__dirname, '../watcher-host/index'),
5254
};
5355

5456
const server = net.createServer();

src/bootstrap/watcher-host/index.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import '@/core/common/asar';
2+
import { createConnection } from 'net';
3+
4+
import { Injector } from '@opensumi/di';
5+
import { SumiConnectionMultiplexer } from '@opensumi/ide-connection';
6+
import { NetSocketConnection } from '@opensumi/ide-connection/lib/common/connection/drivers';
7+
import { argv } from '@opensumi/ide-core-common/lib/node/cli';
8+
import { suppressNodeJSEpipeError } from '@opensumi/ide-core-common/lib/node/utils';
9+
import { CommonProcessReporter, IReporter, ReporterProcessMessage } from '@opensumi/ide-core-common/lib/types';
10+
import { Emitter, isPromiseCanceledError } from '@opensumi/ide-utils';
11+
12+
import { SUMI_WATCHER_PROCESS_SOCK_KEY, WATCHER_INIT_DATA_KEY } from '@opensumi/ide-file-service/lib/common';
13+
14+
import { WatcherProcessLogger } from '@opensumi/ide-file-service/lib/node/hosted/watch-process-log';
15+
import { WatcherHostServiceImpl } from '@opensumi/ide-file-service/lib/node/hosted/watcher.host.service';
16+
import { LogServiceManager as LogServiceManagerToken } from '@opensumi/ide-logs/lib/node/log-manager';
17+
import { LogServiceManager } from '@/logger/node/log-manager';
18+
19+
Error.stackTraceLimit = 100;
20+
const logger: any = console;
21+
22+
async function initWatcherProcess() {
23+
patchConsole();
24+
patchProcess();
25+
const watcherInjector = new Injector();
26+
const reporterEmitter = new Emitter<ReporterProcessMessage>();
27+
28+
watcherInjector.addProviders({
29+
token: IReporter,
30+
useValue: new CommonProcessReporter(reporterEmitter),
31+
}, {
32+
token: LogServiceManagerToken,
33+
useClass: LogServiceManager
34+
});
35+
36+
const initData = JSON.parse(argv[WATCHER_INIT_DATA_KEY]);
37+
const connection = JSON.parse(argv[SUMI_WATCHER_PROCESS_SOCK_KEY]);
38+
39+
const socket = createConnection(connection);
40+
41+
const watcherProtocol = new SumiConnectionMultiplexer(new NetSocketConnection(socket), {
42+
timeout: -1,
43+
});
44+
45+
const logger = new WatcherProcessLogger(watcherInjector, initData.logDir, initData.logLevel);
46+
const watcherHostService = new WatcherHostServiceImpl(watcherProtocol, logger);
47+
watcherHostService.initWatcherServer();
48+
}
49+
50+
(async () => {
51+
await initWatcherProcess();
52+
})();
53+
54+
function getErrorLogger() {
55+
// eslint-disable-next-line no-console
56+
return (logger && logger.error.bind(logger)) || console.error.bind(console);
57+
}
58+
59+
function getWarnLogger() {
60+
// eslint-disable-next-line no-console
61+
return (logger && logger.warn.bind(logger)) || console.warn.bind(console);
62+
}
63+
64+
function patchProcess() {
65+
process.exit = function (code?: number) {
66+
const err = new Error(`An extension called process.exit(${code ?? ''}) and this was prevented.`);
67+
getWarnLogger()(err.stack);
68+
} as (code?: number) => never;
69+
70+
// override Electron's process.crash() method
71+
process.crash = function () {
72+
const err = new Error('An extension called process.crash() and this was prevented.');
73+
getWarnLogger()(err.stack);
74+
};
75+
}
76+
77+
function _wrapConsoleMethod(method: 'log' | 'info' | 'warn' | 'error') {
78+
// eslint-disable-next-line no-console
79+
const original = console[method].bind(console);
80+
81+
Object.defineProperty(console, method, {
82+
set: () => {
83+
// empty
84+
},
85+
get: () =>
86+
function (...args: any[]) {
87+
original(...args);
88+
},
89+
});
90+
}
91+
92+
function patchConsole() {
93+
_wrapConsoleMethod('info');
94+
_wrapConsoleMethod('log');
95+
_wrapConsoleMethod('warn');
96+
_wrapConsoleMethod('error');
97+
}
98+
99+
function unexpectedErrorHandler(e: any) {
100+
setTimeout(() => {
101+
getErrorLogger()('[Watcehr-Host]', e.message, e.stack && '\n\n' + e.stack);
102+
}, 0);
103+
}
104+
105+
function onUnexpectedError(e: any) {
106+
let err = e;
107+
if (!err) {
108+
getWarnLogger()(`Unknown Exception ${err}`);
109+
return;
110+
}
111+
112+
if (isPromiseCanceledError(err)) {
113+
getWarnLogger()(`Canceled ${err.message}`);
114+
return;
115+
}
116+
117+
if (!(err instanceof Error)) {
118+
err = new Error(e);
119+
}
120+
121+
unexpectedErrorHandler(err);
122+
}
123+
124+
suppressNodeJSEpipeError(process, (msg) => {
125+
getErrorLogger()(msg);
126+
});
127+
128+
process.on('uncaughtException', (err) => {
129+
onUnexpectedError(err);
130+
});
131+
132+
const unhandledPromises: Promise<any>[] = [];
133+
process.on('unhandledRejection', (reason, promise) => {
134+
unhandledPromises.push(promise);
135+
setTimeout(() => {
136+
const idx = unhandledPromises.indexOf(promise);
137+
if (idx >= 0) {
138+
promise.catch((e) => {
139+
unhandledPromises.splice(idx, 1);
140+
onUnexpectedError(e);
141+
});
142+
}
143+
}, 1000);
144+
});
145+
146+
process.on('rejectionHandled', (promise: Promise<any>) => {
147+
const idx = unhandledPromises.indexOf(promise);
148+
if (idx >= 0) {
149+
unhandledPromises.splice(idx, 1);
150+
}
151+
});

0 commit comments

Comments
 (0)