deploy: current vibn theia state
Made-with: Cursor
This commit is contained in:
10
dev-packages/application-package/.eslintrc.js
Normal file
10
dev-packages/application-package/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
extends: [
|
||||
'../../configs/build.eslintrc.json'
|
||||
],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: 'tsconfig.json'
|
||||
}
|
||||
};
|
||||
26
dev-packages/application-package/README.md
Normal file
26
dev-packages/application-package/README.md
Normal 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>
|
||||
52
dev-packages/application-package/package.json
Normal file
52
dev-packages/application-package/package.json
Normal 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"
|
||||
}
|
||||
21
dev-packages/application-package/src/api.ts
Normal file
21
dev-packages/application-package/src/api.ts
Normal 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';
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
328
dev-packages/application-package/src/application-package.ts
Normal file
328
dev-packages/application-package/src/application-package.ts
Normal 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!;
|
||||
}
|
||||
}
|
||||
314
dev-packages/application-package/src/application-props.ts
Normal file
314
dev-packages/application-package/src/application-props.ts
Normal 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
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
76
dev-packages/application-package/src/environment.ts
Normal file
76
dev-packages/application-package/src/environment.ts
Normal 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 };
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
223
dev-packages/application-package/src/extension-package.ts
Normal file
223
dev-packages/application-package/src/extension-package.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
22
dev-packages/application-package/src/index.ts
Normal file
22
dev-packages/application-package/src/index.ts
Normal 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';
|
||||
25
dev-packages/application-package/src/json-file.ts
Normal file
25
dev-packages/application-package/src/json-file.ts
Normal 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 };
|
||||
166
dev-packages/application-package/src/npm-registry.ts
Normal file
166
dev-packages/application-package/src/npm-registry.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
16
dev-packages/application-package/tsconfig.json
Normal file
16
dev-packages/application-package/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../configs/base.tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../request"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user