mirror of https://github.com/coder/code-server.git
commit
ce656a0104
|
@ -23,6 +23,13 @@ rules:
|
||||||
no-dupe-class-members: off
|
no-dupe-class-members: off
|
||||||
"@typescript-eslint/no-use-before-define": off
|
"@typescript-eslint/no-use-before-define": off
|
||||||
"@typescript-eslint/no-non-null-assertion": off
|
"@typescript-eslint/no-non-null-assertion": off
|
||||||
|
"@typescript-eslint/ban-types": off
|
||||||
|
"@typescript-eslint/no-var-requires": off
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": off
|
||||||
|
"@typescript-eslint/no-explicit-any": off
|
||||||
|
eqeqeq: error
|
||||||
|
import/order:
|
||||||
|
[error, { alphabetize: { order: "asc" }, groups: [["builtin", "external", "internal"], "parent", "sibling"] }]
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
# Does not work with CommonJS unfortunately.
|
# Does not work with CommonJS unfortunately.
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Question
|
||||||
|
url: https://github.com/cdr/code-server/discussions/new?category_id=22503114
|
||||||
|
about: Ask the community for help
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
name: Documentation improvement
|
||||||
|
about: Suggest a documentation improvement
|
||||||
|
title: ""
|
||||||
|
labels: "docs"
|
||||||
|
assignees: ""
|
||||||
|
---
|
|
@ -1,4 +0,0 @@
|
||||||
<!--
|
|
||||||
Please file all questions and support requests at https://www.reddit.com/r/codeserver/
|
|
||||||
The issue tracker is only for bugs and features.
|
|
||||||
-->
|
|
|
@ -1,4 +1,6 @@
|
||||||
<!--
|
<!--
|
||||||
Please link to the issue this PR solves.
|
Please link to the issue this PR solves.
|
||||||
If there is no existing issue, please first create one unless the fix is minor.
|
If there is no existing issue, please first create one unless the fix is minor.
|
||||||
|
|
||||||
|
Please make sure the base of your PR is the master branch!
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -10,3 +10,4 @@ release-gcp/
|
||||||
release-images/
|
release-images/
|
||||||
node_modules
|
node_modules
|
||||||
node-*
|
node-*
|
||||||
|
/plugins
|
||||||
|
|
|
@ -11,7 +11,7 @@ Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and a
|
||||||
- Develop on a Linux machine and pick up from any device with a web browser.
|
- Develop on a Linux machine and pick up from any device with a web browser.
|
||||||
- **Server-powered**
|
- **Server-powered**
|
||||||
- Take advantage of large cloud servers to speed up tests, compilations, downloads, and more.
|
- Take advantage of large cloud servers to speed up tests, compilations, downloads, and more.
|
||||||
- Preserve battery life when you're on the go as all intensive tasks runs on your server.
|
- Preserve battery life when you're on the go as all intensive tasks run on your server.
|
||||||
- Make use of a spare computer you have lying around and turn it into a full development environment.
|
- Make use of a spare computer you have lying around and turn it into a full development environment.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
@ -52,7 +52,7 @@ See [./doc/CONTRIBUTING.md](./doc/CONTRIBUTING.md).
|
||||||
|
|
||||||
## Hiring
|
## Hiring
|
||||||
|
|
||||||
We ([@cdr](https://github.com/cdr)) are looking for a engineers to help maintain
|
We ([@cdr](https://github.com/cdr)) are looking for engineers to help maintain
|
||||||
code-server, innovate on open source and streamline dev workflows.
|
code-server, innovate on open source and streamline dev workflows.
|
||||||
|
|
||||||
Our main office is in Austin, Texas. Remote is ok as long as
|
Our main office is in Austin, Texas. Remote is ok as long as
|
||||||
|
@ -60,6 +60,9 @@ you're in North America or Europe.
|
||||||
|
|
||||||
Please get in [touch](mailto:jobs@coder.com) with your resume/github if interested.
|
Please get in [touch](mailto:jobs@coder.com) with your resume/github if interested.
|
||||||
|
|
||||||
|
We're also hiring someone specifically to help maintain code-server.
|
||||||
|
See the listing [here](https://jobs.lever.co/coder/e40becde-2cbd-4885-9029-e5c7b0a734b8).
|
||||||
|
|
||||||
## For Organizations
|
## For Organizations
|
||||||
|
|
||||||
Visit [our website](https://coder.com) for more information about remote development for your organization or enterprise.
|
Visit [our website](https://coder.com) for more information about remote development for your organization or enterprise.
|
||||||
|
|
|
@ -18,13 +18,15 @@ Make sure you have `$GITHUB_TOKEN` set and [hub](https://github.com/github/hub)
|
||||||
1. Update in `package.json`
|
1. Update in `package.json`
|
||||||
2. Update in [./doc/install.md](../doc/install.md)
|
2. Update in [./doc/install.md](../doc/install.md)
|
||||||
2. GitHub actions will generate the `npm-package`, `release-packages` and `release-images` artifacts.
|
2. GitHub actions will generate the `npm-package`, `release-packages` and `release-images` artifacts.
|
||||||
|
1. You do not have to wait for these.
|
||||||
3. Run `yarn release:github-draft` to create a GitHub draft release from the template with
|
3. Run `yarn release:github-draft` to create a GitHub draft release from the template with
|
||||||
the updated version.
|
the updated version.
|
||||||
1. Summarize the major changes in the release notes and link to the relevant issues.
|
1. Summarize the major changes in the release notes and link to the relevant issues.
|
||||||
4. Wait for the artifacts in step 2 to build.
|
4. Wait for the artifacts in step 2 to build.
|
||||||
5. Run `yarn release:github-assets` to download the `release-packages` artifact and
|
5. Run `yarn release:github-assets` to download the `release-packages` artifact.
|
||||||
upload them to the draft release.
|
- It will upload them to the draft release.
|
||||||
6. Run some basic sanity tests on one of the released packages.
|
6. Run some basic sanity tests on one of the released packages.
|
||||||
|
- Especially make sure the terminal works fine.
|
||||||
7. Make sure the github release tag is the commit with the artifacts. This is a bug in
|
7. Make sure the github release tag is the commit with the artifacts. This is a bug in
|
||||||
`hub` where uploading assets in step 5 will break the tag.
|
`hub` where uploading assets in step 5 will break the tag.
|
||||||
8. Publish the release and merge the PR.
|
8. Publish the release and merge the PR.
|
||||||
|
@ -36,7 +38,6 @@ Make sure you have `$GITHUB_TOKEN` set and [hub](https://github.com/github/hub)
|
||||||
10. Wait for the npm package to be published.
|
10. Wait for the npm package to be published.
|
||||||
11. Update the homebrew package.
|
11. Update the homebrew package.
|
||||||
- Send a pull request to [homebrew-core](https://github.com/Homebrew/homebrew-core) with the URL in the [formula](https://github.com/Homebrew/homebrew-core/blob/master/Formula/code-server.rb) updated.
|
- Send a pull request to [homebrew-core](https://github.com/Homebrew/homebrew-core) with the URL in the [formula](https://github.com/Homebrew/homebrew-core/blob/master/Formula/code-server.rb) updated.
|
||||||
12. Make sure to add a release without the `v` prefix for autoupdate from `3.2.0`.
|
|
||||||
|
|
||||||
## dev
|
## dev
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ MINIFY=${MINIFY-true}
|
||||||
main() {
|
main() {
|
||||||
cd "$(dirname "${0}")/../.."
|
cd "$(dirname "${0}")/../.."
|
||||||
|
|
||||||
tsc --outDir out --tsBuildInfoFile .cache/out.tsbuildinfo
|
tsc
|
||||||
|
|
||||||
# If out/node/entry.js does not already have the shebang,
|
# If out/node/entry.js does not already have the shebang,
|
||||||
# we make sure to add it and make it executable.
|
# we make sure to add it and make it executable.
|
||||||
if ! grep -q -m1 "^#!/usr/bin/env node" out/node/entry.js; then
|
if ! grep -q -m1 "^#!/usr/bin/env node" out/node/entry.js; then
|
||||||
|
@ -18,11 +19,13 @@ main() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
parcel build \
|
parcel build \
|
||||||
--public-url "/static/$(git rev-parse HEAD)/dist" \
|
--public-url "." \
|
||||||
--out-dir dist \
|
--out-dir dist \
|
||||||
$([[ $MINIFY ]] || echo --no-minify) \
|
$([[ $MINIFY ]] || echo --no-minify) \
|
||||||
src/browser/register.ts \
|
src/browser/register.ts \
|
||||||
src/browser/serviceWorker.ts
|
src/browser/serviceWorker.ts \
|
||||||
|
src/browser/pages/login.ts \
|
||||||
|
src/browser/pages/vscode.ts
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|
|
@ -21,6 +21,12 @@ main() {
|
||||||
rsync README.md "$RELEASE_PATH"
|
rsync README.md "$RELEASE_PATH"
|
||||||
rsync LICENSE.txt "$RELEASE_PATH"
|
rsync LICENSE.txt "$RELEASE_PATH"
|
||||||
rsync ./lib/vscode/ThirdPartyNotices.txt "$RELEASE_PATH"
|
rsync ./lib/vscode/ThirdPartyNotices.txt "$RELEASE_PATH"
|
||||||
|
|
||||||
|
# code-server exports types which can be imported and used by plugins. Those
|
||||||
|
# types import ipc.d.ts but it isn't included in the final vscode build so
|
||||||
|
# we'll copy it ourselves here.
|
||||||
|
mkdir -p "$RELEASE_PATH/lib/vscode/src/vs/server"
|
||||||
|
rsync ./lib/vscode/src/vs/server/ipc.d.ts "$RELEASE_PATH/lib/vscode/src/vs/server"
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle_code_server() {
|
bundle_code_server() {
|
||||||
|
@ -31,6 +37,7 @@ bundle_code_server() {
|
||||||
rsync src/browser/media/ "$RELEASE_PATH/src/browser/media"
|
rsync src/browser/media/ "$RELEASE_PATH/src/browser/media"
|
||||||
mkdir -p "$RELEASE_PATH/src/browser/pages"
|
mkdir -p "$RELEASE_PATH/src/browser/pages"
|
||||||
rsync src/browser/pages/*.html "$RELEASE_PATH/src/browser/pages"
|
rsync src/browser/pages/*.html "$RELEASE_PATH/src/browser/pages"
|
||||||
|
rsync src/browser/robots.txt "$RELEASE_PATH/src/browser"
|
||||||
|
|
||||||
# Adds the commit to package.json
|
# Adds the commit to package.json
|
||||||
jq --slurp '.[0] * .[1]' package.json <(
|
jq --slurp '.[0] * .[1]' package.json <(
|
||||||
|
|
|
@ -5,16 +5,16 @@ main() {
|
||||||
cd "$(dirname "${0}")/../.."
|
cd "$(dirname "${0}")/../.."
|
||||||
source ./ci/lib.sh
|
source ./ci/lib.sh
|
||||||
|
|
||||||
rm -Rf \
|
rm -rf \
|
||||||
out \
|
out \
|
||||||
release \
|
release \
|
||||||
release-standalone \
|
release-standalone \
|
||||||
release-packages \
|
release-packages \
|
||||||
release-gcp \
|
release-gcp \
|
||||||
release-images/ \
|
release-images \
|
||||||
dist \
|
dist \
|
||||||
.tsbuildinfo \
|
.cache \
|
||||||
.cache/out.tsbuildinfo
|
node-*
|
||||||
|
|
||||||
pushd lib/vscode
|
pushd lib/vscode
|
||||||
git clean -xffd
|
git clean -xffd
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=code-server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=exec
|
||||||
|
ExecStart=/usr/bin/code-server
|
||||||
|
Restart=always
|
||||||
|
User=%i
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
|
@ -12,5 +12,8 @@ homepage: "https://github.com/cdr/code-server"
|
||||||
license: "MIT"
|
license: "MIT"
|
||||||
files:
|
files:
|
||||||
./ci/build/code-server-nfpm.sh: /usr/bin/code-server
|
./ci/build/code-server-nfpm.sh: /usr/bin/code-server
|
||||||
./ci/build/code-server.service: /usr/lib/systemd/user/code-server.service
|
./ci/build/code-server@.service: /usr/lib/systemd/system/code-server@.service
|
||||||
|
# Only included for backwards compat with previous releases that shipped
|
||||||
|
# the user service. See #1997
|
||||||
|
./ci/build/code-server-user.service: /usr/lib/systemd/user/code-server.service
|
||||||
./release-standalone/**/*: "/usr/lib/code-server/"
|
./release-standalone/**/*: "/usr/lib/code-server/"
|
||||||
|
|
|
@ -6,7 +6,7 @@ main() {
|
||||||
|
|
||||||
cd ./lib/vscode
|
cd ./lib/vscode
|
||||||
git add -A
|
git add -A
|
||||||
git diff HEAD > ../../ci/dev/vscode.patch
|
git diff HEAD --full-index > ../../ci/dev/vscode.patch
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
FROM node:12
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
curl \
|
|
||||||
iproute2 \
|
|
||||||
vim \
|
|
||||||
iptables \
|
|
||||||
net-tools \
|
|
||||||
libsecret-1-dev \
|
|
||||||
libx11-dev \
|
|
||||||
libxkbfile-dev
|
|
||||||
|
|
||||||
CMD ["/bin/bash"]
|
|
|
@ -1,48 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Opens an interactive bash session inside of a docker container
|
|
||||||
# for improved isolation during development.
|
|
||||||
# If the container exists it is restarted if necessary, then reused.
|
|
||||||
|
|
||||||
main() {
|
|
||||||
cd "$(dirname "${0}")/../../.."
|
|
||||||
|
|
||||||
local container_name=code-server-dev
|
|
||||||
|
|
||||||
if docker inspect $container_name &> /dev/null; then
|
|
||||||
echo "-- Starting container"
|
|
||||||
docker start "$container_name" > /dev/null
|
|
||||||
|
|
||||||
enter
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
build
|
|
||||||
run
|
|
||||||
enter
|
|
||||||
}
|
|
||||||
|
|
||||||
enter() {
|
|
||||||
echo "--- Entering $container_name"
|
|
||||||
docker exec -it "$container_name" /bin/bash
|
|
||||||
}
|
|
||||||
|
|
||||||
run() {
|
|
||||||
echo "--- Spawning $container_name"
|
|
||||||
docker run \
|
|
||||||
-it \
|
|
||||||
--name $container_name \
|
|
||||||
"-v=$PWD:/code-server" \
|
|
||||||
"-w=/code-server" \
|
|
||||||
"-p=127.0.0.1:8080:8080" \
|
|
||||||
$(if [[ -t 0 ]]; then echo -it; fi) \
|
|
||||||
"$container_name"
|
|
||||||
}
|
|
||||||
|
|
||||||
build() {
|
|
||||||
echo "--- Building $container_name"
|
|
||||||
docker build -t $container_name ./ci/dev/image > /dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
main() {
|
||||||
|
cd "$(dirname "$0")/../../.."
|
||||||
|
source ./ci/lib.sh
|
||||||
|
|
||||||
|
docker run \
|
||||||
|
-it \
|
||||||
|
--rm \
|
||||||
|
-v "$PWD:/src" \
|
||||||
|
-w /src \
|
||||||
|
-p 127.0.0.1:8080:8080 \
|
||||||
|
-u "$(id -u):$(id -g)" \
|
||||||
|
-e CI \
|
||||||
|
"$(docker_build ./ci/images/debian8)" \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_build() {
|
||||||
|
docker build "$@" >&2
|
||||||
|
docker build -q "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
File diff suppressed because it is too large
Load Diff
|
@ -37,6 +37,9 @@ class Watcher {
|
||||||
|
|
||||||
const vscode = cp.spawn("yarn", ["watch"], { cwd: this.vscodeSourcePath })
|
const vscode = cp.spawn("yarn", ["watch"], { 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
|
||||||
|
? cp.spawn("yarn", ["build", "--watch"], { cwd: process.env.PLUGIN_DIR })
|
||||||
|
: undefined
|
||||||
const bundler = this.createBundler()
|
const bundler = this.createBundler()
|
||||||
|
|
||||||
const cleanup = (code?: number | null): void => {
|
const cleanup = (code?: number | null): void => {
|
||||||
|
@ -48,6 +51,12 @@ class Watcher {
|
||||||
tsc.removeAllListeners()
|
tsc.removeAllListeners()
|
||||||
tsc.kill()
|
tsc.kill()
|
||||||
|
|
||||||
|
if (plugin) {
|
||||||
|
Watcher.log("killing plugin")
|
||||||
|
plugin.removeAllListeners()
|
||||||
|
plugin.kill()
|
||||||
|
}
|
||||||
|
|
||||||
if (server) {
|
if (server) {
|
||||||
Watcher.log("killing server")
|
Watcher.log("killing server")
|
||||||
server.removeAllListeners()
|
server.removeAllListeners()
|
||||||
|
@ -69,6 +78,12 @@ class Watcher {
|
||||||
Watcher.log("tsc terminated unexpectedly")
|
Watcher.log("tsc terminated unexpectedly")
|
||||||
cleanup(code)
|
cleanup(code)
|
||||||
})
|
})
|
||||||
|
if (plugin) {
|
||||||
|
plugin.on("exit", (code) => {
|
||||||
|
Watcher.log("plugin terminated unexpectedly")
|
||||||
|
cleanup(code)
|
||||||
|
})
|
||||||
|
}
|
||||||
const bundle = bundler.bundle().catch(() => {
|
const bundle = bundler.bundle().catch(() => {
|
||||||
Watcher.log("parcel watcher terminated unexpectedly")
|
Watcher.log("parcel watcher terminated unexpectedly")
|
||||||
cleanup(1)
|
cleanup(1)
|
||||||
|
@ -82,6 +97,9 @@ class Watcher {
|
||||||
|
|
||||||
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) {
|
||||||
|
plugin.stderr.on("data", (d) => process.stderr.write(d))
|
||||||
|
}
|
||||||
|
|
||||||
// From https://github.com/chalk/ansi-regex
|
// From https://github.com/chalk/ansi-regex
|
||||||
const pattern = [
|
const pattern = [
|
||||||
|
@ -140,17 +158,34 @@ class Watcher {
|
||||||
bundle.then(restartServer)
|
bundle.then(restartServer)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (plugin) {
|
||||||
|
onLine(plugin, (line, original) => {
|
||||||
|
// tsc outputs blank lines; skip them.
|
||||||
|
if (line !== "") {
|
||||||
|
console.log("[plugin]", original)
|
||||||
|
}
|
||||||
|
if (line.includes("Watching for file changes")) {
|
||||||
|
bundle.then(restartServer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createBundler(out = "dist"): Bundler {
|
private createBundler(out = "dist"): Bundler {
|
||||||
return new Bundler(
|
return new Bundler(
|
||||||
[path.join(this.rootPath, "src/browser/register.ts"), path.join(this.rootPath, "src/browser/serviceWorker.ts")],
|
[
|
||||||
|
path.join(this.rootPath, "src/browser/register.ts"),
|
||||||
|
path.join(this.rootPath, "src/browser/serviceWorker.ts"),
|
||||||
|
path.join(this.rootPath, "src/browser/pages/login.ts"),
|
||||||
|
path.join(this.rootPath, "src/browser/pages/vscode.ts"),
|
||||||
|
],
|
||||||
{
|
{
|
||||||
outDir: path.join(this.rootPath, out),
|
outDir: path.join(this.rootPath, out),
|
||||||
cacheDir: path.join(this.rootPath, ".cache"),
|
cacheDir: path.join(this.rootPath, ".cache"),
|
||||||
minify: !!process.env.MINIFY,
|
minify: !!process.env.MINIFY,
|
||||||
logLevel: 1,
|
logLevel: 1,
|
||||||
publicUrl: "/static/development/dist",
|
publicUrl: ".",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
FROM centos:7
|
FROM centos:7
|
||||||
|
|
||||||
|
ARG NODE_VERSION=v12.18.3
|
||||||
RUN ARCH="$(uname -m | sed 's/86_64/64/; s/aarch64/arm64/')" && \
|
RUN ARCH="$(uname -m | sed 's/86_64/64/; s/aarch64/arm64/')" && \
|
||||||
curl -fsSL "https://nodejs.org/dist/v14.4.0/node-v14.4.0-linux-$ARCH.tar.xz" | tar -C /usr/local -xJ && \
|
curl -fsSL "https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-linux-$ARCH.tar.xz" | tar -C /usr/local -xJ && \
|
||||||
mv /usr/local/node-v14.4.0-linux-$ARCH /usr/local/node-v14.4.0
|
mv "/usr/local/node-$NODE_VERSION-linux-$ARCH" "/usr/local/node-$NODE_VERSION"
|
||||||
ENV PATH=/usr/local/node-v14.4.0/bin:$PATH
|
ENV PATH=/usr/local/node-$NODE_VERSION/bin:$PATH
|
||||||
RUN npm install -g yarn
|
RUN npm install -g yarn
|
||||||
|
|
||||||
RUN yum groupinstall -y 'Development Tools'
|
RUN yum groupinstall -y 'Development Tools'
|
||||||
|
|
|
@ -6,7 +6,7 @@ RUN apt-get update
|
||||||
RUN apt-get install -y curl gnupg
|
RUN apt-get install -y curl gnupg
|
||||||
|
|
||||||
# Installs node.
|
# Installs node.
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash - && \
|
RUN curl -fsSL https://deb.nodesource.com/setup_12.x | bash - && \
|
||||||
apt-get install -y nodejs
|
apt-get install -y nodejs
|
||||||
|
|
||||||
# Installs yarn.
|
# Installs yarn.
|
||||||
|
|
|
@ -35,9 +35,13 @@ RUN ARCH="$(dpkg --print-architecture)" && \
|
||||||
printf "user: coder\ngroup: coder\n" > /etc/fixuid/config.yml
|
printf "user: coder\ngroup: coder\n" > /etc/fixuid/config.yml
|
||||||
|
|
||||||
COPY release-packages/code-server*.deb /tmp/
|
COPY release-packages/code-server*.deb /tmp/
|
||||||
|
COPY ci/release-image/entrypoint.sh /usr/bin/entrypoint.sh
|
||||||
RUN dpkg -i /tmp/code-server*$(dpkg --print-architecture).deb && rm /tmp/code-server*.deb
|
RUN dpkg -i /tmp/code-server*$(dpkg --print-architecture).deb && rm /tmp/code-server*.deb
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
USER coder
|
# This way, if someone sets $DOCKER_USER, docker-exec will still work as
|
||||||
|
# the uid will remain the same. note: only relevant if -u isn't passed to
|
||||||
|
# docker-run.
|
||||||
|
USER 1000
|
||||||
WORKDIR /home/coder
|
WORKDIR /home/coder
|
||||||
ENTRYPOINT ["dumb-init", "fixuid", "-q", "/usr/bin/code-server", "--bind-addr", "0.0.0.0:8080", "."]
|
ENTRYPOINT ["/usr/bin/entrypoint.sh", "--bind-addr", "0.0.0.0:8080", "."]
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# This isn't set by default.
|
||||||
|
export USER="$(whoami)"
|
||||||
|
|
||||||
|
if [ "${DOCKER_USER-}" != "$USER" ]; then
|
||||||
|
echo "$DOCKER_USER ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers.d/nopasswd > /dev/null
|
||||||
|
# Unfortunately we cannot change $HOME as we cannot move any bind mounts
|
||||||
|
# nor can we bind mount $HOME into a new home as that requires a privileged container.
|
||||||
|
sudo usermod --login "$DOCKER_USER" coder
|
||||||
|
sudo groupmod -n "$DOCKER_USER" coder
|
||||||
|
|
||||||
|
export USER="$(whoami)"
|
||||||
|
|
||||||
|
sudo sed -i "/coder/d" /etc/sudoers.d/nopasswd
|
||||||
|
sudo sed -i "s/coder/$DOCKER_USER/g" /etc/fixuid/config.yml
|
||||||
|
fi
|
||||||
|
|
||||||
|
dumb-init fixuid -q /usr/bin/code-server "$@"
|
|
@ -4,10 +4,11 @@ set -euo pipefail
|
||||||
main() {
|
main() {
|
||||||
cd "$(dirname "$0")/../.."
|
cd "$(dirname "$0")/../.."
|
||||||
|
|
||||||
if [[ $OSTYPE == darwin* ]]; then
|
NODE_VERSION=v12.18.3
|
||||||
curl -L https://nodejs.org/dist/v14.4.0/node-v14.4.0-darwin-x64.tar.gz | tar -xz
|
NODE_OS="$(uname | tr '[:upper:]' '[:lower:]')"
|
||||||
PATH="$PWD/node-v14.4.0-darwin-x64/bin:$PATH"
|
NODE_ARCH="$(uname -m | sed 's/86_64/64/; s/aarch64/arm64/')"
|
||||||
fi
|
curl -L "https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-$NODE_OS-$NODE_ARCH.tar.gz" | tar -xz
|
||||||
|
PATH="$PWD/node-$NODE_VERSION-$NODE_OS-$NODE_ARCH/bin:$PATH"
|
||||||
|
|
||||||
# https://github.com/actions/upload-artifact/issues/38
|
# https://github.com/actions/upload-artifact/issues/38
|
||||||
tar -xzf release-npm-package/package.tar.gz
|
tar -xzf release-npm-package/package.tar.gz
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
- [Pull Requests](#pull-requests)
|
||||||
- [Requirements](#requirements)
|
- [Requirements](#requirements)
|
||||||
- [Development Workflow](#development-workflow)
|
- [Development Workflow](#development-workflow)
|
||||||
- [Build](#build)
|
- [Build](#build)
|
||||||
|
@ -12,6 +13,16 @@
|
||||||
|
|
||||||
- [Detailed CI and build process docs](../ci)
|
- [Detailed CI and build process docs](../ci)
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
Please link to the issue each PR solves.
|
||||||
|
If there is no existing issue, please first create one unless the fix is minor.
|
||||||
|
|
||||||
|
Please make sure the base of your PR is the master branch. We keep the GitHub
|
||||||
|
default branch the latest release branch to avoid confusion as the
|
||||||
|
documentation is on GitHub and we don't want users to see docs on unreleased
|
||||||
|
features.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
Please refer to [VS Code's prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites).
|
Please refer to [VS Code's prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites).
|
||||||
|
@ -35,40 +46,57 @@ yarn watch
|
||||||
To develop inside of an isolated docker container:
|
To develop inside of an isolated docker container:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./ci/dev/image/exec.sh
|
./ci/dev/image/run.sh yarn
|
||||||
|
./ci/dev/image/run.sh yarn vscode
|
||||||
root@12345:/code-server# yarn
|
./ci/dev/image/run.sh yarn watch
|
||||||
root@12345:/code-server# yarn vscode
|
|
||||||
root@12345:/code-server# yarn watch
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Any changes made to the source will be live reloaded.
|
`yarn watch` will live reload changes to the source.
|
||||||
|
|
||||||
If changes are made to the patch and you've built previously you must manually
|
If changes are made to the patch and you've built previously you must manually
|
||||||
reset VS Code then run `yarn vscode:patch`.
|
reset VS Code then run `yarn vscode:patch`.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
|
You can build with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./ci/dev/image/run.sh ./ci/steps/release.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Run your build with:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd release
|
||||||
|
yarn --production
|
||||||
|
# Runs the built JavaScript with Node.
|
||||||
|
node .
|
||||||
|
```
|
||||||
|
|
||||||
|
Build release packages (make sure you run `./ci/steps/release.sh` first):
|
||||||
|
|
||||||
|
```
|
||||||
|
./ci/dev/image/run.sh ./ci/steps/release-packages.sh
|
||||||
|
# The standalone release is in ./release-standalone
|
||||||
|
# .deb, .rpm and the standalone archive are in ./release-packages
|
||||||
|
```
|
||||||
|
|
||||||
|
The `release.sh` script is the equivalent of:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
yarn
|
yarn
|
||||||
yarn vscode
|
yarn vscode
|
||||||
yarn build
|
yarn build
|
||||||
yarn build:vscode
|
yarn build:vscode
|
||||||
yarn release
|
yarn release
|
||||||
cd release
|
|
||||||
yarn --production
|
|
||||||
# Runs the built JavaScript with Node.
|
|
||||||
node .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can build release packages with:
|
And `release-packages.sh` is:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn release:standalone
|
yarn release:standalone
|
||||||
# The standalone release is in ./release-standalone
|
|
||||||
yarn test:standalone-release
|
yarn test:standalone-release
|
||||||
yarn package
|
yarn package
|
||||||
# .deb, .rpm and the standalone archive are in ./release-packages
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
19
doc/FAQ.md
19
doc/FAQ.md
|
@ -19,6 +19,7 @@
|
||||||
- [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open)
|
- [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open)
|
||||||
- [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server)
|
- [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server)
|
||||||
- [Heartbeat File](#heartbeat-file)
|
- [Heartbeat File](#heartbeat-file)
|
||||||
|
- [Healthz endpoint](#healthz-endpoint)
|
||||||
- [How does the config file work?](#how-does-the-config-file-work)
|
- [How does the config file work?](#how-does-the-config-file-work)
|
||||||
- [Blank screen on iPad?](#blank-screen-on-ipad)
|
- [Blank screen on iPad?](#blank-screen-on-ipad)
|
||||||
- [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure)
|
- [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure)
|
||||||
|
@ -30,9 +31,7 @@
|
||||||
|
|
||||||
## Questions?
|
## Questions?
|
||||||
|
|
||||||
Please file all questions and support requests at https://www.reddit.com/r/codeserver/.
|
Please file all questions and support requests at https://github.com/cdr/code-server/discussions.
|
||||||
|
|
||||||
The issue tracker is **only** for bugs and features.
|
|
||||||
|
|
||||||
## How can I reuse my VS Code configuration?
|
## How can I reuse my VS Code configuration?
|
||||||
|
|
||||||
|
@ -244,6 +243,20 @@ older than X minutes, kill `code-server`.
|
||||||
|
|
||||||
[#1636](https://github.com/cdr/code-server/issues/1636) will make the experience here better.
|
[#1636](https://github.com/cdr/code-server/issues/1636) will make the experience here better.
|
||||||
|
|
||||||
|
## Healthz endpoint
|
||||||
|
|
||||||
|
`code-server` exposes an endpoint at `/healthz` which can be used to check
|
||||||
|
whether `code-server` is up without triggering a heartbeat. The response will
|
||||||
|
include a status (`alive` or `expired`) and a timestamp for the last heartbeat
|
||||||
|
(defaults to `0`). This endpoint does not require authentication.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "alive",
|
||||||
|
"lastHeartbeat": 1599166210566
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## How does the config file work?
|
## How does the config file work?
|
||||||
|
|
||||||
When `code-server` starts up, it creates a default config file in `~/.config/code-server/config.yaml` that looks
|
When `code-server` starts up, it creates a default config file in `~/.config/code-server/config.yaml` that looks
|
||||||
|
|
10
doc/guide.md
10
doc/guide.md
|
@ -131,16 +131,16 @@ sed -i.bak 's/auth: password/auth: none/' ~/.config/code-server/config.yaml
|
||||||
Restart `code-server` with (assuming you followed the guide):
|
Restart `code-server` with (assuming you followed the guide):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
systemctl --user restart code-server
|
sudo systemctl restart code-server@$USER
|
||||||
```
|
```
|
||||||
|
|
||||||
Now forward local port 8080 to `127.0.0.1:8080` on the remote instance.
|
Now forward local port 8080 to `127.0.0.1:8080` on the remote instance by running the following command on your local machine.
|
||||||
|
|
||||||
Recommended reading: https://help.ubuntu.com/community/SSH/OpenSSH/PortForwarding.
|
Recommended reading: https://help.ubuntu.com/community/SSH/OpenSSH/PortForwarding.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# -N disables executing a remote shell
|
# -N disables executing a remote shell
|
||||||
ssh -N -L 8080:127.0.0.1:8080 <instance-ip>
|
ssh -N -L 8080:127.0.0.1:8080 [user]@<instance-ip>
|
||||||
```
|
```
|
||||||
|
|
||||||
Now if you access http://127.0.0.1:8080 locally, you should see `code-server`!
|
Now if you access http://127.0.0.1:8080 locally, you should see `code-server`!
|
||||||
|
@ -277,7 +277,7 @@ sudo setcap cap_net_bind_service=+ep /usr/lib/code-server/lib/node
|
||||||
Assuming you have been following the guide, restart `code-server` with:
|
Assuming you have been following the guide, restart `code-server` with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
systemctl --user restart code-server
|
sudo systemctl restart code-server@$USER
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit your instance and checkmark the allow HTTPS traffic option.
|
Edit your instance and checkmark the allow HTTPS traffic option.
|
||||||
|
@ -295,7 +295,7 @@ Edit the `password` field in the `code-server` config file at `~/.config/code-se
|
||||||
and then restart `code-server` with:
|
and then restart `code-server` with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
systemctl --user restart code-server
|
sudo systemctl restart code-server@$USER
|
||||||
```
|
```
|
||||||
|
|
||||||
### How do I securely access development web services?
|
### How do I securely access development web services?
|
||||||
|
|
|
@ -79,18 +79,18 @@ commands presented in the rest of this document.
|
||||||
## Debian, Ubuntu
|
## Debian, Ubuntu
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fOL https://github.com/cdr/code-server/releases/download/v3.4.1/code-server_3.4.1_amd64.deb
|
curl -fOL https://github.com/cdr/code-server/releases/download/v3.5.0/code-server_3.5.0_amd64.deb
|
||||||
sudo dpkg -i code-server_3.4.1_amd64.deb
|
sudo dpkg -i code-server_3.5.0_amd64.deb
|
||||||
systemctl --user enable --now code-server
|
sudo systemctl enable --now code-server@$USER
|
||||||
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fedora, CentOS, RHEL, SUSE
|
## Fedora, CentOS, RHEL, SUSE
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fOL https://github.com/cdr/code-server/releases/download/v3.4.1/code-server-3.4.1-amd64.rpm
|
curl -fOL https://github.com/cdr/code-server/releases/download/v3.5.0/code-server-3.5.0-amd64.rpm
|
||||||
sudo rpm -i code-server-3.4.1-amd64.rpm
|
sudo rpm -i code-server-3.5.0-amd64.rpm
|
||||||
systemctl --user enable --now code-server
|
sudo systemctl enable --now code-server@$USER
|
||||||
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ systemctl --user enable --now code-server
|
||||||
```bash
|
```bash
|
||||||
# Installs code-server from the AUR using yay.
|
# Installs code-server from the AUR using yay.
|
||||||
yay -S code-server
|
yay -S code-server
|
||||||
systemctl --user enable --now code-server
|
sudo systemctl enable --now code-server@$USER
|
||||||
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ systemctl --user enable --now code-server
|
||||||
git clone https://aur.archlinux.org/code-server.git
|
git clone https://aur.archlinux.org/code-server.git
|
||||||
cd code-server
|
cd code-server
|
||||||
makepkg -si
|
makepkg -si
|
||||||
systemctl --user enable --now code-server
|
sudo systemctl enable --now code-server@$USER
|
||||||
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -158,10 +158,10 @@ Here is an example script for installing and using a standalone `code-server` re
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/.local/lib ~/.local/bin
|
mkdir -p ~/.local/lib ~/.local/bin
|
||||||
curl -fL https://github.com/cdr/code-server/releases/download/v3.4.1/code-server-3.4.1-linux-amd64.tar.gz \
|
curl -fL https://github.com/cdr/code-server/releases/download/v3.5.0/code-server-3.5.0-linux-amd64.tar.gz \
|
||||||
| tar -C ~/.local/lib -xz
|
| tar -C ~/.local/lib -xz
|
||||||
mv ~/.local/lib/code-server-3.4.1-linux-amd64 ~/.local/lib/code-server-3.4.1
|
mv ~/.local/lib/code-server-3.5.0-linux-amd64 ~/.local/lib/code-server-3.5.0
|
||||||
ln -s ~/.local/lib/code-server-3.4.1/bin/code-server ~/.local/bin/code-server
|
ln -s ~/.local/lib/code-server-3.5.0/bin/code-server ~/.local/bin/code-server
|
||||||
PATH="~/.local/bin:$PATH"
|
PATH="~/.local/bin:$PATH"
|
||||||
code-server
|
code-server
|
||||||
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
||||||
|
@ -174,9 +174,16 @@ code-server
|
||||||
# It will also mount your current directory into the container as `/home/coder/project`
|
# It will also mount your current directory into the container as `/home/coder/project`
|
||||||
# and forward your UID/GID so that all file system operations occur as your user outside
|
# and forward your UID/GID so that all file system operations occur as your user outside
|
||||||
# the container.
|
# the container.
|
||||||
docker run -it -p 127.0.0.1:8080:8080 \
|
#
|
||||||
|
# Your $HOME/.config is mounted at $HOME/.config within the container to ensure you can
|
||||||
|
# easily access/modify your code-server config in $HOME/.config/code-server/config.json
|
||||||
|
# outside the container.
|
||||||
|
mkdir -p ~/.config
|
||||||
|
docker run -it --name code-server -p 127.0.0.1:8080:8080 \
|
||||||
|
-v "$HOME/.config:/home/coder/.config" \
|
||||||
-v "$PWD:/home/coder/project" \
|
-v "$PWD:/home/coder/project" \
|
||||||
-u "$(id -u):$(id -g)" \
|
-u "$(id -u):$(id -g)" \
|
||||||
|
-e "DOCKER_USER=$USER" \
|
||||||
codercom/code-server:latest
|
codercom/code-server:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,9 @@ sudo apt-get install -y \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
libx11-dev \
|
libx11-dev \
|
||||||
libxkbfile-dev \
|
libxkbfile-dev \
|
||||||
libsecret-1-dev
|
libsecret-1-dev \
|
||||||
|
python3
|
||||||
|
npm config set python python3
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fedora, CentOS, RHEL
|
## Fedora, CentOS, RHEL
|
||||||
|
|
|
@ -84,7 +84,7 @@ echo_systemd_postinstall() {
|
||||||
echoh
|
echoh
|
||||||
cath << EOF
|
cath << EOF
|
||||||
To have systemd start code-server now and restart on boot:
|
To have systemd start code-server now and restart on boot:
|
||||||
systemctl --user enable --now code-server
|
sudo systemctl enable --now code-server@\$USER
|
||||||
Or, if you don't want/need a background service you can run:
|
Or, if you don't want/need a background service you can run:
|
||||||
code-server
|
code-server
|
||||||
EOF
|
EOF
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 17299e413d5590b14ab0340ea477cdd86ff13daf
|
Subproject commit a0479759d6e9ea56afa657e454193f72aef85bd0
|
27
package.json
27
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "code-server",
|
"name": "code-server",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "3.4.1",
|
"version": "3.5.0",
|
||||||
"description": "Run VS Code on a remote server.",
|
"description": "Run VS Code on a remote server.",
|
||||||
"homepage": "https://github.com/cdr/code-server",
|
"homepage": "https://github.com/cdr/code-server",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -26,37 +26,37 @@
|
||||||
"lint": "./ci/dev/lint.sh",
|
"lint": "./ci/dev/lint.sh",
|
||||||
"test": "./ci/dev/test.sh",
|
"test": "./ci/dev/test.sh",
|
||||||
"ci": "./ci/dev/ci.sh",
|
"ci": "./ci/dev/ci.sh",
|
||||||
"watch": "NODE_OPTIONS=--max_old_space_size=32384 ts-node ./ci/dev/watch.ts"
|
"watch": "VSCODE_IPC_HOOK_CLI= NODE_OPTIONS=--max_old_space_size=32384 ts-node ./ci/dev/watch.ts"
|
||||||
},
|
},
|
||||||
"main": "out/node/entry.js",
|
"main": "out/node/entry.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/fs-extra": "^8.0.1",
|
"@types/fs-extra": "^8.0.1",
|
||||||
"@types/http-proxy": "^1.17.4",
|
"@types/http-proxy": "^1.17.4",
|
||||||
"@types/js-yaml": "^3.12.3",
|
"@types/js-yaml": "^3.12.3",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^8.0.3",
|
||||||
"@types/node": "^12.12.7",
|
"@types/node": "^12.12.7",
|
||||||
"@types/parcel-bundler": "^1.12.1",
|
"@types/parcel-bundler": "^1.12.1",
|
||||||
"@types/pem": "^1.9.5",
|
"@types/pem": "^1.9.5",
|
||||||
"@types/safe-compare": "^1.1.0",
|
"@types/safe-compare": "^1.1.0",
|
||||||
"@types/semver": "^7.1.0",
|
"@types/semver": "^7.1.0",
|
||||||
"@types/tar-fs": "^1.16.2",
|
"@types/tar-fs": "^2.0.0",
|
||||||
"@types/tar-stream": "^1.6.1",
|
"@types/tar-stream": "^2.1.0",
|
||||||
"@types/ws": "^6.0.4",
|
"@types/ws": "^7.2.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.0.0",
|
"@typescript-eslint/eslint-plugin": "^3.10.1",
|
||||||
"@typescript-eslint/parser": "^2.0.0",
|
"@typescript-eslint/parser": "^3.10.1",
|
||||||
"doctoc": "^1.4.0",
|
"doctoc": "^1.4.0",
|
||||||
"eslint": "^6.2.0",
|
"eslint": "^7.7.0",
|
||||||
"eslint-config-prettier": "^6.0.0",
|
"eslint-config-prettier": "^6.0.0",
|
||||||
"eslint-plugin-import": "^2.18.2",
|
"eslint-plugin-import": "^2.18.2",
|
||||||
"eslint-plugin-prettier": "^3.1.0",
|
"eslint-plugin-prettier": "^3.1.0",
|
||||||
"leaked-handles": "^5.2.0",
|
"leaked-handles": "^5.2.0",
|
||||||
"mocha": "^6.2.0",
|
"mocha": "^8.1.2",
|
||||||
"parcel-bundler": "^1.12.4",
|
"parcel-bundler": "^1.12.4",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.0.5",
|
||||||
"stylelint": "^13.0.0",
|
"stylelint": "^13.0.0",
|
||||||
"stylelint-config-recommended": "^3.0.0",
|
"stylelint-config-recommended": "^3.0.0",
|
||||||
"ts-node": "^8.4.1",
|
"ts-node": "^9.0.0",
|
||||||
"typescript": "3.7.2"
|
"typescript": "4.0.2"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/node": "^12.12.7",
|
"@types/node": "^12.12.7",
|
||||||
|
@ -66,13 +66,14 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coder/logger": "1.1.16",
|
"@coder/logger": "1.1.16",
|
||||||
"env-paths": "^2.2.0",
|
"env-paths": "^2.2.0",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^9.0.1",
|
||||||
"http-proxy": "^1.18.0",
|
"http-proxy": "^1.18.0",
|
||||||
"httpolyglot": "^0.1.2",
|
"httpolyglot": "^0.1.2",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"limiter": "^1.1.5",
|
"limiter": "^1.1.5",
|
||||||
"pem": "^1.14.2",
|
"pem": "^1.14.2",
|
||||||
"rotating-file-stream": "^2.1.1",
|
"rotating-file-stream": "^2.1.1",
|
||||||
|
"safe-buffer": "^5.1.1",
|
||||||
"safe-compare": "^1.1.4",
|
"safe-compare": "^1.1.4",
|
||||||
"semver": "^7.1.3",
|
"semver": "^7.1.3",
|
||||||
"tar": "^6.0.1",
|
"tar": "^6.0.1",
|
||||||
|
|
|
@ -7,32 +7,32 @@
|
||||||
"description": "Run editors on a remote server.",
|
"description": "Run editors on a remote server.",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-96.png",
|
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-96.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "96x96"
|
"sizes": "96x96"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-128.png",
|
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-128.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "128x128"
|
"sizes": "128x128"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-192.png",
|
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "192x192"
|
"sizes": "192x192"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-256.png",
|
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-256.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "256x256"
|
"sizes": "256x256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png",
|
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-384.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "384x384"
|
"sizes": "384x384"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-512.png",
|
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "512x512"
|
"sizes": "512x512"
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,28 +11,22 @@
|
||||||
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="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" type="image/x-icon" />
|
||||||
<link
|
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
||||||
rel="manifest"
|
<link rel="apple-touch-icon" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-384.png" />
|
||||||
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
|
<link href="{{CS_STATIC_BASE}}/dist/register.css" rel="stylesheet" />
|
||||||
crossorigin="use-credentials"
|
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png" />
|
|
||||||
<link href="{{BASE}}/static/{{COMMIT}}/dist/register.css" rel="stylesheet" />
|
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="center-container">
|
<div class="center-container">
|
||||||
<div class="error-display">
|
<div class="error-display">
|
||||||
<h2 class="header">{{ERROR_HEADER}}</h2>
|
<h2 class="header">{{ERROR_HEADER}}</h2>
|
||||||
<div class="body">
|
<div class="body">{{ERROR_BODY}}</div>
|
||||||
{{ERROR_BODY}}
|
|
||||||
</div>
|
|
||||||
<div class="links">
|
<div class="links">
|
||||||
<a class="link" href="{{BASE}}{{TO}}">go home</a>
|
<a class="link" href="{{BASE}}{{TO}}">go home</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
|
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/dist/register.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -11,14 +11,10 @@
|
||||||
content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;"
|
content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;"
|
||||||
/>
|
/>
|
||||||
<title>code-server login</title>
|
<title>code-server login</title>
|
||||||
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" type="image/x-icon" />
|
||||||
<link
|
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
||||||
rel="manifest"
|
<link rel="apple-touch-icon" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-384.png" />
|
||||||
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
|
<link href="{{CS_STATIC_BASE}}/dist/register.css" rel="stylesheet" />
|
||||||
crossorigin="use-credentials"
|
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png" />
|
|
||||||
<link href="{{BASE}}/static/{{COMMIT}}/dist/register.css" rel="stylesheet" />
|
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -50,11 +46,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
|
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/dist/register.js"></script>
|
||||||
<script>
|
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/dist/pages/login.js"></script>
|
||||||
const parts = window.location.pathname.replace(/^\//g, "").split("/")
|
|
||||||
parts[parts.length - 1] = "{{BASE}}"
|
|
||||||
const url = new URL(window.location.origin + "/" + parts.join("/"))
|
|
||||||
document.getElementById("base").value = url.pathname
|
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { getOptions } from "../../common/util"
|
||||||
|
|
||||||
|
const options = getOptions()
|
||||||
|
const el = document.getElementById("base") as HTMLInputElement
|
||||||
|
if (el) {
|
||||||
|
el.value = options.base
|
||||||
|
}
|
|
@ -2,6 +2,11 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<script>
|
||||||
|
globalThis.MonacoPerformanceMarks = globalThis.MonacoPerformanceMarks || []
|
||||||
|
globalThis.MonacoPerformanceMarks.push("renderer/started", Date.now())
|
||||||
|
</script>
|
||||||
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|
||||||
<meta
|
<meta
|
||||||
|
@ -24,21 +29,17 @@
|
||||||
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}" />
|
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}" />
|
||||||
|
|
||||||
<!-- Workbench Icon/Manifest/CSS -->
|
<!-- Workbench Icon/Manifest/CSS -->
|
||||||
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" type="image/x-icon" />
|
||||||
<link
|
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
||||||
rel="manifest"
|
|
||||||
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
|
|
||||||
crossorigin="use-credentials"
|
|
||||||
/>
|
|
||||||
<!-- PROD_ONLY
|
<!-- PROD_ONLY
|
||||||
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/workbench/workbench.web.api.css">
|
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="{{CS_STATIC_BASE}}/lib/vscode/out/vs/workbench/workbench.web.api.css">
|
||||||
END_PROD_ONLY -->
|
END_PROD_ONLY -->
|
||||||
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png" />
|
<link rel="apple-touch-icon" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-384.png" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
|
||||||
<!-- Prefetch to avoid waterfall -->
|
<!-- Prefetch to avoid waterfall -->
|
||||||
<!-- PROD_ONLY
|
<!-- PROD_ONLY
|
||||||
<link rel="prefetch" href="{{BASE}}/static/{{COMMIT}}/lib/vscode/node_modules/semver-umd/lib/semver-umd.js">
|
<link rel="prefetch" href="{{CS_STATIC_BASE}}/lib/vscode/node_modules/semver-umd/lib/semver-umd.js">
|
||||||
END_PROD_ONLY -->
|
END_PROD_ONLY -->
|
||||||
|
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
|
@ -47,65 +48,17 @@
|
||||||
<body aria-label=""></body>
|
<body aria-label=""></body>
|
||||||
|
|
||||||
<!-- Startup (do not modify order of script tags!) -->
|
<!-- Startup (do not modify order of script tags!) -->
|
||||||
|
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/dist/pages/vscode.js"></script>
|
||||||
|
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/dist/register.js"></script>
|
||||||
|
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/lib/vscode/out/vs/loader.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const parts = window.location.pathname.replace(/^\//g, "").split("/")
|
globalThis.MonacoPerformanceMarks.push("willLoadWorkbenchMain", Date.now())
|
||||||
parts[parts.length - 1] = "{{BASE}}"
|
|
||||||
const url = new URL(window.location.origin + "/" + parts.join("/"))
|
|
||||||
const staticBase = url.href.replace(/\/+$/, "") + "/static/{{COMMIT}}/lib/vscode"
|
|
||||||
let nlsConfig
|
|
||||||
try {
|
|
||||||
nlsConfig = JSON.parse(document.getElementById("vscode-remote-nls-configuration").getAttribute("data-settings"))
|
|
||||||
if (nlsConfig._resolvedLanguagePackCoreLocation) {
|
|
||||||
const bundles = Object.create(null)
|
|
||||||
nlsConfig.loadBundle = (bundle, language, cb) => {
|
|
||||||
let result = bundles[bundle]
|
|
||||||
if (result) {
|
|
||||||
return cb(undefined, result)
|
|
||||||
}
|
|
||||||
// FIXME: Only works if path separators are /.
|
|
||||||
const path = nlsConfig._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json"
|
|
||||||
fetch(`${url.href}/resource/?path=${encodeURIComponent(path)}`)
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((json) => {
|
|
||||||
bundles[bundle] = json
|
|
||||||
cb(undefined, json)
|
|
||||||
})
|
|
||||||
.catch(cb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
/* Probably fine. */
|
|
||||||
}
|
|
||||||
self.require = {
|
|
||||||
baseUrl: `${staticBase}/out`,
|
|
||||||
paths: {
|
|
||||||
"vscode-textmate": `${staticBase}/node_modules/vscode-textmate/release/main`,
|
|
||||||
"vscode-oniguruma": `${staticBase}/node_modules/vscode-oniguruma/release/main`,
|
|
||||||
xterm: `${staticBase}/node_modules/xterm/lib/xterm.js`,
|
|
||||||
"xterm-addon-search": `${staticBase}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
|
||||||
"xterm-addon-unicode11": `${staticBase}/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
|
|
||||||
"xterm-addon-webgl": `${staticBase}/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
|
|
||||||
"semver-umd": `${staticBase}/node_modules/semver-umd/lib/semver-umd.js`,
|
|
||||||
"iconv-lite-umd": `${staticBase}/node_modules/iconv-lite-umd/lib/iconv-lite-umd.js`,
|
|
||||||
jschardet: `${staticBase}/node_modules/jschardet/dist/jschardet.min.js`,
|
|
||||||
},
|
|
||||||
"vs/nls": nlsConfig,
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
|
|
||||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/loader.js"></script>
|
|
||||||
<!-- PROD_ONLY
|
<!-- PROD_ONLY
|
||||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/workbench/workbench.web.api.nls.js"></script>
|
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/lib/vscode/out/vs/workbench/workbench.web.api.nls.js"></script>
|
||||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/workbench/workbench.web.api.js"></script>
|
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/lib/vscode/out/vs/workbench/workbench.web.api.js"></script>
|
||||||
END_PROD_ONLY -->
|
END_PROD_ONLY -->
|
||||||
<script>
|
<script>
|
||||||
require(["vs/code/browser/workbench/workbench"], function () {})
|
require(["vs/code/browser/workbench/workbench"], function () {})
|
||||||
</script>
|
</script>
|
||||||
<script>
|
|
||||||
try {
|
|
||||||
document.body.style.background = JSON.parse(localStorage.getItem("colorThemeData")).colorMap["editor.background"]
|
|
||||||
} catch (error) {
|
|
||||||
// Oh well.
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { getOptions } from "../../common/util"
|
||||||
|
|
||||||
|
const options = getOptions()
|
||||||
|
|
||||||
|
// TODO: Add proper types.
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
let nlsConfig: any
|
||||||
|
try {
|
||||||
|
nlsConfig = JSON.parse(document.getElementById("vscode-remote-nls-configuration")!.getAttribute("data-settings")!)
|
||||||
|
if (nlsConfig._resolvedLanguagePackCoreLocation) {
|
||||||
|
const bundles = Object.create(null)
|
||||||
|
nlsConfig.loadBundle = (bundle: any, _language: any, cb: any): void => {
|
||||||
|
const result = bundles[bundle]
|
||||||
|
if (result) {
|
||||||
|
return cb(undefined, result)
|
||||||
|
}
|
||||||
|
// FIXME: Only works if path separators are /.
|
||||||
|
const path = nlsConfig._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json"
|
||||||
|
fetch(`${options.base}/vscode/resource/?path=${encodeURIComponent(path)}`)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((json) => {
|
||||||
|
bundles[bundle] = json
|
||||||
|
cb(undefined, json)
|
||||||
|
})
|
||||||
|
.catch(cb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
/* Probably fine. */
|
||||||
|
}
|
||||||
|
|
||||||
|
;(self.require as any) = {
|
||||||
|
baseUrl: `${options.csStaticBase}/lib/vscode/out`,
|
||||||
|
recordStats: true,
|
||||||
|
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`,
|
||||||
|
"semver-umd": `../node_modules/semver-umd/lib/semver-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,
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.body.style.background = JSON.parse(localStorage.getItem("colorThemeData")!).colorMap["editor.background"]
|
||||||
|
} catch (error) {
|
||||||
|
// Oh well.
|
||||||
|
}
|
|
@ -7,10 +7,10 @@ import "./pages/global.css"
|
||||||
import "./pages/login.css"
|
import "./pages/login.css"
|
||||||
|
|
||||||
if ("serviceWorker" in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
const path = normalize(`${options.base}/static/${options.commit}/dist/serviceWorker.js`)
|
const path = normalize(`${options.csStaticBase}/dist/serviceWorker.js`)
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register(path, {
|
.register(path, {
|
||||||
scope: options.base || "/",
|
scope: (options.base ?? "") + "/",
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("[Service Worker] registered")
|
console.log("[Service Worker] registered")
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
|
@ -8,17 +8,6 @@ self.addEventListener("activate", (event: any) => {
|
||||||
event.waitUntil((self as any).clients.claim())
|
event.waitUntil((self as any).clients.claim())
|
||||||
})
|
})
|
||||||
|
|
||||||
self.addEventListener("fetch", (event: any) => {
|
self.addEventListener("fetch", () => {
|
||||||
if (!navigator.onLine) {
|
// Without this event handler we won't be recognized as a PWA.
|
||||||
event.respondWith(
|
|
||||||
new Promise((resolve) => {
|
|
||||||
resolve(
|
|
||||||
new Response("OFFLINE", {
|
|
||||||
status: 200,
|
|
||||||
statusText: "OK",
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,9 +2,8 @@ import { logger, field } from "@coder/logger"
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
base: string
|
base: string
|
||||||
commit: string
|
csStaticBase: string
|
||||||
logLevel: number
|
logLevel: number
|
||||||
pid?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,21 +43,28 @@ export const trimSlashes = (url: string): string => {
|
||||||
return url.replace(/^\/+|\/+$/g, "")
|
return url.replace(/^\/+|\/+$/g, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a relative base against the window location. This is used for
|
||||||
|
* anything that doesn't work with a relative path.
|
||||||
|
*/
|
||||||
|
export const resolveBase = (base?: string): string => {
|
||||||
|
// After resolving the base will either start with / or be an empty string.
|
||||||
|
if (!base || base.startsWith("/")) {
|
||||||
|
return base ?? ""
|
||||||
|
}
|
||||||
|
const parts = location.pathname.split("/")
|
||||||
|
parts[parts.length - 1] = base
|
||||||
|
const url = new URL(location.origin + "/" + parts.join("/"))
|
||||||
|
return normalize(url.pathname)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get options embedded in the HTML or query params.
|
* Get options embedded in the HTML or query params.
|
||||||
*/
|
*/
|
||||||
export const getOptions = <T extends Options>(): T => {
|
export const getOptions = <T extends Options>(): T => {
|
||||||
let options: T
|
let options: T
|
||||||
try {
|
try {
|
||||||
const el = document.getElementById("coder-options")
|
options = JSON.parse(document.getElementById("coder-options")!.getAttribute("data-settings")!)
|
||||||
if (!el) {
|
|
||||||
throw new Error("no options element")
|
|
||||||
}
|
|
||||||
const value = el.getAttribute("data-settings")
|
|
||||||
if (!value) {
|
|
||||||
throw new Error("no options value")
|
|
||||||
}
|
|
||||||
options = JSON.parse(value)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
options = {} as T
|
options = {} as T
|
||||||
}
|
}
|
||||||
|
@ -72,15 +78,10 @@ export const getOptions = <T extends Options>(): T => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof options.logLevel !== "undefined") {
|
|
||||||
logger.level = options.logLevel
|
logger.level = options.logLevel
|
||||||
}
|
|
||||||
if (options.base) {
|
options.base = resolveBase(options.base)
|
||||||
const parts = location.pathname.replace(/^\//g, "").split("/")
|
options.csStaticBase = resolveBase(options.csStaticBase)
|
||||||
parts[parts.length - 1] = options.base
|
|
||||||
const url = new URL(location.origin + "/" + parts.join("/"))
|
|
||||||
options.base = normalize(url.pathname, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("got options", field("options", options))
|
logger.debug("got options", field("options", options))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { HttpProvider, HttpResponse, Heart, HttpProviderOptions } from "../http"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the heartbeat.
|
||||||
|
*/
|
||||||
|
export class HealthHttpProvider extends HttpProvider {
|
||||||
|
public constructor(options: HttpProviderOptions, private readonly heart: Heart) {
|
||||||
|
super(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handleRequest(): Promise<HttpResponse> {
|
||||||
|
return {
|
||||||
|
cache: false,
|
||||||
|
mime: "application/json",
|
||||||
|
content: {
|
||||||
|
status: this.heart.alive() ? "alive" : "expired",
|
||||||
|
lastHeartbeat: this.heart.lastHeartbeat,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,10 +8,9 @@ import { HttpProvider, HttpResponse, Route } from "../http"
|
||||||
import { pathToFsPath } from "../util"
|
import { pathToFsPath } from "../util"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static file HTTP provider. Regular static requests (the path is the request
|
* Static file HTTP provider. Static requests do not require authentication if
|
||||||
* itself) do not require authentication and they only allow access to resources
|
* the resource is in the application's directory except requests to serve a
|
||||||
* within the application. Requests for tars (the path is in a query parameter)
|
* directory as a tar which always requires authentication.
|
||||||
* do require permissions and can access any directory.
|
|
||||||
*/
|
*/
|
||||||
export class StaticHttpProvider extends HttpProvider {
|
export class StaticHttpProvider extends HttpProvider {
|
||||||
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
||||||
|
@ -22,7 +21,7 @@ export class StaticHttpProvider extends HttpProvider {
|
||||||
return this.getTarredResource(request, pathToFsPath(route.query.tar))
|
return this.getTarredResource(request, pathToFsPath(route.query.tar))
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.getReplacedResource(route)
|
const response = await this.getReplacedResource(request, route)
|
||||||
if (!this.isDev) {
|
if (!this.isDev) {
|
||||||
response.cache = true
|
response.cache = true
|
||||||
}
|
}
|
||||||
|
@ -32,17 +31,25 @@ export class StaticHttpProvider extends HttpProvider {
|
||||||
/**
|
/**
|
||||||
* Return a resource with variables replaced where necessary.
|
* Return a resource with variables replaced where necessary.
|
||||||
*/
|
*/
|
||||||
protected async getReplacedResource(route: Route): Promise<HttpResponse> {
|
protected async getReplacedResource(request: http.IncomingMessage, route: Route): Promise<HttpResponse> {
|
||||||
// The first part is always the commit (for caching purposes).
|
// The first part is always the commit (for caching purposes).
|
||||||
const split = route.requestPath.split("/").slice(1)
|
const split = route.requestPath.split("/").slice(1)
|
||||||
|
|
||||||
|
const resourcePath = path.resolve("/", ...split)
|
||||||
|
|
||||||
|
// Make sure it's in code-server or a plugin.
|
||||||
|
const validPaths = [this.rootPath, process.env.PLUGIN_DIR]
|
||||||
|
if (!validPaths.find((p) => p && resourcePath.startsWith(p))) {
|
||||||
|
this.ensureAuthenticated(request)
|
||||||
|
}
|
||||||
|
|
||||||
switch (split[split.length - 1]) {
|
switch (split[split.length - 1]) {
|
||||||
case "manifest.json": {
|
case "manifest.json": {
|
||||||
const response = await this.getUtf8Resource(this.rootPath, ...split)
|
const response = await this.getUtf8Resource(resourcePath)
|
||||||
return this.replaceTemplates(route, response)
|
return this.replaceTemplates(route, response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.getResource(this.rootPath, ...split)
|
return this.getResource(resourcePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -200,8 +200,6 @@ export class VscodeHttpProvider extends HttpProvider {
|
||||||
.replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`)
|
.replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`)
|
||||||
.replace(`"{{NLS_CONFIGURATION}}"`, `'${JSON.stringify(options.nlsConfiguration)}'`)
|
.replace(`"{{NLS_CONFIGURATION}}"`, `'${JSON.stringify(options.nlsConfiguration)}'`)
|
||||||
return this.replaceTemplates<Options>(route, response, {
|
return this.replaceTemplates<Options>(route, response, {
|
||||||
base: this.base(route),
|
|
||||||
commit: this.options.commit,
|
|
||||||
disableTelemetry: !!this.args["disable-telemetry"],
|
disableTelemetry: !!this.args["disable-telemetry"],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,8 @@ export interface Args extends VsArgs {
|
||||||
readonly "proxy-domain"?: string[]
|
readonly "proxy-domain"?: string[]
|
||||||
readonly locale?: string
|
readonly locale?: string
|
||||||
readonly _: string[]
|
readonly _: string[]
|
||||||
|
readonly "reuse-window"?: boolean
|
||||||
|
readonly "new-window"?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Option<T> {
|
interface Option<T> {
|
||||||
|
@ -125,11 +127,31 @@ const options: Options<Required<Args>> = {
|
||||||
"extra-builtin-extensions-dir": { type: "string[]", path: true },
|
"extra-builtin-extensions-dir": { type: "string[]", path: true },
|
||||||
"list-extensions": { type: "boolean", description: "List installed VS Code extensions." },
|
"list-extensions": { type: "boolean", description: "List installed VS Code extensions." },
|
||||||
force: { type: "boolean", description: "Avoid prompts when installing VS Code extensions." },
|
force: { type: "boolean", description: "Avoid prompts when installing VS Code extensions." },
|
||||||
"install-extension": { type: "string[]", description: "Install or update a VS Code extension by id or vsix." },
|
"install-extension": {
|
||||||
|
type: "string[]",
|
||||||
|
description:
|
||||||
|
"Install or update a VS Code extension by id or vsix. The identifier of an extension is `${publisher}.${name}`. To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.",
|
||||||
|
},
|
||||||
|
"enable-proposed-api": {
|
||||||
|
type: "string[]",
|
||||||
|
description:
|
||||||
|
"Enable proposed API features for extensions. Can receive one or more extension IDs to enable individually.",
|
||||||
|
},
|
||||||
"uninstall-extension": { type: "string[]", description: "Uninstall a VS Code extension by id." },
|
"uninstall-extension": { type: "string[]", description: "Uninstall a VS Code extension by id." },
|
||||||
"show-versions": { type: "boolean", description: "Show VS Code extension versions." },
|
"show-versions": { type: "boolean", description: "Show VS Code extension versions." },
|
||||||
"proxy-domain": { type: "string[]", description: "Domain used for proxying ports." },
|
"proxy-domain": { type: "string[]", description: "Domain used for proxying ports." },
|
||||||
|
|
||||||
|
"new-window": {
|
||||||
|
type: "boolean",
|
||||||
|
short: "n",
|
||||||
|
description: "Force to open a new window. (use with open-in)",
|
||||||
|
},
|
||||||
|
"reuse-window": {
|
||||||
|
type: "boolean",
|
||||||
|
short: "r",
|
||||||
|
description: "Force to open a file or folder in an already opened window. (use with open-in)",
|
||||||
|
},
|
||||||
|
|
||||||
locale: { type: "string" },
|
locale: { type: "string" },
|
||||||
log: { type: LogLevel },
|
log: { type: LogLevel },
|
||||||
verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." },
|
verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." },
|
||||||
|
@ -172,7 +194,7 @@ export const parse = (
|
||||||
const arg = argv[i]
|
const arg = argv[i]
|
||||||
|
|
||||||
// -- signals the end of option parsing.
|
// -- signals the end of option parsing.
|
||||||
if (!ended && arg == "--") {
|
if (!ended && arg === "--") {
|
||||||
ended = true
|
ended = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -220,7 +242,7 @@ export const parse = (
|
||||||
throw error(`--${key} requires a value`)
|
throw error(`--${key} requires a value`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (option.type == OptionalString && value == "false") {
|
if (option.type === OptionalString && value === "false") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,7 +370,7 @@ export async function readConfigFile(configPath?: string): Promise<Args> {
|
||||||
logger.info(`Wrote default config file to ${humanPath(configPath)}`)
|
logger.info(`Wrote default config file to ${humanPath(configPath)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!process.env.CODE_SERVER_PARENT_PID) {
|
if (!process.env.CODE_SERVER_PARENT_PID && !process.env.VSCODE_IPC_HOOK_CLI) {
|
||||||
logger.info(`Using config file ${humanPath(configPath)}`)
|
logger.info(`Using config file ${humanPath(configPath)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,6 +378,9 @@ export async function readConfigFile(configPath?: string): Promise<Args> {
|
||||||
const config = yaml.safeLoad(configFile.toString(), {
|
const config = yaml.safeLoad(configFile.toString(), {
|
||||||
filename: configPath,
|
filename: configPath,
|
||||||
})
|
})
|
||||||
|
if (!config || typeof config === "string") {
|
||||||
|
throw new Error(`invalid config: ${config}`)
|
||||||
|
}
|
||||||
|
|
||||||
// We convert the config file into a set of flags.
|
// We convert the config file into a set of flags.
|
||||||
// This is a temporary measure until we add a proper CLI library.
|
// This is a temporary measure until we add a proper CLI library.
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { field, logger } from "@coder/logger"
|
import { field, logger } from "@coder/logger"
|
||||||
import * as cp from "child_process"
|
import * as cp from "child_process"
|
||||||
|
import { promises as fs } from "fs"
|
||||||
|
import http from "http"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { CliMessage } from "../../lib/vscode/src/vs/server/ipc"
|
import { CliMessage, OpenCommandPipeArgs } from "../../lib/vscode/src/vs/server/ipc"
|
||||||
|
import { plural } from "../common/util"
|
||||||
|
import { HealthHttpProvider } from "./app/health"
|
||||||
import { LoginHttpProvider } from "./app/login"
|
import { LoginHttpProvider } from "./app/login"
|
||||||
import { ProxyHttpProvider } from "./app/proxy"
|
import { ProxyHttpProvider } from "./app/proxy"
|
||||||
import { StaticHttpProvider } from "./app/static"
|
import { StaticHttpProvider } from "./app/static"
|
||||||
|
@ -9,9 +13,9 @@ import { UpdateHttpProvider } from "./app/update"
|
||||||
import { VscodeHttpProvider } from "./app/vscode"
|
import { VscodeHttpProvider } from "./app/vscode"
|
||||||
import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli"
|
import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli"
|
||||||
import { AuthType, HttpServer, HttpServerOptions } from "./http"
|
import { AuthType, HttpServer, HttpServerOptions } from "./http"
|
||||||
import { generateCertificate, hash, open, humanPath } from "./util"
|
import { loadPlugins } from "./plugin"
|
||||||
|
import { generateCertificate, hash, humanPath, open } from "./util"
|
||||||
import { ipcMain, wrap } from "./wrapper"
|
import { ipcMain, wrap } from "./wrapper"
|
||||||
import { plural } from "../common/util"
|
|
||||||
|
|
||||||
process.on("uncaughtException", (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
logger.error(`Uncaught exception: ${error.message}`)
|
logger.error(`Uncaught exception: ${error.message}`)
|
||||||
|
@ -77,6 +81,9 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise<void>
|
||||||
httpServer.registerHttpProvider("/proxy", ProxyHttpProvider)
|
httpServer.registerHttpProvider("/proxy", ProxyHttpProvider)
|
||||||
httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword)
|
httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword)
|
||||||
httpServer.registerHttpProvider("/static", StaticHttpProvider)
|
httpServer.registerHttpProvider("/static", StaticHttpProvider)
|
||||||
|
httpServer.registerHttpProvider("/healthz", HealthHttpProvider, httpServer.heart)
|
||||||
|
|
||||||
|
await loadPlugins(httpServer, args)
|
||||||
|
|
||||||
ipcMain().onDispose(() => {
|
ipcMain().onDispose(() => {
|
||||||
httpServer.dispose().then((errors) => {
|
httpServer.dispose().then((errors) => {
|
||||||
|
@ -167,7 +174,7 @@ async function entry(): Promise<void> {
|
||||||
CODE_SERVER_PARENT_PID: process.pid.toString(),
|
CODE_SERVER_PARENT_PID: process.pid.toString(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
vscode.once("message", (message) => {
|
vscode.once("message", (message: any) => {
|
||||||
logger.debug("Got message from VS Code", field("message", message))
|
logger.debug("Got message from VS Code", field("message", message))
|
||||||
if (message.type !== "ready") {
|
if (message.type !== "ready") {
|
||||||
logger.error("Unexpected response waiting for ready response")
|
logger.error("Unexpected response waiting for ready response")
|
||||||
|
@ -181,6 +188,57 @@ async function entry(): Promise<void> {
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
})
|
})
|
||||||
vscode.on("exit", (code) => process.exit(code || 0))
|
vscode.on("exit", (code) => process.exit(code || 0))
|
||||||
|
} else if (process.env.VSCODE_IPC_HOOK_CLI) {
|
||||||
|
const pipeArgs: OpenCommandPipeArgs = {
|
||||||
|
type: "open",
|
||||||
|
folderURIs: [],
|
||||||
|
forceReuseWindow: args["reuse-window"],
|
||||||
|
forceNewWindow: args["new-window"],
|
||||||
|
}
|
||||||
|
const isDir = async (path: string): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const st = await fs.stat(path)
|
||||||
|
return st.isDirectory()
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < args._.length; i++) {
|
||||||
|
const fp = path.resolve(args._[i])
|
||||||
|
if (await isDir(fp)) {
|
||||||
|
pipeArgs.folderURIs.push(fp)
|
||||||
|
} else {
|
||||||
|
if (!pipeArgs.fileURIs) {
|
||||||
|
pipeArgs.fileURIs = []
|
||||||
|
}
|
||||||
|
pipeArgs.fileURIs.push(fp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pipeArgs.forceNewWindow && pipeArgs.fileURIs && pipeArgs.fileURIs.length > 0) {
|
||||||
|
logger.error("new-window can only be used with folder paths")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
if (pipeArgs.folderURIs.length === 0 && (!pipeArgs.fileURIs || pipeArgs.fileURIs.length === 0)) {
|
||||||
|
logger.error("Please specify at least one file or folder argument")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
const vscode = http.request(
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
method: "POST",
|
||||||
|
socketPath: process.env["VSCODE_IPC_HOOK_CLI"],
|
||||||
|
},
|
||||||
|
(res) => {
|
||||||
|
res.on("data", (message) => {
|
||||||
|
logger.debug("Got message from VS Code", field("message", message.toString()))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
vscode.on("error", (err) => {
|
||||||
|
logger.debug("Got error from VS Code", field("error", err))
|
||||||
|
})
|
||||||
|
vscode.write(JSON.stringify(pipeArgs))
|
||||||
|
vscode.end()
|
||||||
} else {
|
} else {
|
||||||
wrap(() => main(args, cliArgs, configArgs))
|
wrap(() => main(args, cliArgs, configArgs))
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,11 +209,11 @@ export abstract class HttpProvider {
|
||||||
/**
|
/**
|
||||||
* Get the base relative to the provided route. For each slash we need to go
|
* Get the base relative to the provided route. For each slash we need to go
|
||||||
* up a directory. For example:
|
* up a directory. For example:
|
||||||
* / => ./
|
* / => .
|
||||||
* /foo => ./
|
* /foo => .
|
||||||
* /foo/ => ./../
|
* /foo/ => ./..
|
||||||
* /foo/bar => ./../
|
* /foo/bar => ./..
|
||||||
* /foo/bar/ => ./../../
|
* /foo/bar/ => ./../..
|
||||||
*/
|
*/
|
||||||
public base(route: Route): string {
|
public base(route: Route): string {
|
||||||
const depth = (route.originalPath.match(/\//g) || []).length
|
const depth = (route.originalPath.match(/\//g) || []).length
|
||||||
|
@ -235,30 +235,23 @@ export abstract class HttpProvider {
|
||||||
/**
|
/**
|
||||||
* Replace common templates strings.
|
* Replace common templates strings.
|
||||||
*/
|
*/
|
||||||
protected replaceTemplates(route: Route, response: HttpStringFileResponse, sessionId?: string): HttpStringFileResponse
|
|
||||||
protected replaceTemplates<T extends object>(
|
protected replaceTemplates<T extends object>(
|
||||||
route: Route,
|
route: Route,
|
||||||
response: HttpStringFileResponse,
|
response: HttpStringFileResponse,
|
||||||
options: T,
|
extraOptions?: Omit<T, "base" | "csStaticBase" | "logLevel">,
|
||||||
): HttpStringFileResponse
|
|
||||||
protected replaceTemplates(
|
|
||||||
route: Route,
|
|
||||||
response: HttpStringFileResponse,
|
|
||||||
sessionIdOrOptions?: string | object,
|
|
||||||
): HttpStringFileResponse {
|
): HttpStringFileResponse {
|
||||||
if (typeof sessionIdOrOptions === "undefined" || typeof sessionIdOrOptions === "string") {
|
const base = this.base(route)
|
||||||
sessionIdOrOptions = {
|
const options: Options = {
|
||||||
base: this.base(route),
|
base,
|
||||||
commit: this.options.commit,
|
csStaticBase: base + "/static/" + this.options.commit + this.rootPath,
|
||||||
logLevel: logger.level,
|
logLevel: logger.level,
|
||||||
sessionID: sessionIdOrOptions,
|
...extraOptions,
|
||||||
} as Options
|
|
||||||
}
|
}
|
||||||
response.content = response.content
|
response.content = response.content
|
||||||
.replace(/{{COMMIT}}/g, this.options.commit)
|
|
||||||
.replace(/{{TO}}/g, Array.isArray(route.query.to) ? route.query.to[0] : route.query.to || "/dashboard")
|
.replace(/{{TO}}/g, Array.isArray(route.query.to) ? route.query.to[0] : route.query.to || "/dashboard")
|
||||||
.replace(/{{BASE}}/g, this.base(route))
|
.replace(/{{BASE}}/g, options.base)
|
||||||
.replace(/"{{OPTIONS}}"/, `'${JSON.stringify(sessionIdOrOptions)}'`)
|
.replace(/{{CS_STATIC_BASE}}/g, options.csStaticBase)
|
||||||
|
.replace(/"{{OPTIONS}}"/, `'${JSON.stringify(options)}'`)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,7 +289,7 @@ export abstract class HttpProvider {
|
||||||
/**
|
/**
|
||||||
* Helper to error if not authorized.
|
* Helper to error if not authorized.
|
||||||
*/
|
*/
|
||||||
protected ensureAuthenticated(request: http.IncomingMessage): void {
|
public ensureAuthenticated(request: http.IncomingMessage): void {
|
||||||
if (!this.authenticated(request)) {
|
if (!this.authenticated(request)) {
|
||||||
throw new HttpError("Unauthorized", HttpCode.Unauthorized)
|
throw new HttpError("Unauthorized", HttpCode.Unauthorized)
|
||||||
}
|
}
|
||||||
|
@ -403,23 +396,26 @@ export abstract class HttpProvider {
|
||||||
export class Heart {
|
export class Heart {
|
||||||
private heartbeatTimer?: NodeJS.Timeout
|
private heartbeatTimer?: NodeJS.Timeout
|
||||||
private heartbeatInterval = 60000
|
private heartbeatInterval = 60000
|
||||||
private lastHeartbeat = 0
|
public lastHeartbeat = 0
|
||||||
|
|
||||||
public constructor(private readonly heartbeatPath: string, private readonly isActive: () => Promise<boolean>) {}
|
public constructor(private readonly heartbeatPath: string, private readonly isActive: () => Promise<boolean>) {}
|
||||||
|
|
||||||
|
public alive(): boolean {
|
||||||
|
const now = Date.now()
|
||||||
|
return now - this.lastHeartbeat < this.heartbeatInterval
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Write to the heartbeat file if we haven't already done so within the
|
* Write to the heartbeat file if we haven't already done so within the
|
||||||
* timeout and start or reset a timer that keeps running as long as there is
|
* timeout and start or reset a timer that keeps running as long as there is
|
||||||
* activity. Failures are logged as warnings.
|
* activity. Failures are logged as warnings.
|
||||||
*/
|
*/
|
||||||
public beat(): void {
|
public beat(): void {
|
||||||
const now = Date.now()
|
if (!this.alive()) {
|
||||||
if (now - this.lastHeartbeat >= this.heartbeatInterval) {
|
|
||||||
logger.trace("heartbeat")
|
logger.trace("heartbeat")
|
||||||
fs.outputFile(this.heartbeatPath, "").catch((error) => {
|
fs.outputFile(this.heartbeatPath, "").catch((error) => {
|
||||||
logger.warn(error.message)
|
logger.warn(error.message)
|
||||||
})
|
})
|
||||||
this.lastHeartbeat = now
|
this.lastHeartbeat = Date.now()
|
||||||
if (typeof this.heartbeatTimer !== "undefined") {
|
if (typeof this.heartbeatTimer !== "undefined") {
|
||||||
clearTimeout(this.heartbeatTimer)
|
clearTimeout(this.heartbeatTimer)
|
||||||
}
|
}
|
||||||
|
@ -464,7 +460,7 @@ export class HttpServer {
|
||||||
private listenPromise: Promise<string | null> | undefined
|
private listenPromise: Promise<string | null> | undefined
|
||||||
public readonly protocol: "http" | "https"
|
public readonly protocol: "http" | "https"
|
||||||
private readonly providers = new Map<string, HttpProvider>()
|
private readonly providers = new Map<string, HttpProvider>()
|
||||||
private readonly heart: Heart
|
public readonly heart: Heart
|
||||||
private readonly socketProvider = new SocketProxyProvider()
|
private readonly socketProvider = new SocketProxyProvider()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -609,8 +605,10 @@ export class HttpServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onRequest = async (request: http.IncomingMessage, response: http.ServerResponse): Promise<void> => {
|
private onRequest = async (request: http.IncomingMessage, response: http.ServerResponse): Promise<void> => {
|
||||||
this.heart.beat()
|
|
||||||
const route = this.parseUrl(request)
|
const route = this.parseUrl(request)
|
||||||
|
if (route.providerBase !== "/healthz") {
|
||||||
|
this.heart.beat()
|
||||||
|
}
|
||||||
const write = (payload: HttpResponse): void => {
|
const write = (payload: HttpResponse): void => {
|
||||||
response.writeHead(payload.redirect ? HttpCode.Redirect : payload.code || HttpCode.Ok, {
|
response.writeHead(payload.redirect ? HttpCode.Redirect : payload.code || HttpCode.Ok, {
|
||||||
"Content-Type": payload.mime || getMediaMime(payload.filePath),
|
"Content-Type": payload.mime || getMediaMime(payload.filePath),
|
||||||
|
@ -649,10 +647,7 @@ export class HttpServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload =
|
const payload = (await this.handleRequest(route, request)) || (await route.provider.handleRequest(route, request))
|
||||||
this.maybeRedirect(request, route) ||
|
|
||||||
(route.provider.authenticated(request) && this.maybeProxy(request)) ||
|
|
||||||
(await route.provider.handleRequest(route, request))
|
|
||||||
if (payload.proxy) {
|
if (payload.proxy) {
|
||||||
this.doProxy(route, request, response, payload.proxy)
|
this.doProxy(route, request, response, payload.proxy)
|
||||||
} else {
|
} else {
|
||||||
|
@ -664,7 +659,7 @@ export class HttpServer {
|
||||||
e = new HttpError("Not found", HttpCode.NotFound)
|
e = new HttpError("Not found", HttpCode.NotFound)
|
||||||
}
|
}
|
||||||
const code = typeof e.code === "number" ? e.code : HttpCode.ServerError
|
const code = typeof e.code === "number" ? e.code : HttpCode.ServerError
|
||||||
logger.debug("Request error", field("url", request.url), field("code", code))
|
logger.debug("Request error", field("url", request.url), field("code", code), field("error", error))
|
||||||
if (code >= HttpCode.ServerError) {
|
if (code >= HttpCode.ServerError) {
|
||||||
logger.error(error.stack)
|
logger.error(error.stack)
|
||||||
}
|
}
|
||||||
|
@ -687,15 +682,23 @@ export class HttpServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return any necessary redirection before delegating to a provider.
|
* Handle requests that are always in effect no matter what provider is
|
||||||
|
* registered at the route.
|
||||||
*/
|
*/
|
||||||
private maybeRedirect(request: http.IncomingMessage, route: ProviderRoute): RedirectResponse | undefined {
|
private async handleRequest(route: ProviderRoute, request: http.IncomingMessage): Promise<HttpResponse | undefined> {
|
||||||
// If we're handling TLS ensure all requests are redirected to HTTPS.
|
// If we're handling TLS ensure all requests are redirected to HTTPS.
|
||||||
if (this.options.cert && !(request.connection as tls.TLSSocket).encrypted) {
|
if (this.options.cert && !(request.connection as tls.TLSSocket).encrypted) {
|
||||||
return { redirect: route.fullPath }
|
return { redirect: route.fullPath }
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined
|
// Return robots.txt.
|
||||||
|
if (route.fullPath === "/robots.txt") {
|
||||||
|
const filePath = path.resolve(__dirname, "../../src/browser/robots.txt")
|
||||||
|
return { content: await fs.readFile(filePath), filePath }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle proxy domains.
|
||||||
|
return this.maybeProxy(route, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -746,7 +749,7 @@ export class HttpServer {
|
||||||
// can't be transferred so we need an in-between).
|
// can't be transferred so we need an in-between).
|
||||||
const socketProxy = await this.socketProvider.createProxy(socket)
|
const socketProxy = await this.socketProvider.createProxy(socket)
|
||||||
const payload =
|
const payload =
|
||||||
this.maybeProxy(request) || (await route.provider.handleWebSocket(route, request, socketProxy, head))
|
this.maybeProxy(route, request) || (await route.provider.handleWebSocket(route, request, socketProxy, head))
|
||||||
if (payload && payload.proxy) {
|
if (payload && payload.proxy) {
|
||||||
this.doProxy(route, request, { socket: socketProxy, head }, payload.proxy)
|
this.doProxy(route, request, { socket: socketProxy, head }, payload.proxy)
|
||||||
}
|
}
|
||||||
|
@ -875,6 +878,7 @@ export class HttpServer {
|
||||||
// isn't setting the host header to match the access domain.
|
// isn't setting the host header to match the access domain.
|
||||||
host === "localhost"
|
host === "localhost"
|
||||||
) {
|
) {
|
||||||
|
logger.debug("no valid cookie doman", field("host", host))
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -884,6 +888,7 @@ export class HttpServer {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
logger.debug("got cookie doman", field("host", host))
|
||||||
return host ? `Domain=${host}` : undefined
|
return host ? `Domain=${host}` : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -894,8 +899,10 @@ export class HttpServer {
|
||||||
*
|
*
|
||||||
* For example if `coder.com` is specified `8080.coder.com` will be proxied
|
* For example if `coder.com` is specified `8080.coder.com` will be proxied
|
||||||
* but `8080.test.coder.com` and `test.8080.coder.com` will not.
|
* but `8080.test.coder.com` and `test.8080.coder.com` will not.
|
||||||
|
*
|
||||||
|
* Throw an error if proxying but the user isn't authenticated.
|
||||||
*/
|
*/
|
||||||
public maybeProxy(request: http.IncomingMessage): HttpResponse | undefined {
|
public maybeProxy(route: ProviderRoute, request: http.IncomingMessage): HttpResponse | undefined {
|
||||||
// Split into parts.
|
// Split into parts.
|
||||||
const host = request.headers.host || ""
|
const host = request.headers.host || ""
|
||||||
const idx = host.indexOf(":")
|
const idx = host.indexOf(":")
|
||||||
|
@ -909,6 +916,9 @@ export class HttpServer {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must be authenticated to use the proxy.
|
||||||
|
route.provider.ensureAuthenticated(request)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
proxy: {
|
proxy: {
|
||||||
port,
|
port,
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { field, logger } from "@coder/logger"
|
||||||
|
import * as fs from "fs"
|
||||||
|
import * as path from "path"
|
||||||
|
import * as util from "util"
|
||||||
|
import { Args } from "./cli"
|
||||||
|
import { HttpServer } from "./http"
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
|
||||||
|
export type Activate = (httpServer: HttpServer, args: Args) => void
|
||||||
|
|
||||||
|
export interface Plugin {
|
||||||
|
activate: Activate
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercept imports so we can inject code-server when the plugin tries to
|
||||||
|
* import it.
|
||||||
|
*/
|
||||||
|
const originalLoad = require("module")._load
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
require("module")._load = function (request: string, parent: object, isMain: boolean): any {
|
||||||
|
return originalLoad.apply(this, [request.replace(/^code-server/, path.resolve(__dirname, "../..")), parent, isMain])
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadPlugin = async (pluginPath: string, httpServer: HttpServer, args: Args): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const plugin: Plugin = require(pluginPath)
|
||||||
|
plugin.activate(httpServer, args)
|
||||||
|
logger.debug("Loaded plugin", field("name", path.basename(pluginPath)))
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "MODULE_NOT_FOUND") {
|
||||||
|
logger.warn(error.message)
|
||||||
|
} else {
|
||||||
|
logger.error(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _loadPlugins = async (httpServer: HttpServer, args: Args): Promise<void> => {
|
||||||
|
const pluginPath = path.resolve(__dirname, "../../plugins")
|
||||||
|
const files = await util.promisify(fs.readdir)(pluginPath, {
|
||||||
|
withFileTypes: true,
|
||||||
|
})
|
||||||
|
await Promise.all(files.map((file) => loadPlugin(path.join(pluginPath, file.name), httpServer, args)))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadPlugins = async (httpServer: HttpServer, args: Args): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await _loadPlugins(httpServer, args)
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "ENOENT") {
|
||||||
|
logger.warn(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.PLUGIN_DIR) {
|
||||||
|
await loadPlugin(process.env.PLUGIN_DIR, httpServer, args)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
|
import { logger } from "@coder/logger"
|
||||||
import * as fs from "fs-extra"
|
import * as fs from "fs-extra"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { extend, paths } from "./util"
|
|
||||||
import { logger } from "@coder/logger"
|
|
||||||
import { Route } from "./http"
|
import { Route } from "./http"
|
||||||
|
import { paths } from "./util"
|
||||||
|
|
||||||
export type Settings = { [key: string]: Settings | string | boolean | number }
|
export type Settings = { [key: string]: Settings | string | boolean | number }
|
||||||
|
|
||||||
|
@ -30,12 +30,12 @@ export class SettingsProvider<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write settings combined with current settings. On failure log a warning.
|
* Write settings combined with current settings. On failure log a warning.
|
||||||
* Settings can be shallow or deep merged.
|
* Settings will be merged shallowly.
|
||||||
*/
|
*/
|
||||||
public async write(settings: Partial<T>, shallow = true): Promise<void> {
|
public async write(settings: Partial<T>): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const oldSettings = await this.read()
|
const oldSettings = await this.read()
|
||||||
const nextSettings = shallow ? Object.assign({}, oldSettings, settings) : extend(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) {
|
||||||
logger.warn(error.message)
|
logger.warn(error.message)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as cp from "child_process"
|
import * as cp from "child_process"
|
||||||
import * as crypto from "crypto"
|
import * as crypto from "crypto"
|
||||||
|
import envPaths from "env-paths"
|
||||||
import * as fs from "fs-extra"
|
import * as fs from "fs-extra"
|
||||||
import * as os from "os"
|
import * as os from "os"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import * as util from "util"
|
import * as util from "util"
|
||||||
import envPaths from "env-paths"
|
|
||||||
import xdgBasedir from "xdg-basedir"
|
import xdgBasedir from "xdg-basedir"
|
||||||
|
|
||||||
export const tmpdir = path.join(os.tmpdir(), "code-server")
|
export const tmpdir = path.join(os.tmpdir(), "code-server")
|
||||||
|
@ -199,25 +199,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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extend a with b and return a new object. Properties with objects will be
|
|
||||||
* recursively merged while all other properties are just overwritten.
|
|
||||||
*/
|
|
||||||
export function extend<A, B>(a: A, b: B): A & B
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export function extend(...args: any[]): any {
|
|
||||||
const c = {} as any // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
for (const obj of args) {
|
|
||||||
if (!isObject(obj)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for (const key in obj) {
|
|
||||||
c[key] = isObject(obj[key]) ? extend(c[key], obj[key]) : obj[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Taken from vs/base/common/charCode.ts. Copied for now instead of importing so
|
* 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
|
* we don't have to set up a `vs` alias to be able to import with types (since
|
||||||
|
|
|
@ -32,7 +32,7 @@ export class IpcMain {
|
||||||
public readonly onMessage = this._onMessage.event
|
public readonly onMessage = this._onMessage.event
|
||||||
private readonly _onDispose = new Emitter<NodeJS.Signals | undefined>()
|
private readonly _onDispose = new Emitter<NodeJS.Signals | undefined>()
|
||||||
public readonly onDispose = this._onDispose.event
|
public readonly onDispose = this._onDispose.event
|
||||||
public readonly exit: (code?: number) => never
|
public readonly processExit: (code?: number) => never
|
||||||
|
|
||||||
public constructor(public readonly parentPid?: number) {
|
public constructor(public readonly parentPid?: number) {
|
||||||
process.on("SIGINT", () => this._onDispose.emit("SIGINT"))
|
process.on("SIGINT", () => this._onDispose.emit("SIGINT"))
|
||||||
|
@ -40,7 +40,7 @@ export class IpcMain {
|
||||||
process.on("exit", () => this._onDispose.emit(undefined))
|
process.on("exit", () => this._onDispose.emit(undefined))
|
||||||
|
|
||||||
// Ensure we control when the process exits.
|
// Ensure we control when the process exits.
|
||||||
this.exit = process.exit
|
this.processExit = process.exit
|
||||||
process.exit = function (code?: number) {
|
process.exit = function (code?: number) {
|
||||||
logger.warn(`process.exit() was prevented: ${code || "unknown code"}.`)
|
logger.warn(`process.exit() was prevented: ${code || "unknown code"}.`)
|
||||||
} as (code?: number) => never
|
} as (code?: number) => never
|
||||||
|
@ -71,6 +71,14 @@ export class IpcMain {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public exit(error?: number | ProcessError): never {
|
||||||
|
if (error && typeof error !== "number") {
|
||||||
|
this.processExit(typeof error.code === "number" ? error.code : 1)
|
||||||
|
} else {
|
||||||
|
this.processExit(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public handshake(child?: cp.ChildProcess): Promise<void> {
|
public handshake(child?: cp.ChildProcess): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const target = child || process
|
const target = child || process
|
||||||
|
@ -161,11 +169,26 @@ export class WrapperProcess {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain().onMessage(async (message) => {
|
ipcMain().onMessage((message) => {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case "relaunch":
|
case "relaunch":
|
||||||
logger.info(`Relaunching: ${this.currentVersion} -> ${message.version}`)
|
logger.info(`Relaunching: ${this.currentVersion} -> ${message.version}`)
|
||||||
this.currentVersion = message.version
|
this.currentVersion = message.version
|
||||||
|
this.relaunch()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
logger.error(`Unrecognized message ${message}`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on("SIGUSR1", async () => {
|
||||||
|
logger.info("Received SIGUSR1; hotswapping")
|
||||||
|
this.relaunch()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async relaunch(): Promise<void> {
|
||||||
this.started = undefined
|
this.started = undefined
|
||||||
if (this.process) {
|
if (this.process) {
|
||||||
this.process.removeAllListeners()
|
this.process.removeAllListeners()
|
||||||
|
@ -177,12 +200,6 @@ export class WrapperProcess {
|
||||||
logger.error(error.message)
|
logger.error(error.message)
|
||||||
ipcMain().exit(typeof error.code === "number" ? error.code : 1)
|
ipcMain().exit(typeof error.code === "number" ? error.code : 1)
|
||||||
}
|
}
|
||||||
break
|
|
||||||
default:
|
|
||||||
logger.error(`Unrecognized message ${message}`)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(): Promise<void> {
|
public start(): Promise<void> {
|
||||||
|
@ -244,13 +261,13 @@ export const wrap = (fn: () => Promise<void>): void => {
|
||||||
.then(() => fn())
|
.then(() => fn())
|
||||||
.catch((error: ProcessError): void => {
|
.catch((error: ProcessError): void => {
|
||||||
logger.error(error.message)
|
logger.error(error.message)
|
||||||
ipcMain().exit(typeof error.code === "number" ? error.code : 1)
|
ipcMain().exit(error)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const wrapper = new WrapperProcess(require("../../package.json").version)
|
const wrapper = new WrapperProcess(require("../../package.json").version)
|
||||||
wrapper.start().catch((error) => {
|
wrapper.start().catch((error) => {
|
||||||
logger.error(error.message)
|
logger.error(error.message)
|
||||||
ipcMain().exit(typeof error.code === "number" ? error.code : 1)
|
ipcMain().exit(error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import * as net from "net"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import * as tls from "tls"
|
import * as tls from "tls"
|
||||||
import { Emitter } from "../src/common/emitter"
|
import { Emitter } from "../src/common/emitter"
|
||||||
import { generateCertificate, tmpdir } from "../src/node/util"
|
|
||||||
import { SocketProxyProvider } from "../src/node/socket"
|
import { SocketProxyProvider } from "../src/node/socket"
|
||||||
|
import { generateCertificate, tmpdir } from "../src/node/util"
|
||||||
|
|
||||||
describe("SocketProxyProvider", () => {
|
describe("SocketProxyProvider", () => {
|
||||||
const provider = new SocketProxyProvider()
|
const provider = new SocketProxyProvider()
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { SettingsProvider, UpdateSettings } from "../src/node/settings"
|
||||||
import { tmpdir } from "../src/node/util"
|
import { tmpdir } from "../src/node/util"
|
||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
|
return
|
||||||
let version = "1.0.0"
|
let version = "1.0.0"
|
||||||
let spy: string[] = []
|
let spy: string[] = []
|
||||||
const server = http.createServer((request: http.IncomingMessage, response: http.ServerResponse) => {
|
const server = http.createServer((request: http.IncomingMessage, response: http.ServerResponse) => {
|
||||||
|
|
|
@ -1,43 +1,7 @@
|
||||||
import * as assert from "assert"
|
import * as assert from "assert"
|
||||||
import { normalize } from "../src/common/util"
|
import { normalize } from "../src/common/util"
|
||||||
import { extend } from "../src/node/util"
|
|
||||||
|
|
||||||
describe("util", () => {
|
describe("util", () => {
|
||||||
describe("extend", () => {
|
|
||||||
it("should extend", () => {
|
|
||||||
const a = { foo: { bar: 0, baz: 2 }, garply: 4, waldo: 6 }
|
|
||||||
const b = { foo: { bar: 1, qux: 3 }, garply: "5", fred: 7 }
|
|
||||||
const extended = extend(a, b)
|
|
||||||
assert.deepEqual(extended, {
|
|
||||||
foo: { bar: 1, baz: 2, qux: 3 },
|
|
||||||
garply: "5",
|
|
||||||
waldo: 6,
|
|
||||||
fred: 7,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should make deep copies of the original objects", () => {
|
|
||||||
const a = { foo: 0, bar: { frobnozzle: 2 }, mumble: { qux: { thud: 4 } } }
|
|
||||||
const b = { foo: 1, bar: { chad: 3 } }
|
|
||||||
const extended = extend(a, b)
|
|
||||||
assert.notEqual(a.bar, extended.bar)
|
|
||||||
assert.notEqual(b.bar, extended.bar)
|
|
||||||
assert.notEqual(a.mumble, extended.mumble)
|
|
||||||
assert.notEqual(a.mumble.qux, extended.mumble.qux)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle mismatch in type", () => {
|
|
||||||
const a = { foo: { bar: 0, baz: 2, qux: { mumble: 11 } }, garply: 4, waldo: { thud: 10 } }
|
|
||||||
const b = { foo: { bar: [1], baz: { plugh: 8 }, qux: 12 }, garply: { nox: 9 }, waldo: 7 }
|
|
||||||
const extended = extend(a, b)
|
|
||||||
assert.deepEqual(extended, {
|
|
||||||
foo: { bar: [1], baz: { plugh: 8 }, qux: 12 },
|
|
||||||
garply: { nox: 9 },
|
|
||||||
waldo: 7,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("normalize", () => {
|
describe("normalize", () => {
|
||||||
it("should remove multiple slashes", () => {
|
it("should remove multiple slashes", () => {
|
||||||
assert.equal(normalize("//foo//bar//baz///mumble"), "/foo/bar/baz/mumble")
|
assert.equal(normalize("//foo//bar//baz///mumble"), "/foo/bar/baz/mumble")
|
||||||
|
|
|
@ -8,17 +8,15 @@
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"outDir": "./out",
|
"outDir": "./out",
|
||||||
"allowJs": false,
|
|
||||||
"jsx": "react",
|
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"tsBuildInfoFile": "./.tsbuildinfo",
|
"tsBuildInfoFile": "./.cache/tsbuildinfo",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"typeRoots": ["./node_modules/@types", "./typings"]
|
"typeRoots": ["./node_modules/@types", "./typings"]
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*.ts", "./src/**/*.tsx"]
|
"include": ["./src/**/*.ts"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue