feat: apply patch after setting up subtree
This commit is contained in:
parent
41bee49d07
commit
51a2a2ad2d
@ -14,3 +14,5 @@
|
||||
**/extensions/**/build/**
|
||||
**/extensions/markdown-language-features/media/**
|
||||
**/extensions/typescript-basics/test/colorize-fixtures/**
|
||||
# This is a code-server code symlink.
|
||||
src/vs/base/node/proxy_agent.ts
|
||||
|
@ -64,7 +64,7 @@
|
||||
"code-no-standalone-editor": "warn",
|
||||
"code-no-unexternalized-strings": "warn",
|
||||
"code-layering": [
|
||||
"warn",
|
||||
"off",
|
||||
{
|
||||
"common": [],
|
||||
"node": [
|
||||
@ -90,7 +90,7 @@
|
||||
}
|
||||
],
|
||||
"code-import-patterns": [
|
||||
"warn",
|
||||
"off",
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// !!! Do not relax these rules !!!
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
1
lib/vscode/.gitignore
vendored
1
lib/vscode/.gitignore
vendored
@ -25,7 +25,6 @@ out-vscode-reh-web-pkg/
|
||||
out-vscode-web/
|
||||
out-vscode-web-min/
|
||||
out-vscode-web-pkg/
|
||||
src/vs/server
|
||||
resources/server
|
||||
build/node_modules
|
||||
coverage/
|
||||
|
@ -1,3 +0,0 @@
|
||||
disturl "https://electronjs.org/headers"
|
||||
target "9.3.3"
|
||||
runtime "electron"
|
@ -44,6 +44,7 @@ BUILD_TARGETS.forEach(({ platform, arch }) => {
|
||||
});
|
||||
|
||||
function getNodeVersion() {
|
||||
return process.versions.node;
|
||||
const yarnrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.yarnrc'), 'utf8');
|
||||
const target = /^target "(.*)"$/m.exec(yarnrc)[1];
|
||||
return target;
|
||||
|
@ -70,7 +70,7 @@ function fromLocal(extensionPath: string, forWeb: boolean): Stream {
|
||||
if (isWebPacked) {
|
||||
input = updateExtensionPackageJSON(input, (data: any) => {
|
||||
delete data.scripts;
|
||||
delete data.dependencies;
|
||||
// https://github.com/cdr/code-server/pull/2041#issuecomment-685910322
|
||||
delete data.devDependencies;
|
||||
if (data.main) {
|
||||
data.main = data.main.replace('/out/', /dist/);
|
||||
|
@ -4,12 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const root = path.dirname(path.dirname(__dirname));
|
||||
const yarnrcPath = path.join(root, 'remote', '.yarnrc');
|
||||
const yarnrc = fs.readFileSync(yarnrcPath, 'utf8');
|
||||
const version = /^target\s+"([^"]+)"$/m.exec(yarnrc)![1];
|
||||
const version = process.versions.node;
|
||||
const node = process.platform === 'win32' ? 'node.exe' : 'node';
|
||||
const nodePath = path.join(root, '.build', 'node', `v${version}`, `${process.platform}-${process.arch}`, node);
|
||||
|
||||
|
@ -336,6 +336,7 @@ export function streamToPromise(stream: NodeJS.ReadWriteStream): Promise<void> {
|
||||
}
|
||||
|
||||
export function getElectronVersion(): string {
|
||||
return process.versions.node;
|
||||
const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8');
|
||||
const target = /^target "(.*)"$/m.exec(yarnrc)![1];
|
||||
return target;
|
||||
|
@ -33,10 +33,11 @@ function yarnInstall(location, opts) {
|
||||
|
||||
yarnInstall('extensions'); // node modules shared by all extensions
|
||||
|
||||
if (!(process.platform === 'win32' && (process.arch === 'arm64' || process.env['npm_config_arch'] === 'arm64'))) {
|
||||
yarnInstall('remote'); // node modules used by vscode server
|
||||
yarnInstall('remote/web'); // node modules used by vscode web
|
||||
}
|
||||
// NOTE@coder: Skip these dependencies since we don't use them.
|
||||
// if (!(process.platform === 'win32' && (process.arch === 'arm64' || process.env['npm_config_arch'] === 'arm64'))) {
|
||||
// yarnInstall('remote'); // node modules used by vscode server
|
||||
// yarnInstall('remote/web'); // node modules used by vscode web
|
||||
// }
|
||||
|
||||
const allExtensionFolders = fs.readdirSync('extensions');
|
||||
const extensions = allExtensionFolders.filter(e => {
|
||||
@ -69,9 +70,9 @@ runtime "${runtime}"`;
|
||||
}
|
||||
|
||||
yarnInstall(`build`); // node modules required for build
|
||||
yarnInstall('test/automation'); // node modules required for smoketest
|
||||
yarnInstall('test/smoke'); // node modules required for smoketest
|
||||
yarnInstall('test/integration/browser'); // node modules required for integration
|
||||
// yarnInstall('test/automation'); // node modules required for smoketest
|
||||
// yarnInstall('test/smoke'); // node modules required for smoketest
|
||||
// yarnInstall('test/integration/browser'); // node modules required for integration
|
||||
yarnInstallBuildDependencies(); // node modules for watching, specific to host node version, not electron
|
||||
|
||||
cp.execSync('git config pull.rebase true');
|
||||
|
@ -8,8 +8,9 @@ let err = false;
|
||||
const majorNodeVersion = parseInt(/^(\d+)\./.exec(process.versions.node)[1]);
|
||||
|
||||
if (majorNodeVersion < 10 || majorNodeVersion >= 13) {
|
||||
console.error('\033[1;31m*** Please use node >=10 and <=12.\033[0;0m');
|
||||
err = true;
|
||||
// We are ok building above Node 12.
|
||||
// console.error('\033[1;31m*** Please use node >=10 and <=12.\033[0;0m');
|
||||
// err = true;
|
||||
}
|
||||
|
||||
const cp = require('child_process');
|
||||
|
64
lib/vscode/coder.js
Normal file
64
lib/vscode/coder.js
Normal file
@ -0,0 +1,64 @@
|
||||
// This must be ran from VS Code's root.
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const _ = require("underscore");
|
||||
const buildfile = require("./src/buildfile");
|
||||
const common = require("./build/lib/optimize");
|
||||
const util = require("./build/lib/util");
|
||||
const deps = require("./build/dependencies");
|
||||
|
||||
const vscodeEntryPoints = _.flatten([
|
||||
buildfile.entrypoint("vs/workbench/workbench.web.api"),
|
||||
buildfile.entrypoint("vs/server/entry"),
|
||||
buildfile.base,
|
||||
buildfile.workbenchWeb,
|
||||
buildfile.workerExtensionHost,
|
||||
buildfile.workerNotebook,
|
||||
buildfile.keyboardMaps,
|
||||
buildfile.entrypoint("vs/platform/files/node/watcher/unix/watcherApp"),
|
||||
buildfile.entrypoint("vs/platform/files/node/watcher/nsfw/watcherApp"),
|
||||
buildfile.entrypoint("vs/workbench/services/extensions/node/extensionHostProcess"),
|
||||
]);
|
||||
|
||||
const vscodeResources = [
|
||||
"out-build/vs/server/fork.js",
|
||||
"!out-build/vs/server/doc/**",
|
||||
"out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js",
|
||||
"out-build/bootstrap.js",
|
||||
"out-build/bootstrap-fork.js",
|
||||
"out-build/bootstrap-amd.js",
|
||||
'out-build/bootstrap-node.js',
|
||||
"out-build/paths.js",
|
||||
'out-build/vs/**/*.{svg,png,html,ttf}',
|
||||
"!out-build/vs/code/browser/workbench/*.html",
|
||||
'!out-build/vs/code/electron-browser/**',
|
||||
"out-build/vs/base/common/performance.js",
|
||||
"out-build/vs/base/node/languagePacks.js",
|
||||
'out-build/vs/base/browser/ui/codicons/codicon/**',
|
||||
"out-build/vs/workbench/browser/media/*-theme.css",
|
||||
"out-build/vs/workbench/contrib/debug/**/*.json",
|
||||
"out-build/vs/workbench/contrib/externalTerminal/**/*.scpt",
|
||||
"out-build/vs/workbench/contrib/webview/browser/pre/*.js",
|
||||
"out-build/vs/**/markdown.css",
|
||||
"out-build/vs/workbench/contrib/tasks/**/*.json",
|
||||
"out-build/vs/platform/files/**/*.md",
|
||||
"!**/test/**"
|
||||
];
|
||||
|
||||
gulp.task("optimize", gulp.series(
|
||||
util.rimraf("out-vscode"),
|
||||
common.optimizeTask({
|
||||
src: "out-build",
|
||||
entryPoints: vscodeEntryPoints,
|
||||
resources: vscodeResources,
|
||||
loaderConfig: common.loaderConfig(),
|
||||
out: "out-vscode",
|
||||
inlineAmdImages: true,
|
||||
bundleInfo: undefined
|
||||
}),
|
||||
));
|
||||
|
||||
gulp.task("minify", gulp.series(
|
||||
util.rimraf("out-vscode-min"),
|
||||
common.minifyTask("out-vscode")
|
||||
));
|
@ -24,6 +24,9 @@ function processRoot() {
|
||||
rimraf.sync(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete .bin so it doesn't contain broken symlinks that trip up nfpm.
|
||||
rimraf.sync(path.join(__dirname, 'node_modules', '.bin'));
|
||||
}
|
||||
|
||||
function processLib() {
|
||||
|
@ -6,6 +6,6 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function isWeb(): boolean {
|
||||
// @ts-expect-error
|
||||
// NOTE@coder: Remove unused ts-expect-error directive which causes tsc to error.
|
||||
return typeof navigator !== 'undefined' && vscode.env.uiKind === vscode.UIKind.Web;
|
||||
}
|
||||
|
@ -46,7 +46,11 @@
|
||||
"watch-web": "gulp watch-web --max_old_space_size=4095",
|
||||
"eslint": "eslint -c .eslintrc.json --rulesdir ./build/lib/eslint --ext .ts --ext .js ./src/vs ./extensions"
|
||||
},
|
||||
"dependencies_comment": "Move rimraf to dependencies because it is used in the postinstall script.",
|
||||
"dependencies": {
|
||||
"@coder/logger": "1.1.16",
|
||||
"@coder/node-browser": "^1.0.8",
|
||||
"@coder/requirefs": "^1.1.5",
|
||||
"applicationinsights": "1.0.8",
|
||||
"chokidar": "3.4.3",
|
||||
"graceful-fs": "4.2.3",
|
||||
@ -60,6 +64,8 @@
|
||||
"native-keymap": "2.2.0",
|
||||
"native-watchdog": "1.3.0",
|
||||
"node-pty": "0.10.0-beta17",
|
||||
"proxy-agent": "^4.0.0",
|
||||
"rimraf": "^2.2.8",
|
||||
"spdlog": "^0.11.1",
|
||||
"sudo-prompt": "9.1.1",
|
||||
"tas-client-umd": "0.1.2",
|
||||
@ -161,7 +167,6 @@
|
||||
"pump": "^1.0.1",
|
||||
"queue": "3.0.6",
|
||||
"rcedit": "^1.1.0",
|
||||
"rimraf": "^2.2.8",
|
||||
"sinon": "^1.17.2",
|
||||
"source-map": "^0.4.4",
|
||||
"style-loader": "^1.0.0",
|
||||
@ -193,5 +198,8 @@
|
||||
"windows-foreground-love": "0.2.0",
|
||||
"windows-mutex": "0.3.0",
|
||||
"windows-process-tree": "0.2.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"nameShort": "Code - OSS",
|
||||
"nameLong": "Code - OSS",
|
||||
"nameShort": "code-server",
|
||||
"nameLong": "code-server",
|
||||
"applicationName": "code-oss",
|
||||
"dataFolderName": ".vscode-oss",
|
||||
"win32MutexName": "vscodeoss",
|
||||
@ -20,7 +20,7 @@
|
||||
"darwinBundleIdentifier": "com.visualstudio.code.oss",
|
||||
"linuxIconName": "com.visualstudio.code.oss",
|
||||
"licenseFileName": "LICENSE.txt",
|
||||
"reportIssueUrl": "https://github.com/microsoft/vscode/issues/new",
|
||||
"reportIssueUrl": "https://github.com/cdr/code-server/issues/new",
|
||||
"urlProtocol": "code-oss",
|
||||
"extensionAllowedProposedApi": [
|
||||
"ms-vscode.vscode-js-profile-flame",
|
||||
@ -136,5 +136,14 @@
|
||||
"publisherDisplayName": "Microsoft"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
"//": "https://github.com/VSCodium/vscodium/pull/155/files",
|
||||
"documentationUrl": "https://go.microsoft.com/fwlink/?LinkID=533484#vscode",
|
||||
"keyboardShortcutsUrlMac": "https://go.microsoft.com/fwlink/?linkid=832143",
|
||||
"keyboardShortcutsUrlLinux": "https://go.microsoft.com/fwlink/?linkid=832144",
|
||||
"keyboardShortcutsUrlWin": "https://go.microsoft.com/fwlink/?linkid=832145",
|
||||
"introductoryVideosUrl": "https://go.microsoft.com/fwlink/?linkid=832146",
|
||||
"tipsAndTricksUrl": "https://go.microsoft.com/fwlink/?linkid=852118",
|
||||
"newsletterSignupUrl": "https://www.research.net/r/vsc-newsletter"
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
disturl "http://nodejs.org/dist"
|
||||
target "12.14.1"
|
||||
runtime "node"
|
@ -113,16 +113,17 @@ class RemoteAuthoritiesImpl {
|
||||
if (host && host.indexOf(':') !== -1) {
|
||||
host = `[${host}]`;
|
||||
}
|
||||
const port = this._ports[authority];
|
||||
// const port = this._ports[authority];
|
||||
const connectionToken = this._connectionTokens[authority];
|
||||
let query = `path=${encodeURIComponent(uri.path)}`;
|
||||
if (typeof connectionToken === 'string') {
|
||||
query += `&tkn=${encodeURIComponent(connectionToken)}`;
|
||||
}
|
||||
// NOTE@coder: Changed this to work against the current path.
|
||||
return URI.from({
|
||||
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
|
||||
authority: `${host}:${port}`,
|
||||
path: `/vscode-remote-resource`,
|
||||
authority: window.location.host,
|
||||
path: `${window.location.pathname.replace(/\/+$/, '')}/vscode-remote-resource`,
|
||||
query
|
||||
});
|
||||
}
|
||||
|
@ -71,6 +71,18 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
|
||||
_isWeb = true;
|
||||
_locale = navigator.language;
|
||||
_language = _locale;
|
||||
|
||||
// NOTE@coder: Make languages work.
|
||||
const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
|
||||
const rawNlsConfig = el && el.getAttribute('data-settings');
|
||||
if (rawNlsConfig) {
|
||||
try {
|
||||
const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
|
||||
_locale = nlsConfig.locale;
|
||||
_translationsConfigFile = nlsConfig._translationsConfigFile;
|
||||
_language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT;
|
||||
} catch (error) { /* Oh well. */ }
|
||||
}
|
||||
}
|
||||
|
||||
// Native environment
|
||||
|
@ -112,6 +112,7 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve
|
||||
/^VSCODE_.+$/,
|
||||
/^SNAP(|_.*)$/,
|
||||
/^GDK_PIXBUF_.+$/,
|
||||
/^CODE_SERVER_.+$/,
|
||||
];
|
||||
const envKeys = Object.keys(env);
|
||||
envKeys
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { MarshalledObject } from 'vs/base/common/marshalling';
|
||||
import { Schemas } from './network';
|
||||
|
||||
export interface IURITransformer {
|
||||
transformIncoming(uri: UriComponents): UriComponents;
|
||||
@ -31,29 +32,35 @@ function toJSON(uri: URI): UriComponents {
|
||||
|
||||
export class URITransformer implements IURITransformer {
|
||||
|
||||
private readonly _uriTransformer: IRawURITransformer;
|
||||
|
||||
constructor(uriTransformer: IRawURITransformer) {
|
||||
this._uriTransformer = uriTransformer;
|
||||
constructor(private readonly remoteAuthority: string) {
|
||||
}
|
||||
|
||||
// NOTE@coder: Coming in from the browser it'll be vscode-remote so it needs
|
||||
// to be transformed into file.
|
||||
public transformIncoming(uri: UriComponents): UriComponents {
|
||||
const result = this._uriTransformer.transformIncoming(uri);
|
||||
return (result === uri ? uri : toJSON(URI.from(result)));
|
||||
return uri.scheme === Schemas.vscodeRemote
|
||||
? toJSON(URI.file(uri.path))
|
||||
: uri;
|
||||
}
|
||||
|
||||
// NOTE@coder: Going out to the browser it'll be file so it needs to be
|
||||
// transformed into vscode-remote.
|
||||
public transformOutgoing(uri: UriComponents): UriComponents {
|
||||
const result = this._uriTransformer.transformOutgoing(uri);
|
||||
return (result === uri ? uri : toJSON(URI.from(result)));
|
||||
return uri.scheme === Schemas.file
|
||||
? toJSON(URI.from({ authority: this.remoteAuthority, scheme: Schemas.vscodeRemote, path: uri.path }))
|
||||
: uri;
|
||||
}
|
||||
|
||||
public transformOutgoingURI(uri: URI): URI {
|
||||
const result = this._uriTransformer.transformOutgoing(uri);
|
||||
return (result === uri ? uri : URI.from(result));
|
||||
return uri.scheme === Schemas.file
|
||||
? URI.from({ authority: this.remoteAuthority, scheme: Schemas.vscodeRemote, path:uri.path })
|
||||
: uri;
|
||||
}
|
||||
|
||||
public transformOutgoingScheme(scheme: string): string {
|
||||
return this._uriTransformer.transformOutgoingScheme(scheme);
|
||||
return scheme === Schemas.file
|
||||
? Schemas.vscodeRemote
|
||||
: scheme;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,10 @@ function factory(nodeRequire, path, fs, perf) {
|
||||
function getLanguagePackConfigurations(userDataPath) {
|
||||
const configFile = path.join(userDataPath, 'languagepacks.json');
|
||||
try {
|
||||
return nodeRequire(configFile);
|
||||
// NOTE@coder: Swapped require with readFile since require is cached and
|
||||
// we don't restart the server-side portion of code-server when the
|
||||
// language changes.
|
||||
return JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
||||
} catch (err) {
|
||||
// Do nothing. If we can't read the file we have no
|
||||
// language pack config.
|
||||
|
1
lib/vscode/src/vs/base/node/proxy_agent.ts
Symbolic link
1
lib/vscode/src/vs/base/node/proxy_agent.ts
Symbolic link
@ -0,0 +1 @@
|
||||
../../../../../../src/node/proxy_agent.ts
|
@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchConstructionOptions, create, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IWindowIndicator, IHomeIndicator, IProductQualityChangeHandler, ISettingsSyncOptions } from 'vs/workbench/workbench.web.api';
|
||||
import { IWorkbenchConstructionOptions, create, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IWindowIndicator, IProductQualityChangeHandler, ISettingsSyncOptions } from 'vs/workbench/workbench.web.api';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
@ -17,6 +17,7 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { encodePath } from 'vs/server/node/util';
|
||||
|
||||
function doCreateUri(path: string, queryValues: Map<string, string>): URI {
|
||||
let query: string | undefined = undefined;
|
||||
@ -309,12 +310,18 @@ class WorkspaceProvider implements IWorkspaceProvider {
|
||||
|
||||
// Folder
|
||||
else if (isFolderToOpen(workspace)) {
|
||||
targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${encodeURIComponent(workspace.folderUri.toString())}`;
|
||||
const target = workspace.folderUri.scheme === Schemas.vscodeRemote
|
||||
? encodePath(workspace.folderUri.path)
|
||||
: encodeURIComponent(workspace.folderUri.toString());
|
||||
targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${target}`;
|
||||
}
|
||||
|
||||
// Workspace
|
||||
else if (isWorkspaceToOpen(workspace)) {
|
||||
targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${encodeURIComponent(workspace.workspaceUri.toString())}`;
|
||||
const target = workspace.workspaceUri.scheme === Schemas.vscodeRemote
|
||||
? encodePath(workspace.workspaceUri.path)
|
||||
: encodeURIComponent(workspace.workspaceUri.toString());
|
||||
targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${target}`;
|
||||
}
|
||||
|
||||
// Append payload if any
|
||||
@ -404,7 +411,22 @@ class WindowIndicator implements IWindowIndicator {
|
||||
throw new Error('Missing web configuration element');
|
||||
}
|
||||
|
||||
const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute);
|
||||
const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = {
|
||||
webviewEndpoint: `${window.location.origin}${window.location.pathname.replace(/\/+$/, '')}/webview`,
|
||||
...JSON.parse(configElementAttribute),
|
||||
};
|
||||
|
||||
// Strip the protocol from the authority if it exists.
|
||||
const normalizeAuthority = (authority: string): string => authority.replace(/^https?:\/\//, '');
|
||||
if (config.remoteAuthority) {
|
||||
(config as any).remoteAuthority = normalizeAuthority(config.remoteAuthority);
|
||||
}
|
||||
if (config.workspaceUri && config.workspaceUri.authority) {
|
||||
config.workspaceUri.authority = normalizeAuthority(config.workspaceUri.authority);
|
||||
}
|
||||
if (config.folderUri && config.folderUri.authority) {
|
||||
config.folderUri.authority = normalizeAuthority(config.folderUri.authority);
|
||||
}
|
||||
|
||||
// Revive static extension locations
|
||||
if (Array.isArray(config.staticExtensions)) {
|
||||
@ -416,40 +438,7 @@ class WindowIndicator implements IWindowIndicator {
|
||||
// Find workspace to open and payload
|
||||
let foundWorkspace = false;
|
||||
let workspace: IWorkspace;
|
||||
let payload = Object.create(null);
|
||||
|
||||
const query = new URL(document.location.href).searchParams;
|
||||
query.forEach((value, key) => {
|
||||
switch (key) {
|
||||
|
||||
// Folder
|
||||
case WorkspaceProvider.QUERY_PARAM_FOLDER:
|
||||
workspace = { folderUri: URI.parse(value) };
|
||||
foundWorkspace = true;
|
||||
break;
|
||||
|
||||
// Workspace
|
||||
case WorkspaceProvider.QUERY_PARAM_WORKSPACE:
|
||||
workspace = { workspaceUri: URI.parse(value) };
|
||||
foundWorkspace = true;
|
||||
break;
|
||||
|
||||
// Empty
|
||||
case WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW:
|
||||
workspace = undefined;
|
||||
foundWorkspace = true;
|
||||
break;
|
||||
|
||||
// Payload
|
||||
case WorkspaceProvider.QUERY_PARAM_PAYLOAD:
|
||||
try {
|
||||
payload = JSON.parse(value);
|
||||
} catch (error) {
|
||||
console.error(error); // possible invalid JSON
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
let payload = config.workspaceProvider?.payload || Object.create(null);
|
||||
|
||||
// If no workspace is provided through the URL, check for config attribute from server
|
||||
if (!foundWorkspace) {
|
||||
@ -465,13 +454,6 @@ class WindowIndicator implements IWindowIndicator {
|
||||
// Workspace Provider
|
||||
const workspaceProvider = new WorkspaceProvider(workspace, payload);
|
||||
|
||||
// Home Indicator
|
||||
const homeIndicator: IHomeIndicator = {
|
||||
href: 'https://github.com/microsoft/vscode',
|
||||
icon: 'code',
|
||||
title: localize('home', "Home")
|
||||
};
|
||||
|
||||
// Window indicator (unless connected to a remote)
|
||||
let windowIndicator: WindowIndicator | undefined = undefined;
|
||||
if (!workspaceProvider.hasRemote()) {
|
||||
@ -515,7 +497,6 @@ class WindowIndicator implements IWindowIndicator {
|
||||
create(document.body, {
|
||||
...config,
|
||||
settingsSyncOptions,
|
||||
homeIndicator,
|
||||
windowIndicator,
|
||||
productQualityChangeHandler,
|
||||
workspaceProvider,
|
||||
|
@ -7,6 +7,8 @@
|
||||
* A list of command line arguments we support natively.
|
||||
*/
|
||||
export interface NativeParsedArgs {
|
||||
'extra-extensions-dir'?: string[];
|
||||
'extra-builtin-extensions-dir'?: string[];
|
||||
_: string[];
|
||||
'folder-uri'?: string[]; // undefined or array of 1 or more
|
||||
'file-uri'?: string[]; // undefined or array of 1 or more
|
||||
|
@ -122,6 +122,8 @@ export interface INativeEnvironmentService extends IEnvironmentService {
|
||||
extensionsPath?: string;
|
||||
extensionsDownloadPath: string;
|
||||
builtinExtensionsPath: string;
|
||||
extraExtensionPaths: string[]
|
||||
extraBuiltinExtensionPaths: string[]
|
||||
|
||||
// --- Smoke test support
|
||||
driverHandle?: string;
|
||||
|
@ -54,6 +54,8 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
||||
'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
|
||||
'extensions-download-dir': { type: 'string' },
|
||||
'builtin-extensions-dir': { type: 'string' },
|
||||
'extra-builtin-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra builtin extension directory.' },
|
||||
'extra-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra user extension directory.' },
|
||||
'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
|
||||
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
|
||||
'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") },
|
||||
@ -318,4 +320,3 @@ export function buildHelpMessage(productName: string, executableName: string, ve
|
||||
export function buildVersionMessage(version: string | undefined, commit: string | undefined): string {
|
||||
return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`;
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,13 @@ export class NativeEnvironmentService implements INativeEnvironmentService {
|
||||
return resources.joinPath(this.userHome, product.dataFolderName, 'extensions').fsPath;
|
||||
}
|
||||
|
||||
@memoize get extraExtensionPaths(): string[] {
|
||||
return (this._args['extra-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
|
||||
}
|
||||
@memoize get extraBuiltinExtensionPaths(): string[] {
|
||||
return (this._args['extra-builtin-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
|
||||
}
|
||||
|
||||
@memoize
|
||||
get extensionDevelopmentLocationURI(): URI[] | undefined {
|
||||
const s = this._args.extensionDevelopmentPath;
|
||||
|
@ -91,7 +91,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
|
||||
async scanAllUserExtensions(): Promise<ILocalExtension[]> {
|
||||
return this.scanExtensionsInDir(this.extensionsPath, ExtensionType.User);
|
||||
return this.scanExtensionsInDirs(this.extensionsPath, this.environmentService.extraExtensionPaths, ExtensionType.User);
|
||||
}
|
||||
|
||||
async extractUserExtension(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, token: CancellationToken): Promise<ILocalExtension> {
|
||||
@ -236,7 +236,13 @@ export class ExtensionsScanner extends Disposable {
|
||||
|
||||
private async scanExtensionsInDir(dir: string, type: ExtensionType): Promise<ILocalExtension[]> {
|
||||
const limiter = new Limiter<any>(10);
|
||||
const extensionsFolders = await pfs.readdir(dir);
|
||||
const extensionsFolders = await pfs.readdir(dir)
|
||||
.catch((error) => {
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error;
|
||||
}
|
||||
return <string[]>[];
|
||||
});
|
||||
const extensions = await Promise.all<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, dir, type))));
|
||||
return extensions.filter(e => e && e.identifier);
|
||||
}
|
||||
@ -266,7 +272,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
|
||||
private async scanDefaultSystemExtensions(): Promise<ILocalExtension[]> {
|
||||
const result = await this.scanExtensionsInDir(this.systemExtensionsPath, ExtensionType.System);
|
||||
const result = await this.scanExtensionsInDirs(this.systemExtensionsPath, this.environmentService.extraBuiltinExtensionPaths, ExtensionType.System);
|
||||
this.logService.trace('Scanned system extensions:', result.length);
|
||||
return result;
|
||||
}
|
||||
@ -370,4 +376,9 @@ export class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async scanExtensionsInDirs(dir: string, dirs: string[], type: ExtensionType): Promise<ILocalExtension[]>{
|
||||
const results = await Promise.all([dir, ...dirs].map((path) => this.scanExtensionsInDir(path, type)));
|
||||
return results.reduce((flat, current) => flat.concat(current), []);
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,12 @@ if (isWeb || typeof require === 'undefined' || typeof require.__$__nodeRequire !
|
||||
],
|
||||
});
|
||||
}
|
||||
// NOTE@coder: Add the ability to inject settings from the server.
|
||||
const el = document.getElementById('vscode-remote-product-configuration');
|
||||
const rawProductConfiguration = el && el.getAttribute('data-settings');
|
||||
if (rawProductConfiguration) {
|
||||
Object.assign(product, JSON.parse(rawProductConfiguration));
|
||||
}
|
||||
}
|
||||
|
||||
// Native (non-sandboxed)
|
||||
|
@ -32,6 +32,8 @@ export type ConfigurationSyncStore = {
|
||||
};
|
||||
|
||||
export interface IProductConfiguration {
|
||||
readonly codeServerVersion?: string;
|
||||
|
||||
readonly version: string;
|
||||
readonly date?: string;
|
||||
readonly quality?: string;
|
||||
|
@ -208,7 +208,8 @@ export class BrowserSocketFactory implements ISocketFactory {
|
||||
}
|
||||
|
||||
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
|
||||
const socket = this._webSocketFactory.create(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`);
|
||||
// NOTE@coder: Modified to work against the current path.
|
||||
const socket = this._webSocketFactory.create(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}${window.location.pathname}?${query}&skipWebSocketFrames=false`);
|
||||
const errorListener = socket.onError((err) => callback(err, undefined));
|
||||
socket.onOpen(() => {
|
||||
errorListener.dispose();
|
||||
@ -216,6 +217,3 @@ export class BrowserSocketFactory implements ISocketFactory {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -93,7 +93,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio
|
||||
options.socketFactory.connect(
|
||||
options.host,
|
||||
options.port,
|
||||
`reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`,
|
||||
`type=${connectionTypeToString(connectionType)}&reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`,
|
||||
(err: any, socket: ISocket | undefined) => {
|
||||
if (err || !socket) {
|
||||
options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`);
|
||||
@ -338,7 +338,8 @@ export class ReconnectionWaitEvent {
|
||||
public readonly type = PersistentConnectionEventType.ReconnectionWait;
|
||||
constructor(
|
||||
public readonly durationSeconds: number,
|
||||
private readonly cancellableTimer: CancelablePromise<void>
|
||||
private readonly cancellableTimer: CancelablePromise<void>,
|
||||
public readonly connectionAttempt: number
|
||||
) { }
|
||||
|
||||
public skipWait(): void {
|
||||
@ -422,7 +423,7 @@ abstract class PersistentConnection extends Disposable {
|
||||
const waitTime = (attempt < TIMES.length ? TIMES[attempt] : TIMES[TIMES.length - 1]);
|
||||
try {
|
||||
const sleepPromise = sleep(waitTime);
|
||||
this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime, sleepPromise));
|
||||
this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime, sleepPromise, attempt+1));
|
||||
|
||||
this._options.logService.info(`${logPrefix} waiting for ${waitTime} seconds before reconnecting...`);
|
||||
try {
|
||||
|
@ -122,8 +122,8 @@ export class BrowserStorageService extends Disposable implements IStorageService
|
||||
return this.getStorage(scope).getNumber(key, fallbackValue);
|
||||
}
|
||||
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void {
|
||||
this.getStorage(scope).set(key, value);
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise<void> {
|
||||
return this.getStorage(scope).set(key, value);
|
||||
}
|
||||
|
||||
remove(key: string, scope: StorageScope): void {
|
||||
|
@ -85,7 +85,7 @@ export interface IStorageService {
|
||||
* The scope argument allows to define the scope of the storage
|
||||
* operation to either the current workspace only or all workspaces.
|
||||
*/
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void;
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise<void> | void;
|
||||
|
||||
/**
|
||||
* Delete an element stored under the provided key from storage.
|
||||
|
@ -201,8 +201,8 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
return this.getStorage(scope).getNumber(key, fallbackValue);
|
||||
}
|
||||
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void {
|
||||
this.getStorage(scope).set(key, value);
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise<void> {
|
||||
return this.getStorage(scope).set(key, value);
|
||||
}
|
||||
|
||||
remove(key: string, scope: StorageScope): void {
|
||||
|
241
lib/vscode/src/vs/server/browser/client.ts
Normal file
241
lib/vscode/src/vs/server/browser/client.ts
Normal file
@ -0,0 +1,241 @@
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { INodeProxyService, NodeProxyChannelClient } from 'vs/server/common/nodeProxy';
|
||||
import { TelemetryChannelClient } from 'vs/server/common/telemetry';
|
||||
import 'vs/workbench/contrib/localizations/browser/localizations.contribution';
|
||||
import { LocalizationsService } from 'vs/workbench/services/localizations/electron-browser/localizationsService';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { Options } from 'vs/server/ipc.d';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import * as path from 'vs/base/common/path';
|
||||
|
||||
class TelemetryService extends TelemetryChannelClient {
|
||||
public constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
) {
|
||||
super(remoteAgentService.getConnection()!.getChannel('telemetry'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove extra slashes in a URL.
|
||||
*/
|
||||
export const normalize = (url: string, keepTrailing = false): string => {
|
||||
return url.replace(/\/\/+/g, '/').replace(/\/+$/, keepTrailing ? '/' : '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get options embedded in the HTML.
|
||||
*/
|
||||
export const getOptions = <T extends Options>(): T => {
|
||||
try {
|
||||
return JSON.parse(document.getElementById('coder-options')!.getAttribute('data-settings')!);
|
||||
} catch (error) {
|
||||
return {} as T;
|
||||
}
|
||||
};
|
||||
|
||||
const options = getOptions();
|
||||
|
||||
const TELEMETRY_SECTION_ID = 'telemetry';
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
|
||||
'id': TELEMETRY_SECTION_ID,
|
||||
'order': 110,
|
||||
'type': 'object',
|
||||
'title': localize('telemetryConfigurationTitle', 'Telemetry'),
|
||||
'properties': {
|
||||
'telemetry.enableTelemetry': {
|
||||
'type': 'boolean',
|
||||
'description': localize('telemetry.enableTelemetry', 'Enable usage data and errors to be sent to a Microsoft online service.'),
|
||||
'default': !options.disableTelemetry,
|
||||
'tags': ['usesOnlineServices']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
class NodeProxyService extends NodeProxyChannelClient implements INodeProxyService {
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
public readonly onClose = this._onClose.event;
|
||||
private readonly _onDown = new Emitter<void>();
|
||||
public readonly onDown = this._onDown.event;
|
||||
private readonly _onUp = new Emitter<void>();
|
||||
public readonly onUp = this._onUp.event;
|
||||
|
||||
public constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
) {
|
||||
super(remoteAgentService.getConnection()!.getChannel('nodeProxy'));
|
||||
remoteAgentService.getConnection()!.onDidStateChange((state) => {
|
||||
switch (state.type) {
|
||||
case PersistentConnectionEventType.ConnectionGain:
|
||||
return this._onUp.fire();
|
||||
case PersistentConnectionEventType.ConnectionLost:
|
||||
return this._onDown.fire();
|
||||
case PersistentConnectionEventType.ReconnectionPermanentFailure:
|
||||
return this._onClose.fire();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(ILocalizationsService, LocalizationsService);
|
||||
registerSingleton(INodeProxyService, NodeProxyService);
|
||||
registerSingleton(ITelemetryService, TelemetryService);
|
||||
|
||||
/**
|
||||
* This is called by vs/workbench/browser/web.main.ts after the workbench has
|
||||
* been initialized so we can initialize our own client-side code.
|
||||
*/
|
||||
export const initialize = async (services: ServiceCollection): Promise<void> => {
|
||||
const event = new CustomEvent('ide-ready');
|
||||
window.dispatchEvent(event);
|
||||
|
||||
if (parent) {
|
||||
// Tell the parent loading has completed.
|
||||
parent.postMessage({ event: 'loaded' }, window.location.origin);
|
||||
|
||||
// Proxy or stop proxing events as requested by the parent.
|
||||
const listeners = new Map<string, (event: Event) => void>();
|
||||
window.addEventListener('message', (parentEvent) => {
|
||||
const eventName = parentEvent.data.bind || parentEvent.data.unbind;
|
||||
if (eventName) {
|
||||
const oldListener = listeners.get(eventName);
|
||||
if (oldListener) {
|
||||
document.removeEventListener(eventName, oldListener);
|
||||
}
|
||||
}
|
||||
|
||||
if (parentEvent.data.bind && parentEvent.data.prop) {
|
||||
const listener = (event: Event) => {
|
||||
parent.postMessage({
|
||||
event: parentEvent.data.event,
|
||||
[parentEvent.data.prop]: event[parentEvent.data.prop as keyof Event]
|
||||
}, window.location.origin);
|
||||
};
|
||||
listeners.set(parentEvent.data.bind, listener);
|
||||
document.addEventListener(parentEvent.data.bind, listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!window.isSecureContext) {
|
||||
(services.get(INotificationService) as INotificationService).notify({
|
||||
severity: Severity.Warning,
|
||||
message: 'code-server is being accessed over an insecure domain. Web views, the clipboard, and other functionality will not work as expected.',
|
||||
actions: {
|
||||
primary: [{
|
||||
id: 'understand',
|
||||
label: 'I understand',
|
||||
tooltip: '',
|
||||
class: undefined,
|
||||
enabled: true,
|
||||
checked: true,
|
||||
dispose: () => undefined,
|
||||
run: () => {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}],
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const logService = (services.get(ILogService) as ILogService);
|
||||
const storageService = (services.get(IStorageService) as IStorageService);
|
||||
const updateCheckEndpoint = path.join(options.base, '/update/check');
|
||||
const getUpdate = async (): Promise<void> => {
|
||||
logService.debug('Checking for update...');
|
||||
|
||||
const response = await fetch(updateCheckEndpoint, {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
const json = await response.json();
|
||||
if (json.error) {
|
||||
throw new Error(json.error);
|
||||
}
|
||||
if (json.isLatest) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastNoti = storageService.getNumber('csLastUpdateNotification', StorageScope.GLOBAL);
|
||||
if (lastNoti) {
|
||||
// Only remind them again after 1 week.
|
||||
const timeout = 1000*60*60*24*7;
|
||||
const threshold = lastNoti + timeout;
|
||||
if (Date.now() < threshold) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
storageService.store('csLastUpdateNotification', Date.now(), StorageScope.GLOBAL);
|
||||
(services.get(INotificationService) as INotificationService).notify({
|
||||
severity: Severity.Info,
|
||||
message: `[code-server v${json.latest}](https://github.com/cdr/code-server/releases/tag/v${json.latest}) has been released!`,
|
||||
});
|
||||
};
|
||||
|
||||
const updateLoop = (): void => {
|
||||
getUpdate().catch((error) => {
|
||||
logService.debug(`failed to check for update: ${error}`);
|
||||
}).finally(() => {
|
||||
// Check again every 6 hours.
|
||||
setTimeout(updateLoop, 1000*60*60*6);
|
||||
});
|
||||
};
|
||||
|
||||
if (!options.disableUpdateCheck) {
|
||||
updateLoop();
|
||||
}
|
||||
|
||||
// This will be used to set the background color while VS Code loads.
|
||||
const theme = storageService.get('colorThemeData', StorageScope.GLOBAL);
|
||||
if (theme) {
|
||||
localStorage.setItem('colorThemeData', theme);
|
||||
}
|
||||
};
|
||||
|
||||
export interface Query {
|
||||
[key: string]: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a string up to the delimiter. If the delimiter doesn't exist the first
|
||||
* item will have all the text and the second item will be an empty string.
|
||||
*/
|
||||
export const split = (str: string, delimiter: string): [string, string] => {
|
||||
const index = str.indexOf(delimiter);
|
||||
return index !== -1 ? [str.substring(0, index).trim(), str.substring(index + 1)] : [str, ''];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the URL modified with the specified query variables. It's pretty
|
||||
* stupid so it probably doesn't cover any edge cases. Undefined values will
|
||||
* unset existing values. Doesn't allow duplicates.
|
||||
*/
|
||||
export const withQuery = (url: string, replace: Query): string => {
|
||||
const uri = URI.parse(url);
|
||||
const query = { ...replace };
|
||||
uri.query.split('&').forEach((kv) => {
|
||||
const [key, value] = split(kv, '=');
|
||||
if (!(key in query)) {
|
||||
query[key] = value;
|
||||
}
|
||||
});
|
||||
return uri.with({
|
||||
query: Object.keys(query)
|
||||
.filter((k) => typeof query[k] !== 'undefined')
|
||||
.map((k) => `${k}=${query[k]}`).join('&'),
|
||||
}).toString(true);
|
||||
};
|
51
lib/vscode/src/vs/server/browser/extHostNodeProxy.ts
Normal file
51
lib/vscode/src/vs/server/browser/extHostNodeProxy.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { UriComponents } from 'vs/base/common/uri';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ExtHostNodeProxyShape, MainContext, MainThreadNodeProxyShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
|
||||
export class ExtHostNodeProxy implements ExtHostNodeProxyShape {
|
||||
_serviceBrand: any;
|
||||
|
||||
private readonly _onMessage = new Emitter<string>();
|
||||
public readonly onMessage = this._onMessage.event;
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
public readonly onClose = this._onClose.event;
|
||||
private readonly _onDown = new Emitter<void>();
|
||||
public readonly onDown = this._onDown.event;
|
||||
private readonly _onUp = new Emitter<void>();
|
||||
public readonly onUp = this._onUp.event;
|
||||
|
||||
private readonly proxy: MainThreadNodeProxyShape;
|
||||
|
||||
constructor(@IExtHostRpcService rpc: IExtHostRpcService) {
|
||||
this.proxy = rpc.getProxy(MainContext.MainThreadNodeProxy);
|
||||
}
|
||||
|
||||
public $onMessage(message: string): void {
|
||||
this._onMessage.fire(message);
|
||||
}
|
||||
|
||||
public $onClose(): void {
|
||||
this._onClose.fire();
|
||||
}
|
||||
|
||||
public $onUp(): void {
|
||||
this._onUp.fire();
|
||||
}
|
||||
|
||||
public $onDown(): void {
|
||||
this._onDown.fire();
|
||||
}
|
||||
|
||||
public send(message: string): void {
|
||||
this.proxy.$send(message);
|
||||
}
|
||||
|
||||
public async fetchExtension(extensionUri: UriComponents): Promise<Uint8Array> {
|
||||
return this.proxy.$fetchExtension(extensionUri).then(b => b.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExtHostNodeProxy extends ExtHostNodeProxy { }
|
||||
export const IExtHostNodeProxy = createDecorator<IExtHostNodeProxy>('IExtHostNodeProxy');
|
55
lib/vscode/src/vs/server/browser/mainThreadNodeProxy.ts
Normal file
55
lib/vscode/src/vs/server/browser/mainThreadNodeProxy.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { INodeProxyService } from 'vs/server/common/nodeProxy';
|
||||
import { ExtHostContext, IExtHostContext, MainContext, MainThreadNodeProxyShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadNodeProxy)
|
||||
export class MainThreadNodeProxy implements MainThreadNodeProxyShape {
|
||||
private disposed = false;
|
||||
private disposables = <IDisposable[]>[];
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@INodeProxyService private readonly proxyService: INodeProxyService,
|
||||
) {
|
||||
if (!extHostContext.remoteAuthority) { // HACK: A terrible way to detect if running in the worker.
|
||||
const proxy = extHostContext.getProxy(ExtHostContext.ExtHostNodeProxy);
|
||||
this.disposables = [
|
||||
this.proxyService.onMessage((message: string) => proxy.$onMessage(message)),
|
||||
this.proxyService.onClose(() => proxy.$onClose()),
|
||||
this.proxyService.onDown(() => proxy.$onDown()),
|
||||
this.proxyService.onUp(() => proxy.$onUp()),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$send(message: string): void {
|
||||
if (!this.disposed) {
|
||||
this.proxyService.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
async $fetchExtension(extensionUri: UriComponents): Promise<VSBuffer> {
|
||||
const fetchUri = URI.from({
|
||||
scheme: window.location.protocol.replace(':', ''),
|
||||
authority: window.location.host,
|
||||
// Use FileAccess to get the static base path.
|
||||
path: FileAccess.asBrowserUri('', require).path,
|
||||
query: `tar=${encodeURIComponent(extensionUri.path)}`,
|
||||
});
|
||||
const response = await fetch(fetchUri.toString(true));
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`Failed to download extension "${module}"`);
|
||||
}
|
||||
return VSBuffer.wrap(new Uint8Array(await response.arrayBuffer()));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.forEach((d) => d.dispose());
|
||||
this.disposables = [];
|
||||
this.disposed = true;
|
||||
}
|
||||
}
|
48
lib/vscode/src/vs/server/browser/worker.ts
Normal file
48
lib/vscode/src/vs/server/browser/worker.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { Client } from '@coder/node-browser';
|
||||
import { fromTar } from '@coder/requirefs';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { IExtHostNodeProxy } from './extHostNodeProxy';
|
||||
|
||||
export const loadCommonJSModule = async <T>(
|
||||
module: URI,
|
||||
activationTimesBuilder: ExtensionActivationTimesBuilder,
|
||||
nodeProxy: IExtHostNodeProxy,
|
||||
logService: ILogService,
|
||||
vscode: any,
|
||||
): Promise<T> => {
|
||||
const client = new Client(nodeProxy, { logger: logService });
|
||||
const [buffer, init] = await Promise.all([
|
||||
nodeProxy.fetchExtension(module),
|
||||
client.handshake(),
|
||||
]);
|
||||
const rfs = fromTar(buffer);
|
||||
(<any>self).global = self;
|
||||
rfs.provide('vscode', vscode);
|
||||
Object.keys(client.modules).forEach((key) => {
|
||||
const mod = (client.modules as any)[key];
|
||||
if (key === 'process') {
|
||||
(<any>self).process = mod;
|
||||
(<any>self).process.env = init.env;
|
||||
return;
|
||||
}
|
||||
|
||||
rfs.provide(key, mod);
|
||||
switch (key) {
|
||||
case 'buffer':
|
||||
(<any>self).Buffer = mod.Buffer;
|
||||
break;
|
||||
case 'timers':
|
||||
(<any>self).setImmediate = mod.setImmediate;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
activationTimesBuilder.codeLoadingStart();
|
||||
return rfs.require('.');
|
||||
} finally {
|
||||
activationTimesBuilder.codeLoadingStop();
|
||||
}
|
||||
};
|
47
lib/vscode/src/vs/server/common/nodeProxy.ts
Normal file
47
lib/vscode/src/vs/server/common/nodeProxy.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { ReadWriteConnection } from '@coder/node-browser';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const INodeProxyService = createDecorator<INodeProxyService>('nodeProxyService');
|
||||
|
||||
export interface INodeProxyService extends ReadWriteConnection {
|
||||
_serviceBrand: any;
|
||||
send(message: string): void;
|
||||
onMessage: Event<string>;
|
||||
onUp: Event<void>;
|
||||
onClose: Event<void>;
|
||||
onDown: Event<void>;
|
||||
}
|
||||
|
||||
export class NodeProxyChannel implements IServerChannel {
|
||||
constructor(private service: INodeProxyService) {}
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
switch (event) {
|
||||
case 'onMessage': return this.service.onMessage;
|
||||
}
|
||||
throw new Error(`Invalid listen ${event}`);
|
||||
}
|
||||
|
||||
async call(_: unknown, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'send': return this.service.send(args[0]);
|
||||
}
|
||||
throw new Error(`Invalid call ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeProxyChannelClient {
|
||||
_serviceBrand: any;
|
||||
|
||||
public readonly onMessage: Event<string>;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
this.onMessage = this.channel.listen<string>('onMessage');
|
||||
}
|
||||
|
||||
public send(data: string): void {
|
||||
this.channel.call('send', [data]);
|
||||
}
|
||||
}
|
65
lib/vscode/src/vs/server/common/telemetry.ts
Normal file
65
lib/vscode/src/vs/server/common/telemetry.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { ITelemetryData } from 'vs/base/common/actions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
|
||||
import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export class TelemetryChannel implements IServerChannel {
|
||||
constructor(private service: ITelemetryService) {}
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Invalid listen ${event}`);
|
||||
}
|
||||
|
||||
call(_: unknown, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'publicLog': return this.service.publicLog(args[0], args[1], args[2]);
|
||||
case 'publicLog2': return this.service.publicLog2(args[0], args[1], args[2]);
|
||||
case 'publicLogError': return this.service.publicLogError(args[0], args[1]);
|
||||
case 'publicLogError2': return this.service.publicLogError2(args[0], args[1]);
|
||||
case 'setEnabled': return Promise.resolve(this.service.setEnabled(args[0]));
|
||||
case 'getTelemetryInfo': return this.service.getTelemetryInfo();
|
||||
case 'setExperimentProperty': return Promise.resolve(this.service.setExperimentProperty(args[0], args[1]));
|
||||
}
|
||||
throw new Error(`Invalid call ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class TelemetryChannelClient implements ITelemetryService {
|
||||
_serviceBrand: any;
|
||||
|
||||
// These don't matter; telemetry is sent to the Node side which decides
|
||||
// whether to send the telemetry event.
|
||||
public isOptedIn = true;
|
||||
public sendErrorTelemetry = true;
|
||||
|
||||
constructor(private readonly channel: IChannel) {}
|
||||
|
||||
public publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise<void> {
|
||||
return this.channel.call('publicLog', [eventName, data, anonymizeFilePaths]);
|
||||
}
|
||||
|
||||
public publicLog2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>, anonymizeFilePaths?: boolean): Promise<void> {
|
||||
return this.channel.call('publicLog2', [eventName, data, anonymizeFilePaths]);
|
||||
}
|
||||
|
||||
public publicLogError(errorEventName: string, data?: ITelemetryData): Promise<void> {
|
||||
return this.channel.call('publicLogError', [errorEventName, data]);
|
||||
}
|
||||
|
||||
public publicLogError2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>): Promise<void> {
|
||||
return this.channel.call('publicLogError2', [eventName, data]);
|
||||
}
|
||||
|
||||
public setEnabled(value: boolean): void {
|
||||
this.channel.call('setEnable', [value]);
|
||||
}
|
||||
|
||||
public getTelemetryInfo(): Promise<ITelemetryInfo> {
|
||||
return this.channel.call('getTelemetryInfo');
|
||||
}
|
||||
|
||||
public setExperimentProperty(name: string, value: string): void {
|
||||
this.channel.call('setExperimentProperty', [name, value]);
|
||||
}
|
||||
}
|
81
lib/vscode/src/vs/server/entry.ts
Normal file
81
lib/vscode/src/vs/server/entry.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { field } from '@coder/logger';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { CodeServerMessage, VscodeMessage } from 'vs/server/ipc';
|
||||
import { logger } from 'vs/server/node/logger';
|
||||
import { enableCustomMarketplace } from 'vs/server/node/marketplace';
|
||||
import { Vscode } from 'vs/server/node/server';
|
||||
import * as proxyAgent from 'vs/base/node/proxy_agent';
|
||||
|
||||
setUnexpectedErrorHandler((error) => logger.warn(error instanceof Error ? error.message : error));
|
||||
enableCustomMarketplace();
|
||||
proxyAgent.monkeyPatch(true);
|
||||
|
||||
/**
|
||||
* Ensure we control when the process exits.
|
||||
*/
|
||||
const exit = process.exit;
|
||||
process.exit = function(code?: number) {
|
||||
logger.warn(`process.exit() was prevented: ${code || 'unknown code'}.`);
|
||||
} as (code?: number) => never;
|
||||
|
||||
// Kill VS Code if the parent process dies.
|
||||
if (typeof process.env.CODE_SERVER_PARENT_PID !== 'undefined') {
|
||||
const parentPid = parseInt(process.env.CODE_SERVER_PARENT_PID, 10);
|
||||
setInterval(() => {
|
||||
try {
|
||||
process.kill(parentPid, 0); // Throws an exception if the process doesn't exist anymore.
|
||||
} catch (e) {
|
||||
exit();
|
||||
}
|
||||
}, 5000);
|
||||
} else {
|
||||
logger.error('no parent process');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const vscode = new Vscode();
|
||||
const send = (message: VscodeMessage): void => {
|
||||
if (!process.send) {
|
||||
throw new Error('not spawned with IPC');
|
||||
}
|
||||
process.send(message);
|
||||
};
|
||||
|
||||
// Wait for the init message then start up VS Code. Subsequent messages will
|
||||
// return new workbench options without starting a new instance.
|
||||
process.on('message', async (message: CodeServerMessage, socket) => {
|
||||
logger.debug('got message from code-server', field('type', message.type));
|
||||
logger.trace('code-server message content', field('message', message));
|
||||
switch (message.type) {
|
||||
case 'init':
|
||||
try {
|
||||
const options = await vscode.initialize(message.options);
|
||||
send({ type: 'options', id: message.id, options });
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
logger.error(error.stack);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 'cli':
|
||||
try {
|
||||
await vscode.cli(message.args);
|
||||
exit(0);
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
logger.error(error.stack);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 'socket':
|
||||
vscode.handleWebSocket(socket, message.query);
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (!process.send) {
|
||||
logger.error('not spawned with IPC');
|
||||
exit(1);
|
||||
} else {
|
||||
// This lets the parent know the child is ready to receive messages.
|
||||
send({ type: 'ready' });
|
||||
}
|
3
lib/vscode/src/vs/server/fork.js
Normal file
3
lib/vscode/src/vs/server/fork.js
Normal file
@ -0,0 +1,3 @@
|
||||
// This must be a JS file otherwise when it gets compiled it turns into AMD
|
||||
// syntax which will not work without the right loader.
|
||||
require('../../bootstrap-amd').load('vs/server/entry');
|
140
lib/vscode/src/vs/server/ipc.d.ts
vendored
Normal file
140
lib/vscode/src/vs/server/ipc.d.ts
vendored
Normal file
@ -0,0 +1,140 @@
|
||||
/**
|
||||
* External interfaces for integration into code-server over IPC. No vs imports
|
||||
* should be made in this file.
|
||||
*/
|
||||
export interface Options {
|
||||
base: string
|
||||
disableTelemetry: boolean
|
||||
disableUpdateCheck: boolean
|
||||
}
|
||||
|
||||
export interface InitMessage {
|
||||
type: 'init';
|
||||
id: string;
|
||||
options: VscodeOptions;
|
||||
}
|
||||
|
||||
export type Query = { [key: string]: string | string[] | undefined | Query | Query[] };
|
||||
|
||||
export interface SocketMessage {
|
||||
type: 'socket';
|
||||
query: Query;
|
||||
}
|
||||
|
||||
export interface CliMessage {
|
||||
type: 'cli';
|
||||
args: Args;
|
||||
}
|
||||
|
||||
export interface OpenCommandPipeArgs {
|
||||
type: 'open';
|
||||
fileURIs?: string[];
|
||||
folderURIs: string[];
|
||||
forceNewWindow?: boolean;
|
||||
diffMode?: boolean;
|
||||
addMode?: boolean;
|
||||
gotoLineMode?: boolean;
|
||||
forceReuseWindow?: boolean;
|
||||
waitMarkerFilePath?: string;
|
||||
}
|
||||
|
||||
export type CodeServerMessage = InitMessage | SocketMessage | CliMessage;
|
||||
|
||||
export interface ReadyMessage {
|
||||
type: 'ready';
|
||||
}
|
||||
|
||||
export interface OptionsMessage {
|
||||
id: string;
|
||||
type: 'options';
|
||||
options: WorkbenchOptions;
|
||||
}
|
||||
|
||||
export type VscodeMessage = ReadyMessage | OptionsMessage;
|
||||
|
||||
export interface StartPath {
|
||||
url: string;
|
||||
workspace: boolean;
|
||||
}
|
||||
|
||||
export interface Args {
|
||||
'user-data-dir'?: string;
|
||||
|
||||
'enable-proposed-api'?: string[];
|
||||
'extensions-dir'?: string;
|
||||
'builtin-extensions-dir'?: string;
|
||||
'extra-extensions-dir'?: string[];
|
||||
'extra-builtin-extensions-dir'?: string[];
|
||||
'ignore-last-opened'?: boolean;
|
||||
|
||||
locale?: string
|
||||
|
||||
log?: string;
|
||||
verbose?: boolean;
|
||||
home?: string;
|
||||
|
||||
_: string[];
|
||||
}
|
||||
|
||||
export interface VscodeOptions {
|
||||
readonly args: Args;
|
||||
readonly remoteAuthority: string;
|
||||
readonly startPath?: StartPath;
|
||||
}
|
||||
|
||||
export interface VscodeOptionsMessage extends VscodeOptions {
|
||||
readonly id: string;
|
||||
}
|
||||
|
||||
export interface UriComponents {
|
||||
readonly scheme: string;
|
||||
readonly authority: string;
|
||||
readonly path: string;
|
||||
readonly query: string;
|
||||
readonly fragment: string;
|
||||
}
|
||||
|
||||
export interface NLSConfiguration {
|
||||
locale: string;
|
||||
availableLanguages: {
|
||||
[key: string]: string;
|
||||
};
|
||||
pseudo?: boolean;
|
||||
_languagePackSupport?: boolean;
|
||||
}
|
||||
|
||||
export interface WorkbenchOptions {
|
||||
readonly workbenchWebConfiguration: {
|
||||
readonly remoteAuthority?: string;
|
||||
readonly folderUri?: UriComponents;
|
||||
readonly workspaceUri?: UriComponents;
|
||||
readonly logLevel?: number;
|
||||
readonly workspaceProvider?: {
|
||||
payload: [
|
||||
['userDataPath', string],
|
||||
['enableProposedApi', string],
|
||||
];
|
||||
};
|
||||
readonly homeIndicator?: {
|
||||
href: string,
|
||||
icon: string,
|
||||
title: string,
|
||||
},
|
||||
};
|
||||
readonly remoteUserDataUri: UriComponents;
|
||||
readonly productConfiguration: {
|
||||
codeServerVersion?: string;
|
||||
readonly extensionsGallery?: {
|
||||
readonly serviceUrl: string;
|
||||
readonly itemUrl: string;
|
||||
readonly controlUrl: string;
|
||||
readonly recommendationsUrl: string;
|
||||
};
|
||||
};
|
||||
readonly nlsConfiguration: NLSConfiguration;
|
||||
readonly commit: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchOptionsMessage {
|
||||
id: string;
|
||||
}
|
906
lib/vscode/src/vs/server/node/channel.ts
Normal file
906
lib/vscode/src/vs/server/node/channel.ts
Normal file
@ -0,0 +1,906 @@
|
||||
import { field, logger } from '@coder/logger';
|
||||
import { Server } from '@coder/node-browser';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { ReadableStreamEventPayload } from 'vs/base/common/stream';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { transformOutgoingURIs } from 'vs/base/common/uriIpc';
|
||||
import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileReadStreamOptions, FileType, FileWriteOptions, IStat, IWatchOptions } from 'vs/platform/files/common/files';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IRemoteAgentEnvironment, RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { INodeProxyService } from 'vs/server/common/nodeProxy';
|
||||
import { getTranslations } from 'vs/server/node/nls';
|
||||
import { getUriTransformer } from 'vs/server/node/util';
|
||||
import { IFileChangeDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
|
||||
import { deserializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
|
||||
import * as terminal from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel';
|
||||
import { IShellLaunchConfig, ITerminalEnvironment, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering';
|
||||
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
|
||||
import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal';
|
||||
import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
|
||||
import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess';
|
||||
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver';
|
||||
import { ExtensionScanner, ExtensionScannerInput } from 'vs/workbench/services/extensions/node/extensionPoints';
|
||||
|
||||
/**
|
||||
* Extend the file provider to allow unwatching.
|
||||
*/
|
||||
class Watcher extends DiskFileSystemProvider {
|
||||
public readonly watches = new Map<number, IDisposable>();
|
||||
|
||||
public dispose(): void {
|
||||
this.watches.forEach((w) => w.dispose());
|
||||
this.watches.clear();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public _watch(req: number, resource: URI, opts: IWatchOptions): void {
|
||||
this.watches.set(req, this.watch(resource, opts));
|
||||
}
|
||||
|
||||
public unwatch(req: number): void {
|
||||
this.watches.get(req)!.dispose();
|
||||
this.watches.delete(req);
|
||||
}
|
||||
}
|
||||
|
||||
export class FileProviderChannel implements IServerChannel<RemoteAgentConnectionContext>, IDisposable {
|
||||
private readonly provider: DiskFileSystemProvider;
|
||||
private readonly watchers = new Map<string, Watcher>();
|
||||
|
||||
public constructor(
|
||||
private readonly environmentService: INativeEnvironmentService,
|
||||
private readonly logService: ILogService,
|
||||
) {
|
||||
this.provider = new DiskFileSystemProvider(this.logService);
|
||||
}
|
||||
|
||||
public listen(context: RemoteAgentConnectionContext, event: string, args?: any): Event<any> {
|
||||
switch (event) {
|
||||
case 'filechange': return this.filechange(context, args[0]);
|
||||
case 'readFileStream': return this.readFileStream(args[0], args[1]);
|
||||
}
|
||||
|
||||
throw new Error(`Invalid listen '${event}'`);
|
||||
}
|
||||
|
||||
private filechange(context: RemoteAgentConnectionContext, session: string): Event<IFileChangeDto[]> {
|
||||
const emitter = new Emitter<IFileChangeDto[]>({
|
||||
onFirstListenerAdd: () => {
|
||||
const provider = new Watcher(this.logService);
|
||||
this.watchers.set(session, provider);
|
||||
const transformer = getUriTransformer(context.remoteAuthority);
|
||||
provider.onDidChangeFile((events) => {
|
||||
emitter.fire(events.map((event) => ({
|
||||
...event,
|
||||
resource: transformer.transformOutgoing(event.resource),
|
||||
})));
|
||||
});
|
||||
provider.onDidErrorOccur((event) => this.logService.error(event));
|
||||
},
|
||||
onLastListenerRemove: () => {
|
||||
this.watchers.get(session)!.dispose();
|
||||
this.watchers.delete(session);
|
||||
},
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
private readFileStream(resource: UriComponents, opts: FileReadStreamOptions): Event<ReadableStreamEventPayload<VSBuffer>> {
|
||||
const cts = new CancellationTokenSource();
|
||||
const fileStream = this.provider.readFileStream(this.transform(resource), opts, cts.token);
|
||||
const emitter = new Emitter<ReadableStreamEventPayload<VSBuffer>>({
|
||||
onFirstListenerAdd: () => {
|
||||
fileStream.on('data', (data) => emitter.fire(VSBuffer.wrap(data)));
|
||||
fileStream.on('error', (error) => emitter.fire(error));
|
||||
fileStream.on('end', () => emitter.fire('end'));
|
||||
},
|
||||
onLastListenerRemove: () => cts.cancel(),
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
public call(_: unknown, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'stat': return this.stat(args[0]);
|
||||
case 'open': return this.open(args[0], args[1]);
|
||||
case 'close': return this.close(args[0]);
|
||||
case 'read': return this.read(args[0], args[1], args[2]);
|
||||
case 'readFile': return this.readFile(args[0]);
|
||||
case 'write': return this.write(args[0], args[1], args[2], args[3], args[4]);
|
||||
case 'writeFile': return this.writeFile(args[0], args[1], args[2]);
|
||||
case 'delete': return this.delete(args[0], args[1]);
|
||||
case 'mkdir': return this.mkdir(args[0]);
|
||||
case 'readdir': return this.readdir(args[0]);
|
||||
case 'rename': return this.rename(args[0], args[1], args[2]);
|
||||
case 'copy': return this.copy(args[0], args[1], args[2]);
|
||||
case 'watch': return this.watch(args[0], args[1], args[2], args[3]);
|
||||
case 'unwatch': return this.unwatch(args[0], args[1]);
|
||||
}
|
||||
|
||||
throw new Error(`Invalid call '${command}'`);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.watchers.forEach((w) => w.dispose());
|
||||
this.watchers.clear();
|
||||
}
|
||||
|
||||
private async stat(resource: UriComponents): Promise<IStat> {
|
||||
return this.provider.stat(this.transform(resource));
|
||||
}
|
||||
|
||||
private async open(resource: UriComponents, opts: FileOpenOptions): Promise<number> {
|
||||
return this.provider.open(this.transform(resource), opts);
|
||||
}
|
||||
|
||||
private async close(fd: number): Promise<void> {
|
||||
return this.provider.close(fd);
|
||||
}
|
||||
|
||||
private async read(fd: number, pos: number, length: number): Promise<[VSBuffer, number]> {
|
||||
const buffer = VSBuffer.alloc(length);
|
||||
const bytesRead = await this.provider.read(fd, pos, buffer.buffer, 0, length);
|
||||
return [buffer, bytesRead];
|
||||
}
|
||||
|
||||
private async readFile(resource: UriComponents): Promise<VSBuffer> {
|
||||
return VSBuffer.wrap(await this.provider.readFile(this.transform(resource)));
|
||||
}
|
||||
|
||||
private write(fd: number, pos: number, buffer: VSBuffer, offset: number, length: number): Promise<number> {
|
||||
return this.provider.write(fd, pos, buffer.buffer, offset, length);
|
||||
}
|
||||
|
||||
private writeFile(resource: UriComponents, buffer: VSBuffer, opts: FileWriteOptions): Promise<void> {
|
||||
return this.provider.writeFile(this.transform(resource), buffer.buffer, opts);
|
||||
}
|
||||
|
||||
private async delete(resource: UriComponents, opts: FileDeleteOptions): Promise<void> {
|
||||
return this.provider.delete(this.transform(resource), opts);
|
||||
}
|
||||
|
||||
private async mkdir(resource: UriComponents): Promise<void> {
|
||||
return this.provider.mkdir(this.transform(resource));
|
||||
}
|
||||
|
||||
private async readdir(resource: UriComponents): Promise<[string, FileType][]> {
|
||||
return this.provider.readdir(this.transform(resource));
|
||||
}
|
||||
|
||||
private async rename(resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise<void> {
|
||||
return this.provider.rename(this.transform(resource), URI.from(target), opts);
|
||||
}
|
||||
|
||||
private copy(resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise<void> {
|
||||
return this.provider.copy(this.transform(resource), URI.from(target), opts);
|
||||
}
|
||||
|
||||
private async watch(session: string, req: number, resource: UriComponents, opts: IWatchOptions): Promise<void> {
|
||||
this.watchers.get(session)!._watch(req, this.transform(resource), opts);
|
||||
}
|
||||
|
||||
private async unwatch(session: string, req: number): Promise<void> {
|
||||
this.watchers.get(session)!.unwatch(req);
|
||||
}
|
||||
|
||||
private transform(resource: UriComponents): URI {
|
||||
// Used for walkthrough content.
|
||||
if (/^\/static[^/]*\//.test(resource.path)) {
|
||||
return URI.file(this.environmentService.appRoot + resource.path.replace(/^\/static[^/]*\//, '/'));
|
||||
// Used by the webview service worker to load resources.
|
||||
} else if (resource.path === '/vscode-resource' && resource.query) {
|
||||
try {
|
||||
const query = JSON.parse(resource.query);
|
||||
if (query.requestResourcePath) {
|
||||
return URI.file(query.requestResourcePath);
|
||||
}
|
||||
} catch (error) { /* Carry on. */ }
|
||||
}
|
||||
return URI.from(resource);
|
||||
}
|
||||
}
|
||||
|
||||
// See ../../workbench/services/remote/common/remoteAgentEnvironmentChannel.ts
|
||||
export class ExtensionEnvironmentChannel implements IServerChannel {
|
||||
public constructor(
|
||||
private readonly environment: INativeEnvironmentService,
|
||||
private readonly log: ILogService,
|
||||
private readonly telemetry: ITelemetryService,
|
||||
private readonly connectionToken: string,
|
||||
) {}
|
||||
|
||||
public listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Invalid listen '${event}'`);
|
||||
}
|
||||
|
||||
public async call(context: any, command: string, args: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'getEnvironmentData':
|
||||
return transformOutgoingURIs(
|
||||
await this.getEnvironmentData(),
|
||||
getUriTransformer(context.remoteAuthority),
|
||||
);
|
||||
case 'scanExtensions':
|
||||
return transformOutgoingURIs(
|
||||
await this.scanExtensions(args.language),
|
||||
getUriTransformer(context.remoteAuthority),
|
||||
);
|
||||
case 'getDiagnosticInfo': return this.getDiagnosticInfo();
|
||||
case 'disableTelemetry': return this.disableTelemetry();
|
||||
case 'logTelemetry': return this.logTelemetry(args[0], args[1]);
|
||||
case 'flushTelemetry': return this.flushTelemetry();
|
||||
}
|
||||
throw new Error(`Invalid call '${command}'`);
|
||||
}
|
||||
|
||||
private async getEnvironmentData(): Promise<IRemoteAgentEnvironment> {
|
||||
return {
|
||||
pid: process.pid,
|
||||
connectionToken: this.connectionToken,
|
||||
appRoot: URI.file(this.environment.appRoot),
|
||||
settingsPath: this.environment.settingsResource,
|
||||
logsPath: URI.file(this.environment.logsPath),
|
||||
extensionsPath: URI.file(this.environment.extensionsPath!),
|
||||
extensionHostLogsPath: URI.file(path.join(this.environment.logsPath, 'extension-host')),
|
||||
globalStorageHome: this.environment.globalStorageHome,
|
||||
workspaceStorageHome: this.environment.workspaceStorageHome,
|
||||
userHome: this.environment.userHome,
|
||||
os: platform.OS,
|
||||
};
|
||||
}
|
||||
|
||||
private async scanExtensions(language: string): Promise<IExtensionDescription[]> {
|
||||
const translations = await getTranslations(language, this.environment.userDataPath);
|
||||
|
||||
const scanMultiple = (isBuiltin: boolean, isUnderDevelopment: boolean, paths: string[]): Promise<IExtensionDescription[][]> => {
|
||||
return Promise.all(paths.map((path) => {
|
||||
return ExtensionScanner.scanExtensions(new ExtensionScannerInput(
|
||||
product.version,
|
||||
product.commit,
|
||||
language,
|
||||
!!process.env.VSCODE_DEV,
|
||||
path,
|
||||
isBuiltin,
|
||||
isUnderDevelopment,
|
||||
translations,
|
||||
), this.log);
|
||||
}));
|
||||
};
|
||||
|
||||
const scanBuiltin = async (): Promise<IExtensionDescription[][]> => {
|
||||
return scanMultiple(true, false, [this.environment.builtinExtensionsPath, ...this.environment.extraBuiltinExtensionPaths]);
|
||||
};
|
||||
|
||||
const scanInstalled = async (): Promise<IExtensionDescription[][]> => {
|
||||
return scanMultiple(false, true, [this.environment.extensionsPath!, ...this.environment.extraExtensionPaths]);
|
||||
};
|
||||
|
||||
return Promise.all([scanBuiltin(), scanInstalled()]).then((allExtensions) => {
|
||||
const uniqueExtensions = new Map<string, IExtensionDescription>();
|
||||
allExtensions.forEach((multipleExtensions) => {
|
||||
multipleExtensions.forEach((extensions) => {
|
||||
extensions.forEach((extension) => {
|
||||
const id = ExtensionIdentifier.toKey(extension.identifier);
|
||||
if (uniqueExtensions.has(id)) {
|
||||
const oldPath = uniqueExtensions.get(id)!.extensionLocation.fsPath;
|
||||
const newPath = extension.extensionLocation.fsPath;
|
||||
this.log.warn(`${oldPath} has been overridden ${newPath}`);
|
||||
}
|
||||
uniqueExtensions.set(id, {
|
||||
...extension,
|
||||
// Force extensions that should run on the client due to latency
|
||||
// issues.
|
||||
extensionKind: extension.identifier.value === 'vscodevim.vim'
|
||||
? [ 'web' ]
|
||||
: extension.extensionKind,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return Array.from(uniqueExtensions.values());
|
||||
});
|
||||
}
|
||||
|
||||
private getDiagnosticInfo(): Promise<IDiagnosticInfo> {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
private async disableTelemetry(): Promise<void> {
|
||||
this.telemetry.setEnabled(false);
|
||||
}
|
||||
|
||||
private async logTelemetry(eventName: string, data: ITelemetryData): Promise<void> {
|
||||
this.telemetry.publicLog(eventName, data);
|
||||
}
|
||||
|
||||
private async flushTelemetry(): Promise<void> {
|
||||
// We always send immediately at the moment.
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeProxyService implements INodeProxyService {
|
||||
public _serviceBrand = undefined;
|
||||
|
||||
public readonly server: Server;
|
||||
|
||||
private readonly _onMessage = new Emitter<string>();
|
||||
public readonly onMessage = this._onMessage.event;
|
||||
private readonly _$onMessage = new Emitter<string>();
|
||||
public readonly $onMessage = this._$onMessage.event;
|
||||
public readonly _onDown = new Emitter<void>();
|
||||
public readonly onDown = this._onDown.event;
|
||||
public readonly _onUp = new Emitter<void>();
|
||||
public readonly onUp = this._onUp.event;
|
||||
|
||||
// Unused because the server connection will never permanently close.
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
public readonly onClose = this._onClose.event;
|
||||
|
||||
public constructor() {
|
||||
// TODO: down/up
|
||||
this.server = new Server({
|
||||
onMessage: this.$onMessage,
|
||||
onClose: this.onClose,
|
||||
onDown: this.onDown,
|
||||
onUp: this.onUp,
|
||||
send: (message: string): void => {
|
||||
this._onMessage.fire(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public send(message: string): void {
|
||||
this._$onMessage.fire(message);
|
||||
}
|
||||
}
|
||||
|
||||
class VariableResolverService extends AbstractVariableResolverService {
|
||||
constructor(
|
||||
remoteAuthority: string,
|
||||
args: terminal.ICreateTerminalProcessArguments,
|
||||
env: platform.IProcessEnvironment,
|
||||
) {
|
||||
super({
|
||||
getFolderUri: (name: string): URI | undefined => {
|
||||
const folder = args.workspaceFolders.find((f) => f.name === name);
|
||||
return folder && URI.revive(folder.uri);
|
||||
},
|
||||
getWorkspaceFolderCount: (): number => {
|
||||
return args.workspaceFolders.length;
|
||||
},
|
||||
// In ../../workbench/contrib/terminal/common/remoteTerminalChannel.ts it
|
||||
// looks like there are `config:` entries which must be for this? Not sure
|
||||
// how/if the URI comes into play though.
|
||||
getConfigurationValue: (_: URI, section: string): string | undefined => {
|
||||
return args.resolvedVariables[`config:${section}`];
|
||||
},
|
||||
getExecPath: (): string | undefined => {
|
||||
// Assuming that resolverEnv is just for use in the resolver and not for
|
||||
// the terminal itself.
|
||||
return (args.resolverEnv && args.resolverEnv['VSCODE_EXEC_PATH']) || env['VSCODE_EXEC_PATH'];
|
||||
},
|
||||
// This is just a guess; this is the only file-related thing we're sent
|
||||
// and none of these resolver methods seem to get called so I don't know
|
||||
// how to test.
|
||||
getFilePath: (): string | undefined => {
|
||||
const resource = transformIncoming(remoteAuthority, args.activeFileResource);
|
||||
if (!resource) {
|
||||
return undefined;
|
||||
}
|
||||
// See ../../editor/standalone/browser/simpleServices.ts;
|
||||
// `BaseConfigurationResolverService` calls `getUriLabel` from there.
|
||||
if (resource.scheme === 'file') {
|
||||
return resource.fsPath;
|
||||
}
|
||||
return resource.path;
|
||||
},
|
||||
// It looks like these are set here although they aren't on the types:
|
||||
// ../../workbench/contrib/terminal/common/remoteTerminalChannel.ts
|
||||
getSelectedText: (): string | undefined => {
|
||||
return args.resolvedVariables.selectedText;
|
||||
},
|
||||
getLineNumber: (): string | undefined => {
|
||||
return args.resolvedVariables.selectedText;
|
||||
},
|
||||
}, undefined, env);
|
||||
}
|
||||
}
|
||||
|
||||
class Terminal {
|
||||
private readonly process: TerminalProcess;
|
||||
private _pid: number = -1;
|
||||
private _title: string = '';
|
||||
public readonly workspaceId: string;
|
||||
public readonly workspaceName: string;
|
||||
private readonly persist: boolean;
|
||||
|
||||
private readonly _onDispose = new Emitter<void>();
|
||||
public get onDispose(): Event<void> { return this._onDispose.event; }
|
||||
|
||||
private _isOrphan = true;
|
||||
public get isOrphan(): boolean { return this._isOrphan; }
|
||||
|
||||
// These are replayed when a client reconnects.
|
||||
private cols: number;
|
||||
private rows: number;
|
||||
private replayData: string[] = [];
|
||||
// This is based on string length and is pretty arbitrary.
|
||||
private readonly maxReplayData = 10000;
|
||||
private totalReplayData = 0;
|
||||
|
||||
// According to the release notes the terminals are supposed to dispose after
|
||||
// a short timeout; in our case we'll use 48 hours so you can get them back
|
||||
// the next day or over the weekend.
|
||||
private disposeTimeout: NodeJS.Timeout | undefined;
|
||||
private disposeDelay = 48 * 60 * 60 * 1000;
|
||||
|
||||
private buffering = false;
|
||||
private readonly _onEvent = new Emitter<terminal.IRemoteTerminalProcessEvent>({
|
||||
// Don't bind to data until something is listening.
|
||||
onFirstListenerAdd: () => {
|
||||
logger.debug('Terminal bound', field('id', this.id));
|
||||
this._isOrphan = false;
|
||||
if (!this.buffering) {
|
||||
this.buffering = true;
|
||||
this.bufferer.startBuffering(this.id, this.process.onProcessData);
|
||||
}
|
||||
},
|
||||
|
||||
// Replay stored events.
|
||||
onFirstListenerDidAdd: () => {
|
||||
// We only need to replay if the terminal is being reconnected which is
|
||||
// true if there is a dispose timeout.
|
||||
if (typeof this.disposeTimeout !== 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.disposeTimeout);
|
||||
this.disposeTimeout = undefined;
|
||||
|
||||
logger.debug('Terminal replaying', field('id', this.id));
|
||||
this._onEvent.fire({
|
||||
type: 'replay',
|
||||
events: [{
|
||||
cols: this.cols,
|
||||
rows: this.rows,
|
||||
data: this.replayData.join(''),
|
||||
}]
|
||||
});
|
||||
},
|
||||
|
||||
onLastListenerRemove: () => {
|
||||
logger.debug('Terminal unbound', field('id', this.id));
|
||||
this._isOrphan = true;
|
||||
if (!this.persist) { // Used by debug consoles.
|
||||
this.dispose();
|
||||
} else {
|
||||
this.disposeTimeout = setTimeout(() => {
|
||||
this.dispose();
|
||||
}, this.disposeDelay);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public get onEvent(): Event<terminal.IRemoteTerminalProcessEvent> { return this._onEvent.event; }
|
||||
|
||||
// Buffer to reduce the number of messages going to the renderer.
|
||||
private readonly bufferer = new TerminalDataBufferer((_, data) => {
|
||||
this._onEvent.fire({
|
||||
type: 'data',
|
||||
data,
|
||||
});
|
||||
|
||||
// No need to store data if we aren't persisting.
|
||||
if (!this.persist) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.replayData.push(data);
|
||||
this.totalReplayData += data.length;
|
||||
|
||||
let overflow = this.totalReplayData - this.maxReplayData;
|
||||
if (overflow <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop events until doing so would put us under budget.
|
||||
let deleteCount = 0;
|
||||
for (; deleteCount < this.replayData.length
|
||||
&& this.replayData[deleteCount].length <= overflow; ++deleteCount) {
|
||||
overflow -= this.replayData[deleteCount].length;
|
||||
}
|
||||
|
||||
if (deleteCount > 0) {
|
||||
this.replayData.splice(0, deleteCount);
|
||||
}
|
||||
|
||||
// Dropping any more events would put us under budget; trim the first event
|
||||
// instead if still over budget.
|
||||
if (overflow > 0 && this.replayData.length > 0) {
|
||||
this.replayData[0] = this.replayData[0].substring(overflow);
|
||||
}
|
||||
|
||||
this.totalReplayData = this.replayData.reduce((p, c) => p + c.length, 0);
|
||||
});
|
||||
|
||||
public get pid(): number {
|
||||
return this._pid;
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
public readonly id: number,
|
||||
config: IShellLaunchConfig & { cwd: string },
|
||||
args: terminal.ICreateTerminalProcessArguments,
|
||||
env: platform.IProcessEnvironment,
|
||||
logService: ILogService,
|
||||
) {
|
||||
this.workspaceId = args.workspaceId;
|
||||
this.workspaceName = args.workspaceName;
|
||||
|
||||
this.cols = args.cols;
|
||||
this.rows = args.rows;
|
||||
|
||||
// TODO: Don't persist terminals until we make it work with things like
|
||||
// htop, vim, etc.
|
||||
// this.persist = args.shouldPersistTerminal;
|
||||
this.persist = false;
|
||||
|
||||
this.process = new TerminalProcess(
|
||||
config,
|
||||
config.cwd,
|
||||
this.cols,
|
||||
this.rows,
|
||||
env,
|
||||
process.env as platform.IProcessEnvironment, // Environment used for `findExecutable`.
|
||||
false, // windowsEnableConpty: boolean,
|
||||
logService,
|
||||
);
|
||||
|
||||
// The current pid and title aren't exposed so they have to be tracked.
|
||||
this.process.onProcessReady((event) => {
|
||||
this._pid = event.pid;
|
||||
this._onEvent.fire({
|
||||
type: 'ready',
|
||||
pid: event.pid,
|
||||
cwd: event.cwd,
|
||||
});
|
||||
});
|
||||
|
||||
this.process.onProcessTitleChanged((title) => {
|
||||
this._title = title;
|
||||
this._onEvent.fire({
|
||||
type: 'titleChanged',
|
||||
title,
|
||||
});
|
||||
});
|
||||
|
||||
this.process.onProcessExit((exitCode) => {
|
||||
logger.debug('Terminal exited', field('id', this.id), field('code', exitCode));
|
||||
this._onEvent.fire({
|
||||
type: 'exit',
|
||||
exitCode,
|
||||
});
|
||||
this.dispose();
|
||||
});
|
||||
|
||||
// TODO: I think `execCommand` must have something to do with running
|
||||
// commands on the terminal that will do things in VS Code but we already
|
||||
// have that functionality via a socket so I'm not sure what this is for.
|
||||
// type: 'execCommand';
|
||||
// reqId: number;
|
||||
// commandId: string;
|
||||
// commandArgs: any[];
|
||||
|
||||
// TODO: Maybe this is to ask if the terminal is currently attached to
|
||||
// anything? But we already know that on account of whether anything is
|
||||
// listening to our event emitter.
|
||||
// type: 'orphan?';
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
logger.debug('Terminal disposing', field('id', this.id));
|
||||
this._onEvent.dispose();
|
||||
this.bufferer.dispose();
|
||||
this.process.dispose();
|
||||
this.process.shutdown(true);
|
||||
this._onDispose.fire();
|
||||
this._onDispose.dispose();
|
||||
}
|
||||
|
||||
public shutdown(immediate: boolean): void {
|
||||
return this.process.shutdown(immediate);
|
||||
}
|
||||
|
||||
public getCwd(): Promise<string> {
|
||||
return this.process.getCwd();
|
||||
}
|
||||
|
||||
public getInitialCwd(): Promise<string> {
|
||||
return this.process.getInitialCwd();
|
||||
}
|
||||
|
||||
public start(): Promise<ITerminalLaunchError | undefined> {
|
||||
return this.process.start();
|
||||
}
|
||||
|
||||
public input(data: string): void {
|
||||
return this.process.input(data);
|
||||
}
|
||||
|
||||
public resize(cols: number, rows: number): void {
|
||||
this.cols = cols;
|
||||
this.rows = rows;
|
||||
return this.process.resize(cols, rows);
|
||||
}
|
||||
}
|
||||
|
||||
// References: - ../../workbench/api/node/extHostTerminalService.ts
|
||||
// - ../../workbench/contrib/terminal/browser/terminalProcessManager.ts
|
||||
export class TerminalProviderChannel implements IServerChannel<RemoteAgentConnectionContext>, IDisposable {
|
||||
private readonly terminals = new Map<number, Terminal>();
|
||||
private id = 0;
|
||||
|
||||
public constructor (private readonly logService: ILogService) {
|
||||
|
||||
}
|
||||
|
||||
public listen(_: RemoteAgentConnectionContext, event: string, args?: any): Event<any> {
|
||||
switch (event) {
|
||||
case '$onTerminalProcessEvent': return this.onTerminalProcessEvent(args);
|
||||
}
|
||||
|
||||
throw new Error(`Invalid listen '${event}'`);
|
||||
}
|
||||
|
||||
private onTerminalProcessEvent(args: terminal.IOnTerminalProcessEventArguments): Event<terminal.IRemoteTerminalProcessEvent> {
|
||||
return this.getTerminal(args.id).onEvent;
|
||||
}
|
||||
|
||||
public call(context: RemoteAgentConnectionContext, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case '$createTerminalProcess': return this.createTerminalProcess(context.remoteAuthority, args);
|
||||
case '$startTerminalProcess': return this.startTerminalProcess(args);
|
||||
case '$sendInputToTerminalProcess': return this.sendInputToTerminalProcess(args);
|
||||
case '$shutdownTerminalProcess': return this.shutdownTerminalProcess(args);
|
||||
case '$resizeTerminalProcess': return this.resizeTerminalProcess(args);
|
||||
case '$getTerminalInitialCwd': return this.getTerminalInitialCwd(args);
|
||||
case '$getTerminalCwd': return this.getTerminalCwd(args);
|
||||
case '$sendCommandResultToTerminalProcess': return this.sendCommandResultToTerminalProcess(args);
|
||||
case '$orphanQuestionReply': return this.orphanQuestionReply(args[0]);
|
||||
case '$listTerminals': return this.listTerminals(args[0]);
|
||||
}
|
||||
|
||||
throw new Error(`Invalid call '${command}'`);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.terminals.forEach((t) => t.dispose());
|
||||
}
|
||||
|
||||
private async createTerminalProcess(remoteAuthority: string, args: terminal.ICreateTerminalProcessArguments): Promise<terminal.ICreateTerminalProcessResult> {
|
||||
const terminalId = this.id++;
|
||||
logger.debug('Creating terminal', field('id', terminalId), field('terminals', this.terminals.size));
|
||||
|
||||
const shellLaunchConfig: IShellLaunchConfig = {
|
||||
name: args.shellLaunchConfig.name,
|
||||
executable: args.shellLaunchConfig.executable,
|
||||
args: args.shellLaunchConfig.args,
|
||||
// TODO: Should we transform if it's a string as well? The incoming
|
||||
// transform only takes `UriComponents` so I suspect it's not necessary.
|
||||
cwd: typeof args.shellLaunchConfig.cwd !== 'string'
|
||||
? transformIncoming(remoteAuthority, args.shellLaunchConfig.cwd)
|
||||
: args.shellLaunchConfig.cwd,
|
||||
env: args.shellLaunchConfig.env,
|
||||
};
|
||||
|
||||
const activeWorkspaceUri = transformIncoming(remoteAuthority, args.activeWorkspaceFolder?.uri);
|
||||
const activeWorkspace = activeWorkspaceUri && args.activeWorkspaceFolder ? {
|
||||
...args.activeWorkspaceFolder,
|
||||
uri: activeWorkspaceUri,
|
||||
toResource: (relativePath: string) => resources.joinPath(activeWorkspaceUri, relativePath),
|
||||
} : undefined;
|
||||
|
||||
const resolverService = new VariableResolverService(remoteAuthority, args, process.env as platform.IProcessEnvironment);
|
||||
const resolver = terminalEnvironment.createVariableResolver(activeWorkspace, resolverService);
|
||||
|
||||
const getDefaultShellAndArgs = (): { executable: string; args: string[] | string } => {
|
||||
if (shellLaunchConfig.executable) {
|
||||
const executable = resolverService.resolve(activeWorkspace, shellLaunchConfig.executable);
|
||||
let resolvedArgs: string[] | string = [];
|
||||
if (shellLaunchConfig.args && Array.isArray(shellLaunchConfig.args)) {
|
||||
for (const arg of shellLaunchConfig.args) {
|
||||
resolvedArgs.push(resolverService.resolve(activeWorkspace, arg));
|
||||
}
|
||||
} else if (shellLaunchConfig.args) {
|
||||
resolvedArgs = resolverService.resolve(activeWorkspace, shellLaunchConfig.args);
|
||||
}
|
||||
return { executable, args: resolvedArgs };
|
||||
}
|
||||
|
||||
const executable = terminalEnvironment.getDefaultShell(
|
||||
(key) => args.configuration[key],
|
||||
args.isWorkspaceShellAllowed,
|
||||
getSystemShell(platform.platform),
|
||||
process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
|
||||
process.env.windir,
|
||||
resolver,
|
||||
this.logService,
|
||||
false, // useAutomationShell
|
||||
);
|
||||
|
||||
const resolvedArgs = terminalEnvironment.getDefaultShellArgs(
|
||||
(key) => args.configuration[key],
|
||||
args.isWorkspaceShellAllowed,
|
||||
false, // useAutomationShell
|
||||
resolver,
|
||||
this.logService,
|
||||
);
|
||||
|
||||
return { executable, args: resolvedArgs };
|
||||
};
|
||||
|
||||
const getInitialCwd = (): string => {
|
||||
return terminalEnvironment.getCwd(
|
||||
shellLaunchConfig,
|
||||
os.homedir(),
|
||||
resolver,
|
||||
activeWorkspaceUri,
|
||||
args.configuration['terminal.integrated.cwd'],
|
||||
this.logService,
|
||||
);
|
||||
};
|
||||
|
||||
// Use a separate var so Typescript recognizes these properties are no
|
||||
// longer undefined.
|
||||
const resolvedShellLaunchConfig = {
|
||||
...shellLaunchConfig,
|
||||
...getDefaultShellAndArgs(),
|
||||
cwd: getInitialCwd(),
|
||||
};
|
||||
|
||||
logger.debug('Resolved shell launch configuration', field('id', terminalId));
|
||||
|
||||
// Use instead of `terminal.integrated.env.${platform}` to make types work.
|
||||
const getEnvFromConfig = (): terminal.ISingleTerminalConfiguration<ITerminalEnvironment> => {
|
||||
if (platform.isWindows) {
|
||||
return args.configuration['terminal.integrated.env.windows'];
|
||||
} else if (platform.isMacintosh) {
|
||||
return args.configuration['terminal.integrated.env.osx'];
|
||||
}
|
||||
return args.configuration['terminal.integrated.env.linux'];
|
||||
};
|
||||
|
||||
const getNonInheritedEnv = async (): Promise<platform.IProcessEnvironment> => {
|
||||
const env = await getMainProcessParentEnv();
|
||||
env.VSCODE_IPC_HOOK_CLI = process.env['VSCODE_IPC_HOOK_CLI']!;
|
||||
return env;
|
||||
};
|
||||
|
||||
const env = terminalEnvironment.createTerminalEnvironment(
|
||||
shellLaunchConfig,
|
||||
getEnvFromConfig(),
|
||||
resolver,
|
||||
args.isWorkspaceShellAllowed,
|
||||
product.version,
|
||||
args.configuration['terminal.integrated.detectLocale'],
|
||||
args.configuration['terminal.integrated.inheritEnv'] !== false
|
||||
? process.env as platform.IProcessEnvironment
|
||||
: await getNonInheritedEnv()
|
||||
);
|
||||
|
||||
// Apply extension environment variable collections to the environment.
|
||||
if (!shellLaunchConfig.strictEnv) {
|
||||
// They come in an array and in serialized format.
|
||||
const envVariableCollections = new Map<string, IEnvironmentVariableCollection>();
|
||||
for (const [k, v] of args.envVariableCollections) {
|
||||
envVariableCollections.set(k, { map: deserializeEnvironmentVariableCollection(v) });
|
||||
}
|
||||
const mergedCollection = new MergedEnvironmentVariableCollection(envVariableCollections);
|
||||
mergedCollection.applyToProcessEnvironment(env);
|
||||
}
|
||||
|
||||
logger.debug('Resolved terminal environment', field('id', terminalId));
|
||||
|
||||
const terminal = new Terminal(terminalId, resolvedShellLaunchConfig, args, env, this.logService);
|
||||
this.terminals.set(terminalId, terminal);
|
||||
logger.debug('Created terminal', field('id', terminalId));
|
||||
terminal.onDispose(() => this.terminals.delete(terminalId));
|
||||
|
||||
return {
|
||||
terminalId,
|
||||
resolvedShellLaunchConfig,
|
||||
};
|
||||
}
|
||||
|
||||
private getTerminal(id: number): Terminal {
|
||||
const terminal = this.terminals.get(id);
|
||||
if (!terminal) {
|
||||
throw new Error(`terminal with id ${id} does not exist`);
|
||||
}
|
||||
return terminal;
|
||||
}
|
||||
|
||||
private async startTerminalProcess(args: terminal.IStartTerminalProcessArguments): Promise<ITerminalLaunchError | void> {
|
||||
return this.getTerminal(args.id).start();
|
||||
}
|
||||
|
||||
private async sendInputToTerminalProcess(args: terminal.ISendInputToTerminalProcessArguments): Promise<void> {
|
||||
return this.getTerminal(args.id).input(args.data);
|
||||
}
|
||||
|
||||
private async shutdownTerminalProcess(args: terminal.IShutdownTerminalProcessArguments): Promise<void> {
|
||||
return this.getTerminal(args.id).shutdown(args.immediate);
|
||||
}
|
||||
|
||||
private async resizeTerminalProcess(args: terminal.IResizeTerminalProcessArguments): Promise<void> {
|
||||
return this.getTerminal(args.id).resize(args.cols, args.rows);
|
||||
}
|
||||
|
||||
private async getTerminalInitialCwd(args: terminal.IGetTerminalInitialCwdArguments): Promise<string> {
|
||||
return this.getTerminal(args.id).getInitialCwd();
|
||||
}
|
||||
|
||||
private async getTerminalCwd(args: terminal.IGetTerminalCwdArguments): Promise<string> {
|
||||
return this.getTerminal(args.id).getCwd();
|
||||
}
|
||||
|
||||
private async sendCommandResultToTerminalProcess(_: terminal.ISendCommandResultToTerminalProcessArguments): Promise<void> {
|
||||
// NOTE: Not required unless we implement the `execCommand` event, see above.
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
private async orphanQuestionReply(_: terminal.IOrphanQuestionReplyArgs): Promise<void> {
|
||||
// NOTE: Not required unless we implement the `orphan?` event, see above.
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
private async listTerminals(_: terminal.IListTerminalsArgs): Promise<terminal.IRemoteTerminalDescriptionDto[]> {
|
||||
// TODO: args.isInitialization. Maybe this is to have slightly different
|
||||
// behavior when first listing terminals but I don't know what you'd want to
|
||||
// do differently. Maybe it's to reset the terminal dispose timeouts or
|
||||
// something like that, but why not do it each time you list?
|
||||
const terminals = await Promise.all(Array.from(this.terminals).map(async ([id, terminal]) => {
|
||||
const cwd = await terminal.getCwd();
|
||||
return {
|
||||
id,
|
||||
pid: terminal.pid,
|
||||
title: terminal.title,
|
||||
cwd,
|
||||
workspaceId: terminal.workspaceId,
|
||||
workspaceName: terminal.workspaceName,
|
||||
isOrphan: terminal.isOrphan,
|
||||
};
|
||||
}));
|
||||
// Only returned orphaned terminals so we don't end up attaching to
|
||||
// terminals already attached elsewhere.
|
||||
return terminals.filter((t) => t.isOrphan);
|
||||
}
|
||||
}
|
||||
|
||||
function transformIncoming(remoteAuthority: string, uri: UriComponents | undefined): URI | undefined {
|
||||
const transformer = getUriTransformer(remoteAuthority);
|
||||
return uri ? URI.revive(transformer.transformIncoming(uri)) : uri;
|
||||
}
|
192
lib/vscode/src/vs/server/node/connection.ts
Normal file
192
lib/vscode/src/vs/server/node/connection.ts
Normal file
@ -0,0 +1,192 @@
|
||||
import { field, Logger, logger } from '@coder/logger';
|
||||
import * as cp from 'child_process';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { getNlsConfiguration } from 'vs/server/node/nls';
|
||||
import { Protocol } from 'vs/server/node/protocol';
|
||||
import { IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
||||
|
||||
export abstract class Connection {
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
public readonly onClose = this._onClose.event;
|
||||
private disposed = false;
|
||||
private _offline: number | undefined;
|
||||
|
||||
public constructor(protected protocol: Protocol, public readonly token: string) {}
|
||||
|
||||
public get offline(): number | undefined {
|
||||
return this._offline;
|
||||
}
|
||||
|
||||
public reconnect(socket: ISocket, buffer: VSBuffer): void {
|
||||
this._offline = undefined;
|
||||
this.doReconnect(socket, buffer);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this.disposed) {
|
||||
this.disposed = true;
|
||||
this.doDispose();
|
||||
this._onClose.fire();
|
||||
}
|
||||
}
|
||||
|
||||
protected setOffline(): void {
|
||||
if (!this._offline) {
|
||||
this._offline = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the connection on a new socket.
|
||||
*/
|
||||
protected abstract doReconnect(socket: ISocket, buffer: VSBuffer): void;
|
||||
protected abstract doDispose(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for all the IPC channels.
|
||||
*/
|
||||
export class ManagementConnection extends Connection {
|
||||
public constructor(protected protocol: Protocol, token: string) {
|
||||
super(protocol, token);
|
||||
protocol.onClose(() => this.dispose()); // Explicit close.
|
||||
protocol.onSocketClose(() => this.setOffline()); // Might reconnect.
|
||||
}
|
||||
|
||||
protected doDispose(): void {
|
||||
this.protocol.sendDisconnect();
|
||||
this.protocol.dispose();
|
||||
this.protocol.getUnderlyingSocket().destroy();
|
||||
}
|
||||
|
||||
protected doReconnect(socket: ISocket, buffer: VSBuffer): void {
|
||||
this.protocol.beginAcceptReconnection(socket, buffer);
|
||||
this.protocol.endAcceptReconnection();
|
||||
}
|
||||
}
|
||||
|
||||
interface DisconnectedMessage {
|
||||
type: 'VSCODE_EXTHOST_DISCONNECTED';
|
||||
}
|
||||
|
||||
interface ConsoleMessage {
|
||||
type: '__$console';
|
||||
// See bootstrap-fork.js#L135.
|
||||
severity: 'log' | 'warn' | 'error';
|
||||
arguments: any[];
|
||||
}
|
||||
|
||||
type ExtHostMessage = DisconnectedMessage | ConsoleMessage | IExtHostReadyMessage;
|
||||
|
||||
export class ExtensionHostConnection extends Connection {
|
||||
private process?: cp.ChildProcess;
|
||||
private readonly logger: Logger;
|
||||
|
||||
public constructor(
|
||||
locale:string, protocol: Protocol, buffer: VSBuffer, token: string,
|
||||
private readonly environment: INativeEnvironmentService,
|
||||
) {
|
||||
super(protocol, token);
|
||||
this.logger = logger.named('exthost', field('token', token));
|
||||
this.protocol.dispose();
|
||||
this.spawn(locale, buffer).then((p) => this.process = p);
|
||||
this.protocol.getUnderlyingSocket().pause();
|
||||
}
|
||||
|
||||
protected doDispose(): void {
|
||||
if (this.process) {
|
||||
this.process.kill();
|
||||
}
|
||||
this.protocol.getUnderlyingSocket().destroy();
|
||||
}
|
||||
|
||||
protected doReconnect(socket: ISocket, buffer: VSBuffer): void {
|
||||
// This is just to set the new socket.
|
||||
this.protocol.beginAcceptReconnection(socket, null);
|
||||
this.protocol.dispose();
|
||||
this.sendInitMessage(buffer);
|
||||
}
|
||||
|
||||
private sendInitMessage(buffer: VSBuffer): void {
|
||||
const socket = this.protocol.getUnderlyingSocket();
|
||||
socket.pause();
|
||||
this.logger.trace('Sending socket');
|
||||
this.process!.send({ // Process must be set at this point.
|
||||
type: 'VSCODE_EXTHOST_IPC_SOCKET',
|
||||
initialDataChunk: (buffer.buffer as Buffer).toString('base64'),
|
||||
skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
private async spawn(locale: string, buffer: VSBuffer): Promise<cp.ChildProcess> {
|
||||
this.logger.trace('Getting NLS configuration...');
|
||||
const config = await getNlsConfiguration(locale, this.environment.userDataPath);
|
||||
this.logger.trace('Spawning extension host...');
|
||||
const proc = cp.fork(
|
||||
FileAccess.asFileUri('bootstrap-fork', require).fsPath,
|
||||
// While not technically necessary, makes it easier to tell which process
|
||||
// bootstrap-fork is executing. Can also do pkill -f extensionHost
|
||||
// Other spawns in the VS Code codebase behave similarly.
|
||||
[ '--type=extensionHost' ],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',
|
||||
PIPE_LOGGING: 'true',
|
||||
VERBOSE_LOGGING: 'true',
|
||||
VSCODE_EXTHOST_WILL_SEND_SOCKET: 'true',
|
||||
VSCODE_HANDLES_UNCAUGHT_ERRORS: 'true',
|
||||
VSCODE_LOG_STACK: 'false',
|
||||
VSCODE_LOG_LEVEL: process.env.LOG_LEVEL,
|
||||
VSCODE_NLS_CONFIG: JSON.stringify(config),
|
||||
},
|
||||
silent: true,
|
||||
},
|
||||
);
|
||||
|
||||
proc.on('error', (error) => {
|
||||
this.logger.error('Exited unexpectedly', field('error', error));
|
||||
this.dispose();
|
||||
});
|
||||
proc.on('exit', (code) => {
|
||||
this.logger.trace('Exited', field('code', code));
|
||||
this.dispose();
|
||||
});
|
||||
if (proc.stdout && proc.stderr) {
|
||||
proc.stdout.setEncoding('utf8').on('data', (d) => this.logger.info(d));
|
||||
proc.stderr.setEncoding('utf8').on('data', (d) => this.logger.error(d));
|
||||
}
|
||||
|
||||
proc.on('message', (event: ExtHostMessage) => {
|
||||
switch (event.type) {
|
||||
case '__$console':
|
||||
const fn = this.logger[event.severity === 'log' ? 'info' : event.severity];
|
||||
if (fn) {
|
||||
fn.bind(this.logger)('console', field('arguments', event.arguments));
|
||||
} else {
|
||||
this.logger.error('Unexpected severity', field('event', event));
|
||||
}
|
||||
break;
|
||||
case 'VSCODE_EXTHOST_DISCONNECTED':
|
||||
this.logger.trace('Going offline');
|
||||
this.setOffline();
|
||||
break;
|
||||
case 'VSCODE_EXTHOST_IPC_READY':
|
||||
this.logger.trace('Got ready message');
|
||||
this.sendInitMessage(buffer);
|
||||
break;
|
||||
default:
|
||||
this.logger.error('Unexpected message', field('event', event));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.logger.trace('Waiting for handshake...');
|
||||
return proc;
|
||||
}
|
||||
}
|
124
lib/vscode/src/vs/server/node/insights.ts
Normal file
124
lib/vscode/src/vs/server/node/insights.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import * as appInsights from 'applicationinsights';
|
||||
import * as https from 'https';
|
||||
import * as http from 'http';
|
||||
import * as os from 'os';
|
||||
|
||||
class Channel {
|
||||
public get _sender() {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
public get _buffer() {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public setUseDiskRetryCaching(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
public send(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
public triggerSend(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
}
|
||||
|
||||
export class TelemetryClient {
|
||||
public context: any = undefined;
|
||||
public commonProperties: any = undefined;
|
||||
public config: any = {};
|
||||
|
||||
public channel: any = new Channel();
|
||||
|
||||
public addTelemetryProcessor(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public clearTelemetryProcessors(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public runTelemetryProcessors(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public trackTrace(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public trackMetric(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public trackException(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public trackRequest(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public trackDependency(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public track(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public trackNodeHttpRequestSync(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public trackNodeHttpRequest(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public trackNodeHttpDependency(): void {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
public trackEvent(options: appInsights.Contracts.EventTelemetry): void {
|
||||
if (!options.properties) {
|
||||
options.properties = {};
|
||||
}
|
||||
if (!options.measurements) {
|
||||
options.measurements = {};
|
||||
}
|
||||
|
||||
try {
|
||||
const cpus = os.cpus();
|
||||
options.measurements.cores = cpus.length;
|
||||
options.properties['common.cpuModel'] = cpus[0].model;
|
||||
} catch (error) {}
|
||||
|
||||
try {
|
||||
options.measurements.memoryFree = os.freemem();
|
||||
options.measurements.memoryTotal = os.totalmem();
|
||||
} catch (error) {}
|
||||
|
||||
try {
|
||||
options.properties['common.shell'] = os.userInfo().shell;
|
||||
options.properties['common.release'] = os.release();
|
||||
options.properties['common.arch'] = os.arch();
|
||||
} catch (error) {}
|
||||
|
||||
try {
|
||||
const url = process.env.TELEMETRY_URL || 'https://v1.telemetry.coder.com/track';
|
||||
const request = (/^http:/.test(url) ? http : https).request(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
request.on('error', () => { /* We don't care. */ });
|
||||
request.write(JSON.stringify(options));
|
||||
request.end();
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
public flush(options: { callback: (v: string) => void }): void {
|
||||
if (options.callback) {
|
||||
options.callback('');
|
||||
}
|
||||
}
|
||||
}
|
61
lib/vscode/src/vs/server/node/ipc.ts
Normal file
61
lib/vscode/src/vs/server/node/ipc.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import * as cp from 'child_process';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
enum ControlMessage {
|
||||
okToChild = 'ok>',
|
||||
okFromChild = 'ok<',
|
||||
}
|
||||
|
||||
interface RelaunchMessage {
|
||||
type: 'relaunch';
|
||||
version: string;
|
||||
}
|
||||
|
||||
export type Message = RelaunchMessage;
|
||||
|
||||
class IpcMain {
|
||||
protected readonly _onMessage = new Emitter<Message>();
|
||||
public readonly onMessage = this._onMessage.event;
|
||||
|
||||
public handshake(child?: cp.ChildProcess): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const target = child || process;
|
||||
if (!target.send) {
|
||||
throw new Error('Not spawned with IPC enabled');
|
||||
}
|
||||
target.on('message', (message) => {
|
||||
if (message === child ? ControlMessage.okFromChild : ControlMessage.okToChild) {
|
||||
target.removeAllListeners();
|
||||
target.on('message', (msg) => this._onMessage.fire(msg));
|
||||
if (child) {
|
||||
target.send!(ControlMessage.okToChild);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
if (child) {
|
||||
child.once('error', reject);
|
||||
child.once('exit', (code) => {
|
||||
const error = new Error(`Unexpected exit with code ${code}`);
|
||||
(error as any).code = code;
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
target.send(ControlMessage.okFromChild);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public relaunch(version: string): void {
|
||||
this.send({ type: 'relaunch', version });
|
||||
}
|
||||
|
||||
private send(message: Message): void {
|
||||
if (!process.send) {
|
||||
throw new Error('Not a child process with IPC enabled');
|
||||
}
|
||||
process.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
export const ipcMain = new IpcMain();
|
2
lib/vscode/src/vs/server/node/logger.ts
Normal file
2
lib/vscode/src/vs/server/node/logger.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { logger as baseLogger } from '@coder/logger';
|
||||
export const logger = baseLogger.named('vscode');
|
174
lib/vscode/src/vs/server/node/marketplace.ts
Normal file
174
lib/vscode/src/vs/server/node/marketplace.ts
Normal file
@ -0,0 +1,174 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as tarStream from 'tar-stream';
|
||||
import * as util from 'util';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { mkdirp } from 'vs/base/node/pfs';
|
||||
import * as vszip from 'vs/base/node/zip';
|
||||
import * as nls from 'vs/nls';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
|
||||
// We will be overriding these, so keep a reference to the original.
|
||||
const vszipExtract = vszip.extract;
|
||||
const vszipBuffer = vszip.buffer;
|
||||
|
||||
export interface IExtractOptions {
|
||||
overwrite?: boolean;
|
||||
/**
|
||||
* Source path within the TAR/ZIP archive. Only the files
|
||||
* contained in this path will be extracted.
|
||||
*/
|
||||
sourcePath?: string;
|
||||
}
|
||||
|
||||
export interface IFile {
|
||||
path: string;
|
||||
contents?: Buffer | string;
|
||||
localPath?: string;
|
||||
}
|
||||
|
||||
export const tar = async (tarPath: string, files: IFile[]): Promise<string> => {
|
||||
const pack = tarStream.pack();
|
||||
const chunks: Buffer[] = [];
|
||||
const ended = new Promise<Buffer>((resolve) => {
|
||||
pack.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
});
|
||||
pack.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
pack.entry({ name: file.path }, file.contents);
|
||||
}
|
||||
pack.finalize();
|
||||
await util.promisify(fs.writeFile)(tarPath, await ended);
|
||||
return tarPath;
|
||||
};
|
||||
|
||||
export const extract = async (archivePath: string, extractPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> => {
|
||||
try {
|
||||
await extractTar(archivePath, extractPath, options, token);
|
||||
} catch (error) {
|
||||
if (error.toString().includes('Invalid tar header')) {
|
||||
await vszipExtract(archivePath, extractPath, options, token);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const buffer = (targetPath: string, filePath: string): Promise<Buffer> => {
|
||||
return new Promise<Buffer>(async (resolve, reject) => {
|
||||
try {
|
||||
let done: boolean = false;
|
||||
await extractAssets(targetPath, new RegExp(filePath), (assetPath: string, data: Buffer) => {
|
||||
if (path.normalize(assetPath) === path.normalize(filePath)) {
|
||||
done = true;
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
if (!done) {
|
||||
throw new Error('couldn\'t find asset ' + filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.toString().includes('Invalid tar header')) {
|
||||
vszipBuffer(targetPath, filePath).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const extractAssets = async (tarPath: string, match: RegExp, callback: (path: string, data: Buffer) => void): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject): void => {
|
||||
const extractor = tarStream.extract();
|
||||
const fail = (error: Error) => {
|
||||
extractor.destroy();
|
||||
reject(error);
|
||||
};
|
||||
extractor.once('error', fail);
|
||||
extractor.on('entry', async (header, stream, next) => {
|
||||
const name = header.name;
|
||||
if (match.test(name)) {
|
||||
extractData(stream).then((data) => {
|
||||
callback(name, data);
|
||||
next();
|
||||
}).catch(fail);
|
||||
} else {
|
||||
stream.on('end', () => next());
|
||||
stream.resume(); // Just drain it.
|
||||
}
|
||||
});
|
||||
extractor.on('finish', resolve);
|
||||
fs.createReadStream(tarPath).pipe(extractor);
|
||||
});
|
||||
};
|
||||
|
||||
const extractData = (stream: NodeJS.ReadableStream): Promise<Buffer> => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
const fileData: Buffer[] = [];
|
||||
stream.on('error', reject);
|
||||
stream.on('end', () => resolve(Buffer.concat(fileData)));
|
||||
stream.on('data', (data) => fileData.push(data));
|
||||
});
|
||||
};
|
||||
|
||||
const extractTar = async (tarPath: string, targetPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject): void => {
|
||||
const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : '');
|
||||
const extractor = tarStream.extract();
|
||||
const fail = (error: Error) => {
|
||||
extractor.destroy();
|
||||
reject(error);
|
||||
};
|
||||
extractor.once('error', fail);
|
||||
extractor.on('entry', async (header, stream, next) => {
|
||||
const nextEntry = (): void => {
|
||||
stream.on('end', () => next());
|
||||
stream.resume();
|
||||
};
|
||||
|
||||
const rawName = path.normalize(header.name);
|
||||
if (token.isCancellationRequested || !sourcePathRegex.test(rawName)) {
|
||||
return nextEntry();
|
||||
}
|
||||
|
||||
const fileName = rawName.replace(sourcePathRegex, '');
|
||||
const targetFileName = path.join(targetPath, fileName);
|
||||
if (/\/$/.test(fileName)) {
|
||||
return mkdirp(targetFileName).then(nextEntry);
|
||||
}
|
||||
|
||||
const dirName = path.dirname(fileName);
|
||||
const targetDirName = path.join(targetPath, dirName);
|
||||
if (targetDirName.indexOf(targetPath) !== 0) {
|
||||
return fail(new Error(nls.localize('invalid file', 'Error extracting {0}. Invalid file.', fileName)));
|
||||
}
|
||||
|
||||
await mkdirp(targetDirName, undefined);
|
||||
|
||||
const fstream = fs.createWriteStream(targetFileName, { mode: header.mode });
|
||||
fstream.once('close', () => next());
|
||||
fstream.once('error', fail);
|
||||
stream.pipe(fstream);
|
||||
});
|
||||
extractor.once('finish', resolve);
|
||||
fs.createReadStream(tarPath).pipe(extractor);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Override original functionality so we can use a custom marketplace with
|
||||
* either tars or zips.
|
||||
*/
|
||||
export const enableCustomMarketplace = (): void => {
|
||||
(<any>product).extensionsGallery = { // Use `any` to override readonly.
|
||||
serviceUrl: process.env.SERVICE_URL || 'https://extensions.coder.com/api',
|
||||
itemUrl: process.env.ITEM_URL || '',
|
||||
controlUrl: '',
|
||||
recommendationsUrl: '',
|
||||
...(product.extensionsGallery || {}),
|
||||
};
|
||||
|
||||
const target = vszip as typeof vszip;
|
||||
target.zip = tar;
|
||||
target.extract = extract;
|
||||
target.buffer = buffer;
|
||||
};
|
88
lib/vscode/src/vs/server/node/nls.ts
Normal file
88
lib/vscode/src/vs/server/node/nls.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as util from 'util';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import * as lp from 'vs/base/node/languagePacks';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { Translations } from 'vs/workbench/services/extensions/common/extensionPoints';
|
||||
|
||||
const configurations = new Map<string, Promise<lp.NLSConfiguration>>();
|
||||
const metadataPath = path.join(getPathFromAmdModule(require, ''), 'nls.metadata.json');
|
||||
|
||||
export const isInternalConfiguration = (config: lp.NLSConfiguration): config is lp.InternalNLSConfiguration => {
|
||||
return config && !!(<lp.InternalNLSConfiguration>config)._languagePackId;
|
||||
};
|
||||
|
||||
const DefaultConfiguration = {
|
||||
locale: 'en',
|
||||
availableLanguages: {},
|
||||
};
|
||||
|
||||
export const getNlsConfiguration = async (locale: string, userDataPath: string): Promise<lp.NLSConfiguration> => {
|
||||
const id = `${locale}: ${userDataPath}`;
|
||||
if (!configurations.has(id)) {
|
||||
configurations.set(id, new Promise(async (resolve) => {
|
||||
const config = product.commit && await util.promisify(fs.exists)(metadataPath)
|
||||
? await lp.getNLSConfiguration(product.commit, userDataPath, metadataPath, locale)
|
||||
: DefaultConfiguration;
|
||||
if (isInternalConfiguration(config)) {
|
||||
config._languagePackSupport = true;
|
||||
}
|
||||
// If the configuration has no results keep trying since code-server
|
||||
// doesn't restart when a language is installed so this result would
|
||||
// persist (the plugin might not be installed yet or something).
|
||||
if (config.locale !== 'en' && config.locale !== 'en-us' && Object.keys(config.availableLanguages).length === 0) {
|
||||
configurations.delete(id);
|
||||
}
|
||||
resolve(config);
|
||||
}));
|
||||
}
|
||||
return configurations.get(id)!;
|
||||
};
|
||||
|
||||
export const getTranslations = async (locale: string, userDataPath: string): Promise<Translations> => {
|
||||
const config = await getNlsConfiguration(locale, userDataPath);
|
||||
if (isInternalConfiguration(config)) {
|
||||
try {
|
||||
return JSON.parse(await util.promisify(fs.readFile)(config._translationsConfigFile, 'utf8'));
|
||||
} catch (error) { /* Nothing yet. */}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export const getLocaleFromConfig = async (userDataPath: string): Promise<string> => {
|
||||
const files = ['locale.json', 'argv.json'];
|
||||
for (let i = 0; i < files.length; ++i) {
|
||||
try {
|
||||
const localeConfigUri = path.join(userDataPath, 'User', files[i]);
|
||||
const content = stripComments(await util.promisify(fs.readFile)(localeConfigUri, 'utf8'));
|
||||
return JSON.parse(content).locale;
|
||||
} catch (error) { /* Ignore. */ }
|
||||
}
|
||||
return 'en';
|
||||
};
|
||||
|
||||
// Taken from src/main.js in the main VS Code source.
|
||||
const stripComments = (content: string): string => {
|
||||
const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
|
||||
|
||||
return content.replace(regexp, (match, _m1, _m2, m3, m4) => {
|
||||
// Only one of m1, m2, m3, m4 matches
|
||||
if (m3) {
|
||||
// A block comment. Replace with nothing
|
||||
return '';
|
||||
} else if (m4) {
|
||||
// A line comment. If it ends in \r?\n then keep it.
|
||||
const length_1 = m4.length;
|
||||
if (length_1 > 2 && m4[length_1 - 1] === '\n') {
|
||||
return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
} else {
|
||||
// We match a string
|
||||
return match;
|
||||
}
|
||||
});
|
||||
};
|
91
lib/vscode/src/vs/server/node/protocol.ts
Normal file
91
lib/vscode/src/vs/server/node/protocol.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { field } from '@coder/logger';
|
||||
import * as net from 'net';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { AuthRequest, ConnectionTypeRequest, HandshakeMessage } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { logger } from 'vs/server/node/logger';
|
||||
|
||||
export interface SocketOptions {
|
||||
readonly reconnectionToken: string;
|
||||
readonly reconnection: boolean;
|
||||
readonly skipWebSocketFrames: boolean;
|
||||
}
|
||||
|
||||
export class Protocol extends PersistentProtocol {
|
||||
public constructor(socket: net.Socket, public readonly options: SocketOptions) {
|
||||
super(
|
||||
options.skipWebSocketFrames
|
||||
? new NodeSocket(socket)
|
||||
: new WebSocketNodeSocket(new NodeSocket(socket)),
|
||||
);
|
||||
}
|
||||
|
||||
public getUnderlyingSocket(): net.Socket {
|
||||
const socket = this.getSocket();
|
||||
return socket instanceof NodeSocket
|
||||
? socket.socket
|
||||
: (socket as WebSocketNodeSocket).socket.socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a handshake to get a connection request.
|
||||
*/
|
||||
public handshake(): Promise<ConnectionTypeRequest> {
|
||||
logger.trace('Protocol handshake', field('token', this.options.reconnectionToken));
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
logger.error('Handshake timed out', field('token', this.options.reconnectionToken));
|
||||
reject(new Error('timed out'));
|
||||
}, 10000); // Matches the client timeout.
|
||||
|
||||
const handler = this.onControlMessage((rawMessage) => {
|
||||
try {
|
||||
const raw = rawMessage.toString();
|
||||
logger.trace('Protocol message', field('token', this.options.reconnectionToken), field('message', raw));
|
||||
const message = JSON.parse(raw);
|
||||
switch (message.type) {
|
||||
case 'auth':
|
||||
return this.authenticate(message);
|
||||
case 'connectionType':
|
||||
handler.dispose();
|
||||
clearTimeout(timeout);
|
||||
return resolve(message);
|
||||
default:
|
||||
throw new Error('Unrecognized message type');
|
||||
}
|
||||
} catch (error) {
|
||||
handler.dispose();
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Kick off the handshake in case we missed the client's opening shot.
|
||||
// TODO: Investigate why that message seems to get lost.
|
||||
this.authenticate();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: This ignores the authentication process entirely for now.
|
||||
*/
|
||||
private authenticate(_?: AuthRequest): void {
|
||||
this.sendMessage({ type: 'sign', data: '' });
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: implement.
|
||||
*/
|
||||
public tunnel(): void {
|
||||
throw new Error('Tunnel is not implemented yet');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a handshake message. In the case of the extension host, it just sends
|
||||
* back a debug port.
|
||||
*/
|
||||
public sendMessage(message: HandshakeMessage | { debugPort?: number } ): void {
|
||||
this.sendControl(VSBuffer.fromString(JSON.stringify(message)));
|
||||
}
|
||||
}
|
308
lib/vscode/src/vs/server/node/server.ts
Normal file
308
lib/vscode/src/vs/server/node/server.ts
Normal file
@ -0,0 +1,308 @@
|
||||
import { field } from '@coder/logger';
|
||||
import * as fs from 'fs';
|
||||
import * as net from 'net';
|
||||
import * as path from 'path';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getMachineId } from 'vs/base/node/id';
|
||||
import { ClientConnectionEvent, createChannelReceiver, IPCServer, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
|
||||
import { main } from 'vs/code/node/cliProcessMain';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
|
||||
import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
|
||||
import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
|
||||
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
|
||||
import { getLogLevel, ILoggerService, ILogService } from 'vs/platform/log/common/log';
|
||||
import { LoggerChannel } from 'vs/platform/log/common/logIpc';
|
||||
import { LoggerService } from 'vs/platform/log/node/loggerService';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ConnectionType, ConnectionTypeRequest } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { RequestChannel } from 'vs/platform/request/common/requestIpc';
|
||||
import { RequestService } from 'vs/platform/request/node/requestService';
|
||||
import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender';
|
||||
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
||||
import { INodeProxyService, NodeProxyChannel } from 'vs/server/common/nodeProxy';
|
||||
import { TelemetryChannel } from 'vs/server/common/telemetry';
|
||||
import { Query, VscodeOptions, WorkbenchOptions } from 'vs/server/ipc';
|
||||
import { ExtensionEnvironmentChannel, FileProviderChannel, NodeProxyService, TerminalProviderChannel } from 'vs/server/node/channel';
|
||||
import { Connection, ExtensionHostConnection, ManagementConnection } from 'vs/server/node/connection';
|
||||
import { TelemetryClient } from 'vs/server/node/insights';
|
||||
import { logger } from 'vs/server/node/logger';
|
||||
import { getLocaleFromConfig, getNlsConfiguration } from 'vs/server/node/nls';
|
||||
import { Protocol } from 'vs/server/node/protocol';
|
||||
import { getUriTransformer } from 'vs/server/node/util';
|
||||
import { REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel';
|
||||
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel';
|
||||
import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export class Vscode {
|
||||
public readonly _onDidClientConnect = new Emitter<ClientConnectionEvent>();
|
||||
public readonly onDidClientConnect = this._onDidClientConnect.event;
|
||||
private readonly ipc = new IPCServer<RemoteAgentConnectionContext>(this.onDidClientConnect);
|
||||
|
||||
private readonly maxExtraOfflineConnections = 0;
|
||||
private readonly connections = new Map<ConnectionType, Map<string, Connection>>();
|
||||
|
||||
private readonly services = new ServiceCollection();
|
||||
private servicesPromise?: Promise<void>;
|
||||
|
||||
public async cli(args: NativeParsedArgs): Promise<void> {
|
||||
return main(args);
|
||||
}
|
||||
|
||||
public async initialize(options: VscodeOptions): Promise<WorkbenchOptions> {
|
||||
const transformer = getUriTransformer(options.remoteAuthority);
|
||||
if (!this.servicesPromise) {
|
||||
this.servicesPromise = this.initializeServices(options.args);
|
||||
}
|
||||
await this.servicesPromise;
|
||||
const environment = this.services.get(IEnvironmentService) as INativeEnvironmentService;
|
||||
const startPath = options.startPath;
|
||||
const parseUrl = (url: string): URI => {
|
||||
// This might be a fully-specified URL or just a path.
|
||||
try {
|
||||
return URI.parse(url, true);
|
||||
} catch (error) {
|
||||
return URI.from({
|
||||
scheme: Schemas.vscodeRemote,
|
||||
authority: options.remoteAuthority,
|
||||
path: url,
|
||||
});
|
||||
}
|
||||
};
|
||||
return {
|
||||
workbenchWebConfiguration: {
|
||||
workspaceUri: startPath && startPath.workspace ? parseUrl(startPath.url) : undefined,
|
||||
folderUri: startPath && !startPath.workspace ? parseUrl(startPath.url) : undefined,
|
||||
remoteAuthority: options.remoteAuthority,
|
||||
logLevel: getLogLevel(environment),
|
||||
workspaceProvider: {
|
||||
payload: [
|
||||
['userDataPath', environment.userDataPath],
|
||||
['enableProposedApi', JSON.stringify(options.args['enable-proposed-api'] || [])]
|
||||
],
|
||||
},
|
||||
homeIndicator: {
|
||||
href: options.args.home || 'https://github.com/cdr/code-server',
|
||||
icon: 'code',
|
||||
title: localize('home', "Home"),
|
||||
},
|
||||
},
|
||||
remoteUserDataUri: transformer.transformOutgoing(URI.file(environment.userDataPath)),
|
||||
productConfiguration: product,
|
||||
nlsConfiguration: await getNlsConfiguration(environment.args.locale || await getLocaleFromConfig(environment.userDataPath), environment.userDataPath),
|
||||
commit: product.commit || 'development',
|
||||
};
|
||||
}
|
||||
|
||||
public async handleWebSocket(socket: net.Socket, query: Query): Promise<true> {
|
||||
if (!query.reconnectionToken) {
|
||||
throw new Error('Reconnection token is missing from query parameters');
|
||||
}
|
||||
const protocol = new Protocol(socket, {
|
||||
reconnectionToken: <string>query.reconnectionToken,
|
||||
reconnection: query.reconnection === 'true',
|
||||
skipWebSocketFrames: query.skipWebSocketFrames === 'true',
|
||||
});
|
||||
try {
|
||||
await this.connect(await protocol.handshake(), protocol);
|
||||
} catch (error) {
|
||||
protocol.sendMessage({ type: 'error', reason: error.message });
|
||||
protocol.dispose();
|
||||
protocol.getSocket().dispose();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise<void> {
|
||||
if (product.commit && message.commit !== product.commit) {
|
||||
logger.warn(`Version mismatch (${message.commit} instead of ${product.commit})`);
|
||||
}
|
||||
|
||||
switch (message.desiredConnectionType) {
|
||||
case ConnectionType.ExtensionHost:
|
||||
case ConnectionType.Management:
|
||||
if (!this.connections.has(message.desiredConnectionType)) {
|
||||
this.connections.set(message.desiredConnectionType, new Map());
|
||||
}
|
||||
const connections = this.connections.get(message.desiredConnectionType)!;
|
||||
|
||||
const ok = async () => {
|
||||
return message.desiredConnectionType === ConnectionType.ExtensionHost
|
||||
? { debugPort: await this.getDebugPort() }
|
||||
: { type: 'ok' };
|
||||
};
|
||||
|
||||
const token = protocol.options.reconnectionToken;
|
||||
if (protocol.options.reconnection && connections.has(token)) {
|
||||
protocol.sendMessage(await ok());
|
||||
const buffer = protocol.readEntireBuffer();
|
||||
protocol.dispose();
|
||||
return connections.get(token)!.reconnect(protocol.getSocket(), buffer);
|
||||
} else if (protocol.options.reconnection || connections.has(token)) {
|
||||
throw new Error(protocol.options.reconnection
|
||||
? 'Unrecognized reconnection token'
|
||||
: 'Duplicate reconnection token'
|
||||
);
|
||||
}
|
||||
|
||||
logger.debug('New connection', field('token', token));
|
||||
protocol.sendMessage(await ok());
|
||||
|
||||
let connection: Connection;
|
||||
if (message.desiredConnectionType === ConnectionType.Management) {
|
||||
connection = new ManagementConnection(protocol, token);
|
||||
this._onDidClientConnect.fire({
|
||||
protocol, onDidClientDisconnect: connection.onClose,
|
||||
});
|
||||
// TODO: Need a way to match clients with a connection. For now
|
||||
// dispose everything which only works because no extensions currently
|
||||
// utilize long-running proxies.
|
||||
(this.services.get(INodeProxyService) as NodeProxyService)._onUp.fire();
|
||||
connection.onClose(() => (this.services.get(INodeProxyService) as NodeProxyService)._onDown.fire());
|
||||
} else {
|
||||
const buffer = protocol.readEntireBuffer();
|
||||
connection = new ExtensionHostConnection(
|
||||
message.args ? message.args.language : 'en',
|
||||
protocol, buffer, token,
|
||||
this.services.get(IEnvironmentService) as INativeEnvironmentService,
|
||||
);
|
||||
}
|
||||
connections.set(token, connection);
|
||||
connection.onClose(() => {
|
||||
logger.debug('Connection closed', field('token', token));
|
||||
connections.delete(token);
|
||||
});
|
||||
this.disposeOldOfflineConnections(connections);
|
||||
break;
|
||||
case ConnectionType.Tunnel: return protocol.tunnel();
|
||||
default: throw new Error('Unrecognized connection type');
|
||||
}
|
||||
}
|
||||
|
||||
private disposeOldOfflineConnections(connections: Map<string, Connection>): void {
|
||||
const offline = Array.from(connections.values())
|
||||
.filter((connection) => typeof connection.offline !== 'undefined');
|
||||
for (let i = 0, max = offline.length - this.maxExtraOfflineConnections; i < max; ++i) {
|
||||
logger.debug('Disposing offline connection', field('token', offline[i].token));
|
||||
offline[i].dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeServices(args: NativeParsedArgs): Promise<void> {
|
||||
const environmentService = new NativeEnvironmentService(args);
|
||||
// https://github.com/cdr/code-server/issues/1693
|
||||
fs.mkdirSync(environmentService.globalStorageHome.fsPath, { recursive: true });
|
||||
|
||||
const logService = new SpdLogService(RemoteExtensionLogFileName, environmentService.logsPath, getLogLevel(environmentService));
|
||||
const fileService = new FileService(logService);
|
||||
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService));
|
||||
|
||||
const piiPaths = [
|
||||
path.join(environmentService.userDataPath, 'clp'), // Language packs.
|
||||
environmentService.appRoot,
|
||||
environmentService.extensionsPath,
|
||||
environmentService.builtinExtensionsPath,
|
||||
...environmentService.extraExtensionPaths,
|
||||
...environmentService.extraBuiltinExtensionPaths,
|
||||
];
|
||||
|
||||
this.ipc.registerChannel('logger', new LoggerChannel(logService));
|
||||
this.ipc.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel());
|
||||
|
||||
this.services.set(ILogService, logService);
|
||||
this.services.set(IEnvironmentService, environmentService);
|
||||
this.services.set(INativeEnvironmentService, environmentService);
|
||||
this.services.set(ILoggerService, new SyncDescriptor(LoggerService));
|
||||
|
||||
const configurationService = new ConfigurationService(environmentService.settingsResource, fileService);
|
||||
await configurationService.initialize();
|
||||
this.services.set(IConfigurationService, configurationService);
|
||||
|
||||
this.services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
this.services.set(IFileService, fileService);
|
||||
this.services.set(IProductService, { _serviceBrand: undefined, ...product });
|
||||
|
||||
const machineId = await getMachineId();
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const instantiationService = new InstantiationService(this.services);
|
||||
|
||||
instantiationService.invokeFunction((accessor) => {
|
||||
instantiationService.createInstance(LogsDataCleaner);
|
||||
|
||||
let telemetryService: ITelemetryService;
|
||||
if (!environmentService.disableTelemetry) {
|
||||
telemetryService = new TelemetryService({
|
||||
appender: combinedAppender(
|
||||
new AppInsightsAppender('code-server', null, () => new TelemetryClient() as any),
|
||||
new TelemetryLogAppender(accessor.get(ILoggerService), environmentService)
|
||||
),
|
||||
sendErrorTelemetry: true,
|
||||
commonProperties: resolveCommonProperties(
|
||||
product.commit, product.version, machineId,
|
||||
[], environmentService.installSourcePath, 'code-server',
|
||||
),
|
||||
piiPaths,
|
||||
}, configurationService);
|
||||
} else {
|
||||
telemetryService = NullTelemetryService;
|
||||
}
|
||||
|
||||
this.services.set(ITelemetryService, telemetryService);
|
||||
|
||||
this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
|
||||
this.services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
|
||||
this.services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));
|
||||
this.services.set(INodeProxyService, new SyncDescriptor(NodeProxyService));
|
||||
|
||||
this.ipc.registerChannel('extensions', new ExtensionManagementChannel(
|
||||
accessor.get(IExtensionManagementService),
|
||||
(context) => getUriTransformer(context.remoteAuthority),
|
||||
));
|
||||
this.ipc.registerChannel('remoteextensionsenvironment', new ExtensionEnvironmentChannel(
|
||||
environmentService, logService, telemetryService, '',
|
||||
));
|
||||
this.ipc.registerChannel('request', new RequestChannel(accessor.get(IRequestService)));
|
||||
this.ipc.registerChannel('telemetry', new TelemetryChannel(telemetryService));
|
||||
this.ipc.registerChannel('nodeProxy', new NodeProxyChannel(accessor.get(INodeProxyService)));
|
||||
this.ipc.registerChannel('localizations', <IServerChannel<any>>createChannelReceiver(accessor.get(ILocalizationsService)));
|
||||
this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, new FileProviderChannel(environmentService, logService));
|
||||
this.ipc.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new TerminalProviderChannel(logService));
|
||||
resolve(new ErrorTelemetry(telemetryService));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: implement.
|
||||
*/
|
||||
private async getDebugPort(): Promise<number | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
}
|
13
lib/vscode/src/vs/server/node/util.ts
Normal file
13
lib/vscode/src/vs/server/node/util.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { URITransformer } from 'vs/base/common/uriIpc';
|
||||
|
||||
export const getUriTransformer = (remoteAuthority: string): URITransformer => {
|
||||
return new URITransformer(remoteAuthority);
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode a path for opening via the folder or workspace query parameter. This
|
||||
* preserves slashes so it can be edited by hand more easily.
|
||||
*/
|
||||
export const encodePath = (path: string): string => {
|
||||
return path.split('/').map((p) => encodeURIComponent(p)).join('/');
|
||||
};
|
@ -61,6 +61,7 @@ import './mainThreadComments';
|
||||
import './mainThreadNotebook';
|
||||
import './mainThreadTask';
|
||||
import './mainThreadLabelService';
|
||||
import 'vs/server/browser/mainThreadNodeProxy';
|
||||
import './mainThreadTunnelService';
|
||||
import './mainThreadAuthentication';
|
||||
import './mainThreadTimeline';
|
||||
|
@ -62,11 +62,11 @@ export class MainThreadStorage implements MainThreadStorageShape {
|
||||
return JSON.parse(jsonValue);
|
||||
}
|
||||
|
||||
$setValue(shared: boolean, key: string, value: object): Promise<void> {
|
||||
async $setValue(shared: boolean, key: string, value: object): Promise<void> {
|
||||
let jsonValue: string;
|
||||
try {
|
||||
jsonValue = JSON.stringify(value);
|
||||
this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE);
|
||||
await this._storageService.store(key, jsonValue, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE);
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransf
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
|
||||
import { IExtHostNodeProxy } from 'vs/server/browser/extHostNodeProxy';
|
||||
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming';
|
||||
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
|
||||
@ -103,6 +104,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostStorage = accessor.get(IExtHostStorage);
|
||||
const extensionStoragePaths = accessor.get(IExtensionStoragePaths);
|
||||
const extHostLogService = accessor.get(ILogService);
|
||||
const extHostNodeProxy = accessor.get(IExtHostNodeProxy);
|
||||
const extHostTunnelService = accessor.get(IExtHostTunnelService);
|
||||
const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService);
|
||||
const extHostWindow = accessor.get(IExtHostWindow);
|
||||
@ -114,6 +116,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostNodeProxy, extHostNodeProxy);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostTunnelService, extHostTunnelService);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostWindow, extHostWindow);
|
||||
|
||||
|
@ -816,6 +816,17 @@ export interface MainThreadLabelServiceShape extends IDisposable {
|
||||
$unregisterResourceLabelFormatter(handle: number): void;
|
||||
}
|
||||
|
||||
export interface MainThreadNodeProxyShape extends IDisposable {
|
||||
$send(message: string): void;
|
||||
$fetchExtension(extensionUri: UriComponents): Promise<VSBuffer>;
|
||||
}
|
||||
export interface ExtHostNodeProxyShape {
|
||||
$onMessage(message: string): void;
|
||||
$onClose(): void;
|
||||
$onDown(): void;
|
||||
$onUp(): void;
|
||||
}
|
||||
|
||||
export interface MainThreadSearchShape extends IDisposable {
|
||||
$registerFileSearchProvider(handle: number, scheme: string): void;
|
||||
$registerTextSearchProvider(handle: number, scheme: string): void;
|
||||
@ -1796,6 +1807,7 @@ export const MainContext = {
|
||||
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
|
||||
MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService'),
|
||||
MainThreadNotebook: createMainId<MainThreadNotebookShape>('MainThreadNotebook'),
|
||||
MainThreadNodeProxy: createMainId<MainThreadNodeProxyShape>('MainThreadNodeProxy'),
|
||||
MainThreadTheming: createMainId<MainThreadThemingShape>('MainThreadTheming'),
|
||||
MainThreadTunnelService: createMainId<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
|
||||
MainThreadTimeline: createMainId<MainThreadTimelineShape>('MainThreadTimeline')
|
||||
@ -1838,6 +1850,7 @@ export const ExtHostContext = {
|
||||
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
|
||||
ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
|
||||
ExtHostNotebook: createMainId<ExtHostNotebookShape>('ExtHostNotebook'),
|
||||
ExtHostNodeProxy: createMainId<ExtHostNodeProxyShape>('ExtHostNodeProxy'),
|
||||
ExtHostTheming: createMainId<ExtHostThemingShape>('ExtHostTheming'),
|
||||
ExtHostTunnelService: createMainId<ExtHostTunnelServiceShape>('ExtHostTunnelService'),
|
||||
ExtHostAuthentication: createMainId<ExtHostAuthenticationShape>('ExtHostAuthentication'),
|
||||
|
@ -31,6 +31,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData
|
||||
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IExtHostNodeProxy } from 'vs/server/browser/extHostNodeProxy';
|
||||
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
@ -82,6 +83,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
protected readonly _extHostWorkspace: ExtHostWorkspace;
|
||||
protected readonly _extHostConfiguration: ExtHostConfiguration;
|
||||
protected readonly _logService: ILogService;
|
||||
protected readonly _nodeProxy: IExtHostNodeProxy;
|
||||
protected readonly _extHostTunnelService: IExtHostTunnelService;
|
||||
protected readonly _extHostTerminalService: IExtHostTerminalService;
|
||||
|
||||
@ -114,6 +116,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
@ILogService logService: ILogService,
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService,
|
||||
@IExtensionStoragePaths storagePath: IExtensionStoragePaths,
|
||||
@IExtHostNodeProxy nodeProxy: IExtHostNodeProxy,
|
||||
@IExtHostTunnelService extHostTunnelService: IExtHostTunnelService,
|
||||
@IExtHostTerminalService extHostTerminalService: IExtHostTerminalService
|
||||
) {
|
||||
@ -125,6 +128,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
this._extHostWorkspace = extHostWorkspace;
|
||||
this._extHostConfiguration = extHostConfiguration;
|
||||
this._logService = logService;
|
||||
this._nodeProxy = nodeProxy;
|
||||
this._extHostTunnelService = extHostTunnelService;
|
||||
this._extHostTerminalService = extHostTerminalService;
|
||||
this._disposables = new DisposableStore();
|
||||
@ -362,7 +366,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
|
||||
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
|
||||
return Promise.all([
|
||||
this._loadCommonJSModule<IExtensionModule>(joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder),
|
||||
this._loadCommonJSModule<IExtensionModule>(joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder, !extensionDescription.browser),
|
||||
this._loadExtensionContext(extensionDescription)
|
||||
]).then(values => {
|
||||
return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder);
|
||||
@ -754,7 +758,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
|
||||
protected abstract _beforeAlmostReadyToRunExtensions(): Promise<void>;
|
||||
protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined;
|
||||
protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
|
||||
protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder, isRemote?: boolean): Promise<T>;
|
||||
public abstract $setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IExtHostNodeProxy } from 'vs/server/browser/extHostNodeProxy';
|
||||
import { NotImplementedProxy } from 'vs/base/common/types';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ExtHostOutputService2 } from 'vs/workbench/api/node/extHostOutputService';
|
||||
import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService';
|
||||
@ -36,3 +38,4 @@ registerSingleton(IExtHostSearch, NativeExtHostSearch);
|
||||
registerSingleton(IExtHostTask, ExtHostTask);
|
||||
registerSingleton(IExtHostTerminalService, ExtHostTerminalService);
|
||||
registerSingleton(IExtHostTunnelService, ExtHostTunnelService);
|
||||
registerSingleton(IExtHostNodeProxy, class extends NotImplementedProxy<IExtHostNodeProxy>(String(IExtHostNodeProxy)) { whenReady = Promise.resolve(); });
|
||||
|
@ -11,6 +11,8 @@ import { IWindowOpenable, IOpenWindowOptions } from 'vs/platform/windows/common/
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { tmpdir } from 'os';
|
||||
|
||||
export interface OpenCommandPipeArgs {
|
||||
type: 'open';
|
||||
@ -58,6 +60,11 @@ export class CLIServerBase {
|
||||
}
|
||||
|
||||
private async setup(): Promise<string> {
|
||||
// NOTE@coder: Write this out so we can get the most recent path.
|
||||
fs.promises.writeFile(join(tmpdir(), 'vscode-ipc'), this._ipcHandlePath).catch((error) => {
|
||||
this.logService.error(error);
|
||||
});
|
||||
|
||||
try {
|
||||
this._server.listen(this.ipcHandlePath);
|
||||
this._server.on('error', err => this.logService.error(err));
|
||||
|
@ -8,6 +8,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService';
|
||||
import { ExtHostNodeProxy, IExtHostNodeProxy } from 'vs/server/browser/extHostNodeProxy';
|
||||
|
||||
// #########################################################################
|
||||
// ### ###
|
||||
@ -17,3 +18,4 @@ import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService';
|
||||
|
||||
registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
|
||||
registerSingleton(ILogService, ExtHostLogService);
|
||||
registerSingleton(IExtHostNodeProxy, ExtHostNodeProxy);
|
||||
|
@ -11,6 +11,7 @@ import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterc
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { loadCommonJSModule } from 'vs/server/browser/worker';
|
||||
|
||||
class WorkerRequireInterceptor extends RequireInterceptor {
|
||||
|
||||
@ -46,10 +47,15 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
}
|
||||
|
||||
protected _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined {
|
||||
return extensionDescription.browser;
|
||||
// NOTE@coder: We can support regular Node modules as well. These will just
|
||||
// require the root of the extension.
|
||||
return extensionDescription.browser || '.';
|
||||
}
|
||||
|
||||
protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder, isRemote?: boolean): Promise<T> {
|
||||
if (isRemote) {
|
||||
return loadCommonJSModule(module, activationTimesBuilder, this._nodeProxy, this._logService, this._fakeModules!.getModule('vscode', module));
|
||||
}
|
||||
|
||||
module = module.with({ path: ensureSuffix(module.path, '.js') });
|
||||
const response = await fetch(module.toString(true));
|
||||
|
@ -311,3 +311,6 @@ actionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPreviousP
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(GoHomeContributor, LifecyclePhase.Ready);
|
||||
export const _1 = workbenchRegistry;
|
||||
export const _2 = GoHomeContributor;
|
||||
export const _3 = LifecyclePhase.Ready;
|
||||
|
@ -55,6 +55,10 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
order: -1;
|
||||
|
||||
/* NOTE@coder: Hide since it doesn't seem to do anything when used with
|
||||
code-server except open the VS Code repository. */
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar > .content > .home-bar > .home-bar-icon-badge {
|
||||
|
@ -43,6 +43,7 @@ import { FileLogService } from 'vs/platform/log/common/fileLogService';
|
||||
import { toLocalISOString } from 'vs/base/common/date';
|
||||
import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
|
||||
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
|
||||
import { initialize } from 'vs/server/browser/client';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
@ -101,6 +102,8 @@ class BrowserMain extends Disposable {
|
||||
// Startup
|
||||
const instantiationService = workbench.startup();
|
||||
|
||||
await initialize(services.serviceCollection);
|
||||
|
||||
// Return API Facade
|
||||
return instantiationService.invokeFunction(accessor => {
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
@ -15,6 +15,7 @@ import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export class ResourceContextKey extends Disposable implements IContextKey<URI> {
|
||||
|
||||
@ -74,7 +75,8 @@ export class ResourceContextKey extends Disposable implements IContextKey<URI> {
|
||||
if (!ResourceContextKey._uriEquals(this._resourceKey.get(), value)) {
|
||||
this._contextKeyService.bufferChangeEvents(() => {
|
||||
this._resourceKey.set(value);
|
||||
this._schemeKey.set(value ? value.scheme : null);
|
||||
// NOTE@coder: Fixes source control context menus (#1104).
|
||||
this._schemeKey.set(value ? (value.scheme === Schemas.vscodeRemote ? Schemas.file : value.scheme) : null);
|
||||
this._filenameKey.set(value ? basename(value) : null);
|
||||
this._dirnameKey.set(value ? dirname(value).fsPath : null);
|
||||
this._pathKey.set(value ? value.fsPath : null);
|
||||
|
@ -778,17 +778,30 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution {
|
||||
}
|
||||
switch (e.type) {
|
||||
case PersistentConnectionEventType.ConnectionLost:
|
||||
break;
|
||||
case PersistentConnectionEventType.ReconnectionWait:
|
||||
const BACKGROUND_RECONNECT_THRESHOLD = 2;
|
||||
// If the first reconnect fails, we show the popup.
|
||||
// This corresponds to about 5s wait.
|
||||
if (e.connectionAttempt < BACKGROUND_RECONNECT_THRESHOLD) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!visibleProgress) {
|
||||
visibleProgress = showProgress(ProgressLocation.Dialog, [reconnectButton, reloadButton]);
|
||||
}
|
||||
visibleProgress.report(nls.localize('connectionLost', "Connection Lost"));
|
||||
break;
|
||||
case PersistentConnectionEventType.ReconnectionWait:
|
||||
|
||||
reconnectWaitEvent = e;
|
||||
visibleProgress = showProgress(lastLocation || ProgressLocation.Notification, [reconnectButton, reloadButton]);
|
||||
visibleProgress.startTimer(Date.now() + 1000 * e.durationSeconds);
|
||||
break;
|
||||
case PersistentConnectionEventType.ReconnectionRunning:
|
||||
if (!visibleProgress) {
|
||||
// Our background reconnection threshold hasn't been hit yet.
|
||||
break;
|
||||
}
|
||||
|
||||
visibleProgress = showProgress(lastLocation || ProgressLocation.Notification, [reloadButton]);
|
||||
visibleProgress.report(nls.localize('reconnectionRunning', "Attempting to reconnect..."));
|
||||
|
||||
|
@ -149,9 +149,11 @@
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
||||
flex-grow: 100;
|
||||
}
|
||||
/* NOTE@coder: Causes the label to shrink to zero width in Firefox due to
|
||||
* overflow:hidden. This isn't right anyway, as far as I can tell. */
|
||||
/* .scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { */
|
||||
/* flex-grow: 100; */
|
||||
/* } */
|
||||
|
||||
.scm-view .monaco-list .monaco-list-row .resource-group > .actions,
|
||||
.scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
||||
|
@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export default () => `
|
||||
@ -11,7 +12,7 @@ export default () => `
|
||||
<div class="welcomePage" role="document">
|
||||
<div class="title">
|
||||
<h1 class="caption">${escape(localize('welcomePage.vscode', "Visual Studio Code"))}</h1>
|
||||
<p class="subtitle detail">${escape(localize({ key: 'welcomePage.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))}</p>
|
||||
<p class="subtitle detail">VS Code v${product.version}</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="splash">
|
||||
@ -32,6 +33,19 @@ export default () => `
|
||||
</ul>
|
||||
<p class="none detail">${escape(localize('welcomePage.noRecentFolders', "No recent folders"))}</p>
|
||||
</div>
|
||||
<div class="section help">
|
||||
<h2 class="caption">code-server ${escape(localize('welcomePage.help', "Help"))}</h2>
|
||||
<ul>
|
||||
<li><a href="https://github.com/cdr/code-server">GitHub Repository</a></li>
|
||||
<li><a href="https://github.com/cdr/code-server/releases/tag/v${product.codeServerVersion}">Release Notes</a></li>
|
||||
<li><a href="https://github.com/cdr/code-server/issues">Issue Tracker</a></li>
|
||||
<li><a href="https://github.com/cdr/code-server/blob/master/doc/FAQ.md">FAQ</a></li>
|
||||
<li><a href="https://github.com/cdr/code-server/blob/master/doc/guide.md">Setup Guide</a></li>
|
||||
<li><a href="https://github.com/cdr/code-server/tree/master/doc">Docs</a></li>
|
||||
<li><a href="https://github.com/cdr/code-server/discussions">Discussions</a></li>
|
||||
<li><a href="https://cdr.co/join-community">Slack</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section help">
|
||||
<h2 class="caption">${escape(localize('welcomePage.help', "Help"))}</h2>
|
||||
<ul>
|
||||
|
@ -94,7 +94,7 @@
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash .section {
|
||||
margin-bottom: 5em;
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash ul {
|
||||
|
@ -328,7 +328,7 @@ class WelcomePage extends Disposable {
|
||||
|
||||
const prodName = container.querySelector('.welcomePage .title .caption') as HTMLElement;
|
||||
if (prodName) {
|
||||
prodName.textContent = this.productService.nameLong;
|
||||
prodName.textContent = `code-server v${this.productService.codeServerVersion}`;
|
||||
}
|
||||
|
||||
recentlyOpened.then(({ workspaces }) => {
|
||||
|
@ -130,6 +130,8 @@ export class SimpleNativeWorkbenchEnvironmentService implements INativeWorkbench
|
||||
extensionsPath?: string | undefined;
|
||||
extensionsDownloadPath: string = undefined!;
|
||||
builtinExtensionsPath: string = undefined!;
|
||||
extraExtensionPaths: string[] = undefined!;
|
||||
extraBuiltinExtensionPaths: string[] = undefined!;
|
||||
|
||||
driverHandle?: string | undefined;
|
||||
|
||||
|
@ -125,11 +125,12 @@ export class DialogService implements IDialogService {
|
||||
async about(): Promise<void> {
|
||||
const detailString = (useAgo: boolean): string => {
|
||||
return nls.localize('aboutDetail',
|
||||
"Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
|
||||
"code-server: v{4}\n VS Code: v{0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
|
||||
this.productService.version || 'Unknown',
|
||||
this.productService.commit || 'Unknown',
|
||||
this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown',
|
||||
navigator.userAgent
|
||||
navigator.userAgent,
|
||||
this.productService.codeServerVersion || 'Unknown',
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -119,8 +119,25 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
|
||||
@memoize
|
||||
get logFile(): URI { return joinPath(this.options.logsPath, 'window.log'); }
|
||||
|
||||
// NOTE@coder: Use the same path in // ../../../../platform/environment/node/environmentService.ts
|
||||
// and don't use the user data scheme. This solves two problems:
|
||||
// 1. Extensions running in the browser (like Vim) might use these paths
|
||||
// directly instead of using the file service and most likely can't write
|
||||
// to `/User` on disk.
|
||||
// 2. Settings will be stored in the file system instead of in browser
|
||||
// storage. Using browser storage makes sharing or seeding settings
|
||||
// between browsers difficult. We may want to revisit this once/if we get
|
||||
// settings sync.
|
||||
@memoize
|
||||
get userRoamingDataHome(): URI { return URI.file('/User').with({ scheme: Schemas.userData }); }
|
||||
get userRoamingDataHome(): URI { return joinPath(URI.file(this.userDataPath).with({ scheme: Schemas.vscodeRemote }), 'User'); }
|
||||
@memoize
|
||||
get userDataPath(): string {
|
||||
const dataPath = this.payload?.get('userDataPath');
|
||||
if (!dataPath) {
|
||||
throw new Error('userDataPath was not provided to environment service');
|
||||
}
|
||||
return dataPath;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); }
|
||||
@ -301,7 +318,12 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
|
||||
extensionHostDebugEnvironment.params.port = parseInt(value);
|
||||
break;
|
||||
case 'enableProposedApi':
|
||||
extensionHostDebugEnvironment.extensionEnabledProposedApi = [];
|
||||
try {
|
||||
extensionHostDebugEnvironment.extensionEnabledProposedApi = JSON.parse(value);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
extensionHostDebugEnvironment.extensionEnabledProposedApi = [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false; // NOTE@coder: Don't disable anything by extensionKind.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -251,7 +251,9 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
|
||||
|
||||
// Install Language pack on all servers
|
||||
if (isLanguagePackExtension(manifest)) {
|
||||
servers.push(...this.servers);
|
||||
// NOTE@coder: It does not appear language packs can be installed on the web
|
||||
// extension management server at this time. Filter out the web to fix this.
|
||||
servers.push(...this.servers.filter(s => s !== this.extensionManagementServerService.webExtensionManagementServer));
|
||||
} else {
|
||||
const server = this.getExtensionManagementServerToInstall(manifest);
|
||||
if (server) {
|
||||
@ -320,6 +322,11 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
|
||||
return this.extensionManagementServerService.webExtensionManagementServer;
|
||||
}
|
||||
|
||||
// NOTE@coder: Fall back to installing on the remote server.
|
||||
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
|
||||
return this.extensionManagementServerService.remoteExtensionManagementServer;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
if (code === ExtensionHostExitCode.StartTimeout10s) {
|
||||
this._notificationService.prompt(
|
||||
Severity.Error,
|
||||
nls.localize('extensionService.startTimeout', "The Web Worker Extension Host did not start in 10s."),
|
||||
nls.localize('extensionService.startTimeout', 'The Web Worker Extension Host did not start in 10s.'),
|
||||
[]
|
||||
);
|
||||
return;
|
||||
@ -177,8 +177,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
this._remoteAgentService.getEnvironment(),
|
||||
this._remoteAgentService.scanExtensions()
|
||||
]);
|
||||
localExtensions = this._checkEnabledAndProposedAPI(localExtensions);
|
||||
remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions);
|
||||
// NOTE@coder: Include remotely hosted extensions that should run locally.
|
||||
localExtensions = this._checkEnabledAndProposedAPI(localExtensions)
|
||||
.concat(remoteExtensions.filter(ext => !ext.browser && ext.extensionKind && (ext.extensionKind === 'web' || ext.extensionKind.includes('web'))));
|
||||
|
||||
const remoteAgentConnection = this._remoteAgentService.getConnection();
|
||||
this._runningLocation = this._runningLocationClassifier.determineRunningLocation(localExtensions, remoteExtensions);
|
||||
@ -188,7 +190,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
|
||||
const result = this._registry.deltaExtensions(remoteExtensions.concat(localExtensions), []);
|
||||
if (result.removedDueToLooping.length > 0) {
|
||||
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
|
||||
this._logOrShowMessage(Severity.Error, nls.localize('looping', 'The following extensions contain dependency loops and have been disabled: {0}', result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
|
||||
}
|
||||
|
||||
if (remoteEnv && remoteAgentConnection) {
|
||||
|
@ -37,7 +37,8 @@ export function canExecuteOnWorkspace(manifest: IExtensionManifest, productServi
|
||||
|
||||
export function canExecuteOnWeb(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean {
|
||||
const extensionKind = getExtensionKind(manifest, productService, configurationService);
|
||||
return extensionKind.some(kind => kind === 'web');
|
||||
// NOTE@coder: Hardcode vim for now.
|
||||
return extensionKind.some(kind => kind === 'web') || manifest.name === 'vim';
|
||||
}
|
||||
|
||||
export function getExtensionKind(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): ExtensionKind[] {
|
||||
|
@ -16,11 +16,12 @@ import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage, IExtHostReduceGraceTimeMessage, ExtensionHostExitCode } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
||||
import { ExtensionHostMain, IExitFn } from 'vs/workbench/services/extensions/common/extensionHostMain';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc';
|
||||
import { IURITransformer, URITransformer } from 'vs/base/common/uriIpc';
|
||||
import { exists } from 'vs/base/node/pfs';
|
||||
import { realpath } from 'vs/base/node/extpath';
|
||||
import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as proxyAgent from 'vs/base/node/proxy_agent';
|
||||
|
||||
import 'vs/workbench/api/common/extHost.common.services';
|
||||
import 'vs/workbench/api/node/extHost.node.services';
|
||||
@ -57,12 +58,13 @@ const args = minimist(process.argv.slice(2), {
|
||||
const Module = require.__$__nodeRequire('module') as any;
|
||||
const originalLoad = Module._load;
|
||||
|
||||
Module._load = function (request: string) {
|
||||
Module._load = function (request: string, parent: object, isMain: boolean) {
|
||||
if (request === 'natives') {
|
||||
throw new Error('Either the extension or a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more');
|
||||
}
|
||||
|
||||
return originalLoad.apply(this, arguments);
|
||||
// NOTE@coder: Map node_module.asar requests to regular node_modules.
|
||||
return originalLoad.apply(this, [request.replace(/node_modules\.asar(\.unpacked)?/, 'node_modules'), parent, isMain]);
|
||||
};
|
||||
})();
|
||||
|
||||
@ -135,8 +137,11 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
|
||||
|
||||
// Wait for rich client to reconnect
|
||||
protocol.onSocketClose(() => {
|
||||
// The socket has closed, let's give the renderer a certain amount of time to reconnect
|
||||
disconnectRunner1.schedule();
|
||||
// NOTE@coder: Inform the server so we can manage offline
|
||||
// connections there instead. Our goal is to persist connections
|
||||
// forever (to a reasonable point) to account for things like
|
||||
// hibernating overnight.
|
||||
process.send!({ type: 'VSCODE_EXTHOST_DISCONNECTED' });
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -295,6 +300,7 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
|
||||
}
|
||||
|
||||
export async function startExtensionHostProcess(): Promise<void> {
|
||||
proxyAgent.monkeyPatch(true);
|
||||
|
||||
const protocol = await createExtHostProtocol();
|
||||
const renderer = await connectToRenderer(protocol);
|
||||
@ -313,11 +319,9 @@ export async function startExtensionHostProcess(): Promise<void> {
|
||||
|
||||
// Attempt to load uri transformer
|
||||
let uriTransformer: IURITransformer | null = null;
|
||||
if (initData.remote.authority && args.uriTransformerPath) {
|
||||
if (initData.remote.authority) {
|
||||
try {
|
||||
const rawURITransformerFactory = <any>require.__$__nodeRequire(args.uriTransformerPath);
|
||||
const rawURITransformer = <IRawURITransformer>rawURITransformerFactory(initData.remote.authority);
|
||||
uriTransformer = new URITransformer(rawURITransformer);
|
||||
uriTransformer = new URITransformer(initData.remote.authority);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
@ -15,7 +15,11 @@
|
||||
require.config({
|
||||
baseUrl: monacoBaseUrl,
|
||||
catchError: true,
|
||||
createTrustedScriptURL: (value: string) => value
|
||||
createTrustedScriptURL: (value: string) => value,
|
||||
paths: {
|
||||
'@coder/node-browser': `../node_modules/@coder/node-browser/out/client/client.js`,
|
||||
'@coder/requirefs': `../node_modules/@coder/requirefs/out/requirefs.js`,
|
||||
}
|
||||
});
|
||||
|
||||
require(['vs/workbench/services/extensions/worker/extensionHostWorker'], () => { }, err => console.error(err));
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
|
||||
// @ts-ignore: interface is implemented via proxy
|
||||
export class LocalizationsService implements ILocalizationsService {
|
||||
@ -14,9 +14,9 @@ export class LocalizationsService implements ILocalizationsService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
@ISharedProcessService sharedProcessService: ISharedProcessService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
) {
|
||||
return createChannelSender<ILocalizationsService>(sharedProcessService.getChannel('localizations'));
|
||||
return createChannelSender<ILocalizationsService>(remoteAgentService.getConnection()!.getChannel('localizations'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,8 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService';
|
||||
import 'vs/workbench/services/keybinding/browser/keymapService';
|
||||
import 'vs/workbench/services/extensions/browser/extensionService';
|
||||
import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService';
|
||||
import 'vs/workbench/services/telemetry/browser/telemetryService';
|
||||
// NOTE@coder: We send it all to the server side to be processed there instead.
|
||||
// import 'vs/workbench/services/telemetry/browser/telemetryService';
|
||||
import 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
|
||||
import 'vs/workbench/services/credentials/browser/credentialsService';
|
||||
import 'vs/workbench/services/url/browser/urlService';
|
||||
|
@ -140,6 +140,23 @@
|
||||
lodash "^4.17.13"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@coder/logger@1.1.16":
|
||||
version "1.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@coder/logger/-/logger-1.1.16.tgz#ee5b1b188f680733f35c11b065bbd139d618c1e1"
|
||||
integrity sha512-X6VB1++IkosYY6amRAiMvuvCf12NA4+ooX+gOuu5bJIkdjmh4Lz7QpJcWRdgxesvo1msriDDr9E/sDbIWf6vsQ==
|
||||
|
||||
"@coder/node-browser@^1.0.8":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@coder/node-browser/-/node-browser-1.0.8.tgz#c22f581b089ad7d95ad1362fd351c57b7fbc6e70"
|
||||
integrity sha512-NLF9sYMRCN9WK1C224pHax1Cay3qKypg25BhVg7VfNbo3Cpa3daata8RF/rT8JK3lPsu8PmFgDRQjzGC9X1Lrw==
|
||||
|
||||
"@coder/requirefs@^1.1.5":
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@coder/requirefs/-/requirefs-1.1.5.tgz#259db370d563a79a96fb150bc9d69c7db6edc9fb"
|
||||
integrity sha512-3jB47OFCql9+9FI6Vc4YX0cfFnG5rxBfrZUH45S4XYtYGOz+/Xl4h4d2iMk50b7veHkeSWGlB4VHC3UZ16zuYQ==
|
||||
optionalDependencies:
|
||||
jszip "2.6.0"
|
||||
|
||||
"@electron/get@^1.0.1":
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.7.2.tgz#286436a9fb56ff1a1fcdf0e80131fd65f4d1e0fd"
|
||||
@ -172,6 +189,11 @@
|
||||
dependencies:
|
||||
defer-to-connect "^1.0.1"
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
|
||||
|
||||
"@types/applicationinsights@0.20.0":
|
||||
version "0.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/applicationinsights/-/applicationinsights-0.20.0.tgz#fa7b36dc954f635fa9037cad27c378446b1048fb"
|
||||
@ -634,6 +656,13 @@ agent-base@^4.3.0:
|
||||
dependencies:
|
||||
es6-promisify "^5.0.0"
|
||||
|
||||
agent-base@^6.0.0:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
|
||||
dependencies:
|
||||
debug "4"
|
||||
|
||||
agent-base@~4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
||||
@ -1045,6 +1074,13 @@ assign-symbols@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
|
||||
|
||||
ast-types@^0.13.2:
|
||||
version "0.13.4"
|
||||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782"
|
||||
integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==
|
||||
dependencies:
|
||||
tslib "^2.0.1"
|
||||
|
||||
astral-regex@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
|
||||
@ -1464,6 +1500,11 @@ builtin-status-codes@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
||||
integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
|
||||
|
||||
bytes@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
||||
|
||||
cacache@^10.0.4:
|
||||
version "10.0.4"
|
||||
resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460"
|
||||
@ -2365,6 +2406,11 @@ dashdash@^1.12.0:
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
data-uri-to-buffer@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636"
|
||||
integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==
|
||||
|
||||
date-fns@^2.0.1:
|
||||
version "2.14.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.14.0.tgz#359a87a265bb34ef2e38f93ecf63ac453f9bc7ba"
|
||||
@ -2513,6 +2559,15 @@ defined@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
|
||||
integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
|
||||
|
||||
degenerator@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-2.2.0.tgz#49e98c11fa0293c5b26edfbb52f15729afcdb254"
|
||||
integrity sha512-aiQcQowF01RxFI4ZLFMpzyotbQonhNpBao6dkI8JPk5a+hmSjR5ErHp2CQySmQe8os3VBqLCIh87nDBgZXvsmg==
|
||||
dependencies:
|
||||
ast-types "^0.13.2"
|
||||
escodegen "^1.8.1"
|
||||
esprima "^4.0.0"
|
||||
|
||||
del@^2.0.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
|
||||
@ -2546,6 +2601,11 @@ denodeify@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631"
|
||||
integrity sha1-OjYof1A05pnnV3kBBSwubJQlFjE=
|
||||
|
||||
depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
des.js@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
|
||||
@ -2931,6 +2991,18 @@ escape-string-regexp@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
|
||||
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
|
||||
|
||||
escodegen@^1.8.1:
|
||||
version "1.14.3"
|
||||
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503"
|
||||
integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==
|
||||
dependencies:
|
||||
esprima "^4.0.1"
|
||||
estraverse "^4.2.0"
|
||||
esutils "^2.0.2"
|
||||
optionator "^0.8.1"
|
||||
optionalDependencies:
|
||||
source-map "~0.6.1"
|
||||
|
||||
eslint-plugin-jsdoc@^19.1.0:
|
||||
version "19.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-19.1.0.tgz#fcc17f0378fdd6ee1c847a79b7211745cb05d014"
|
||||
@ -3126,6 +3198,11 @@ esprima@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
|
||||
integrity sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==
|
||||
|
||||
esprima@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||
|
||||
esquery@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708"
|
||||
@ -3146,6 +3223,11 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
|
||||
integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
|
||||
|
||||
estraverse@^4.2.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
|
||||
@ -3435,6 +3517,11 @@ file-uri-to-path@1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
|
||||
|
||||
file-uri-to-path@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba"
|
||||
integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==
|
||||
|
||||
filename-regex@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775"
|
||||
@ -3746,6 +3833,14 @@ fstream@^1.0.2:
|
||||
mkdirp ">=0.5 0"
|
||||
rimraf "2"
|
||||
|
||||
ftp@^0.3.10:
|
||||
version "0.3.10"
|
||||
resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d"
|
||||
integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=
|
||||
dependencies:
|
||||
readable-stream "1.1.x"
|
||||
xregexp "2.0.0"
|
||||
|
||||
function-bind@^1.0.2, function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
@ -3799,6 +3894,18 @@ get-stream@^5.1.0:
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-uri@3:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c"
|
||||
integrity sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==
|
||||
dependencies:
|
||||
"@tootallnate/once" "1"
|
||||
data-uri-to-buffer "3"
|
||||
debug "4"
|
||||
file-uri-to-path "2"
|
||||
fs-extra "^8.1.0"
|
||||
ftp "^0.3.10"
|
||||
|
||||
get-value@^2.0.3, get-value@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
||||
@ -4541,6 +4648,17 @@ http-cache-semantics@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5"
|
||||
integrity sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==
|
||||
|
||||
http-errors@1.7.3:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
|
||||
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
inherits "2.0.4"
|
||||
setprototypeof "1.1.1"
|
||||
statuses ">= 1.5.0 < 2"
|
||||
toidentifier "1.0.0"
|
||||
|
||||
http-proxy-agent@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||
@ -4549,6 +4667,15 @@ http-proxy-agent@^2.1.0:
|
||||
agent-base "4"
|
||||
debug "3.1.0"
|
||||
|
||||
http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
|
||||
integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
|
||||
dependencies:
|
||||
"@tootallnate/once" "1"
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
http-signature@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||
@ -4563,6 +4690,14 @@ https-browserify@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
|
||||
|
||||
https-proxy-agent@5, https-proxy-agent@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
|
||||
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
|
||||
dependencies:
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
https-proxy-agent@^2.2.3:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
|
||||
@ -4579,14 +4714,6 @@ https-proxy-agent@^4.0.0:
|
||||
agent-base "5"
|
||||
debug "4"
|
||||
|
||||
https-proxy-agent@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
|
||||
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
|
||||
dependencies:
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
husky@^0.13.1:
|
||||
version "0.13.4"
|
||||
resolved "https://registry.yarnpkg.com/husky/-/husky-0.13.4.tgz#48785c5028de3452a51c48c12c4f94b2124a1407"
|
||||
@ -4602,18 +4729,18 @@ iconv-lite-umd@0.6.8:
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0"
|
||||
integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A==
|
||||
|
||||
iconv-lite@^0.4.19:
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||
integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
|
||||
|
||||
iconv-lite@^0.4.24:
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
iconv-lite@^0.4.19:
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||
integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
|
||||
|
||||
iconv-lite@^0.4.4:
|
||||
version "0.4.23"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
|
||||
@ -4704,7 +4831,7 @@ inherits@2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
|
||||
integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
|
||||
|
||||
inherits@^2.0.4:
|
||||
inherits@2.0.4, inherits@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@ -5403,6 +5530,13 @@ jsprim@^1.2.2:
|
||||
json-schema "0.2.3"
|
||||
verror "1.10.0"
|
||||
|
||||
jszip@2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/jszip/-/jszip-2.6.0.tgz#7fb3e9c2f11c8a9840612db5dabbc8cf3a7534b7"
|
||||
integrity sha1-f7PpwvEciphAYS212rvIzzp1NLc=
|
||||
dependencies:
|
||||
pako "~1.0.0"
|
||||
|
||||
just-debounce@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea"
|
||||
@ -5983,26 +6117,11 @@ minimatch@0.3:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
||||
|
||||
minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
||||
|
||||
minimist@^1.2.5:
|
||||
minimist@0.0.8, minimist@^1.2.0, minimist@^1.2.5, minimist@~0.0.1:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
minimist@~0.0.1:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
||||
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
|
||||
|
||||
minipass@^2.2.1, minipass@^2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233"
|
||||
@ -6232,6 +6351,11 @@ neo-async@^2.6.1:
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
|
||||
|
||||
netmask@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
|
||||
integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=
|
||||
|
||||
nice-try@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
|
||||
@ -6581,6 +6705,18 @@ optimist@^0.6.1:
|
||||
minimist "~0.0.1"
|
||||
wordwrap "~0.0.2"
|
||||
|
||||
optionator@^0.8.1, optionator@^0.8.3:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
|
||||
integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
|
||||
dependencies:
|
||||
deep-is "~0.1.3"
|
||||
fast-levenshtein "~2.0.6"
|
||||
levn "~0.3.0"
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
word-wrap "~1.2.3"
|
||||
|
||||
optionator@^0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
|
||||
@ -6593,18 +6729,6 @@ optionator@^0.8.2:
|
||||
type-check "~0.3.2"
|
||||
wordwrap "~1.0.0"
|
||||
|
||||
optionator@^0.8.3:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
|
||||
integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
|
||||
dependencies:
|
||||
deep-is "~0.1.3"
|
||||
fast-levenshtein "~2.0.6"
|
||||
levn "~0.3.0"
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
word-wrap "~1.2.3"
|
||||
|
||||
ordered-read-streams@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b"
|
||||
@ -6744,6 +6868,35 @@ p-try@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
|
||||
integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==
|
||||
|
||||
pac-proxy-agent@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-4.1.0.tgz#66883eeabadc915fc5e95457324cb0f0ac78defb"
|
||||
integrity sha512-ejNgYm2HTXSIYX9eFlkvqFp8hyJ374uDf0Zq5YUAifiSh1D6fo+iBivQZirGvVv8dCYUsLhmLBRhlAYvBKI5+Q==
|
||||
dependencies:
|
||||
"@tootallnate/once" "1"
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
get-uri "3"
|
||||
http-proxy-agent "^4.0.1"
|
||||
https-proxy-agent "5"
|
||||
pac-resolver "^4.1.0"
|
||||
raw-body "^2.2.0"
|
||||
socks-proxy-agent "5"
|
||||
|
||||
pac-resolver@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-4.1.0.tgz#4b12e7d096b255a3b84e53f6831f32e9c7e5fe95"
|
||||
integrity sha512-d6lf2IrZJJ7ooVHr7BfwSjRO1yKSJMaiiWYSHcrxSIUtZrCa4KKGwcztdkZ/E9LFleJfjoi1yl+XLR7AX24nbQ==
|
||||
dependencies:
|
||||
degenerator "^2.2.0"
|
||||
ip "^1.1.5"
|
||||
netmask "^1.0.6"
|
||||
|
||||
pako@~1.0.0:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||
|
||||
pako@~1.0.5:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
|
||||
@ -7439,7 +7592,21 @@ proto-list@~1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
||||
integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
proxy-agent@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-4.0.0.tgz#a92976af3fbc7d846f2e850e2ac5ac6ca3fb74c7"
|
||||
integrity sha512-8P0Y2SkwvKjiGU1IkEfYuTteioMIDFxPL4/j49zzt5Mz3pG1KO+mIrDG1qH0PQUHTTczjwGcYl+EzfXiFj5vUQ==
|
||||
dependencies:
|
||||
agent-base "^6.0.0"
|
||||
debug "4"
|
||||
http-proxy-agent "^4.0.0"
|
||||
https-proxy-agent "^5.0.0"
|
||||
lru-cache "^5.1.1"
|
||||
pac-proxy-agent "^4.1.0"
|
||||
proxy-from-env "^1.0.0"
|
||||
socks-proxy-agent "^5.0.0"
|
||||
|
||||
proxy-from-env@^1.0.0, proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
@ -7607,6 +7774,16 @@ randomfill@^1.0.3:
|
||||
randombytes "^2.0.5"
|
||||
safe-buffer "^5.1.0"
|
||||
|
||||
raw-body@^2.2.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
|
||||
integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==
|
||||
dependencies:
|
||||
bytes "3.1.0"
|
||||
http-errors "1.7.3"
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
rc@^1.2.7:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
@ -7673,6 +7850,16 @@ read@^1.0.7:
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@1.1.x:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
"readable-stream@2 || 3":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06"
|
||||
@ -8296,6 +8483,11 @@ setimmediate@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
|
||||
|
||||
setprototypeof@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
||||
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
||||
|
||||
sha.js@^2.4.0, sha.js@^2.4.8:
|
||||
version "2.4.11"
|
||||
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
|
||||
@ -8374,6 +8566,11 @@ smart-buffer@4.0.2:
|
||||
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.2.tgz#5207858c3815cc69110703c6b94e46c15634395d"
|
||||
integrity sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==
|
||||
|
||||
smart-buffer@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba"
|
||||
integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==
|
||||
|
||||
snapdragon-node@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
|
||||
@ -8411,6 +8608,15 @@ sntp@2.x.x:
|
||||
dependencies:
|
||||
hoek "4.x.x"
|
||||
|
||||
socks-proxy-agent@5, socks-proxy-agent@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60"
|
||||
integrity sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==
|
||||
dependencies:
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
socks "^2.3.3"
|
||||
|
||||
socks-proxy-agent@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386"
|
||||
@ -8419,6 +8625,14 @@ socks-proxy-agent@^4.0.1:
|
||||
agent-base "~4.2.1"
|
||||
socks "~2.3.2"
|
||||
|
||||
socks@^2.3.3:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/socks/-/socks-2.5.1.tgz#7720640b6b5ec9a07d556419203baa3f0596df5f"
|
||||
integrity sha512-oZCsJJxapULAYJaEYBSzMcz8m3jqgGrHaGhkmU/o/PQfFWYWxkAaA0UMGImb6s6tEXfKi959X6VJjMMQ3P6TTQ==
|
||||
dependencies:
|
||||
ip "^1.1.5"
|
||||
smart-buffer "^4.1.0"
|
||||
|
||||
socks@~2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.2.tgz#ade388e9e6d87fdb11649c15746c578922a5883e"
|
||||
@ -8612,6 +8826,11 @@ static-extend@^0.1.1:
|
||||
define-property "^0.2.5"
|
||||
object-copy "^0.1.0"
|
||||
|
||||
"statuses@>= 1.5.0 < 2":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||
|
||||
stream-browserify@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
|
||||
@ -9170,6 +9389,11 @@ to-through@^2.0.0:
|
||||
dependencies:
|
||||
through2 "^2.0.3"
|
||||
|
||||
toidentifier@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||
|
||||
tough-cookie@~2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
|
||||
@ -9219,6 +9443,11 @@ tslib@^1.8.1, tslib@^1.9.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
||||
|
||||
tslib@^2.0.1:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c"
|
||||
integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==
|
||||
|
||||
tsutils@^3.17.1:
|
||||
version "3.17.1"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
|
||||
@ -9397,6 +9626,11 @@ universalify@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||
|
||||
unpipe@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
|
||||
|
||||
unset-value@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"
|
||||
@ -10049,6 +10283,11 @@ xmldom@0.1.x:
|
||||
resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
|
||||
integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=
|
||||
|
||||
xregexp@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
|
||||
integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=
|
||||
|
||||
"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
Loading…
Reference in New Issue
Block a user