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,10 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: [
'../../configs/build.eslintrc.json'
],
parserOptions: {
tsconfigRootDir: __dirname,
project: 'tsconfig.json'
}
};

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-PACKAGE</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,52 @@
{
"name": "@theia/application-package",
"version": "1.68.0",
"description": "Theia application package 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": {
"@theia/request": "1.68.0",
"@types/fs-extra": "^4.0.2",
"@types/semver": "^7.5.0",
"@types/write-json-file": "^2.2.1",
"deepmerge": "^4.2.2",
"fs-extra": "^4.0.2",
"is-electron": "^2.1.0",
"nano": "^10.1.3",
"resolve-package-path": "^4.0.3",
"semver": "^7.5.4",
"tslib": "^2.6.2",
"write-json-file": "^2.2.0"
},
"devDependencies": {
"@theia/ext-scripts": "1.68.0"
},
"nyc": {
"extends": "../../configs/nyc.json"
},
"gitHead": "21358137e41342742707f660b8e222f940a27652"
}

View File

@@ -0,0 +1,21 @@
// *****************************************************************************
// Copyright (C) 2021 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
// *****************************************************************************
/**
* The default supported API version the framework supports.
* The version should be in the format `x.y.z`.
*/
export const DEFAULT_SUPPORTED_API_VERSION = '1.108.0';

View File

@@ -0,0 +1,62 @@
// *****************************************************************************
// Copyright (C) 2020 Maksim Ryzhikov 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 assert from 'assert';
import * as temp from 'temp';
import * as fs from 'fs-extra';
import * as path from 'path';
import { ApplicationPackage } from './application-package';
import { ApplicationProps } from './application-props';
import * as sinon from 'sinon';
const track = temp.track();
const sandbox = sinon.createSandbox();
describe('application-package', function (): void {
after((): void => {
sandbox.restore();
track.cleanupSync();
});
it('should print warning if user set unknown target in package.json and use browser as a default value', function (): void {
const warn = sandbox.stub(console, 'warn');
const root = createProjectWithTarget('foo');
const applicationPackage = new ApplicationPackage({ projectPath: root });
assert.deepStrictEqual(applicationPackage.target, ApplicationProps.ApplicationTarget.browser);
assert.deepStrictEqual(warn.called, true);
});
it('should set target from package.json', function (): void {
const target = 'electron';
const root = createProjectWithTarget(target);
const applicationPackage = new ApplicationPackage({ projectPath: root });
assert.deepStrictEqual(applicationPackage.target, target);
});
it('should prefer target from passed options over target from package.json', function (): void {
const pckTarget = 'electron';
const optTarget = 'browser';
const root = createProjectWithTarget(pckTarget);
const applicationPackage = new ApplicationPackage({ projectPath: root, appTarget: optTarget });
assert.deepStrictEqual(applicationPackage.target, optTarget);
});
function createProjectWithTarget(target: string): string {
const root = track.mkdirSync('foo-project');
fs.writeFileSync(path.join(root, 'package.json'), `{"theia": {"target": "${target}"}}`);
return root;
}
});

View File

