diff options
author | Alex Dima <alexdima@microsoft.com> | 2022-01-20 16:46:08 +0300 |
---|---|---|
committer | Alex Dima <alexdima@microsoft.com> | 2022-01-20 16:46:08 +0300 |
commit | 43d18c529c9a2ec5c4ad76f57340897ea20195c7 (patch) | |
tree | a5c2ee78e0dce9e228563c115b6399b2526c3454 /resources | |
parent | e5167eb8f253dfd5605cc92a81376610bbc24c19 (diff) | |
parent | 8a9b17d61880b98e1292ff5753cccd8603f18db9 (diff) |
Merge remote-tracking branch 'origin/main' into alex/issue-140997
Diffstat (limited to 'resources')
-rw-r--r-- | resources/server/bin-dev/code-server.bat | 43 | ||||
-rw-r--r-- | resources/server/bin-dev/code-server.js | 93 | ||||
-rwxr-xr-x | resources/server/bin-dev/code-server.sh | 28 | ||||
-rw-r--r-- | resources/server/bin-dev/code-web.js | 87 | ||||
-rw-r--r-- | resources/server/web.bat | 24 | ||||
-rwxr-xr-x | resources/server/web.sh | 26 | ||||
-rw-r--r-- | resources/web/bin-dev/code-web-playground.js | 130 | ||||
-rw-r--r-- | resources/web/callback.html | 81 | ||||
-rw-r--r-- | resources/web/code-web.js | 663 |
9 files changed, 223 insertions, 952 deletions
diff --git a/resources/server/bin-dev/code-server.bat b/resources/server/bin-dev/code-server.bat deleted file mode 100644 index ac152f65f06..00000000000 --- a/resources/server/bin-dev/code-server.bat +++ /dev/null @@ -1,43 +0,0 @@ -@echo off -setlocal - -title VSCode Remote Agent - -pushd %~dp0\..\..\.. - -:: Configuration -set NODE_ENV=development -set VSCODE_DEV=1 - -:: Sync built-in extensions -call yarn download-builtin-extensions - -FOR /F "tokens=*" %%g IN ('node build/lib/node.js') do (SET NODE=%%g) - -:: Download nodejs executable for remote -IF NOT EXIST "%NODE%" ( - call yarn gulp node -) - -:: Launch Agent -set _FIRST_ARG=%1 -if "%_FIRST_ARG:~0,9%"=="--inspect" ( - set INSPECT=%1 - shift -) else ( - set INSPECT= -) - -:loop1 -if "%~1"=="" goto after_loop -set RESTVAR=%RESTVAR% %1 -shift -goto loop1 - -:after_loop - -call "%NODE%" %INSPECT% "out\server-main.js" %RESTVAR% - -popd - -endlocal diff --git a/resources/server/bin-dev/code-server.js b/resources/server/bin-dev/code-server.js new file mode 100644 index 00000000000..5b46325a906 --- /dev/null +++ b/resources/server/bin-dev/code-server.js @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// @ts-check + +const cp = require('child_process'); +const path = require('path'); +const os = require('os'); +const opn = require('opn'); +const crypto = require('crypto'); +const minimist = require('minimist'); + +const args = minimist(process.argv.slice(2), { + boolean: [ + 'help', + ], + string: [ + 'host', + 'port', + 'driver', + 'connection-token' + ], +}); + +if (args.help) { + console.log( + './scripts/code-server.sh|bat [options]\n' + + ' --launch Opens a browser' + ); +} + +const serverArgs = process.argv.slice(2).filter(v => v !== '--launch'); + +const HOST = args['host'] ?? 'localhost'; +const PORT = args['port'] ?? '9888'; +const TOKEN = args['connection-token'] ?? String(crypto.randomInt(0xffffffff)); + +if (!args['connection-token'] === undefined && !args['connection-token-file'] === undefined && !args['no-connection-token']) { + serverArgs.push('--connection-token', TOKEN); +} +if (args['host'] === undefined) { + serverArgs.push('--host', HOST); +} +if (args['port'] === undefined) { + serverArgs.push('--port', PORT); +} + +if (args['driver']) { + // given a DRIVER, we auto-shutdown when tests are done + serverArgs.push('--enable-remote-auto-shutdown', '--remote-auto-shutdown-without-delay'); +} + +const env = { ...process.env }; +env['VSCODE_AGENT_FOLDER'] = env['VSCODE_AGENT_FOLDER'] || path.join(os.homedir(), '.vscode-server-oss-dev'); +env['NODE_ENV'] = 'development'; +env['VSCODE_DEV'] = '1'; +const entryPoint = path.join(__dirname, '..', '..', '..', 'out', 'server-main.js'); + +startServer(); + +function startServer() { + console.log(`Starting server: ${entryPoint} ${serverArgs.join(' ')}`); + const proc = cp.spawn(process.execPath, [entryPoint, ...serverArgs], { env }); + + proc.stdout.on('data', data => { + // Log everything + console.log(data.toString()); + }); + + // Log errors + proc.stderr.on('data', data => { + console.error(data.toString()); + }); + + proc.on('exit', () => process.exit()); + + process.on('exit', () => proc.kill()); + process.on('SIGINT', () => { + proc.kill(); + process.exit(128 + 2); // https://nodejs.org/docs/v14.16.0/api/process.html#process_signal_events + }); + process.on('SIGTERM', () => { + proc.kill(); + process.exit(128 + 15); // https://nodejs.org/docs/v14.16.0/api/process.html#process_signal_events + }); + +} + +if (args['launch']) { + opn(`http://${HOST}:${PORT}/?tkn=${TOKEN}`); +} diff --git a/resources/server/bin-dev/code-server.sh b/resources/server/bin-dev/code-server.sh deleted file mode 100755 index 88c67ed4801..00000000000 --- a/resources/server/bin-dev/code-server.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -if [[ "$OSTYPE" == "darwin"* ]]; then - realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; } - ROOT=$(dirname $(dirname $(dirname $(dirname $(realpath "$0"))))) -else - ROOT=$(dirname $(dirname $(dirname $(dirname $(readlink -f $0))))) -fi - -function code() { - cd $ROOT - - # Sync built-in extensions - yarn download-builtin-extensions - - NODE=$(node build/lib/node.js) - - # Download nodejs - if [ ! -f $NODE ]; then - yarn gulp node - fi - - NODE_ENV=development \ - VSCODE_DEV=1 \ - $NODE "$ROOT/out/server-main.js" "$@" -} - -code "$@" diff --git a/resources/server/bin-dev/code-web.js b/resources/server/bin-dev/code-web.js deleted file mode 100644 index 0fdb7c76d2c..00000000000 --- a/resources/server/bin-dev/code-web.js +++ /dev/null @@ -1,87 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// @ts-check - -const cp = require('child_process'); -const path = require('path'); -const os = require('os'); - -const serverArgs = []; - -// Server Config -let PORT = 9888; -let DRIVER = undefined; -let LOGS_PATH = undefined; - -// Workspace Config -let FOLDER = undefined; -let WORKSPACE = undefined; - -// Settings Sync Config -let GITHUB_AUTH_TOKEN = undefined; -let ENABLE_SYNC = false; - -for (let idx = 0; idx <= process.argv.length - 2; idx++) { - const arg = process.argv[idx]; - switch (arg) { - case '--port': PORT = Number(process.argv[idx + 1]); break; - case '--folder': FOLDER = process.argv[idx + 1]; break; - case '--workspace': WORKSPACE = process.argv[idx + 1]; break; - case '--driver': DRIVER = process.argv[idx + 1]; break; - case '--github-auth': GITHUB_AUTH_TOKEN = process.argv[idx + 1]; break; - case '--logsPath': LOGS_PATH = process.argv[idx + 1]; break; - case '--enable-sync': ENABLE_SYNC = true; break; - } -} - -serverArgs.push('--port', String(PORT)); -if (FOLDER) { - serverArgs.push('--folder', FOLDER); -} -if (WORKSPACE) { - serverArgs.push('--workspace', WORKSPACE); -} -if (DRIVER) { - serverArgs.push('--driver', DRIVER); - - // given a DRIVER, we auto-shutdown when tests are done - serverArgs.push('--enable-remote-auto-shutdown', '--remote-auto-shutdown-without-delay'); -} -if (LOGS_PATH) { - serverArgs.push('--logsPath', LOGS_PATH); -} -if (GITHUB_AUTH_TOKEN) { - serverArgs.push('--github-auth', GITHUB_AUTH_TOKEN); -} -if (ENABLE_SYNC) { - serverArgs.push('--enable-sync', true); -} - -// Connection Token -serverArgs.push('--connection-token', '00000'); - -// Server should really only listen from localhost -serverArgs.push('--host', '127.0.0.1'); - -const env = { ...process.env }; -env['VSCODE_AGENT_FOLDER'] = env['VSCODE_AGENT_FOLDER'] || path.join(os.homedir(), '.vscode-web-dev'); -const entryPoint = path.join(__dirname, '..', '..', '..', 'out', 'vs', 'server', 'main.js'); - -startServer(); - -function startServer() { - const proc = cp.spawn(process.execPath, [entryPoint, ...serverArgs], { env }); - - proc.stdout.on('data', data => { - // Log everything - console.log(data.toString()); - }); - - // Log errors - proc.stderr.on('data', data => { - console.error(data.toString()); - }); -} diff --git a/resources/server/web.bat b/resources/server/web.bat deleted file mode 100644 index d131dafffc6..00000000000 --- a/resources/server/web.bat +++ /dev/null @@ -1,24 +0,0 @@ -@echo off -setlocal - -title VSCode Web Server - -pushd %~dp0\..\.. - -:: Configuration -set NODE_ENV=development -set VSCODE_DEV=1 - -:: Sync built-in extensions -call yarn download-builtin-extensions - -:: Download nodejs executable for remote -call yarn gulp node - -:: Launch Server -FOR /F "tokens=*" %%g IN ('node build/lib/node.js') do (SET NODE=%%g) -call "%NODE%" resources\server\bin-dev\code-web.js %* - -popd - -endlocal
\ No newline at end of file diff --git a/resources/server/web.sh b/resources/server/web.sh deleted file mode 100755 index da072e5f2d0..00000000000 --- a/resources/server/web.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -if [[ "$OSTYPE" == "darwin"* ]]; then - realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; } - ROOT=$(dirname $(dirname $(dirname $(realpath "$0")))) -else - ROOT=$(dirname $(dirname $(dirname $(readlink -f $0)))) -fi - -function code() { - cd $ROOT - - # Sync built-in extensions - yarn download-builtin-extensions - - # Load remote node - yarn gulp node - - NODE=$(node build/lib/node.js) - - NODE_ENV=development \ - VSCODE_DEV=1 \ - $NODE $(dirname "$0")/bin-dev/code-web.js "$@" -} - -code "$@" diff --git a/resources/web/bin-dev/code-web-playground.js b/resources/web/bin-dev/code-web-playground.js new file mode 100644 index 00000000000..57a3e4d8d26 --- /dev/null +++ b/resources/web/bin-dev/code-web-playground.js @@ -0,0 +1,130 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// @ts-check + +const testWeb = require('@vscode/test-web'); + +const fs = require('fs'); +const path = require('path'); + +const minimist = require('minimist'); +const fancyLog = require('fancy-log'); +const ansiColors = require('ansi-colors'); +const remote = require('gulp-remote-retry-src'); +const vfs = require('vinyl-fs'); +const opn = require('opn'); + +const APP_ROOT = path.join(__dirname, '..', '..', '..'); +const WEB_DEV_EXTENSIONS_ROOT = path.join(APP_ROOT, '.build', 'builtInWebDevExtensions'); + +const WEB_PLAYGROUND_VERSION = '0.0.13'; + +const args = minimist(process.argv.slice(2), { + boolean: [ + 'help', + 'verbose', + 'open-devtools' + ], + string: [ + 'host', + 'port', + 'extension', + 'browserType' + ], +}); + +if (args.help) { + console.log( + './scripts/code-web.sh|bat [options]\n' + + ' --host Server host address\n' + + ' --port Server port\n' + + ' --browserType The browser type to launch: `chromium` (default), `firefox`, `webkit` or `none`' + + ' --extension Path of an extension to include\n' + + ' --open-devtools Open the dev tools' + + ' --verbose Print out more information\n' + + ' --help\n' + + '[Example]\n' + + ' ./scripts/code-web.sh|bat --port 8080' + ); + process.exit(0); +} + +openTestWeb(); + + +async function openTestWeb() { + await ensureWebDevExtensions(); + const extensionPaths = [WEB_DEV_EXTENSIONS_ROOT]; + const extensions = args['extension']; + if (Array.isArray(extensions)) { + extensionPaths.push(...extensions); + } else if (extensions) { + extensionPaths.push(extensions); + } + const host = args.host || 'localhost'; + const port = args.port || 8080; + + await testWeb.open({ + browserType: args['browserType'] ?? 'none', + host, + port, + folderUri: 'memfs:///sample-folder', + vsCodeDevPath: APP_ROOT, + extensionPaths, + devTools: !!args['open-devtools'], + hideServerLog: !args['verbose'], + verbose: !!args['verbose'] + }); + + + if (!args['browserType']) { + opn(`http://${host}:${port}/`); + } +} + +async function directoryExists(path) { + try { + return (await fs.promises.stat(path)).isDirectory(); + } catch { + return false; + } +} + +async function ensureWebDevExtensions() { + + // Playground (https://github.com/microsoft/vscode-web-playground) + const webDevPlaygroundRoot = path.join(WEB_DEV_EXTENSIONS_ROOT, 'vscode-web-playground'); + const webDevPlaygroundExists = await directoryExists(webDevPlaygroundRoot); + + let downloadPlayground = false; + if (webDevPlaygroundExists) { + try { + const webDevPlaygroundPackageJson = JSON.parse(((await fs.promises.readFile(path.join(webDevPlaygroundRoot, 'package.json'))).toString())); + if (webDevPlaygroundPackageJson.version !== WEB_PLAYGROUND_VERSION) { + downloadPlayground = true; + } + } catch (error) { + downloadPlayground = true; + } + } else { + downloadPlayground = true; + } + + if (downloadPlayground) { + if (args.verbose) { + fancyLog(`${ansiColors.magenta('Web Development extensions')}: Downloading vscode-web-playground to ${webDevPlaygroundRoot}`); + } + await new Promise((resolve, reject) => { + remote(['package.json', 'dist/extension.js', 'dist/extension.js.map'], { + base: 'https://raw.githubusercontent.com/microsoft/vscode-web-playground/main/' + }).pipe(vfs.dest(webDevPlaygroundRoot)).on('end', resolve).on('error', reject); + }); + } else { + if (args.verbose) { + fancyLog(`${ansiColors.magenta('Web Development extensions')}: Using existing vscode-web-playground in ${webDevPlaygroundRoot}`); + } + } +} diff --git a/resources/web/callback.html b/resources/web/callback.html deleted file mode 100644 index 81f217e980b..00000000000 --- a/resources/web/callback.html +++ /dev/null @@ -1,81 +0,0 @@ -<!-- Copyright (C) Microsoft Corporation. All rights reserved. --> -<!DOCTYPE html> -<html> - -<head> - <meta charset="utf-8" /> - - <!-- Disable pinch zooming --> - <meta name="viewport" - content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"> - - <!-- Content Security Policy --> - <meta http-equiv="Content-Security-Policy" content=" - default-src 'self'; - img-src 'self' https: data: blob:; - media-src 'none'; - script-src 'self'; - style-src 'self' 'unsafe-inline'; - font-src 'self' blob:; - "> - - <title>Visual Studio Code</title> - - <!-- Styling --> - <style type="text/css"> - html { - height: 100%; - } - - body { - box-sizing: border-box; - min-height: 100%; - margin: 0; - padding: 15px 30px; - display: flex; - flex-direction: column; - color: white; - font-family: "Segoe UI", "Helvetica Neue", "Helvetica", Arial, sans-serif; - background-color: #373277; - } - - .branding { - background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGRlZnM+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtmaWxsOiNmNmY2ZjY7b3BhY2l0eTowO30uaWNvbi13aGl0ZXtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5CcmFuZFZpc3VhbFN0dWRpb0NvZGUyMDE3UlRXXzI0eF93aGl0ZV8yNHg8L3RpdGxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTI0LDBWMjRIMFYwWiIvPjxwYXRoIGNsYXNzPSJpY29uLXdoaXRlIiBkPSJNMjQsMi41VjIxLjVMMTgsMjQsMCwxOC41di0uNTYxbDE4LDEuNTQ1VjBaTTEsMTMuMTExLDQuMzg1LDEwLDEsNi44ODlsMS40MTgtLjgyN0w1Ljg1Myw4LjY1LDEyLDNsMywxLjQ1NlYxNS41NDRMMTIsMTcsNS44NTMsMTEuMzUsMi40MTksMTMuOTM5Wk03LjY0NCwxMCwxMiwxMy4yODNWNi43MTdaIi8+PC9zdmc+"); - background-size: 24px; - background-repeat: no-repeat; - background-position: left 50%; - padding-left: 36px; - font-size: 20px; - letter-spacing: -0.04rem; - font-weight: 400; - color: white; - text-decoration: none; - } - - .message-container { - flex-grow: 1; - display: flex; - align-items: center; - justify-content: center; - margin: 0 30px; - } - - .message { - font-weight: 300; - font-size: 1.3rem; - } - </style> -</head> - -<body> - <span class="branding"> - Visual Studio Code - </span> - <div class="message-container"> - <div class="message"> - You can close this page now. - </div> - </div> -</body> - -</html> diff --git a/resources/web/code-web.js b/resources/web/code-web.js deleted file mode 100644 index 1f038e7d5f8..00000000000 --- a/resources/web/code-web.js +++ /dev/null @@ -1,663 +0,0 @@ -#!/usr/bin/env node - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// @ts-check - -const http = require('http'); -const url = require('url'); -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const opn = require('opn'); -const minimist = require('minimist'); -const fancyLog = require('fancy-log'); -const ansiColors = require('ansi-colors'); -const remote = require('gulp-remote-retry-src'); -const vfs = require('vinyl-fs'); -const uuid = require('uuid'); - -const extensions = require('../../build/lib/extensions'); -const { getBuiltInExtensions } = require('../../build/lib/builtInExtensions'); - -const APP_ROOT = path.join(__dirname, '..', '..'); -const BUILTIN_EXTENSIONS_ROOT = path.join(APP_ROOT, 'extensions'); -const BUILTIN_MARKETPLACE_EXTENSIONS_ROOT = path.join(APP_ROOT, '.build', 'builtInExtensions'); -const WEB_DEV_EXTENSIONS_ROOT = path.join(APP_ROOT, '.build', 'builtInWebDevExtensions'); -const WEB_MAIN = path.join(APP_ROOT, 'src', 'vs', 'code', 'browser', 'workbench', 'workbench-dev.html'); - -// This is useful to simulate real world CORS -const ALLOWED_CORS_ORIGINS = [ - 'http://localhost:8081', - 'http://127.0.0.1:8081', - 'http://localhost:8080', - 'http://127.0.0.1:8080', -]; - -const WEB_PLAYGROUND_VERSION = '0.0.13'; - -const args = minimist(process.argv, { - boolean: [ - 'no-launch', - 'help', - 'verbose', - 'wrap-iframe', - 'enable-sync', - ], - string: [ - 'scheme', - 'host', - 'port', - 'local_port', - 'extension', - 'extensionId', - 'github-auth', - 'open-file' - ], -}); - -if (args.help) { - console.log( - 'yarn web [options]\n' + - ' --no-launch Do not open Code in the browser\n' + - ' --wrap-iframe Wrap the Web Worker Extension Host in an iframe\n' + - ' --enable-sync Enable sync by default\n' + - ' --scheme Protocol (https or http)\n' + - ' --host Remote host\n' + - ' --port Remote/Local port\n' + - ' --local_port Local port override\n' + - ' --extension Path of an extension to include\n' + - ' --extensionId Id of an extension to include\n' + - ' --open-file uri of the file to open. Also support selections in the file. Eg: scheme://authority/path#L1:2-L10:3\n' + - ' --github-auth Github authentication token\n' + - ' --verbose Print out more information\n' + - ' --help\n' + - '[Example]\n' + - ' yarn web --scheme https --host example.com --port 8080 --local_port 30000' - ); - process.exit(0); -} - -const PORT = args.port || process.env.PORT || 8080; -const LOCAL_PORT = args.local_port || process.env.LOCAL_PORT || PORT; -const SCHEME = args.scheme || process.env.VSCODE_SCHEME || 'http'; -const HOST = args.host || 'localhost'; -const AUTHORITY = process.env.VSCODE_AUTHORITY || `${HOST}:${PORT}`; - -const exists = (path) => util.promisify(fs.exists)(path); -const readFile = (path) => util.promisify(fs.readFile)(path); - -async function getBuiltInExtensionInfos() { - await getBuiltInExtensions(); - - const allExtensions = []; - /** @type {Object.<string, string>} */ - const locations = {}; - - const [localExtensions, marketplaceExtensions, webDevExtensions] = await Promise.all([ - extensions.scanBuiltinExtensions(BUILTIN_EXTENSIONS_ROOT), - extensions.scanBuiltinExtensions(BUILTIN_MARKETPLACE_EXTENSIONS_ROOT), - ensureWebDevExtensions().then(() => extensions.scanBuiltinExtensions(WEB_DEV_EXTENSIONS_ROOT)) - ]); - for (const ext of localExtensions) { - allExtensions.push(ext); - locations[ext.extensionPath] = path.join(BUILTIN_EXTENSIONS_ROOT, ext.extensionPath); - } - for (const ext of marketplaceExtensions) { - allExtensions.push(ext); - locations[ext.extensionPath] = path.join(BUILTIN_MARKETPLACE_EXTENSIONS_ROOT, ext.extensionPath); - } - for (const ext of webDevExtensions) { - allExtensions.push(ext); - locations[ext.extensionPath] = path.join(WEB_DEV_EXTENSIONS_ROOT, ext.extensionPath); - } - for (const ext of allExtensions) { - if (ext.packageJSON.browser) { - let mainFilePath = path.join(locations[ext.extensionPath], ext.packageJSON.browser); - if (path.extname(mainFilePath) !== '.js') { - mainFilePath += '.js'; - } - if (!await exists(mainFilePath)) { - fancyLog(`${ansiColors.red('Error')}: Could not find ${mainFilePath}. Use ${ansiColors.cyan('yarn watch-web')} to build the built-in extensions.`); - } - } - } - return { extensions: allExtensions, locations }; -} - -async function ensureWebDevExtensions() { - - // Playground (https://github.com/microsoft/vscode-web-playground) - const webDevPlaygroundRoot = path.join(WEB_DEV_EXTENSIONS_ROOT, 'vscode-web-playground'); - const webDevPlaygroundExists = await exists(webDevPlaygroundRoot); - - let downloadPlayground = false; - if (webDevPlaygroundExists) { - try { - const webDevPlaygroundPackageJson = JSON.parse(((await readFile(path.join(webDevPlaygroundRoot, 'package.json'))).toString())); - if (webDevPlaygroundPackageJson.version !== WEB_PLAYGROUND_VERSION) { - downloadPlayground = true; - } - } catch (error) { - downloadPlayground = true; - } - } else { - downloadPlayground = true; - } - - if (downloadPlayground) { - if (args.verbose) { - fancyLog(`${ansiColors.magenta('Web Development extensions')}: Downloading vscode-web-playground to ${webDevPlaygroundRoot}`); - } - await new Promise((resolve, reject) => { - remote(['package.json', 'dist/extension.js', 'dist/extension.js.map'], { - base: 'https://raw.githubusercontent.com/microsoft/vscode-web-playground/main/' - }).pipe(vfs.dest(webDevPlaygroundRoot)).on('end', resolve).on('error', reject); - }); - } else { - if (args.verbose) { - fancyLog(`${ansiColors.magenta('Web Development extensions')}: Using existing vscode-web-playground in ${webDevPlaygroundRoot}`); - } - } -} - -async function getCommandlineProvidedExtensionInfos() { - const extensions = []; - - /** @type {Object.<string, string>} */ - const locations = {}; - - let extensionArg = args['extension']; - let extensionIdArg = args['extensionId']; - if (!extensionArg && !extensionIdArg) { - return { extensions, locations }; - } - - if (extensionArg) { - const extensionPaths = Array.isArray(extensionArg) ? extensionArg : [extensionArg]; - await Promise.all(extensionPaths.map(async extensionPath => { - extensionPath = path.resolve(process.cwd(), extensionPath); - const packageJSON = await getExtensionPackageJSON(extensionPath); - if (packageJSON) { - const extensionId = `${packageJSON.publisher}.${packageJSON.name}`; - extensions.push({ scheme: SCHEME, authority: AUTHORITY, path: `/extension/${extensionId}` }); - locations[extensionId] = extensionPath; - } - })); - } - - if (extensionIdArg) { - extensions.push(...(Array.isArray(extensionIdArg) ? extensionIdArg : [extensionIdArg])); - } - - return { extensions, locations }; -} - -async function getExtensionPackageJSON(extensionPath) { - - const packageJSONPath = path.join(extensionPath, 'package.json'); - if (await exists(packageJSONPath)) { - try { - let packageJSON = JSON.parse((await readFile(packageJSONPath)).toString()); - if (packageJSON.main && !packageJSON.browser) { - return; // unsupported - } - return packageJSON; - } catch (e) { - console.log(e); - } - } - return undefined; -} - -const builtInExtensionsPromise = getBuiltInExtensionInfos(); -const commandlineProvidedExtensionsPromise = getCommandlineProvidedExtensionInfos(); - -const mapCallbackUriToRequestId = new Map(); - -/** - * @param req {http.IncomingMessage} - * @param res {http.ServerResponse} - */ -const requestHandler = (req, res) => { - const parsedUrl = url.parse(req.url, true); - const pathname = parsedUrl.pathname; - - res.setHeader('Access-Control-Allow-Origin', '*'); - - try { - if (/(\/static)?\/favicon\.ico/.test(pathname)) { - // favicon - return serveFile(req, res, path.join(APP_ROOT, 'resources', 'win32', 'code.ico')); - } - if (/(\/static)?\/manifest\.json/.test(pathname)) { - // manifest - res.writeHead(200, { 'Content-Type': 'application/json' }); - return res.end(JSON.stringify({ - 'name': 'Code - OSS', - 'short_name': 'Code - OSS', - 'start_url': '/', - 'lang': 'en-US', - 'display': 'standalone' - })); - } - if (/^\/static\//.test(pathname)) { - // static requests - return handleStatic(req, res, parsedUrl); - } - if (/^\/extension\//.test(pathname)) { - // default extension requests - return handleExtension(req, res, parsedUrl); - } - if (pathname === '/') { - // main web - return handleRoot(req, res); - } else if (pathname === '/callback') { - // callback support - return handleCallback(req, res, parsedUrl); - } else if (pathname === '/fetch-callback') { - // callback fetch support - return handleFetchCallback(req, res, parsedUrl); - } else if (pathname === '/builtin') { - // builtin extnesions JSON - return handleBuiltInExtensions(req, res, parsedUrl); - } - - return serveError(req, res, 404, 'Not found.'); - } catch (error) { - console.error(error.toString()); - - return serveError(req, res, 500, 'Internal Server Error.'); - } -}; - -const server = http.createServer(requestHandler); -server.listen(LOCAL_PORT, () => { - if (LOCAL_PORT !== PORT) { - console.log(`Operating location at http://0.0.0.0:${LOCAL_PORT}`); - } - console.log(`Web UI available at ${SCHEME}://${AUTHORITY}`); -}); -server.on('error', err => { - console.error(`Error occurred in server:`); - console.error(err); -}); - -/** - * @param {import('http').IncomingMessage} req - */ -function addCORSReplyHeader(req) { - if (typeof req.headers['origin'] !== 'string') { - // not a CORS request - return false; - } - return (ALLOWED_CORS_ORIGINS.indexOf(req.headers['origin']) >= 0); -} - -/** - * @param {import('http').IncomingMessage} req - * @param {import('http').ServerResponse} res - * @param {import('url').UrlWithParsedQuery} parsedUrl - */ -async function handleBuiltInExtensions(req, res, parsedUrl) { - const { extensions } = await builtInExtensionsPromise; - res.writeHead(200, { 'Content-Type': 'application/json' }); - return res.end(JSON.stringify(extensions)); -} - -/** - * @param {import('http').IncomingMessage} req - * @param {import('http').ServerResponse} res - * @param {import('url').UrlWithParsedQuery} parsedUrl - */ -async function handleStatic(req, res, parsedUrl) { - - if (/^\/static\/extensions\//.test(parsedUrl.pathname)) { - const relativePath = decodeURIComponent(parsedUrl.pathname.substr('/static/extensions/'.length)); - const filePath = getExtensionFilePath(relativePath, (await builtInExtensionsPromise).locations); - const responseHeaders = {}; - if (addCORSReplyHeader(req)) { - responseHeaders['Access-Control-Allow-Origin'] = '*'; - } - if (!filePath) { - return serveError(req, res, 400, `Bad request.`, responseHeaders); - } - return serveFile(req, res, filePath, responseHeaders); - } - - // Strip `/static/` from the path - const relativeFilePath = path.normalize(decodeURIComponent(parsedUrl.pathname.substr('/static/'.length))); - - return serveFile(req, res, path.join(APP_ROOT, relativeFilePath)); -} - -/** - * @param {import('http').IncomingMessage} req - * @param {import('http').ServerResponse} res - * @param {import('url').UrlWithParsedQuery} parsedUrl - */ -async function handleExtension(req, res, parsedUrl) { - // Strip `/extension/` from the path - const relativePath = decodeURIComponent(parsedUrl.pathname.substr('/extension/'.length)); - const filePath = getExtensionFilePath(relativePath, (await commandlineProvidedExtensionsPromise).locations); - const responseHeaders = {}; - if (addCORSReplyHeader(req)) { - responseHeaders['Access-Control-Allow-Origin'] = '*'; - } - if (!filePath) { - return serveError(req, res, 400, `Bad request.`, responseHeaders); - } - return serveFile(req, res, filePath, responseHeaders); -} - -/** - * @param {import('http').IncomingMessage} req - * @param {import('http').ServerResponse} res - */ -async function handleRoot(req, res) { - let folderUri = { scheme: 'memfs', path: `/sample-folder` }; - - const match = req.url && req.url.match(/\?([^#]+)/); - if (match) { - const qs = new URLSearchParams(match[1]); - - let gh = qs.get('gh'); - if (gh) { - if (gh.startsWith('/')) { - gh = gh.substr(1); - } - - const [owner, repo, ...branch] = gh.split('/', 3); - const ref = branch.join('/'); - folderUri = { scheme: 'github', authority: `${owner}+${repo}${ref ? `+${ref}` : ''}`, path: '/' }; - } else { - let cs = qs.get('cs'); - if (cs) { - if (cs.startsWith('/')) { - cs = cs.substr(1); - } - - const [owner, repo, ...branch] = cs.split('/'); - const ref = branch.join('/'); - folderUri = { scheme: 'codespace', authority: `${owner}+${repo}${ref ? `+${ref}` : ''}`, path: '/' }; - } - } - } - - const { extensions: builtInExtensions } = await builtInExtensionsPromise; - const { extensions: additionalBuiltinExtensions, locations: staticLocations } = await commandlineProvidedExtensionsPromise; - - const dedupedBuiltInExtensions = []; - for (const builtInExtension of builtInExtensions) { - const extensionId = `${builtInExtension.packageJSON.publisher}.${builtInExtension.packageJSON.name}`; - if (staticLocations[extensionId]) { - fancyLog(`${ansiColors.magenta('BuiltIn extensions')}: Ignoring built-in ${extensionId} because it was overridden via --extension argument`); - continue; - } - - dedupedBuiltInExtensions.push(builtInExtension); - } - - if (args.verbose) { - fancyLog(`${ansiColors.magenta('BuiltIn extensions')}: ${dedupedBuiltInExtensions.map(e => path.basename(e.extensionPath)).join(', ')}`); - fancyLog(`${ansiColors.magenta('Additional extensions')}: ${additionalBuiltinExtensions.map(e => typeof e === 'string' ? e : path.basename(e.path)).join(', ') || 'None'}`); - } - - const openFileUrl = args['open-file'] ? url.parse(args['open-file'], true) : undefined; - let selection; - if (openFileUrl?.hash) { - const rangeMatch = /L(?<startLineNumber>\d+)(?::(?<startColumn>\d+))?((?:-L(?<endLineNumber>\d+))(?::(?<endColumn>\d+))?)?/.exec(openFileUrl.hash); - if (rangeMatch?.groups) { - const { startLineNumber, startColumn, endLineNumber, endColumn } = rangeMatch.groups; - const start = { line: parseInt(startLineNumber), column: startColumn ? (parseInt(startColumn) || 1) : 1 }; - const end = endLineNumber ? { line: parseInt(endLineNumber), column: endColumn ? (parseInt(endColumn) || 1) : 1 } : start; - selection = { start, end } - } - } - const webConfigJSON = { - folderUri: folderUri, - additionalBuiltinExtensions, - defaultLayout: openFileUrl ? { - force: true, - editors: [{ - uri: { - scheme: openFileUrl.protocol.substring(0, openFileUrl.protocol.length - 1), - authority: openFileUrl.host, - path: openFileUrl.path, - }, - selection, - }] - } : undefined, - settingsSyncOptions: args['enable-sync'] ? { - enabled: true - } : undefined - }; - if (args['wrap-iframe']) { - webConfigJSON._wrapWebWorkerExtHostInIframe = true; - } - - const authSessionInfo = args['github-auth'] ? { - id: uuid.v4(), - providerId: 'github', - accessToken: args['github-auth'], - scopes: [['user:email'], ['repo']] - } : undefined; - - const data = (await readFile(WEB_MAIN)).toString() - .replace('{{WORKBENCH_WEB_CONFIGURATION}}', () => escapeAttribute(JSON.stringify(webConfigJSON))) // use a replace function to avoid that regexp replace patterns ($&, $0, ...) are applied - .replace('{{WORKBENCH_BUILTIN_EXTENSIONS}}', () => escapeAttribute(JSON.stringify(dedupedBuiltInExtensions))) - .replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : '') - .replace('{{WEBVIEW_ENDPOINT}}', ''); - - const headers = { - 'Content-Type': 'text/html', - 'Content-Security-Policy': 'require-trusted-types-for \'script\';' - }; - res.writeHead(200, headers); - return res.end(data); -} - -/** - * Handle HTTP requests for /callback - * @param {import('http').IncomingMessage} req - * @param {import('http').ServerResponse} res - * @param {import('url').UrlWithParsedQuery} parsedUrl -*/ -async function handleCallback(req, res, parsedUrl) { - const wellKnownKeys = ['vscode-requestId', 'vscode-scheme', 'vscode-authority', 'vscode-path', 'vscode-query', 'vscode-fragment']; - const [requestId, vscodeScheme, vscodeAuthority, vscodePath, vscodeQuery, vscodeFragment] = wellKnownKeys.map(key => { - const value = getFirstQueryValue(parsedUrl, key); - if (value) { - return decodeURIComponent(value); - } - - return value; - }); - - if (!requestId) { - res.writeHead(400, { 'Content-Type': 'text/plain' }); - return res.end(`Bad request.`); - } - - // merge over additional query values that we got - let query = vscodeQuery; - let index = 0; - getFirstQueryValues(parsedUrl, wellKnownKeys).forEach((value, key) => { - if (!query) { - query = ''; - } - - const prefix = (index++ === 0) ? '' : '&'; - query += `${prefix}${key}=${value}`; - }); - - - // add to map of known callbacks - mapCallbackUriToRequestId.set(requestId, JSON.stringify({ scheme: vscodeScheme || 'code-oss', authority: vscodeAuthority, path: vscodePath, query, fragment: vscodeFragment })); - return serveFile(req, res, path.join(APP_ROOT, 'resources', 'web', 'callback.html'), { 'Content-Type': 'text/html' }); -} - -/** - * Handle HTTP requests for /fetch-callback - * @param {import('http').IncomingMessage} req - * @param {import('http').ServerResponse} res - * @param {import('url').UrlWithParsedQuery} parsedUrl -*/ -async function handleFetchCallback(req, res, parsedUrl) { - const requestId = getFirstQueryValue(parsedUrl, 'vscode-requestId'); - if (!requestId) { - res.writeHead(400, { 'Content-Type': 'text/plain' }); - return res.end(`Bad request.`); - } - - const knownCallbackUri = mapCallbackUriToRequestId.get(requestId); - if (knownCallbackUri) { - mapCallbackUriToRequestId.delete(requestId); - } - - res.writeHead(200, { 'Content-Type': 'text/json' }); - return res.end(knownCallbackUri); -} - -/** - * @param {import('url').UrlWithParsedQuery} parsedUrl - * @param {string} key - * @returns {string | undefined} -*/ -function getFirstQueryValue(parsedUrl, key) { - const result = parsedUrl.query[key]; - return Array.isArray(result) ? result[0] : result; -} - -/** - * @param {import('url').UrlWithParsedQuery} parsedUrl - * @param {string[] | undefined} ignoreKeys - * @returns {Map<string, string>} -*/ -function getFirstQueryValues(parsedUrl, ignoreKeys) { - const queryValues = new Map(); - - for (const key in parsedUrl.query) { - if (ignoreKeys && ignoreKeys.indexOf(key) >= 0) { - continue; - } - - const value = getFirstQueryValue(parsedUrl, key); - if (typeof value === 'string') { - queryValues.set(key, value); - } - } - - return queryValues; -} - -/** - * @param {string} value - */ -function escapeAttribute(value) { - return value.replace(/"/g, '"'); -} - -/** - * @param {string} relativePath - * @param {Object.<string, string>} locations - * @returns {string | undefined} -*/ -function getExtensionFilePath(relativePath, locations) { - const firstSlash = relativePath.indexOf('/'); - if (firstSlash === -1) { - return undefined; - } - const extensionId = relativePath.substr(0, firstSlash); - - const extensionPath = locations[extensionId]; - if (!extensionPath) { - return undefined; - } - return path.join(extensionPath, relativePath.substr(firstSlash + 1)); -} - -/** - * @param {import('http').IncomingMessage} req - * @param {import('http').ServerResponse} res - * @param {string} errorMessage - */ -function serveError(req, res, errorCode, errorMessage, responseHeaders = Object.create(null)) { - responseHeaders['Content-Type'] = 'text/plain'; - res.writeHead(errorCode, responseHeaders); - res.end(errorMessage); -} - -const textMimeType = { - '.html': 'text/html', - '.js': 'text/javascript', - '.json': 'application/json', - '.css': 'text/css', - '.svg': 'image/svg+xml', -}; - -const mapExtToMediaMimes = { - '.bmp': 'image/bmp', - '.gif': 'image/gif', - '.ico': 'image/x-icon', - '.jpe': 'image/jpg', - '.jpeg': 'image/jpg', - '.jpg': 'image/jpg', - '.png': 'image/png', - '.tga': 'image/x-tga', - '.tif': 'image/tiff', - '.tiff': 'image/tiff', - '.woff': 'application/font-woff' -}; - -/** - * @param {string} forPath - */ -function getMediaMime(forPath) { - const ext = path.extname(forPath); - - return mapExtToMediaMimes[ext.toLowerCase()]; -} - -/** - * @param {import('http').IncomingMessage} req - * @param {import('http').ServerResponse} res - * @param {string} filePath - */ -async function serveFile(req, res, filePath, responseHeaders = Object.create(null)) { - try { - - // Sanity checks - filePath = path.normalize(filePath); // ensure no "." and ".." - - const stat = await util.promisify(fs.stat)(filePath); - - // Check if file modified since - const etag = `W/"${[stat.ino, stat.size, stat.mtime.getTime()].join('-')}"`; // weak validator (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) - if (req.headers['if-none-match'] === etag) { - res.writeHead(304); - return res.end(); - } - - // Headers - responseHeaders['Content-Type'] = textMimeType[path.extname(filePath)] || getMediaMime(filePath) || 'text/plain'; - responseHeaders['Etag'] = etag; - - res.writeHead(200, responseHeaders); - - // Data - fs.createReadStream(filePath).pipe(res); - } catch (error) { - console.error(error.toString()); - responseHeaders['Content-Type'] = 'text/plain'; - res.writeHead(404, responseHeaders); - return res.end('Not found'); - } -} - -if (args.launch !== false) { - opn(`${SCHEME}://${HOST}:${PORT}`); -} |