deploy: current vibn theia state
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled

Made-with: Cursor
This commit is contained in:
2026-02-27 12:01:08 -08:00
commit 8bb5110148
3782 changed files with 640947 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: [
'../../configs/build.eslintrc.json'
],
parserOptions: {
tsconfigRootDir: __dirname,
project: 'tsconfig.json'
},
rules: {
'import/no-dynamic-require': 'off'
}
};

View File

@@ -0,0 +1,26 @@
<div align='center'>
<br />
<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />
<h2>ECLIPSE THEIA - APPLICATION-MANAGER</h2>
<hr />
</div>
## Additional Information
- [Theia - GitHub](https://github.com/eclipse-theia/theia)
- [Theia - Website](https://theia-ide.org/)
## License
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
## Trademark
"Theia" is a trademark of the Eclipse Foundation
<https://www.eclipse.org/theia>

View File

@@ -0,0 +1,81 @@
{
"name": "@theia/application-manager",
"version": "1.68.0",
"description": "Theia application manager API.",
"publishConfig": {
"access": "public"
},
"license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
"repository": {
"type": "git",
"url": "https://github.com/eclipse-theia/theia.git"
},
"bugs": {
"url": "https://github.com/eclipse-theia/theia/issues"
},
"homepage": "https://github.com/eclipse-theia/theia",
"files": [
"lib",
"src"
],
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"scripts": {
"build": "theiaext build",
"clean": "theiaext clean",
"compile": "theiaext compile",
"lint": "theiaext lint",
"test": "theiaext test",
"watch": "theiaext watch"
},
"dependencies": {
"@babel/core": "^7.10.0",
"@babel/plugin-transform-classes": "^7.10.0",
"@babel/plugin-transform-runtime": "^7.10.0",
"@babel/preset-env": "^7.10.0",
"@theia/application-package": "1.68.0",
"@theia/ffmpeg": "1.68.0",
"@theia/native-webpack-plugin": "1.68.0",
"@types/fs-extra": "^4.0.2",
"@types/semver": "^7.5.0",
"babel-loader": "^8.2.2",
"buffer": "^6.0.3",
"compression-webpack-plugin": "^9.0.0",
"copy-webpack-plugin": "^8.1.1",
"css-loader": "^6.2.0",
"@electron/rebuild": "^3.7.2",
"fs-extra": "^4.0.2",
"http-server": "^14.1.1",
"ignore-loader": "^0.1.2",
"less": "^3.0.3",
"mini-css-extract-plugin": "^2.6.1",
"node-loader": "^2.0.0",
"path-browserify": "^1.0.1",
"semver": "^7.5.4",
"source-map": "^0.6.1",
"source-map-loader": "^2.0.1",
"source-map-support": "^0.5.19",
"style-loader": "^2.0.0",
"tslib": "^2.6.2",
"umd-compat-loader": "^2.1.2",
"webpack": "^5.76.0",
"webpack-cli": "4.7.0",
"worker-loader": "^3.0.8",
"yargs": "^15.3.1"
},
"peerDependencies": {
"@theia/electron": "*"
},
"peerDependenciesMeta": {
"@theia/electron": {
"optional": true
}
},
"devDependencies": {
"@theia/ext-scripts": "1.68.0"
},
"nyc": {
"extends": "../../configs/nyc.json"
},
"gitHead": "21358137e41342742707f660b8e222f940a27652"
}

View File

@@ -0,0 +1,263 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import * as path from 'path';
import * as fs from 'fs-extra';
import * as cp from 'child_process';
import * as semver from 'semver';
import { ApplicationPackage, ApplicationPackageOptions } from '@theia/application-package';
import { WebpackGenerator, FrontendGenerator, BackendGenerator } from './generator';
import { ApplicationProcess } from './application-process';
import { GeneratorOptions } from './generator/abstract-generator';
import yargs = require('yargs');
// Declare missing exports from `@types/semver@7`
declare module 'semver' {
function minVersion(range: string): string;
}
class AbortError extends Error {
constructor(...args: Parameters<ErrorConstructor>) {
super(...args);
Object.setPrototypeOf(this, AbortError.prototype);
}
}
export class ApplicationPackageManager {
static defineGeneratorOptions<T>(cli: yargs.Argv<T>): yargs.Argv<T & {
mode: 'development' | 'production'
splitFrontend?: boolean
}> {
return cli
.option('mode', {
description: 'Generation mode to use',
choices: ['development', 'production'],
default: 'production' as const,
})
.option('split-frontend', {
description: 'Split frontend modules into separate chunks. By default enabled in the `development` mode and disabled in the `production` mode.',
type: 'boolean'
});
}
readonly pck: ApplicationPackage;
/** application process */
readonly process: ApplicationProcess;
/** manager process */
protected readonly __process: ApplicationProcess;
constructor(options: ApplicationPackageOptions) {
this.pck = new ApplicationPackage(options);
this.process = new ApplicationProcess(this.pck, options.projectPath);
this.__process = new ApplicationProcess(this.pck, path.join(__dirname, '..'));
}
protected async remove(fsPath: string): Promise<void> {
if (await fs.pathExists(fsPath)) {
await fs.remove(fsPath);
}
}
async clean(): Promise<void> {
const webpackGenerator = new WebpackGenerator(this.pck);
await Promise.all([
this.remove(this.pck.lib()),
this.remove(this.pck.srcGen()),
this.remove(webpackGenerator.genConfigPath),
this.remove(webpackGenerator.genNodeConfigPath)
]);
}
async prepare(): Promise<void> {
if (this.pck.isElectron()) {
await this.prepareElectron();
}
}
async generate(options: GeneratorOptions = {}): Promise<void> {
try {
await this.prepare();
} catch (error) {
if (error instanceof AbortError) {
console.warn(error.message);
process.exit(1);
}
throw error;
}
await Promise.all([
new WebpackGenerator(this.pck, options).generate(),
new BackendGenerator(this.pck, options).generate(),
new FrontendGenerator(this.pck, options).generate(),
]);
}
async copy(): Promise<void> {
await fs.ensureDir(this.pck.lib('frontend'));
await fs.copy(this.pck.frontend('index.html'), this.pck.lib('frontend', 'index.html'));
}
async build(args: string[] = [], options: GeneratorOptions = {}): Promise<void> {
await this.generate(options);
await this.copy();
return this.__process.run('webpack', args);
}
start(args: string[] = []): cp.ChildProcess {
if (this.pck.isElectron()) {
return this.startElectron(args);
} else if (this.pck.isBrowserOnly()) {
return this.startBrowserOnly(args);
}
return this.startBrowser(args);
}
startBrowserOnly(args: string[]): cp.ChildProcess {
const { command, mainArgs, options } = this.adjustBrowserOnlyArgs(args);
return this.__process.spawnBin(command, mainArgs, options);
}
adjustBrowserOnlyArgs(args: string[]): Readonly<{ command: string, mainArgs: string[]; options: cp.SpawnOptions }> {
let { mainArgs, options } = this.adjustArgs(args);
// first parameter: path to generated frontend
// second parameter: disable cache to support watching
mainArgs = ['lib/frontend', '-c-1', ...mainArgs];
const portIndex = mainArgs.findIndex(v => v.startsWith('--port'));
if (portIndex === -1) {
mainArgs.push('--port=3000');
}
return { command: 'http-server', mainArgs, options };
}
startElectron(args: string[]): cp.ChildProcess {
// If possible, pass the project root directory to electron rather than the script file so that Electron
// can determine the app name. This requires that the package.json has a main field.
let appPath = this.pck.projectPath;
if (!this.pck.pck.main) {
// Try the bundled electron app first
appPath = this.pck.lib('backend', 'electron-main.js');
if (!fs.existsSync(appPath)) {
// Fallback to the generated electron app in src-gen
appPath = this.pck.backend('electron-main.js');
}
console.warn(
`WARNING: ${this.pck.packagePath} does not have a "main" entry.\n` +
'Please add the following line:\n' +
' "main": "lib/backend/electron-main.js"'
);
}
const { mainArgs, options } = this.adjustArgs([appPath, ...args]);
const electronCli = require.resolve('electron/cli.js', { paths: [this.pck.projectPath] });
return this.__process.fork(electronCli, mainArgs, options);
}
startBrowser(args: string[]): cp.ChildProcess {
const { mainArgs, options } = this.adjustArgs(args);
// The backend must be a process group leader on UNIX in order to kill the tree later.
// See https://nodejs.org/api/child_process.html#child_process_options_detached
options.detached = process.platform !== 'win32';
// Try the bundled backend app first
let mainPath = this.pck.lib('backend', 'main.js');
if (!fs.existsSync(mainPath)) {
// Fallback to the generated backend file in src-gen
mainPath = this.pck.backend('main.js');
}
return this.__process.fork(mainPath, mainArgs, options);
}
/**
* Inject Theia's Electron-specific dependencies into the application's package.json.
*
* Only overwrite the Electron range if the current minimum supported version is lower than the recommended one.
*/
protected async prepareElectron(): Promise<void> {
let theiaElectron;
try {
theiaElectron = await import('@theia/electron');
} catch (error) {
if (error.code === 'ERR_MODULE_NOT_FOUND') {
throw new AbortError('Please install @theia/electron as part of your Theia Electron application');
}
throw error;
}
const expectedRange = theiaElectron.electronRange;
const appPackageJsonPath = this.pck.path('package.json');
const appPackageJson = await fs.readJSON(appPackageJsonPath) as { devDependencies?: Record<string, string> };
if (!appPackageJson.devDependencies) {
appPackageJson.devDependencies = {};
}
const currentRange: string | undefined = appPackageJson.devDependencies.electron;
if (!currentRange || semver.compare(semver.minVersion(currentRange), semver.minVersion(expectedRange)) < 0) {
// Update the range with the recommended one and write it on disk.
appPackageJson.devDependencies = this.insertAlphabetically(appPackageJson.devDependencies, 'electron', expectedRange);
await fs.writeJSON(appPackageJsonPath, appPackageJson, { spaces: 2 });
throw new AbortError('Updated dependencies, please run "install" again');
}
if (!theiaElectron.electronVersion || !semver.satisfies(theiaElectron.electronVersion, currentRange)) {
throw new AbortError('Dependencies are out of sync, please run "install" again');
}
const ffmpeg = await import('@theia/ffmpeg');
await ffmpeg.replaceFfmpeg();
await ffmpeg.checkFfmpeg();
}
protected insertAlphabetically<T extends Record<string, string>>(object: T, key: string, value: string): T {
const updated: Record<string, unknown> = {};
for (const property of Object.keys(object)) {
if (property.localeCompare(key) > 0) {
updated[key] = value;
}
updated[property] = object[property];
}
if (!(key in updated)) {
updated[key] = value;
}
return updated as T;
}
private adjustArgs(args: string[], forkOptions: cp.ForkOptions = {}): Readonly<{ mainArgs: string[]; options: cp.ForkOptions }> {
const options = {
...this.forkOptions,
forkOptions
};
const mainArgs = [...args];
const inspectIndex = mainArgs.findIndex(v => v.startsWith('--inspect'));
if (inspectIndex !== -1) {
const inspectArg = mainArgs.splice(inspectIndex, 1)[0];
options.execArgv = ['--nolazy', inspectArg];
}
return {
mainArgs,
options
};
}
private get forkOptions(): cp.ForkOptions {
return {
stdio: [0, 1, 2, 'ipc'],
env: {
...process.env,
THEIA_PARENT_PID: String(process.pid)
}
};
}
}

View File

@@ -0,0 +1,100 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import * as path from 'path';
import * as fs from 'fs-extra';
import * as cp from 'child_process';
import { ApplicationPackage } from '@theia/application-package';
export class ApplicationProcess {
protected readonly defaultOptions = {
cwd: this.pck.projectPath,
env: process.env
};
constructor(
protected readonly pck: ApplicationPackage,
protected readonly binProjectPath: string
) { }
spawn(command: string, args?: string[], options?: cp.SpawnOptions): cp.ChildProcess {
return cp.spawn(command, args || [], Object.assign({}, this.defaultOptions, {
...options,
shell: true
}));
}
fork(modulePath: string, args?: string[], options?: cp.ForkOptions): cp.ChildProcess {
return cp.fork(modulePath, args, Object.assign({}, this.defaultOptions, options));
}
canRun(command: string): boolean {
const binPath = this.resolveBin(this.binProjectPath, command);
return !!binPath && fs.existsSync(binPath);
}
run(command: string, args: string[], options?: cp.SpawnOptions): Promise<void> {
const commandProcess = this.spawnBin(command, args, options);
return this.promisify(command, commandProcess);
}
spawnBin(command: string, args: string[], options?: cp.SpawnOptions): cp.ChildProcess {
const binPath = this.resolveBin(this.binProjectPath, command);
if (!binPath) {
throw new Error(`Could not resolve ${command} relative to ${this.binProjectPath}`);
}
return this.spawn(binPath, args, {
...options,
shell: true
});
}
protected resolveBin(rootPath: string, command: string): string | undefined {
let commandPath = path.resolve(rootPath, 'node_modules', '.bin', command);
if (process.platform === 'win32') {
commandPath = commandPath + '.cmd';
}
if (fs.existsSync(commandPath)) {
return commandPath;
}
const parentDir = path.dirname(rootPath);
if (parentDir === rootPath) {
return undefined;
}
return this.resolveBin(parentDir, command);
}
protected promisify(command: string, p: cp.ChildProcess): Promise<void> {
return new Promise((resolve, reject) => {
p.stdout!.on('data', data => this.pck.log(data.toString()));
p.stderr!.on('data', data => this.pck.error(data.toString()));
p.on('error', reject);
p.on('close', (code, signal) => {
if (signal) {
reject(new Error(`${command} exited with an unexpected signal: ${signal}.`));
return;
}
if (code === 0) {
resolve();
} else {
reject(new Error(`${command} exited with an unexpected code: ${code}.`));
}
});
});
}
}

View File

@@ -0,0 +1,80 @@
// *****************************************************************************
// Copyright (C) 2020 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import * as fs from 'fs-extra';
import * as path from 'path';
// eslint-disable-next-line import/no-extraneous-dependencies
import type { RawSourceMap } from 'source-map';
import { ApplicationPackage } from '@theia/application-package/lib/application-package';
const modulePackages: { dir: string, name?: string }[] = [];
for (const extensionPackage of new ApplicationPackage({ projectPath: process.cwd() }).extensionPackages) {
modulePackages.push({
name: extensionPackage.name,
dir: path.dirname(extensionPackage.raw.installed!.packagePath)
});
}
function exposeModule(modulePackage: { dir: string, name?: string }, resourcePath: string, source: string): string {
if (!modulePackage.name) {
return source;
}
const { dir, name } = path.parse(resourcePath);
let moduleName = path.join(modulePackage.name, dir.substring(modulePackage.dir.length));
if (name !== 'index') {
moduleName = path.join(moduleName, name);
}
if (path.sep !== '/') {
moduleName = moduleName.split(path.sep).join('/');
}
return source + `\n;(globalThis['theia'] = globalThis['theia'] || {})['${moduleName}'] = this;\n`;
}
/**
* Expose bundled modules on window.theia.moduleName namespace, e.g.
* window['theia']['@theia/core/lib/common/uri'].
* Such syntax can be used by external code, for instance, for testing.
*/
// TODO: webpack@5.36.2 is missing a `LoaderContext` interface so we'll use any in the meantime
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export = function (this: any, source: string, sourceMap?: RawSourceMap): string | undefined {
if (this.cacheable) {
this.cacheable();
}
let modulePackage = modulePackages.find(({ dir }) => this.resourcePath.startsWith(dir + path.sep));
if (modulePackage) {
this.callback(undefined, exposeModule(modulePackage, this.resourcePath, source), sourceMap);
return;
}
const searchString = path.sep + 'node_modules';
const index = this.resourcePath.lastIndexOf(searchString);
if (index !== -1) {
const nodeModulesPath = this.resourcePath.substring(0, index + searchString.length);
let dir = this.resourcePath;
while ((dir = path.dirname(dir)) !== nodeModulesPath) {
try {
const { name } = fs.readJSONSync(path.join(dir, 'package.json'));
modulePackage = { name, dir };
modulePackages.push(modulePackage);
this.callback(undefined, exposeModule(modulePackage, this.resourcePath, source), sourceMap);
return;
} catch {
/** no-op */
}
}
}
this.callback(undefined, source, sourceMap);
};

View File

@@ -0,0 +1,69 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import * as fs from 'fs-extra';
import { ApplicationPackage } from '@theia/application-package';
export interface GeneratorOptions {
mode?: 'development' | 'production'
splitFrontend?: boolean
}
export abstract class AbstractGenerator {
constructor(
protected readonly pck: ApplicationPackage,
protected options: GeneratorOptions = {}
) { }
protected ifBrowser(value: string, defaultValue: string = ''): string {
return this.pck.ifBrowser(value, defaultValue);
}
protected ifElectron(value: string, defaultValue: string = ''): string {
return this.pck.ifElectron(value, defaultValue);
}
protected ifBrowserOnly(value: string, defaultValue: string = ''): string {
return this.pck.ifBrowserOnly(value, defaultValue);
}
protected async write(path: string, content: string): Promise<void> {
await fs.ensureFile(path);
await fs.writeFile(path, content);
}
protected ifMonaco(value: () => string, defaultValue: () => string = () => ''): string {
return this.ifPackage([
'@theia/monaco',
'@theia/monaco-editor-core'
], value, defaultValue);
}
protected ifPackage(packageName: string | string[], value: string | (() => string), defaultValue: string | (() => string) = ''): string {
const packages = Array.isArray(packageName) ? packageName : [packageName];
if (this.pck.extensionPackages.some(e => packages.includes(e.name))) {
return typeof value === 'string' ? value : value();
} else {
return typeof defaultValue === 'string' ? defaultValue : defaultValue();
}
}
protected prettyStringify(object: object): string {
return JSON.stringify(object, undefined, 4);
}
}

View File

@@ -0,0 +1,204 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { EOL } from 'os';
import { AbstractGenerator } from './abstract-generator';
export class BackendGenerator extends AbstractGenerator {
async generate(): Promise<void> {
if (this.pck.isBrowserOnly()) {
// no backend generation in case of browser-only target
return;
}
const backendModules = this.pck.targetBackendModules;
await this.write(this.pck.backend('server.js'), this.compileServer(backendModules));
await this.write(this.pck.backend('main.js'), this.compileMain(backendModules));
if (this.pck.isElectron()) {
await this.write(this.pck.backend('electron-main.js'), this.compileElectronMain(this.pck.targetElectronMainModules));
}
}
protected compileElectronMain(electronMainModules?: Map<string, string>): string {
return `// @ts-check
require('@theia/core/shared/reflect-metadata');
// Workaround for https://github.com/electron/electron/issues/9225. Chrome has an issue where
// in certain locales (e.g. PL), image metrics are wrongly computed. We explicitly set the
// LC_NUMERIC to prevent this from happening (selects the numeric formatting category of the
// C locale, http://en.cppreference.com/w/cpp/locale/LC_categories).
if (process.env.LC_ALL) {
process.env.LC_ALL = 'C';
}
process.env.LC_NUMERIC = 'C';
(async () => {
// Useful for Electron/NW.js apps as GUI apps on macOS doesn't inherit the \`$PATH\` define
// in your dotfiles (.bashrc/.bash_profile/.zshrc/etc).
// https://github.com/electron/electron/issues/550#issuecomment-162037357
// https://github.com/eclipse-theia/theia/pull/3534#issuecomment-439689082
(await require('@theia/core/electron-shared/fix-path')).default();
const { resolve } = require('path');
const theiaAppProjectPath = resolve(__dirname, '..', '..');
process.env.THEIA_APP_PROJECT_PATH = theiaAppProjectPath;
const { default: electronMainApplicationModule } = require('@theia/core/lib/electron-main/electron-main-application-module');
const { ElectronMainApplication, ElectronMainApplicationGlobals } = require('@theia/core/lib/electron-main/electron-main-application');
const { Container } = require('@theia/core/shared/inversify');
const { app } = require('electron');
const config = ${this.prettyStringify(this.pck.props.frontend.config)};
const isSingleInstance = ${this.pck.props.backend.config.singleInstance === true ? 'true' : 'false'};
if (isSingleInstance && !app.requestSingleInstanceLock(process.argv)) {
// There is another instance running, exit now. The other instance will request focus.
app.quit();
return;
}
const container = new Container();
container.load(electronMainApplicationModule);
container.bind(ElectronMainApplicationGlobals).toConstantValue({
THEIA_APP_PROJECT_PATH: theiaAppProjectPath,
THEIA_BACKEND_MAIN_PATH: resolve(__dirname, 'main.js'),
THEIA_FRONTEND_HTML_PATH: resolve(__dirname, '..', '..', 'lib', 'frontend', 'index.html'),
THEIA_SECONDARY_WINDOW_HTML_PATH: resolve(__dirname, '..', '..', 'lib', 'frontend', 'secondary-window.html')
});
function load(raw) {
return Promise.resolve(raw.default).then(module =>
container.load(module)
);
}
async function start() {
const application = container.get(ElectronMainApplication);
await application.start(config);
}
try {
${Array.from(electronMainModules?.values() ?? [], jsModulePath => `\
await load(require('${jsModulePath}'));`).join(EOL)}
await start();
} catch (reason) {
if (typeof reason !== 'number') {
console.error('Failed to start the electron application.');
if (reason) {
console.error(reason);
}
}
app.quit();
};
})();
`;
}
protected compileServer(backendModules: Map<string, string>): string {
return `// @ts-check
require('reflect-metadata');${this.ifElectron(`
// Patch electron version if missing, see https://github.com/eclipse-theia/theia/pull/7361#pullrequestreview-377065146
if (typeof process.versions.electron === 'undefined' && typeof process.env.THEIA_ELECTRON_VERSION === 'string') {
process.versions.electron = process.env.THEIA_ELECTRON_VERSION;
}`)}
// Erase the ELECTRON_RUN_AS_NODE variable from the environment, else Electron apps started using Theia will pick it up.
if ('ELECTRON_RUN_AS_NODE' in process.env) {
delete process.env.ELECTRON_RUN_AS_NODE;
}
const path = require('path');
process.env.THEIA_APP_PROJECT_PATH = path.resolve(__dirname, '..', '..')
const express = require('@theia/core/shared/express');
const { Container } = require('@theia/core/shared/inversify');
const { BackendApplication, BackendApplicationServer, CliManager } = require('@theia/core/lib/node');
const { backendApplicationModule } = require('@theia/core/lib/node/backend-application-module');
const { messagingBackendModule } = require('@theia/core/lib/node/messaging/messaging-backend-module');
const { loggerBackendModule } = require('@theia/core/lib/node/logger-backend-module');
const container = new Container();
container.load(backendApplicationModule);
container.load(messagingBackendModule);
container.load(loggerBackendModule);
function defaultServeStatic(app) {
app.use(express.static(path.resolve(__dirname, '../../lib/frontend')))
}
function load(raw) {
return Promise.resolve(raw).then(
module => container.load(module.default)
);
}
async function start(port, host, argv = process.argv) {
if (!container.isBound(BackendApplicationServer)) {
container.bind(BackendApplicationServer).toConstantValue({ configure: defaultServeStatic });
}
let result = undefined;
await container.get(CliManager).initializeCli(argv.slice(2),
() => container.get(BackendApplication).configured,
async () => {
result = container.get(BackendApplication).start(port, host);
});
if (result) {
return result;
} else {
return Promise.reject(0);
}
}
module.exports = async (port, host, argv) => {
try {
${Array.from(backendModules.values(), jsModulePath => `\
await load(require('${jsModulePath}'));`).join(EOL)}
return await start(port, host, argv);
} catch (error) {
if (typeof error !== 'number') {
console.error('Failed to start the backend application:');
console.error(error);
process.exitCode = 1;
}
throw error;
}
}
`;
}
protected compileMain(backendModules: Map<string, string>): string {
return `// @ts-check
const { BackendApplicationConfigProvider } = require('@theia/core/lib/node/backend-application-config-provider');
const main = require('@theia/core/lib/node/main');
BackendApplicationConfigProvider.set(${this.prettyStringify(this.pck.props.backend.config)});
globalThis.extensionInfo = ${this.prettyStringify(this.pck.extensionPackages.map(({ name, version }) => ({ name, version })))};
const serverModule = require('./server');
const serverAddress = main.start(serverModule());
serverAddress.then((addressInfo) => {
if (process && process.send && addressInfo) {
process.send(addressInfo);
}
});
globalThis.serverAddress = serverAddress;
`;
}
}

View File

@@ -0,0 +1,221 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/* eslint-disable @typescript-eslint/indent */
import { EOL } from 'os';
import { AbstractGenerator, GeneratorOptions } from './abstract-generator';
import { existsSync, readFileSync } from 'fs';
export class FrontendGenerator extends AbstractGenerator {
async generate(options?: GeneratorOptions): Promise<void> {
await this.write(this.pck.frontend('index.html'), this.compileIndexHtml(this.pck.targetFrontendModules));
await this.write(this.pck.frontend('index.js'), this.compileIndexJs(this.pck.targetFrontendModules, this.pck.targetFrontendPreloadModules));
await this.write(this.pck.frontend('secondary-window.html'), this.compileSecondaryWindowHtml());
await this.write(this.pck.frontend('secondary-index.js'), this.compileSecondaryIndexJs(this.pck.secondaryWindowModules));
if (this.pck.isElectron()) {
await this.write(this.pck.frontend('preload.js'), this.compilePreloadJs());
}
}
protected compileIndexPreload(frontendModules: Map<string, string>): string {
const template = this.pck.props.generator.config.preloadTemplate;
if (!template) {
return '';
}
// Support path to html file
if (existsSync(template)) {
return readFileSync(template).toString();
}
return template;
}
protected compileIndexHtml(frontendModules: Map<string, string>): string {
return `<!DOCTYPE html>
<html lang="en">
<head>${this.compileIndexHead(frontendModules)}
</head>
<body>
<div class="theia-preload">${this.compileIndexPreload(frontendModules)}</div>
<script type="text/javascript" src="./bundle.js" charset="utf-8"></script>
</body>
</html>`;
}
protected compileIndexHead(frontendModules: Map<string, string>): string {
return `
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>${this.pck.props.frontend.config.applicationName}</title>
<link rel="stylesheet" href="./vibn.css">`;
}
protected compileIndexJs(frontendModules: Map<string, string>, frontendPreloadModules: Map<string, string>): string {
return `\
// @ts-check
require('reflect-metadata');
const { Container } = require('@theia/core/shared/inversify');
const { FrontendApplicationConfigProvider } = require('@theia/core/lib/browser/frontend-application-config-provider');
FrontendApplicationConfigProvider.set(${this.prettyStringify(this.pck.props.frontend.config)});
${this.ifMonaco(() => `
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
return './editor.worker.js';
}
}`)}
function load(container, jsModule) {
return Promise.resolve(jsModule)
.then(containerModule => container.load(containerModule.default));
}
async function preload(container) {
try {
${Array.from(frontendPreloadModules.values(), jsModulePath => `\
await load(container, ${this.importOrRequire()}('${jsModulePath}'));`).join(EOL)}
const { Preloader } = require('@theia/core/lib/browser/preload/preloader');
const preloader = container.get(Preloader);
await preloader.initialize();
} catch (reason) {
console.error('Failed to run preload scripts.');
if (reason) {
console.error(reason);
}
}
}
module.exports = (async () => {
const { messagingFrontendModule } = require('@theia/core/lib/${this.pck.isBrowser() || this.pck.isBrowserOnly()
? 'browser/messaging/messaging-frontend-module'
: 'electron-browser/messaging/electron-messaging-frontend-module'}');
const container = new Container();
container.load(messagingFrontendModule);
${this.ifBrowserOnly(`const { messagingFrontendOnlyModule } = require('@theia/core/lib/browser-only/messaging/messaging-frontend-only-module');
container.load(messagingFrontendOnlyModule);`)}
await preload(container);
${this.ifMonaco(() => `
const { MonacoInit } = require('@theia/monaco/lib/browser/monaco-init');
`)};
const { FrontendApplication } = require('@theia/core/lib/browser');
const { frontendApplicationModule } = require('@theia/core/lib/browser/frontend-application-module');
const { loggerFrontendModule } = require('@theia/core/lib/browser/logger-frontend-module');
container.load(frontendApplicationModule);
${this.pck.ifBrowserOnly(`const { frontendOnlyApplicationModule } = require('@theia/core/lib/browser-only/frontend-only-application-module');
container.load(frontendOnlyApplicationModule);`)}
container.load(loggerFrontendModule);
${this.ifBrowserOnly(`const { loggerFrontendOnlyModule } = require('@theia/core/lib/browser-only/logger-frontend-only-module');
container.load(loggerFrontendOnlyModule);`)}
try {
${Array.from(frontendModules.values(), jsModulePath => `\
await load(container, ${this.importOrRequire()}('${jsModulePath}'));`).join(EOL)}
${this.ifMonaco(() => `
MonacoInit.init(container);
`)};
await start();
} catch (reason) {
console.error('Failed to start the frontend application.');
if (reason) {
console.error(reason);
}
}
function start() {
(window['theia'] = window['theia'] || {}).container = container;
return container.get(FrontendApplication).start();
}
})();
`;
}
protected importOrRequire(): string {
return this.options.mode !== 'production' ? 'import' : 'require';
}
/** HTML for secondary windows that contain an extracted widget. */
protected compileSecondaryWindowHtml(): string {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Theia — Secondary Window</title>
<style>
html, body {
overflow: hidden;
-ms-overflow-style: none;
}
body {
margin: 0;
}
html,
head,
body,
.secondary-widget-root,
#widget-host {
width: 100% !important;
height: 100% !important;
}
</style>
<link rel="stylesheet" href="./secondary-window.css">
</head>
<body>
<div id="widget-host"></div>
</body>
</html>`;
}
protected compileSecondaryIndexJs(secondaryWindowModules: Map<string, string>): string {
return `\
// @ts-check
require('reflect-metadata');
const { Container } = require('@theia/core/shared/inversify');
module.exports = Promise.resolve().then(() => {
const { frontendApplicationModule } = require('@theia/core/lib/browser/frontend-application-module');
const container = new Container();
container.load(frontendApplicationModule);
${Array.from(secondaryWindowModules.values(), jsModulePath => `\
container.load(require('${jsModulePath}').default);`).join(EOL)}
});
`;
}
compilePreloadJs(): string {
return `\
// @ts-check
${Array.from(this.pck.preloadModules.values(), path => `require('${path}').preload();`).join(EOL)}
`;
}
}

View File

@@ -0,0 +1,19 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
export * from './webpack-generator';
export * from './frontend-generator';
export * from './backend-generator';

View File

@@ -0,0 +1,529 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import * as paths from 'path';
import * as fs from 'fs-extra';
import { AbstractGenerator } from './abstract-generator';
export class WebpackGenerator extends AbstractGenerator {
async generate(): Promise<void> {
await this.write(this.genConfigPath, this.compileWebpackConfig());
if (!this.pck.isBrowserOnly()) {
await this.write(this.genNodeConfigPath, this.compileNodeWebpackConfig());
}
if (await this.shouldGenerateUserWebpackConfig()) {
await this.write(this.configPath, this.compileUserWebpackConfig());
}
}
protected async shouldGenerateUserWebpackConfig(): Promise<boolean> {
if (!(await fs.pathExists(this.configPath))) {
return true;
}
const content = await fs.readFile(this.configPath, 'utf8');
return content.indexOf('gen-webpack') === -1;
}
get configPath(): string {
return this.pck.path('webpack.config.js');
}
get genConfigPath(): string {
return this.pck.path('gen-webpack.config.js');
}
get genNodeConfigPath(): string {
return this.pck.path('gen-webpack.node.config.js');
}
protected compileWebpackConfig(): string {
return `/**
* Don't touch this file. It will be regenerated by theia build.
* To customize webpack configuration change ${this.configPath}
*/
// @ts-check
const path = require('path');
const webpack = require('webpack');
const yargs = require('yargs');
const resolvePackagePath = require('resolve-package-path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { MonacoWebpackPlugin } = require('@theia/native-webpack-plugin/lib/monaco-webpack-plugins.js');
const outputPath = path.resolve(__dirname, 'lib', 'frontend');
const { mode, staticCompression } = yargs.option('mode', {
description: "Mode to use",
choices: ["development", "production"],
default: "production"
}).option('static-compression', {
description: 'Controls whether to enable compression of static artifacts.',
type: 'boolean',
default: true
}).argv;
const development = mode === 'development';
const plugins = [
new CopyWebpackPlugin({
patterns: [
{
// copy secondary window html file to lib folder
from: path.resolve(__dirname, 'src-gen/frontend/secondary-window.html')
}${this.ifPackage('@theia/plugin-ext', `,
{
// copy webview files to lib folder
from: path.join(resolvePackagePath('@theia/plugin-ext', __dirname), '..', 'src', 'main', 'browser', 'webview', 'pre'),
to: path.resolve(__dirname, 'lib', 'webview', 'pre')
}`)}
${this.ifPackage('@theia/plugin-ext-vscode', `,
{
// copy frontend plugin host files
from: path.join(resolvePackagePath('@theia/plugin-ext-vscode', __dirname), '..', 'lib', 'node', 'context', 'plugin-vscode-init-fe.js'),
to: path.resolve(__dirname, 'lib', 'frontend', 'context', 'plugin-vscode-init-fe.js')
}`)}
]
}),
new webpack.ProvidePlugin({
// the Buffer class doesn't exist in the browser but some dependencies rely on it
Buffer: ['buffer', 'Buffer']
}),
new MonacoWebpackPlugin()
];
// it should go after copy-plugin in order to compress monaco as well
if (staticCompression) {
plugins.push(new CompressionPlugin({}));
}
module.exports = [{
mode,
plugins,
devtool: 'source-map',
entry: {
bundle: path.resolve(__dirname, 'src-gen/frontend/index.js'),
${this.ifMonaco(() => "'editor.worker': '@theia/monaco-editor-core/esm/vs/editor/editor.worker.js'")}
},
output: {
filename: '[name].js',
path: outputPath,
devtoolModuleFilenameTemplate: 'webpack:///[resource-path]?[loaders]',
globalObject: 'self'
},
target: 'web',
cache: staticCompression,
module: {
rules: [
{
test: /\\.css$/,
exclude: /materialcolors\\.css$|\\.useable\\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /materialcolors\\.css$|\\.useable\\.css$/,
use: [
{
loader: 'style-loader',
options: {
esModule: false,
injectType: 'lazySingletonStyleTag',
attributes: {
id: 'theia-theme'
}
}
},
'css-loader'
]
},
{
test: /\\.(ttf|eot|svg)(\\?v=\\d+\\.\\d+\\.\\d+)?$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10000,
}
},
generator: {
dataUrl: {
mimetype: 'image/svg+xml'
}
}
},
{
test: /\\.(jpg|png|gif)$/,
type: 'asset/resource',
generator: {
filename: '[hash].[ext]'
}
},
{
// see https://github.com/eclipse-theia/theia/issues/556
test: /source-map-support/,
loader: 'ignore-loader'
},
{
test: /\\.d\\.ts$/,
loader: 'ignore-loader'
},
{
test: /\\.js$/,
enforce: 'pre',
loader: 'source-map-loader',
exclude: /jsonc-parser|fast-plist|onigasm/
},
{
test: /\\.woff(2)?(\\?v=[0-9]\\.[0-9]\\.[0-9])?$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10000,
}
},
generator: {
dataUrl: {
mimetype: 'image/svg+xml'
}
}
},
{
test: /node_modules[\\\\|\/](vscode-languageserver-types|vscode-uri|jsonc-parser|vscode-languageserver-protocol)/,
loader: 'umd-compat-loader'
},
{
test: /\\.wasm$/,
type: 'asset/resource'
},
{
test: /\\.plist$/,
type: 'asset/resource'
}
]
},
resolve: {
fallback: {
'child_process': false,
'crypto': false,
'net': false,
'path': require.resolve('path-browserify'),
'process': false,
'os': false,
'timers': false
},
extensions: ['.js']
},
stats: {
warnings: true,
children: true
},
ignoreWarnings: [
// Some packages do not have source maps, that's ok
/Failed to parse source map/,
{
// Monaco uses 'require' in a non-standard way
module: /@theia\\/monaco-editor-core/,
message: /require function is used in a way in which dependencies cannot be statically extracted/
}
]
},
{
mode,
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "[name].css",
chunkFilename: "[id].css",
}),
new MonacoWebpackPlugin(),
],
devtool: 'source-map',
entry: {
"secondary-window": path.resolve(__dirname, 'src-gen/frontend/secondary-index.js'),
},
output: {
filename: '[name].js',
path: outputPath,
devtoolModuleFilenameTemplate: 'webpack:///[resource-path]?[loaders]',
globalObject: 'self'
},
target: 'web',
cache: staticCompression,
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader"]
},
{
test: /\.wasm$/,
type: 'asset/resource'
}
]
},
resolve: {
fallback: {
'child_process': false,
'crypto': false,
'net': false,
'path': require.resolve('path-browserify'),
'process': false,
'os': false,
'timers': false
},
extensions: ['.js']
},
stats: {
warnings: true,
children: true
},
ignoreWarnings: [
{
// Monaco uses 'require' in a non-standard way
module: /@theia\\/monaco-editor-core/,
message: /require function is used in a way in which dependencies cannot be statically extracted/
}
]
}${this.ifElectron(`, {
mode,
devtool: 'source-map',
entry: {
"preload": path.resolve(__dirname, 'src-gen/frontend/preload.js'),
},
output: {
filename: '[name].js',
path: outputPath,
devtoolModuleFilenameTemplate: 'webpack:///[resource-path]?[loaders]',
globalObject: 'self'
},
target: 'electron-preload',
cache: staticCompression,
stats: {
warnings: true,
children: true
}
}`)}];`;
}
protected compileUserWebpackConfig(): string {
return `/**
* This file can be edited to customize webpack configuration.
* To reset delete this file and rerun theia build again.
*/
// @ts-check
const configs = require('./${paths.basename(this.genConfigPath)}');
${this.ifBrowserOnly('', `const nodeConfig = require('./${paths.basename(this.genNodeConfigPath)}');`)}
/**
* Expose bundled modules on window.theia.moduleName namespace, e.g.
* window['theia']['@theia/core/lib/common/uri'].
* Such syntax can be used by external code, for instance, for testing.
configs[0].module.rules.push({
test: /\\.js$/,
loader: require.resolve('@theia/application-manager/lib/expose-loader')
}); */
${this.ifBrowserOnly('module.exports = configs;', `module.exports = [
...configs,
nodeConfig.config
];`)}
`;
}
protected compileNodeWebpackConfig(): string {
return `/**
* Don't touch this file. It will be regenerated by theia build.
* To customize webpack configuration change ${this.configPath}
*/
// @ts-check
const path = require('path');
const yargs = require('yargs');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const NativeWebpackPlugin = require('@theia/native-webpack-plugin');
const { MonacoWebpackPlugin } = require('@theia/native-webpack-plugin/lib/monaco-webpack-plugins.js');
const { mode } = yargs.option('mode', {
description: "Mode to use",
choices: ["development", "production"],
default: "production"
}).argv;
const production = mode === 'production';
/** @type {import('webpack').EntryObject} */
const commonJsLibraries = {};
for (const [entryPointName, entryPointPath] of Object.entries({
${this.ifPackage('@theia/plugin-ext', "'backend-init-theia': '@theia/plugin-ext/lib/hosted/node/scanners/backend-init-theia',")}
${this.ifPackage('@theia/filesystem', "'parcel-watcher': '@theia/filesystem/lib/node/parcel-watcher',")}
${this.ifPackage('@theia/plugin-ext-vscode', "'plugin-vscode-init': '@theia/plugin-ext-vscode/lib/node/plugin-vscode-init',")}
${this.ifPackage('@theia/api-provider-sample', "'gotd-api-init': '@theia/api-provider-sample/lib/plugin/gotd-api-init',")}
${this.ifPackage('@theia/git', "'git-locator-host': '@theia/git/lib/node/git-locator/git-locator-host',")}
})) {
commonJsLibraries[entryPointName] = {
import: require.resolve(entryPointPath),
library: {
type: 'commonjs2',
},
};
}
const ignoredResources = new Set();
if (process.platform !== 'win32') {
ignoredResources.add('@vscode/windows-ca-certs');
ignoredResources.add('@vscode/windows-ca-certs/build/Release/crypt32.node');
}
const nativePlugin = new NativeWebpackPlugin({
out: 'native',
trash: ${this.ifPackage('@theia/filesystem', 'true', 'false')},
ripgrep: ${this.ifPackage(['@theia/search-in-workspace', '@theia/file-search'], 'true', 'false')},
pty: ${this.ifPackage('@theia/process', 'true', 'false')},
nativeBindings: {
drivelist: 'drivelist/build/Release/drivelist.node'
}
});
${this.ifPackage('@theia/process', () => `// Ensure that node-pty is correctly hoisted
try {
require.resolve('node-pty');
} catch {
console.error('"node-pty" dependency is not installed correctly. Ensure that it is available in the root node_modules directory.');
console.error('Exiting webpack build process.');
process.exit(1);
}`)}
/** @type {import('webpack').Configuration} */
const config = {
mode,
devtool: mode === 'development' ? 'source-map' : false,
target: 'node',
node: {
global: false,
__filename: false,
__dirname: false
},
resolve: {
extensions: ['.js', '.json', '.wasm', '.node'],
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'lib', 'backend'),
devtoolModuleFilenameTemplate: 'webpack:///[absolute-resource-path]?[loaders]',
},${this.ifElectron(`
externals: {
electron: 'require("electron")'
},`)}
entry: {
// Main entry point of the Theia application backend:
'main': require.resolve('./src-gen/backend/main'),
// Theia's IPC mechanism:
'ipc-bootstrap': require.resolve('@theia/core/lib/node/messaging/ipc-bootstrap'),
${this.ifPackage('@theia/plugin-ext', () => `// VS Code extension support:
'plugin-host': require.resolve('@theia/plugin-ext/lib/hosted/node/plugin-host'),`)}
${this.ifPackage('@theia/plugin-ext-headless', () => `// Theia Headless Plugin support:
'plugin-host-headless': require.resolve('@theia/plugin-ext-headless/lib/hosted/node/plugin-host-headless'),`)}
${this.ifPackage('@theia/process', () => `// Make sure the node-pty thread worker can be executed:
'worker/conoutSocketWorker': require.resolve('node-pty/lib/worker/conoutSocketWorker'),`)}
${this.ifElectron("'electron-main': require.resolve('./src-gen/backend/electron-main'),")}
${this.ifPackage('@theia/dev-container', () => `// VS Code Dev-Container communication:
'dev-container-server': require.resolve('@theia/dev-container/lib/dev-container-server/dev-container-server'),`)}
...commonJsLibraries
},
module: {
rules: [
// Make sure we can still find and load our native addons.
{
test: /\\.node$/,
loader: 'node-loader',
options: {
name: 'native/[name].[ext]'
}
},
{
test: /\\.d\\.ts$/,
loader: 'ignore-loader'
},
{
test: /\\.js$/,
enforce: 'pre',
loader: 'source-map-loader'
},
// jsonc-parser exposes its UMD implementation by default, which
// confuses Webpack leading to missing js in the bundles.
{
test: /node_modules[\\/](jsonc-parser)/,
loader: 'umd-compat-loader'
}
]
},
plugins: [
// Some native dependencies need special handling
nativePlugin,
// Optional node dependencies can be safely ignored
new webpack.IgnorePlugin({
checkResource: resource => ignoredResources.has(resource)
}),
new MonacoWebpackPlugin()
],
optimization: {
// Split and reuse code across the various entry points
splitChunks: {
chunks: 'all'
},
// Only minimize if we run webpack in production mode
minimize: production,
minimizer: [
new TerserPlugin({
exclude: /^(lib|builtins)\\//${this.ifPackage(['@theia/scanoss', '@theia/ai-anthropic', '@theia/ai-openai'], () => `,
terserOptions: {
keep_classnames: /AbortSignal/
}`)}
})
]
},
ignoreWarnings: [
// Some packages do not have source maps, that's ok
/Failed to parse source map/,
// require with expressions are not supported
/the request of a dependency is an expression/,
// Some packages use dynamic requires, we can safely ignore them (they are handled by the native webpack plugin)
/require function is used in a way in which dependencies cannot be statically extracted/, {
module: /yargs/
}, {
module: /node-pty/
}, {
module: /require-main-filename/
}, {
module: /ws/
}, {
module: /express/
}, {
module: /cross-spawn/
}, {
module: /@parcel\\/watcher/
}
]
};
module.exports = {
config,
nativePlugin,
ignoredResources
};
`;
}
}

View File

@@ -0,0 +1,19 @@
// *****************************************************************************
// Copyright (C) 2018 Ericsson and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
export * from './rebuild';
export * from './application-package-manager';
export * from './application-process';

View File

@@ -0,0 +1,28 @@
// *****************************************************************************
// Copyright (C) 2018 Ericsson and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/* note: this bogus test file is required so that
we are able to run mocha unit tests on this
package, without having any actual unit tests in it.
This way a coverage report will be generated,
showing 0% coverage, instead of no report.
This file can be removed once we have real unit
tests in place. */
describe('application-manager package', () => {
it('should support code coverage statistics', () => true);
});

View File

@@ -0,0 +1,349 @@
// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import cp = require('child_process');
import fs = require('fs-extra');
import path = require('path');
import os = require('os');
export type RebuildTarget = 'electron' | 'browser' | 'browser-only';
const EXIT_SIGNALS: NodeJS.Signals[] = ['SIGINT', 'SIGTERM'];
interface ExitToken {
getLastSignal(): NodeJS.Signals | undefined
onSignal(callback: (signal: NodeJS.Signals) => void): void
}
type NodeABI = string | number;
export const DEFAULT_MODULES = [
'node-pty',
'native-keymap',
'find-git-repositories',
'drivelist',
'keytar',
'ssh2',
'cpu-features'
];
export interface RebuildOptions {
/**
* What modules to rebuild.
*/
modules?: string[]
/**
* Folder where the module cache will be created/read from.
*/
cacheRoot?: string
/**
* In the event that `node-abi` doesn't recognize the current Electron version,
* you can specify the Node ABI to rebuild for.
*/
forceAbi?: NodeABI
}
/**
* @param target What to rebuild for.
* @param options
*/
export function rebuild(target: RebuildTarget, options: RebuildOptions = {}): void {
const {
modules = DEFAULT_MODULES,
cacheRoot = process.cwd(),
forceAbi,
} = options;
const cache = path.resolve(cacheRoot, '.browser_modules');
const cacheExists = folderExists(cache);
guardExit(async token => {
if (target === 'electron' && !cacheExists) {
process.exitCode = await rebuildElectronModules(cache, modules, forceAbi, token);
} else if (target === 'browser' && cacheExists) {
process.exitCode = await revertBrowserModules(cache, modules);
} else {
console.log(`native node modules are already rebuilt for ${target}`);
}
}).catch(errorOrSignal => {
if (typeof errorOrSignal === 'string' && errorOrSignal in os.constants.signals) {
process.kill(process.pid, errorOrSignal);
} else {
throw errorOrSignal;
}
});
}
function folderExists(folder: string): boolean {
if (fs.existsSync(folder)) {
if (fs.statSync(folder).isDirectory()) {
return true;
} else {
throw new Error(`"${folder}" exists but it is not a directory`);
}
}
return false;
}
/**
* Schema for `<browserModuleCache>/modules.json`.
*/
interface ModulesJson {
[moduleName: string]: ModuleBackup
}
interface ModuleBackup {
originalLocation: string
}
async function rebuildElectronModules(browserModuleCache: string, modules: string[], forceAbi: NodeABI | undefined, token: ExitToken): Promise<number> {
const modulesJsonPath = path.join(browserModuleCache, 'modules.json');
const modulesJson: ModulesJson = await fs.access(modulesJsonPath).then(
() => fs.readJson(modulesJsonPath),
() => ({})
);
let success = true;
// Backup already built browser modules.
await Promise.all(modules.map(async module => {
let modulePath;
try {
modulePath = require.resolve(`${module}/package.json`, {
paths: [process.cwd()],
});
} catch (_) {
console.debug(`Module not found: ${module}`);
return; // Skip current module.
}
const src = path.dirname(modulePath);
const dest = path.join(browserModuleCache, module);
try {
await fs.remove(dest);
await fs.copy(src, dest, { overwrite: true });
modulesJson[module] = {
originalLocation: src,
};
console.debug(`Processed "${module}"`);
} catch (error) {
console.error(`Error while doing a backup for "${module}": ${error}`);
success = false;
}
}));
if (Object.keys(modulesJson).length === 0) {
console.debug('No module to rebuild.');
return 0;
}
// Update manifest tracking the backups' original locations.
await fs.writeJson(modulesJsonPath, modulesJson, { spaces: 2 });
// If we failed to process a module then exit now.
if (!success) {
return 1;
}
const todo = modules.map(m => {
// electron-rebuild ignores the module namespace...
const slash = m.indexOf('/');
return m.startsWith('@') && slash !== -1
? m.substring(slash + 1)
: m;
});
let exitCode: number | undefined;
try {
if (process.env.THEIA_REBUILD_NO_WORKAROUND) {
exitCode = await runElectronRebuild(todo, forceAbi, token);
} else {
exitCode = await electronRebuildExtraModulesWorkaround(process.cwd(), todo, () => runElectronRebuild(todo, forceAbi, token), token);
}
} catch (error) {
console.error(error);
} finally {
// If code is undefined or different from zero we need to revert back to the browser modules.
if (exitCode !== 0) {
await revertBrowserModules(browserModuleCache, modules);
}
return exitCode ?? 1;
}
}
async function revertBrowserModules(browserModuleCache: string, modules: string[]): Promise<number> {
let exitCode = 0;
const modulesJsonPath = path.join(browserModuleCache, 'modules.json');
const modulesJson: ModulesJson = await fs.readJson(modulesJsonPath);
await Promise.all(Object.entries(modulesJson).map(async ([moduleName, entry]) => {
if (!modules.includes(moduleName)) {
return; // Skip modules that weren't requested.
}
const src = path.join(browserModuleCache, moduleName);
if (!await fs.pathExists(src)) {
delete modulesJson[moduleName];
console.error(`Missing backup for ${moduleName}!`);
exitCode = 1;
return;
}
const dest = entry.originalLocation;
try {
await fs.remove(dest);
await fs.copy(src, dest, { overwrite: false });
await fs.remove(src);
delete modulesJson[moduleName];
console.debug(`Reverted "${moduleName}"`);
} catch (error) {
console.error(`Error while reverting "${moduleName}": ${error}`);
exitCode = 1;
}
}));
if (Object.keys(modulesJson).length === 0) {
// We restored everything, so we can delete the cache.
await fs.remove(browserModuleCache);
} else {
// Some things were not restored, so we update the manifest.
await fs.writeJson(modulesJsonPath, modulesJson, { spaces: 2 });
}
return exitCode;
}
async function runElectronRebuild(modules: string[], forceAbi: NodeABI | undefined, token: ExitToken): Promise<number> {
const todo = modules.join(',');
return new Promise(async (resolve, reject) => {
let command = `npx --no-install electron-rebuild -f -w=${todo} -o=${todo}`;
if (forceAbi) {
command += ` --force-abi ${forceAbi}`;
}
const electronRebuild = cp.spawn(command, {
stdio: 'inherit',
shell: true,
});
token.onSignal(signal => electronRebuild.kill(signal));
electronRebuild.on('error', reject);
electronRebuild.on('close', (code, signal) => {
if (signal) {
reject(new Error(`electron-rebuild exited with "${signal}"`));
} else {
resolve(code!);
}
});
});
}
/**
* `electron-rebuild` is supposed to accept a list of modules to build, even when not part of the dependencies.
* But there is a bug that causes `electron-rebuild` to not correctly process this list of modules.
*
* This workaround will temporarily modify the current package.json file.
*
* PR with fix: https://github.com/electron/electron-rebuild/pull/888
*
* TODO: Remove this workaround.
*/
async function electronRebuildExtraModulesWorkaround<T>(cwd: string, extraModules: string[], run: (token: ExitToken) => Promise<T>, token: ExitToken): Promise<T> {
const packageJsonPath = path.resolve(cwd, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
// package.json exists: We back it up before modifying it then revert it.
const packageJsonCopyPath = `${packageJsonPath}.copy`;
const packageJson = await fs.readJson(packageJsonPath);
await fs.copy(packageJsonPath, packageJsonCopyPath);
await throwIfSignal(token, async () => {
await fs.unlink(packageJsonCopyPath);
});
if (typeof packageJson.dependencies !== 'object') {
packageJson.dependencies = {};
}
for (const extraModule of extraModules) {
if (!packageJson.dependencies[extraModule]) {
packageJson.dependencies[extraModule] = '*';
}
}
try {
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
await throwIfSignal(token);
return await run(token);
} finally {
await fs.move(packageJsonCopyPath, packageJsonPath, { overwrite: true });
}
} else {
// package.json does not exist: We create one then remove it.
const packageJson = {
name: 'theia-rebuild-workaround',
version: '0.0.0',
dependencies: {} as Record<string, string>,
};
for (const extraModule of extraModules) {
packageJson.dependencies[extraModule] = '*';
}
try {
await fs.writeJson(packageJsonPath, packageJson);
await throwIfSignal(token);
return await run(token);
} finally {
await fs.unlink(packageJsonPath);
}
}
}
/**
* Temporarily install hooks to **try** to prevent the process from exiting while `run` is running.
*
* Note that it is still possible to kill the process and prevent cleanup logic (e.g. SIGKILL, computer forced shutdown, etc).
*/
async function guardExit<T>(run: (token: ExitToken) => Promise<T>): Promise<T> {
const token = new ExitTokenImpl();
const signalListener = (signal: NodeJS.Signals) => token._emitSignal(signal);
for (const signal of EXIT_SIGNALS) {
process.on(signal, signalListener);
}
try {
return await run(token);
} finally {
for (const signal of EXIT_SIGNALS) {
// FIXME we have a type clash here between Node, Electron and Mocha.
// Typescript is resolving here to Electron's Process interface which extends the NodeJS.EventEmitter interface
// However instead of the actual NodeJS.EventEmitter interface it resolves to an empty stub of Mocha
// Therefore it can't find the correct "off" signature and throws an error
// By casting to the NodeJS.EventEmitter ourselves, we short circuit the resolving and it succeeds
(process as NodeJS.EventEmitter).off(signal, signalListener);
}
}
}
class ExitTokenImpl implements ExitToken {
protected _listeners = new Set<(signal: NodeJS.Signals) => void>();
protected _lastSignal?: NodeJS.Signals;
onSignal(callback: (signal: NodeJS.Signals) => void): void {
this._listeners.add(callback);
}
getLastSignal(): NodeJS.Signals | undefined {
return this._lastSignal;
}
_emitSignal(signal: NodeJS.Signals): void {
this._lastSignal = signal;
for (const listener of this._listeners) {
listener(signal);
}
}
}
/**
* Throw `signal` if one was received, runs `cleanup` before doing so.
*/
async function throwIfSignal(token: ExitToken, cleanup?: () => Promise<void>): Promise<void> {
if (token.getLastSignal()) {
try {
await cleanup?.();
} finally {
// eslint-disable-next-line no-throw-literal
throw token.getLastSignal()!;
}
}
}

View File

@@ -0,0 +1,22 @@
{
"extends": "../../configs/base.tsconfig",
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "lib"
},
"include": [
"src"
],
"references": [
{
"path": "../application-package"
},
{
"path": "../ffmpeg"
},
{
"path": "../native-webpack-plugin"
}
]
}