1
0
mirror of https://git.tuxpa.in/a/code-server.git synced 2024-12-27 04:35:25 +00:00

Merge pull request #1338 from cdr/restructure

This commit is contained in:
Asher 2020-03-03 13:25:08 -06:00 committed by GitHub
commit 2c4ca14d53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
143 changed files with 14345 additions and 6984 deletions

View File

@ -1,12 +1,2 @@
Dockerfile
build
deployment
doc
.github
.gitignore
.node-version
.travis.yml
LICENSE
README.md
node_modules
release
**
!release

View File

@ -1,408 +0,0 @@
kind: pipeline
type: docker
name: amd64:linux
platform:
arch: amd64
steps:
- name: cache:restore
image: node:12
commands:
- ./scripts/cacher.sh
- name: build
image: codercom/nbin:centos
commands:
- yum install -y libxkbfile-devel libsecret-devel
- . /opt/rh/devtoolset-6/enable
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12
commands:
- yarn test
- name: publish:github
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files: release/*.tar.gz
draft: true
overwrite: true
title: ${DRONE_TAG}
when:
event: tag
- name: publish:docker
image: plugins/docker
settings:
username:
from_secret: docker_user
password:
from_secret: docker_pass
repo: codercom/code-server
dockerfile: scripts/ci.dockerfile
tags:
- ${DRONE_TAG}
when:
event: tag
- name: publish:gcs
image: plugins/gcs
settings:
source: gcs_bucket
target: codesrv-ci.cdr.sh/
token:
from_secret: gcs-token
when:
event: tag
---
kind: pipeline
type: docker
name: amd64:alpine
platform:
arch: amd64
steps:
- name: cache:restore
image: node:12-alpine
commands:
- ./scripts/cacher.sh
- name: build
image: node:12-alpine
commands:
- apk add libxkbfile-dev libsecret-dev build-base git bash python
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12-alpine
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12-alpine
commands:
- yarn test
- name: publish:github
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files: release/*.tar.gz
draft: true
overwrite: true
title: ${DRONE_TAG}
when:
event: tag
- name: publish:gcs
image: plugins/gcs
settings:
source: gcs_bucket
target: codesrv-ci.cdr.sh/
token:
from_secret: gcs-token
when:
event: tag
---
kind: pipeline
type: docker
name: arm64:linux
platform:
arch: arm64
steps:
- name: cache:restore
image: node:12
commands:
- ./scripts/cacher.sh
- name: build
image: node:12
commands:
- apt update && apt install -y build-essential git libsecret-1-dev libx11-dev libxkbfile-dev
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12
commands:
- yarn test
- name: publish:github
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files: release/*.tar.gz
draft: true
overwrite: true
title: ${DRONE_TAG}
when:
event: tag
- name: publish:docker
image: plugins/docker
settings:
username:
from_secret: docker_user
password:
from_secret: docker_pass
repo: codercom/code-server
dockerfile: scripts/ci.dockerfile
tags:
- ${DRONE_TAG}-arm64
when:
event: tag
- name: publish:gcs
image: plugins/gcs
settings:
source: gcs_bucket
target: codesrv-ci.cdr.sh/
token:
from_secret: gcs-token
when:
event: tag
---
kind: pipeline
type: docker
name: arm64:alpine
platform:
arch: arm64
steps:
- name: cache:restore
image: node:12-alpine
commands:
- ./scripts/cacher.sh
- name: build
image: node:12-alpine
commands:
- apk add libxkbfile-dev libsecret-dev build-base git bash python
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12-alpine
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12-alpine
commands:
- yarn test
- name: publish:github
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files: release/*.tar.gz
draft: true
overwrite: true
title: ${DRONE_TAG}
when:
event: tag
- name: publish:gcs
image: plugins/gcs
settings:
source: gcs_bucket
target: codesrv-ci.cdr.sh/
token:
from_secret: gcs-token
when:
event: tag
---
kind: pipeline
type: docker
name: arm:linux
platform:
arch: arm
steps:
- name: cache:restore
image: node:12
commands:
- ./scripts/cacher.sh
- name: build
image: node:12
commands:
- apt update && apt install -y build-essential git libsecret-1-dev libx11-dev libxkbfile-dev
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12
failure: ignore
commands:
- yarn test
# - name: publish:github
# image: plugins/github-release
# settings:
# api_key:
# from_secret: github_token
# files: release/*.tar.gz
# draft: true
# overwrite: true
# title: ${DRONE_TAG}
# when:
# event: tag
# - name: publish:docker
# image: plugins/docker
# settings:
# username:
# from_secret: docker_user
# password:
# from_secret: docker_pass
# repo: codercom/code-server
# dockerfile: scripts/ci.dockerfile
# tags:
# - ${DRONE_TAG}-arm
# when:
# event: tag
---
kind: pipeline
type: docker
name: arm:alpine
platform:
arch: arm
steps:
- name: cache:restore
image: node:12-alpine
commands:
- ./scripts/cacher.sh
- name: build
image: node:12-alpine
commands:
- apk add libxkbfile-dev libsecret-dev build-base git bash python
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12-alpine
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12-alpine
failure: ignore
commands:
- yarn test
# - name: publish:github
# image: plugins/github-release
# failure: ignore
# settings:
# api_key:
# from_secret: github_token
# files: release/*.tar.gz
# draft: true
# overwrite: true
# title: ${DRONE_TAG}
# when:
# event: tag

6
.editorconfig Normal file
View File

@ -0,0 +1,6 @@
root = true
[*]
indent_style = space
trim_trailing_whitespace = true
indent_size = 2

23
.eslintrc.yaml Normal file
View File

@ -0,0 +1,23 @@
parser: "@typescript-eslint/parser"
env:
browser: true
es6: true # Map, etc.
mocha: true
node: true
parserOptions:
ecmaVersion: 2018
sourceType: module
extends:
- eslint:recommended
- plugin:@typescript-eslint/recommended
- plugin:import/recommended
- plugin:import/typescript
- plugin:prettier/recommended
- prettier # Removes eslint rules that conflict with prettier.
- prettier/@typescript-eslint # Remove conflicts again.
rules:
# For overloads.
no-dupe-class-members: off

3
.github/CODEOWNERS vendored
View File

@ -1,2 +1 @@
* @code-asher @kylecarbs
Dockerfile @nhooyr
* @code-asher @nhooyr

View File

@ -1,22 +0,0 @@
---
name: Bug Report
about: Report problems and unexpected behavior.
title: ''
labels: 'bug'
assignees: ''
---
<!-- Please search existing issues to avoid creating duplicates. -->
<!-- All extension-specific issues should be created with the `Extension Bug` template. -->
- `code-server` version: <!-- The version of code-server -->
- OS Version: <!-- OS version, cloud provider, -->
## Description
<!-- Describes the problem here -->
## Steps to Reproduce
1. <!-- step 1: click ... -->
1. <!-- step 2: ... -->

View File

@ -1,22 +0,0 @@
---
name: Extension Bug
about: Report problems and unexpected behavior with extensions.
title: ''
labels: 'extension-specific'
assignees: ''
---
<!-- Please search existing issues to avoid creating duplicates. -->
- `code-server` version: <!-- The version of code-server -->
- OS Version: <!-- OS version, cloud provider, -->
- Extension: <!-- Link to extension -->
## Description
<!-- Describes the problem here -->
## Steps to Reproduce
1. <!-- step 1: click ... -->
1. <!-- step 2: ... -->

View File

@ -1,11 +0,0 @@
---
name: Feature Request
about: Suggest an idea for this project.
title: ''
labels: 'feature'
assignees: ''
---
<!-- Please search existing issues to avoid creating duplicates. -->
<!-- Describe the feature you'd like. -->

6
.github/issue_template.md vendored Normal file
View File

@ -0,0 +1,6 @@
<!--
Please file all questions and support requests at https://www.reddit.com/r/codeserver/
The issue tracker is only for bugs.
Please search for existing issues before filing.
-->

View File

@ -1,6 +1,4 @@
<!-- Please answer these questions before submitting your PR. Thanks! -->
### Describe in detail the problem you had and how this PR fixes it
### Is there an open issue you can link to?
<!--
Please link to the issue this PR solves.
If there is no existing issue, please first create one unless the fix is minor.
-->

11
.gitignore vendored
View File

@ -1,5 +1,8 @@
*.tsbuildinfo
.cache
build
dist*
out*
release*
node_modules
/build
/release
/binaries
/lib
binaries

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "lib/vscode"]
path = lib/vscode
url = https://github.com/microsoft/vscode

View File

@ -1 +0,0 @@
12.14.0

1
.npmrc
View File

@ -1 +0,0 @@
scripts-prepend-node-path=true

4
.prettierrc.yaml Normal file
View File

@ -0,0 +1,4 @@
printWidth: 120
semi: false
trailingComma: all
arrowParens: always

2
.stylelintrc.yaml Normal file
View File

@ -0,0 +1,2 @@
extends:
- stylelint-config-recommended

View File

@ -1,37 +1,53 @@
language: node_js
node_js:
- 12.14.0
services:
- docker
language: minimal
jobs:
include:
- name: "MacOS build"
- name: Test
if: tag IS blank
script: ./ci/image/run.sh "yarn && yarn vscode && ./ci/ci.sh"
deploy: null
- name: Linux Release
if: tag IS present
script:
- travis_wait 60 ./ci/image/run.sh "yarn && yarn vscode && ci/release.sh"
- ./ci/release-image/push.sh
- name: Linux ARM64 Release
if: tag IS present
script:
- travis_wait 60 ./ci/image/run.sh "yarn && yarn vscode && ci/release.sh"
- ./ci/release-image/push.sh
arch: arm64
- name: MacOS Release
if: tag IS present
os: osx
script: travis_wait 60 scripts/ci.bash
language: node_js
node_js: 12
script: yarn && yarn vscode && travis_wait 60 ci/release.sh
git:
depth: 3
before_deploy:
- openssl aes-256-cbc -K $encrypted_0c1654c01c97_key -iv $encrypted_0c1654c01c97_iv -in ./ci/key.json.enc -out ./ci/key.json -d
deploy:
- provider: releases
file_glob: true
edge: true
draft: true
tag_name: "$TRAVIS_TAG"
target_commitish: "$TRAVIS_COMMIT"
name: "$TRAVIS_TAG"
skip_cleanup: true
api_key:
secure: YL/x24KjYjgYXPcJWk3FV7FGxI79Mh6gBECQEcdlf3fkLEoKFVgzHBoUNWrFPzyR4tgLyWNAgcpD9Lkme1TRWTom7UPjXcwMNyLcLa+uec7ciSAnYD9ntLTpiCuPDD1u0LtRGclSi/EHQ+F8YVq+HZJpXTsJeAmOmihma3GVbGKSZr+BRum+0YZSG4w+o4TOlYzw/4bLWS52MogZcwpjd+hemBbgXLuGU2ziKv2vEKCZFbEeA16II4x1WLI4mutDdCeh7+3aLzGLwDa49NxtsVYNjyNFF75JhCTCNA55e2YMiLz9Uq69IXe/mi5F7xUaFfhIqqLNyKBnKeEOzu3dYnc+8n3LjnQ+00PmkF05nx9kBn3UfV1kwQGh6QbyDmTtBP07rtUMyI14aeQqHjxsaVRdMnwj9Q2DjXRr8UDqESZF0rmK3pHCXS2fBhIzLE8tLVW5Heiba2pQRFMHMZW+KBE97FzcFh7is90Ait3T8enfcd/PWFPYoBejDAdjwxwOkezh5N5ZkYquEfDYuWrFi6zRFCktsruaAcA+xGtTf9oilBBzUqu8Ie+YFWH5me83xakcblJWdaW/D2rLJAJH3m6LFm8lBqyUgDX5t/etob6CpDuYHu5D1J3XINOj/+aLAcadq6qlh70PMZS3zYffUu3JlzaD2amlSHIT8b5YXFc=
tag_name: $TRAVIS_TAG
target_commitish: $TRAVIS_COMMIT
name: $TRAVIS_TAG
file:
- release/*.tar.gz
- release/*.zip
on:
repo: cdr/code-server
tags: true
- provider: gcs
edge: true
bucket: "codesrv-ci.cdr.sh"
upload_dir: "releases"
key_file: ./ci/key.json
local_dir: release-upload
on:
tags: true
cache:
timeout: 1000
timeout: 600
yarn: true
directories:
- source

View File

@ -1,57 +0,0 @@
FROM node:12.14.0
ARG tag
ARG githubToken
# Install VS Code's deps. These are the only two it seems we need.
RUN apt-get update && apt-get install -y \
libxkbfile-dev \
libsecret-1-dev
WORKDIR /src
COPY . .
RUN yarn \
&& DRONE_TAG="$tag" MINIFY=true BINARY=true GITHUB_TOKEN="$githubToken" ./scripts/ci.bash \
&& rm -r /src/build \
&& rm -r /src/source
# We deploy with Ubuntu so that devs have a familiar environment.
FROM ubuntu:18.04
RUN apt-get update && apt-get install -y \
openssl \
net-tools \
git \
locales \
sudo \
dumb-init \
vim \
curl \
wget \
&& rm -rf /var/lib/apt/lists/*
RUN locale-gen en_US.UTF-8
# We cannot use update-locale because docker will not use the env variables
# configured in /etc/default/locale so we need to set it manually.
ENV LC_ALL=en_US.UTF-8 \
SHELL=/bin/bash
RUN adduser --gecos '' --disabled-password coder && \
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
USER coder
# Create first so these directories will be owned by coder instead of root
# (workdir and mounting appear to both default to root).
RUN mkdir -p /home/coder/project \
&& mkdir -p /home/coder/.local/share/code-server
WORKDIR /home/coder/project
# This ensures we have a volume mounted even if the user forgot to do bind
# mount. So that they do not lose their data if they delete the container.
VOLUME [ "/home/coder/project" ]
COPY --from=0 /src/binaries/code-server /usr/local/bin/code-server
EXPOSE 8080
ENTRYPOINT ["dumb-init", "code-server", "--host", "0.0.0.0"]

169
README.md
View File

@ -1,4 +1,4 @@
# code-server &middot; [![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/cdr/code-server/blob/master/LICENSE) [!["Latest Release"](https://img.shields.io/github/release/cdr/code-server.svg)](https://github.com/cdr/code-server/releases/latest) [![Build Status](https://img.shields.io/travis/com/cdr/code-server/master)](https://github.com/cdr/code-server)
# code-server
`code-server` is [VS Code](https://github.com/Microsoft/vscode) running on a
remote server, accessible through the browser.
@ -9,14 +9,14 @@ Try it out:
docker run -it -p 127.0.0.1:8080:8080 -v "$PWD:/home/coder/project" codercom/code-server
```
- **Consistent environment:** Code on your Chromebook, tablet, and laptop with a
consistent dev environment. develop more easily for Linux if you have a
Windows or Mac, and pick up where you left off when switching workstations.
- **Code anywhere:** Code on your Chromebook, tablet, and laptop with a
consistent dev environment. Develop on a Linux machine and pick up from any
device with a web browser.
- **Server-powered:** Take advantage of large cloud servers to speed up tests,
compilations, downloads, and more. Preserve battery life when you're on the go
since all intensive computation runs on your server.
![Screenshot](/doc/assets/ide.gif)
![Example gif](/doc/assets/code-server.gif)
## Getting Started
@ -25,171 +25,30 @@ docker run -it -p 127.0.0.1:8080:8080 -v "$PWD:/home/coder/project" codercom/cod
- 64-bit host.
- At least 1GB of RAM.
- 2 cores or more are recommended (1 core works but not optimally).
- Secure connection over HTTPS or localhost (required for service workers).
- Secure connection over HTTPS or localhost (required for service workers and
clipboard support).
- For Linux: GLIBC 2.17 or later and GLIBCXX 3.4.15 or later.
- Docker (for Docker versions of `code-server`).
### Run over SSH
Use [sshcode](https://github.com/codercom/sshcode) for a simple setup.
### Docker
See the Docker one-liner mentioned above. Dockerfile is at [/Dockerfile](/Dockerfile).
To debug Golang using the
[ms-vscode-go extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.Go),
you need to add `--security-opt seccomp=unconfined` to your `docker run`
arguments when launching code-server with Docker. See
[#725](https://github.com/cdr/code-server/issues/725) for details.
### Digital Ocean
[![Create a Droplet](./doc/assets/droplet.svg)](https://marketplace.digitalocean.com/apps/code-server?action=deploy)
### Binaries
### Releases
1. [Download a binary](https://github.com/cdr/code-server/releases). (Linux and
OS X supported. Windows coming soon)
2. Unpack the downloaded file then run the binary.
1. [Download a release](https://github.com/cdr/code-server/releases). (Linux and
OS X supported. Windows support planned.)
2. Unpack the downloaded release then run the included `code-server` script.
3. In your browser navigate to `localhost:8080`.
- For self-hosting and other information see [doc/quickstart.md](doc/quickstart.md).
- For hosting on cloud platforms see [doc/deploy.md](doc/deploy.md).
## FAQ
### Build
See
[VS Code's prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
before building.
```shell
export OUT=/path/to/output/build # Optional if only building. Required if also developing.
yarn build $vscodeVersion $codeServerVersion # See scripts/ci.bash for the VS Code version to use.
# The code-server version can be anything you want.
node /path/to/output/build/out/vs/server/main.js # You can run the built JavaScript with Node.
yarn binary $vscodeVersion $codeServerVersion # Or you can package it into a binary.
```
## Security
### Authentication
By default `code-server` enables password authentication using a randomly
generated password. You can set the `PASSWORD` environment variable to use your
own instead or use `--auth none` to disable password authentication.
Do not expose `code-server` to the open internet without some form of
authentication.
### Encrypting traffic with HTTPS
If you aren't doing SSL termination elsewhere you can directly give
`code-server` a certificate with `code-server --cert` followed by the path to
your certificate. Additionally, you can use certificate keys with `--cert-key`
followed by the path to your key. If you pass `--cert` without any path
`code-server` will generate a self-signed certificate.
If `code-server` has been passed a certificate it will also respond to HTTPS
requests and will redirect all HTTP requests to HTTPS. Otherwise it will respond
only to HTTP requests.
You can use [Let's Encrypt](https://letsencrypt.org/) to get an SSL certificate
for free.
Do not expose `code-server` to the open internet without SSL, whether built-in
or through a proxy.
## Known Issues
- Creating custom VS Code extensions and debugging them doesn't work.
- Extension profiling and tips are currently disabled.
## Future
- **Stay up to date!** Get notified about new releases of code-server.
![Screenshot](/doc/assets/release.gif)
- Windows support.
- Electron and Chrome OS applications to bridge the gap between local<->remote.
- Run VS Code unit tests against our builds to ensure features work as expected.
## Extensions
code-server does not provide access to the official
[Visual Studio Marketplace](https://marketplace.visualstudio.com/vscode). Instead,
Coder has created a custom extension marketplace that we manage for open-source
extensions. If you want to use an extension with code-server that we do not have
in our marketplace please look for a release in the extensions repository,
contact us to see if we have one in the works or, if you build an extension
locally from open source, you can copy it to the `extensions` folder. If you
build one locally from open-source please contribute it to the project and let
us know so we can give you props! If you have your own custom marketplace, it is
possible to point code-server to it by setting the `SERVICE_URL` and `ITEM_URL`
environment variables.
## Telemetry
Use the `--disable-telemetry` flag to completely disable telemetry. We use the
data collected to improve code-server.
## Contributing
### Development
See
[VS Code's prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
before developing.
```shell
git clone https://github.com/microsoft/vscode
cd vscode
git checkout ${vscodeVersion} # See scripts/ci.bash for the version to use.
yarn
git clone https://github.com/cdr/code-server src/vs/server
cd src/vs/server
yarn
yarn patch:apply
yarn watch
# Wait for the initial compilation to complete (it will say "Finished compilation").
# Run the next command in another shell.
yarn start
# Visit http://localhost:8080
```
If you run into issues about a different version of Node being used, try running
`npm rebuild` in the VS Code directory.
### Upgrading VS Code
We patch VS Code to provide and fix some functionality. As the web portion of VS
Code matures, we'll be able to shrink and maybe even entirely eliminate our
patch. In the meantime, however, upgrading the VS Code version requires ensuring
that the patch still applies and has the intended effects.
To generate a new patch, **stage all the changes** you want to be included in
the patch in the VS Code source, then run `yarn patch:generate` in this
directory.
Our changes include:
- Allow multiple extension directories (both user and built-in).
- Modify the loader, websocket, webview, service worker, and asset requests to
use the URL of the page as a base (and TLS if necessary for the websocket).
- Send client-side telemetry through the server.
- Make changing the display language work.
- Make it possible for us to load code on the client.
- Make extensions work in the browser.
- Fix getting permanently disconnected when you sleep or hibernate for a while.
- Make it possible to automatically update the binary.
## License
[MIT](LICENSE)
See [./doc/FAQ.md](./doc/FAQ.md).
## Enterprise
Visit [our enterprise page](https://coder.com) for more information about our
enterprise offering.
## Commercialization
If you would like to commercialize code-server, please contact
contact@coder.com.
enterprise offerings.

364
ci/build.ts Normal file
View File

@ -0,0 +1,364 @@
import * as cp from "child_process"
import * as fs from "fs-extra"
import Bundler from "parcel-bundler"
import * as path from "path"
import * as util from "util"
enum Task {
Build = "build",
Watch = "watch",
}
class Builder {
private readonly rootPath = path.resolve(__dirname, "..")
private readonly vscodeSourcePath = path.join(this.rootPath, "lib/vscode")
private readonly buildPath = path.join(this.rootPath, "build")
private readonly codeServerVersion: string
private currentTask?: Task
public constructor() {
this.ensureArgument("rootPath", this.rootPath)
this.codeServerVersion = this.ensureArgument(
"codeServerVersion",
process.env.VERSION || require(path.join(this.rootPath, "package.json")).version,
)
}
public run(task: Task | undefined): void {
this.currentTask = task
this.doRun(task).catch((error) => {
console.error(error.message)
process.exit(1)
})
}
private async task<T>(message: string, fn: () => Promise<T>): Promise<T> {
const time = Date.now()
this.log(`${message}...`, true)
try {
const t = await fn()
process.stdout.write(`took ${Date.now() - time}ms\n`)
return t
} catch (error) {
process.stdout.write("failed\n")
throw error
}
}
/**
* Writes to stdout with an optional newline.
*/
private log(message: string, skipNewline = false): void {
process.stdout.write(`[${this.currentTask || "default"}] ${message}`)
if (!skipNewline) {
process.stdout.write("\n")
}
}
private async doRun(task: Task | undefined): Promise<void> {
if (!task) {
throw new Error("No task provided")
}
switch (task) {
case Task.Watch:
return this.watch()
case Task.Build:
return this.build()
default:
throw new Error(`No task matching "${task}"`)
}
}
/**
* Make sure the argument is set. Display the value if it is.
*/
private ensureArgument(name: string, arg?: string): string {
if (!arg) {
throw new Error(`${name} is missing`)
}
this.log(`${name} is "${arg}"`)
return arg
}
/**
* Build VS Code and code-server.
*/
private async build(): Promise<void> {
process.env.NODE_OPTIONS = "--max-old-space-size=32384 " + (process.env.NODE_OPTIONS || "")
process.env.NODE_ENV = "production"
await this.task("cleaning up old build", async () => {
if (!process.env.SKIP_VSCODE) {
return fs.remove(this.buildPath)
}
// If skipping VS Code, keep the existing build if any.
try {
const files = await fs.readdir(this.buildPath)
return Promise.all(files.filter((f) => f !== "lib").map((f) => fs.remove(path.join(this.buildPath, f))))
} catch (error) {
if (error.code !== "ENOENT") {
throw error
}
}
})
const commit = require(path.join(this.vscodeSourcePath, "build/lib/util")).getVersion(this.rootPath) as string
if (!process.env.SKIP_VSCODE) {
await this.buildVscode(commit)
} else {
this.log("skipping vs code build")
}
await this.buildCodeServer(commit)
this.log(`final build: ${this.buildPath}`)
}
private async buildCodeServer(commit: string): Promise<void> {
await this.task("building code-server", async () => {
return util.promisify(cp.exec)("tsc --outDir ./out-build --tsBuildInfoFile ./.prod.tsbuildinfo", {
cwd: this.rootPath,
})
})
await this.task("bundling code-server", async () => {
return this.createBundler("dist-build", commit).bundle()
})
await this.task("copying code-server into build directory", async () => {
await fs.mkdirp(this.buildPath)
await Promise.all([
fs.copy(path.join(this.rootPath, "out-build"), path.join(this.buildPath, "out")),
fs.copy(path.join(this.rootPath, "dist-build"), path.join(this.buildPath, "dist")),
// For source maps and images.
fs.copy(path.join(this.rootPath, "src"), path.join(this.buildPath, "src")),
])
})
await this.copyDependencies("code-server", this.rootPath, this.buildPath, {
commit,
version: this.codeServerVersion,
})
}
private async buildVscode(commit: string): Promise<void> {
await this.task("building vs code", () => {
return util.promisify(cp.exec)("yarn gulp compile-build", { cwd: this.vscodeSourcePath })
})
await this.task("building builtin extensions", async () => {
const exists = await fs.pathExists(path.join(this.vscodeSourcePath, ".build/extensions"))
if (exists) {
process.stdout.write("already built, skipping...")
} else {
await util.promisify(cp.exec)("yarn gulp compile-extensions-build", { cwd: this.vscodeSourcePath })
}
})
await this.task("optimizing vs code", async () => {
return util.promisify(cp.exec)("yarn gulp optimize --gulpfile ./coder.js", { cwd: this.vscodeSourcePath })
})
if (process.env.MINIFY) {
await this.task("minifying vs code", () => {
return util.promisify(cp.exec)("yarn gulp minify --gulpfile ./coder.js", { cwd: this.vscodeSourcePath })
})
}
const vscodeBuildPath = path.join(this.buildPath, "lib/vscode")
await this.task("copying vs code into build directory", async () => {
await fs.mkdirp(path.join(vscodeBuildPath, "resources/linux"))
await Promise.all([
fs.move(
path.join(this.vscodeSourcePath, `out-vscode${process.env.MINIFY ? "-min" : ""}`),
path.join(vscodeBuildPath, "out"),
),
fs.copy(path.join(this.vscodeSourcePath, ".build/extensions"), path.join(vscodeBuildPath, "extensions")),
fs.copy(
path.join(this.vscodeSourcePath, "resources/linux/code.png"),
path.join(vscodeBuildPath, "resources/linux/code.png"),
),
])
})
await this.copyDependencies("vs code", this.vscodeSourcePath, vscodeBuildPath, {
commit,
date: new Date().toISOString(),
})
}
private async copyDependencies(name: string, sourcePath: string, buildPath: string, merge: object): Promise<void> {
await this.task(`copying ${name} dependencies`, async () => {
return Promise.all(
["node_modules", "package.json", "yarn.lock"].map((fileName) => {
return fs.copy(path.join(sourcePath, fileName), path.join(buildPath, fileName))
}),
)
})
const fileName = name === "code-server" ? "package" : "product"
await this.task(`writing final ${name} ${fileName}.json`, async () => {
const json = JSON.parse(await fs.readFile(path.join(sourcePath, `${fileName}.json`), "utf8"))
return fs.writeFile(
path.join(buildPath, `${fileName}.json`),
JSON.stringify(
{
...json,
...merge,
},
null,
2,
),
)
})
if (process.env.MINIFY) {
await this.task(`restricting ${name} to production dependencies`, async () => {
await util.promisify(cp.exec)("yarn --production --ignore-scripts", { cwd: buildPath })
})
}
}
private async watch(): Promise<void> {
let server: cp.ChildProcess | undefined
const restartServer = (): void => {
if (server) {
server.kill()
}
const s = cp.fork(path.join(this.rootPath, "out/node/entry.js"), process.argv.slice(3))
console.log(`[server] spawned process ${s.pid}`)
s.on("exit", () => console.log(`[server] process ${s.pid} exited`))
server = s
}
const vscode = cp.spawn("yarn", ["watch"], { cwd: this.vscodeSourcePath })
const tsc = cp.spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath })
const bundler = this.createBundler()
const cleanup = (code?: number | null): void => {
this.log("killing vs code watcher")
vscode.removeAllListeners()
vscode.kill()
this.log("killing tsc")
tsc.removeAllListeners()
tsc.kill()
if (server) {
this.log("killing server")
server.removeAllListeners()
server.kill()
}
this.log("killing bundler")
process.exit(code || 0)
}
process.on("SIGINT", () => cleanup())
process.on("SIGTERM", () => cleanup())
vscode.on("exit", (code) => {
this.log("vs code watcher terminated unexpectedly")
cleanup(code)
})
tsc.on("exit", (code) => {
this.log("tsc terminated unexpectedly")
cleanup(code)
})
const bundle = bundler.bundle().catch(() => {
this.log("parcel watcher terminated unexpectedly")
cleanup(1)
})
bundler.on("buildEnd", () => {
console.log("[parcel] bundled")
})
bundler.on("buildError", (error) => {
console.error("[parcel]", error)
})
vscode.stderr.on("data", (d) => process.stderr.write(d))
tsc.stderr.on("data", (d) => process.stderr.write(d))
// From https://github.com/chalk/ansi-regex
const pattern = [
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))",
].join("|")
const re = new RegExp(pattern, "g")
/**
* Split stdout on newlines and strip ANSI codes.
*/
const onLine = (proc: cp.ChildProcess, callback: (strippedLine: string, originalLine: string) => void): void => {
let buffer = ""
if (!proc.stdout) {
throw new Error("no stdout")
}
proc.stdout.setEncoding("utf8")
proc.stdout.on("data", (d) => {
const data = buffer + d
const split = data.split("\n")
const last = split.length - 1
for (let i = 0; i < last; ++i) {
callback(split[i].replace(re, ""), split[i])
}
// The last item will either be an empty string (the data ended with a
// newline) or a partial line (did not end with a newline) and we must
// wait to parse it until we get a full line.
buffer = split[last]
})
}
let startingVscode = false
let startedVscode = false
onLine(vscode, (line, original) => {
console.log("[vscode]", original)
// Wait for watch-client since "Finished compilation" will appear multiple
// times before the client starts building.
if (!startingVscode && line.includes("Starting watch-client")) {
startingVscode = true
} else if (startingVscode && line.includes("Finished compilation")) {
if (startedVscode) {
bundle.then(restartServer)
}
startedVscode = true
}
})
onLine(tsc, (line, original) => {
// tsc outputs blank lines; skip them.
if (line !== "") {
console.log("[tsc]", original)
}
if (line.includes("Watching for file changes")) {
bundle.then(restartServer)
}
})
}
private createBundler(out = "dist", commit?: string): Bundler {
return new Bundler(
[
path.join(this.rootPath, "src/browser/pages/app.ts"),
path.join(this.rootPath, "src/browser/register.ts"),
path.join(this.rootPath, "src/browser/serviceWorker.ts"),
],
{
cache: true,
cacheDir: path.join(this.rootPath, ".cache"),
detailedReport: true,
minify: !!process.env.MINIFY,
hmr: false,
logLevel: 1,
outDir: path.join(this.rootPath, out),
publicUrl: `/static/${commit || "development"}/dist`,
target: "browser",
},
)
}
}
const builder = new Builder()
builder.run(process.argv[2] as Task)

