From e8c744898f514e704bbf0221b223d5454f0a5ed6 Mon Sep 17 00:00:00 2001 From: "xinglong.wangwxl" Date: Wed, 5 Feb 2025 15:18:16 +0800 Subject: [PATCH] feat: build watcher host --- build/webpack/ForgeWebpackPlugin.ts | 11 ++ build/webpack/webpack.watcher-host.config.ts | 23 +++ src/bootstrap/node/index.ts | 2 + src/bootstrap/watcher-host/index.ts | 151 +++++++++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 build/webpack/webpack.watcher-host.config.ts create mode 100644 src/bootstrap/watcher-host/index.ts diff --git a/build/webpack/ForgeWebpackPlugin.ts b/build/webpack/ForgeWebpackPlugin.ts index 81f8e4e..b1704bf 100644 --- a/build/webpack/ForgeWebpackPlugin.ts +++ b/build/webpack/ForgeWebpackPlugin.ts @@ -15,6 +15,7 @@ import mainConfig from './webpack.main.config' import rendererConfig from './webpack.renderer.config' import nodeConfig from './webpack.node.config' import { extHostConfig, workerHostConfig } from './webpack.ext-host.config' +import { watcherHostConfig } from './webpack.watcher-host.config' import webviewConfig from './webpack.webview.config' const d = debug('electron-forge:plugin:webpack'); @@ -145,6 +146,16 @@ export class WebpackPlugin extends PluginBase { timer: { ...PRESET_TIMER }, }, }, + { + title: 'Compiling wathcer host code', + task: async () => { + const tab = logger.createTab('Watcher Host') + await this.compile(watcherHostConfig, 'watcher-host', 'watcher-host', false, tab) + }, + rendererOptions: { + timer: { ...PRESET_TIMER }, + }, + }, { title: 'Compiling webview process code', task: async () => { diff --git a/build/webpack/webpack.watcher-host.config.ts b/build/webpack/webpack.watcher-host.config.ts new file mode 100644 index 0000000..e478398 --- /dev/null +++ b/build/webpack/webpack.watcher-host.config.ts @@ -0,0 +1,23 @@ +import path from 'node:path'; +import { createConfig, webpackDir } from './webpack.base.config'; +import { asarDeps } from '../deps'; + +const srcDir = path.resolve('src/bootstrap/watcher-host'); +const outDir = path.join(webpackDir, 'watcher-host'); + +export const watcherHostConfig = createConfig(() => ({ + entry: srcDir, + output: { + filename: 'index.js', + path: outDir, + }, + externals: [ + ({ request }, callback) => { + if (asarDeps.includes(request!)) { + return callback(null, 'commonjs ' + request); + } + callback(); + }, + ], + target: 'node', +})); \ No newline at end of file diff --git a/src/bootstrap/node/index.ts b/src/bootstrap/node/index.ts index 48f5043..7262690 100644 --- a/src/bootstrap/node/index.ts +++ b/src/bootstrap/node/index.ts @@ -1,6 +1,7 @@ import '@/core/common/asar' import * as net from 'node:net'; +import path from 'node:path'; import mri from 'mri' import { IServerAppOpts, ServerApp, ConstructorOf, NodeModule } from '@opensumi/ide-core-node'; import { ServerCommonModule } from '@opensumi/ide-core-node'; @@ -49,6 +50,7 @@ async function startServer() { showBuiltinExtensions: true, extensionDir: process.env.IDE_EXTENSIONS_PATH!, }, + watcherHost: path.join(__dirname, '../watcher-host/index'), }; const server = net.createServer(); diff --git a/src/bootstrap/watcher-host/index.ts b/src/bootstrap/watcher-host/index.ts new file mode 100644 index 0000000..26a1c90 --- /dev/null +++ b/src/bootstrap/watcher-host/index.ts @@ -0,0 +1,151 @@ +import '@/core/common/asar'; +import { createConnection } from 'net'; + +import { Injector } from '@opensumi/di'; +import { SumiConnectionMultiplexer } from '@opensumi/ide-connection'; +import { NetSocketConnection } from '@opensumi/ide-connection/lib/common/connection/drivers'; +import { argv } from '@opensumi/ide-core-common/lib/node/cli'; +import { suppressNodeJSEpipeError } from '@opensumi/ide-core-common/lib/node/utils'; +import { CommonProcessReporter, IReporter, ReporterProcessMessage } from '@opensumi/ide-core-common/lib/types'; +import { Emitter, isPromiseCanceledError } from '@opensumi/ide-utils'; + +import { SUMI_WATCHER_PROCESS_SOCK_KEY, WATCHER_INIT_DATA_KEY } from '@opensumi/ide-file-service/lib/common'; + +import { WatcherProcessLogger } from '@opensumi/ide-file-service/lib/node/hosted/watch-process-log'; +import { WatcherHostServiceImpl } from '@opensumi/ide-file-service/lib/node/hosted/watcher.host.service'; +import { LogServiceManager as LogServiceManagerToken } from '@opensumi/ide-logs/lib/node/log-manager'; +import { LogServiceManager } from '@/logger/node/log-manager'; + +Error.stackTraceLimit = 100; +const logger: any = console; + +async function initWatcherProcess() { + patchConsole(); + patchProcess(); + const watcherInjector = new Injector(); + const reporterEmitter = new Emitter(); + + watcherInjector.addProviders({ + token: IReporter, + useValue: new CommonProcessReporter(reporterEmitter), + }, { + token: LogServiceManagerToken, + useClass: LogServiceManager + }); + + const initData = JSON.parse(argv[WATCHER_INIT_DATA_KEY]); + const connection = JSON.parse(argv[SUMI_WATCHER_PROCESS_SOCK_KEY]); + + const socket = createConnection(connection); + + const watcherProtocol = new SumiConnectionMultiplexer(new NetSocketConnection(socket), { + timeout: -1, + }); + + const logger = new WatcherProcessLogger(watcherInjector, initData.logDir, initData.logLevel); + const watcherHostService = new WatcherHostServiceImpl(watcherProtocol, logger); + watcherHostService.initWatcherServer(); +} + +(async () => { + await initWatcherProcess(); +})(); + +function getErrorLogger() { + // eslint-disable-next-line no-console + return (logger && logger.error.bind(logger)) || console.error.bind(console); +} + +function getWarnLogger() { + // eslint-disable-next-line no-console + return (logger && logger.warn.bind(logger)) || console.warn.bind(console); +} + +function patchProcess() { + process.exit = function (code?: number) { + const err = new Error(`An extension called process.exit(${code ?? ''}) and this was prevented.`); + getWarnLogger()(err.stack); + } as (code?: number) => never; + + // override Electron's process.crash() method + process.crash = function () { + const err = new Error('An extension called process.crash() and this was prevented.'); + getWarnLogger()(err.stack); + }; +} + +function _wrapConsoleMethod(method: 'log' | 'info' | 'warn' | 'error') { + // eslint-disable-next-line no-console + const original = console[method].bind(console); + + Object.defineProperty(console, method, { + set: () => { + // empty + }, + get: () => + function (...args: any[]) { + original(...args); + }, + }); +} + +function patchConsole() { + _wrapConsoleMethod('info'); + _wrapConsoleMethod('log'); + _wrapConsoleMethod('warn'); + _wrapConsoleMethod('error'); +} + +function unexpectedErrorHandler(e: any) { + setTimeout(() => { + getErrorLogger()('[Watcehr-Host]', e.message, e.stack && '\n\n' + e.stack); + }, 0); +} + +function onUnexpectedError(e: any) { + let err = e; + if (!err) { + getWarnLogger()(`Unknown Exception ${err}`); + return; + } + + if (isPromiseCanceledError(err)) { + getWarnLogger()(`Canceled ${err.message}`); + return; + } + + if (!(err instanceof Error)) { + err = new Error(e); + } + + unexpectedErrorHandler(err); +} + +suppressNodeJSEpipeError(process, (msg) => { + getErrorLogger()(msg); +}); + +process.on('uncaughtException', (err) => { + onUnexpectedError(err); +}); + +const unhandledPromises: Promise[] = []; +process.on('unhandledRejection', (reason, promise) => { + unhandledPromises.push(promise); + setTimeout(() => { + const idx = unhandledPromises.indexOf(promise); + if (idx >= 0) { + promise.catch((e) => { + unhandledPromises.splice(idx, 1); + onUnexpectedError(e); + }); + } + }, 1000); +}); + +process.on('rejectionHandled', (promise: Promise) => { + const idx = unhandledPromises.indexOf(promise); + if (idx >= 0) { + unhandledPromises.splice(idx, 1); + } +});