Refactor vscode endpoints to use fork directly.
This commit is contained in:
parent
beebf53adc
commit
d8c344beda
@ -3,4 +3,9 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
# The indent size used in the `package.json` file cannot be changed
|
||||||
|
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
|
||||||
|
[{*.yml,*.yaml,package.json}]
|
||||||
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
7
.github/workflows/ci.yaml
vendored
7
.github/workflows/ci.yaml
vendored
@ -54,10 +54,6 @@ jobs:
|
|||||||
run: yarn lint
|
run: yarn lint
|
||||||
if: success()
|
if: success()
|
||||||
|
|
||||||
- name: Run code-server unit tests
|
|
||||||
run: yarn test:unit
|
|
||||||
if: success()
|
|
||||||
|
|
||||||
- name: Upload coverage report to Codecov
|
- name: Upload coverage report to Codecov
|
||||||
run: yarn coverage
|
run: yarn coverage
|
||||||
if: success()
|
if: success()
|
||||||
@ -408,6 +404,9 @@ jobs:
|
|||||||
rm -r node_modules/playwright
|
rm -r node_modules/playwright
|
||||||
yarn install --check-files
|
yarn install --check-files
|
||||||
|
|
||||||
|
- name: Run end-to-end tests
|
||||||
|
run: yarn test:unit
|
||||||
|
|
||||||
- name: Run end-to-end tests
|
- name: Run end-to-end tests
|
||||||
run: yarn test:e2e
|
run: yarn test:e2e
|
||||||
|
|
||||||
|
@ -2,3 +2,16 @@ printWidth: 120
|
|||||||
semi: false
|
semi: false
|
||||||
trailingComma: all
|
trailingComma: all
|
||||||
arrowParens: always
|
arrowParens: always
|
||||||
|
singleQuote: false
|
||||||
|
useTabs: false
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
# Attempt to keep VScode's existing code style intact.
|
||||||
|
- files: "vendor/modules/code-oss-dev/**/*.ts"
|
||||||
|
options:
|
||||||
|
# No limit defined upstream.
|
||||||
|
printWidth: 10000
|
||||||
|
semi: true
|
||||||
|
singleQuote: true
|
||||||
|
useTabs: true
|
||||||
|
arrowParens: avoid
|
||||||
|
@ -37,10 +37,6 @@ main() {
|
|||||||
chmod +x ./lib/linkup
|
chmod +x ./lib/linkup
|
||||||
set -e
|
set -e
|
||||||
fi
|
fi
|
||||||
|
|
||||||
yarn browserify out/browser/register.js -o out/browser/register.browserified.js
|
|
||||||
yarn browserify out/browser/pages/login.js -o out/browser/pages/login.browserified.js
|
|
||||||
yarn browserify out/browser/pages/vscode.js -o out/browser/pages/vscode.browserified.js
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
@ -81,8 +81,8 @@ bundle_vscode() {
|
|||||||
rsync "$VSCODE_SRC_PATH/extensions/postinstall.js" "$VSCODE_OUT_PATH/extensions"
|
rsync "$VSCODE_SRC_PATH/extensions/postinstall.js" "$VSCODE_OUT_PATH/extensions"
|
||||||
|
|
||||||
mkdir -p "$VSCODE_OUT_PATH/resources/"{linux,web}
|
mkdir -p "$VSCODE_OUT_PATH/resources/"{linux,web}
|
||||||
rsync "$VSCODE_SRC_PATH/resources/linux/code.png" "$VSCODE_OUT_PATH/resources/linux/code.png"
|
rsync "$VSCODE_SRC_PATH/resources/linux/" "$VSCODE_OUT_PATH/resources/linux/"
|
||||||
rsync "$VSCODE_SRC_PATH/resources/web/callback.html" "$VSCODE_OUT_PATH/resources/web/callback.html"
|
rsync "$VSCODE_SRC_PATH/resources/web/" "$VSCODE_OUT_PATH/resources/web/"
|
||||||
|
|
||||||
# Add the commit and date and enable telemetry. This just makes telemetry
|
# Add the commit and date and enable telemetry. This just makes telemetry
|
||||||
# available; telemetry can still be disabled by flag or setting.
|
# available; telemetry can still be disabled by flag or setting.
|
||||||
|
@ -11,8 +11,10 @@ main() {
|
|||||||
|
|
||||||
cd vendor/modules/code-oss-dev
|
cd vendor/modules/code-oss-dev
|
||||||
|
|
||||||
yarn gulp compile-build compile-extensions-build compile-extension-media
|
yarn gulp compile-build compile-extensions-build compile-extension-media compile-web
|
||||||
|
|
||||||
yarn gulp optimize --gulpfile ./coder.js
|
yarn gulp optimize --gulpfile ./coder.js
|
||||||
|
|
||||||
if [[ $MINIFY ]]; then
|
if [[ $MINIFY ]]; then
|
||||||
yarn gulp minify --gulpfile ./coder.js
|
yarn gulp minify --gulpfile ./coder.js
|
||||||
fi
|
fi
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import browserify from "browserify"
|
|
||||||
import * as cp from "child_process"
|
import * as cp from "child_process"
|
||||||
import * as fs from "fs"
|
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { onLine } from "../../src/node/util"
|
import { onLine } from "../../src/node/util"
|
||||||
|
|
||||||
@ -8,7 +6,7 @@ async function main(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
const watcher = new Watcher()
|
const watcher = new Watcher()
|
||||||
await watcher.watch()
|
await watcher.watch()
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error(error.message)
|
console.error(error.message)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
@ -38,6 +36,9 @@ class Watcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const vscode = cp.spawn("yarn", ["watch"], { cwd: this.vscodeSourcePath })
|
const vscode = cp.spawn("yarn", ["watch"], { cwd: this.vscodeSourcePath })
|
||||||
|
|
||||||
|
const vscodeWebExtensions = cp.spawn("yarn", ["watch-web"], { cwd: this.vscodeSourcePath })
|
||||||
|
|
||||||
const tsc = cp.spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath })
|
const tsc = cp.spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath })
|
||||||
const plugin = process.env.PLUGIN_DIR
|
const plugin = process.env.PLUGIN_DIR
|
||||||
? cp.spawn("yarn", ["build", "--watch"], { cwd: process.env.PLUGIN_DIR })
|
? cp.spawn("yarn", ["build", "--watch"], { cwd: process.env.PLUGIN_DIR })
|
||||||
@ -48,6 +49,10 @@ class Watcher {
|
|||||||
vscode.removeAllListeners()
|
vscode.removeAllListeners()
|
||||||
vscode.kill()
|
vscode.kill()
|
||||||
|
|
||||||
|
Watcher.log("killing vs code web extension watcher")
|
||||||
|
vscodeWebExtensions.removeAllListeners()
|
||||||
|
vscodeWebExtensions.kill()
|
||||||
|
|
||||||
Watcher.log("killing tsc")
|
Watcher.log("killing tsc")
|
||||||
tsc.removeAllListeners()
|
tsc.removeAllListeners()
|
||||||
tsc.kill()
|
tsc.kill()
|
||||||
@ -75,10 +80,17 @@ class Watcher {
|
|||||||
Watcher.log("vs code watcher terminated unexpectedly")
|
Watcher.log("vs code watcher terminated unexpectedly")
|
||||||
cleanup(code)
|
cleanup(code)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
vscodeWebExtensions.on("exit", (code) => {
|
||||||
|
Watcher.log("vs code extension watcher terminated unexpectedly")
|
||||||
|
cleanup(code)
|
||||||
|
})
|
||||||
|
|
||||||
tsc.on("exit", (code) => {
|
tsc.on("exit", (code) => {
|
||||||
Watcher.log("tsc terminated unexpectedly")
|
Watcher.log("tsc terminated unexpectedly")
|
||||||
cleanup(code)
|
cleanup(code)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (plugin) {
|
if (plugin) {
|
||||||
plugin.on("exit", (code) => {
|
plugin.on("exit", (code) => {
|
||||||
Watcher.log("plugin terminated unexpectedly")
|
Watcher.log("plugin terminated unexpectedly")
|
||||||
@ -86,18 +98,14 @@ class Watcher {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vscodeWebExtensions.stderr.on("data", (d) => process.stderr.write(d))
|
||||||
vscode.stderr.on("data", (d) => process.stderr.write(d))
|
vscode.stderr.on("data", (d) => process.stderr.write(d))
|
||||||
tsc.stderr.on("data", (d) => process.stderr.write(d))
|
tsc.stderr.on("data", (d) => process.stderr.write(d))
|
||||||
|
|
||||||
if (plugin) {
|
if (plugin) {
|
||||||
plugin.stderr.on("data", (d) => process.stderr.write(d))
|
plugin.stderr.on("data", (d) => process.stderr.write(d))
|
||||||
}
|
}
|
||||||
|
|
||||||
const browserFiles = [
|
|
||||||
path.join(this.rootPath, "out/browser/register.js"),
|
|
||||||
path.join(this.rootPath, "out/browser/pages/login.js"),
|
|
||||||
path.join(this.rootPath, "out/browser/pages/vscode.js"),
|
|
||||||
]
|
|
||||||
|
|
||||||
let startingVscode = false
|
let startingVscode = false
|
||||||
let startedVscode = false
|
let startedVscode = false
|
||||||
onLine(vscode, (line, original) => {
|
onLine(vscode, (line, original) => {
|
||||||
@ -120,7 +128,6 @@ class Watcher {
|
|||||||
console.log("[tsc]", original)
|
console.log("[tsc]", original)
|
||||||
}
|
}
|
||||||
if (line.includes("Watching for file changes")) {
|
if (line.includes("Watching for file changes")) {
|
||||||
bundleBrowserCode(browserFiles)
|
|
||||||
restartServer()
|
restartServer()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -139,19 +146,4 @@ class Watcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function bundleBrowserCode(inputFiles: string[]) {
|
|
||||||
console.log(`[browser] bundling...`)
|
|
||||||
inputFiles.forEach(async (path: string) => {
|
|
||||||
const outputPath = path.replace(".js", ".browserified.js")
|
|
||||||
browserify()
|
|
||||||
.add(path)
|
|
||||||
.bundle()
|
|
||||||
.on("error", function (error: Error) {
|
|
||||||
console.error(error.toString())
|
|
||||||
})
|
|
||||||
.pipe(fs.createWriteStream(outputPath))
|
|
||||||
})
|
|
||||||
console.log(`[browser] done bundling`)
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
10
package.json
10
package.json
@ -35,8 +35,6 @@
|
|||||||
"main": "out/node/entry.js",
|
"main": "out/node/entry.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@schemastore/package": "^0.0.6",
|
"@schemastore/package": "^0.0.6",
|
||||||
"@types/body-parser": "^1.19.0",
|
|
||||||
"@types/browserify": "^12.0.36",
|
|
||||||
"@types/compression": "^1.7.0",
|
"@types/compression": "^1.7.0",
|
||||||
"@types/cookie-parser": "^1.4.2",
|
"@types/cookie-parser": "^1.4.2",
|
||||||
"@types/express": "^4.17.8",
|
"@types/express": "^4.17.8",
|
||||||
@ -48,13 +46,11 @@
|
|||||||
"@types/safe-compare": "^1.1.0",
|
"@types/safe-compare": "^1.1.0",
|
||||||
"@types/semver": "^7.1.0",
|
"@types/semver": "^7.1.0",
|
||||||
"@types/split2": "^3.2.0",
|
"@types/split2": "^3.2.0",
|
||||||
"@types/tar-fs": "^2.0.0",
|
"@types/trusted-types": "^2.0.2",
|
||||||
"@types/tar-stream": "^2.1.0",
|
|
||||||
"@types/ws": "^8.0.0",
|
"@types/ws": "^8.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.7.0",
|
"@typescript-eslint/eslint-plugin": "^4.7.0",
|
||||||
"@typescript-eslint/parser": "^4.7.0",
|
"@typescript-eslint/parser": "^4.7.0",
|
||||||
"audit-ci": "^4.0.0",
|
"audit-ci": "^4.0.0",
|
||||||
"browserify": "^17.0.0",
|
|
||||||
"codecov": "^3.8.3",
|
"codecov": "^3.8.3",
|
||||||
"doctoc": "^2.0.0",
|
"doctoc": "^2.0.0",
|
||||||
"eslint": "^7.7.0",
|
"eslint": "^7.7.0",
|
||||||
@ -68,7 +64,7 @@
|
|||||||
"stylelint": "^13.0.0",
|
"stylelint": "^13.0.0",
|
||||||
"stylelint-config-recommended": "^5.0.0",
|
"stylelint-config-recommended": "^5.0.0",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
"typescript": "^4.1.3"
|
"typescript": "^4.4.0-dev.20210528"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"ansi-regex": "^5.0.1",
|
"ansi-regex": "^5.0.1",
|
||||||
@ -85,7 +81,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coder/logger": "1.1.16",
|
"@coder/logger": "1.1.16",
|
||||||
"argon2": "^0.28.0",
|
"argon2": "^0.28.0",
|
||||||
"body-parser": "^1.19.0",
|
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cookie-parser": "^1.4.5",
|
"cookie-parser": "^1.4.5",
|
||||||
"env-paths": "^2.2.0",
|
"env-paths": "^2.2.0",
|
||||||
@ -103,7 +98,6 @@
|
|||||||
"safe-compare": "^1.1.4",
|
"safe-compare": "^1.1.4",
|
||||||
"semver": "^7.1.3",
|
"semver": "^7.1.3",
|
||||||
"split2": "^3.2.2",
|
"split2": "^3.2.2",
|
||||||
"tar-fs": "^2.0.0",
|
|
||||||
"ws": "^8.0.0",
|
"ws": "^8.0.0",
|
||||||
"xdg-basedir": "^4.0.0",
|
"xdg-basedir": "^4.0.0",
|
||||||
"yarn": "^1.22.4"
|
"yarn": "^1.22.4"
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "code-server",
|
|
||||||
"short_name": "code-server",
|
|
||||||
"start_url": "{{BASE}}",
|
|
||||||
"display": "fullscreen",
|
|
||||||
"background-color": "#fff",
|
|
||||||
"description": "Run editors on a remote server.",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -10,10 +10,11 @@
|
|||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="style-src 'self'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;"
|
content="style-src 'self'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<title>{{ERROR_TITLE}} - code-server</title>
|
<title>{{ERROR_TITLE}} - code-server</title>
|
||||||
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
|
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
|
||||||
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
|
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
|
||||||
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
||||||
<link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" />
|
<link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" />
|
||||||
<link rel="apple-touch-icon" sizes="512x512" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png" />
|
<link rel="apple-touch-icon" sizes="512x512" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png" />
|
||||||
<link href="{{CS_STATIC_BASE}}/src/browser/pages/global.css" rel="stylesheet" />
|
<link href="{{CS_STATIC_BASE}}/src/browser/pages/global.css" rel="stylesheet" />
|
||||||
@ -30,6 +31,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/out/browser/register.browserified.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<title>code-server login</title>
|
<title>code-server login</title>
|
||||||
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
|
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
|
||||||
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
|
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
|
||||||
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
<link rel="manifest" href="{{BASE}}/manifest.json" crossorigin="use-credentials" />
|
||||||
<link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" />
|
<link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" />
|
||||||
<link rel="apple-touch-icon" sizes="512x512" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png" />
|
<link rel="apple-touch-icon" sizes="512x512" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png" />
|
||||||
<link href="{{CS_STATIC_BASE}}/src/browser/pages/global.css" rel="stylesheet" />
|
<link href="{{CS_STATIC_BASE}}/src/browser/pages/global.css" rel="stylesheet" />
|
||||||
@ -30,7 +30,6 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<form class="login-form" method="post">
|
<form class="login-form" method="post">
|
||||||
<input class="user" type="text" autocomplete="username" />
|
<input class="user" type="text" autocomplete="username" />
|
||||||
<input id="base" type="hidden" name="base" value="/" />
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<input
|
<input
|
||||||
required
|
required
|
||||||
@ -49,5 +48,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/out/browser/pages/login.browserified.js"></script>
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
import { getOptions } from "../../common/util"
|
|
||||||
import "../register"
|
|
||||||
|
|
||||||
const options = getOptions()
|
|
||||||
const el = document.getElementById("base") as HTMLInputElement
|
|
||||||
if (el) {
|
|
||||||
el.value = options.base
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script>
|
|
||||||
performance.mark("code/didStartRenderer")
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<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"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Workbench Configuration -->
|
|
||||||
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}" />
|
|
||||||
|
|
||||||
<!-- Workarounds/Hacks (remote user data uri) -->
|
|
||||||
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}" />
|
|
||||||
<meta id="vscode-remote-product-configuration" data-settings="{{PRODUCT_CONFIGURATION}}" />
|
|
||||||
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}" />
|
|
||||||
|
|
||||||
<!-- Workbench Icon/Manifest/CSS -->
|
|
||||||
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
|
|
||||||
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
|
|
||||||
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
|
||||||
<!-- PROD_ONLY
|
|
||||||
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="{{CS_STATIC_BASE}}/vendor/modules/code-oss-dev/out/vs/workbench/workbench.web.api.css">
|
|
||||||
END_PROD_ONLY -->
|
|
||||||
<link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" />
|
|
||||||
<link rel="apple-touch-icon" sizes="512x512" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png" />
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
||||||
|
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body aria-label=""></body>
|
|
||||||
|
|
||||||
<!-- Startup (do not modify order of script tags!) -->
|
|
||||||
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/out/browser/pages/vscode.browserified.js"></script>
|
|
||||||
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/vendor/modules/code-oss-dev/out/vs/loader.js"></script>
|
|
||||||
<script>
|
|
||||||
performance.mark("code/willLoadWorkbenchMain")
|
|
||||||
</script>
|
|
||||||
<!-- PROD_ONLY
|
|
||||||
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/vendor/modules/code-oss-dev/out/vs/workbench/workbench.web.api.nls.js"></script>
|
|
||||||
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/vendor/modules/code-oss-dev/out/vs/workbench/workbench.web.api.js"></script>
|
|
||||||
END_PROD_ONLY -->
|
|
||||||
<script>
|
|
||||||
require(["vs/code/browser/workbench/workbench"], function () {})
|
|
||||||
</script>
|
|
||||||
</html>
|
|
@ -1,253 +0,0 @@
|
|||||||
import { getOptions, Options } from "../../common/util"
|
|
||||||
import "../register"
|
|
||||||
|
|
||||||
// TODO@jsjoeio: Add proper types.
|
|
||||||
type FixMeLater = any
|
|
||||||
|
|
||||||
// NOTE@jsjoeio
|
|
||||||
// This lives here ../../../lib/vscode/src/vs/base/common/platform.ts#L106
|
|
||||||
export const nlsConfigElementId = "vscode-remote-nls-configuration"
|
|
||||||
|
|
||||||
type NlsConfiguration = {
|
|
||||||
locale: string
|
|
||||||
availableLanguages: { [key: string]: string } | {}
|
|
||||||
_languagePackId?: string
|
|
||||||
_translationsConfigFile?: string
|
|
||||||
_cacheRoot?: string
|
|
||||||
_resolvedLanguagePackCoreLocation?: string
|
|
||||||
_corruptedFile?: string
|
|
||||||
_languagePackSupport?: boolean
|
|
||||||
loadBundle?: FixMeLater
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to create the path to the bundle
|
|
||||||
* for getNlsConfiguration.
|
|
||||||
*/
|
|
||||||
export function createBundlePath(_resolvedLanguagePackCoreLocation: string | undefined, bundle: string) {
|
|
||||||
// NOTE@jsjoeio - this comment was here before me
|
|
||||||
// Refers to operating systems that use a different path separator.
|
|
||||||
// Probably just Windows but we're not sure if "/" breaks on Windows
|
|
||||||
// so we'll leave it alone for now.
|
|
||||||
// FIXME: Only works if path separators are /.
|
|
||||||
return (_resolvedLanguagePackCoreLocation || "") + "/" + bundle.replace(/\//g, "!") + ".nls.json"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper function to get the NLS Configuration settings.
|
|
||||||
*
|
|
||||||
* This is used by VSCode for localizations (i.e. changing
|
|
||||||
* the display language).
|
|
||||||
*
|
|
||||||
* Make sure to wrap this in a try/catch block when you call it.
|
|
||||||
**/
|
|
||||||
export function getNlsConfiguration(_document: Document, base: string) {
|
|
||||||
const errorMsgPrefix = "[vscode]"
|
|
||||||
const nlsConfigElement = _document?.getElementById(nlsConfigElementId)
|
|
||||||
const dataSettings = nlsConfigElement?.getAttribute("data-settings")
|
|
||||||
|
|
||||||
if (!nlsConfigElement) {
|
|
||||||
throw new Error(
|
|
||||||
`${errorMsgPrefix} Could not parse NLS configuration. Could not find nlsConfigElement with id: ${nlsConfigElementId}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dataSettings) {
|
|
||||||
throw new Error(
|
|
||||||
`${errorMsgPrefix} Could not parse NLS configuration. Found nlsConfigElement but missing data-settings attribute.`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const nlsConfig = JSON.parse(dataSettings) as NlsConfiguration
|
|
||||||
|
|
||||||
if (nlsConfig._resolvedLanguagePackCoreLocation) {
|
|
||||||
// NOTE@jsjoeio
|
|
||||||
// Not sure why we use Object.create(null) instead of {}
|
|
||||||
// They are not the same
|
|
||||||
// See: https://stackoverflow.com/a/15518712/3015595
|
|
||||||
// We copied this from ../../../lib/vscode/src/bootstrap.js#L143
|
|
||||||
const bundles: {
|
|
||||||
[key: string]: string
|
|
||||||
} = Object.create(null)
|
|
||||||
|
|
||||||
type LoadBundleCallback = (_: undefined, result?: string) => void
|
|
||||||
|
|
||||||
nlsConfig.loadBundle = async (bundle: string, _language: string, cb: LoadBundleCallback): Promise<void> => {
|
|
||||||
const result = bundles[bundle]
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
return cb(undefined, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const path = createBundlePath(nlsConfig._resolvedLanguagePackCoreLocation, bundle)
|
|
||||||
const response = await fetch(`${base}/vscode/resource/?path=${encodeURIComponent(path)}`)
|
|
||||||
const json = await response.json()
|
|
||||||
bundles[bundle] = json
|
|
||||||
return cb(undefined, json)
|
|
||||||
} catch (error) {
|
|
||||||
return cb(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nlsConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetLoaderParams = {
|
|
||||||
nlsConfig: NlsConfiguration
|
|
||||||
options: Options
|
|
||||||
_window: Window
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Link to types in the loader source repo
|
|
||||||
* https://github.com/microsoft/vscode-loader/blob/main/src/loader.d.ts#L280
|
|
||||||
*/
|
|
||||||
type Loader = {
|
|
||||||
baseUrl: string
|
|
||||||
recordStats: boolean
|
|
||||||
// TODO@jsjoeio: There don't appear to be any types for trustedTypes yet.
|
|
||||||
trustedTypesPolicy: FixMeLater
|
|
||||||
paths: {
|
|
||||||
[key: string]: string
|
|
||||||
}
|
|
||||||
"vs/nls": NlsConfiguration
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper function which creates a script url if the value
|
|
||||||
* is valid.
|
|
||||||
*
|
|
||||||
* Extracted into a function to make it easier to test
|
|
||||||
*/
|
|
||||||
export function _createScriptURL(value: string, origin: string): string {
|
|
||||||
if (value.startsWith(origin)) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
throw new Error(`Invalid script url: ${value}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper function to get the require loader
|
|
||||||
*
|
|
||||||
* This used by VSCode/code-server
|
|
||||||
* to load files.
|
|
||||||
*
|
|
||||||
* We extracted the logic into a function so that
|
|
||||||
* it's easier to test.
|
|
||||||
**/
|
|
||||||
export function getConfigurationForLoader({ nlsConfig, options, _window }: GetLoaderParams) {
|
|
||||||
const loader: Loader = {
|
|
||||||
// Without the full URL VS Code will try to load file://.
|
|
||||||
baseUrl: `${window.location.origin}${options.csStaticBase}/vendor/modules/code-oss-dev/out`,
|
|
||||||
recordStats: true,
|
|
||||||
trustedTypesPolicy: (_window as FixMeLater).trustedTypes?.createPolicy("amdLoader", {
|
|
||||||
createScriptURL(value: string): string {
|
|
||||||
return _createScriptURL(value, window.location.origin)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
paths: {
|
|
||||||
"vscode-textmate": `../node_modules/vscode-textmate/release/main`,
|
|
||||||
"vscode-oniguruma": `../node_modules/vscode-oniguruma/release/main`,
|
|
||||||
xterm: `../node_modules/xterm/lib/xterm.js`,
|
|
||||||
"xterm-addon-search": `../node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
|
||||||
"xterm-addon-unicode11": `../node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
|
|
||||||
"xterm-addon-webgl": `../node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
|
|
||||||
"tas-client-umd": `../node_modules/tas-client-umd/lib/tas-client-umd.js`,
|
|
||||||
"iconv-lite-umd": `../node_modules/iconv-lite-umd/lib/iconv-lite-umd.js`,
|
|
||||||
jschardet: `../node_modules/jschardet/dist/jschardet.min.js`,
|
|
||||||
},
|
|
||||||
"vs/nls": nlsConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
return loader
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the body background color to match the theme.
|
|
||||||
*/
|
|
||||||
export function setBodyBackgroundToThemeBackgroundColor(_document: Document, _localStorage: Storage) {
|
|
||||||
const errorMsgPrefix = "[vscode]"
|
|
||||||
const colorThemeData = _localStorage.getItem("colorThemeData")
|
|
||||||
|
|
||||||
if (!colorThemeData) {
|
|
||||||
throw new Error(
|
|
||||||
`${errorMsgPrefix} Could not set body background to theme background color. Could not find colorThemeData in localStorage.`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let _colorThemeData
|
|
||||||
try {
|
|
||||||
// We wrap this JSON.parse logic in a try/catch
|
|
||||||
// because it can throw if the JSON is invalid.
|
|
||||||
// and instead of throwing a random error
|
|
||||||
// we can throw our own error, which will be more helpful
|
|
||||||
// to the end user.
|
|
||||||
_colorThemeData = JSON.parse(colorThemeData)
|
|
||||||
} catch {
|
|
||||||
throw new Error(
|
|
||||||
`${errorMsgPrefix} Could not set body background to theme background color. Could not parse colorThemeData from localStorage.`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasColorMapProperty = Object.prototype.hasOwnProperty.call(_colorThemeData, "colorMap")
|
|
||||||
if (!hasColorMapProperty) {
|
|
||||||
throw new Error(
|
|
||||||
`${errorMsgPrefix} Could not set body background to theme background color. colorThemeData is missing colorMap.`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const editorBgColor = _colorThemeData.colorMap["editor.background"]
|
|
||||||
|
|
||||||
if (!editorBgColor) {
|
|
||||||
throw new Error(
|
|
||||||
`${errorMsgPrefix} Could not set body background to theme background color. colorThemeData.colorMap["editor.background"] is undefined.`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_document.body.style.background = editorBgColor
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper function to encapsulate all the
|
|
||||||
* logic used in this file.
|
|
||||||
*
|
|
||||||
* We purposely include all of this in a single function
|
|
||||||
* so that it's easier to test.
|
|
||||||
*/
|
|
||||||
export function main(_document: Document | undefined, _window: Window | undefined, _localStorage: Storage | undefined) {
|
|
||||||
if (!_document) {
|
|
||||||
throw new Error(`document is undefined.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_window) {
|
|
||||||
throw new Error(`window is undefined.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_localStorage) {
|
|
||||||
throw new Error(`localStorage is undefined.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = getOptions()
|
|
||||||
const nlsConfig = getNlsConfiguration(_document, options.base)
|
|
||||||
|
|
||||||
const loader = getConfigurationForLoader({
|
|
||||||
nlsConfig,
|
|
||||||
options,
|
|
||||||
_window,
|
|
||||||
})
|
|
||||||
|
|
||||||
;(self.require as unknown as Loader) = loader
|
|
||||||
|
|
||||||
setBodyBackgroundToThemeBackgroundColor(_document, _localStorage)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
main(document, window, localStorage)
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[vscode] failed to initialize VS Code")
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { logger } from "@coder/logger"
|
|
||||||
import { getOptions, normalize, logError } from "../common/util"
|
|
||||||
|
|
||||||
export async function registerServiceWorker(): Promise<void> {
|
|
||||||
const options = getOptions()
|
|
||||||
logger.level = options.logLevel
|
|
||||||
|
|
||||||
const path = normalize(`${options.csStaticBase}/out/browser/serviceWorker.js`)
|
|
||||||
try {
|
|
||||||
await navigator.serviceWorker.register(path, {
|
|
||||||
scope: options.base + "/",
|
|
||||||
})
|
|
||||||
logger.info(`[Service Worker] registered`)
|
|
||||||
} catch (error) {
|
|
||||||
logError(logger, `[Service Worker] registration`, error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof navigator !== "undefined" && "serviceWorker" in navigator) {
|
|
||||||
registerServiceWorker()
|
|
||||||
} else {
|
|
||||||
logger.error(`[Service Worker] navigator is undefined`)
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
|
|
||||||
self.addEventListener("install", () => {
|
|
||||||
console.log("[Service Worker] installed")
|
|
||||||
})
|
|
||||||
|
|
||||||
self.addEventListener("activate", (event: any) => {
|
|
||||||
event.waitUntil((self as any).clients.claim())
|
|
||||||
console.log("[Service Worker] activated")
|
|
||||||
})
|
|
||||||
|
|
||||||
self.addEventListener("fetch", () => {
|
|
||||||
// Without this event handler we won't be recognized as a PWA.
|
|
||||||
})
|
|
@ -46,7 +46,7 @@ export class Emitter<T> {
|
|||||||
this.listeners.map(async (cb) => {
|
this.listeners.map(async (cb) => {
|
||||||
try {
|
try {
|
||||||
await cb(value, promise)
|
await cb(value, promise)
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(error.message)
|
logger.error(error.message)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -1,19 +1,3 @@
|
|||||||
/*
|
|
||||||
* This file exists in two locations:
|
|
||||||
* - src/common/util.ts
|
|
||||||
* - lib/vscode/src/vs/server/common/util.ts
|
|
||||||
* The second is a symlink to the first.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base options included on every page.
|
|
||||||
*/
|
|
||||||
export interface Options {
|
|
||||||
base: string
|
|
||||||
csStaticBase: string
|
|
||||||
logLevel: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split a string up to the delimiter. If the delimiter doesn't exist the first
|
* 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.
|
* item will have all the text and the second item will be an empty string.
|
||||||
@ -67,14 +51,14 @@ export const resolveBase = (base?: string): string => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get options embedded in the HTML or query params.
|
* Get client-side configuration embedded in the HTML or query params.
|
||||||
*/
|
*/
|
||||||
export const getOptions = <T extends Options>(): T => {
|
export const getClientConfiguration = <T extends CodeServerLib.ClientConfiguration>(): T => {
|
||||||
let options: T
|
let config: T
|
||||||
try {
|
try {
|
||||||
options = JSON.parse(document.getElementById("coder-options")!.getAttribute("data-settings")!)
|
config = JSON.parse(document.getElementById("coder-options")!.getAttribute("data-settings")!)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
options = {} as T
|
config = {} as T
|
||||||
}
|
}
|
||||||
|
|
||||||
// You can also pass options in stringified form to the options query
|
// You can also pass options in stringified form to the options query
|
||||||
@ -83,16 +67,16 @@ export const getOptions = <T extends Options>(): T => {
|
|||||||
const params = new URLSearchParams(location.search)
|
const params = new URLSearchParams(location.search)
|
||||||
const queryOpts = params.get("options")
|
const queryOpts = params.get("options")
|
||||||
if (queryOpts) {
|
if (queryOpts) {
|
||||||
options = {
|
config = {
|
||||||
...options,
|
...config,
|
||||||
...JSON.parse(queryOpts),
|
...JSON.parse(queryOpts),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options.base = resolveBase(options.base)
|
config.base = resolveBase(config.base)
|
||||||
options.csStaticBase = resolveBase(options.csStaticBase)
|
config.csStaticBase = resolveBase(config.csStaticBase)
|
||||||
|
|
||||||
return options
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,17 +93,6 @@ export const arrayify = <T>(value?: T | T[]): T[] => {
|
|||||||
return [value]
|
return [value]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the first string. If there's no string return undefined.
|
|
||||||
*/
|
|
||||||
export const getFirstString = (value: string | string[] | object | undefined): string | undefined => {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof value === "string" ? value : undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Might make sense to add Error handling to the logger itself.
|
// TODO: Might make sense to add Error handling to the logger itself.
|
||||||
export function logError(logger: { error: (msg: string) => void }, prefix: string, err: Error | string): void {
|
export function logError(logger: { error: (msg: string) => void }, prefix: string, err: Error | string): void {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
|
@ -9,6 +9,35 @@ import { DefaultedArgs } from "./cli"
|
|||||||
import { isNodeJSErrnoException } from "./util"
|
import { isNodeJSErrnoException } from "./util"
|
||||||
import { handleUpgrade } from "./wsRouter"
|
import { handleUpgrade } from "./wsRouter"
|
||||||
|
|
||||||
|
type ListenOptions = Pick<DefaultedArgs, "socket" | "port" | "host">
|
||||||
|
|
||||||
|
const listen = (server: http.Server, { host, port, socket }: ListenOptions) => {
|
||||||
|
return new Promise<void>(async (resolve, reject) => {
|
||||||
|
server.on("error", reject)
|
||||||
|
|
||||||
|
const onListen = () => {
|
||||||
|
// Promise resolved earlier so this is an unrelated error.
|
||||||
|
server.off("error", reject)
|
||||||
|
server.on("error", (err) => util.logError(logger, "http server error", err))
|
||||||
|
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socket) {
|
||||||
|
try {
|
||||||
|
await fs.unlink(socket)
|
||||||
|
} catch (error: any) {
|
||||||
|
handleArgsSocketCatchError(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
server.listen(socket, onListen)
|
||||||
|
} else {
|
||||||
|
// [] is the correct format when using :: but Node errors with them.
|
||||||
|
server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an Express app and an HTTP/S server to serve it.
|
* Create an Express app and an HTTP/S server to serve it.
|
||||||
*/
|
*/
|
||||||
@ -27,28 +56,7 @@ export const createApp = async (args: DefaultedArgs): Promise<[Express, Express,
|
|||||||
)
|
)
|
||||||
: http.createServer(app)
|
: http.createServer(app)
|
||||||
|
|
||||||
let resolved = false
|
await listen(server, args)
|
||||||
await new Promise<void>(async (resolve2, reject) => {
|
|
||||||
const resolve = () => {
|
|
||||||
resolved = true
|
|
||||||
resolve2()
|
|
||||||
}
|
|
||||||
server.on("error", (err) => {
|
|
||||||
handleServerError(resolved, err, reject)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (args.socket) {
|
|
||||||
try {
|
|
||||||
await fs.unlink(args.socket)
|
|
||||||
} catch (error: any) {
|
|
||||||
handleArgsSocketCatchError(error)
|
|
||||||
}
|
|
||||||
server.listen(args.socket, resolve)
|
|
||||||
} else {
|
|
||||||
// [] is the correct format when using :: but Node errors with them.
|
|
||||||
server.listen(args.port, args.host.replace(/^\[|\]$/g, ""), resolve)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const wsApp = express()
|
const wsApp = express()
|
||||||
handleUpgrade(wsApp, server)
|
handleUpgrade(wsApp, server)
|
||||||
|
@ -3,12 +3,11 @@ import { promises as fs } from "fs"
|
|||||||
import yaml from "js-yaml"
|
import yaml from "js-yaml"
|
||||||
import * as os from "os"
|
import * as os from "os"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { Args as VsArgs } from "../../typings/ipc"
|
|
||||||
import { canConnect, generateCertificate, generatePassword, humanPath, paths } from "./util"
|
import { canConnect, generateCertificate, generatePassword, humanPath, paths } from "./util"
|
||||||
|
|
||||||
export enum Feature {
|
export enum Feature {
|
||||||
/** Web socket compression. */
|
// No current experimental features!
|
||||||
PermessageDeflate = "permessage-deflate",
|
Placeholder = "placeholder",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AuthType {
|
export enum AuthType {
|
||||||
@ -30,7 +29,21 @@ export enum LogLevel {
|
|||||||
|
|
||||||
export class OptionalString extends Optional<string> {}
|
export class OptionalString extends Optional<string> {}
|
||||||
|
|
||||||
export interface Args extends VsArgs {
|
export interface Args
|
||||||
|
extends Pick<
|
||||||
|
CodeServerLib.NativeParsedArgs,
|
||||||
|
| "user-data-dir"
|
||||||
|
| "enable-proposed-api"
|
||||||
|
| "extensions-dir"
|
||||||
|
| "builtin-extensions-dir"
|
||||||
|
| "extra-extensions-dir"
|
||||||
|
| "extra-builtin-extensions-dir"
|
||||||
|
| "ignore-last-opened"
|
||||||
|
| "locale"
|
||||||
|
| "log"
|
||||||
|
| "verbose"
|
||||||
|
| "_"
|
||||||
|
> {
|
||||||
config?: string
|
config?: string
|
||||||
auth?: AuthType
|
auth?: AuthType
|
||||||
password?: string
|
password?: string
|
||||||
@ -56,8 +69,6 @@ export interface Args extends VsArgs {
|
|||||||
"show-versions"?: boolean
|
"show-versions"?: boolean
|
||||||
"uninstall-extension"?: string[]
|
"uninstall-extension"?: string[]
|
||||||
"proxy-domain"?: string[]
|
"proxy-domain"?: string[]
|
||||||
locale?: string
|
|
||||||
_: string[]
|
|
||||||
"reuse-window"?: boolean
|
"reuse-window"?: boolean
|
||||||
"new-window"?: boolean
|
"new-window"?: boolean
|
||||||
|
|
||||||
@ -546,7 +557,7 @@ export async function readConfigFile(configPath?: string): Promise<ConfigArgs> {
|
|||||||
flag: "wx", // wx means to fail if the path exists.
|
flag: "wx", // wx means to fail if the path exists.
|
||||||
})
|
})
|
||||||
logger.info(`Wrote default config file to ${humanPath(configPath)}`)
|
logger.info(`Wrote default config file to ${humanPath(configPath)}`)
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
// EEXIST is fine; we don't want to overwrite existing configurations.
|
// EEXIST is fine; we don't want to overwrite existing configurations.
|
||||||
if (error.code !== "EEXIST") {
|
if (error.code !== "EEXIST") {
|
||||||
throw error
|
throw error
|
||||||
@ -670,7 +681,7 @@ export const shouldOpenInExistingInstance = async (args: Args): Promise<string |
|
|||||||
const readSocketPath = async (): Promise<string | undefined> => {
|
const readSocketPath = async (): Promise<string | undefined> => {
|
||||||
try {
|
try {
|
||||||
return await fs.readFile(path.join(os.tmpdir(), "vscode-ipc"), "utf8")
|
return await fs.readFile(path.join(os.tmpdir(), "vscode-ipc"), "utf8")
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
if (error.code !== "ENOENT") {
|
if (error.code !== "ENOENT") {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,13 @@ import { JSONSchemaForNPMPackageJsonFiles } from "@schemastore/package"
|
|||||||
import * as os from "os"
|
import * as os from "os"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
|
|
||||||
|
export const WORKBENCH_WEB_CONFIG_ID = "vscode-workbench-web-configuration"
|
||||||
|
|
||||||
export function getPackageJson(relativePath: string): JSONSchemaForNPMPackageJsonFiles {
|
export function getPackageJson(relativePath: string): JSONSchemaForNPMPackageJsonFiles {
|
||||||
let pkg = {}
|
let pkg = {}
|
||||||
try {
|
try {
|
||||||
pkg = require(relativePath)
|
pkg = require(relativePath)
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.warn(error.message)
|
logger.warn(error.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,5 +21,6 @@ const pkg = getPackageJson("../../package.json")
|
|||||||
export const version = pkg.version || "development"
|
export const version = pkg.version || "development"
|
||||||
export const commit = pkg.commit || "development"
|
export const commit = pkg.commit || "development"
|
||||||
export const rootPath = path.resolve(__dirname, "../..")
|
export const rootPath = path.resolve(__dirname, "../..")
|
||||||
|
export const vsRootPath = path.join(rootPath, "vendor/modules/code-oss-dev")
|
||||||
export const tmpdir = path.join(os.tmpdir(), "code-server")
|
export const tmpdir = path.join(os.tmpdir(), "code-server")
|
||||||
export const isDevMode = commit === "development"
|
export const isDevMode = commit === "development"
|
||||||
|
@ -9,11 +9,11 @@ import {
|
|||||||
} from "./cli"
|
} from "./cli"
|
||||||
import { commit, version } from "./constants"
|
import { commit, version } from "./constants"
|
||||||
import { openInExistingInstance, runCodeServer, runVsCodeCli } from "./main"
|
import { openInExistingInstance, runCodeServer, runVsCodeCli } from "./main"
|
||||||
import * as proxyAgent from "./proxy_agent"
|
import { monkeyPatchProxyProtocols } from "./proxy_agent"
|
||||||
import { isChild, wrapper } from "./wrapper"
|
import { isChild, wrapper } from "./wrapper"
|
||||||
|
|
||||||
async function entry(): Promise<void> {
|
async function entry(): Promise<void> {
|
||||||
proxyAgent.monkeyPatch(false)
|
monkeyPatchProxyProtocols()
|
||||||
|
|
||||||
// There's no need to check flags like --help or to spawn in an existing
|
// There's no need to check flags like --help or to spawn in an existing
|
||||||
// instance for the child process because these would have already happened in
|
// instance for the child process because these would have already happened in
|
||||||
@ -46,11 +46,13 @@ async function entry(): Promise<void> {
|
|||||||
|
|
||||||
if (args.version) {
|
if (args.version) {
|
||||||
if (args.json) {
|
if (args.json) {
|
||||||
console.log({
|
console.log(
|
||||||
codeServer: version,
|
JSON.stringify({
|
||||||
commit,
|
codeServer: version,
|
||||||
vscode: require("../../vendor/modules/code-oss-dev/package.json").version,
|
commit,
|
||||||
})
|
vscode: require("../../vendor/modules/code-oss-dev/package.json").version,
|
||||||
|
}),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
console.log(version, commit)
|
console.log(version, commit)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { field, logger } from "@coder/logger"
|
import { field, logger } from "@coder/logger"
|
||||||
import * as express from "express"
|
import * as express from "express"
|
||||||
import * as expressCore from "express-serve-static-core"
|
import * as expressCore from "express-serve-static-core"
|
||||||
|
import path from "path"
|
||||||
import qs from "qs"
|
import qs from "qs"
|
||||||
import { HttpCode, HttpError } from "../common/http"
|
import { HttpCode, HttpError } from "../common/http"
|
||||||
import { normalize, Options } from "../common/util"
|
import { normalize } from "../common/util"
|
||||||
import { AuthType, DefaultedArgs } from "./cli"
|
import { AuthType, DefaultedArgs } from "./cli"
|
||||||
import { commit, rootPath } from "./constants"
|
import { version as codeServerVersion } from "./constants"
|
||||||
import { Heart } from "./heart"
|
import { Heart } from "./heart"
|
||||||
import { getPasswordMethod, IsCookieValidArgs, isCookieValid, sanitizeString, escapeHtml } from "./util"
|
import { getPasswordMethod, IsCookieValidArgs, isCookieValid, sanitizeString, escapeHtml, escapeJSON } from "./util"
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
@ -19,6 +20,16 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createClientConfiguration = (req: express.Request): CodeServerLib.ClientConfiguration => {
|
||||||
|
const base = relativeRoot(req)
|
||||||
|
|
||||||
|
return {
|
||||||
|
base,
|
||||||
|
csStaticBase: normalize(path.join(base, "_static/")),
|
||||||
|
codeServerVersion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace common variable strings in HTML templates.
|
* Replace common variable strings in HTML templates.
|
||||||
*/
|
*/
|
||||||
@ -27,18 +38,16 @@ export const replaceTemplates = <T extends object>(
|
|||||||
content: string,
|
content: string,
|
||||||
extraOpts?: Omit<T, "base" | "csStaticBase" | "logLevel">,
|
extraOpts?: Omit<T, "base" | "csStaticBase" | "logLevel">,
|
||||||
): string => {
|
): string => {
|
||||||
const base = relativeRoot(req)
|
const serverOptions: CodeServerLib.ClientConfiguration = {
|
||||||
const options: Options = {
|
...createClientConfiguration(req),
|
||||||
base,
|
|
||||||
csStaticBase: base + "/static/" + commit + rootPath,
|
|
||||||
logLevel: logger.level,
|
|
||||||
...extraOpts,
|
...extraOpts,
|
||||||
}
|
}
|
||||||
|
|
||||||
return content
|
return content
|
||||||
.replace(/{{TO}}/g, (typeof req.query.to === "string" && escapeHtml(req.query.to)) || "/")
|
.replace(/{{TO}}/g, (typeof req.query.to === "string" && escapeHtml(req.query.to)) || "/")
|
||||||
.replace(/{{BASE}}/g, options.base)
|
.replace(/{{BASE}}/g, serverOptions.base)
|
||||||
.replace(/{{CS_STATIC_BASE}}/g, options.csStaticBase)
|
.replace(/{{CS_STATIC_BASE}}/g, serverOptions.csStaticBase)
|
||||||
.replace(/"{{OPTIONS}}"/, `'${JSON.stringify(options)}'`)
|
.replace("{{OPTIONS}}", () => escapeJSON(serverOptions))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { field, logger } from "@coder/logger"
|
import { field, logger } from "@coder/logger"
|
||||||
import * as cp from "child_process"
|
|
||||||
import http from "http"
|
import http from "http"
|
||||||
import * as path from "path"
|
import path from "path"
|
||||||
import { CliMessage, OpenCommandPipeArgs } from "../../typings/ipc"
|
|
||||||
import { plural } from "../common/util"
|
import { plural } from "../common/util"
|
||||||
import { createApp, ensureAddress } from "./app"
|
import { createApp, ensureAddress } from "./app"
|
||||||
import { AuthType, DefaultedArgs, Feature } from "./cli"
|
import { AuthType, DefaultedArgs, Feature } from "./cli"
|
||||||
@ -10,41 +8,35 @@ import { coderCloudBind } from "./coder_cloud"
|
|||||||
import { commit, version } from "./constants"
|
import { commit, version } from "./constants"
|
||||||
import { startLink } from "./link"
|
import { startLink } from "./link"
|
||||||
import { register } from "./routes"
|
import { register } from "./routes"
|
||||||
import { humanPath, isFile, open } from "./util"
|
import { humanPath, isFile, loadAMDModule, open } from "./util"
|
||||||
|
|
||||||
export const runVsCodeCli = (args: DefaultedArgs): void => {
|
/**
|
||||||
logger.debug("forking vs code cli...")
|
* This is useful when an CLI arg should be passed to VS Code directly,
|
||||||
const vscode = cp.fork(path.resolve(__dirname, "../../vendor/modules/code-oss-dev/out/vs/server/fork"), [], {
|
* such as when managing extensions.
|
||||||
env: {
|
* @deprecated This should be removed when code-server merges with lib/vscode.
|
||||||
...process.env,
|
*/
|
||||||
CODE_SERVER_PARENT_PID: process.pid.toString(),
|
export const runVsCodeCli = async (args: DefaultedArgs): Promise<void> => {
|
||||||
},
|
logger.debug("Running VS Code CLI")
|
||||||
})
|
|
||||||
vscode.once("message", (message: any) => {
|
const cliProcessMain = await loadAMDModule<CodeServerLib.IMainCli["main"]>("vs/code/node/cliProcessMain", "main")
|
||||||
logger.debug("got message from VS Code", field("message", message))
|
|
||||||
if (message.type !== "ready") {
|
try {
|
||||||
logger.error("Unexpected response waiting for ready response", field("type", message.type))
|
await cliProcessMain(args)
|
||||||
process.exit(1)
|
} catch (error) {
|
||||||
}
|
|
||||||
const send: CliMessage = { type: "cli", args }
|
|
||||||
vscode.send(send)
|
|
||||||
})
|
|
||||||
vscode.once("error", (error) => {
|
|
||||||
logger.error("Got error from VS Code", field("error", error))
|
logger.error("Got error from VS Code", field("error", error))
|
||||||
process.exit(1)
|
}
|
||||||
})
|
|
||||||
vscode.on("exit", (code) => process.exit(code || 0))
|
process.exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => {
|
export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => {
|
||||||
const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = {
|
const pipeArgs: CodeServerLib.OpenCommandPipeArgs & { fileURIs: string[] } = {
|
||||||
type: "open",
|
type: "open",
|
||||||
folderURIs: [],
|
folderURIs: [],
|
||||||
fileURIs: [],
|
fileURIs: [],
|
||||||
forceReuseWindow: args["reuse-window"],
|
forceReuseWindow: args["reuse-window"],
|
||||||
forceNewWindow: args["new-window"],
|
forceNewWindow: args["new-window"],
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < args._.length; i++) {
|
for (let i = 0; i < args._.length; i++) {
|
||||||
const fp = path.resolve(args._[i])
|
const fp = path.resolve(args._[i])
|
||||||
if (await isFile(fp)) {
|
if (await isFile(fp)) {
|
||||||
@ -53,17 +45,14 @@ export const openInExistingInstance = async (args: DefaultedArgs, socketPath: st
|
|||||||
pipeArgs.folderURIs.push(fp)
|
pipeArgs.folderURIs.push(fp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pipeArgs.forceNewWindow && pipeArgs.fileURIs.length > 0) {
|
if (pipeArgs.forceNewWindow && pipeArgs.fileURIs.length > 0) {
|
||||||
logger.error("--new-window can only be used with folder paths")
|
logger.error("--new-window can only be used with folder paths")
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pipeArgs.folderURIs.length === 0 && pipeArgs.fileURIs.length === 0) {
|
if (pipeArgs.folderURIs.length === 0 && pipeArgs.fileURIs.length === 0) {
|
||||||
logger.error("Please specify at least one file or folder")
|
logger.error("Please specify at least one file or folder")
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const vscode = http.request(
|
const vscode = http.request(
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
@ -135,8 +124,8 @@ export const runCodeServer = async (args: DefaultedArgs): Promise<http.Server> =
|
|||||||
startLink(port).catch((ex) => {
|
startLink(port).catch((ex) => {
|
||||||
logger.debug("Link daemon exited!", field("error", ex))
|
logger.debug("Link daemon exited!", field("error", ex))
|
||||||
})
|
})
|
||||||
} catch (ex) {
|
} catch (error) {
|
||||||
logger.debug("Failed to start link daemon!", ex)
|
logger.debug("Failed to start link daemon!", error as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.enable && args.enable.length > 0) {
|
if (args.enable && args.enable.length > 0) {
|
||||||
|
@ -172,9 +172,9 @@ export class PluginAPI {
|
|||||||
}
|
}
|
||||||
await this.loadPlugin(path.join(dir, ent.name))
|
await this.loadPlugin(path.join(dir, ent.name))
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (error: any) {
|
||||||
if (err.code !== "ENOENT") {
|
if (error.code !== "ENOENT") {
|
||||||
this.logger.warn(`failed to load plugins from ${q(dir)}: ${err.message}`)
|
this.logger.warn(`failed to load plugins from ${q(dir)}: ${error.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,9 +195,9 @@ export class PluginAPI {
|
|||||||
}
|
}
|
||||||
const p = this._loadPlugin(dir, packageJSON)
|
const p = this._loadPlugin(dir, packageJSON)
|
||||||
this.plugins.set(p.name, p)
|
this.plugins.set(p.name, p)
|
||||||
} catch (err) {
|
} catch (error: any) {
|
||||||
if (err.code !== "ENOENT") {
|
if (error.code !== "ENOENT") {
|
||||||
this.logger.warn(`failed to load plugin: ${err.stack}`)
|
this.logger.warn(`failed to load plugin: ${error.stack}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -278,7 +278,7 @@ export class PluginAPI {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await p.deinit()
|
await p.deinit()
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
this.logger.error("plugin failed to deinit", field("name", p.name), field("error", error.message))
|
this.logger.error("plugin failed to deinit", field("name", p.name), field("error", error.message))
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { logger } from "@coder/logger"
|
/*---------------------------------------------------------------------------------------------
|
||||||
import * as http from "http"
|
* Copyright (c) Coder Technologies. All rights reserved.
|
||||||
import * as proxyAgent from "proxy-agent"
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
import * as proxyFromEnv from "proxy-from-env"
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import ProxyAgent from "proxy-agent"
|
||||||
|
import { getProxyForUrl } from "proxy-from-env"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file has nothing to do with the code-server proxy.
|
* This file has nothing to do with the code-server proxy.
|
||||||
@ -11,10 +14,6 @@ import * as proxyFromEnv from "proxy-from-env"
|
|||||||
* - https://www.npmjs.com/package/proxy-agent
|
* - https://www.npmjs.com/package/proxy-agent
|
||||||
* - https://www.npmjs.com/package/proxy-from-env
|
* - https://www.npmjs.com/package/proxy-from-env
|
||||||
*
|
*
|
||||||
* This file exists in two locations:
|
|
||||||
* - src/node/proxy_agent.ts
|
|
||||||
* - lib/vscode/src/vs/base/node/proxy_agent.ts
|
|
||||||
* The second is a symlink to the first.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,50 +30,41 @@ import * as proxyFromEnv from "proxy-from-env"
|
|||||||
* Even if they do, it's probably the same proxy so we should be fine! And those knobs
|
* Even if they do, it's probably the same proxy so we should be fine! And those knobs
|
||||||
* are deprecated anyway.
|
* are deprecated anyway.
|
||||||
*/
|
*/
|
||||||
export function monkeyPatch(inVSCode: boolean): void {
|
export function monkeyPatchProxyProtocols(): void {
|
||||||
if (shouldEnableProxy()) {
|
if (!shouldEnableProxy()) {
|
||||||
const http = require("http")
|
return
|
||||||
const https = require("https")
|
|
||||||
|
|
||||||
// If we do not pass in a proxy URL, proxy-agent will get the URL from the environment.
|
|
||||||
// See https://www.npmjs.com/package/proxy-from-env.
|
|
||||||
// Also see shouldEnableProxy.
|
|
||||||
const pa = newProxyAgent(inVSCode)
|
|
||||||
http.globalAgent = pa
|
|
||||||
https.globalAgent = pa
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const http = require("http")
|
||||||
|
const https = require("https")
|
||||||
|
|
||||||
|
// If we do not pass in a proxy URL, proxy-agent will get the URL from the environment.
|
||||||
|
// See https://www.npmjs.com/package/proxy-from-env.
|
||||||
|
// Also see shouldEnableProxy.
|
||||||
|
const pa = new ProxyAgent()
|
||||||
|
http.globalAgent = pa
|
||||||
|
https.globalAgent = pa
|
||||||
}
|
}
|
||||||
|
|
||||||
function newProxyAgent(inVSCode: boolean): http.Agent {
|
const sampleUrls = [new URL("http://example.com"), new URL("https://example.com")]
|
||||||
// The reasoning for this split is that VS Code's build process does not have
|
|
||||||
// esModuleInterop enabled but the code-server one does. As a result depending on where
|
|
||||||
// we execute, we either have a default attribute or we don't.
|
|
||||||
//
|
|
||||||
// I can't enable esModuleInterop in VS Code's build process as it breaks and spits out
|
|
||||||
// a huge number of errors. And we can't use require as otherwise the modules won't be
|
|
||||||
// included in the final product.
|
|
||||||
if (inVSCode) {
|
|
||||||
return new (proxyAgent as any)()
|
|
||||||
} else {
|
|
||||||
return new (proxyAgent as any).default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If they have $NO_PROXY set to example.com then this check won't work!
|
// If they have $NO_PROXY set to example.com then this check won't work!
|
||||||
// But that's drastically unlikely.
|
// But that's drastically unlikely.
|
||||||
export function shouldEnableProxy(): boolean {
|
export function shouldEnableProxy(): boolean {
|
||||||
|
const testedProxyEndpoints = sampleUrls.map((url) => {
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
proxyUrl: getProxyForUrl(url.toString()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
let shouldEnable = false
|
let shouldEnable = false
|
||||||
|
|
||||||
const httpProxy = proxyFromEnv.getProxyForUrl(`http://example.com`)
|
for (const { url, proxyUrl } of testedProxyEndpoints) {
|
||||||
if (httpProxy) {
|
if (proxyUrl) {
|
||||||
shouldEnable = true
|
console.debug(`${url.protocol} -- Using "${proxyUrl}"`)
|
||||||
logger.debug(`using $HTTP_PROXY ${httpProxy}`)
|
shouldEnable = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const httpsProxy = proxyFromEnv.getProxyForUrl(`https://example.com`)
|
|
||||||
if (httpsProxy) {
|
|
||||||
shouldEnable = true
|
|
||||||
logger.debug(`using $HTTPS_PROXY ${httpsProxy}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return shouldEnable
|
return shouldEnable
|
||||||
|
45
src/node/routes/errors.ts
Normal file
45
src/node/routes/errors.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { logger } from "@coder/logger"
|
||||||
|
import express from "express"
|
||||||
|
import { promises as fs } from "fs"
|
||||||
|
import path from "path"
|
||||||
|
import { WebsocketRequest } from "../../../typings/pluginapi"
|
||||||
|
import { HttpCode } from "../../common/http"
|
||||||
|
import { rootPath } from "../constants"
|
||||||
|
import { replaceTemplates } from "../http"
|
||||||
|
import { getMediaMime } from "../util"
|
||||||
|
|
||||||
|
const notFoundCodes = ["ENOENT", "EISDIR", "FileNotFound"]
|
||||||
|
export const errorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
|
||||||
|
if (notFoundCodes.includes(err.code)) {
|
||||||
|
err.status = HttpCode.NotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = err.status ?? err.statusCode ?? 500
|
||||||
|
res.status(status)
|
||||||
|
|
||||||
|
// Assume anything that explicitly accepts text/html is a user browsing a
|
||||||
|
// page (as opposed to an xhr request). Don't use `req.accepts()` since
|
||||||
|
// *every* request that I've seen (in Firefox and Chromium at least)
|
||||||
|
// includes `*/*` making it always truthy. Even for css/javascript.
|
||||||
|
if (req.headers.accept && req.headers.accept.includes("text/html")) {
|
||||||
|
const resourcePath = path.resolve(rootPath, "src/browser/pages/error.html")
|
||||||
|
res.set("Content-Type", getMediaMime(resourcePath))
|
||||||
|
const content = await fs.readFile(resourcePath, "utf8")
|
||||||
|
res.send(
|
||||||
|
replaceTemplates(req, content)
|
||||||
|
.replace(/{{ERROR_TITLE}}/g, status)
|
||||||
|
.replace(/{{ERROR_HEADER}}/g, status)
|
||||||
|
.replace(/{{ERROR_BODY}}/g, err.message),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
res.json({
|
||||||
|
error: err.message,
|
||||||
|
...(err.details || {}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
|
||||||
|
logger.error(`${err.message} ${err.stack}`)
|
||||||
|
;(req as WebsocketRequest).ws.end()
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
import { logger } from "@coder/logger"
|
import { logger } from "@coder/logger"
|
||||||
import bodyParser from "body-parser"
|
|
||||||
import cookieParser from "cookie-parser"
|
import cookieParser from "cookie-parser"
|
||||||
import * as express from "express"
|
import * as express from "express"
|
||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
@ -10,22 +9,21 @@ import * as pluginapi from "../../../typings/pluginapi"
|
|||||||
import { HttpCode, HttpError } from "../../common/http"
|
import { HttpCode, HttpError } from "../../common/http"
|
||||||
import { plural } from "../../common/util"
|
import { plural } from "../../common/util"
|
||||||
import { AuthType, DefaultedArgs } from "../cli"
|
import { AuthType, DefaultedArgs } from "../cli"
|
||||||
import { rootPath } from "../constants"
|
import { commit, isDevMode, rootPath } from "../constants"
|
||||||
import { Heart } from "../heart"
|
import { Heart } from "../heart"
|
||||||
import { ensureAuthenticated, redirect, replaceTemplates } from "../http"
|
import { ensureAuthenticated, redirect } from "../http"
|
||||||
import { PluginAPI } from "../plugin"
|
import { PluginAPI } from "../plugin"
|
||||||
import { getMediaMime, paths } from "../util"
|
import { getMediaMime, paths } from "../util"
|
||||||
import { wrapper } from "../wrapper"
|
import { wrapper } from "../wrapper"
|
||||||
import * as apps from "./apps"
|
import * as apps from "./apps"
|
||||||
import * as domainProxy from "./domainProxy"
|
import * as domainProxy from "./domainProxy"
|
||||||
|
import { errorHandler, wsErrorHandler } from "./errors"
|
||||||
import * as health from "./health"
|
import * as health from "./health"
|
||||||
import * as login from "./login"
|
import * as login from "./login"
|
||||||
import * as logout from "./logout"
|
import * as logout from "./logout"
|
||||||
import * as pathProxy from "./pathProxy"
|
import * as pathProxy from "./pathProxy"
|
||||||
// static is a reserved keyword.
|
|
||||||
import * as _static from "./static"
|
|
||||||
import * as update from "./update"
|
import * as update from "./update"
|
||||||
import * as vscode from "./vscode"
|
import { createVSServerRouter, VSServerResult } from "./vscode"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register all routes and middleware.
|
* Register all routes and middleware.
|
||||||
@ -124,13 +122,15 @@ export const register = async (
|
|||||||
wrapper.onDispose(() => pluginApi.dispose())
|
wrapper.onDispose(() => pluginApi.dispose())
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(bodyParser.json())
|
app.use(express.json())
|
||||||
app.use(bodyParser.urlencoded({ extended: true }))
|
app.use(express.urlencoded({ extended: true }))
|
||||||
|
|
||||||
app.use("/", vscode.router)
|
app.use(
|
||||||
wsApp.use("/", vscode.wsRouter.router)
|
"/_static",
|
||||||
app.use("/vscode", vscode.router)
|
express.static(rootPath, {
|
||||||
wsApp.use("/vscode", vscode.wsRouter.router)
|
cacheControl: commit !== "development",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
app.use("/healthz", health.router)
|
app.use("/healthz", health.router)
|
||||||
wsApp.use("/healthz", health.wsRouter.router)
|
wsApp.use("/healthz", health.wsRouter.router)
|
||||||
@ -143,49 +143,32 @@ export const register = async (
|
|||||||
app.all("/logout", (req, res) => redirect(req, res, "/", {}))
|
app.all("/logout", (req, res) => redirect(req, res, "/", {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use("/static", _static.router)
|
|
||||||
app.use("/update", update.router)
|
app.use("/update", update.router)
|
||||||
|
|
||||||
|
let vscode: VSServerResult
|
||||||
|
try {
|
||||||
|
vscode = await createVSServerRouter(args)
|
||||||
|
app.use("/", vscode.router)
|
||||||
|
wsApp.use("/", vscode.wsRouter.router)
|
||||||
|
app.use("/vscode", vscode.router)
|
||||||
|
wsApp.use("/vscode", vscode.wsRouter.router)
|
||||||
|
} catch (error: any) {
|
||||||
|
if (isDevMode) {
|
||||||
|
logger.warn(error)
|
||||||
|
logger.warn("VS Server router may still be compiling.")
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server.on("close", () => {
|
||||||
|
vscode.vscodeServer.close()
|
||||||
|
})
|
||||||
|
|
||||||
app.use(() => {
|
app.use(() => {
|
||||||
throw new HttpError("Not Found", HttpCode.NotFound)
|
throw new HttpError("Not Found", HttpCode.NotFound)
|
||||||
})
|
})
|
||||||
|
|
||||||
const errorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
|
|
||||||
if (err.code === "ENOENT" || err.code === "EISDIR") {
|
|
||||||
err.status = HttpCode.NotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = err.status ?? err.statusCode ?? 500
|
|
||||||
res.status(status)
|
|
||||||
|
|
||||||
// Assume anything that explicitly accepts text/html is a user browsing a
|
|
||||||
// page (as opposed to an xhr request). Don't use `req.accepts()` since
|
|
||||||
// *every* request that I've seen (in Firefox and Chromium at least)
|
|
||||||
// includes `*/*` making it always truthy. Even for css/javascript.
|
|
||||||
if (req.headers.accept && req.headers.accept.includes("text/html")) {
|
|
||||||
const resourcePath = path.resolve(rootPath, "src/browser/pages/error.html")
|
|
||||||
res.set("Content-Type", getMediaMime(resourcePath))
|
|
||||||
const content = await fs.readFile(resourcePath, "utf8")
|
|
||||||
res.send(
|
|
||||||
replaceTemplates(req, content)
|
|
||||||
.replace(/{{ERROR_TITLE}}/g, status)
|
|
||||||
.replace(/{{ERROR_HEADER}}/g, status)
|
|
||||||
.replace(/{{ERROR_BODY}}/g, err.message),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
res.json({
|
|
||||||
error: err.message,
|
|
||||||
...(err.details || {}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(errorHandler)
|
app.use(errorHandler)
|
||||||
|
|
||||||
const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
|
|
||||||
logger.error(`${err.message} ${err.stack}`)
|
|
||||||
;(req as pluginapi.WebsocketRequest).ws.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
wsApp.use(wsErrorHandler)
|
wsApp.use(wsErrorHandler)
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ router.post("/", async (req, res) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
throw new Error("Incorrect password")
|
throw new Error("Incorrect password")
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
const renderedHtml = await getRoot(req, error)
|
const renderedHtml = await getRoot(req, error)
|
||||||
res.send(renderedHtml)
|
res.send(renderedHtml)
|
||||||
}
|
}
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
import { field, logger } from "@coder/logger"
|
|
||||||
import { Router } from "express"
|
|
||||||
import { promises as fs } from "fs"
|
|
||||||
import * as path from "path"
|
|
||||||
import { Readable } from "stream"
|
|
||||||
import * as tarFs from "tar-fs"
|
|
||||||
import * as zlib from "zlib"
|
|
||||||
import { HttpCode, HttpError } from "../../common/http"
|
|
||||||
import { getFirstString } from "../../common/util"
|
|
||||||
import { rootPath } from "../constants"
|
|
||||||
import { authenticated, ensureAuthenticated, replaceTemplates } from "../http"
|
|
||||||
import { getMediaMime, pathToFsPath } from "../util"
|
|
||||||
|
|
||||||
export const router = Router()
|
|
||||||
|
|
||||||
// The commit is for caching.
|
|
||||||
router.get("/(:commit)(/*)?", async (req, res) => {
|
|
||||||
// Used by VS Code to load extensions into the web worker.
|
|
||||||
const tar = getFirstString(req.query.tar)
|
|
||||||
if (tar) {
|
|
||||||
await ensureAuthenticated(req)
|
|
||||||
let stream: Readable = tarFs.pack(pathToFsPath(tar))
|
|
||||||
if (req.headers["accept-encoding"] && req.headers["accept-encoding"].includes("gzip")) {
|
|
||||||
logger.debug("gzipping tar", field("path", tar))
|
|
||||||
const compress = zlib.createGzip()
|
|
||||||
stream.pipe(compress)
|
|
||||||
stream.on("error", (error) => compress.destroy(error))
|
|
||||||
stream.on("close", () => compress.end())
|
|
||||||
stream = compress
|
|
||||||
res.header("content-encoding", "gzip")
|
|
||||||
}
|
|
||||||
res.set("Content-Type", "application/x-tar")
|
|
||||||
stream.on("close", () => res.end())
|
|
||||||
return stream.pipe(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not a tar use the remainder of the path to load the resource.
|
|
||||||
if (!req.params[0]) {
|
|
||||||
throw new HttpError("Not Found", HttpCode.NotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourcePath = path.resolve(req.params[0])
|
|
||||||
|
|
||||||
// Make sure it's in code-server if you aren't authenticated. This lets
|
|
||||||
// unauthenticated users load the login assets.
|
|
||||||
const isAuthenticated = await authenticated(req)
|
|
||||||
if (!resourcePath.startsWith(rootPath) && !isAuthenticated) {
|
|
||||||
throw new HttpError("Unauthorized", HttpCode.Unauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't cache during development. - can also be used if you want to make a
|
|
||||||
// static request without caching.
|
|
||||||
if (req.params.commit !== "development" && req.params.commit !== "-") {
|
|
||||||
res.header("Cache-Control", "public, max-age=31536000")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Without this the default is to use the directory the script loaded from.
|
|
||||||
if (req.headers["service-worker"]) {
|
|
||||||
res.header("service-worker-allowed", "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
res.set("Content-Type", getMediaMime(resourcePath))
|
|
||||||
|
|
||||||
if (resourcePath.endsWith("manifest.json")) {
|
|
||||||
const content = await fs.readFile(resourcePath, "utf8")
|
|
||||||
return res.send(replaceTemplates(req, content))
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = await fs.readFile(resourcePath)
|
|
||||||
return res.send(content)
|
|
||||||
})
|
|
@ -1,232 +1,73 @@
|
|||||||
import * as crypto from "crypto"
|
import * as express from "express"
|
||||||
import { Request, Router } from "express"
|
import { Server } from "http"
|
||||||
import { promises as fs } from "fs"
|
import path from "path"
|
||||||
import * as path from "path"
|
import { AuthType, DefaultedArgs } from "../cli"
|
||||||
import qs from "qs"
|
import { version as codeServerVersion, vsRootPath } from "../constants"
|
||||||
import * as ipc from "../../../typings/ipc"
|
import { ensureAuthenticated } from "../http"
|
||||||
import { Emitter } from "../../common/emitter"
|
import { loadAMDModule } from "../util"
|
||||||
import { HttpCode, HttpError } from "../../common/http"
|
import { Router as WsRouter, WebsocketRouter } from "../wsRouter"
|
||||||
import { getFirstString } from "../../common/util"
|
import { errorHandler } from "./errors"
|
||||||
import { Feature } from "../cli"
|
|
||||||
import { isDevMode, rootPath, version } from "../constants"
|
|
||||||
import { authenticated, ensureAuthenticated, redirect, replaceTemplates } from "../http"
|
|
||||||
import { getMediaMime, pathToFsPath } from "../util"
|
|
||||||
import { VscodeProvider } from "../vscode"
|
|
||||||
import { Router as WsRouter } from "../wsRouter"
|
|
||||||
|
|
||||||
export const router = Router()
|
export interface VSServerResult {
|
||||||
|
router: express.Router
|
||||||
const vscode = new VscodeProvider()
|
wsRouter: WebsocketRouter
|
||||||
|
vscodeServer: Server
|
||||||
router.get("/", async (req, res) => {
|
|
||||||
const isAuthenticated = await authenticated(req)
|
|
||||||
if (!isAuthenticated) {
|
|
||||||
return redirect(req, res, "login", {
|
|
||||||
// req.baseUrl can be blank if already at the root.
|
|
||||||
to: req.baseUrl && req.baseUrl !== "/" ? req.baseUrl : undefined,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const [content, options] = await Promise.all([
|
|
||||||
await fs.readFile(path.join(rootPath, "src/browser/pages/vscode.html"), "utf8"),
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
return await vscode.initialize({ args: req.args, remoteAuthority: req.headers.host || "" }, req.query)
|
|
||||||
} catch (error) {
|
|
||||||
const devMessage = isDevMode ? "It might not have finished compiling." : ""
|
|
||||||
throw new Error(`VS Code failed to load. ${devMessage} ${error.message}`)
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
])
|
|
||||||
|
|
||||||
options.productConfiguration.codeServerVersion = version
|
|
||||||
|
|
||||||
res.send(
|
|
||||||
replaceTemplates<ipc.Options>(
|
|
||||||
req,
|
|
||||||
// Uncomment prod blocks if not in development. TODO: Would this be
|
|
||||||
// better as a build step? Or maintain two HTML files again?
|
|
||||||
!isDevMode ? content.replace(/<!-- PROD_ONLY/g, "").replace(/END_PROD_ONLY -->/g, "") : content,
|
|
||||||
{
|
|
||||||
authed: req.args.auth !== "none",
|
|
||||||
disableUpdateCheck: !!req.args["disable-update-check"],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.replace(`"{{REMOTE_USER_DATA_URI}}"`, `'${JSON.stringify(options.remoteUserDataUri)}'`)
|
|
||||||
.replace(`"{{PRODUCT_CONFIGURATION}}"`, `'${JSON.stringify(options.productConfiguration)}'`)
|
|
||||||
.replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`)
|
|
||||||
.replace(`"{{NLS_CONFIGURATION}}"`, `'${JSON.stringify(options.nlsConfiguration)}'`),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Might currently be unused.
|
|
||||||
*/
|
|
||||||
router.get("/resource(/*)?", ensureAuthenticated, async (req, res) => {
|
|
||||||
const path = getFirstString(req.query.path)
|
|
||||||
if (path) {
|
|
||||||
res.set("Content-Type", getMediaMime(path))
|
|
||||||
res.send(await fs.readFile(pathToFsPath(path)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used by VS Code to load files.
|
|
||||||
*/
|
|
||||||
router.get("/vscode-remote-resource(/*)?", ensureAuthenticated, async (req, res) => {
|
|
||||||
const path = getFirstString(req.query.path)
|
|
||||||
if (path) {
|
|
||||||
res.set("Content-Type", getMediaMime(path))
|
|
||||||
res.send(await fs.readFile(pathToFsPath(path)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* VS Code webviews use these paths to load files and to load webview assets
|
|
||||||
* like HTML and JavaScript.
|
|
||||||
*/
|
|
||||||
router.get("/webview/*", ensureAuthenticated, async (req, res) => {
|
|
||||||
res.set("Content-Type", getMediaMime(req.path))
|
|
||||||
if (/^vscode-resource/.test(req.params[0])) {
|
|
||||||
return res.send(await fs.readFile(req.params[0].replace(/^vscode-resource(\/file)?/, "")))
|
|
||||||
}
|
|
||||||
return res.send(
|
|
||||||
await fs.readFile(path.join(vscode.vsRootPath, "out/vs/workbench/contrib/webview/browser/pre", req.params[0])),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
interface Callback {
|
|
||||||
uri: {
|
|
||||||
scheme: string
|
|
||||||
authority?: string
|
|
||||||
path?: string
|
|
||||||
query?: string
|
|
||||||
fragment?: string
|
|
||||||
}
|
|
||||||
timeout: NodeJS.Timeout
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const callbacks = new Map<string, Callback>()
|
export const createVSServerRouter = async (args: DefaultedArgs): Promise<VSServerResult> => {
|
||||||
const callbackEmitter = new Emitter<{ id: string; callback: Callback }>()
|
// Delete `VSCODE_CWD` very early even before
|
||||||
|
// importing bootstrap files. We have seen
|
||||||
|
// reports where `code .` would use the wrong
|
||||||
|
// current working directory due to our variable
|
||||||
|
// somehow escaping to the parent shell
|
||||||
|
// (https://github.com/microsoft/vscode/issues/126399)
|
||||||
|
delete process.env["VSCODE_CWD"]
|
||||||
|
|
||||||
/**
|
const bootstrap = require(path.join(vsRootPath, "out", "bootstrap"))
|
||||||
* Get vscode-requestId from the query and throw if it's missing or invalid.
|
const bootstrapNode = require(path.join(vsRootPath, "out", "bootstrap-node"))
|
||||||
*/
|
const product = require(path.join(vsRootPath, "product.json"))
|
||||||
const getRequestId = (req: Request): string => {
|
|
||||||
if (!req.query["vscode-requestId"]) {
|
|
||||||
throw new HttpError("vscode-requestId is missing", HttpCode.BadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof req.query["vscode-requestId"] !== "string") {
|
// Avoid Monkey Patches from Application Insights
|
||||||
throw new HttpError("vscode-requestId is not a string", HttpCode.BadRequest)
|
bootstrap.avoidMonkeyPatchFromAppInsights()
|
||||||
}
|
|
||||||
|
|
||||||
return req.query["vscode-requestId"]
|
// Enable portable support
|
||||||
}
|
bootstrapNode.configurePortable(product)
|
||||||
|
|
||||||
// Matches VS Code's fetch timeout.
|
// Enable ASAR support
|
||||||
const fetchTimeout = 5 * 60 * 1000
|
bootstrap.enableASARSupport()
|
||||||
|
|
||||||
// The callback endpoints are used during authentication. A URI is stored on
|
// Signal processes that we got launched as CLI
|
||||||
// /callback and then fetched later on /fetch-callback.
|
process.env["VSCODE_CLI"] = "1"
|
||||||
// See ../../../lib/vscode/resources/web/code-web.js
|
|
||||||
router.get("/callback", ensureAuthenticated, async (req, res) => {
|
|
||||||
const uriKeys = [
|
|
||||||
"vscode-requestId",
|
|
||||||
"vscode-scheme",
|
|
||||||
"vscode-authority",
|
|
||||||
"vscode-path",
|
|
||||||
"vscode-query",
|
|
||||||
"vscode-fragment",
|
|
||||||
]
|
|
||||||
|
|
||||||
const id = getRequestId(req)
|
const vscodeServerMain = await loadAMDModule<CodeServerLib.CreateVSServer>("vs/server/entry", "createVSServer")
|
||||||
|
|
||||||
// Move any query variables that aren't URI keys into the URI's query
|
const serverUrl = new URL(`${args.cert ? "https" : "http"}://${args.host}:${args.port}`)
|
||||||
// (importantly, this will include the code for oauth).
|
const vscodeServer = await vscodeServerMain({
|
||||||
const query: qs.ParsedQs = {}
|
codeServerVersion,
|
||||||
for (const key in req.query) {
|
serverUrl,
|
||||||
if (!uriKeys.includes(key)) {
|
args,
|
||||||
query[key] = req.query[key]
|
authed: args.auth !== AuthType.None,
|
||||||
}
|
disableUpdateCheck: !!args["disable-update-check"],
|
||||||
}
|
|
||||||
|
|
||||||
const callback = {
|
|
||||||
uri: {
|
|
||||||
scheme: getFirstString(req.query["vscode-scheme"]) || "code-oss",
|
|
||||||
authority: getFirstString(req.query["vscode-authority"]),
|
|
||||||
path: getFirstString(req.query["vscode-path"]),
|
|
||||||
query: (getFirstString(req.query.query) || "") + "&" + qs.stringify(query),
|
|
||||||
fragment: getFirstString(req.query["vscode-fragment"]),
|
|
||||||
},
|
|
||||||
// Make sure the map doesn't leak if nothing fetches this URI.
|
|
||||||
timeout: setTimeout(() => callbacks.delete(id), fetchTimeout),
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks.set(id, callback)
|
|
||||||
callbackEmitter.emit({ id, callback })
|
|
||||||
|
|
||||||
res.sendFile(path.join(rootPath, "vendor/modules/code-oss-dev/resources/web/callback.html"))
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get("/fetch-callback", ensureAuthenticated, async (req, res) => {
|
|
||||||
const id = getRequestId(req)
|
|
||||||
|
|
||||||
const send = (callback: Callback) => {
|
|
||||||
clearTimeout(callback.timeout)
|
|
||||||
callbacks.delete(id)
|
|
||||||
res.json(callback.uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
const callback = callbacks.get(id)
|
|
||||||
if (callback) {
|
|
||||||
return send(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VS Code will try again if the route returns no content but it seems more
|
|
||||||
// efficient to just wait on this request for as long as possible?
|
|
||||||
const handler = callbackEmitter.event(({ id: emitId, callback }) => {
|
|
||||||
if (id === emitId) {
|
|
||||||
handler.dispose()
|
|
||||||
send(callback)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// If the client closes the connection.
|
const router = express.Router()
|
||||||
req.on("close", () => handler.dispose())
|
const wsRouter = WsRouter()
|
||||||
})
|
|
||||||
|
|
||||||
export const wsRouter = WsRouter()
|
router.all("*", ensureAuthenticated, (req, res, next) => {
|
||||||
|
req.on("error", (error) => errorHandler(error, req, res, next))
|
||||||
|
|
||||||
wsRouter.ws("/", ensureAuthenticated, async (req) => {
|
vscodeServer.emit("request", req, res)
|
||||||
const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
})
|
||||||
const reply = crypto
|
|
||||||
.createHash("sha1")
|
|
||||||
.update(req.headers["sec-websocket-key"] + magic)
|
|
||||||
.digest("base64")
|
|
||||||
|
|
||||||
const responseHeaders = [
|
wsRouter.ws("/", ensureAuthenticated, (req) => {
|
||||||
"HTTP/1.1 101 Switching Protocols",
|
vscodeServer.emit("upgrade", req, req.socket, req.head)
|
||||||
"Upgrade: websocket",
|
|
||||||
"Connection: Upgrade",
|
|
||||||
`Sec-WebSocket-Accept: ${reply}`,
|
|
||||||
]
|
|
||||||
|
|
||||||
// See if the browser reports it supports web socket compression.
|
req.socket.resume()
|
||||||
// TODO: Parse this header properly.
|
})
|
||||||
const extensions = req.headers["sec-websocket-extensions"]
|
|
||||||
const isCompressionSupported = extensions ? extensions.includes("permessage-deflate") : false
|
|
||||||
|
|
||||||
// TODO: For now we only use compression if the user enables it.
|
return {
|
||||||
const isCompressionEnabled = !!req.args.enable?.includes(Feature.PermessageDeflate)
|
router,
|
||||||
|
wsRouter,
|
||||||
const useCompression = isCompressionEnabled && isCompressionSupported
|
vscodeServer,
|
||||||
if (useCompression) {
|
|
||||||
// This response header tells the browser the server supports compression.
|
|
||||||
responseHeaders.push("Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15")
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
req.ws.write(responseHeaders.join("\r\n") + "\r\n\r\n")
|
|
||||||
|
|
||||||
await vscode.sendWebsocket(req.ws, req.query, useCompression)
|
|
||||||
})
|
|
||||||
|
@ -20,7 +20,7 @@ export class SettingsProvider<T> {
|
|||||||
try {
|
try {
|
||||||
const raw = (await fs.readFile(this.settingsPath, "utf8")).trim()
|
const raw = (await fs.readFile(this.settingsPath, "utf8")).trim()
|
||||||
return raw ? JSON.parse(raw) : {}
|
return raw ? JSON.parse(raw) : {}
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
if (error.code !== "ENOENT") {
|
if (error.code !== "ENOENT") {
|
||||||
logger.warn(error.message)
|
logger.warn(error.message)
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ export class SettingsProvider<T> {
|
|||||||
const oldSettings = await this.read()
|
const oldSettings = await this.read()
|
||||||
const nextSettings = { ...oldSettings, ...settings }
|
const nextSettings = { ...oldSettings, ...settings }
|
||||||
await fs.writeFile(this.settingsPath, JSON.stringify(nextSettings, null, 2))
|
await fs.writeFile(this.settingsPath, JSON.stringify(nextSettings, null, 2))
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.warn(error.message)
|
logger.warn(error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ export class UpdateProvider {
|
|||||||
}
|
}
|
||||||
logger.debug("got latest version", field("latest", update.version))
|
logger.debug("got latest version", field("latest", update.version))
|
||||||
return update
|
return update
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error("Failed to get latest version", field("error", error.message))
|
logger.error("Failed to get latest version", field("error", error.message))
|
||||||
return {
|
return {
|
||||||
checked: now,
|
checked: now,
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
// In a bit of a hack, this file is stored in two places
|
|
||||||
// - src/node/uri_transformer.ts
|
|
||||||
// - lib/vscode/src/vs/server/uriTransformer.ts
|
|
||||||
|
|
||||||
// The reason for this is that we need a CommonJS-compiled
|
|
||||||
// version of this file to supply as a command line argument
|
|
||||||
// to extensionHostProcessSetup.ts; but we also need to include
|
|
||||||
// it ourselves cleanly in `lib/vscode/src/vs/server`.
|
|
||||||
|
|
||||||
// @oxy: Could not figure out how to compile as a CommonJS module
|
|
||||||
// in the same tree as VSCode, which is why I came up with the solution
|
|
||||||
// of storing it in two places.
|
|
||||||
|
|
||||||
// NOTE: copied over from lib/vscode/src/vs/common/uriIpc.ts
|
|
||||||
// remember to update this for proper type checks!
|
|
||||||
|
|
||||||
interface UriParts {
|
|
||||||
scheme: string
|
|
||||||
authority?: string
|
|
||||||
path?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IRawURITransformer {
|
|
||||||
transformIncoming(uri: UriParts): UriParts
|
|
||||||
transformOutgoing(uri: UriParts): UriParts
|
|
||||||
transformOutgoingScheme(scheme: string): string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Using `export =` is deliberate.
|
|
||||||
// See lib/vscode/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts;
|
|
||||||
// they include the file directly with a node require and expect a function as `module.exports`.
|
|
||||||
// `export =` in TypeScript is equivalent to `module.exports =` in vanilla JS.
|
|
||||||
export = function rawURITransformerFactory(authority: string) {
|
|
||||||
return new RawURITransformer(authority)
|
|
||||||
}
|
|
||||||
|
|
||||||
class RawURITransformer implements IRawURITransformer {
|
|
||||||
constructor(private readonly authority: string) {}
|
|
||||||
|
|
||||||
transformIncoming(uri: UriParts): UriParts {
|
|
||||||
switch (uri.scheme) {
|
|
||||||
case "vscode-remote":
|
|
||||||
return { scheme: "file", path: uri.path }
|
|
||||||
default:
|
|
||||||
return uri
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transformOutgoing(uri: UriParts): UriParts {
|
|
||||||
switch (uri.scheme) {
|
|
||||||
case "file":
|
|
||||||
return { scheme: "vscode-remote", authority: this.authority, path: uri.path }
|
|
||||||
default:
|
|
||||||
return uri
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transformOutgoingScheme(scheme: string): string {
|
|
||||||
switch (scheme) {
|
|
||||||
case "file":
|
|
||||||
return "vscode-remote"
|
|
||||||
default:
|
|
||||||
return scheme
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ import * as path from "path"
|
|||||||
import safeCompare from "safe-compare"
|
import safeCompare from "safe-compare"
|
||||||
import * as util from "util"
|
import * as util from "util"
|
||||||
import xdgBasedir from "xdg-basedir"
|
import xdgBasedir from "xdg-basedir"
|
||||||
import { getFirstString } from "../common/util"
|
import { vsRootPath } from "./constants"
|
||||||
|
|
||||||
export interface Paths {
|
export interface Paths {
|
||||||
data: string
|
data: string
|
||||||
@ -157,7 +157,7 @@ export const generatePassword = async (length = 24): Promise<string> => {
|
|||||||
export const hash = async (password: string): Promise<string> => {
|
export const hash = async (password: string): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
return await argon2.hash(password)
|
return await argon2.hash(password)
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -172,7 +172,7 @@ export const isHashMatch = async (password: string, hash: string) => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return await argon2.verify(hash, password)
|
return await argon2.verify(hash, password)
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
throw new Error(error)
|
throw new Error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -439,55 +439,6 @@ export const isObject = <T extends object>(obj: T): obj is T => {
|
|||||||
return !Array.isArray(obj) && typeof obj === "object" && obj !== null
|
return !Array.isArray(obj) && typeof obj === "object" && obj !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Taken from vs/base/common/charCode.ts. Copied for now instead of importing so
|
|
||||||
* we don't have to set up a `vs` alias to be able to import with types (since
|
|
||||||
* the alternative is to directly import from `out`).
|
|
||||||
*/
|
|
||||||
enum CharCode {
|
|
||||||
Slash = 47,
|
|
||||||
A = 65,
|
|
||||||
Z = 90,
|
|
||||||
a = 97,
|
|
||||||
z = 122,
|
|
||||||
Colon = 58,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute `fsPath` for the given uri.
|
|
||||||
* Taken from vs/base/common/uri.ts. It's not imported to avoid also importing
|
|
||||||
* everything that file imports.
|
|
||||||
*/
|
|
||||||
export function pathToFsPath(path: string, keepDriveLetterCasing = false): string {
|
|
||||||
const isWindows = process.platform === "win32"
|
|
||||||
const uri = { authority: undefined, path: getFirstString(path) || "", scheme: "file" }
|
|
||||||
let value: string
|
|
||||||
|
|
||||||
if (uri.authority && uri.path.length > 1 && uri.scheme === "file") {
|
|
||||||
// unc path: file://shares/c$/far/boo
|
|
||||||
value = `//${uri.authority}${uri.path}`
|
|
||||||
} else if (
|
|
||||||
uri.path.charCodeAt(0) === CharCode.Slash &&
|
|
||||||
((uri.path.charCodeAt(1) >= CharCode.A && uri.path.charCodeAt(1) <= CharCode.Z) ||
|
|
||||||
(uri.path.charCodeAt(1) >= CharCode.a && uri.path.charCodeAt(1) <= CharCode.z)) &&
|
|
||||||
uri.path.charCodeAt(2) === CharCode.Colon
|
|
||||||
) {
|
|
||||||
if (!keepDriveLetterCasing) {
|
|
||||||
// windows drive letter: file:///c:/far/boo
|
|
||||||
value = uri.path[1].toLowerCase() + uri.path.substr(2)
|
|
||||||
} else {
|
|
||||||
value = uri.path.substr(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// other path
|
|
||||||
value = uri.path
|
|
||||||
}
|
|
||||||
if (isWindows) {
|
|
||||||
value = value.replace(/\//g, "\\")
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a promise that resolves with whether the socket path is active.
|
* Return a promise that resolves with whether the socket path is active.
|
||||||
*/
|
*/
|
||||||
@ -533,3 +484,23 @@ export function escapeHtml(unsafe: string): string {
|
|||||||
export function isNodeJSErrnoException(error: unknown): error is NodeJS.ErrnoException {
|
export function isNodeJSErrnoException(error: unknown): error is NodeJS.ErrnoException {
|
||||||
return error instanceof Error && (error as NodeJS.ErrnoException).code !== undefined
|
return error instanceof Error && (error as NodeJS.ErrnoException).code !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Replace with proper templating system.
|
||||||
|
export const escapeJSON = (value: cp.Serializable) => JSON.stringify(value).replace(/"/g, """)
|
||||||
|
|
||||||
|
type AMDModule<T> = { [exportName: string]: T }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads AMD module, typically from a compiled VSCode bundle.
|
||||||
|
*
|
||||||
|
* @deprecated This should be gradually phased out as code-server migrates to lib/vscode
|
||||||
|
* @param amdPath Path to module relative to lib/vscode
|
||||||
|
* @param exportName Given name of export in the file
|
||||||
|
*/
|
||||||
|
export const loadAMDModule = async <T>(amdPath: string, exportName: string): Promise<T> => {
|
||||||
|
const module = await new Promise<AMDModule<T>>((resolve, reject) => {
|
||||||
|
require(path.join(vsRootPath, "out/bootstrap-amd")).load(amdPath, resolve, reject)
|
||||||
|
})
|
||||||
|
|
||||||
|
return module[exportName] as T
|
||||||
|
}
|
||||||
|
@ -1,168 +0,0 @@
|
|||||||
import { logger } from "@coder/logger"
|
|
||||||
import * as cp from "child_process"
|
|
||||||
import * as net from "net"
|
|
||||||
import * as path from "path"
|
|
||||||
import * as ipc from "../../typings/ipc"
|
|
||||||
import { arrayify, generateUuid } from "../common/util"
|
|
||||||
import { rootPath } from "./constants"
|
|
||||||
import { settings } from "./settings"
|
|
||||||
import { SocketProxyProvider } from "./socket"
|
|
||||||
import { isFile } from "./util"
|
|
||||||
import { onMessage, wrapper } from "./wrapper"
|
|
||||||
|
|
||||||
export class VscodeProvider {
|
|
||||||
public readonly serverRootPath: string
|
|
||||||
public readonly vsRootPath: string
|
|
||||||
private _vscode?: Promise<cp.ChildProcess>
|
|
||||||
private readonly socketProvider = new SocketProxyProvider()
|
|
||||||
|
|
||||||
public constructor() {
|
|
||||||
this.vsRootPath = path.resolve(rootPath, "vendor/modules/code-oss-dev")
|
|
||||||
this.serverRootPath = path.join(this.vsRootPath, "out/vs/server")
|
|
||||||
wrapper.onDispose(() => this.dispose())
|
|
||||||
}
|
|
||||||
|
|
||||||
public async dispose(): Promise<void> {
|
|
||||||
this.socketProvider.stop()
|
|
||||||
if (this._vscode) {
|
|
||||||
const vscode = await this._vscode
|
|
||||||
vscode.removeAllListeners()
|
|
||||||
vscode.kill()
|
|
||||||
this._vscode = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async initialize(
|
|
||||||
options: Omit<ipc.VscodeOptions, "startPath">,
|
|
||||||
query: ipc.Query,
|
|
||||||
): Promise<ipc.WorkbenchOptions> {
|
|
||||||
const { lastVisited } = await settings.read()
|
|
||||||
let startPath = await this.getFirstPath([
|
|
||||||
{ url: query.workspace, workspace: true },
|
|
||||||
{ url: query.folder, workspace: false },
|
|
||||||
options.args._ && options.args._.length > 0
|
|
||||||
? { url: path.resolve(options.args._[options.args._.length - 1]) }
|
|
||||||
: undefined,
|
|
||||||
!options.args["ignore-last-opened"] ? lastVisited : undefined,
|
|
||||||
])
|
|
||||||
|
|
||||||
if (query.ew) {
|
|
||||||
startPath = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.write({
|
|
||||||
lastVisited: startPath,
|
|
||||||
query,
|
|
||||||
})
|
|
||||||
|
|
||||||
const id = generateUuid()
|
|
||||||
const vscode = await this.fork()
|
|
||||||
|
|
||||||
logger.debug("setting up vs code...")
|
|
||||||
|
|
||||||
this.send(
|
|
||||||
{
|
|
||||||
type: "init",
|
|
||||||
id,
|
|
||||||
options: {
|
|
||||||
...options,
|
|
||||||
startPath,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
vscode,
|
|
||||||
)
|
|
||||||
|
|
||||||
const message = await onMessage<ipc.VscodeMessage, ipc.OptionsMessage>(
|
|
||||||
vscode,
|
|
||||||
(message): message is ipc.OptionsMessage => {
|
|
||||||
// There can be parallel initializations so wait for the right ID.
|
|
||||||
return message.type === "options" && message.id === id
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return message.options
|
|
||||||
}
|
|
||||||
|
|
||||||
private fork(): Promise<cp.ChildProcess> {
|
|
||||||
if (this._vscode) {
|
|
||||||
return this._vscode
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("forking vs code...")
|
|
||||||
const vscode = cp.fork(path.join(this.serverRootPath, "fork"))
|
|
||||||
|
|
||||||
const dispose = () => {
|
|
||||||
vscode.removeAllListeners()
|
|
||||||
vscode.kill()
|
|
||||||
this._vscode = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
vscode.on("error", (error: Error) => {
|
|
||||||
logger.error(error.message)
|
|
||||||
if (error.stack) {
|
|
||||||
logger.debug(error.stack)
|
|
||||||
}
|
|
||||||
dispose()
|
|
||||||
})
|
|
||||||
|
|
||||||
vscode.on("exit", (code) => {
|
|
||||||
logger.error(`VS Code exited unexpectedly with code ${code}`)
|
|
||||||
dispose()
|
|
||||||
})
|
|
||||||
|
|
||||||
this._vscode = onMessage<ipc.VscodeMessage, ipc.ReadyMessage>(vscode, (message): message is ipc.ReadyMessage => {
|
|
||||||
return message.type === "ready"
|
|
||||||
}).then(() => vscode)
|
|
||||||
|
|
||||||
return this._vscode
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* VS Code expects a raw socket. It will handle all the web socket frames.
|
|
||||||
*/
|
|
||||||
public async sendWebsocket(socket: net.Socket, query: ipc.Query, permessageDeflate: boolean): Promise<void> {
|
|
||||||
const vscode = await this._vscode
|
|
||||||
// TLS sockets cannot be transferred to child processes so we need an
|
|
||||||
// in-between. Non-TLS sockets will be returned as-is.
|
|
||||||
const socketProxy = await this.socketProvider.createProxy(socket)
|
|
||||||
this.send({ type: "socket", query, permessageDeflate }, vscode, socketProxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
private send(message: ipc.CodeServerMessage, vscode?: cp.ChildProcess, socket?: net.Socket): void {
|
|
||||||
if (!vscode || vscode.killed) {
|
|
||||||
throw new Error("vscode is not running")
|
|
||||||
}
|
|
||||||
vscode.send(message, socket)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Choose the first non-empty path from the provided array.
|
|
||||||
*
|
|
||||||
* Each array item consists of `url` and an optional `workspace` boolean that
|
|
||||||
* indicates whether that url is for a workspace.
|
|
||||||
*
|
|
||||||
* `url` can be a fully qualified URL or just the path portion.
|
|
||||||
*
|
|
||||||
* `url` can also be a query object to make it easier to pass in query
|
|
||||||
* variables directly but anything that isn't a string or string array is not
|
|
||||||
* valid and will be ignored.
|
|
||||||
*/
|
|
||||||
private async getFirstPath(
|
|
||||||
startPaths: Array<{ url?: string | string[] | ipc.Query | ipc.Query[]; workspace?: boolean } | undefined>,
|
|
||||||
): Promise<ipc.StartPath | undefined> {
|
|
||||||
for (let i = 0; i < startPaths.length; ++i) {
|
|
||||||
const startPath = startPaths[i]
|
|
||||||
const url = arrayify(startPath && startPath.url).find((p) => !!p)
|
|
||||||
if (startPath && url && typeof url === "string") {
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
// The only time `workspace` is undefined is for the command-line
|
|
||||||
// argument, in which case it's a path (not a URL) so we can stat it
|
|
||||||
// without having to parse it.
|
|
||||||
workspace: typeof startPath.workspace !== "undefined" ? startPath.workspace : await isFile(url),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
@ -267,7 +267,7 @@ export class ParentProcess extends Process {
|
|||||||
try {
|
try {
|
||||||
this.started = this._start()
|
this.started = this._start()
|
||||||
await this.started
|
await this.started
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
this.logger.error(error.message)
|
this.logger.error(error.message)
|
||||||
this.exit(typeof error.code === "number" ? error.code : 1)
|
this.exit(typeof error.code === "number" ? error.code : 1)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.12.1",
|
"@playwright/test": "^1.12.1",
|
||||||
"@types/jest": "^26.0.20",
|
"@types/jest": "^26.0.20",
|
||||||
"@types/jsdom": "^16.2.6",
|
"@types/jsdom": "^16.2.13",
|
||||||
"@types/node-fetch": "^2.5.8",
|
"@types/node-fetch": "^2.5.8",
|
||||||
"@types/supertest": "^2.0.10",
|
"@types/supertest": "^2.0.10",
|
||||||
"@types/wtfnode": "^0.7.0",
|
"@types/wtfnode": "^0.7.0",
|
||||||
|
@ -24,10 +24,8 @@ describe("login", () => {
|
|||||||
const spy = jest.spyOn(document, "getElementById")
|
const spy = jest.spyOn(document, "getElementById")
|
||||||
// Create a fake element and set the attribute
|
// Create a fake element and set the attribute
|
||||||
const mockElement = document.createElement("input")
|
const mockElement = document.createElement("input")
|
||||||
mockElement.setAttribute("id", "base")
|
|
||||||
const expected = {
|
const expected = {
|
||||||
base: "./hello-world",
|
base: "./hello-world",
|
||||||
csStaticBase: "./static/development/Users/jp/Dev/code-server",
|
|
||||||
logLevel: 2,
|
logLevel: 2,
|
||||||
disableTelemetry: false,
|
disableTelemetry: false,
|
||||||
disableUpdateCheck: false,
|
disableUpdateCheck: false,
|
||||||
@ -35,11 +33,6 @@ describe("login", () => {
|
|||||||
mockElement.setAttribute("data-settings", JSON.stringify(expected))
|
mockElement.setAttribute("data-settings", JSON.stringify(expected))
|
||||||
document.body.appendChild(mockElement)
|
document.body.appendChild(mockElement)
|
||||||
spy.mockImplementation(() => mockElement)
|
spy.mockImplementation(() => mockElement)
|
||||||
// Load file
|
|
||||||
require("../../../../src/browser/pages/login")
|
|
||||||
|
|
||||||
const el: HTMLInputElement | null = document.querySelector("input#base")
|
|
||||||
expect(el?.value).toBe("/hello-world")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe("there is not an element with id 'base'", () => {
|
describe("there is not an element with id 'base'", () => {
|
||||||
@ -76,15 +69,5 @@ describe("login", () => {
|
|||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
jest.restoreAllMocks()
|
jest.restoreAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should do nothing", () => {
|
|
||||||
spy.mockImplementation(() => null)
|
|
||||||
// Load file
|
|
||||||
require("../../../../src/browser/pages/login")
|
|
||||||
|
|
||||||
// It's called once by getOptions in the top of the file
|
|
||||||
// and then another to get the base element
|
|
||||||
expect(spy).toHaveBeenCalledTimes(2)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,400 +0,0 @@
|
|||||||
/**
|
|
||||||
* @jest-environment jsdom
|
|
||||||
*/
|
|
||||||
import fetchMock from "jest-fetch-mock"
|
|
||||||
import { JSDOM } from "jsdom"
|
|
||||||
import {
|
|
||||||
getNlsConfiguration,
|
|
||||||
nlsConfigElementId,
|
|
||||||
getConfigurationForLoader,
|
|
||||||
setBodyBackgroundToThemeBackgroundColor,
|
|
||||||
_createScriptURL,
|
|
||||||
main,
|
|
||||||
createBundlePath,
|
|
||||||
} from "../../../../src/browser/pages/vscode"
|
|
||||||
|
|
||||||
describe("vscode", () => {
|
|
||||||
describe("getNlsConfiguration", () => {
|
|
||||||
let _document: Document
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// We use underscores to not confuse with global values
|
|
||||||
const { window: _window } = new JSDOM()
|
|
||||||
_document = _window.document
|
|
||||||
fetchMock.enableMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
fetchMock.resetMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw an error if no nlsConfigElement", () => {
|
|
||||||
const errorMsgPrefix = "[vscode]"
|
|
||||||
const errorMessage = `${errorMsgPrefix} Could not parse NLS configuration. Could not find nlsConfigElement with id: ${nlsConfigElementId}`
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
getNlsConfiguration(_document, "")
|
|
||||||
}).toThrowError(errorMessage)
|
|
||||||
})
|
|
||||||
it("should throw an error if no nlsConfig", () => {
|
|
||||||
const mockElement = _document.createElement("div")
|
|
||||||
mockElement.setAttribute("id", nlsConfigElementId)
|
|
||||||
_document.body.appendChild(mockElement)
|
|
||||||
|
|
||||||
const errorMsgPrefix = "[vscode]"
|
|
||||||
const errorMessage = `${errorMsgPrefix} Could not parse NLS configuration. Found nlsConfigElement but missing data-settings attribute.`
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
getNlsConfiguration(_document, "")
|
|
||||||
}).toThrowError(errorMessage)
|
|
||||||
|
|
||||||
_document.body.removeChild(mockElement)
|
|
||||||
})
|
|
||||||
it("should return the correct configuration", () => {
|
|
||||||
const mockElement = _document.createElement("div")
|
|
||||||
const dataSettings = {
|
|
||||||
first: "Jane",
|
|
||||||
last: "Doe",
|
|
||||||
}
|
|
||||||
|
|
||||||
mockElement.setAttribute("id", nlsConfigElementId)
|
|
||||||
mockElement.setAttribute("data-settings", JSON.stringify(dataSettings))
|
|
||||||
_document.body.appendChild(mockElement)
|
|
||||||
const actual = getNlsConfiguration(_document, "")
|
|
||||||
|
|
||||||
expect(actual).toStrictEqual(dataSettings)
|
|
||||||
|
|
||||||
_document.body.removeChild(mockElement)
|
|
||||||
})
|
|
||||||
it("should return and have a loadBundle property if _resolvedLangaugePackCoreLocation", async () => {
|
|
||||||
const mockElement = _document.createElement("div")
|
|
||||||
const dataSettings = {
|
|
||||||
locale: "en",
|
|
||||||
availableLanguages: ["en", "de"],
|
|
||||||
_resolvedLanguagePackCoreLocation: "./",
|
|
||||||
}
|
|
||||||
|
|
||||||
mockElement.setAttribute("id", nlsConfigElementId)
|
|
||||||
mockElement.setAttribute("data-settings", JSON.stringify(dataSettings))
|
|
||||||
_document.body.appendChild(mockElement)
|
|
||||||
const nlsConfig = getNlsConfiguration(_document, "")
|
|
||||||
|
|
||||||
expect(nlsConfig._resolvedLanguagePackCoreLocation).not.toBe(undefined)
|
|
||||||
expect(nlsConfig.loadBundle).not.toBe(undefined)
|
|
||||||
|
|
||||||
const mockCallbackFn = jest.fn((_, bundle) => {
|
|
||||||
return bundle
|
|
||||||
})
|
|
||||||
|
|
||||||
fetchMock.mockOnce(JSON.stringify({ key: "hello world" }))
|
|
||||||
// Ensure that load bundle works as expected
|
|
||||||
// by mocking the fetch response and checking that the callback
|
|
||||||
// had the expected value
|
|
||||||
await nlsConfig.loadBundle("hello", "en", mockCallbackFn)
|
|
||||||
expect(mockCallbackFn).toHaveBeenCalledTimes(1)
|
|
||||||
expect(mockCallbackFn).toHaveBeenCalledWith(undefined, { key: "hello world" })
|
|
||||||
|
|
||||||
// Call it again to ensure it loads from the cache
|
|
||||||
// it should return the same value
|
|
||||||
await nlsConfig.loadBundle("hello", "en", mockCallbackFn)
|
|
||||||
expect(mockCallbackFn).toHaveBeenCalledTimes(2)
|
|
||||||
expect(mockCallbackFn).toHaveBeenCalledWith(undefined, { key: "hello world" })
|
|
||||||
|
|
||||||
fetchMock.mockReject(new Error("fake error message"))
|
|
||||||
const mockCallbackFn2 = jest.fn((error) => error)
|
|
||||||
// Call it for a different bundle and mock a failed fetch call
|
|
||||||
// to ensure we get the expected error
|
|
||||||
const error = await nlsConfig.loadBundle("goodbye", "es", mockCallbackFn2)
|
|
||||||
expect(error.message).toEqual("fake error message")
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
_document.body.removeChild(mockElement)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe("createBundlePath", () => {
|
|
||||||
it("should return the correct path", () => {
|
|
||||||
const _resolvedLangaugePackCoreLocation = "./languages"
|
|
||||||
const bundle = "/bundle.js"
|
|
||||||
const expected = "./languages/!bundle.js.nls.json"
|
|
||||||
const actual = createBundlePath(_resolvedLangaugePackCoreLocation, bundle)
|
|
||||||
expect(actual).toBe(expected)
|
|
||||||
})
|
|
||||||
it("should return the correct path (even if _resolvedLangaugePackCoreLocation is undefined)", () => {
|
|
||||||
const _resolvedLangaugePackCoreLocation = undefined
|
|
||||||
const bundle = "/bundle.js"
|
|
||||||
const expected = "/!bundle.js.nls.json"
|
|
||||||
const actual = createBundlePath(_resolvedLangaugePackCoreLocation, bundle)
|
|
||||||
expect(actual).toBe(expected)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe("setBodyBackgroundToThemeBackgroundColor", () => {
|
|
||||||
let _document: Document
|
|
||||||
let _localStorage: Storage
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// We need to set the url in the JSDOM constructor
|
|
||||||
// to prevent this error "SecurityError: localStorage is not available for opaque origins"
|
|
||||||
// See: https://github.com/jsdom/jsdom/issues/2304#issuecomment-622314949
|
|
||||||
const { window: _window } = new JSDOM("", { url: "http://localhost" })
|
|
||||||
_document = _window.document
|
|
||||||
_localStorage = _window.localStorage
|
|
||||||
})
|
|
||||||
it("should return null", () => {
|
|
||||||
const test = {
|
|
||||||
colorMap: {
|
|
||||||
[`editor.background`]: "#ff3270",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_localStorage.setItem("colorThemeData", JSON.stringify(test))
|
|
||||||
|
|
||||||
expect(setBodyBackgroundToThemeBackgroundColor(_document, _localStorage)).toBeNull()
|
|
||||||
|
|
||||||
_localStorage.removeItem("colorThemeData")
|
|
||||||
})
|
|
||||||
it("should throw an error if it can't find colorThemeData in localStorage", () => {
|
|
||||||
const errorMsgPrefix = "[vscode]"
|
|
||||||
const errorMessage = `${errorMsgPrefix} Could not set body background to theme background color. Could not find colorThemeData in localStorage.`
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
setBodyBackgroundToThemeBackgroundColor(_document, _localStorage)
|
|
||||||
}).toThrowError(errorMessage)
|
|
||||||
})
|
|
||||||
it("should throw an error if there is an error parsing colorThemeData from localStorage", () => {
|
|
||||||
const errorMsgPrefix = "[vscode]"
|
|
||||||
const errorMessage = `${errorMsgPrefix} Could not set body background to theme background color. Could not parse colorThemeData from localStorage.`
|
|
||||||
|
|
||||||
_localStorage.setItem(
|
|
||||||
"colorThemeData",
|
|
||||||
'{"id":"vs-dark max-SS-Cyberpunk-themes-cyberpunk-umbra-color-theme-json","label":"Activate UMBRA protocol","settingsId":"Activate "errorForeground":"#ff3270","foreground":"#ffffff","sideBarTitle.foreground":"#bbbbbb"},"watch\\":::false}',
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
setBodyBackgroundToThemeBackgroundColor(_document, _localStorage)
|
|
||||||
}).toThrowError(errorMessage)
|
|
||||||
|
|
||||||
localStorage.removeItem("colorThemeData")
|
|
||||||
})
|
|
||||||
it("should throw an error if there is no colorMap property", () => {
|
|
||||||
const errorMsgPrefix = "[vscode]"
|
|
||||||
const errorMessage = `${errorMsgPrefix} Could not set body background to theme background color. colorThemeData is missing colorMap.`
|
|
||||||
|
|
||||||
const test = {
|
|
||||||
id: "hey-joe",
|
|
||||||
}
|
|
||||||
_localStorage.setItem("colorThemeData", JSON.stringify(test))
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
setBodyBackgroundToThemeBackgroundColor(_document, _localStorage)
|
|
||||||
}).toThrowError(errorMessage)
|
|
||||||
|
|
||||||
_localStorage.removeItem("colorThemeData")
|
|
||||||
})
|
|
||||||
it("should throw an error if there is no editor.background color", () => {
|
|
||||||
const errorMsgPrefix = "[vscode]"
|
|
||||||
const errorMessage = `${errorMsgPrefix} Could not set body background to theme background color. colorThemeData.colorMap["editor.background"] is undefined.`
|
|
||||||
|
|
||||||
const test = {
|
|
||||||
id: "hey-joe",
|
|
||||||
colorMap: {
|
|
||||||
editor: "#fff",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_localStorage.setItem("colorThemeData", JSON.stringify(test))
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
setBodyBackgroundToThemeBackgroundColor(_document, _localStorage)
|
|
||||||
}).toThrowError(errorMessage)
|
|
||||||
|
|
||||||
_localStorage.removeItem("colorThemeData")
|
|
||||||
})
|
|
||||||
it("should set the body background to the editor background color", () => {
|
|
||||||
const test = {
|
|
||||||
colorMap: {
|
|
||||||
[`editor.background`]: "#ff3270",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_localStorage.setItem("colorThemeData", JSON.stringify(test))
|
|
||||||
|
|
||||||
setBodyBackgroundToThemeBackgroundColor(_document, _localStorage)
|
|
||||||
|
|
||||||
// When the body.style.backgroundColor is set using hex
|
|
||||||
// it is converted to rgb
|
|
||||||
// which is why we use that in the assertion
|
|
||||||
expect(_document.body.style.backgroundColor).toBe("rgb(255, 50, 112)")
|
|
||||||
|
|
||||||
_localStorage.removeItem("colorThemeData")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe("getConfigurationForLoader", () => {
|
|
||||||
let _window: Window
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const { window: __window } = new JSDOM()
|
|
||||||
// @ts-expect-error the Window from JSDOM is not exactly the same as Window
|
|
||||||
// so we expect an error here
|
|
||||||
_window = __window
|
|
||||||
})
|
|
||||||
it("should return a loader object (with undefined trustedTypesPolicy)", () => {
|
|
||||||
const options = {
|
|
||||||
base: ".",
|
|
||||||
csStaticBase: "/",
|
|
||||||
logLevel: 1,
|
|
||||||
}
|
|
||||||
const nlsConfig = {
|
|
||||||
first: "Jane",
|
|
||||||
last: "Doe",
|
|
||||||
locale: "en",
|
|
||||||
availableLanguages: {},
|
|
||||||
}
|
|
||||||
const loader = getConfigurationForLoader({
|
|
||||||
options,
|
|
||||||
_window,
|
|
||||||
nlsConfig: nlsConfig,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(loader).toStrictEqual({
|
|
||||||
baseUrl: "http://localhost//vendor/modules/code-oss-dev/out",
|
|
||||||
paths: {
|
|
||||||
"iconv-lite-umd": "../node_modules/iconv-lite-umd/lib/iconv-lite-umd.js",
|
|
||||||
jschardet: "../node_modules/jschardet/dist/jschardet.min.js",
|
|
||||||
"tas-client-umd": "../node_modules/tas-client-umd/lib/tas-client-umd.js",
|
|
||||||
"vscode-oniguruma": "../node_modules/vscode-oniguruma/release/main",
|
|
||||||
"vscode-textmate": "../node_modules/vscode-textmate/release/main",
|
|
||||||
xterm: "../node_modules/xterm/lib/xterm.js",
|
|
||||||
"xterm-addon-search": "../node_modules/xterm-addon-search/lib/xterm-addon-search.js",
|
|
||||||
"xterm-addon-unicode11": "../node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js",
|
|
||||||
"xterm-addon-webgl": "../node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js",
|
|
||||||
},
|
|
||||||
recordStats: true,
|
|
||||||
|
|
||||||
trustedTypesPolicy: undefined,
|
|
||||||
"vs/nls": {
|
|
||||||
availableLanguages: {},
|
|
||||||
first: "Jane",
|
|
||||||
last: "Doe",
|
|
||||||
locale: "en",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it("should return a loader object with trustedTypesPolicy", () => {
|
|
||||||
interface PolicyOptions {
|
|
||||||
createScriptUrl: (url: string) => string
|
|
||||||
}
|
|
||||||
|
|
||||||
function mockCreatePolicy(policyName: string, options: PolicyOptions) {
|
|
||||||
return {
|
|
||||||
name: policyName,
|
|
||||||
...options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockFn = jest.fn(mockCreatePolicy)
|
|
||||||
|
|
||||||
// @ts-expect-error we are adding a custom property to window
|
|
||||||
_window.trustedTypes = {
|
|
||||||
createPolicy: mockFn,
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
base: "/",
|
|
||||||
csStaticBase: "/",
|
|
||||||
logLevel: 1,
|
|
||||||
}
|
|
||||||
const nlsConfig = {
|
|
||||||
first: "Jane",
|
|
||||||
last: "Doe",
|
|
||||||
locale: "en",
|
|
||||||
availableLanguages: {},
|
|
||||||
}
|
|
||||||
const loader = getConfigurationForLoader({
|
|
||||||
options,
|
|
||||||
_window,
|
|
||||||
nlsConfig: nlsConfig,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(loader.trustedTypesPolicy).not.toBe(undefined)
|
|
||||||
expect(loader.trustedTypesPolicy.name).toBe("amdLoader")
|
|
||||||
|
|
||||||
// Check that we can actually create a script URL
|
|
||||||
// using the createScriptURL on the loader object
|
|
||||||
const scriptUrl = loader.trustedTypesPolicy.createScriptURL("http://localhost/foo.js")
|
|
||||||
expect(scriptUrl).toBe("http://localhost/foo.js")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe("_createScriptURL", () => {
|
|
||||||
it("should return the correct url", () => {
|
|
||||||
const url = _createScriptURL("localhost/foo/bar.js", "localhost")
|
|
||||||
|
|
||||||
expect(url).toBe("localhost/foo/bar.js")
|
|
||||||
})
|
|
||||||
it("should throw if the value doesn't start with the origin", () => {
|
|
||||||
expect(() => {
|
|
||||||
_createScriptURL("localhost/foo/bar.js", "coder.com")
|
|
||||||
}).toThrow("Invalid script url: localhost/foo/bar.js")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe("main", () => {
|
|
||||||
let _window: Window
|
|
||||||
let _document: Document
|
|
||||||
let _localStorage: Storage
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// We need to set the url in the JSDOM constructor
|
|
||||||
// to prevent this error "SecurityError: localStorage is not available for opaque origins"
|
|
||||||
// See: https://github.com/jsdom/jsdom/issues/2304#issuecomment-62231494
|
|
||||||
const { window: __window } = new JSDOM("", { url: "http://localhost" })
|
|
||||||
// @ts-expect-error the Window from JSDOM is not exactly the same as Window
|
|
||||||
// so we expect an error here
|
|
||||||
_window = __window
|
|
||||||
_document = __window.document
|
|
||||||
_localStorage = __window.localStorage
|
|
||||||
|
|
||||||
const mockElement = _document.createElement("div")
|
|
||||||
const dataSettings = {
|
|
||||||
first: "Jane",
|
|
||||||
last: "Doe",
|
|
||||||
}
|
|
||||||
|
|
||||||
mockElement.setAttribute("id", nlsConfigElementId)
|
|
||||||
mockElement.setAttribute("data-settings", JSON.stringify(dataSettings))
|
|
||||||
_document.body.appendChild(mockElement)
|
|
||||||
|
|
||||||
const test = {
|
|
||||||
colorMap: {
|
|
||||||
[`editor.background`]: "#ff3270",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_localStorage.setItem("colorThemeData", JSON.stringify(test))
|
|
||||||
})
|
|
||||||
afterEach(() => {
|
|
||||||
_localStorage.removeItem("colorThemeData")
|
|
||||||
})
|
|
||||||
it("should throw if document is missing", () => {
|
|
||||||
expect(() => {
|
|
||||||
main(undefined, _window, _localStorage)
|
|
||||||
}).toThrow("document is undefined.")
|
|
||||||
})
|
|
||||||
it("should throw if window is missing", () => {
|
|
||||||
expect(() => {
|
|
||||||
main(_document, undefined, _localStorage)
|
|
||||||
}).toThrow("window is undefined.")
|
|
||||||
})
|
|
||||||
it("should throw if localStorage is missing", () => {
|
|
||||||
expect(() => {
|
|
||||||
main(_document, _window, undefined)
|
|
||||||
}).toThrow("localStorage is undefined.")
|
|
||||||
})
|
|
||||||
it("should add loader to self.require", () => {
|
|
||||||
main(_document, _window, _localStorage)
|
|
||||||
|
|
||||||
expect(Object.prototype.hasOwnProperty.call(self, "require")).toBe(true)
|
|
||||||
})
|
|
||||||
it("should not throw in browser context", () => {
|
|
||||||
// Assuming we call it in a normal browser context
|
|
||||||
// where everything is defined
|
|
||||||
expect(() => {
|
|
||||||
main(_document, _window, _localStorage)
|
|
||||||
}).not.toThrow()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,183 +0,0 @@
|
|||||||
import { JSDOM } from "jsdom"
|
|
||||||
import { registerServiceWorker } from "../../../src/browser/register"
|
|
||||||
import { createLoggerMock } from "../../utils/helpers"
|
|
||||||
import { LocationLike } from "../common/util.test"
|
|
||||||
|
|
||||||
describe("register", () => {
|
|
||||||
describe("when navigator and serviceWorker are defined", () => {
|
|
||||||
const mockRegisterFn = jest.fn()
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
const { window } = new JSDOM()
|
|
||||||
global.window = window as unknown as Window & typeof globalThis
|
|
||||||
global.document = window.document
|
|
||||||
global.navigator = window.navigator
|
|
||||||
global.location = window.location
|
|
||||||
|
|
||||||
Object.defineProperty(global.navigator, "serviceWorker", {
|
|
||||||
value: {
|
|
||||||
register: mockRegisterFn,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const loggerModule = createLoggerMock()
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
jest.mock("@coder/logger", () => loggerModule)
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.resetModules()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
jest.restoreAllMocks()
|
|
||||||
|
|
||||||
// We don't want these to stay around because it can affect other tests
|
|
||||||
global.window = undefined as unknown as Window & typeof globalThis
|
|
||||||
global.document = undefined as unknown as Document & typeof globalThis
|
|
||||||
global.navigator = undefined as unknown as Navigator & typeof globalThis
|
|
||||||
global.location = undefined as unknown as Location & typeof globalThis
|
|
||||||
})
|
|
||||||
|
|
||||||
it("test should have access to browser globals from beforeAll", () => {
|
|
||||||
expect(typeof global.window).not.toBeFalsy()
|
|
||||||
expect(typeof global.document).not.toBeFalsy()
|
|
||||||
expect(typeof global.navigator).not.toBeFalsy()
|
|
||||||
expect(typeof global.location).not.toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should register a ServiceWorker", () => {
|
|
||||||
// Load service worker like you would in the browser
|
|
||||||
require("../../../src/browser/register")
|
|
||||||
expect(mockRegisterFn).toHaveBeenCalled()
|
|
||||||
expect(mockRegisterFn).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should log an error if something doesn't work", () => {
|
|
||||||
const message = "Can't find browser"
|
|
||||||
const error = new Error(message)
|
|
||||||
|
|
||||||
mockRegisterFn.mockImplementation(() => {
|
|
||||||
throw error
|
|
||||||
})
|
|
||||||
|
|
||||||
// Load service worker like you would in the browser
|
|
||||||
require("../../../src/browser/register")
|
|
||||||
|
|
||||||
expect(mockRegisterFn).toHaveBeenCalled()
|
|
||||||
expect(loggerModule.logger.error).toHaveBeenCalled()
|
|
||||||
expect(loggerModule.logger.error).toHaveBeenCalledTimes(1)
|
|
||||||
expect(loggerModule.logger.error).toHaveBeenCalledWith(
|
|
||||||
`[Service Worker] registration: ${error.message} ${error.stack}`,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("when navigator and serviceWorker are NOT defined", () => {
|
|
||||||
const loggerModule = createLoggerMock()
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
jest.mock("@coder/logger", () => loggerModule)
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
jest.restoreAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should log an error", () => {
|
|
||||||
// Load service worker like you would in the browser
|
|
||||||
require("../../../src/browser/register")
|
|
||||||
expect(loggerModule.logger.error).toHaveBeenCalled()
|
|
||||||
expect(loggerModule.logger.error).toHaveBeenCalledTimes(1)
|
|
||||||
expect(loggerModule.logger.error).toHaveBeenCalledWith("[Service Worker] navigator is undefined")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("registerServiceWorker", () => {
|
|
||||||
let serviceWorkerPath: string
|
|
||||||
let serviceWorkerScope: string
|
|
||||||
const mockFn = jest.fn((path: string, options: { scope: string }) => {
|
|
||||||
serviceWorkerPath = path
|
|
||||||
serviceWorkerScope = options.scope
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
const location: LocationLike = {
|
|
||||||
pathname: "",
|
|
||||||
origin: "http://localhost:8080",
|
|
||||||
}
|
|
||||||
const { window } = new JSDOM()
|
|
||||||
global.window = window as unknown as Window & typeof globalThis
|
|
||||||
global.document = window.document
|
|
||||||
global.navigator = window.navigator
|
|
||||||
global.location = location as Location
|
|
||||||
|
|
||||||
Object.defineProperty(global.navigator, "serviceWorker", {
|
|
||||||
value: {
|
|
||||||
register: mockFn,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mockFn.mockClear()
|
|
||||||
jest.resetModules()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
jest.restoreAllMocks()
|
|
||||||
|
|
||||||
// We don't want these to stay around because it can affect other tests
|
|
||||||
global.window = undefined as unknown as Window & typeof globalThis
|
|
||||||
global.document = undefined as unknown as Document & typeof globalThis
|
|
||||||
global.navigator = undefined as unknown as Navigator & typeof globalThis
|
|
||||||
global.location = undefined as unknown as Location & typeof globalThis
|
|
||||||
})
|
|
||||||
it("should register when options.base is undefined", async () => {
|
|
||||||
// Mock getElementById
|
|
||||||
const csStaticBasePath = "/static/development/Users/jp/Dev/code-server"
|
|
||||||
const spy = jest.spyOn(document, "getElementById")
|
|
||||||
// Create a fake element and set the attribute
|
|
||||||
const mockElement = document.createElement("div")
|
|
||||||
mockElement.id = "coder-options"
|
|
||||||
mockElement.setAttribute(
|
|
||||||
"data-settings",
|
|
||||||
`{"csStaticBase":"${csStaticBasePath}","logLevel":2,"disableUpdateCheck":false}`,
|
|
||||||
)
|
|
||||||
// Return mockElement from the spy
|
|
||||||
// this way, when we call "getElementById"
|
|
||||||
// it returns the element
|
|
||||||
spy.mockImplementation(() => mockElement)
|
|
||||||
|
|
||||||
await registerServiceWorker()
|
|
||||||
|
|
||||||
expect(mockFn).toBeCalled()
|
|
||||||
expect(serviceWorkerPath).toMatch(`${csStaticBasePath}/out/browser/serviceWorker.js`)
|
|
||||||
expect(serviceWorkerScope).toMatch("/")
|
|
||||||
})
|
|
||||||
it("should register when options.base is defined", async () => {
|
|
||||||
const csStaticBasePath = "/static/development/Users/jp/Dev/code-server"
|
|
||||||
const spy = jest.spyOn(document, "getElementById")
|
|
||||||
// Create a fake element and set the attribute
|
|
||||||
const mockElement = document.createElement("div")
|
|
||||||
mockElement.id = "coder-options"
|
|
||||||
mockElement.setAttribute(
|
|
||||||
"data-settings",
|
|
||||||
`{"base":"proxy/","csStaticBase":"${csStaticBasePath}","logLevel":2,"disableUpdateCheck":false}`,
|
|
||||||
)
|
|
||||||
// Return mockElement from the spy
|
|
||||||
// this way, when we call "getElementById"
|
|
||||||
// it returns the element
|
|
||||||
spy.mockImplementation(() => mockElement)
|
|
||||||
|
|
||||||
await registerServiceWorker()
|
|
||||||
|
|
||||||
expect(mockFn).toBeCalled()
|
|
||||||
expect(serviceWorkerPath).toMatch(`/out/browser/serviceWorker.js`)
|
|
||||||
expect(serviceWorkerScope).toMatch("/")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,92 +0,0 @@
|
|||||||
interface MockEvent {
|
|
||||||
claim: jest.Mock<any, any>
|
|
||||||
waitUntil?: jest.Mock<any, any>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
event: string
|
|
||||||
cb: (event?: MockEvent) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("serviceWorker", () => {
|
|
||||||
let listeners: Listener[] = []
|
|
||||||
let spy: jest.SpyInstance
|
|
||||||
let claimSpy: jest.Mock<any, any>
|
|
||||||
let waitUntilSpy: jest.Mock<any, any>
|
|
||||||
|
|
||||||
function emit(event: string) {
|
|
||||||
listeners
|
|
||||||
.filter((listener) => listener.event === event)
|
|
||||||
.forEach((listener) => {
|
|
||||||
switch (event) {
|
|
||||||
case "activate":
|
|
||||||
listener.cb({
|
|
||||||
claim: jest.fn(),
|
|
||||||
waitUntil: jest.fn(() => waitUntilSpy()),
|
|
||||||
})
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
listener.cb()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
claimSpy = jest.fn()
|
|
||||||
spy = jest.spyOn(console, "log")
|
|
||||||
waitUntilSpy = jest.fn()
|
|
||||||
|
|
||||||
Object.assign(global, {
|
|
||||||
self: global,
|
|
||||||
addEventListener: (event: string, cb: () => void) => {
|
|
||||||
listeners.push({ event, cb })
|
|
||||||
},
|
|
||||||
clients: {
|
|
||||||
claim: claimSpy.mockResolvedValue("claimed"),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.restoreAllMocks()
|
|
||||||
jest.resetModules()
|
|
||||||
spy.mockClear()
|
|
||||||
claimSpy.mockClear()
|
|
||||||
|
|
||||||
// Clear all the listeners
|
|
||||||
listeners = []
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should add 3 listeners: install, activate and fetch", () => {
|
|
||||||
require("../../../src/browser/serviceWorker.ts")
|
|
||||||
const listenerEventNames = listeners.map((listener) => listener.event)
|
|
||||||
|
|
||||||
expect(listeners).toHaveLength(3)
|
|
||||||
expect(listenerEventNames).toContain("install")
|
|
||||||
expect(listenerEventNames).toContain("activate")
|
|
||||||
expect(listenerEventNames).toContain("fetch")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should call the proper callbacks for 'install'", async () => {
|
|
||||||
require("../../../src/browser/serviceWorker.ts")
|
|
||||||
emit("install")
|
|
||||||
expect(spy).toHaveBeenCalledWith("[Service Worker] installed")
|
|
||||||
expect(spy).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should do nothing when 'fetch' is called", async () => {
|
|
||||||
require("../../../src/browser/serviceWorker.ts")
|
|
||||||
emit("fetch")
|
|
||||||
expect(spy).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should call the proper callbacks for 'activate'", async () => {
|
|
||||||
require("../../../src/browser/serviceWorker.ts")
|
|
||||||
emit("activate")
|
|
||||||
|
|
||||||
// Activate serviceWorker
|
|
||||||
expect(spy).toHaveBeenCalledWith("[Service Worker] activated")
|
|
||||||
expect(waitUntilSpy).toHaveBeenCalled()
|
|
||||||
expect(claimSpy).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
@ -131,7 +131,7 @@ describe("util", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should return options with base and cssStaticBase even if it doesn't exist", () => {
|
it("should return options with base and cssStaticBase even if it doesn't exist", () => {
|
||||||
expect(util.getOptions()).toStrictEqual({
|
expect(util.getClientConfiguration()).toStrictEqual({
|
||||||
base: "",
|
base: "",
|
||||||
csStaticBase: "",
|
csStaticBase: "",
|
||||||
})
|
})
|
||||||
@ -151,7 +151,7 @@ describe("util", () => {
|
|||||||
// it returns the element
|
// it returns the element
|
||||||
spy.mockImplementation(() => mockElement)
|
spy.mockImplementation(() => mockElement)
|
||||||
|
|
||||||
expect(util.getOptions()).toStrictEqual({
|
expect(util.getClientConfiguration()).toStrictEqual({
|
||||||
base: "",
|
base: "",
|
||||||
csStaticBase: "/static/development/Users/jp/Dev/code-server",
|
csStaticBase: "/static/development/Users/jp/Dev/code-server",
|
||||||
disableUpdateCheck: false,
|
disableUpdateCheck: false,
|
||||||
@ -167,7 +167,7 @@ describe("util", () => {
|
|||||||
// spreads the original options
|
// spreads the original options
|
||||||
// then parses the queryOpts
|
// then parses the queryOpts
|
||||||
location.search = '?options={"logLevel":2}'
|
location.search = '?options={"logLevel":2}'
|
||||||
expect(util.getOptions()).toStrictEqual({
|
expect(util.getClientConfiguration()).toStrictEqual({
|
||||||
base: "",
|
base: "",
|
||||||
csStaticBase: "",
|
csStaticBase: "",
|
||||||
logLevel: 2,
|
logLevel: 2,
|
||||||
@ -194,20 +194,6 @@ describe("util", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("getFirstString", () => {
|
|
||||||
it("should return the string if passed a string", () => {
|
|
||||||
expect(util.getFirstString("Hello world!")).toBe("Hello world!")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should get the first string from an array", () => {
|
|
||||||
expect(util.getFirstString(["Hello", "World"])).toBe("Hello")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should return undefined if the value isn't an array or a string", () => {
|
|
||||||
expect(util.getFirstString({ name: "Coder" })).toBe(undefined)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("logError", () => {
|
describe("logError", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
@ -122,26 +122,6 @@ describe("createApp", () => {
|
|||||||
expect(unlinkSpy).toHaveBeenCalledTimes(1)
|
expect(unlinkSpy).toHaveBeenCalledTimes(1)
|
||||||
server.close()
|
server.close()
|
||||||
})
|
})
|
||||||
it("should catch errors thrown when unlinking a socket", async () => {
|
|
||||||
const tmpDir2 = await tmpdir("unlink-socket-error")
|
|
||||||
const tmpFile = path.join(tmpDir2, "unlink-socket-file")
|
|
||||||
// await promises.writeFile(tmpFile, "")
|
|
||||||
const socketPath = tmpFile
|
|
||||||
const defaultArgs = await setDefaults({
|
|
||||||
_: [],
|
|
||||||
socket: socketPath,
|
|
||||||
})
|
|
||||||
|
|
||||||
const app = await createApp(defaultArgs)
|
|
||||||
const server = app[2]
|
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledTimes(1)
|
|
||||||
expect(spy).toHaveBeenCalledWith(`ENOENT: no such file or directory, unlink '${socketPath}'`)
|
|
||||||
|
|
||||||
server.close()
|
|
||||||
// Ensure directory was removed
|
|
||||||
rmdirSync(tmpDir2, { recursive: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should create an https server if args.cert exists", async () => {
|
it("should create an https server if args.cert exists", async () => {
|
||||||
const testCertificate = await generateCertificate("localhost")
|
const testCertificate = await generateCertificate("localhost")
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
|
import { rootPath } from "../../../../src/node/constants"
|
||||||
import { tmpdir } from "../../../utils/helpers"
|
import { tmpdir } from "../../../utils/helpers"
|
||||||
import * as httpserver from "../../../utils/httpserver"
|
import * as httpserver from "../../../utils/httpserver"
|
||||||
import * as integration from "../../../utils/integration"
|
import * as integration from "../../../utils/integration"
|
||||||
|
|
||||||
describe("/static", () => {
|
const NOT_FOUND = {
|
||||||
|
code: 404,
|
||||||
|
message: "not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("/_static", () => {
|
||||||
let _codeServer: httpserver.HttpServer | undefined
|
let _codeServer: httpserver.HttpServer | undefined
|
||||||
function codeServer(): httpserver.HttpServer {
|
function codeServer(): httpserver.HttpServer {
|
||||||
if (!_codeServer) {
|
if (!_codeServer) {
|
||||||
@ -17,14 +23,8 @@ describe("/static", () => {
|
|||||||
let testFileContent: string | undefined
|
let testFileContent: string | undefined
|
||||||
let nonExistentTestFile: string | undefined
|
let nonExistentTestFile: string | undefined
|
||||||
|
|
||||||
// The static endpoint expects a commit and then the full path of the file.
|
|
||||||
// The commit is just for cache busting so we can use anything we want. `-`
|
|
||||||
// and `development` are specially recognized in that they will cause the
|
|
||||||
// static endpoint to avoid sending cache headers.
|
|
||||||
const commit = "-"
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const testDir = await tmpdir("static")
|
const testDir = await tmpdir("_static")
|
||||||
testFile = path.join(testDir, "test")
|
testFile = path.join(testDir, "test")
|
||||||
testFileContent = "static file contents"
|
testFileContent = "static file contents"
|
||||||
nonExistentTestFile = path.join(testDir, "i-am-not-here")
|
nonExistentTestFile = path.join(testDir, "i-am-not-here")
|
||||||
@ -39,20 +39,12 @@ describe("/static", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function commonTests() {
|
function commonTests() {
|
||||||
it("should return a 404 when a commit and file are not provided", async () => {
|
|
||||||
const resp = await codeServer().fetch("/static")
|
|
||||||
expect(resp.status).toBe(404)
|
|
||||||
|
|
||||||
const content = await resp.json()
|
|
||||||
expect(content).toStrictEqual({ error: "Not Found" })
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should return a 404 when a file is not provided", async () => {
|
it("should return a 404 when a file is not provided", async () => {
|
||||||
const resp = await codeServer().fetch(`/static/${commit}`)
|
const resp = await codeServer().fetch(`/_static/`)
|
||||||
expect(resp.status).toBe(404)
|
expect(resp.status).toBe(NOT_FOUND.code)
|
||||||
|
|
||||||
const content = await resp.json()
|
const content = await resp.json()
|
||||||
expect(content).toStrictEqual({ error: "Not Found" })
|
expect(content.error).toContain(NOT_FOUND.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,73 +56,22 @@ describe("/static", () => {
|
|||||||
commonTests()
|
commonTests()
|
||||||
|
|
||||||
it("should return a 404 for a nonexistent file", async () => {
|
it("should return a 404 for a nonexistent file", async () => {
|
||||||
const resp = await codeServer().fetch(`/static/${commit}/${nonExistentTestFile}`)
|
const filePath = path.join("/_static/", nonExistentTestFile!)
|
||||||
expect(resp.status).toBe(404)
|
|
||||||
|
|
||||||
const content = await resp.json()
|
const resp = await codeServer().fetch(filePath)
|
||||||
expect(content.error).toMatch("ENOENT")
|
expect(resp.status).toBe(NOT_FOUND.code)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return a 200 and file contents for an existent file", async () => {
|
it("should return a 200 and file contents for an existent file", async () => {
|
||||||
const resp = await codeServer().fetch(`/static/${commit}${testFile}`)
|
const resp = await codeServer().fetch("/_static/src/browser/robots.txt")
|
||||||
expect(resp.status).toBe(200)
|
expect(resp.status).toBe(200)
|
||||||
|
|
||||||
|
const localFilePath = path.join(rootPath, "src/browser/robots.txt")
|
||||||
|
const localFileContent = await fs.readFile(localFilePath, "utf8")
|
||||||
|
|
||||||
|
// console.log(localFileContent)
|
||||||
const content = await resp.text()
|
const content = await resp.text()
|
||||||
expect(content).toStrictEqual(testFileContent)
|
expect(content).toStrictEqual(localFileContent)
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("enabled authentication", () => {
|
|
||||||
// Store whatever might be in here so we can restore it afterward.
|
|
||||||
// TODO: We should probably pass this as an argument somehow instead of
|
|
||||||
// manipulating the environment.
|
|
||||||
const previousEnvPassword = process.env.PASSWORD
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
process.env.PASSWORD = "test"
|
|
||||||
_codeServer = await integration.setup(["--auth=password"], "")
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
process.env.PASSWORD = previousEnvPassword
|
|
||||||
})
|
|
||||||
|
|
||||||
commonTests()
|
|
||||||
|
|
||||||
describe("inside code-server root", () => {
|
|
||||||
it("should return a 404 for a nonexistent file", async () => {
|
|
||||||
const resp = await codeServer().fetch(`/static/${commit}/${__filename}-does-not-exist`)
|
|
||||||
expect(resp.status).toBe(404)
|
|
||||||
|
|
||||||
const content = await resp.json()
|
|
||||||
expect(content.error).toMatch("ENOENT")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should return a 200 and file contents for an existent file", async () => {
|
|
||||||
const resp = await codeServer().fetch(`/static/${commit}${__filename}`)
|
|
||||||
expect(resp.status).toBe(200)
|
|
||||||
|
|
||||||
const content = await resp.text()
|
|
||||||
expect(content).toStrictEqual(await fs.readFile(__filename, "utf8"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("outside code-server root", () => {
|
|
||||||
it("should return a 401 for a nonexistent file", async () => {
|
|
||||||
const resp = await codeServer().fetch(`/static/${commit}/${nonExistentTestFile}`)
|
|
||||||
expect(resp.status).toBe(401)
|
|
||||||
|
|
||||||
const content = await resp.json()
|
|
||||||
expect(content).toStrictEqual({ error: "Unauthorized" })
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should return a 401 for an existent file", async () => {
|
|
||||||
const resp = await codeServer().fetch(`/static/${commit}${testFile}`)
|
|
||||||
expect(resp.status).toBe(401)
|
|
||||||
|
|
||||||
const content = await resp.json()
|
|
||||||
expect(content).toStrictEqual({ error: "Unauthorized" })
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -457,31 +457,6 @@ describe("escapeHtml", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("pathToFsPath", () => {
|
|
||||||
it("should convert a path to a file system path", () => {
|
|
||||||
expect(util.pathToFsPath("/foo/bar/baz")).toBe("/foo/bar/baz")
|
|
||||||
})
|
|
||||||
it("should lowercase drive letter casing by default", () => {
|
|
||||||
expect(util.pathToFsPath("/C:/far/boo")).toBe("c:/far/boo")
|
|
||||||
})
|
|
||||||
it("should keep drive letter casing when set to true", () => {
|
|
||||||
expect(util.pathToFsPath("/C:/far/bo", true)).toBe("C:/far/bo")
|
|
||||||
})
|
|
||||||
it("should replace / with \\ on Windows", () => {
|
|
||||||
const ORIGINAL_PLATFORM = process.platform
|
|
||||||
|
|
||||||
Object.defineProperty(process, "platform", {
|
|
||||||
value: "win32",
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(util.pathToFsPath("/C:/far/boo")).toBe("c:\\far\\boo")
|
|
||||||
|
|
||||||
Object.defineProperty(process, "platform", {
|
|
||||||
value: ORIGINAL_PLATFORM,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("isFile", () => {
|
describe("isFile", () => {
|
||||||
const testDir = path.join(tmpdir, "tests", "isFile")
|
const testDir = path.join(tmpdir, "tests", "isFile")
|
||||||
let pathToFile = ""
|
let pathToFile = ""
|
||||||
|
@ -1101,10 +1101,10 @@
|
|||||||
jest-diff "^26.0.0"
|
jest-diff "^26.0.0"
|
||||||
pretty-format "^26.0.0"
|
pretty-format "^26.0.0"
|
||||||
|
|
||||||
"@types/jsdom@^16.2.6":
|
"@types/jsdom@^16.2.13":
|
||||||
version "16.2.6"
|
version "16.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.6.tgz#9ddf0521e49be5365797e690c3ba63148e562c29"
|
resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.13.tgz#126c8b7441b159d6234610a48de77b6066f1823f"
|
||||||
integrity sha512-yQA+HxknGtW9AkRTNyiSH3OKW5V+WzO8OPTdne99XwJkYC+KYxfNIcoJjeiSqP3V00PUUpFP6Myoo9wdIu78DQ==
|
integrity sha512-8JQCjdeAidptSsOcRWk2iTm9wCcwn9l+kRG6k5bzUacrnm1ezV4forq0kWjUih/tumAeoG+OspOvQEbbRucBTw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
"@types/parse5" "*"
|
"@types/parse5" "*"
|
||||||
|
@ -15,9 +15,14 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"tsBuildInfoFile": "./.cache/tsbuildinfo",
|
"tsBuildInfoFile": "./.cache/tsbuildinfo",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"typeRoots": ["./node_modules/@types", "./typings", "./test/node_modules/@types"],
|
"typeRoots": [
|
||||||
|
"./node_modules/@types",
|
||||||
|
"./typings",
|
||||||
|
"./test/node_modules/@types",
|
||||||
|
"./vendor/modules/code-oss-dev/src/vs/server/@types"
|
||||||
|
],
|
||||||
"downlevelIteration": true
|
"downlevelIteration": true
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*.ts"],
|
"include": ["./src/**/*"],
|
||||||
"exclude": ["/test", "/lib", "/ci", "/doc"]
|
"exclude": ["/test", "/lib", "/ci", "/doc"]
|
||||||
}
|
}
|
||||||
|
137
typings/ipc.d.ts
vendored
137
typings/ipc.d.ts
vendored
@ -1,137 +0,0 @@
|
|||||||
/**
|
|
||||||
* External interfaces for integration into code-server over IPC.
|
|
||||||
* This file exists in two locations:
|
|
||||||
* - typings/ipc.d.ts
|
|
||||||
* - lib/vscode/src/typings/ipc.d.ts
|
|
||||||
* The second is a symlink to the first.
|
|
||||||
*/
|
|
||||||
export interface Options {
|
|
||||||
authed: boolean
|
|
||||||
base: string
|
|
||||||
csStaticBase: string
|
|
||||||
disableUpdateCheck: boolean
|
|
||||||
logLevel: number
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
permessageDeflate: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
_: 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 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
|
|
||||||
}
|
|
2
vendor/package.json
vendored
2
vendor/package.json
vendored
@ -7,6 +7,6 @@
|
|||||||
"postinstall": "./postinstall.sh"
|
"postinstall": "./postinstall.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"code-oss-dev": "cdr/vscode#9cb5fb3759f46b10bc66e676fa7f44c51e84824b"
|
"code-oss-dev": "cdr/vscode#63718dfe9f975d6638d46c1548a5feb482c6dfa8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
285
vendor/yarn.lock
vendored
285
vendor/yarn.lock
vendored
@ -2,11 +2,6 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@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==
|
|
||||||
|
|
||||||
"@electron/get@^1.0.1":
|
"@electron/get@^1.0.1":
|
||||||
version "1.13.0"
|
version "1.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.13.0.tgz#95c6bcaff4f9a505ea46792424f451efea89228c"
|
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.13.0.tgz#95c6bcaff4f9a505ea46792424f451efea89228c"
|
||||||
@ -150,7 +145,7 @@ agent-base@5:
|
|||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
|
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
|
||||||
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
|
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
|
||||||
|
|
||||||
agent-base@6, agent-base@^6.0.0, agent-base@^6.0.2:
|
agent-base@6, agent-base@^6.0.2:
|
||||||
version "6.0.2"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||||
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
|
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
|
||||||
@ -197,13 +192,6 @@ are-we-there-yet@~1.1.2:
|
|||||||
delegates "^1.0.0"
|
delegates "^1.0.0"
|
||||||
readable-stream "^2.0.6"
|
readable-stream "^2.0.6"
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
base64-js@^1.3.1:
|
base64-js@^1.3.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||||
@ -260,11 +248,6 @@ buffer@^5.5.0:
|
|||||||
base64-js "^1.3.1"
|
base64-js "^1.3.1"
|
||||||
ieee754 "^1.1.13"
|
ieee754 "^1.1.13"
|
||||||
|
|
||||||
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==
|
|
||||||
|
|
||||||
cacheable-request@^6.0.0:
|
cacheable-request@^6.0.0:
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
|
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
|
||||||
@ -313,17 +296,17 @@ clone-response@^1.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-response "^1.0.0"
|
mimic-response "^1.0.0"
|
||||||
|
|
||||||
code-oss-dev@cdr/vscode#9cb5fb3759f46b10bc66e676fa7f44c51e84824b:
|
code-oss-dev@cdr/vscode#63718dfe9f975d6638d46c1548a5feb482c6dfa8:
|
||||||
version "1.60.0"
|
version "1.60.2"
|
||||||
resolved "https://codeload.github.com/cdr/vscode/tar.gz/9cb5fb3759f46b10bc66e676fa7f44c51e84824b"
|
resolved "https://codeload.github.com/cdr/vscode/tar.gz/63718dfe9f975d6638d46c1548a5feb482c6dfa8"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@coder/logger" "^1.1.16"
|
|
||||||
"@microsoft/applicationinsights-web" "^2.6.4"
|
"@microsoft/applicationinsights-web" "^2.6.4"
|
||||||
"@vscode/sqlite3" "4.0.12"
|
"@vscode/sqlite3" "4.0.12"
|
||||||
"@vscode/vscode-languagedetection" "1.0.20"
|
"@vscode/vscode-languagedetection" "1.0.20"
|
||||||
applicationinsights "1.0.8"
|
applicationinsights "1.0.8"
|
||||||
chokidar "3.5.1"
|
chokidar "3.5.1"
|
||||||
graceful-fs "4.2.6"
|
graceful-fs "4.2.6"
|
||||||
|
handlebars "^4.7.7"
|
||||||
http-proxy-agent "^2.1.0"
|
http-proxy-agent "^2.1.0"
|
||||||
https-proxy-agent "^2.2.3"
|
https-proxy-agent "^2.2.3"
|
||||||
iconv-lite-umd "0.6.8"
|
iconv-lite-umd "0.6.8"
|
||||||
@ -333,8 +316,7 @@ code-oss-dev@cdr/vscode#9cb5fb3759f46b10bc66e676fa7f44c51e84824b:
|
|||||||
native-watchdog "1.3.0"
|
native-watchdog "1.3.0"
|
||||||
node-pty "0.11.0-beta7"
|
node-pty "0.11.0-beta7"
|
||||||
nsfw "2.1.2"
|
nsfw "2.1.2"
|
||||||
proxy-agent "^4.0.1"
|
path-to-regexp "^6.2.0"
|
||||||
proxy-from-env "^1.1.0"
|
|
||||||
spdlog "^0.13.0"
|
spdlog "^0.13.0"
|
||||||
sudo-prompt "9.2.1"
|
sudo-prompt "9.2.1"
|
||||||
tar-stream "^2.2.0"
|
tar-stream "^2.2.0"
|
||||||
@ -457,11 +439,6 @@ deep-extend@^0.6.0:
|
|||||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||||
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
||||||
|
|
||||||
deep-is@~0.1.3:
|
|
||||||
version "0.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
|
||||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
|
||||||
|
|
||||||
defer-to-connect@^1.0.1:
|
defer-to-connect@^1.0.1:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
|
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
|
||||||
@ -474,25 +451,11 @@ define-properties@^1.1.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
object-keys "^1.0.12"
|
object-keys "^1.0.12"
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
delegates@^1.0.0:
|
delegates@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||||
|
|
||||||
depd@~1.1.2:
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
|
||||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
|
||||||
|
|
||||||
detect-libc@^1.0.3:
|
detect-libc@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
||||||
@ -568,33 +531,6 @@ escape-string-regexp@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
esprima@^4.0.0, 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==
|
|
||||||
|
|
||||||
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.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
|
||||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
|
||||||
|
|
||||||
expand-template@^2.0.3:
|
expand-template@^2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
||||||
@ -610,11 +546,6 @@ extract-zip@^1.0.3:
|
|||||||
mkdirp "^0.5.4"
|
mkdirp "^0.5.4"
|
||||||
yauzl "^2.10.0"
|
yauzl "^2.10.0"
|
||||||
|
|
||||||
fast-levenshtein@~2.0.6:
|
|
||||||
version "2.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
|
||||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
|
||||||
|
|
||||||
fd-slicer@~1.1.0:
|
fd-slicer@~1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||||
@ -694,7 +625,7 @@ get-stream@^5.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
pump "^3.0.0"
|
pump "^3.0.0"
|
||||||
|
|
||||||
get-uri@3, get-uri@^3.0.2:
|
get-uri@^3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c"
|
resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c"
|
||||||
integrity sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==
|
integrity sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==
|
||||||
@ -775,6 +706,18 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
|
||||||
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
|
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
|
||||||
|
|
||||||
|
handlebars@^4.7.7:
|
||||||
|
version "4.7.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
|
||||||
|
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
|
||||||
|
dependencies:
|
||||||
|
minimist "^1.2.5"
|
||||||
|
neo-async "^2.6.0"
|
||||||
|
source-map "^0.6.1"
|
||||||
|
wordwrap "^1.0.0"
|
||||||
|
optionalDependencies:
|
||||||
|
uglify-js "^3.1.4"
|
||||||
|
|
||||||
has-unicode@^2.0.0:
|
has-unicode@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||||
@ -785,17 +728,6 @@ http-cache-semantics@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
|
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
|
||||||
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
|
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
|
||||||
|
|
||||||
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:
|
http-proxy-agent@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||||
@ -804,7 +736,7 @@ http-proxy-agent@^2.1.0:
|
|||||||
agent-base "4"
|
agent-base "4"
|
||||||
debug "3.1.0"
|
debug "3.1.0"
|
||||||
|
|
||||||
http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1:
|
http-proxy-agent@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
|
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
|
||||||
integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
|
integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
|
||||||
@ -813,14 +745,6 @@ http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1:
|
|||||||
agent-base "6"
|
agent-base "6"
|
||||||
debug "4"
|
debug "4"
|
||||||
|
|
||||||
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:
|
https-proxy-agent@^2.2.3:
|
||||||
version "2.2.4"
|
version "2.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
|
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
|
||||||
@ -837,24 +761,25 @@ https-proxy-agent@^4.0.0:
|
|||||||
agent-base "5"
|
agent-base "5"
|
||||||
debug "4"
|
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"
|
||||||
|
|
||||||
iconv-lite-umd@0.6.8:
|
iconv-lite-umd@0.6.8:
|
||||||
version "0.6.8"
|
version "0.6.8"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0"
|
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0"
|
||||||
integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A==
|
integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A==
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
ieee754@^1.1.13:
|
ieee754@^1.1.13:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||||
|
|
||||||
inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
|
inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
@ -952,14 +877,6 @@ keyv@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
json-buffer "3.0.0"
|
json-buffer "3.0.0"
|
||||||
|
|
||||||
levn@~0.3.0:
|
|
||||||
version "0.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
|
|
||||||
integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
|
|
||||||
dependencies:
|
|
||||||
prelude-ls "~1.1.2"
|
|
||||||
type-check "~0.3.2"
|
|
||||||
|
|
||||||
lodash@^4.17.10:
|
lodash@^4.17.10:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
@ -975,13 +892,6 @@ lowercase-keys@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
|
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
|
||||||
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
|
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
|
||||||
|
|
||||||
lru-cache@^5.1.1:
|
|
||||||
version "5.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
|
|
||||||
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
|
|
||||||
dependencies:
|
|
||||||
yallist "^3.0.2"
|
|
||||||
|
|
||||||
lru-cache@^6.0.0:
|
lru-cache@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||||
@ -1068,10 +978,10 @@ native-watchdog@1.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.3.0.tgz#88cee94c9dc766b85c8506eda14c8bd8c9618e27"
|
resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.3.0.tgz#88cee94c9dc766b85c8506eda14c8bd8c9618e27"
|
||||||
integrity sha512-WOjGRNGkYZ5MXsntcvCYrKtSYMaewlbCFplbcUVo9bE80LPVt8TAVFHYWB8+a6fWCGYheq21+Wtt6CJrUaCJhw==
|
integrity sha512-WOjGRNGkYZ5MXsntcvCYrKtSYMaewlbCFplbcUVo9bE80LPVt8TAVFHYWB8+a6fWCGYheq21+Wtt6CJrUaCJhw==
|
||||||
|
|
||||||
netmask@^2.0.1:
|
neo-async@^2.6.0:
|
||||||
version "2.0.2"
|
version "2.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7"
|
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
||||||
integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==
|
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||||
|
|
||||||
node-abi@^2.21.0:
|
node-abi@^2.21.0:
|
||||||
version "2.30.1"
|
version "2.30.1"
|
||||||
@ -1154,46 +1064,15 @@ once@^1.3.1, once@^1.4.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
wrappy "1"
|
wrappy "1"
|
||||||
|
|
||||||
optionator@^0.8.1:
|
|
||||||
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"
|
|
||||||
|
|
||||||
p-cancelable@^1.0.0:
|
p-cancelable@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
|
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
|
||||||
integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
|
integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
|
||||||
|
|
||||||
pac-proxy-agent@^4.1.0:
|
path-to-regexp@^6.2.0:
|
||||||
version "4.1.0"
|
version "6.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-4.1.0.tgz#66883eeabadc915fc5e95457324cb0f0ac78defb"
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38"
|
||||||
integrity sha512-ejNgYm2HTXSIYX9eFlkvqFp8hyJ374uDf0Zq5YUAifiSh1D6fo+iBivQZirGvVv8dCYUsLhmLBRhlAYvBKI5+Q==
|
integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==
|
||||||
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.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-4.2.0.tgz#b82bcb9992d48166920bc83c7542abb454bd9bdd"
|
|
||||||
integrity sha512-rPACZdUyuxT5Io/gFKUeeZFfE5T7ve7cAkE5TUZRRfuKP0u5Hocwe48X7ZEm6mYB+bTB0Qf+xlVlA/RM/i6RCQ==
|
|
||||||
dependencies:
|
|
||||||
degenerator "^2.2.0"
|
|
||||||
ip "^1.1.5"
|
|
||||||
netmask "^2.0.1"
|
|
||||||
|
|
||||||
pend@~1.2.0:
|
pend@~1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
@ -1229,11 +1108,6 @@ prebuild-install@^6.0.0:
|
|||||||
tar-fs "^2.0.0"
|
tar-fs "^2.0.0"
|
||||||
tunnel-agent "^0.6.0"
|
tunnel-agent "^0.6.0"
|
||||||
|
|
||||||
prelude-ls@~1.1.2:
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
|
||||||
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
|
|
||||||
|
|
||||||
prepend-http@^2.0.0:
|
prepend-http@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||||
@ -1254,21 +1128,7 @@ proto-list@~1.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
||||||
integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
|
integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
|
||||||
|
|
||||||
proxy-agent@^4.0.1:
|
proxy-from-env@^1.1.0:
|
||||||
version "4.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-4.0.1.tgz#326c3250776c7044cd19655ccbfadf2e065a045c"
|
|
||||||
integrity sha512-ODnQnW2jc/FUVwHHuaZEfN5otg/fMbvMxz9nMSUQfJ9JU7q2SZvSULSsjLloVgJOiv9yhc8GlNMKc4GkFmcVEA==
|
|
||||||
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"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||||
@ -1281,16 +1141,6 @@ pump@^3.0.0:
|
|||||||
end-of-stream "^1.1.0"
|
end-of-stream "^1.1.0"
|
||||||
once "^1.3.1"
|
once "^1.3.1"
|
||||||
|
|
||||||
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:
|
rc@^1.2.7:
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||||
@ -1369,11 +1219,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
|
||||||
"safer-buffer@>= 2.1.2 < 3":
|
|
||||||
version "2.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
|
||||||
|
|
||||||
semver-compare@^1.0.0:
|
semver-compare@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||||
@ -1408,11 +1253,6 @@ set-blocking@~2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||||
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
||||||
|
|
||||||
setprototypeof@1.1.1:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
|
||||||
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
|
||||||
|
|
||||||
signal-exit@^3.0.0:
|
signal-exit@^3.0.0:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
|
||||||
@ -1437,7 +1277,7 @@ smart-buffer@^4.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
|
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
|
||||||
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
|
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
|
||||||
|
|
||||||
socks-proxy-agent@5, socks-proxy-agent@^5.0.0:
|
socks-proxy-agent@^5.0.0:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e"
|
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e"
|
||||||
integrity sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==
|
integrity sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==
|
||||||
@ -1454,7 +1294,7 @@ socks@^2.3.3:
|
|||||||
ip "^1.1.5"
|
ip "^1.1.5"
|
||||||
smart-buffer "^4.1.0"
|
smart-buffer "^4.1.0"
|
||||||
|
|
||||||
source-map@~0.6.1:
|
source-map@^0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||||
@ -1473,11 +1313,6 @@ sprintf-js@^1.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
|
||||||
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
|
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
|
||||||
|
|
||||||
"statuses@>= 1.5.0 < 2":
|
|
||||||
version "1.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
|
||||||
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
|
||||||
|
|
||||||
string-width@^1.0.1:
|
string-width@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
|
||||||
@ -1583,16 +1418,6 @@ to-regex-range@^5.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-number "^7.0.0"
|
is-number "^7.0.0"
|
||||||
|
|
||||||
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==
|
|
||||||
|
|
||||||
tslib@^2.0.1:
|
|
||||||
version "2.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
|
||||||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
|
||||||
|
|
||||||
tunnel-agent@^0.6.0:
|
tunnel-agent@^0.6.0:
|
||||||
version "0.6.0"
|
version "0.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||||
@ -1605,13 +1430,6 @@ tunnel@^0.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
||||||
|
|
||||||
type-check@~0.3.2:
|
|
||||||
version "0.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
|
|
||||||
integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=
|
|
||||||
dependencies:
|
|
||||||
prelude-ls "~1.1.2"
|
|
||||||
|
|
||||||
type-fest@^0.13.1:
|
type-fest@^0.13.1:
|
||||||
version "0.13.1"
|
version "0.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
|
||||||
@ -1622,16 +1440,16 @@ typedarray@^0.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||||
|
|
||||||
|
uglify-js@^3.1.4:
|
||||||
|
version "3.14.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.2.tgz#d7dd6a46ca57214f54a2d0a43cad0f35db82ac99"
|
||||||
|
integrity sha512-rtPMlmcO4agTUfz10CbgJ1k6UAoXM2gWb3GoMPPZB/+/Ackf8lNWk11K4rYi2D0apgoFRLtQOZhb+/iGNJq26A==
|
||||||
|
|
||||||
universalify@^0.1.0:
|
universalify@^0.1.0:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
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=
|
|
||||||
|
|
||||||
url-parse-lax@^3.0.0:
|
url-parse-lax@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
|
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
|
||||||
@ -1728,10 +1546,10 @@ windows-process-tree@0.3.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
nan "^2.13.2"
|
nan "^2.13.2"
|
||||||
|
|
||||||
word-wrap@~1.2.3:
|
wordwrap@^1.0.0:
|
||||||
version "1.2.3"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
|
||||||
|
|
||||||
wrappy@1:
|
wrappy@1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
@ -1778,11 +1596,6 @@ xterm@4.14.0-beta.22:
|
|||||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.14.0-beta.22.tgz#89e1060927f6542645f53584bfcd35cec08abe5a"
|
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.14.0-beta.22.tgz#89e1060927f6542645f53584bfcd35cec08abe5a"
|
||||||
integrity sha512-zl4d2fmjAoCB+G0O5tq2kNkoe7dnnrcY2Daj6VFPPV6nItyXrgRzlKWNfTjKYunC3kU2ApYy+FnHulO7lWuXSw==
|
integrity sha512-zl4d2fmjAoCB+G0O5tq2kNkoe7dnnrcY2Daj6VFPPV6nItyXrgRzlKWNfTjKYunC3kU2ApYy+FnHulO7lWuXSw==
|
||||||
|
|
||||||
yallist@^3.0.2:
|
|
||||||
version "3.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
|
||||||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
|
||||||
|
|
||||||
yallist@^4.0.0:
|
yallist@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||||
|
Reference in New Issue
Block a user