12
ci/ci.sh Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/.."
yarn fmt
yarn lint
yarn test
}
main "$@"

11
ci/clean.sh Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
git clean -Xffd
git submodule foreach --recursive git clean -xffd
git submodule foreach --recursive git reset --hard
}
main "$@"

6
ci/code-server.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env sh
# code-server.sh -- Run code-server with the bundled Node binary.
cd "$(dirname "$0")" || exit 1
./node ./out/node/entry.js "$@"

32
ci/fmt.sh Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
shfmt -i 2 -w -s -sr $(git ls-files "*.sh")
local prettierExts
prettierExts=(
"*.js"
"*.ts"
"*.tsx"
"*.html"
"*.json"
"*.css"
"*.md"
"*.toml"
"*.yaml"
"*.yml"
)
prettier --write --loglevel=warn $(git ls-files "${prettierExts[@]}")
if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then
echo "Files need generation or are formatted incorrectly:"
git -c color.ui=always status | grep --color=no '\[31m'
echo "Please run the following locally:"
echo " yarn fmt"
exit 1
fi
}
main "$@"

10
ci/image/Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM node:12.14.0
RUN apt-get update && apt-get install -y \
libxkbfile-dev \
libx11-dev \
libsecret-1-dev
RUN curl -L https://github.com/mvdan/sh/releases/download/v3.0.1/shfmt_v3.0.1_linux_amd64 > /usr/local/bin/shfmt && chmod +x /usr/local/bin/shfmt
ENTRYPOINT ["/bin/bash", "-c"]

13
ci/image/run.sh Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
docker build ci/image
imageTag="$(docker build -q ci/image)"
docker run -t --rm -e CI -e GITHUB_TOKEN -e TRAVIS_TAG -v "$(yarn cache dir):/usr/local/share/.cache/yarn/v6" -v "$PWD:/repo" -w /repo "$imageTag" "$*"
}
main "$@"

BIN
ci/key.json.enc Normal file

Binary file not shown.

10
ci/lib.sh Normal file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
set_version() {
local code_server_version=${VERSION:-${TRAVIS_TAG:-}}
if [[ -z $code_server_version ]]; then
code_server_version=$(grep version ./package.json | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[:space:]')
fi
export VERSION=$code_server_version
}

11
ci/lint.sh Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js")
stylelint $(git ls-files "*.css")
tsc --noEmit
}
main "$@"

View File

@ -0,0 +1,38 @@
FROM debian:10
RUN apt-get update
RUN apt-get install -y curl
# https://wiki.debian.org/Locale#Manually
RUN apt-get install -y locales
RUN sed -i "s/# en_US.UTF-8/en_US.UTF-8/" /etc/locale.gen
RUN locale-gen
ENV LANG=en_US.UTF-8
RUN chsh -s /bin/bash
ENV SHELL=/bin/bash
RUN apt-get install -y dumb-init sudo
RUN apt-get install -y man procps vim nano htop ssh git
RUN adduser --gecos '' --disabled-password coder && \
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
RUN curl -SsL https://github.com/boxboat/fixuid/releases/download/v0.4/fixuid-0.4-linux-amd64.tar.gz | tar -C /usr/local/bin -xzf - && \
chown root:root /usr/local/bin/fixuid && \
chmod 4755 /usr/local/bin/fixuid && \
mkdir -p /etc/fixuid && \
printf "user: coder\ngroup: coder\n" > /etc/fixuid/config.yml
RUN rm -rf /var/lib/apt/lists/*
COPY release/code-server*.tar.gz /tmp
RUN cd /tmp && tar -xzf code-server*.tar.gz && rm code-server*.tar.gz && \
mv code-server* /usr/local/lib/code-server && \
sed 's/\$0/\/usr\/local\/lib\/code-server\/code-server/g' /usr/local/lib/code-server/code-server > /usr/local/bin/code-server && \
chmod +x /usr/local/bin/code-server
EXPOSE 8080
USER coder
WORKDIR /home/coder
ENTRYPOINT ["dumb-init", "fixuid", "-q", "/usr/local/bin/code-server", "--host", "0.0.0.0", "."]

22
ci/release-image/push.sh Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
source ./ci/lib.sh
set_version
if [[ ${CI:-} ]]; then
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
fi
imageTag="codercom/code-server:$VERSION"
if [[ ${TRAVIS_CPU_ARCH:-} == "arm64" ]]; then
imageTag+="-arm64"
fi
docker build -t "$imageTag" -f ./ci/release-image/Dockerfile .
docker push codercom/code-server
}
main "$@"

75
ci/release.sh Executable file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env bash
# ci.bash -- Build code-server in the CI.
set -euo pipefail
function package() {
local target
target=$(uname | tr '[:upper:]' '[:lower:]')
if [[ $target == "linux" ]]; then
# Alpine's ldd doesn't have a version flag but if you use an invalid flag
# (like --version) it outputs the version to stderr and exits with 1.
local ldd_output
ldd_output=$(ldd --version 2>&1 || true)
if echo "$ldd_output" | grep -iq musl; then
target="alpine"
fi
fi
local arch
arch="$(uname -m)"
echo -n "Creating release..."
cp "$(command -v node)" ./build
cp README.md ./build
cp LICENSE.txt ./build
cp ./lib/vscode/ThirdPartyNotices.txt ./build
cp ./ci/code-server.sh ./build/code-server
local archive_name="code-server-$VERSION-$target-$arch"
mkdir -p ./release
local ext
if [[ $target == "linux" ]]; then
ext=".tar.gz"
tar -czf "release/$archive_name$ext" --transform "s/^\.\/build/$archive_name/" ./build
else
mv ./build "./$archive_name"
ext=".zip"
zip -r "release/$archive_name$ext" ./code-server
mv "./$archive_name" ./build
fi
echo "done (release/$archive_name)"
mkdir -p "./release-upload/$VERSION"
cp "./release/$archive_name$ext" "./release-upload/$VERSION/$target-$arch.tar.gz"
mkdir -p "./release-upload/latest"
cp "./release/$archive_name$ext" "./release-upload/latest/$target-$arch.tar.gz"
}
# This script assumes that yarn has already ran.
function build() {
# Always minify and package on CI.
if [[ ${CI:-} ]]; then
export MINIFY="true"
fi
yarn build
}
function main() {
cd "$(dirname "${0}")/.."
source ./ci/lib.sh
set_version
build
if [[ ${CI:-} ]]; then
package
fi
}
main "$@"

4
ci/tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
"extends": "../tsconfig.json",
"include": ["./**/*.ts"]
}

3226
ci/vscode.patch Normal file

File diff suppressed because it is too large Load Diff

25
ci/vscode.sh Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
# 1. Ensures VS Code is cloned.
# 2. Patches it.
# 3. Installs it.
main() {
cd "$(dirname "$0")/.."
git submodule update --init
# If the patch fails to apply, then it's likely already applied
yarn vscode:patch &> /dev/null || true
(
cd lib/vscode
# Install VS Code dependencies.
yarn
# NODE_MODULE_VERSION mismatch errors without this.
npm rebuild
)
}
main "$@"