@@ -0,0 +1,328 @@
// *****************************************************************************
// 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 { readJsonFile, writeJsonFile } from './json-file';
import { NpmRegistry, NodePackage, PublishedNodePackage, sortByKey } from './npm-registry';
import { Extension, ExtensionPackage, ExtensionPackageOptions, RawExtensionPackage } from './extension-package';
import { ExtensionPackageCollector } from './extension-package-collector';
import { ApplicationProps } from './application-props';
import deepmerge = require('deepmerge');
import resolvePackagePath = require('resolve-package-path');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ApplicationLog = (message?: any, ...optionalParams: any[]) => void;
export class ApplicationPackageOptions {
readonly projectPath: string;
readonly log?: ApplicationLog;
readonly error?: ApplicationLog;
readonly registry?: NpmRegistry;
readonly appTarget?: ApplicationProps.Target;
}
export type ApplicationModuleResolver = (parentPackagePath: string, modulePath: string) => string;
export class ApplicationPackage {
readonly projectPath: string;
readonly log: ApplicationLog;
readonly error: ApplicationLog;
constructor(
protected readonly options: ApplicationPackageOptions
) {
this.projectPath = options.projectPath;
this.log = options.log || console.log.bind(console);
this.error = options.error || console.error.bind(console);
}
protected _registry: NpmRegistry | undefined;
get registry(): NpmRegistry {
if (this._registry) {
return this._registry;
}
this._registry = this.options.registry || new NpmRegistry();
this._registry.updateProps(this.props);
return this._registry;
}
get target(): ApplicationProps.Target {
return this.props.target;
}
protected _props: ApplicationProps | undefined;
get props(): ApplicationProps {
if (this._props) {
return this._props;
}
const theia = this.pck.theia || {};
if (this.options.appTarget) {
theia.target = this.options.appTarget;
}
if (theia.target && !(Object.values(ApplicationProps.ApplicationTarget).includes(theia.target))) {
const defaultTarget = ApplicationProps.ApplicationTarget.browser;
console.warn(`Unknown application target '${theia.target}', '${defaultTarget}' to be used instead`);
theia.target = defaultTarget;
}
return this._props = deepmerge(ApplicationProps.DEFAULT, theia);
}
protected _pck: NodePackage | undefined;
get pck(): NodePackage {
if (this._pck) {
return this._pck;
}
return this._pck = readJsonFile(this.packagePath);
}
protected _frontendModules: Map<string, string> | undefined;
protected _frontendPreloadModules: Map<string, string> | undefined;
protected _frontendElectronModules: Map<string, string> | undefined;
protected _secondaryWindowModules: Map<string, string> | undefined;
protected _backendModules: Map<string, string> | undefined;
protected _backendElectronModules: Map<string, string> | undefined;
protected _electronMainModules: Map<string, string> | undefined;
protected _preloadModules: Map<string, string> | undefined;
protected _extensionPackages: ReadonlyArray<ExtensionPackage> | undefined;
/**
* Extension packages in the topological order.
*/
get extensionPackages(): ReadonlyArray<ExtensionPackage> {
if (!this._extensionPackages) {
const collector = new ExtensionPackageCollector(
(raw: PublishedNodePackage, options: ExtensionPackageOptions = {}) => this.newExtensionPackage(raw, options),
this.resolveModule
);
this._extensionPackages = collector.collect(this.packagePath, this.pck);
}
return this._extensionPackages;
}
getExtensionPackage(extension: string): ExtensionPackage | undefined {
return this.extensionPackages.find(pck => pck.name === extension);
}
async findExtensionPackage(extension: string): Promise<ExtensionPackage | undefined> {
return this.getExtensionPackage(extension) || this.resolveExtensionPackage(extension);
}
/**
* Resolve an extension name to its associated package
* @param extension the name of the extension's package as defined in "dependencies" (might be aliased)
* @returns the extension package
*/
async resolveExtensionPackage(extension: string): Promise<ExtensionPackage | undefined> {
const raw = await RawExtensionPackage.view(this.registry, extension);
return raw ? this.newExtensionPackage(raw, { alias: extension }) : undefined;
}
protected newExtensionPackage(raw: PublishedNodePackage, options: ExtensionPackageOptions = {}): ExtensionPackage {
return new ExtensionPackage(raw, this.registry, options);
}
get frontendPreloadModules(): Map<string, string> {
return this._frontendPreloadModules ??= this.computeModules('frontendPreload');
}
get frontendOnlyPreloadModules(): Map<string, string> {
if (!this._frontendPreloadModules) {
this._frontendPreloadModules = this.computeModules('frontendOnlyPreload', 'frontendPreload');
}
return this._frontendPreloadModules;
}
get frontendModules(): Map<string, string> {
return this._frontendModules ??= this.computeModules('frontend');
}
get frontendOnlyModules(): Map<string, string> {
if (!this._frontendModules) {
this._frontendModules = this.computeModules('frontendOnly', 'frontend');
}
return this._frontendModules;
}
get frontendElectronModules(): Map<string, string> {
return this._frontendElectronModules ??= this.computeModules('frontendElectron', 'frontend');
}
get secondaryWindowModules(): Map<string, string> {
return this._secondaryWindowModules ??= this.computeModules('secondaryWindow');
}
get backendModules(): Map<string, string> {
return this._backendModules ??= this.computeModules('backend');
}
get backendElectronModules(): Map<string, string> {
return this._backendElectronModules ??= this.computeModules('backendElectron', 'backend');
}
get electronMainModules(): Map<string, string> {
return this._electronMainModules ??= this.computeModules('electronMain');
}
get preloadModules(): Map<string, string> {
return this._preloadModules ??= this.computeModules('preload');
}
protected computeModules<P extends keyof Extension, S extends keyof Extension = P>(primary: P, secondary?: S): Map<string, string> {
const result = new Map<string, string>();
let moduleIndex = 1;
for (const extensionPackage of this.extensionPackages) {
const extensions = extensionPackage.theiaExtensions;
if (extensions) {
for (const extension of extensions) {
const modulePath = extension[primary] || (secondary && extension[secondary]);
if (typeof modulePath === 'string') {
const extensionPath = paths.join(extensionPackage.name, modulePath).split(paths.sep).join('/');
result.set(`${primary}_${moduleIndex}`, extensionPath);
moduleIndex = moduleIndex + 1;
}
}
}
}
return result;
}
relative(path: string): string {
return paths.relative(this.projectPath, path);
}
path(...segments: string[]): string {
return paths.resolve(this.projectPath, ...segments);
}
get packagePath(): string {
return this.path('package.json');
}
lib(...segments: string[]): string {
return this.path('lib', ...segments);
}
srcGen(...segments: string[]): string {
return this.path('src-gen', ...segments);
}
backend(...segments: string[]): string {
return this.srcGen('backend', ...segments);
}
bundledBackend(...segments: string[]): string {
return this.path('backend', 'bundle', ...segments);
}
frontend(...segments: string[]): string {
return this.srcGen('frontend', ...segments);
}
isBrowser(): boolean {
return this.target === ApplicationProps.ApplicationTarget.browser;
}
isElectron(): boolean {
return this.target === ApplicationProps.ApplicationTarget.electron;
}
isBrowserOnly(): boolean {
return this.target === ApplicationProps.ApplicationTarget.browserOnly;
}
ifBrowser<T>(value: T): T | undefined;
ifBrowser<T>(value: T, defaultValue: T): T;
ifBrowser<T>(value: T, defaultValue?: T): T | undefined {
return this.isBrowser() ? value : defaultValue;
}
ifElectron<T>(value: T): T | undefined;
ifElectron<T>(value: T, defaultValue: T): T;
ifElectron<T>(value: T, defaultValue?: T): T | undefined {
return this.isElectron() ? value : defaultValue;
}
ifBrowserOnly<T>(value: T): T | undefined;
ifBrowserOnly<T>(value: T, defaultValue: T): T;
ifBrowserOnly<T>(value: T, defaultValue?: T): T | undefined {
return this.isBrowserOnly() ? value : defaultValue;
}
get targetBackendModules(): Map<string, string> {
if (this.isBrowserOnly()) {
return new Map();
}
return this.ifBrowser(this.backendModules, this.backendElectronModules);
}
get targetFrontendModules(): Map<string, string> {
if (this.isBrowserOnly()) {
return this.frontendOnlyModules;
}
return this.ifBrowser(this.frontendModules, this.frontendElectronModules);
}
get targetFrontendPreloadModules(): Map<string, string> {
if (this.isBrowserOnly()) {
return this.frontendOnlyPreloadModules;
}
return this.frontendPreloadModules;
}
get targetElectronMainModules(): Map<string, string> {
return this.ifElectron(this.electronMainModules, new Map());
}
setDependency(name: string, version: string | undefined): boolean {
const dependencies = this.pck.dependencies || {};
const currentVersion = dependencies[name];
if (currentVersion === version) {
return false;
}
if (version) {
dependencies[name] = version;
} else {
delete dependencies[name];
}
this.pck.dependencies = sortByKey(dependencies);
return true;
}
save(): Promise<void> {
return writeJsonFile(this.packagePath, this.pck, {
detectIndent: true
});
}
protected _moduleResolver: undefined | ApplicationModuleResolver;
/**
* A node module resolver in the context of the application package.
*/
get resolveModule(): ApplicationModuleResolver {
if (!this._moduleResolver) {
this._moduleResolver = (parentPackagePath, modulePath) => {
const resolved = resolvePackagePath(modulePath, parentPackagePath);
if (!resolved) {
throw new Error(`Cannot resolve package ${modulePath} relative to ${parentPackagePath}`);
}
return resolved;
};
}
return this._moduleResolver!;
}
}

View File

@@ -0,0 +1,314 @@
// *****************************************************************************
// Copyright (C) 2018 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 type { BrowserWindowConstructorOptions } from 'electron';
export import deepmerge = require('deepmerge');
export type RequiredRecursive<T> = {
[K in keyof T]-?: T[K] extends object ? RequiredRecursive<T[K]> : T[K]
};
/**
* Base configuration for the Theia application.
*/
export interface ApplicationConfig {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
readonly [key: string]: any;
}
export type ElectronFrontendApplicationConfig = RequiredRecursive<ElectronFrontendApplicationConfig.Partial>;
export namespace ElectronFrontendApplicationConfig {
export const DEFAULT: ElectronFrontendApplicationConfig = {
windowOptions: {},
showWindowEarly: true,
splashScreenOptions: {},
uriScheme: 'theia'
};
export interface SplashScreenOptions {
/**
* Initial width of the splash screen. Defaults to 640.
*/
width?: number;
/**
* Initial height of the splash screen. Defaults to 480.
*/
height?: number;
/**
* Minimum amount of time in milliseconds to show the splash screen before main window is shown.
* Defaults to 0, i.e. the splash screen will be shown until the frontend application is ready.
*/
minDuration?: number;
/**
* Maximum amount of time in milliseconds before splash screen is removed and main window is shown.
* Defaults to 30000.
*/
maxDuration?: number;
/**
* The content to load in the splash screen.
* Will be resolved from application root.
*
* Mandatory attribute.
*/
content?: string;
}
export interface Partial {
/**
* Override or add properties to the electron `windowOptions`.
*
* Defaults to `{}`.
*/
readonly windowOptions?: BrowserWindowConstructorOptions;
/**
* Whether or not to show an empty Electron window as early as possible.
*
* Defaults to `true`.
*/
readonly showWindowEarly?: boolean;
/**
* Configuration options for splash screen.
*
* Defaults to `{}` which results in no splash screen being displayed.
*/
readonly splashScreenOptions?: SplashScreenOptions;
/**
* The custom uri scheme the application registers to in the operating system.
*/
readonly uriScheme: string;
}
}
export type DefaultTheme = string | Readonly<{ light: string, dark: string }>;
export namespace DefaultTheme {
export function defaultForOSTheme(theme: DefaultTheme): string {
if (typeof theme === 'string') {
return theme;
}
if (
typeof window !== 'undefined' &&
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
return theme.dark;
}
return theme.light;
}
export function defaultBackgroundColor(dark?: boolean): string {
// The default light background color is based on the `colors#editor.background` value from
// `packages/monaco/data/monaco-themes/vscode/dark_vs.json` and the dark background comes from the `light_vs.json`.
return dark ? '#1E1E1E' : '#FFFFFF';
}
}
/**
* Application configuration for the frontend. The following properties will be injected into the `index.html`.
*/
export type FrontendApplicationConfig = RequiredRecursive<FrontendApplicationConfig.Partial>;
export namespace FrontendApplicationConfig {
export const DEFAULT: FrontendApplicationConfig = {
applicationName: 'Eclipse Theia',
defaultTheme: { light: 'light', dark: 'dark' },
defaultIconTheme: 'theia-file-icons',
electron: ElectronFrontendApplicationConfig.DEFAULT,
defaultLocale: '',
validatePreferencesSchema: true,
reloadOnReconnect: false,
uriScheme: 'theia'
};
export interface Partial extends ApplicationConfig {
/**
* The default theme for the application.
*
* Defaults to `dark` if the OS's theme is dark. Otherwise `light`.
*/
readonly defaultTheme?: DefaultTheme;
/**
* The default icon theme for the application.
*
* Defaults to `none`.
*/
readonly defaultIconTheme?: string;
/**
* The name of the application.
*
* Defaults to `Eclipse Theia`.
*/
readonly applicationName?: string;
/**
* Electron specific configuration.
*
* Defaults to `ElectronFrontendApplicationConfig.DEFAULT`.
*/
readonly electron?: ElectronFrontendApplicationConfig.Partial;
/**
* The default locale for the application.
*
* Defaults to "".
*/
readonly defaultLocale?: string;
/**
* When `true`, the application will validate the JSON schema of the preferences on start
* and log warnings to the console if the schema is not valid.
*
* Defaults to `true`.
*/
readonly validatePreferencesSchema?: boolean;
/**
* When 'true', the window will reload in case the front end reconnects to a back-end,
* but the back end does not have a connection context for this front end anymore.
*/
readonly reloadOnReconnect?: boolean;
}
}
/**
* Application configuration for the backend.
*/
export type BackendApplicationConfig = RequiredRecursive<BackendApplicationConfig.Partial>;
export namespace BackendApplicationConfig {
export const DEFAULT: BackendApplicationConfig = {
singleInstance: true,
frontendConnectionTimeout: 0,
configurationFolder: '.theia'
};
export interface Partial extends ApplicationConfig {
/**
* If true and in Electron mode, only one instance of the application is allowed to run at a time.
*
* Defaults to `false`.
*/
readonly singleInstance?: boolean;
/**
* The time in ms the connection context will be preserved for reconnection after a front end disconnects.
*/
readonly frontendConnectionTimeout?: number;
/**
* Configuration folder within the home user folder
*
* Defaults to `.theia`
*/
readonly configurationFolder?: string;
}
}
/**
* Configuration for the generator.
*/
export type GeneratorConfig = RequiredRecursive<GeneratorConfig.Partial>;
export namespace GeneratorConfig {
export const DEFAULT: GeneratorConfig = {
preloadTemplate: ''
};
export interface Partial {
/**
* Template to use for extra preload content markup (file path or HTML).
*
* Defaults to `''`.
*/
readonly preloadTemplate?: string;
}
}
export interface NpmRegistryProps {
/**
* Defaults to `false`.
*/
readonly next: boolean;
/**
* Defaults to `https://registry.npmjs.org/`.
*/
readonly registry: string;
}
export namespace NpmRegistryProps {
export const DEFAULT: NpmRegistryProps = {
next: false,
registry: 'https://registry.npmjs.org/'
};
}
/**
* Representation of all backend and frontend related Theia extension and application properties.
*/
export interface ApplicationProps extends NpmRegistryProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
readonly [key: string]: any;
/**
* Whether the extension targets the browser or electron. Defaults to `browser`.
*/
readonly target: ApplicationProps.Target;
/**
* Frontend related properties.
*/
readonly frontend: {
readonly config: FrontendApplicationConfig
};
/**
* Backend specific properties.
*/
readonly backend: {
readonly config: BackendApplicationConfig
};
/**
* Generator specific properties.
*/
readonly generator: {
readonly config: GeneratorConfig
};
}
export namespace ApplicationProps {
export type Target = `${ApplicationTarget}`;
export enum ApplicationTarget {
browser = 'browser',
electron = 'electron',
browserOnly = 'browser-only'
};
export const DEFAULT: ApplicationProps = {
...NpmRegistryProps.DEFAULT,
target: 'browser',
backend: {
config: BackendApplicationConfig.DEFAULT
},
frontend: {
config: FrontendApplicationConfig.DEFAULT
},
generator: {
config: GeneratorConfig.DEFAULT
}
};
}

