Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/server-main.ts
3285 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import './bootstrap-server.js'; // this MUST come before other imports as it changes global state
7
import * as path from 'node:path';
8
import * as http from 'node:http';
9
import type { AddressInfo } from 'node:net';
10
import * as os from 'node:os';
11
import * as readline from 'node:readline';
12
import { performance } from 'node:perf_hooks';
13
import minimist from 'minimist';
14
import { devInjectNodeModuleLookupPath, removeGlobalNodeJsModuleLookupPaths } from './bootstrap-node.js';
15
import { bootstrapESM } from './bootstrap-esm.js';
16
import { resolveNLSConfiguration } from './vs/base/node/nls.js';
17
import { product } from './bootstrap-meta.js';
18
import * as perf from './vs/base/common/performance.js';
19
import { INLSConfiguration } from './vs/nls.js';
20
import { IServerAPI } from './vs/server/node/remoteExtensionHostAgentServer.js';
21
22
perf.mark('code/server/start');
23
(globalThis as any).vscodeServerStartTime = performance.now();
24
25
// Do a quick parse to determine if a server or the cli needs to be started
26
const parsedArgs = minimist(process.argv.slice(2), {
27
boolean: ['start-server', 'list-extensions', 'print-ip-address', 'help', 'version', 'accept-server-license-terms', 'update-extensions'],
28
string: ['install-extension', 'install-builtin-extension', 'uninstall-extension', 'locate-extension', 'socket-path', 'host', 'port', 'compatibility'],
29
alias: { help: 'h', version: 'v' }
30
});
31
['host', 'port', 'accept-server-license-terms'].forEach(e => {
32
if (!parsedArgs[e]) {
33
const envValue = process.env[`VSCODE_SERVER_${e.toUpperCase().replace('-', '_')}`];
34
if (envValue) {
35
parsedArgs[e] = envValue;
36
}
37
}
38
});
39
40
const extensionLookupArgs = ['list-extensions', 'locate-extension'];
41
const extensionInstallArgs = ['install-extension', 'install-builtin-extension', 'uninstall-extension', 'update-extensions'];
42
43
const shouldSpawnCli = parsedArgs.help || parsedArgs.version || extensionLookupArgs.some(a => !!parsedArgs[a]) || (extensionInstallArgs.some(a => !!parsedArgs[a]) && !parsedArgs['start-server']);
44
45
const nlsConfiguration = await resolveNLSConfiguration({ userLocale: 'en', osLocale: 'en', commit: product.commit, userDataPath: '', nlsMetadataPath: import.meta.dirname });
46
47
if (shouldSpawnCli) {
48
loadCode(nlsConfiguration).then((mod) => {
49
mod.spawnCli();
50
});
51
} else {
52
let _remoteExtensionHostAgentServer: IServerAPI | null = null;
53
let _remoteExtensionHostAgentServerPromise: Promise<IServerAPI> | null = null;
54
const getRemoteExtensionHostAgentServer = () => {
55
if (!_remoteExtensionHostAgentServerPromise) {
56
_remoteExtensionHostAgentServerPromise = loadCode(nlsConfiguration).then(async (mod) => {
57
const server = await mod.createServer(address);
58
_remoteExtensionHostAgentServer = server;
59
return server;
60
});
61
}
62
return _remoteExtensionHostAgentServerPromise;
63
};
64
65
if (Array.isArray(product.serverLicense) && product.serverLicense.length) {
66
console.log(product.serverLicense.join('\n'));
67
if (product.serverLicensePrompt && parsedArgs['accept-server-license-terms'] !== true) {
68
if (hasStdinWithoutTty()) {
69
console.log('To accept the license terms, start the server with --accept-server-license-terms');
70
process.exit(1);
71
}
72
try {
73
const accept = await prompt(product.serverLicensePrompt);
74
if (!accept) {
75
process.exit(1);
76
}
77
} catch (e) {
78
console.log(e);
79
process.exit(1);
80
}
81
}
82
}
83
84
let firstRequest = true;
85
let firstWebSocket = true;
86
87
let address: string | AddressInfo | null = null;
88
const server = http.createServer(async (req, res) => {
89
if (firstRequest) {
90
firstRequest = false;
91
perf.mark('code/server/firstRequest');
92
}
93
const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer();
94
return remoteExtensionHostAgentServer.handleRequest(req, res);
95
});
96
server.on('upgrade', async (req, socket) => {
97
if (firstWebSocket) {
98
firstWebSocket = false;
99
perf.mark('code/server/firstWebSocket');
100
}
101
const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer();
102
// @ts-ignore
103
return remoteExtensionHostAgentServer.handleUpgrade(req, socket);
104
});
105
server.on('error', async (err) => {
106
const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer();
107
return remoteExtensionHostAgentServer.handleServerError(err);
108
});
109
110
const host = sanitizeStringArg(parsedArgs['host']) || (parsedArgs['compatibility'] !== '1.63' ? 'localhost' : undefined);
111
const nodeListenOptions = (
112
parsedArgs['socket-path']
113
? { path: sanitizeStringArg(parsedArgs['socket-path']) }
114
: { host, port: await parsePort(host, sanitizeStringArg(parsedArgs['port'])) }
115
);
116
server.listen(nodeListenOptions, async () => {
117
let output = Array.isArray(product.serverGreeting) && product.serverGreeting.length ? `\n\n${product.serverGreeting.join('\n')}\n\n` : ``;
118
119
if (typeof nodeListenOptions.port === 'number' && parsedArgs['print-ip-address']) {
120
const ifaces = os.networkInterfaces();
121
Object.keys(ifaces).forEach(function (ifname) {
122
ifaces[ifname]?.forEach(function (iface) {
123
if (!iface.internal && iface.family === 'IPv4') {
124
output += `IP Address: ${iface.address}\n`;
125
}
126
});
127
});
128
}
129
130
address = server.address();
131
if (address === null) {
132
throw new Error('Unexpected server address');
133
}
134
135
output += `Server bound to ${typeof address === 'string' ? address : `${address.address}:${address.port} (${address.family})`}\n`;
136
// Do not change this line. VS Code looks for this in the output.
137
output += `Extension host agent listening on ${typeof address === 'string' ? address : address.port}\n`;
138
console.log(output);
139
140
perf.mark('code/server/started');
141
(globalThis as any).vscodeServerListenTime = performance.now();
142
143
await getRemoteExtensionHostAgentServer();
144
});
145
146
process.on('exit', () => {
147
server.close();
148
if (_remoteExtensionHostAgentServer) {
149
_remoteExtensionHostAgentServer.dispose();
150
}
151
});
152
}
153
154
function sanitizeStringArg(val: any): string | undefined {
155
if (Array.isArray(val)) { // if an argument is passed multiple times, minimist creates an array
156
val = val.pop(); // take the last item
157
}
158
return typeof val === 'string' ? val : undefined;
159
}
160
161
/**
162
* If `--port` is specified and describes a single port, connect to that port.
163
*
164
* If `--port`describes a port range
165
* then find a free port in that range. Throw error if no
166
* free port available in range.
167
*
168
* In absence of specified ports, connect to port 8000.
169
*/
170
async function parsePort(host: string | undefined, strPort: string | undefined): Promise<number> {
171
if (strPort) {
172
let range: { start: number; end: number } | undefined;
173
if (strPort.match(/^\d+$/)) {
174
return parseInt(strPort, 10);
175
} else if (range = parseRange(strPort)) {
176
const port = await findFreePort(host, range.start, range.end);
177
if (port !== undefined) {
178
return port;
179
}
180
// Remote-SSH extension relies on this exact port error message, treat as an API
181
console.warn(`--port: Could not find free port in range: ${range.start} - ${range.end} (inclusive).`);
182
process.exit(1);
183
184
} else {
185
console.warn(`--port "${strPort}" is not a valid number or range. Ranges must be in the form 'from-to' with 'from' an integer larger than 0 and not larger than 'end'.`);
186
process.exit(1);
187
}
188
}
189
return 8000;
190
}
191
192
function parseRange(strRange: string): { start: number; end: number } | undefined {
193
const match = strRange.match(/^(\d+)-(\d+)$/);
194
if (match) {
195
const start = parseInt(match[1], 10), end = parseInt(match[2], 10);
196
if (start > 0 && start <= end && end <= 65535) {
197
return { start, end };
198
}
199
}
200
return undefined;
201
}
202
203
/**
204
* Starting at the `start` port, look for a free port incrementing
205
* by 1 until `end` inclusive. If no free port is found, undefined is returned.
206
*/
207
async function findFreePort(host: string | undefined, start: number, end: number): Promise<number | undefined> {
208
const testPort = (port: number) => {
209
return new Promise((resolve) => {
210
const server = http.createServer();
211
server.listen(port, host, () => {
212
server.close();
213
resolve(true);
214
}).on('error', () => {
215
resolve(false);
216
});
217
});
218
};
219
for (let port = start; port <= end; port++) {
220
if (await testPort(port)) {
221
return port;
222
}
223
}
224
return undefined;
225
}
226
227
async function loadCode(nlsConfiguration: INLSConfiguration) {
228
229
// required for `bootstrap-esm` to pick up NLS messages
230
process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfiguration);
231
232
// See https://github.com/microsoft/vscode-remote-release/issues/6543
233
// We would normally install a SIGPIPE listener in bootstrap-node.js
234
// But in certain situations, the console itself can be in a broken pipe state
235
// so logging SIGPIPE to the console will cause an infinite async loop
236
process.env['VSCODE_HANDLES_SIGPIPE'] = 'true';
237
238
if (process.env['VSCODE_DEV']) {
239
// When running out of sources, we need to load node modules from remote/node_modules,
240
// which are compiled against nodejs, not electron
241
process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH'] = process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH'] || path.join(import.meta.dirname, '..', 'remote', 'node_modules');
242
devInjectNodeModuleLookupPath(process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH']);
243
} else {
244
delete process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH'];
245
}
246
247
// Remove global paths from the node module lookup (node.js only)
248
removeGlobalNodeJsModuleLookupPaths();
249
250
// Bootstrap ESM
251
await bootstrapESM();
252
253
// Load Server
254
return import('./vs/server/node/server.main.js');
255
}
256
257
function hasStdinWithoutTty(): boolean {
258
try {
259
return !process.stdin.isTTY; // Via https://twitter.com/MylesBorins/status/782009479382626304
260
} catch (error) {
261
// Windows workaround for https://github.com/nodejs/node/issues/11656
262
}
263
return false;
264
}
265
266
function prompt(question: string): Promise<boolean> {
267
const rl = readline.createInterface({
268
input: process.stdin,
269
output: process.stdout
270
});
271
return new Promise((resolve, reject) => {
272
rl.question(question + ' ', async function (data) {
273
rl.close();
274
const str = data.toString().trim().toLowerCase();
275
if (str === '' || str === 'y' || str === 'yes') {
276
resolve(true);
277
} else if (str === 'n' || str === 'no') {
278
resolve(false);
279
} else {
280
process.stdout.write('\nInvalid Response. Answer either yes (y, yes) or no (n, no)\n');
281
resolve(await prompt(question));
282
}
283
});
284
});
285
}
286
287