30
doc/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,30 @@
# Contributing
## Development Workflow
- [VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
```shell
yarn
yarn vscode
yarn watch # Visit http://localhost:8080 once completed.
```
Any changes made to the source will be live reloaded.
If changes are made to the patch and you've built previously you must manually
reset VS Code then run `yarn vscode:patch`.
Some docs are available at [../src/node/app](../src/node/app) on how code-server
works internally.
## Build
- [VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
```shell
yarn
yarn vscode
yarn build
node ./build/out/entry.js # Run the built JavaScript with Node.
```

110
doc/FAQ.md Normal file
View File

@ -0,0 +1,110 @@
# FAQ
## Questions?
Please file all questions and support requests at https://www.reddit.com/r/codeserver/
The issue tracker is only for bugs.
## What's the deal with extensions?
Unfortunately, the Microsoft VS Code Marketplace license prohibits use with any non Microsoft
product.
See https://cdn.vsassets.io/v/M146_20190123.39/_content/Microsoft-Visual-Studio-Marketplace-Terms-of-Use.pdf
> Marketplace Offerings are intended for use only with Visual Studio Products and Services
> and you may only install and use Marketplace Offerings with Visual Studio Products and Services.
As a result, Coder has created its own marketplace for open source extensions. It works by scraping
GitHub for VS Code extensions and building them. It's not perfect but getting better by the day with
more and more extensions.
Issue [https://github.com/cdr/code-server/issues/1299](#1299) is a big one in making the experience here
better by allowing the community to submit extensions and repos to avoid waiting until the scraper finds
an extension.
If an extension does not work, try to grab its VSIX from its Github releases or build it yourself and
copy it to the extensions folder.
## How is this different from VS Code Online?
VS Code Online is a closed source managed service by Microsoft and only runs on Azure.
code-server is open source and can be freely ran on any machine.
## How should I expose code-server to the internet?
By far the most secure method of using code-server is via
[sshcode](https://github.com/codercom/sshcode) as it runs code-server and then forwards
its port over SSH and requires no setup on your part other than having a working SSH server.
You can also forward your SSH key and GPG agent to the remote machine to securely access GitHub
and securely sign commits without duplicating your keys onto the the remote machine.
1. https://developer.github.com/v3/guides/using-ssh-agent-forwarding/
1. https://wiki.gnupg.org/AgentForwarding
If you cannot use sshcode, then you will need to ensure there is some sort of authorization in
front of code-server and that you are using HTTPS to secure all connections.
By default when listening externally, code-server enables password authentication using a
randomly generated password so you can use that. You can set the `PASSWORD` environment variable
to use your own instead. If you want to handle authentication yourself, use `--auth none`
to disable password authentication.
If you want to use external authentication you should handle this with a reverse
proxy using something like [oauth2_proxy](https://github.com/pusher/oauth2_proxy).
For HTTPS, you can use a self signed certificate by passing in just `--cert` or pass in an existing
certificate by providing the path to `--cert` and the path to its key with `--cert-key`.
If `code-server` has been passed a certificate it will also respond to HTTPS
requests and will redirect all HTTP requests to HTTPS. Otherwise it will respond
only to HTTP requests.
You can use [Let's Encrypt](https://letsencrypt.org/) to get an SSL certificate
for free.
## x86 releases?
node has dropped support for x86 and so we decided to as well. See
[nodejs/build/issues/885](https://github.com/nodejs/build/issues/885).
## Alpine builds?
Just install `libc-dev` and code-server should work.
## Multi Tenancy
If you want to run multiple code-server's on shared infrastructure, we recommend using virtual
machines with a VM per user. This will easily allow users to run a docker daemon. If you want
to use kubernetes, you'll definitely want to use [kubevirt](https://kubevirt.io) to give each
user a virtual machine instead of just a container. Docker in docker while supported requires
privileged containers which are a security risk in a multi tenant infrastructure.
## Docker in code-server docker container?
If you'd like to access docker inside of code-server, we'd recommend running a docker:dind container
and mounting in a directory to share between dind and the code-server container at /var/run. After, install
the docker CLI in the code-server container and you should be able to access the daemon as the socket
will be shared at /var/run/docker.sock.
In order to make volume mounts work, mount the home directory in the code-server container and the
dind container at the same path. i.e you'd volume mount a directory from the host to `/home/coder`
on both. This will allow any volume mounts in the home directory to work. Similar process
to make volume mounts in any other directory work.
## Collaboration
At the moment we have no plans for multi user collaboration on code-server but we understand there is strong
demand and will work on it when the time is right.
## How can I disable telemetry?
Use the `--disable-telemetry` flag to completely disable telemetry. We use the
data collected only to improve code-server.
## Enterprise
Visit [our enterprise page](https://coder.com) for more information about our
enterprise offerings.

BIN
doc/assets/code-server.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@ -1,75 +0,0 @@
# Installing code-server in your ChromiumOS/ChromeOS/CloudReady machine
This guide will show you how to install code-server into your CrOS machine.
## Using Crostini
One of the easier ways to run code-server is via
[Crostini](https://www.aboutchromebooks.com/tag/project-crostini/), the Linux
apps support feature in CrOS. Make sure you have enough RAM, HDD space and your
CPU has VT-x/ AMD-V support. If your chromebook has this, then you are
qualified to use Crostini.
If you are running R69, you might want to enable this on
[Chrome Flags](chrome://flags/#enable-experimental-crostini-ui).
If you run R72, however, this is already enabled for you.
After checking your prerequisites, follow the steps in [the self-host install guide](index.md)
on installing code-server. Once done, make sure code-server works by running
it. After running it, simply go to `penguin.linux.test:8080` to access
code-server. Now you should be greeted with this screen. If you did,
congratulations, you have installed code-server in your Chromebook!
![code-server on Chromebook](assets/cros.png)
Alternatively, if you ran code-server in another container and you need the IP
for that specific container, simply go to Termina's shell via `crosh` and type
`vsh termina`.
```bash
Loading extra module: /usr/share/crosh/dev.d/50-crosh.sh
Welcome to crosh, the Chrome OS developer shell.
If you got here by mistake, don't panic! Just close this tab and carry on.
Type 'help' for a list of commands.
If you want to customize the look/behavior, you can use the options page.
Load it by using the Ctrl+Shift+P keyboard shortcut.
crosh> vsh termina
(termina) chronos@localhost ~ $
```
While in termina, run `lxc list`. It should output the list of running containers.
```bash
(termina) chronos@localhost ~ $ lxc list
+---------|---------|-----------------------|------|------------|-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+---------|---------|-----------------------|------|------------|-----------+
| penguin | RUNNING | 100.115.92.199 (eth0) | | PERSISTENT | 0 |
+---------|---------|-----------------------|------|------------|-----------+
(termina) chronos@localhost ~ $
```
For this example, we show the default `penguin` container, which is exposed on
`eth0` at 100.115.92.199. Simply enter the IP of the container where the
code-server runs to Chrome.
## Using Crouton
[Crouton](https://github.com/dnschneid/crouton) is one of the old ways to get a
running full Linux via `chroot` on a Chromebook. To use crouton, enable
developer mode and go to `crosh`. This time, run `shell`, which should drop you
to `bash`.
Make sure you downloaded `crouton`, if so, go ahead and run it under
`~/Downloads`. After installing your chroot container via crouton, go ahead and
enter `enter-chroot` to enter your container.
Follow the instructions set in [the self-host install guide](index.md) to
install code-server. After that is done, run `code-server` and verify it works
by going to `localhost:8080`.
> At this point in writing, `localhost` seems to work in this method. However,
> the author is not sure if it applies still to newer Chromebooks.

View File

@ -1,73 +0,0 @@
# Set up instance
## EC2 on AWS
- Click **Launch Instance** from your [EC2 dashboard](https://console.aws.amazon.com/ec2/v2/home).
- Select the Ubuntu Server 18.04 LTS (HVM), SSD Volume Type
- Select an appropriate instance size (we recommend t2.medium/large, depending
on team size and number of repositories/languages enabled), then
**Next: Configure Instance Details**.
- Select **Next: ...** until you get to the **Configure Security Group** page,
then add a **Custom TCP Rule** rule with port range set to `8080` and source
set to "Anywhere".
> Rules with source of 0.0.0.0/0 allow all IP addresses to access your
> instance. We recommend setting [security group rules](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html?icmpid=docs_ec2_console)
> to allow access from known IP addresses only.
- Click **Launch**.
- You will be prompted to create a key pair.
- From the dropdown choose "create a new pair", give the key pair a name.
- Click **Download Key Pair** and store the file in a safe place.
- Click **Launch Instances**.
- Head to your [EC2 dashboard](https://console.aws.amazon.com/ec2/v2/home)
and choose instances from the left panel.
- In the description of your EC2 instance copy the public DNS (iPv4) address
using the copy to clipboard button.
- Open a terminal on your computer and SSH into your instance:
```
ssh -i ${path to key pair} ubuntu@${public address}
```
## DigitalOcean
[Open your DigitalOcean dashboard](https://cloud.digitalocean.com/droplets/new)
to create a new droplet
- **Choose an image -** Select the **Distributions** tab and then choose Ubuntu.
- **Choose a size -** We recommend at least 4GB RAM and 2 CPU, more depending
on team size and number of repositories/languages enabled.
- Launch your instance.
- Open a terminal on your computer and SSH into your instance:
```
ssh root@${instance ip}
```
## Google Cloud
> Pre-requisite: Set up the [Google Cloud SDK](https://cloud.google.com/sdk/docs/)
> on your local machine
- [Open your Google Cloud console](https://console.cloud.google.com/compute/instances)
to create a new VM instance and click **Create Instance**.
- Choose an appropriate machine type (we recommend 2 vCPU and 7.5 GB RAM, more
depending on team size and number of repositories/languages enabled).
- Choose Ubuntu 16.04 LTS as your boot disk.
- Expand the "Management, security, disks, networking, sole tenancy" section,
go to the "Networking" tab, then under network tags add "code-server".
- Create your VM, and **take note** of its public IP address.
- Visit "VPC network" in the console and go to "Firewall rules". Create a new
firewall rule called "http-8080". Under "Target tags" add "code-server", and
under "Protocols and ports" tick "Specified protocols and ports" and "tcp".
Beside "tcp", add "8080", then create the rule.
- Open a terminal on your computer and SSH into your Google Cloud VM:
```
gcloud compute ssh --zone ${region} ${instance name}
```
# Run code-server
- Download the latest code-server release from the
[releases page](https://github.com/cdr/code-server/releases/latest)
to the instance, extract the file, then run the code-server binary:
```
wget https://github.com/cdr/code-server/releases/download/{version}/code-server{version}-linux-x64.tar.gz
tar -xvzf code-server{version}-linux-x64.tar.gz
cd code-server{version}-linux-x64
./code-server
```
- Open your browser and visit http://$public_ip:8080/ where `$public_ip` is
your instance's public IP address.
- For long-term use, set up a systemd service to run code-server.

View File

@ -1,15 +0,0 @@
# Fail2Ban filter for code-server
[Definition]
failregex = ^Failed login attempt\s+{\"remoteAddress\":\"<HOST>\"
# Use this instead for proxies (ensure the proxy is configured to send the
# X-Forwarded-For header).
# failregex = ^Failed login attempt\s+{\"xForwardedFor\":\"<HOST>\"
ignoreregex =
datepattern = "timestamp":{EPOCH}}$
# Author: Dean Sheather

View File

@ -1,73 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: code-server
---
apiVersion: v1
kind: Service
metadata:
name: code-server
namespace: code-server
spec:
ports:
- port: 8080
name: https
protocol: TCP
selector:
app: code-server
type: ClusterIP
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: gp2
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
fsType: ext4
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: code-store
namespace: code-server
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 60Gi
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: code-server
name: code-server
namespace: code-server
spec:
selector:
matchLabels:
app: code-server
replicas: 1
template:
metadata:
labels:
app: code-server
spec:
containers:
- image: codercom/code-server:v2
imagePullPolicy: Always
name: code-servery
ports:
- containerPort: 8080
name: https
volumeMounts:
- name: code-server-storage
mountPath: /go/src
volumes:
- name: code-server-storage
persistentVolumeClaim:
claimName: code-store

View File

@ -1,43 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: code-server
---
apiVersion: v1
kind: Service
metadata:
name: code-server
namespace: code-server
spec:
ports:
- port: 8080
name: https
protocol: TCP
selector:
app: code-server
type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: code-server
name: code-server
namespace: code-server
spec:
selector:
matchLabels:
app: code-server
replicas: 1
template:
metadata:
labels:
app: code-server
spec:
containers:
- image: codercom/code-server:v2
imagePullPolicy: Always
name: code-server
ports:
- containerPort: 8080
name: https

View File

@ -1,35 +0,0 @@
# Protecting code-server from bruteforce attempts
code-server outputs all failed login attempts, along with the IP address,
provided password, user agent and timestamp by default.
When using a reverse proxy such as Nginx or Apache, the remote address may
appear to be `127.0.0.1` or a similar address so `X-Forwarded-For` should be
used instead. Ensure that you are setting this value in your reverse proxy:
Nginx:
```
location / {
...
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
...
}
```
Apache:
```
<VirtualEnv>
...
SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded
...
</VirtualEnv>
```
It is extremely important that you ensure that your code-server instance is not
accessible from the internet (use localhost or block it in your firewall).
## Fail2Ban
Fail2Ban allows for automatically banning and logging repeated failed
authentication attempts for many applications through regex filters. A working
filter for code-server can be found in `./examples/fail2ban.conf`. Once this
is installed and configured correctly, repeated failed login attempts should
automatically be banned from connecting to your server.

View File

@ -1,98 +0,0 @@
# Quickstart Guide
1. Visit the [releases page](https://github.com/cdr/code-server/releases) and
download the latest binary for your operating system.
2. Unpack the downloaded file then run the binary.
3. In your browser navigate to `localhost:8080`.
## Usage
Run `code-server --help` to view available options.
### Nginx Reverse Proxy
The trailing slashes are important.
```
server {
listen 80;
listen [::]:80;
server_name code.example.com code.example.org;
location /some/path/ { # Or / if hosting at the root.
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Accept-Encoding gzip;
}
}
```
### Apache Reverse Proxy
```
<VirtualHost *:80>
ServerName code.example.com
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://localhost:8080/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) http://localhost:8080/$1 [P,L]
ProxyRequests off
ProxyPass / http://localhost:8080/ nocanon
ProxyPassReverse / http://localhost:8080/
</VirtualHost>
```
### Run automatically at startup
In some cases you might need to run code-server automatically once the host starts. You may use your local init service to do so.
#### Systemd
```ini
[Unit]
Description=Code Server IDE
After=network.target
[Service]
Type=simple
User=<USER>
EnvironmentFile=$HOME/.profile
WorkingDirectory=$HOME
Restart=on-failure
RestartSec=10
ExecStart=<PATH TO BINARY> $(pwd)
StandardOutput=file:/var/log/code-server-output.log
StandardError=file:/var/log/code-server-error.log
[Install]
WantedBy=multi-user.target
```
#### OpenRC
```sh
#!/sbin/openrc-run
depend() {
after net-online
need net
}
supervisor=supervise-daemon
name="code-server"
command="/opt/cdr/code-server"
command_args=""
pidfile="/var/run/cdr.pid"
respawn_delay=5
set -o allexport
if [ -f /etc/environment ]; then source /etc/environment; fi
set +o allexport
```
#### Kubernetes/Docker
Make sure you set your restart policy to always - this will ensure your container starts as the daemon starts.

View File

@ -1,13 +0,0 @@
version: "3"
services:
code-server:
container_name: code-server
image: codercom/code-server
ports:
- "8080:8080"
volumes:
- "${PWD}:/home/coder/project"
- "${HOME}/.local/share/code-server:/home/coder/.local/share/code-server"
environment:
PASSWORD: ${PASSWORD}

1
lib/vscode Submodule

@ -0,0 +1 @@
Subproject commit ae08d5460b5a45169385ff3fd44208f431992451

View File

@ -1,7 +0,0 @@
// Once our entry file is loaded we no longer need nbin to bypass normal Node
// execution. We can still shim the fs into the binary even when bypassing. This
// will ensure for example that a spawn like `${process.argv[0]} -e` will work
// while still allowing us to access files within the binary.
process.env.NBIN_BYPASS = true;
require("../../bootstrap-amd").load("vs/server/src/node/cli");

View File

@ -1,42 +1,60 @@
{
"name": "code-server",
"license": "MIT",
"version": "3.0.0",
"scripts": {
"runner": "cd ./scripts && node --max-old-space-size=32384 -r ts-node/register ./build.ts",
"start": "nodemon --watch ../../../out --verbose ../../../out/vs/server/main.js",
"test": "./scripts/test.sh",
"watch": "cd ../../../ && yarn watch",
"build": "yarn && yarn runner build",
"package": "yarn runner package",
"binary": "yarn runner binary",
"patch:generate": "cd ../../../ && git diff --staged > ./src/vs/server/scripts/vscode.patch",
"patch:apply": "cd ../../../ && git apply ./src/vs/server/scripts/vscode.patch"
"clean": "ci/clean.sh",
"vscode": "ci/vscode.sh",
"vscode:patch": "cd ./lib/vscode && git apply ../../ci/vscode.patch",
"vscode:diff": "cd ./lib/vscode && git diff HEAD > ../../ci/vscode.patch",
"test": "mocha -r ts-node/register ./test/*.test.ts",
"lint": "ci/lint.sh",
"fmt": "ci/fmt.sh",
"runner": "cd ./ci && NODE_OPTIONS=--max_old_space_size=32384 ts-node ./build.ts",
"build": "yarn runner build",
"watch": "yarn runner watch"
},
"devDependencies": {
"@coder/nbin": "^1.2.7",
"@types/adm-zip": "^0.4.32",
"@types/fs-extra": "^8.0.1",
"@types/node": "^10.12.12",
"@types/mocha": "^5.2.7",
"@types/node": "^12.12.7",
"@types/parcel-bundler": "^1.12.1",
"@types/pem": "^1.9.5",
"@types/safe-compare": "^1.1.0",
"@types/tar-fs": "^1.16.1",
"@types/tar-stream": "^1.6.1",
"fs-extra": "^8.1.0",
"nodemon": "^1.19.1",
"@types/semver": "^7.1.0",
"@types/tar-fs": "^1.16.2",
"@types/ws": "^6.0.4",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
"eslint": "^6.2.0",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-prettier": "^3.1.0",
"leaked-handles": "^5.2.0",
"mocha": "^6.2.0",
"parcel-bundler": "^1.12.4",
"prettier": "^1.18.2",
"stylelint": "^13.0.0",
"stylelint-config-recommended": "^3.0.0",
"ts-node": "^8.4.1",
"typescript": "3.6"
"typescript": "3.7.2"
},
"resolutions": {
"@types/node": "^10.12.12",
"safe-buffer": "^5.1.1"
"@types/node": "^12.12.7",
"safe-buffer": "^5.1.1",
"vfile-message": "^2.0.2"
},
"dependencies": {
"@coder/logger": "^1.1.12",
"@coder/node-browser": "^1.0.6",
"@coder/requirefs": "^1.0.6",
"@coder/logger": "1.1.11",
"adm-zip": "^0.4.14",
"fs-extra": "^8.1.0",
"httpolyglot": "^0.1.2",
"pem": "^1.14.2",
"safe-compare": "^1.1.4",
"semver": "^7.1.3",
"tar": "^6.0.1",
"tar-fs": "^2.0.0",
"tar-stream": "^2.1.0",
"util": "^0.12.1"
"ws": "^7.2.0"
}
}

View File

@ -1,391 +0,0 @@
import { Binary } from "@coder/nbin";
import * as cp from "child_process";
// import * as crypto from "crypto";
import * as fs from "fs-extra";
import * as os from "os";
import * as path from "path";
import * as util from "util";
enum Task {
/**
* Use before running anything that only works inside VS Code.
*/
EnsureInVscode = "ensure-in-vscode",
Binary = "binary",
Package = "package",
Build = "build",
}
class Builder {
private readonly rootPath = path.resolve(__dirname, "..");
private readonly outPath = process.env.OUT || this.rootPath;
private _target?: "darwin" | "alpine" | "linux";
private currentTask?: Task;
public run(task: Task | undefined, args: string[]): void {
this.currentTask = task;
this.doRun(task, args).catch((error) => {
console.error(error.message);
process.exit(1);
});
}
private async task<T>(message: string, fn: () => Promise<T>): Promise<T> {
const time = Date.now();
this.log(`${message}...`, true);
try {
const t = await fn();
process.stdout.write(`took ${Date.now() - time}ms\n`);
return t;
} catch (error) {
process.stdout.write("failed\n");
throw error;
}
}
/**
* Writes to stdout with an optional newline.
*/
private log(message: string, skipNewline: boolean = false): void {
process.stdout.write(`[${this.currentTask || "default"}] ${message}`);
if (!skipNewline) {
process.stdout.write("\n");
}
}
private async doRun(task: Task | undefined, args: string[]): Promise<void> {
if (!task) {
throw new Error("No task provided");
}
if (task === Task.EnsureInVscode) {
return process.exit(this.isInVscode(this.rootPath) ? 0 : 1);
}
// If we're inside VS Code assume we want to develop. In that case we should
// set an OUT directory and not build in this directory, otherwise when you
// build/watch VS Code the build directory will be included.
if (this.isInVscode(this.outPath)) {
throw new Error("Should not build inside VS Code; set the OUT environment variable");
}
this.ensureArgument("rootPath", this.rootPath);
this.ensureArgument("outPath", this.outPath);
const arch = this.ensureArgument("arch", os.arch().replace(/^x/, "x86_"));
const target = this.ensureArgument("target", await this.target());
const vscodeVersion = this.ensureArgument("vscodeVersion", args[0]);
const codeServerVersion = this.ensureArgument("codeServerVersion", args[1]);
const vscodeSourcePath = path.join(this.outPath, "source", `vscode-${vscodeVersion}-source`);
const binariesPath = path.join(this.outPath, "binaries");
const binaryName = `code-server${codeServerVersion}-vsc${vscodeVersion}-${target}-${arch}`;
const finalBuildPath = path.join(this.outPath, "build", `${binaryName}-built`);
switch (task) {
case Task.Binary:
return this.binary(finalBuildPath, binariesPath, binaryName);
case Task.Package:
return this.package(vscodeSourcePath, binariesPath, binaryName);
case Task.Build:
return this.build(vscodeSourcePath, vscodeVersion, codeServerVersion, finalBuildPath);
default:
throw new Error(`No task matching "${task}"`);
}
}
/**
* Get the target of the system.
*/
private async target(): Promise<"darwin" | "alpine" | "linux"> {
if (!this._target) {
if (os.platform() === "darwin" || (process.env.OSTYPE && /^darwin/.test(process.env.OSTYPE))) {
this._target = "darwin";
} else {
// Alpine's ldd doesn't have a version flag but if you use an invalid flag
// (like --version) it outputs the version to stderr and exits with 1.
const result = await util.promisify(cp.exec)("ldd --version")
.catch((error) => ({ stderr: error.message, stdout: "" }));
if (/musl/.test(result.stderr) || /musl/.test(result.stdout)) {
this._target = "alpine";
} else {
this._target = "linux";
}
}
}
return this._target;
}
/**
* Make sure the argument is set. Display the value if it is.
*/
private ensureArgument(name: string, arg?: string): string {
if (!arg) {
this.log(`${name} is missing`);
throw new Error("Usage: <vscodeVersion> <codeServerVersion>");
}
this.log(`${name} is "${arg}"`);
return arg;
}
/**
* Return true if it looks like we're inside VS Code. This is used to prevent
* accidentally building inside VS Code while developing which causes issues
* because the watcher will try compiling those built files.
*/
private isInVscode(pathToCheck: string): boolean {
let inside = false;
const maybeVsCode = path.join(pathToCheck, "../../../");
try {
// If it has a package.json with the right name it's probably VS Code.
inside = require(path.join(maybeVsCode, "package.json")).name === "code-oss-dev";
} catch (error) {}
this.log(
inside
? `Running inside VS Code ([${maybeVsCode}]${path.relative(maybeVsCode, pathToCheck)})`
: "Not running inside VS Code"
);
return inside;
}
/**
* Build code-server within VS Code.
*/
private async build(vscodeSourcePath: string, vscodeVersion: string, codeServerVersion: string, finalBuildPath: string): Promise<void> {
// Install dependencies (should be cached by CI).
await this.task("Installing code-server dependencies", async () => {
await util.promisify(cp.exec)("yarn", { cwd: this.rootPath });
});
// Download and prepare VS Code if necessary (should be cached by CI).
if (fs.existsSync(vscodeSourcePath)) {
this.log("Using existing VS Code clone");
} else {
await this.task("Cloning VS Code", () => {
return util.promisify(cp.exec)(
"git clone https://github.com/microsoft/vscode"
+ ` --quiet --branch "${vscodeVersion}"`
+ ` --single-branch --depth=1 "${vscodeSourcePath}"`);
});
}
await this.task("Installing VS Code dependencies", () => {
return util.promisify(cp.exec)("yarn", { cwd: vscodeSourcePath });
});
if (fs.existsSync(path.join(vscodeSourcePath, ".build/extensions"))) {
this.log("Using existing built-in-extensions");
} else {
await this.task("Building default extensions", () => {
return util.promisify(cp.exec)(
"yarn gulp compile-extensions-build --max-old-space-size=32384",
{ cwd: vscodeSourcePath },
);
});
}
// Clean before patching or it could fail if already patched.
await this.task("Patching VS Code", async () => {
await util.promisify(cp.exec)("git reset --hard", { cwd: vscodeSourcePath });
await util.promisify(cp.exec)("git clean -fd", { cwd: vscodeSourcePath });
await util.promisify(cp.exec)(`git apply ${this.rootPath}/scripts/vscode.patch`, { cwd: vscodeSourcePath });
});
const serverPath = path.join(vscodeSourcePath, "src/vs/server");
await this.task("Copying code-server into VS Code", async () => {
await fs.remove(serverPath);
await fs.mkdirp(serverPath);
await Promise.all(["main.js", "node_modules", "src", "typings"].map((fileName) => {
return fs.copy(path.join(this.rootPath, fileName), path.join(serverPath, fileName));
}));
});
await this.task("Building VS Code", () => {
return util.promisify(cp.exec)("yarn gulp compile-build --max-old-space-size=32384", { cwd: vscodeSourcePath });
});
await this.task("Optimizing VS Code", async () => {
await fs.copyFile(path.join(this.rootPath, "scripts/optimize.js"), path.join(vscodeSourcePath, "coder.js"));
await util.promisify(cp.exec)(`yarn gulp optimize --max-old-space-size=32384 --gulpfile ./coder.js`, { cwd: vscodeSourcePath });
});
const { productJson, packageJson } = await this.task("Generating final package.json and product.json", async () => {
const merge = async (name: string, extraJson: { [key: string]: string } = {}): Promise<{ [key: string]: string }> => {
const [aJson, bJson] = (await Promise.all([
fs.readFile(path.join(vscodeSourcePath, `${name}.json`), "utf8"),
fs.readFile(path.join(this.rootPath, `scripts/${name}.json`), "utf8"),
])).map((raw) => {
const json = JSON.parse(raw);
delete json.scripts;
delete json.dependencies;
delete json.devDependencies;
delete json.optionalDependencies;
return json;
});
return { ...aJson, ...bJson, ...extraJson };
};
const date = new Date().toISOString();
const commit = require(path.join(vscodeSourcePath, "build/lib/util")).getVersion(this.rootPath);
const [productJson, packageJson] = await Promise.all([
merge("product", { commit, date }),
merge("package", { codeServerVersion: `${codeServerVersion}-vsc${vscodeVersion}` }),
]);
// We could do this before the optimization but then it'd be copied into
// three files and unused in two which seems like a waste of bytes.
const apiPath = path.join(vscodeSourcePath, "out-vscode/vs/workbench/workbench.web.api.js");
await fs.writeFile(apiPath, (await fs.readFile(apiPath, "utf8")).replace('{ /*BUILD->INSERT_PRODUCT_CONFIGURATION*/}', JSON.stringify({
version: packageJson.version,
codeServerVersion: packageJson.codeServerVersion,
...productJson,
})));
return { productJson, packageJson };
});
if (process.env.MINIFY) {
await this.task("Minifying VS Code", () => {
return util.promisify(cp.exec)("yarn gulp minify --max-old-space-size=32384 --gulpfile ./coder.js", { cwd: vscodeSourcePath });
});
}
const finalServerPath = path.join(finalBuildPath, "out/vs/server");
await this.task("Copying into final build directory", async () => {
await fs.remove(finalBuildPath);
await fs.mkdirp(finalBuildPath);
await Promise.all([
fs.copy(path.join(vscodeSourcePath, "remote/node_modules"), path.join(finalBuildPath, "node_modules")),
fs.copy(path.join(vscodeSourcePath, ".build/extensions"), path.join(finalBuildPath, "extensions")),
fs.copy(path.join(vscodeSourcePath, `out-vscode${process.env.MINIFY ? "-min" : ""}`), path.join(finalBuildPath, "out")).then(() => {
return Promise.all([
fs.remove(path.join(finalServerPath, "node_modules")).then(() => {
return fs.copy(path.join(serverPath, "node_modules"), path.join(finalServerPath, "node_modules"));
}),
fs.copy(path.join(finalServerPath, "src/browser/workbench-build.html"), path.join(finalServerPath, "src/browser/workbench.html")),
]);
}),
]);
});
if (process.env.MINIFY) {
await this.task("Restricting to production dependencies", async () => {
await Promise.all(["package.json", "yarn.lock"].map((fileName) => {
Promise.all([
fs.copy(path.join(this.rootPath, fileName), path.join(finalServerPath, fileName)),
fs.copy(path.join(path.join(vscodeSourcePath, "remote"), fileName), path.join(finalBuildPath, fileName)),
]);
}));
await Promise.all([finalServerPath, finalBuildPath].map((cwd) => {
return util.promisify(cp.exec)("yarn --production", { cwd });
}));
await Promise.all(["package.json", "yarn.lock"].map((fileName) => {
return Promise.all([
fs.remove(path.join(finalServerPath, fileName)),
fs.remove(path.join(finalBuildPath, fileName)),
]);
}));
});
}
await this.task("Writing final package.json and product.json", () => {
return Promise.all([
fs.writeFile(path.join(finalBuildPath, "package.json"), JSON.stringify(packageJson, null, 2)),
fs.writeFile(path.join(finalBuildPath, "product.json"), JSON.stringify(productJson, null, 2)),
]);
});
// Prevent needless cache changes.
await this.task("Cleaning for smaller cache", () => {
return Promise.all([
fs.remove(serverPath),
fs.remove(path.join(vscodeSourcePath, "out-vscode")),
fs.remove(path.join(vscodeSourcePath, "out-vscode-min")),
fs.remove(path.join(vscodeSourcePath, "out-build")),
util.promisify(cp.exec)("git reset --hard", { cwd: vscodeSourcePath }).then(() => {
return util.promisify(cp.exec)("git clean -fd", { cwd: vscodeSourcePath });
}),
]);
});
// Prepend code to the target which enables finding files within the binary.
const prependLoader = async (relativeFilePath: string): Promise<void> => {
const filePath = path.join(finalBuildPath, relativeFilePath);
const shim = `
if (!global.NBIN_LOADED) {
try {
const nbin = require("nbin");
nbin.shimNativeFs("${finalBuildPath}");
global.NBIN_LOADED = true;
const path = require("path");
const rg = require("vscode-ripgrep");
rg.binaryRgPath = rg.rgPath;
rg.rgPath = path.join(require("os").tmpdir(), "code-server", path.basename(rg.binaryRgPath));
} catch (error) { /* Not in the binary. */ }
}
`;
await fs.writeFile(filePath, shim + (await fs.readFile(filePath, "utf8")));
};
await this.task("Prepending nbin loader", () => {
return Promise.all([
prependLoader("out/vs/server/main.js"),
prependLoader("out/bootstrap-fork.js"),
prependLoader("extensions/node_modules/typescript/lib/tsserver.js"),
]);
});
this.log(`Final build: ${finalBuildPath}`);
}
/**
* Bundles the built code into a binary.
*/
private async binary(targetPath: string, binariesPath: string, binaryName: string): Promise<void> {
const bin = new Binary({
mainFile: path.join(targetPath, "out/vs/server/main.js"),
target: await this.target(),
});
bin.writeFiles(path.join(targetPath, "**"));
await fs.mkdirp(binariesPath);
const binaryPath = path.join(binariesPath, binaryName);
await fs.writeFile(binaryPath, await bin.build());
await fs.chmod(binaryPath, "755");
this.log(`Binary: ${binaryPath}`);
}
/**
* Package the binary into a release archive.
*/
private async package(vscodeSourcePath: string, binariesPath: string, binaryName: string): Promise<void> {
const releasePath = path.join(this.outPath, "release");
const archivePath = path.join(releasePath, binaryName);
await fs.remove(archivePath);
await fs.mkdirp(archivePath);
await fs.copyFile(path.join(binariesPath, binaryName), path.join(archivePath, "code-server"));
await fs.copyFile(path.join(this.rootPath, "README.md"), path.join(archivePath, "README.md"));
await fs.copyFile(path.join(vscodeSourcePath, "LICENSE.txt"), path.join(archivePath, "LICENSE.txt"));
await fs.copyFile(path.join(vscodeSourcePath, "ThirdPartyNotices.txt"), path.join(archivePath, "ThirdPartyNotices.txt"));
if ((await this.target()) === "darwin") {
await util.promisify(cp.exec)(`zip -r "${binaryName}.zip" "${binaryName}"`, { cwd: releasePath });
this.log(`Archive: ${archivePath}.zip`);
} else {
await util.promisify(cp.exec)(`tar -czf "${binaryName}.tar.gz" "${binaryName}"`, { cwd: releasePath });
this.log(`Archive: ${archivePath}.tar.gz`);
}
}
}
const builder = new Builder();
builder.run(process.argv[2] as Task, process.argv.slice(3));

View File

@ -1,53 +0,0 @@
#!/usr/bin/env sh
# cacher.sh -- Restore and rebuild cache.
# Cache paths are designed to work with multi-arch builds and are organized
# based on the branch or tag. The master branch cache is used as a fallback.
# This will download and package the cache but it will not upload it.
set -eu
# Try restoring from each argument in turn until we get something.
restore() {
for branch in "$@" ; do
if [ -n "$branch" ] ; then
cache_path="https://codesrv-ci.cdr.sh/cache/$branch/$tar.tar.gz"
if wget "$cache_path" ; then
tar xzvf "$tar.tar.gz"
break
fi
fi
done
}
# We need to cache the built-in extensions and Node modules. Everything inside
# the cache-upload directory will be uploaded as-is to the code-server bucket.
package() {
mkdir -p "cache-upload/cache/$1"
tar czfv "cache-upload/cache/$1/$tar.tar.gz" node_modules source yarn-cache
}
main() {
cd "$(dirname "$0")/.."
# Get the branch for this build.
branch=${DRONE_BRANCH:-${DRONE_SOURCE_BRANCH:-${DRONE_TAG:-}}}
# The cache will be named based on the arch, platform, and libc.
arch=$DRONE_STAGE_ARCH
platform=${PLATFORM:-linux}
case $DRONE_STAGE_NAME in
*alpine*) libc=musl ;;
* ) libc=glibc ;;
esac
tar="$platform-$arch-$libc"
# The action is determined by the name of the step.
case $DRONE_STEP_NAME in
*restore*) restore "$branch" "$DRONE_REPO_BRANCH" ;;
*rebuild*|*package*) package "$branch" ;;
*) exit 1 ;;
esac
}
main "$@"

View File

@ -1,73 +0,0 @@
#!/usr/bin/env bash
# ci.bash -- Build code-server in the CI.
set -euo pipefail
function target() {
local os=$(uname | tr '[:upper:]' '[:lower:]')
if [[ "$os" == "linux" ]]; then
# Using the same strategy to detect Alpine as build.ts.
local ldd_output=$(ldd --version 2>&1 || true)
if echo "$ldd_output" | grep -iq musl; then
os="alpine"
fi
fi
echo "${os}-$(uname -m)"
}
function main() {
cd "$(dirname "${0}")/.."
# Get the version information. If a specific version wasn't set, generate it
# from the tag and VS Code version.
local vscode_version=${VSCODE_VERSION:-1.42.0}
local code_server_version=${VERSION:-${TRAVIS_TAG:-${DRONE_TAG:-daily}}}
# Remove everything that isn't the current VS Code source for caching
# (otherwise the cache will contain old versions).
if [[ -d "source/vscode-$vscode_version-source" ]] ; then
mv "source/vscode-$vscode_version-source" "vscode-$vscode_version-source"
fi
rm -rf source/vscode-*-source
if [[ -d "vscode-$vscode_version-source" ]] ; then
mv "vscode-$vscode_version-source" "source/vscode-$vscode_version-source"
fi
YARN_CACHE_FOLDER="$(pwd)/yarn-cache"
export YARN_CACHE_FOLDER
# Always minify and package on tags since that's when releases are pushed.
if [[ -n ${DRONE_TAG:-} || -n ${TRAVIS_TAG:-} ]] ; then
export MINIFY="true"
export PACKAGE="true"
fi
function run-yarn() {
yarn "$1" "$vscode_version" "$code_server_version"
}
run-yarn build
run-yarn binary
if [[ -n ${PACKAGE:-} ]] ; then
run-yarn package
fi
# In this case provide a plainly named "code-server" binary.
if [[ -n ${BINARY:-} ]] ; then
mv binaries/code-server*-vsc* binaries/code-server
fi
# Prepare GCS bucket directory on release.
if [[ -n ${DRONE_TAG:-} || -n ${TRAVIS_TAG:-} ]] ; then
local gcp_dir="gcs_bucket/releases/$code_server_version/$(target)"
mkdir -p "$gcp_dir"
mv binaries/code-server*-vsc* "$gcp_dir"
if [[ "$(target)" == "linux-x86_64" ]] ; then
mv binaries/code-server*-vsc* "gcs_bucket/latest-linux"
fi
fi
}
main "$@"

View File

@ -1,39 +0,0 @@
# We deploy with Ubuntu so that devs have a familiar environment.
FROM ubuntu:18.04
RUN apt-get update && apt-get install -y \
openssl \
net-tools \
git \
locales \
sudo \
dumb-init \
vim \
curl \
wget
RUN locale-gen en_US.UTF-8
# We cannot use update-locale because docker will not use the env variables
# configured in /etc/default/locale so we need to set it manually.
ENV LC_ALL=en_US.UTF-8 \
SHELL=/bin/bash
RUN adduser --gecos '' --disabled-password coder && \
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
USER coder
# Create first so these directories will be owned by coder instead of root
# (workdir and mounting appear to both default to root).
RUN mkdir -p /home/coder/project
RUN mkdir -p /home/coder/.local/share/code-server
WORKDIR /home/coder/project
# This ensures we have a volume mounted even if the user forgot to do bind
# mount. So that they do not lose their data if they delete the container.
VOLUME [ "/home/coder/project" ]
COPY ./binaries/code-server* /usr/local/bin/code-server
EXPOSE 8080
ENTRYPOINT ["dumb-init", "code-server", "--host", "0.0.0.0"]

View File

@ -1,71 +0,0 @@
// This must be ran from VS Code's root.
const gulp = require("gulp");
const path = require("path");
const _ = require("underscore");
const buildfile = require("./src/buildfile");
const common = require("./build/lib/optimize");
const util = require("./build/lib/util");
const deps = require("./build/dependencies");
const vscodeEntryPoints = _.flatten([
buildfile.entrypoint("vs/workbench/workbench.web.api"),
buildfile.entrypoint("vs/server/src/node/cli"),
buildfile.base,
buildfile.workbenchWeb,
buildfile.workerExtensionHost,
buildfile.keyboardMaps,
buildfile.entrypoint('vs/platform/files/node/watcher/unix/watcherApp', ["vs/css", "vs/nls"]),
buildfile.entrypoint('vs/platform/files/node/watcher/nsfw/watcherApp', ["vs/css", "vs/nls"]),
buildfile.entrypoint('vs/workbench/services/extensions/node/extensionHostProcess', ["vs/css", "vs/nls"]),
]);
const vscodeResources = [
"out-build/vs/server/main.js",
"out-build/vs/server/src/node/uriTransformer.js",
"!out-build/vs/server/doc/**",
"out-build/vs/server/src/media/*",
"out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js",
"out-build/bootstrap.js",
"out-build/bootstrap-fork.js",
"out-build/bootstrap-amd.js",
"out-build/paths.js",
'out-build/vs/**/*.{svg,png,html}',
"!out-build/vs/code/browser/workbench/*.html",
'!out-build/vs/code/electron-browser/**',
"out-build/vs/base/common/performance.js",
"out-build/vs/base/node/languagePacks.js",
"out-build/vs/base/browser/ui/octiconLabel/octicons/**",
"out-build/vs/base/browser/ui/codiconLabel/codicon/**",
"out-build/vs/workbench/browser/media/*-theme.css",
"out-build/vs/workbench/contrib/debug/**/*.json",
"out-build/vs/workbench/contrib/externalTerminal/**/*.scpt",
"out-build/vs/workbench/contrib/webview/browser/pre/*.js",
"out-build/vs/**/markdown.css",
"out-build/vs/workbench/contrib/tasks/**/*.json",
"out-build/vs/platform/files/**/*.md",
"!**/test/**"
];
const rootPath = __dirname;
const nodeModules = ["electron", "original-fs"]
.concat(_.uniq(deps.getProductionDependencies(rootPath).map((d) => d.name)))
.concat(_.uniq(deps.getProductionDependencies(path.join(rootPath, "src/vs/server")).map((d) => d.name)))
.concat(Object.keys(process.binding("natives")).filter((n) => !/^_|\//.test(n)));
gulp.task("optimize", gulp.series(
util.rimraf("out-vscode"),
common.optimizeTask({
src: "out-build",
entryPoints: vscodeEntryPoints,
resources: vscodeResources,
loaderConfig: common.loaderConfig(nodeModules),
out: "out-vscode",
inlineAmdImages: true,
bundleInfo: undefined
}),
));
gulp.task("minify", gulp.series(
util.rimraf("out-vscode-min"),
common.minifyTask("out-vscode")
));

View File

@ -1,5 +0,0 @@
{
"name": "code-server",
"main": "out/vs/server/main",
"desktopName": "code-server-url-handler.desktop"
}

View File

@ -1,21 +0,0 @@
{
"nameShort": "code-server",
"nameLong": "code-server",
"applicationName": "code-server",
"dataFolderName": ".code-server",
"win32MutexName": "codeserver",
"win32DirName": "Code Server",
"win32NameVersion": "Code Server",
"win32RegValueName": "CodeServer",
"win32AppId": "",
"win32x64AppId": "",
"win32UserAppId": "",
"win32x64UserAppId": "",
"win32AppUserModelId": "CodeServer",
"win32ShellNameShort": "C&ode Server",
"darwinBundleIdentifier": "com.code.server",
"linuxIconName": "com.code.server",
"urlProtocol": "code-server",
"updateUrl": "https://api.github.com/repos/cdr/code-server/releases",
"quality": "latest"
}

View File

@ -1,19 +0,0 @@
#!/usr/bin/env sh
# test.sh -- Simple test for CI.
# We'll have more involved tests eventually. This just ensures the binary has
# been built and runs.
set -eu
main() {
cd "$(dirname "$0")/.."
version=$(./binaries/code-server* --version | head -1)
echo "Got '$version' for the version"
case $version in
*-vsc1.42.0) exit 0 ;;
*) exit 1 ;;
esac
}
main "$@"

View File

@ -1,17 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": true,
"experimentalDecorators": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noImplicitThis": true,
"alwaysStrict": true,
"strictBindCallApply": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"target": "esnext"
}
}

View File

@ -1,996 +0,0 @@
diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts
index a68e020f9f..b4ee8a5886 100644
--- a/src/vs/base/common/network.ts
+++ b/src/vs/base/common/network.ts
@@ -88,7 +88,7 @@ class RemoteAuthoritiesImpl {
if (host && host.indexOf(':') !== -1) {
host = `[${host}]`;
}
- const port = this._ports[authority];
+ // const port = this._ports[authority];
const connectionToken = this._connectionTokens[authority];
let query = `path=${encodeURIComponent(uri.path)}`;
if (typeof connectionToken === 'string') {
@@ -96,8 +96,8 @@ class RemoteAuthoritiesImpl {
}
return URI.from({
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
- authority: `${host}:${port}`,
- path: `/vscode-remote-resource`,
+ authority: window.location.host,
+ path: `${window.location.pathname.replace(/\/+$/, '')}/vscode-remote-resource`,
query
});
}
diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts
index 5a631e0b39..8a2b1518d6 100644
--- a/src/vs/base/common/platform.ts
+++ b/src/vs/base/common/platform.ts
@@ -59,6 +59,17 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
_isWeb = true;
_locale = navigator.language;
_language = _locale;
+ // NOTE@coder: make languages work.
+ const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
+ const rawNlsConfig = el && el.getAttribute('data-settings');
+ if (rawNlsConfig) {
+ try {
+ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
+ _locale = nlsConfig.locale;
+ _translationsConfigFile = nlsConfig._translationsConfigFile;
+ _language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT;
+ } catch (error) { /* Oh well. */ }
+ }
} else if (typeof process === 'object') {
_isWindows = (process.platform === 'win32');
_isMacintosh = (process.platform === 'darwin');
diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts
index c52f7b3774..5a7e7f579e 100644
--- a/src/vs/base/common/processes.ts
+++ b/src/vs/base/common/processes.ts
@@ -110,7 +110,10 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve
/^ELECTRON_.+$/,
/^GOOGLE_API_KEY$/,
/^VSCODE_.+$/,
- /^SNAP(|_.*)$/
+ /^SNAP(|_.*)$/,
+ // NOTE@coder: add our own environment variables.
+ /^NBIN_BYPASS$/,
+ /^LAUNCH_VSCODE$/
];
const envKeys = Object.keys(env);
envKeys
diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js
index 2c64061da7..c0ef8faedd 100644
--- a/src/vs/base/node/languagePacks.js
+++ b/src/vs/base/node/languagePacks.js
@@ -128,7 +128,10 @@ function factory(nodeRequire, path, fs, perf) {
function getLanguagePackConfigurations(userDataPath) {
const configFile = path.join(userDataPath, 'languagepacks.json');
try {
- return nodeRequire(configFile);
+ // NOTE@coder: Swapped require with readFile since require is cached and
+ // we don't restart the server-side portion of code-server when the
+ // language changes.
+ return JSON.parse(fs.readFileSync(configFile, "utf8"));
} catch (err) {
// Do nothing. If we can't read the file we have no
// language pack config.
diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts
index a599f5a7eb..ec7ccd43f8 100644
--- a/src/vs/code/browser/workbench/workbench.ts
+++ b/src/vs/code/browser/workbench/workbench.ts
@@ -298,35 +298,6 @@ class WorkspaceProvider implements IWorkspaceProvider {
let workspace: IWorkspace;
let payload = Object.create(null);
- const query = new URL(document.location.href).searchParams;
- query.forEach((value, key) => {
- switch (key) {
-
- // Folder
- case WorkspaceProvider.QUERY_PARAM_FOLDER:
- workspace = { folderUri: URI.parse(value) };
- foundWorkspace = true;
- break;
-
- // Workspace
- case WorkspaceProvider.QUERY_PARAM_WORKSPACE:
- workspace = { workspaceUri: URI.parse(value) };
- foundWorkspace = true;
- break;
-
- // Empty
- case WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW:
- workspace = undefined;
- foundWorkspace = true;
- break;
-
- // Payload
- case WorkspaceProvider.QUERY_PARAM_PAYLOAD:
- payload = JSON.parse(value);
- break;
- }
- });
-
// If no workspace is provided through the URL, check for config attribute from server
if (!foundWorkspace) {
if (config.folderUri) {
diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts
index abd1e33b18..bf75952ce1 100644
--- a/src/vs/platform/environment/common/environment.ts
+++ b/src/vs/platform/environment/common/environment.ts
@@ -37,6 +37,8 @@ export interface ParsedArgs {
logExtensionHostCommunication?: boolean;
'extensions-dir'?: string;
'builtin-extensions-dir'?: string;
+ 'extra-extensions-dir'?: string[];
+ 'extra-builtin-extensions-dir'?: string[];
extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs
extensionTestsPath?: string; // either a local path or a URI
'extension-development-confirm-save'?: boolean;
@@ -147,6 +149,8 @@ export interface IEnvironmentService extends IUserHomeProvider {
disableExtensions: boolean | string[];
builtinExtensionsPath: string;
extensionsPath?: string;
+ extraExtensionPaths: string[];
+ extraBuiltinExtensionPaths: string[];
extensionDevelopmentLocationURI?: URI[];
extensionTestsLocationURI?: URI;
logExtensionHostCommunication?: boolean;
diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts
index e68e0647c3..49a5aae2fa 100644
--- a/src/vs/platform/environment/node/argv.ts
+++ b/src/vs/platform/environment/node/argv.ts
@@ -55,6 +55,8 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
'builtin-extensions-dir': { type: 'string' },
+ 'extra-builtin-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra builtin extension directory.' },
+ 'extra-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra user extension directory.' },
'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") },
@@ -310,4 +312,3 @@ export function buildHelpMessage(productName: string, executableName: string, ve
export function buildVersionMessage(version: string | undefined, commit: string | undefined): string {
return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`;
}
-
diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts
index 0428e1e888..9b3cddcb3a 100644
--- a/src/vs/platform/environment/node/environmentService.ts
+++ b/src/vs/platform/environment/node/environmentService.ts
@@ -197,6 +197,13 @@ export class EnvironmentService implements IEnvironmentService {
return path.join(this.userHome, product.dataFolderName, 'extensions');
}
+ @memoize get extraExtensionPaths(): string[] {
+ return (this._args['extra-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
+ }
+ @memoize get extraBuiltinExtensionPaths(): string[] {
+ return (this._args['extra-builtin-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
+ }
+
@memoize
get extensionDevelopmentLocationURI(): URI[] | undefined {
const s = this._args.extensionDevelopmentPath;
diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
index 5b05650591..dc5140410e 100644
--- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts
+++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
@@ -743,11 +743,15 @@ export class ExtensionManagementService extends Disposable implements IExtension
private scanSystemExtensions(): Promise<ILocalExtension[]> {
this.logService.trace('Started scanning system extensions');
- const systemExtensionsPromise = this.scanExtensions(this.systemExtensionsPath, ExtensionType.System)
- .then(result => {
- this.logService.trace('Scanned system extensions:', result.length);
- return result;
- });
+ const systemExtensionsPromise = Promise.all([
+ this.scanExtensions(this.systemExtensionsPath, ExtensionType.System),
+ ...this.environmentService.extraBuiltinExtensionPaths
+ .map((path) => this.scanExtensions(path, ExtensionType.System))
+ ]).then((results) => {
+ const result = results.reduce((flat, current) => flat.concat(current), []);
+ this.logService.trace('Scanned system extensions:', result.length);
+ return result;
+ });
if (this.environmentService.isBuilt) {
return systemExtensionsPromise;
}
@@ -769,9 +773,17 @@ export class ExtensionManagementService extends Disposable implements IExtension
.then(([systemExtensions, devSystemExtensions]) => [...systemExtensions, ...devSystemExtensions]);
}
+ private scanAllUserExtensions(folderName: string, type: ExtensionType): Promise<ILocalExtension[]> {
+ return Promise.all([
+ this.scanExtensions(folderName, type),
+ ...this.environmentService.extraExtensionPaths.map((p) => this.scanExtensions(p, ExtensionType.User))
+ ]).then((results) => results.reduce((flat, current) => flat.concat(current), []));
+ }
+
+
private scanUserExtensions(excludeOutdated: boolean): Promise<ILocalExtension[]> {
this.logService.trace('Started scanning user extensions');
- return Promise.all([this.getUninstalledExtensions(), this.scanExtensions(this.extensionsPath, ExtensionType.User)])
+ return Promise.all([this.getUninstalledExtensions(), this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User)])
.then(([uninstalled, extensions]) => {
extensions = extensions.filter(e => !uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]);
if (excludeOutdated) {
@@ -786,6 +798,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
private scanExtensions(root: string, type: ExtensionType): Promise<ILocalExtension[]> {
const limiter = new Limiter<any>(10);
return pfs.readdir(root)
+ .catch((error) => {
+ if (error.code !== 'ENOENT') {
+ throw error;
+ }
+ return <string[]>[];
+ })
.then(extensionsFolders => Promise.all<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
.then(extensions => extensions.filter(e => e && e.identifier));
}
@@ -824,7 +842,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
private async removeUninstalledExtensions(): Promise<void> {
const uninstalled = await this.getUninstalledExtensions();
- const extensions = await this.scanExtensions(this.extensionsPath, ExtensionType.User); // All user extensions
+ const extensions = await this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User); // All user extensions
const installed: Set<string> = new Set<string>();
for (const e of extensions) {
if (!uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]) {
@@ -843,7 +861,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
private removeOutdatedExtensions(): Promise<void> {
- return this.scanExtensions(this.extensionsPath, ExtensionType.User) // All user extensions
+ return this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User) // All user extensions
.then(extensions => {
const toRemove: ILocalExtension[] = [];
diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts
index 804d113856..4b651e5c77 100644
--- a/src/vs/platform/product/common/product.ts
+++ b/src/vs/platform/product/common/product.ts
@@ -22,11 +22,19 @@ if (isWeb) {
if (Object.keys(product).length === 0) {
assign(product, {
version: '1.41.0-dev',
+ codeServerVersion: 'dev',
nameLong: 'Visual Studio Code Web Dev',
nameShort: 'VSCode Web Dev',
urlProtocol: 'code-oss'
});
}
+
+ // NOTE@coder: enable injecting settings from the server.
+ const el = document.getElementById('vscode-remote-product-configuration');
+ const rawProductConfiguration = el && el.getAttribute('data-settings');
+ if (rawProductConfiguration) {
+ assign(product, JSON.parse(rawProductConfiguration));
+ }
}
// Node: AMD loader
@@ -36,7 +44,7 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === '
const rootPath = path.dirname(getPathFromAmdModule(require, ''));
product = assign({}, require.__$__nodeRequire(path.join(rootPath, 'product.json')) as IProductConfiguration);
- const pkg = require.__$__nodeRequire(path.join(rootPath, 'package.json')) as { version: string; };
+ const pkg = require.__$__nodeRequire(path.join(rootPath, 'package.json')) as { version: string; codeServerVersion: string; };
// Running out of sources
if (env['VSCODE_DEV']) {
@@ -48,7 +56,8 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === '
}
assign(product, {
- version: pkg.version
+ version: pkg.version,
+ codeServerVersion: pkg.codeServerVersion,
});
}
diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts
index 120fd66644..52547bdb0e 100644
--- a/src/vs/platform/product/common/productService.ts
+++ b/src/vs/platform/product/common/productService.ts
@@ -16,6 +16,7 @@ export interface IProductService extends Readonly<IProductConfiguration> {
export interface IProductConfiguration {
readonly version: string;
+ readonly codeServerVersion: string;
readonly date?: string;
readonly quality?: string;
readonly commit?: string;
diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts
index d0f6e6b18a..1966fd297d 100644
--- a/src/vs/platform/remote/browser/browserSocketFactory.ts
+++ b/src/vs/platform/remote/browser/browserSocketFactory.ts
@@ -205,7 +205,8 @@ export class BrowserSocketFactory implements ISocketFactory {
}
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
- const socket = this._webSocketFactory.create(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`);
+ // NOTE@coder: Modified to work against the current path.
+ const socket = this._webSocketFactory.create(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}${window.location.pathname}?${query}&skipWebSocketFrames=false`);
const errorListener = socket.onError((err) => callback(err, undefined));
socket.onOpen(() => {
errorListener.dispose();
@@ -213,6 +214,3 @@ export class BrowserSocketFactory implements ISocketFactory {
});
}
}
-
-
-
diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts
index 81ec255e65..c94829fc6a 100644
--- a/src/vs/platform/request/common/request.ts
+++ b/src/vs/platform/request/common/request.ts
@@ -16,7 +16,7 @@ export const IRequestService = createDecorator<IRequestService>('requestService'
export interface IRequestService {
_serviceBrand: undefined;
- request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext>;
+ request(options: IRequestOptions, token: CancellationToken, gzip?: boolean): Promise<IRequestContext>;
resolveProxy(url: string): Promise<string | undefined>;
}
diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts
index ad44dcbc33..7a7b5261ff 100644
--- a/src/vs/platform/request/node/requestService.ts
+++ b/src/vs/platform/request/node/requestService.ts
@@ -57,7 +57,7 @@ export class RequestService extends Disposable implements IRequestService {
this.authorization = config.http && config.http.proxyAuthorization;
}
- async request(options: NodeRequestOptions, token: CancellationToken): Promise<IRequestContext> {
+ async request(options: NodeRequestOptions, token: CancellationToken, gzip?: boolean): Promise<IRequestContext> {
this.logService.trace('RequestService#request', options.url);
const { proxyUrl, strictSSL } = this;
@@ -70,7 +70,7 @@ export class RequestService extends Disposable implements IRequestService {
options.headers = assign(options.headers || {}, { 'Proxy-Authorization': this.authorization });
}
- return this._request(options, token);
+ return this._request(options, token, gzip);
}
private async getNodeRequest(options: IRequestOptions): Promise<IRawRequestFunction> {
@@ -79,7 +79,7 @@ export class RequestService extends Disposable implements IRequestService {
return module.request;
}
- private _request(options: NodeRequestOptions, token: CancellationToken): Promise<IRequestContext> {
+ private _request(options: NodeRequestOptions, token: CancellationToken, gzip?: boolean): Promise<IRequestContext> {
return new Promise<IRequestContext>(async (c, e) => {
let req: http.ClientRequest;
@@ -114,7 +114,7 @@ export class RequestService extends Disposable implements IRequestService {
} else {
let stream: streams.ReadableStream<Uint8Array> = res;
- if (res.headers['content-encoding'] === 'gzip') {
+ if (gzip || res.headers['content-encoding'] === 'gzip') {
stream = res.pipe(createGunzip());
}
diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts
index d8bf464fed..748715da3b 100644
--- a/src/vs/platform/update/electron-main/abstractUpdateService.ts
+++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts
@@ -6,7 +6,6 @@
import { Event, Emitter } from 'vs/base/common/event';
import { timeout } from 'vs/base/common/async';
import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/configuration/common/configuration';
-import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import product from 'vs/platform/product/common/product';
import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -44,7 +43,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
}
constructor(
- @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
+ _: any, // NOTE@coder: This depends on Electron so we skip it.
@IConfigurationService protected configurationService: IConfigurationService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IRequestService protected requestService: IRequestService,
@@ -156,15 +155,8 @@ export abstract class AbstractUpdateService implements IUpdateService {
this.logService.trace('update#quitAndInstall(): before lifecycle quit()');
- this.lifecycleMainService.quit(true /* from update */).then(vetod => {
- this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`);
- if (vetod) {
- return;
- }
-
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
this.doQuitAndInstall();
- });
return Promise.resolve(undefined);
}
diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts
index e69aa80159..2960d00456 100644
--- a/src/vs/workbench/api/browser/extensionHost.contribution.ts
+++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts
@@ -62,6 +62,7 @@ import './mainThreadTunnelService';
import './mainThreadAuthentication';
import './mainThreadTimeline';
import 'vs/workbench/api/common/apiCommands';
+import 'vs/server/src/browser/mainThreadNodeProxy';
export class ExtensionPoints implements IWorkbenchContribution {
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
index 91045fcda6..d93d3286d8 100644
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
@@ -72,6 +72,7 @@ import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelServ
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication';
import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline';
+import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
export interface IExtensionApiFactory {
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
@@ -93,6 +94,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostLogService = accessor.get(ILogService);
const extHostTunnelService = accessor.get(IExtHostTunnelService);
const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService);
+ const extHostNodeProxy = accessor.get(IExtHostNodeProxy);
// register addressable instances
rpcProtocol.set(ExtHostContext.ExtHostLogService, <ExtHostLogServiceShape><any>extHostLogService);
@@ -101,6 +103,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage);
rpcProtocol.set(ExtHostContext.ExtHostTunnelService, extHostTunnelService);
+ rpcProtocol.set(ExtHostContext.ExtHostNodeProxy, extHostNodeProxy);
// automatically create and register addressable instances
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations));
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
index 55130ff918..032534b23e 100644
--- a/src/vs/workbench/api/common/extHost.protocol.ts
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
@@ -667,6 +667,16 @@ export interface MainThreadLabelServiceShape extends IDisposable {
$unregisterResourceLabelFormatter(handle: number): void;
}
+export interface MainThreadNodeProxyShape extends IDisposable {
+ $send(message: string): void;
+}
+export interface ExtHostNodeProxyShape {
+ $onMessage(message: string): void;
+ $onClose(): void;
+ $onDown(): void;
+ $onUp(): void;
+}
+
export interface MainThreadSearchShape extends IDisposable {
$registerFileSearchProvider(handle: number, scheme: string): void;
$registerTextSearchProvider(handle: number, scheme: string): void;
@@ -1498,7 +1508,8 @@ export const MainContext = {
MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService'),
MainThreadTheming: createMainId<MainThreadThemingShape>('MainThreadTheming'),
MainThreadTunnelService: createMainId<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
- MainThreadTimeline: createMainId<MainThreadTimelineShape>('MainThreadTimeline')
+ MainThreadTimeline: createMainId<MainThreadTimelineShape>('MainThreadTimeline'),
+ MainThreadNodeProxy: createMainId<MainThreadNodeProxyShape>('MainThreadNodeProxy'),
};
export const ExtHostContext = {
@@ -1536,5 +1547,6 @@ export const ExtHostContext = {
ExtHostTheming: createMainId<ExtHostThemingShape>('ExtHostTheming'),
ExtHostTunnelService: createMainId<ExtHostTunnelServiceShape>('ExtHostTunnelService'),
ExtHostAuthentication: createMainId<ExtHostAuthenticationShape>('ExtHostAuthentication'),
- ExtHostTimeline: createMainId<ExtHostTimelineShape>('ExtHostTimeline')
+ ExtHostTimeline: createMainId<ExtHostTimelineShape>('ExtHostTimeline'),
+ ExtHostNodeProxy: createMainId<ExtHostNodeProxyShape>('ExtHostNodeProxy')
};
diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts
index 978bf32fcd..a63954cce0 100644
--- a/src/vs/workbench/api/common/extHostExtensionService.ts
+++ b/src/vs/workbench/api/common/extHostExtensionService.ts
@@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
-import { originalFSPath, joinPath } from 'vs/base/common/resources';
+import { originalFSPath } from 'vs/base/common/resources';
import { Barrier } from 'vs/base/common/async';
import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { TernarySearchTree } from 'vs/base/common/map';
@@ -33,6 +33,7 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
+import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
interface ITestRunner {
/** Old test runner API, as exported from `vscode/lib/testrunner` */
@@ -78,6 +79,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
protected readonly _extHostConfiguration: ExtHostConfiguration;
protected readonly _logService: ILogService;
protected readonly _extHostTunnelService: IExtHostTunnelService;
+ protected readonly _nodeProxy: IExtHostNodeProxy;
protected readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape;
protected readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape;
@@ -107,7 +109,8 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
@ILogService logService: ILogService,
@IExtHostInitDataService initData: IExtHostInitDataService,
@IExtensionStoragePaths storagePath: IExtensionStoragePaths,
- @IExtHostTunnelService extHostTunnelService: IExtHostTunnelService
+ @IExtHostTunnelService extHostTunnelService: IExtHostTunnelService,
+ @IExtHostNodeProxy nodeProxy: IExtHostNodeProxy,
) {
this._hostUtils = hostUtils;
this._extHostContext = extHostContext;
@@ -116,6 +119,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
this._extHostWorkspace = extHostWorkspace;
this._extHostConfiguration = extHostConfiguration;
this._logService = logService;
+ this._nodeProxy = nodeProxy;
this._extHostTunnelService = extHostTunnelService;
this._disposables = new DisposableStore();
@@ -341,14 +345,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
return Promise.all([
- this._loadCommonJSModule<IExtensionModule>(joinPath(extensionDescription.extensionLocation, extensionDescription.main), activationTimesBuilder),
+ this._loadCommonJSModule<IExtensionModule>(extensionDescription, activationTimesBuilder),
this._loadExtensionContext(extensionDescription)
]).then(values => {
return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder);
});
}
- protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
+ protected abstract _loadCommonJSModule<T>(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise<vscode.ExtensionContext> {
diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts
index 72ad75d63e..8c4edee5e3 100644
--- a/src/vs/workbench/api/node/extHost.services.ts
+++ b/src/vs/workbench/api/node/extHost.services.ts
@@ -29,6 +29,8 @@ import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import { ExtHostTunnelService } from 'vs/workbench/api/node/extHostTunnelService';
import { IExtHostApiDeprecationService, ExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
+import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
+import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
// register singleton services
registerSingleton(ILogService, ExtHostLogService);
@@ -47,3 +49,19 @@ registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
registerSingleton(IExtHostStorage, ExtHostStorage);
registerSingleton(IExtHostTunnelService, ExtHostTunnelService);
+
+function NotImplementedProxy<T>(name: ServiceIdentifier<T>): { new(): T } {
+ return <any>class {
+ constructor() {
+ return new Proxy({}, {
+ get(target: any, prop: string | number) {
+ if (target[prop]) {
+ return target[prop];
+ }
+ throw new Error(`Not Implemented: ${name}->${String(prop)}`);
+ }
+ });
+ }
+ };
+}
+registerSingleton(IExtHostNodeProxy, class extends NotImplementedProxy(IExtHostNodeProxy) {});
diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts
index a1c3e50ffd..910627aaf9 100644
--- a/src/vs/workbench/api/node/extHostExtensionService.ts
+++ b/src/vs/workbench/api/node/extHostExtensionService.ts
@@ -13,6 +13,8 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
+import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
+import { joinPath } from 'vs/base/common/resources';
class NodeModuleRequireInterceptor extends RequireInterceptor {
@@ -76,7 +78,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
};
}
- protected _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
+ protected _loadCommonJSModule<T>(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
+ if (!URI.isUri(module)) {
+ module = joinPath(module.extensionLocation, module.main!);
+ }
if (module.scheme !== Schemas.file) {
throw new Error(`Cannot load URI: '${module}', must be of file-scheme`);
}
diff --git a/src/vs/workbench/api/node/extHostStoragePaths.ts b/src/vs/workbench/api/node/extHostStoragePaths.ts
index afdd6bf398..ac91318ce3 100644
--- a/src/vs/workbench/api/node/extHostStoragePaths.ts
+++ b/src/vs/workbench/api/node/extHostStoragePaths.ts
@@ -5,13 +5,14 @@
import * as path from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
-import * as pfs from 'vs/base/node/pfs';
-import { IEnvironment, IStaticWorkspaceData } from 'vs/workbench/api/common/extHost.protocol';
+import { IEnvironment, IStaticWorkspaceData, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { ILogService } from 'vs/platform/log/common/log';
+import { IExtHostRpcService } from '../common/extHostRpcService';
+import { VSBuffer } from 'vs/base/common/buffer';
export class ExtensionStoragePaths implements IExtensionStoragePaths {
@@ -26,6 +27,7 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths {
constructor(
@IExtHostInitDataService initData: IExtHostInitDataService,
@ILogService private readonly _logService: ILogService,
+ @IExtHostRpcService private readonly _extHostRpc: IExtHostRpcService,
) {
this._workspace = withNullAsUndefined(initData.workspace);
this._environment = initData.environment;
@@ -54,21 +56,25 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths {
const storageName = this._workspace.id;
const storagePath = path.join(this._environment.appSettingsHome.fsPath, 'workspaceStorage', storageName);
- const exists = await pfs.dirExists(storagePath);
+ // NOTE@coder: Use the file system proxy so this will work in the browser.
+ // writeFile performs a mkdirp so we don't need to bother ourselves.
+ const fileSystem = this._extHostRpc.getProxy(MainContext.MainThreadFileSystem);
+ const exists = fileSystem.$stat(URI.file(storagePath))
if (exists) {
return storagePath;
}
try {
- await pfs.mkdirp(storagePath);
- await pfs.writeFile(
- path.join(storagePath, 'meta.json'),
- JSON.stringify({
- id: this._workspace.id,
- configuration: this._workspace.configuration && URI.revive(this._workspace.configuration).toString(),
- name: this._workspace.name
- }, undefined, 2)
+ await fileSystem.$writeFile(
+ URI.file(path.join(storagePath, 'meta.json')),
+ VSBuffer.fromString(
+ JSON.stringify({
+ id: this._workspace.id,
+ configuration: this._workspace.configuration && URI.revive(this._workspace.configuration).toString(),
+ name: this._workspace.name
+ }, undefined, 2)
+ )
);
return storagePath;
diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts
index 4781f22676..25143a97c0 100644
--- a/src/vs/workbench/api/worker/extHostExtensionService.ts
+++ b/src/vs/workbench/api/worker/extHostExtensionService.ts
@@ -9,6 +9,9 @@ import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHost
import { endsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
+import { joinPath } from 'vs/base/common/resources';
+import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
+import { loadCommonJSModule } from 'vs/server/src/browser/worker';
class WorkerRequireInterceptor extends RequireInterceptor {
@@ -41,7 +44,14 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
await this._fakeModules.install();
}
- protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
+ protected async _loadCommonJSModule<T>(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
+ if (!URI.isUri(module) && module.extensionKind !== 'web') {
+ return loadCommonJSModule(module, activationTimesBuilder, this._nodeProxy, this._logService, this._fakeModules!.getModule('vscode', module.extensionLocation));
+ }
+
+ if (!URI.isUri(module)) {
+ module = joinPath(module.extensionLocation, module.main!);
+ }
module = module.with({ path: ensureSuffix(module.path, '.js') });
const response = await fetch(module.toString(true));
@@ -57,7 +67,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
const _exports = {};
const _module = { exports: _exports };
const _require = (request: string) => {
- const result = this._fakeModules!.getModule(request, module);
+ const result = this._fakeModules!.getModule(request, <URI>module);
if (result === undefined) {
throw new Error(`Cannot load module '${request}'`);
}
diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts
index 94e7052574..7e5563b417 100644
--- a/src/vs/workbench/browser/web.main.ts
+++ b/src/vs/workbench/browser/web.main.ts
@@ -49,6 +49,7 @@ import { IndexedDBLogProvider } from 'vs/workbench/services/log/browser/indexedD
import { InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider';
import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
+import { initialize } from 'vs/server/src/browser/client';
class BrowserMain extends Disposable {
@@ -85,6 +86,7 @@ class BrowserMain extends Disposable {
// Startup
workbench.startup();
+ await initialize(services.serviceCollection);
}
private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void {
diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts
index c509716fc4..e416413084 100644
--- a/src/vs/workbench/common/resources.ts
+++ b/src/vs/workbench/common/resources.ts
@@ -15,6 +15,7 @@ import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { withNullAsUndefined } from 'vs/base/common/types';
+import { Schemas } from 'vs/base/common/network';
export class ResourceContextKey extends Disposable implements IContextKey<URI> {
@@ -63,7 +64,8 @@ export class ResourceContextKey extends Disposable implements IContextKey<URI> {
set(value: URI | null) {
if (!ResourceContextKey._uriEquals(this._resourceKey.get(), value)) {
this._resourceKey.set(value);
- this._schemeKey.set(value ? value.scheme : null);
+ // NOTE@coder: fixes extensions matching against file schemas.
+ this._schemeKey.set(value ? (value.scheme === Schemas.vscodeRemote ? Schemas.file : value.scheme) : null);
this._filenameKey.set(value ? basename(value) : null);
this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value) : null);
this._extensionKey.set(value ? extname(value) : null);
diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js
index 63c9af47e2..021358fef9 100644
--- a/src/vs/workbench/contrib/webview/browser/pre/main.js
+++ b/src/vs/workbench/contrib/webview/browser/pre/main.js
@@ -329,7 +329,8 @@
if (data.endpoint) {
try {
const endpointUrl = new URL(data.endpoint);
- csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:(?=(\s|;|$))/g, endpointUrl.origin));
+ // NOTE@coder: Add back the trailing slash so it'll work for sub-paths.
+ csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:(?=(\s|;|$))/g, endpointUrl.origin + "/"));
} catch (e) {
console.error('Could not rewrite csp');
}
diff --git a/src/vs/workbench/services/dialogs/browser/dialogService.ts b/src/vs/workbench/services/dialogs/browser/dialogService.ts
index f67f9aa064..add754cd5a 100644
--- a/src/vs/workbench/services/dialogs/browser/dialogService.ts
+++ b/src/vs/workbench/services/dialogs/browser/dialogService.ts
@@ -122,11 +122,12 @@ export class DialogService implements IDialogService {
async about(): Promise<void> {
const detail = nls.localize('aboutDetail',
- "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
+ "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}\nCode Server Version: {4}",
this.productService.version || 'Unknown',
this.productService.commit || 'Unknown',
this.productService.date || 'Unknown',
- navigator.userAgent
+ navigator.userAgent,
+ this.productService.codeServerVersion || 'Unknown',
);
const { choice } = await this.show(Severity.Info, this.productService.nameLong, [nls.localize('copy', "Copy"), nls.localize('ok', "OK")], { detail, cancelId: 1 });
diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts
index 1bf4cfad2a..924a2fcd87 100644
--- a/src/vs/workbench/services/environment/browser/environmentService.ts
+++ b/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -195,8 +195,8 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
@memoize
get webviewExternalEndpoint(): string {
- // TODO: get fallback from product.json
- return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}').replace('{{commit}}', product.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37');
+ // NOTE@coder: Modified to work against the current URL.
+ return `${window.location.origin}${window.location.pathname.replace(/\/+$/, '')}/webview/`;
}
@memoize
@@ -249,6 +249,8 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
installSourcePath!: string;
builtinExtensionsPath!: string;
+ extraExtensionPaths!: string[];
+ extraBuiltinExtensionPaths!: string[];
globalStorageHome!: string;
workspaceStorageHome!: string;
diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts
index fe891a042e..21d0d4bf61 100644
--- a/src/vs/workbench/services/extensions/browser/extensionService.ts
+++ b/src/vs/workbench/services/extensions/browser/extensionService.ts
@@ -119,6 +119,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
} else {
// remote: only enabled and none-web'ish extension
+ localExtensions.push(...remoteEnv.extensions.filter(extension => this._isEnabled(extension) && canExecuteOnWeb(extension, this._productService, this._configService)));
remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !canExecuteOnWeb(extension, this._productService, this._configService));
this._checkEnableProposedApi(remoteEnv.extensions);
diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts
index 9e8352ac88..2d1cb0a107 100644
--- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts
+++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts
@@ -32,7 +32,8 @@ export function canExecuteOnWorkspace(manifest: IExtensionManifest, productServi
export function canExecuteOnWeb(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean {
const extensionKind = getExtensionKind(manifest, productService, configurationService);
- return extensionKind.some(kind => kind === 'web');
+ // NOTE@coder: hardcode vim for now.
+ return extensionKind.some(kind => kind === 'web') || manifest.name === 'vim';
}
export function getExtensionKind(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): ExtensionKind[] {
diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
index 0f35c54431..32fff09b18 100644
--- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
+++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
@@ -53,12 +53,13 @@ const args = minimist(process.argv.slice(2), {
const Module = require.__$__nodeRequire('module') as any;
const originalLoad = Module._load;
- Module._load = function (request: string) {
+ Module._load = function (request: string, parent: object, isMain: boolean) {
if (request === 'natives') {
throw new Error('Either the extension or a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more');
}
- return originalLoad.apply(this, arguments);
+ // NOTE@coder: Map node_module.asar requests to regular node_modules.
+ return originalLoad.apply(this, [request.replace(/node_modules\.asar(\.unpacked)?/, 'node_modules'), parent, isMain]);
};
})();
@@ -131,8 +132,11 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
// Wait for rich client to reconnect
protocol.onSocketClose(() => {
- // The socket has closed, let's give the renderer a certain amount of time to reconnect
- disconnectRunner1.schedule();
+ // NOTE@coder: Inform the server so we can manage offline
+ // connections there instead. Our goal is to persist connections
+ // forever (to a reasonable point) to account for things like
+ // hibernating overnight.
+ process.send!({ type: 'VSCODE_EXTHOST_DISCONNECTED' });
});
}
}
diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts
index bbb72e9511..63f1f6ff46 100644
--- a/src/vs/workbench/services/extensions/worker/extHost.services.ts
+++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts
@@ -18,11 +18,12 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService';
-import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService';
import { IExtHostTunnelService, ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import { IExtHostApiDeprecationService, ExtHostApiDeprecationService, } from 'vs/workbench/api/common/extHostApiDeprecationService';
+import { ExtHostNodeProxy, IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
+import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths';
// register singleton services
registerSingleton(ILogService, ExtHostLogService);
@@ -37,23 +38,9 @@ registerSingleton(IExtHostStorage, ExtHostStorage);
registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
registerSingleton(IExtHostSearch, ExtHostSearch);
registerSingleton(IExtHostTunnelService, ExtHostTunnelService);
+registerSingleton(IExtHostNodeProxy, ExtHostNodeProxy);
-// register services that only throw errors
-function NotImplementedProxy<T>(name: ServiceIdentifier<T>): { new(): T } {
- return <any>class {
- constructor() {
- return new Proxy({}, {
- get(target: any, prop: string | number) {
- if (target[prop]) {
- return target[prop];
- }
- throw new Error(`Not Implemented: ${name}->${String(prop)}`);
- }
- });
- }
- };
-}
registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService);
registerSingleton(IExtHostTask, WorkerExtHostTask);
registerSingleton(IExtHostDebugService, WorkerExtHostDebugService);
-registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { whenReady = Promise.resolve(); });
+registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
diff --git a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
index 99394090da..4891e0fece 100644
--- a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
+++ b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
@@ -5,17 +5,17 @@
import { createChannelSender } from 'vs/base/parts/ipc/node/ipc';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
-import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
+import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export class LocalizationsService {
_serviceBrand: undefined;
constructor(
- @ISharedProcessService sharedProcessService: ISharedProcessService,
+ @IRemoteAgentService remoteAgentService: IRemoteAgentService,
) {
- return createChannelSender<ILocalizationsService>(sharedProcessService.getChannel('localizations'));
+ return createChannelSender<ILocalizationsService>(remoteAgentService.getConnection()!.getChannel('localizations'));
}
}
diff --git a/src/vs/workbench/services/update/electron-browser/updateService.ts b/src/vs/workbench/services/update/electron-browser/updateService.ts
index b8f6558b2c..7aeafe6e0e 100644
--- a/src/vs/workbench/services/update/electron-browser/updateService.ts
+++ b/src/vs/workbench/services/update/electron-browser/updateService.ts
@@ -6,8 +6,8 @@
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event, Emitter } from 'vs/base/common/event';
import { IUpdateService, State } from 'vs/platform/update/common/update';
-import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
+import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export class NativeUpdateService implements IUpdateService {
@@ -21,8 +21,9 @@ export class NativeUpdateService implements IUpdateService {
private channel: IChannel;
- constructor(@IMainProcessService mainProcessService: IMainProcessService) {
- this.channel = mainProcessService.getChannel('update');
+ // NOTE@coder: patched to work in the browser.
+ constructor(@IRemoteAgentService remoteAgentService: IRemoteAgentService) {
+ this.channel = remoteAgentService.getConnection()!.getChannel('update');
// always set this._state as the state changes
this.onStateChange(state => this._state = state);
diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts
index 0719b361e0..3a4c5cefe8 100644
--- a/src/vs/workbench/workbench.web.main.ts
+++ b/src/vs/workbench/workbench.web.main.ts
@@ -34,11 +34,14 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService';
import 'vs/workbench/services/keybinding/browser/keymapService';
import 'vs/workbench/services/extensions/browser/extensionService';
import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService';
-import 'vs/workbench/services/telemetry/browser/telemetryService';
+// NOTE@coder: We send it all to the server side to be processed there instead.
+// import 'vs/workbench/services/telemetry/browser/telemetryService';
import 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
import 'vs/workbench/services/credentials/browser/credentialsService';
import 'vs/workbench/services/url/browser/urlService';
-import 'vs/workbench/services/update/browser/updateService';
+// NOTE@coder: Use the electron-browser version since it already comes with a
+// channel which lets us actually perform updates.
+import 'vs/workbench/services/update/electron-browser/updateService';
import 'vs/workbench/contrib/tags/browser/workspaceTagsService';
import 'vs/workbench/services/workspaces/browser/workspacesService';
import 'vs/workbench/services/workspaces/browser/workspaceEditingService';

View File

@ -1,126 +0,0 @@
import { Emitter } from "vs/base/common/event";
import { URI } from "vs/base/common/uri";
import { localize } from "vs/nls";
import { Extensions, IConfigurationRegistry } from "vs/platform/configuration/common/configurationRegistry";
import { registerSingleton } from "vs/platform/instantiation/common/extensions";
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
import { ILocalizationsService } from "vs/platform/localizations/common/localizations";
import { INotificationService, Severity } from "vs/platform/notification/common/notification";
import { Registry } from "vs/platform/registry/common/platform";
import { PersistentConnectionEventType } from "vs/platform/remote/common/remoteAgentConnection";
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
import { INodeProxyService, NodeProxyChannelClient } from "vs/server/src/common/nodeProxy";
import { TelemetryChannelClient } from "vs/server/src/common/telemetry";
import { split } from "vs/server/src/common/util";
import "vs/workbench/contrib/localizations/browser/localizations.contribution";
import { LocalizationsService } from "vs/workbench/services/localizations/electron-browser/localizationsService";
import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService";
class TelemetryService extends TelemetryChannelClient {
public constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
) {
super(remoteAgentService.getConnection()!.getChannel("telemetry"));
}
}
const TELEMETRY_SECTION_ID = "telemetry";
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
"id": TELEMETRY_SECTION_ID,
"order": 110,
"type": "object",
"title": localize("telemetryConfigurationTitle", "Telemetry"),
"properties": {
"telemetry.enableTelemetry": {
"type": "boolean",
"description": localize("telemetry.enableTelemetry", "Enable usage data and errors to be sent to a Microsoft online service."),
"default": true,
"tags": ["usesOnlineServices"]
}
}
});
class NodeProxyService extends NodeProxyChannelClient implements INodeProxyService {
private readonly _onClose = new Emitter<void>();
public readonly onClose = this._onClose.event;
private readonly _onDown = new Emitter<void>();
public readonly onDown = this._onDown.event;
private readonly _onUp = new Emitter<void>();
public readonly onUp = this._onUp.event;
public constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
) {
super(remoteAgentService.getConnection()!.getChannel("nodeProxy"));
remoteAgentService.getConnection()!.onDidStateChange((state) => {
switch (state.type) {
case PersistentConnectionEventType.ConnectionGain:
return this._onUp.fire();
case PersistentConnectionEventType.ConnectionLost:
return this._onDown.fire();
case PersistentConnectionEventType.ReconnectionPermanentFailure:
return this._onClose.fire();
}
});
}
}
registerSingleton(ILocalizationsService, LocalizationsService);
registerSingleton(INodeProxyService, NodeProxyService);
registerSingleton(ITelemetryService, TelemetryService);
/**
* This is called by vs/workbench/browser/web.main.ts after the workbench has
* been initialized so we can initialize our own client-side code.
*/
export const initialize = async (services: ServiceCollection): Promise<void> => {
const event = new CustomEvent("ide-ready");
window.dispatchEvent(event);
if (!window.isSecureContext) {
(services.get(INotificationService) as INotificationService).notify({
severity: Severity.Warning,
message: "code-server is being accessed over an insecure domain. Some functionality may not work as expected.",
actions: {
primary: [{
id: "understand",
label: "I understand",
tooltip: "",
class: undefined,
enabled: true,
checked: true,
dispose: () => undefined,
run: () => {
return Promise.resolve();
}
}],
}
});
}
};
export interface Query {
[key: string]: string | undefined;
}
/**
* Return the URL modified with the specified query variables. It's pretty
* stupid so it probably doesn't cover any edge cases. Undefined values will
* unset existing values. Doesn't allow duplicates.
*/
export const withQuery = (url: string, replace: Query): string => {
const uri = URI.parse(url);
const query = { ...replace };
uri.query.split("&").forEach((kv) => {
const [key, value] = split(kv, "=");
if (!(key in query)) {
query[key] = value;
}
});
return uri.with({
query: Object.keys(query)
.filter((k) => typeof query[k] !== "undefined")
.map((k) => `${k}=${query[k]}`).join("&"),
}).toString(true);
};

View File

@ -1,46 +0,0 @@
import { Emitter } from "vs/base/common/event";
import { createDecorator } from "vs/platform/instantiation/common/instantiation";
import { ExtHostNodeProxyShape, MainContext, MainThreadNodeProxyShape } from "vs/workbench/api/common/extHost.protocol";
import { IExtHostRpcService } from "vs/workbench/api/common/extHostRpcService";
export class ExtHostNodeProxy implements ExtHostNodeProxyShape {
_serviceBrand: any;
private readonly _onMessage = new Emitter<string>();
public readonly onMessage = this._onMessage.event;
private readonly _onClose = new Emitter<void>();
public readonly onClose = this._onClose.event;
private readonly _onDown = new Emitter<void>();
public readonly onDown = this._onDown.event;
private readonly _onUp = new Emitter<void>();
public readonly onUp = this._onUp.event;
private readonly proxy: MainThreadNodeProxyShape;
constructor(@IExtHostRpcService rpc: IExtHostRpcService) {
this.proxy = rpc.getProxy(MainContext.MainThreadNodeProxy);
}
public $onMessage(message: string): void {
this._onMessage.fire(message);
}
public $onClose(): void {
this._onClose.fire();
}
public $onUp(): void {
this._onUp.fire();
}
public $onDown(): void {
this._onDown.fire();
}
public send(message: string): void {
this.proxy.$send(message);
}
}
export interface IExtHostNodeProxy extends ExtHostNodeProxy { }
export const IExtHostNodeProxy = createDecorator<IExtHostNodeProxy>("IExtHostNodeProxy");

View File

@ -1,31 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'self' 'unsafe-inline'; script-src 'unsafe-inline'; manifest-src 'self'; img-src 'self';">
<title>Authenticate: code-server</title>
<link rel="icon" href="./static/out/vs/server/src/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./manifest.json" crossorigin="use-credentials">
<link rel="apple-touch-icon" href="./static/out/vs/server/src/media/code-server.png" />
<meta name="apple-mobile-web-app-capable" content="yes">
<link href="./static/out/vs/server/src/media/login.css" rel="stylesheet">
</head>
<body>
<form class="login-form" method="post">
<h4 class="title">code-server</h4>
<h2 class="subtitle">
Enter server password
</h2>
<div class="field">
<!-- The onfocus code places the cursor at the end of the value. -->
<input name="password" type="password" class="input" value=""
required autofocus
onfocus="const value=this.value;this.value='';this.value=value;">
</div>
<button class="button" type="submit">
<span class="label">Enter IDE</span>
</button>
<div class="error-display" style="display:none">{{ERROR}}</div>
</form>
</body>
</html>

View File

@ -1,37 +0,0 @@
import { IDisposable } from "vs/base/common/lifecycle";
import { INodeProxyService } from "vs/server/src/common/nodeProxy";
import { ExtHostContext, IExtHostContext, MainContext, MainThreadNodeProxyShape } from "vs/workbench/api/common/extHost.protocol";
import { extHostNamedCustomer } from "vs/workbench/api/common/extHostCustomers";
@extHostNamedCustomer(MainContext.MainThreadNodeProxy)
export class MainThreadNodeProxy implements MainThreadNodeProxyShape {
private disposed = false;
private disposables = <IDisposable[]>[];
constructor(
extHostContext: IExtHostContext,
@INodeProxyService private readonly proxyService: INodeProxyService,
) {
if (!extHostContext.remoteAuthority) { // HACK: A terrible way to detect if running in the worker.
const proxy = extHostContext.getProxy(ExtHostContext.ExtHostNodeProxy);
this.disposables = [
this.proxyService.onMessage((message: string) => proxy.$onMessage(message)),
this.proxyService.onClose(() => proxy.$onClose()),
this.proxyService.onDown(() => proxy.$onDown()),
this.proxyService.onUp(() => proxy.$onUp()),
];
}
}
$send(message: string): void {
if (!this.disposed) {
this.proxyService.send(message);
}
}
dispose(): void {
this.disposables.forEach((d) => d.dispose());
this.disposables = [];
this.disposed = true;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,40 @@
{
"name": "code-server",
"short_name": "code-server",
"start_url": "{{BASE}}",
"display": "fullscreen",
"background-color": "#fff",
"description": "Run editors on a remote server.",
"icons": [
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-96.png",
"type": "image/png",
"sizes": "96x96"
},
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-128.png",
"type": "image/png",
"sizes": "128x128"
},
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-256.png",
"type": "image/png",
"sizes": "256x256"
},
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png",
"type": "image/png",
"sizes": "384x384"
},
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-512.png",
"type": "image/png",
"sizes": "512x512"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

24
src/browser/pages/app.css Normal file
View File

@ -0,0 +1,24 @@
/* NOTE: Disable scrollbars since an oversized element creates them. */
.app-input {
height: 100%;
left: 0;
outline: none;
position: fixed;
scrollbar-width: none;
top: 0;
width: 100%;
z-index: 20;
}
.app-input::-webkit-scrollbar {
display: none;
}
.app-render {
height: 100%;
left: 0;
position: fixed;
top: 0;
width: 100%;
z-index: 10;
}

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<meta http-equiv="Content-Security-Policy" content="style-src 'self'; manifest-src 'self'; img-src 'self' data:;" />
<title>code-server — {{APP_NAME}}</title>
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body>
<script src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
<script src="{{BASE}}/static/{{COMMIT}}/dist/app.js"></script>
</body>
</html>

12
src/browser/pages/app.ts Normal file
View File

@ -0,0 +1,12 @@
import { getOptions } from "../../common/util"
import "./app.css"
import "./error.css"
import "./global.css"
import "./home.css"
import "./login.css"
import "./update.css"
const options = getOptions()
console.log(options)

View File

@ -0,0 +1,32 @@
.error-display {
box-sizing: border-box;
padding: 20px;
text-align: center;
}
.error-display > .header {
font-size: 6rem;
margin: 0;
}
.error-display > .body {
color: #444;
font-size: 1.2rem;
}
.error-display > .links {
margin-top: 16px;
}
.error-display > .links > .link {
color: rgb(87, 114, 245);
text-decoration: none;
}
.error-display > .links > .link:hover {
text-decoration: underline;
}
.error-display .success {
color: green;
}

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<meta http-equiv="Content-Security-Policy" content="style-src 'self'; manifest-src 'self'; img-src 'self' data:;" />
<title>{{ERROR_TITLE}} - code-server</title>
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body>
<div class="center-container">
<div class="error-display">
<h2 class="header">{{ERROR_HEADER}}</h2>
<div class="body">
{{ERROR_BODY}}
</div>
<div class="links">
<a class="link" href="{{BASE}}{{TO}}">go home</a>
</div>
</div>
</div>
<script src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
</body>
</html>

View File

@ -0,0 +1,76 @@
html,
body,
#root {
height: 100%;
width: 100%;
}
body {
background: rgb(244, 247, 252);
color: #111;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol";
overflow: hidden;
}
input,
button {
font-family: inherit;
font-size: 1rem;
line-height: 1rem;
}
.-button {
background-color: rgb(87, 114, 245);
border-radius: 5px;
border: none;
box-sizing: border-box;
color: white;
cursor: pointer;
padding: 18px 20px;
text-decoration: none;
}
.center-container {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
min-height: 100%;
padding: 20px;
width: 100%;
}
.card-box {
background-color: rgb(250, 253, 258);
border-radius: 5px;
box-shadow: rgba(60, 66, 87, 0.117647) 0px 7px 14px 0px, rgba(0, 0, 0, 0.117647) 0px 3px 6px 0px;
max-width: 650px;
width: 100%;
}
.card-box > .header {
border-bottom: 1px solid #ddd;
color: #444;
padding: 30px;
}
.card-box > .header > .main {
margin: 0;
font-size: 1.5rem;
}
.card-box > .header > .sub {
color: #555;
margin-top: 10px;
}
.card-box > .content {
padding: 40px;
}
.card-box + .card-box {
margin-top: 26px;
}

View File

@ -0,0 +1,47 @@
.block-row {
display: flex;
}
.block-row > .item {
flex: 1;
margin: 2px 0;
}
.block-row > .item.-row {
display: flex;
}
.block-row > .item > .sub {
font-size: 0.95em;
}
.block-row .-link {
color: rgb(87, 114, 245);
display: block;
text-decoration: none;
}
.block-row .-link:hover {
text-decoration: underline;
}
.block-row > .item > .icon {
height: 1rem;
margin-right: 5px;
vertical-align: top;
width: 1rem;
}
.block-row > .item > .icon.-missing {
background-color: rgba(87, 114, 245, 0.2);
text-align: center;
}
.kill-form {
display: inline-block;
}
.kill-form > .kill {
border-radius: 3px;
padding: 2px 5px;
}

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<meta http-equiv="Content-Security-Policy" content="style-src 'self'; manifest-src 'self'; img-src 'self' data:;" />
<title>code-server</title>
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body>
<div class="center-container">
<div class="card-box">
<div class="header">
<h2 class="main">Running</h2>
<div class="sub">Currently running applications.</div>
</div>
<div class="content">
{{APP_LIST:RUNNING}}
</div>
</div>
<div class="card-box">
<div class="header">
<h2 class="main">Recent</h2>
<div class="sub">Choose a recent directory or workspace to launch below.</div>
</div>
<div class="content">
{{APP_LIST:RECENT_PROJECTS}}
</div>
</div>
<div class="card-box">
<div class="header">
<h2 class="main">Editors</h2>
<div class="sub">Choose an editor to launch below.</div>
</div>
<div class="content">
{{APP_LIST:EDITORS}}
</div>
</div>
<!-- <div class="card-box"> -->
<!-- <div class="header"> -->
<!-- <h2 class="main">Other</h2> -->
<!-- <div class="sub">Choose an application to launch below.</div> -->
<!-- </div> -->
<!-- <div class="content"> -->
<!-- {{APP_LIST:OTHER}} -->
<!-- </div> -->
<!-- </div> -->
<div class="card-box">
<div class="header">
<h2 class="main">Version</h2>
<div class="sub">Version information and updates.</div>
</div>
<div class="content">
{{UPDATE:NAME}}
</div>
</div>
</div>
<script src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
</body>
</html>

View File

@ -0,0 +1,39 @@
body {
overflow: auto;
}
.login-form {
display: flex;
flex-direction: column;
flex: 1;
justify-content: center;
}
.login-form > .field {
display: flex;
flex-direction: row;
width: 100%;
}
.login-form > .error {
color: red;
margin-top: 16px;
}
.login-form > .field > .password {
background-color: rgb(244, 247, 252);
border-radius: 5px;
border: 1px solid #ddd;
box-sizing: border-box;
color: black;
flex: 1;
padding: 16px;
}
.login-form > .user {
display: none;
}
.login-form > .field > .submit {
margin-left: 20px;
}

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<meta
http-equiv="Content-Security-Policy"
content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;"
/>
<title>code-server login</title>
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body>
<div class="center-container">
<div class="card-box">
<div class="header">
<h1 class="main">Welcome to code-server</h1>
<div class="sub">Please log in below. Check code-server's logs for the generated password.</div>
</div>
<div class="content">
<form class="login-form" method="post">
<input class="user" type="text" autocomplete="username" />
<input id="base" type="hidden" name="base" value="/" />
<div class="field">
<input
required
autofocus
class="password"
type="password"
placeholder="PASSWORD"
name="password"
autocomplete="current-password"
/>
<input class="submit -button" value="SUBMIT" type="submit" />
</div>
{{ERROR}}
</form>
</div>
</div>
</div>
</body>
<script src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
<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>

View File

@ -0,0 +1,26 @@
.update-form {
text-align: center;
}
.update-form > .cancel {
background-color: red;
}
.update-form > .error {
color: red;
margin-top: 16px;
}
.update-form > .links {
margin-top: 20px;
}
.update-form > .links > .link {
color: rgb(87, 114, 245);
text-align: center;
text-decoration: none;
}
.update-form > .links > .link:hover {
text-decoration: underline;
}

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<meta http-equiv="Content-Security-Policy" content="style-src 'self'; manifest-src 'self'; img-src 'self' data:;" />
<title>code-server</title>
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body>
<div class="center-container">
<div class="card-box">
<div class="header">
<h1 class="main">Update</h1>
<div class="sub">Update code-server.</div>
</div>
<div class="content">
<form class="update-form" action="{{BASE}}/update/apply">
{{UPDATE_STATUS}} {{ERROR}}
<div class="links">
<a class="link" href="{{BASE}}{{TO}}">go home</a>
</div>
</form>
</div>
</div>
</div>
<script src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
</body>
</html>

View File

@ -0,0 +1,105 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
http-equiv="Content-Security-Policy"
content="font-src 'self'; connect-src ws: wss: 'self' https:; default-src ws: wss: 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; manifest-src 'self'; img-src 'self' data: https:;"
/>
<!-- Disable pinch zooming -->
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}" />
<!-- Workarounds/Hacks (remote user data uri) -->
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}" />
<meta id="vscode-remote-commit" data-settings="{{COMMIT}}" />
<meta id="vscode-remote-product-configuration" data-settings="{{PRODUCT_CONFIGURATION}}" />
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}" />
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<!-- 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">
END_PROD_ONLY -->
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/code-server.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Prefetch to avoid waterfall -->
<!-- PROD_ONLY
<link rel="prefetch" href="{{BASE}}/static/{{COMMIT}}/lib/vscode/node_modules/semver-umd/lib/semver-umd.js">
END_PROD_ONLY -->
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body aria-label=""></body>
<!-- Startup (do not modify order of script tags!) -->
<script>
const parts = window.location.pathname.replace(/^\//g, "").split("/")
parts[parts.length - 1] = "{{BASE}}"
const url = new URL(window.location.origin + "/" + parts.join("/"))
const el = document.getElementById("vscode-remote-commit")
const commit = el ? el.getAttribute("data-settings") : ""
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`,
"onigasm-umd": `${staticBase}/node_modules/onigasm-umd/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-web-links": `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.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`,
},
"vs/nls": nlsConfig,
}
</script>
<script src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
<script src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/loader.js"></script>
<!-- PROD_ONLY
<script src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/workbench/workbench.web.api.nls.js"></script>
<script src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/workbench/workbench.web.api.js"></script>
END_PROD_ONLY -->
<script>
require(["vs/code/browser/workbench/workbench"], function() {})
</script>
</html>

14
src/browser/register.ts Normal file
View File

@ -0,0 +1,14 @@
import { getOptions, normalize } from "../common/util"
const options = getOptions()
if ("serviceWorker" in navigator) {
const path = normalize(`${options.base}/static/${options.commit}/dist/serviceWorker.js`)
navigator.serviceWorker
.register(path, {
scope: options.base || "/",
})
.then(function() {
console.log("[Service Worker] registered")
})
}

View File

@ -0,0 +1,24 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
self.addEventListener("install", () => {
console.log("[Service Worker] install")
})
self.addEventListener("activate", (event: any) => {
event.waitUntil((self as any).clients.claim())
})
self.addEventListener("fetch", (event: any) => {
if (!navigator.onLine) {
event.respondWith(
new Promise((resolve) => {
resolve(
new Response("OFFLINE", {
status: 200,
statusText: "OK",
}),
)
}),
)
}
})

208
src/browser/socket.ts Normal file
View File

@ -0,0 +1,208 @@
import { field, logger, Logger } from "@coder/logger"
import { Emitter } from "../common/emitter"
import { generateUuid } from "../common/util"
const decoder = new TextDecoder("utf8")
export const decode = (buffer: string | ArrayBuffer): string => {
return typeof buffer !== "string" ? decoder.decode(buffer) : buffer
}
/**
* A web socket that reconnects itself when it closes. Sending messages while
* disconnected will throw an error.
*/
export class ReconnectingSocket {
protected readonly _onMessage = new Emitter<string | ArrayBuffer>()
public readonly onMessage = this._onMessage.event
protected readonly _onDisconnect = new Emitter<number | undefined>()
public readonly onDisconnect = this._onDisconnect.event
protected readonly _onClose = new Emitter<number | undefined>()
public readonly onClose = this._onClose.event
protected readonly _onConnect = new Emitter<void>()
public readonly onConnect = this._onConnect.event
// This helps distinguish messages between sockets.
private readonly logger: Logger
private socket?: WebSocket
private connecting?: Promise<void>
private closed = false
private readonly openTimeout = 10000
// Every time the socket fails to connect, the retry will be increasingly
// delayed up to a maximum.
private readonly retryBaseDelay = 1000
private readonly retryMaxDelay = 10000
private retryDelay?: number
private readonly retryDelayFactor = 1.5
// The socket must be connected for this amount of time before resetting the
// retry delay. This prevents rapid retries when the socket does connect but
// is closed shortly after.
private resetRetryTimeout?: NodeJS.Timeout
private readonly resetRetryDelay = 10000
private _binaryType: typeof WebSocket.prototype.binaryType = "arraybuffer"
public constructor(private customPath?: string, public readonly id: string = generateUuid(4)) {
// On Firefox the socket seems to somehow persist a page reload so the close
// event runs and we see "attempting to reconnect".
if (typeof window !== "undefined") {
window.addEventListener("beforeunload", () => this.close())
}
this.logger = logger.named(this.id)
}
public set binaryType(b: typeof WebSocket.prototype.binaryType) {
this._binaryType = b
if (this.socket) {
this.socket.binaryType = b
}
}
/**
* Permanently close the connection. Will not attempt to reconnect. Will
* remove event listeners.
*/
public close(code?: number): void {
if (this.closed) {
return
}
if (code) {
this.logger.info(`closing with code ${code}`)
}
if (this.resetRetryTimeout) {
clearTimeout(this.resetRetryTimeout)
}
this.closed = true
if (this.socket) {
this.socket.close()
} else {
this._onClose.emit(code)
}
}
public dispose(): void {
this._onMessage.dispose()
this._onDisconnect.dispose()
this._onClose.dispose()
this._onConnect.dispose()
this.logger.debug("disposed handlers")
}
/**
* Send a message on the socket. Logs an error if currently disconnected.
*/
public send(message: string | ArrayBuffer): void {
this.logger.trace(() => ["sending message", field("message", decode(message))])
if (!this.socket) {
return logger.error("tried to send message on closed socket")
}
this.socket.send(message)
}
/**
* Connect to the socket. Can also be called to wait until the connection is
* established in the case of disconnections. Multiple calls will be handled
* correctly.
*/
public async connect(): Promise<void> {
if (!this.connecting) {
this.connecting = new Promise((resolve, reject) => {
const tryConnect = (): void => {
if (this.closed) {
return reject(new Error("disconnected")) // Don't keep trying if we've closed permanently.
}
if (typeof this.retryDelay === "undefined") {
this.retryDelay = 0
} else {
this.retryDelay = this.retryDelay * this.retryDelayFactor || this.retryBaseDelay
if (this.retryDelay > this.retryMaxDelay) {
this.retryDelay = this.retryMaxDelay
}
}
this._connect()
.then((socket) => {
this.logger.info("connected")
this.socket = socket
this.socket.binaryType = this._binaryType
if (this.resetRetryTimeout) {
clearTimeout(this.resetRetryTimeout)
}
this.resetRetryTimeout = setTimeout(() => (this.retryDelay = undefined), this.resetRetryDelay)
this.connecting = undefined
this._onConnect.emit()
resolve()
})
.catch((error) => {
this.logger.error(`failed to connect: ${error.message}`)
tryConnect()
})
}
tryConnect()
})
}
return this.connecting
}
private async _connect(): Promise<WebSocket> {
const socket = await new Promise<WebSocket>((resolve, _reject) => {
if (this.retryDelay) {
this.logger.info(`retrying in ${this.retryDelay}ms...`)
}
setTimeout(() => {
this.logger.info("connecting...")
const socket = new WebSocket(
`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}${this.customPath || location.pathname}${
location.search ? `?${location.search}` : ""
}`,
)
const reject = (): void => {
_reject(new Error("socket closed"))
}
const timeout = setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
socket.removeEventListener("open", open)
socket.removeEventListener("close", reject)
_reject(new Error("timeout"))
}, this.openTimeout)
const open = (): void => {
clearTimeout(timeout)
socket.removeEventListener("close", reject)
resolve(socket)
}
socket.addEventListener("open", open)
socket.addEventListener("close", reject)
}, this.retryDelay)
})
socket.addEventListener("message", (event) => {
this.logger.trace(() => ["got message", field("message", decode(event.data))])
this._onMessage.emit(event.data)
})
socket.addEventListener("close", (event) => {
this.socket = undefined
if (!this.closed) {
this._onDisconnect.emit(event.code)
// It might be closed in the event handler.
if (!this.closed) {
this.logger.info("connection closed; attempting to reconnect")
this.connect()
}
} else {
this._onClose.emit(event.code)
this.logger.info("connection closed permanently")
}
})
return socket
}
}

View File

@ -1,92 +0,0 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
<!-- Workarounds/Hacks (remote user data uri) -->
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}">
<!-- NOTE@coder: Added the commit for use in caching, the product for the
extensions gallery URL, and nls for language support. -->
<meta id="vscode-remote-commit" data-settings="{{COMMIT}}">
<meta id="vscode-remote-product-configuration" data-settings="{{PRODUCT_CONFIGURATION}}">
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="./static-{{COMMIT}}/out/vs/server/src/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./manifest.json" crossorigin="use-credentials">
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.css">
<link rel="apple-touch-icon" href="./static-{{COMMIT}}/out/vs/server/src/media/code-server.png" />
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- Prefetch to avoid waterfall -->
<link rel="prefetch" href="./static-{{COMMIT}}/node_modules/semver-umd/lib/semver-umd.js">
</head>
<body aria-label="">
</body>
<!-- Startup (do not modify order of script tags!) -->
<!-- NOTE:coder: Modified to work against the current path and use the commit for caching. -->
<script>
// NOTE: Changes to inline scripts require update of content security policy
const basePath = window.location.pathname.replace(/\/+$/, '');
const base = window.location.origin + basePath;
const el = document.getElementById('vscode-remote-commit');
const commit = el ? el.getAttribute('data-settings') : "";
const staticBase = base + '/static-' + commit;
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(`${base}/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`,
'onigasm-umd': `${staticBase}/node_modules/onigasm-umd/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-web-links': `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.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`,
},
'vs/nls': nlsConfig,
};
</script>
<script src="./static-{{COMMIT}}/out/vs/loader.js"></script>
<script src="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.nls.js"></script>
<script src="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.js"></script>
<!-- TODO@coder: This errors with multiple anonymous define calls (one is
workbench.js and one is semver-umd.js). For now use the same method found in
workbench-dev.html. Appears related to the timing of the script load events. -->
<!-- <script src="./static-{{COMMIT}}/out/vs/workbench/workbench.js"></script> -->
<script>
// NOTE: Changes to inline scripts require update of content security policy
require(['vs/code/browser/workbench/workbench'], function() {});
</script>
</html>

View File

@ -1,53 +0,0 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
<!-- Workarounds/Hacks (remote user data uri) -->
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}">
<!-- NOTE@coder: Added the commit for use in caching, the product for the
extensions gallery URL, and nls for language support. -->
<meta id="vscode-remote-commit" data-settings="{{COMMIT}}">
<meta id="vscode-remote-product-configuration" data-settings="{{PRODUCT_CONFIGURATION}}">
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="./static/out/vs/server/src/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./manifest.json" crossorigin="use-credentials">
</head>
<body aria-label="">
</body>
<!-- Startup (do not modify order of script tags!) -->
<script>
const basePath = window.location.pathname.replace(/\/+$/, '');
const base = window.location.origin + basePath;
const el = document.getElementById('vscode-remote-commit');
const commit = el ? el.getAttribute('data-settings') : "";
const staticBase = base + '/static-' + commit;
self.require = {
baseUrl: `${staticBase}/out`,
paths: {
'vscode-textmate': `${staticBase}/node_modules/vscode-textmate/release/main`,
'onigasm-umd': `${staticBase}/node_modules/onigasm-umd/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-web-links': `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.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`,
},
};
</script>
<script src="./static/out/vs/loader.js"></script>
<script>
require(['vs/code/browser/workbench/workbench'], function() {});
</script>
</html>

View File

@ -1,57 +0,0 @@
import { URI } from "vs/base/common/uri";
import { IExtensionDescription } from "vs/platform/extensions/common/extensions";
import { ILogService } from "vs/platform/log/common/log";
import { Client } from "vs/server/node_modules/@coder/node-browser/out/client/client";
import { fromTar } from "vs/server/node_modules/@coder/requirefs/out/requirefs";
import { ExtensionActivationTimesBuilder } from "vs/workbench/api/common/extHostExtensionActivator";
import { IExtHostNodeProxy } from "./extHostNodeProxy";
export const loadCommonJSModule = async <T>(
module: IExtensionDescription,
activationTimesBuilder: ExtensionActivationTimesBuilder,
nodeProxy: IExtHostNodeProxy,
logService: ILogService,
vscode: any,
): Promise<T> => {
const fetchUri = URI.from({
scheme: self.location.protocol.replace(":", ""),
authority: self.location.host,
path: `${self.location.pathname.replace(/\/static.*\/out\/vs\/workbench\/services\/extensions\/worker\/extensionHostWorkerMain.js$/, "")}/tar`,
query: `path=${encodeURIComponent(module.extensionLocation.path)}`,
});
const response = await fetch(fetchUri.toString(true));
if (response.status !== 200) {
throw new Error(`Failed to download extension "${module.extensionLocation.path}"`);
}
const client = new Client(nodeProxy, { logger: logService });
const init = await client.handshake();
const buffer = new Uint8Array(await response.arrayBuffer());
const rfs = fromTar(buffer);
(<any>self).global = self;
rfs.provide("vscode", vscode);
Object.keys(client.modules).forEach((key) => {
const mod = (client.modules as any)[key];
if (key === "process") {
(<any>self).process = mod;
(<any>self).process.env = init.env;
return;
}
rfs.provide(key, mod);
switch (key) {
case "buffer":
(<any>self).Buffer = mod.Buffer;
break;
case "timers":
(<any>self).setImmediate = mod.setImmediate;
break;
}
});
try {
activationTimesBuilder.codeLoadingStart();
return rfs.require(".");
} finally {
activationTimesBuilder.codeLoadingStop();
}
};

59
src/common/api.ts Normal file
View File

@ -0,0 +1,59 @@
export interface Application {
readonly categories?: string[]
readonly comment?: string
readonly directory?: string
readonly exec?: string
readonly genericName?: string
readonly icon?: string
readonly installed?: boolean
readonly name: string
readonly path?: string
readonly sessionId?: string
readonly version?: string
}
export interface ApplicationsResponse {
readonly applications: ReadonlyArray<Application>
}
export enum SessionError {
NotFound = 4000,
FailedToStart,
Starting,
InvalidState,
Unknown,
}
export interface SessionResponse {
/**
* Whether the session was created or an existing one was returned.
*/
created: boolean
sessionId: string
}
export interface RecentResponse {
readonly paths: string[]
readonly workspaces: string[]
}
export interface RunningResponse {
readonly applications: ReadonlyArray<Application>
}
export interface HealthRequest {
readonly event: "health"
}
export type ClientMessage = HealthRequest
export interface HealthResponse {
readonly event: "health"
readonly connections: number
}
export type ServerMessage = HealthResponse
export interface ReadyMessage {
protocol: string
}

40
src/common/emitter.ts Normal file
View File

@ -0,0 +1,40 @@
export interface Disposable {
dispose(): void
}
export interface Event<T> {
(listener: (value: T) => void): Disposable
}
/**
* Emitter typecasts for a single event type.
*/
export class Emitter<T> {
private listeners: Array<(value: T) => void> = []
public get event(): Event<T> {
return (cb: (value: T) => void): Disposable => {
this.listeners.push(cb)
return {
dispose: (): void => {
const i = this.listeners.indexOf(cb)
if (i !== -1) {
this.listeners.splice(i, 1)
}
},
}
}
}
/**
* Emit an event with a value.
*/
public emit(value: T): void {
this.listeners.forEach((cb) => cb(value))
}
public dispose(): void {
this.listeners = []
}
}

25
src/common/http.ts Normal file
View File

@ -0,0 +1,25 @@
export enum HttpCode {
Ok = 200,
Redirect = 302,
NotFound = 404,
BadRequest = 400,
Unauthorized = 401,
LargePayload = 413,
ServerError = 500,
}
export class HttpError extends Error {
public constructor(message: string, public readonly code: number) {
super(message)
this.name = this.constructor.name
}
}
export enum ApiEndpoint {
applications = "/applications",
recent = "/recent",
run = "/run",
running = "/running",
session = "/session",
status = "/status",
}

View File

@ -1,47 +0,0 @@
import { Event } from "vs/base/common/event";
import { IChannel, IServerChannel } from "vs/base/parts/ipc/common/ipc";
import { createDecorator } from "vs/platform/instantiation/common/instantiation";
import { ReadWriteConnection } from "vs/server/node_modules/@coder/node-browser/out/common/connection";
export const INodeProxyService = createDecorator<INodeProxyService>("nodeProxyService");
export interface INodeProxyService extends ReadWriteConnection {
_serviceBrand: any;
send(message: string): void;
onMessage: Event<string>;
onUp: Event<void>;
onClose: Event<void>;
onDown: Event<void>;
}
export class NodeProxyChannel implements IServerChannel {
constructor(private service: INodeProxyService) {}
listen(_: unknown, event: string): Event<any> {
switch (event) {
case "onMessage": return this.service.onMessage;
}
throw new Error(`Invalid listen ${event}`);
}
async call(_: unknown, command: string, args?: any): Promise<any> {
switch (command) {
case "send": return this.service.send(args[0]);
}
throw new Error(`Invalid call ${command}`);
}
}
export class NodeProxyChannelClient {
_serviceBrand: any;
public readonly onMessage: Event<string>;
constructor(private readonly channel: IChannel) {
this.onMessage = this.channel.listen<string>("onMessage");
}
public send(data: string): void {
this.channel.call("send", [data]);
}
}

View File

@ -1,49 +0,0 @@
import { ITelemetryData } from "vs/base/common/actions";
import { Event } from "vs/base/common/event";
import { IChannel, IServerChannel } from "vs/base/parts/ipc/common/ipc";
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from "vs/platform/telemetry/common/gdprTypings";
import { ITelemetryInfo, ITelemetryService } from "vs/platform/telemetry/common/telemetry";
export class TelemetryChannel implements IServerChannel {
constructor(private service: ITelemetryService) {}
listen(_: unknown, event: string): Event<any> {
throw new Error(`Invalid listen ${event}`);
}
call(_: unknown, command: string, args?: any): Promise<any> {
switch (command) {
case "publicLog": return this.service.publicLog(args[0], args[1], args[2]);
case "publicLog2": return this.service.publicLog2(args[0], args[1], args[2]);
case "setEnabled": return Promise.resolve(this.service.setEnabled(args[0]));
case "getTelemetryInfo": return this.service.getTelemetryInfo();
}
throw new Error(`Invalid call ${command}`);
}
}
export class TelemetryChannelClient implements ITelemetryService {
_serviceBrand: any;
constructor(private readonly channel: IChannel) {}
public publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise<void> {
return this.channel.call("publicLog", [eventName, data, anonymizeFilePaths]);
}
public publicLog2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>, anonymizeFilePaths?: boolean): Promise<void> {
return this.channel.call("publicLog2", [eventName, data, anonymizeFilePaths]);
}
public setEnabled(value: boolean): void {
this.channel.call("setEnable", [value]);
}
public getTelemetryInfo(): Promise<ITelemetryInfo> {
return this.channel.call("getTelemetryInfo");
}
public get isOptedIn(): boolean {
return true;
}
}

View File

@ -1,10 +1,67 @@
import { logger } from "@coder/logger"
export interface Options {
base: string
commit: string
logLevel: number
sessionId?: string
}
/**
* Split a string up to the delimiter. If the delimiter doesn't exist the first
* item will have all the text and the second item will be an empty string.
*/
export const split = (str: string, delimiter: string): [string, string] => {
const index = str.indexOf(delimiter);
return index !== -1
? [str.substring(0, index).trim(), str.substring(index + 1)]
: [str, ""];
};
const index = str.indexOf(delimiter)
return index !== -1 ? [str.substring(0, index).trim(), str.substring(index + 1)] : [str, ""]
}
export const plural = (count: number): string => (count === 1 ? "" : "s")
export const generateUuid = (length = 24): string => {
const possible = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
return Array(length)
.fill(1)
.map(() => possible[Math.floor(Math.random() * possible.length)])
.join("")
}
/**
* Remove extra slashes in a URL.
*/
export const normalize = (url: string, keepTrailing = false): string => {
return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "")
}
/**
* Get options embedded in the HTML from the server.
*/
export const getOptions = <T extends Options>(): T => {
if (typeof document === "undefined") {
return {} as T
}
const el = document.getElementById("coder-options")
try {
if (!el) {
throw new Error("no options element")
}
const value = el.getAttribute("data-settings")
if (!value) {
throw new Error("no options value")
}
const options = JSON.parse(value)
if (typeof options.logLevel !== "undefined") {
logger.level = options.logLevel
}
const parts = window.location.pathname.replace(/^\//g, "").split("/")
parts[parts.length - 1] = options.base
const url = new URL(window.location.origin + "/" + parts.join("/"))
return {
...options,
base: normalize(url.pathname, true),
}
} catch (error) {
logger.warn(error.message)
return {} as T
}
}

Some files were not shown because too many files have changed in this diff Show More