View File

@@ -0,0 +1,76 @@
// *****************************************************************************
// Copyright (C) 2018 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
// *****************************************************************************
const isElectron: () => boolean = require('is-electron');
/**
* The electron specific environment.
*/
class ElectronEnv {
/**
* Environment variable that can be accessed on the `process` to check if running in electron or not.
*/
readonly THEIA_ELECTRON_VERSION = 'THEIA_ELECTRON_VERSION';
/**
* `true` if running in electron. Otherwise, `false`.
*
* Can be called from both the `main` and the render process. Also works for forked cluster workers.
*/
is(): boolean {
return isElectron();
}
/**
* `true` if running in Electron in development mode. Otherwise, `false`.
*
* Cannot be used from the browser. From the browser, it is always `false`.
*/
isDevMode(): boolean {
return this.is()
&& typeof process !== 'undefined'
// `defaultApp` does not exist on the Node.js API, but on electron (`electron.d.ts`).
&& ((process as any).defaultApp || /node_modules[/\\]electron[/\\]/.test(process.execPath)); // eslint-disable-line @typescript-eslint/no-explicit-any
}
/**
* Creates and return with a new environment object which always contains the `ELECTRON_RUN_AS_NODE: 1` property pair.
* This should be used to `spawn` and `fork` a new Node.js process from the Node.js shipped with Electron. Otherwise, a new Electron
* process will be spawned which [has side-effects](https://github.com/eclipse-theia/theia/issues/5385).
*
* If called from the backend and the `env` argument is not defined, it falls back to `process.env` such as Node.js behaves
* with the [`SpawnOptions`](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options).
* If `env` is defined, it will be shallow-copied.
*
* Calling this function from the frontend does not make any sense, hence throw an error.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
runAsNodeEnv(env?: any): any & { ELECTRON_RUN_AS_NODE: 1 } {
if (typeof process === 'undefined') {
throw new Error("'process' cannot be undefined.");
}
return {
...(env === undefined ? process.env : env),
ELECTRON_RUN_AS_NODE: 1
};
}
}
const electron = new ElectronEnv();
const environment: Readonly<{ electron: ElectronEnv }> = { electron };
export { environment };

View File

@@ -0,0 +1,88 @@
// *****************************************************************************
// 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 { readJsonFile } from './json-file';
import { NodePackage, PublishedNodePackage } from './npm-registry';
import { ExtensionPackage, ExtensionPackageOptions, RawExtensionPackage } from './extension-package';
export class ExtensionPackageCollector {
protected readonly sorted: ExtensionPackage[] = [];
protected readonly visited = new Set<string>();
constructor(
protected readonly extensionPackageFactory: (raw: PublishedNodePackage, options?: ExtensionPackageOptions) => ExtensionPackage,
protected readonly resolveModule: (packagepath: string, modulePath: string) => string
) { }
protected root: NodePackage;
collect(packagePath: string, pck: NodePackage): ReadonlyArray<ExtensionPackage> {
this.root = pck;
this.collectPackages(packagePath, pck);
return this.sorted;
}
protected collectPackages(packagePath: string, pck: NodePackage): void {
for (const [dependency, versionRange] of [
...Object.entries(pck.dependencies ?? {}),
...Object.entries(pck.peerDependencies ?? {})
]) {
const optional = pck.peerDependenciesMeta?.[dependency]?.optional || false;
this.collectPackage(packagePath, dependency, versionRange!, optional);
}
}
protected parent: ExtensionPackage | undefined;
protected collectPackagesWithParent(packagePath: string, pck: NodePackage, parent: ExtensionPackage): void {
const current = this.parent;
this.parent = parent;
this.collectPackages(packagePath, pck);
this.parent = current;
}
protected collectPackage(parentPackagePath: string, name: string, versionRange: string, optional: boolean): void {
if (this.visited.has(name)) {
return;
}
this.visited.add(name);
let packagePath: string | undefined;
try {
packagePath = this.resolveModule(parentPackagePath, name);
} catch (err) {
if (optional) {
console.log(`Could not resolve optional peer dependency '${name}'. Skipping...`);
} else {
console.error(err.message);
}
}
if (!packagePath) {
return;
}
const pck: NodePackage = readJsonFile(packagePath);
if (RawExtensionPackage.is(pck)) {
const parent = this.parent;
const version = pck.version;
const transitive = !(name in this.root.dependencies!);
pck.installed = { packagePath, version, parent, transitive };
pck.version = versionRange;
const extensionPackage = this.extensionPackageFactory(pck, { alias: name });
this.collectPackagesWithParent(packagePath, pck, extensionPackage);
this.sorted.push(extensionPackage);
}
}
}

View File

@@ -0,0 +1,223 @@
// *****************************************************************************
// 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 * as paths from 'path';
import * as semver from 'semver';
import { NpmRegistry, PublishedNodePackage, NodePackage } from './npm-registry';
export interface Extension {
frontendPreload?: string;
frontendOnlyPreload?: string;
frontend?: string;
frontendOnly?: string;
frontendElectron?: string;
secondaryWindow?: string;
backend?: string;
backendElectron?: string;
electronMain?: string;
preload?: string;
}
export interface ExtensionPackageOptions {
/**
* Alias to use in place of the original package's name.
*/
alias?: string
}
export class ExtensionPackage {
protected _name: string;
constructor(
readonly raw: PublishedNodePackage & Partial<RawExtensionPackage>,
protected readonly registry: NpmRegistry,
options: ExtensionPackageOptions = {}
) {
this._name = options.alias ?? raw.name;
}
/**
* The name of the extension's package as defined in "dependencies" (might be aliased)
*/
get name(): string {
return this._name;
}
get version(): string {
if (this.raw.installed) {
return this.raw.installed.version;
}
if (this.raw.view) {
const latestVersion = this.raw.view.latestVersion;
if (latestVersion) {
return latestVersion;
}
}
return this.raw.version;
}
get description(): string {
return this.raw.description || '';
}
get theiaExtensions(): Extension[] {
return this.raw.theiaExtensions || [];
}
get installed(): boolean {
return !!this.raw.installed;
}
get dependent(): string | undefined {
if (!this.transitive) {
return undefined;
}
let current = this.parent!;
let parent = current.parent;
while (parent !== undefined) {
current = parent;
parent = current.parent;
}
return current.name;
}
get transitive(): boolean {
return !!this.raw.installed && this.raw.installed.transitive;
}
get parent(): ExtensionPackage | undefined {
if (this.raw.installed) {
return this.raw.installed.parent;
}
return undefined;
}
protected async view(): Promise<RawExtensionPackage.ViewState> {
if (this.raw.view === undefined) {
const raw = await RawExtensionPackage.view(this.registry, this.name, this.version);
this.raw.view = raw ? raw.view : new RawExtensionPackage.ViewState(this.registry);
}
return this.raw.view!;
}
protected readme?: string;
async getReadme(): Promise<string> {
if (this.readme === undefined) {
this.readme = await this.resolveReadme();
}
return this.readme;
}
protected async resolveReadme(): Promise<string> {
const raw = await this.view();
if (raw && raw.readme) {
return raw.readme;
}
if (this.raw.installed) {
const readmePath = paths.resolve(this.raw.installed.packagePath, '..', 'README.md');
if (await fs.pathExists(readmePath)) {
return fs.readFile(readmePath, { encoding: 'utf8' });
}
return '';
}
return '';
}
getAuthor(): string {
if (this.raw.publisher) {
return this.raw.publisher.username;
}
if (typeof this.raw.author === 'string') {
return this.raw.author;
}
if (this.raw.author && this.raw.author.name) {
return this.raw.author.name;
}
if (!!this.raw.maintainers && this.raw.maintainers.length > 0) {
return this.raw.maintainers[0].username;
}
return '';
}
}
export interface RawExtensionPackage extends PublishedNodePackage {
installed?: RawExtensionPackage.InstalledState
view?: RawExtensionPackage.ViewState
theiaExtensions: Extension[];
}
export namespace RawExtensionPackage {
export interface InstalledState {
version: string;
packagePath: string;
transitive: boolean;
parent?: ExtensionPackage;
}
export class ViewState {
readme?: string;
tags?: {
[tag: string]: string
};
constructor(
protected readonly registry: NpmRegistry
) { }
get latestVersion(): string | undefined {
if (this.tags) {
if (this.registry.props.next) {
const next = this.tags['next'];
if (next !== undefined) {
return next;
}
}
const latest = this.tags['latest'];
if (this.registry.props.next || !semver.prerelease(latest)) {
return latest;
}
return undefined;
}
return undefined;
}
}
export function is(pck: NodePackage | undefined): pck is RawExtensionPackage {
return PublishedNodePackage.is(pck) && !!pck.theiaExtensions;
}
export async function view(registry: NpmRegistry, name: string, version?: string): Promise<RawExtensionPackage | undefined> {
const result = await registry.view(name).catch(() => undefined);
if (!result) {
return undefined;
}
const tags = result['dist-tags'];
const versions = [tags['latest']];
if (registry.props.next) {
versions.push(tags['next']);
}
if (version) {
versions.push(tags[version], version);
}
for (const current of versions.reverse()) {
const raw = result.versions[current];
if (is(raw)) {
const viewState = new ViewState(registry);
viewState.readme = result.readme;
viewState.tags = tags;
raw.view = viewState;
return raw;
}
}
return undefined;
}
}

View File

@@ -0,0 +1,22 @@
// *****************************************************************************
// 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 './npm-registry';
export * from './extension-package';
export * from './application-package';
export * from './application-props';
export * from './environment';
export * from './api';

View File

@@ -0,0 +1,25 @@
// *****************************************************************************
// 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';
import writeJsonFile = require('write-json-file');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function readJsonFile(path: string): any {
return JSON.parse(fs.readFileSync(path, { encoding: 'utf-8' }));
}
export { writeJsonFile, readJsonFile };

View File

@@ -0,0 +1,166 @@
// *****************************************************************************
// 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/no-explicit-any */
import * as nano from 'nano';
import { RequestContext } from '@theia/request';
import { NodeRequestService } from '@theia/request/lib/node-request-service';
import { NpmRegistryProps } from './application-props';
export interface IChangeStream {
on(event: 'data', cb: (change: { id: string }) => void): void;
destroy(): void;
}
export interface Author {
name: string;
email: string;
}
export interface Maintainer {
username: string;
email: string;
}
export interface Dependencies {
[name: string]: string | undefined;
}
export interface PeerDependenciesMeta {
[name: string]: { optional: boolean } | undefined;
}
export interface NodePackage {
name?: string;
version?: string;
description?: string;
publisher?: Maintainer;
author?: string | Author;
maintainers?: Maintainer[];
keywords?: string[];
dependencies?: Dependencies;
peerDependencies?: Dependencies;
peerDependenciesMeta?: PeerDependenciesMeta;
[property: string]: any;
}
export interface PublishedNodePackage extends NodePackage {
name: string;
version: string;
}
export namespace PublishedNodePackage {
export function is(pck: NodePackage | undefined): pck is PublishedNodePackage {
return !!pck && !!pck.name && !!pck.version;
}
}
export interface ViewResult {
'dist-tags': {
[tag: string]: string
}
'versions': {
[version: string]: NodePackage
},
'readme': string;
[key: string]: any
}
export function sortByKey(object: { [key: string]: any }): {
[key: string]: any;
} {
return Object.keys(object).sort().reduce((sorted, key) => {
sorted[key] = object[key];
return sorted;
}, {} as { [key: string]: any });
}
export class NpmRegistryOptions {
/**
* Default: false.
*/
readonly watchChanges: boolean;
}
export class NpmRegistry {
readonly props: NpmRegistryProps = { ...NpmRegistryProps.DEFAULT };
protected readonly options: NpmRegistryOptions;
protected changes?: nano.ChangesReaderScope;
protected readonly index = new Map<string, Promise<ViewResult>>();
protected request: NodeRequestService;
constructor(options?: Partial<NpmRegistryOptions>) {
this.options = {
watchChanges: false,
...options
};
this.resetIndex();
this.request = new NodeRequestService();
}
updateProps(props?: Partial<NpmRegistryProps>): void {
const oldRegistry = this.props.registry;
Object.assign(this.props, props);
const newRegistry = this.props.registry;
if (oldRegistry !== newRegistry) {
this.resetIndex();
}
}
protected resetIndex(): void {
this.index.clear();
if (this.options.watchChanges && this.props.registry === NpmRegistryProps.DEFAULT.registry) {
if (this.changes) {
this.changes.stop();
}
// Invalidate index with NPM registry web hooks
this.changes = nano('https://replicate.npmjs.com').use('registry').changesReader;
this.changes.get({}).on('change', change => this.invalidate(change.id));
}
}
protected invalidate(name: string): void {
if (this.index.delete(name)) {
this.view(name);
}
}
view(name: string): Promise<ViewResult> {
const indexed = this.index.get(name);
if (indexed) {
return indexed;
}
const result = this.doView(name);
this.index.set(name, result);
result.catch(() => this.index.delete(name));
return result;
}
protected async doView(name: string): Promise<ViewResult> {
let url = this.props.registry;
if (name[0] === '@') {
url += '@' + encodeURIComponent(name.substring(1));
} else {
url += encodeURIComponent(name);
}
const response = await this.request.request({ url });
if (response.res.statusCode !== 200) {
throw new Error(`HTTP ${response.res.statusCode}: for ${url}`);
}
return RequestContext.asJson<ViewResult>(response);
}
}

View File

@@ -0,0 +1,16 @@
{
"extends": "../../configs/base.tsconfig",
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "lib"
},
"include": [
"src"
],
"references": [
{
"path": "../request"
}
]
}