Merge pull request #1338 from cdr/restructure
@ -1,12 +1,2 @@
|
|||||||
Dockerfile
|
**
|
||||||
build
|
!release
|
||||||
deployment
|
|
||||||
doc
|
|
||||||
.github
|
|
||||||
.gitignore
|
|
||||||
.node-version
|
|
||||||
.travis.yml
|
|
||||||
LICENSE
|
|
||||||
README.md
|
|
||||||
node_modules
|
|
||||||
release
|
|
||||||
|
408
.drone.yml
@ -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
@ -0,0 +1,6 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_size = 2
|
23
.eslintrc.yaml
Normal 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
@ -1,2 +1 @@
|
|||||||
* @code-asher @kylecarbs
|
* @code-asher @nhooyr
|
||||||
Dockerfile @nhooyr
|
|
||||||
|
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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: ... -->
|
|
22
.github/ISSUE_TEMPLATE/extension_bug.md
vendored
@ -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: ... -->
|
|
11
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -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
@ -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.
|
||||||
|
-->
|
10
.github/pull_request_template.md
vendored
@ -1,6 +1,4 @@
|
|||||||
<!-- Please answer these questions before submitting your PR. Thanks! -->
|
<!--
|
||||||
|
Please link to the issue this PR solves.
|
||||||
### Describe in detail the problem you had and how this PR fixes it
|
If there is no existing issue, please first create one unless the fix is minor.
|
||||||
|
-->
|
||||||
### Is there an open issue you can link to?
|
|
||||||
|
|
||||||
|
11
.gitignore
vendored
@ -1,5 +1,8 @@
|
|||||||
|
*.tsbuildinfo
|
||||||
|
.cache
|
||||||
|
build
|
||||||
|
dist*
|
||||||
|
out*
|
||||||
|
release*
|
||||||
node_modules
|
node_modules
|
||||||
/build
|
binaries
|
||||||
/release
|
|
||||||
/binaries
|
|
||||||
/lib
|
|
||||||
|
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "lib/vscode"]
|
||||||
|
path = lib/vscode
|
||||||
|
url = https://github.com/microsoft/vscode
|
@ -1 +0,0 @@
|
|||||||
12.14.0
|
|
4
.prettierrc.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
printWidth: 120
|
||||||
|
semi: false
|
||||||
|
trailingComma: all
|
||||||
|
arrowParens: always
|
2
.stylelintrc.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
extends:
|
||||||
|
- stylelint-config-recommended
|
56
.travis.yml
@ -1,37 +1,53 @@
|
|||||||
language: node_js
|
language: minimal
|
||||||
node_js:
|
|
||||||
- 12.14.0
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
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
|
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:
|
before_deploy:
|
||||||
depth: 3
|
- openssl aes-256-cbc -K $encrypted_0c1654c01c97_key -iv $encrypted_0c1654c01c97_iv -in ./ci/key.json.enc -out ./ci/key.json -d
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
- provider: releases
|
- provider: releases
|
||||||
file_glob: true
|
edge: true
|
||||||
draft: true
|
draft: true
|
||||||
tag_name: "$TRAVIS_TAG"
|
tag_name: $TRAVIS_TAG
|
||||||
target_commitish: "$TRAVIS_COMMIT"
|
target_commitish: $TRAVIS_COMMIT
|
||||||
name: "$TRAVIS_TAG"
|
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=
|
|
||||||
file:
|
file:
|
||||||
- release/*.tar.gz
|
- release/*.tar.gz
|
||||||
- release/*.zip
|
- release/*.zip
|
||||||
on:
|
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
|
tags: true
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
timeout: 1000
|
timeout: 600
|
||||||
yarn: true
|
yarn: true
|
||||||
directories:
|
|
||||||
- source
|
|
||||||
|
57
Dockerfile
@ -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
@ -1,4 +1,4 @@
|
|||||||
# code-server · [![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
|
`code-server` is [VS Code](https://github.com/Microsoft/vscode) running on a
|
||||||
remote server, accessible through the browser.
|
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
|
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
|
- **Code anywhere:** Code on your Chromebook, tablet, and laptop with a
|
||||||
consistent dev environment. develop more easily for Linux if you have a
|
consistent dev environment. Develop on a Linux machine and pick up from any
|
||||||
Windows or Mac, and pick up where you left off when switching workstations.
|
device with a web browser.
|
||||||
- **Server-powered:** Take advantage of large cloud servers to speed up tests,
|
- **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
|
compilations, downloads, and more. Preserve battery life when you're on the go
|
||||||
since all intensive computation runs on your server.
|
since all intensive computation runs on your server.
|
||||||
|
|
||||||
![Screenshot](/doc/assets/ide.gif)
|
![Example gif](/doc/assets/code-server.gif)
|
||||||
|
|
||||||
## Getting Started
|
## 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.
|
- 64-bit host.
|
||||||
- At least 1GB of RAM.
|
- At least 1GB of RAM.
|
||||||
- 2 cores or more are recommended (1 core works but not optimally).
|
- 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.
|
- For Linux: GLIBC 2.17 or later and GLIBCXX 3.4.15 or later.
|
||||||
- Docker (for Docker versions of `code-server`).
|
|
||||||
|
|
||||||
### Run over SSH
|
### Run over SSH
|
||||||
|
|
||||||
Use [sshcode](https://github.com/codercom/sshcode) for a simple setup.
|
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
|
### Digital Ocean
|
||||||
|
|
||||||
[![Create a Droplet](./doc/assets/droplet.svg)](https://marketplace.digitalocean.com/apps/code-server?action=deploy)
|
[![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
|
1. [Download a release](https://github.com/cdr/code-server/releases). (Linux and
|
||||||
OS X supported. Windows coming soon)
|
OS X supported. Windows support planned.)
|
||||||
2. Unpack the downloaded file then run the binary.
|
2. Unpack the downloaded release then run the included `code-server` script.
|
||||||
3. In your browser navigate to `localhost:8080`.
|
3. In your browser navigate to `localhost:8080`.
|
||||||
|
|
||||||
- For self-hosting and other information see [doc/quickstart.md](doc/quickstart.md).
|
## FAQ
|
||||||
- For hosting on cloud platforms see [doc/deploy.md](doc/deploy.md).
|
|
||||||
|
|
||||||
### Build
|
See [./doc/FAQ.md](./doc/FAQ.md).
|
||||||
|
|
||||||
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 extension’s 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)
|
|
||||||
|
|
||||||
## Enterprise
|
## Enterprise
|
||||||
|
|
||||||
Visit [our enterprise page](https://coder.com) for more information about our
|
Visit [our enterprise page](https://coder.com) for more information about our
|
||||||
enterprise offering.
|
enterprise offerings.
|
||||||
|
|
||||||
## Commercialization
|
|
||||||
|
|
||||||
If you would like to commercialize code-server, please contact
|
|
||||||
contact@coder.com.
|
|
||||||
|
364
ci/build.ts
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
10
ci/lib.sh
Normal 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
@ -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 "$@"
|
38
ci/release-image/Dockerfile
Normal 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
@ -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
@ -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
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"include": ["./**/*.ts"]
|
||||||
|
}
|
3226
ci/vscode.patch
Normal file
25
ci/vscode.sh
Executable 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
@ -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
@ -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
After Width: | Height: | Size: 4.0 MiB |
Before Width: | Height: | Size: 121 KiB |
Before Width: | Height: | Size: 2.3 MiB |
Before Width: | Height: | Size: 66 KiB |
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
7
main.js
@ -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");
|
|
64
package.json
@ -1,42 +1,60 @@
|
|||||||
{
|
{
|
||||||
|
"name": "code-server",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"version": "3.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"runner": "cd ./scripts && node --max-old-space-size=32384 -r ts-node/register ./build.ts",
|
"clean": "ci/clean.sh",
|
||||||
"start": "nodemon --watch ../../../out --verbose ../../../out/vs/server/main.js",
|
"vscode": "ci/vscode.sh",
|
||||||
"test": "./scripts/test.sh",
|
"vscode:patch": "cd ./lib/vscode && git apply ../../ci/vscode.patch",
|
||||||
"watch": "cd ../../../ && yarn watch",
|
"vscode:diff": "cd ./lib/vscode && git diff HEAD > ../../ci/vscode.patch",
|
||||||
"build": "yarn && yarn runner build",
|
"test": "mocha -r ts-node/register ./test/*.test.ts",
|
||||||
"package": "yarn runner package",
|
"lint": "ci/lint.sh",
|
||||||
"binary": "yarn runner binary",
|
"fmt": "ci/fmt.sh",
|
||||||
"patch:generate": "cd ../../../ && git diff --staged > ./src/vs/server/scripts/vscode.patch",
|
"runner": "cd ./ci && NODE_OPTIONS=--max_old_space_size=32384 ts-node ./build.ts",
|
||||||
"patch:apply": "cd ../../../ && git apply ./src/vs/server/scripts/vscode.patch"
|
"build": "yarn runner build",
|
||||||
|
"watch": "yarn runner watch"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@coder/nbin": "^1.2.7",
|
"@types/adm-zip": "^0.4.32",
|
||||||
"@types/fs-extra": "^8.0.1",
|
"@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/pem": "^1.9.5",
|
||||||
"@types/safe-compare": "^1.1.0",
|
"@types/safe-compare": "^1.1.0",
|
||||||
"@types/tar-fs": "^1.16.1",
|
"@types/semver": "^7.1.0",
|
||||||
"@types/tar-stream": "^1.6.1",
|
"@types/tar-fs": "^1.16.2",
|
||||||
"fs-extra": "^8.1.0",
|
"@types/ws": "^6.0.4",
|
||||||
"nodemon": "^1.19.1",
|
"@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",
|
"ts-node": "^8.4.1",
|
||||||
"typescript": "3.6"
|
"typescript": "3.7.2"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/node": "^10.12.12",
|
"@types/node": "^12.12.7",
|
||||||
"safe-buffer": "^5.1.1"
|
"safe-buffer": "^5.1.1",
|
||||||
|
"vfile-message": "^2.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coder/logger": "^1.1.12",
|
"@coder/logger": "1.1.11",
|
||||||
"@coder/node-browser": "^1.0.6",
|
"adm-zip": "^0.4.14",
|
||||||
"@coder/requirefs": "^1.0.6",
|
"fs-extra": "^8.1.0",
|
||||||
"httpolyglot": "^0.1.2",
|
"httpolyglot": "^0.1.2",
|
||||||
"pem": "^1.14.2",
|
"pem": "^1.14.2",
|
||||||
"safe-compare": "^1.1.4",
|
"safe-compare": "^1.1.4",
|
||||||
|
"semver": "^7.1.3",
|
||||||
|
"tar": "^6.0.1",
|
||||||
"tar-fs": "^2.0.0",
|
"tar-fs": "^2.0.0",
|
||||||
"tar-stream": "^2.1.0",
|
"ws": "^7.2.0"
|
||||||
"util": "^0.12.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
391
scripts/build.ts
@ -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));
|
|
@ -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 "$@"
|
|
@ -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 "$@"
|
|
@ -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"]
|
|
@ -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")
|
|
||||||
));
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "code-server",
|
|
||||||
"main": "out/vs/server/main",
|
|
||||||
"desktopName": "code-server-url-handler.desktop"
|
|
||||||
}
|
|
@ -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"
|
|
||||||
}
|
|
@ -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 "$@"
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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';
|
|
@ -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);
|
|
||||||
};
|
|
@ -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");
|
|
@ -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>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
BIN
src/browser/media/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
40
src/browser/media/manifest.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
src/browser/media/pwa-icon-128.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/browser/media/pwa-icon-192.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/browser/media/pwa-icon-256.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
src/browser/media/pwa-icon-384.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
src/browser/media/pwa-icon-512.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
src/browser/media/pwa-icon-96.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
24
src/browser/pages/app.css
Normal 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;
|
||||||
|
}
|
25
src/browser/pages/app.html
Normal 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
@ -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)
|
32
src/browser/pages/error.css
Normal 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;
|
||||||
|
}
|
35
src/browser/pages/error.html
Normal 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>
|
76
src/browser/pages/global.css
Normal 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;
|
||||||
|
}
|
47
src/browser/pages/home.css
Normal 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;
|
||||||
|
}
|
75
src/browser/pages/home.html
Normal 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>
|
39
src/browser/pages/login.css
Normal 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;
|
||||||
|
}
|
60
src/browser/pages/login.html
Normal 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>
|
26
src/browser/pages/update.css
Normal 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;
|
||||||
|
}
|
40
src/browser/pages/update.html
Normal 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>
|
105
src/browser/pages/vscode.html
Normal 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
@ -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")
|
||||||
|
})
|
||||||
|
}
|
24
src/browser/serviceWorker.ts
Normal 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
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
|
@ -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>
|
|
@ -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
@ -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
@ -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
@ -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",
|
||||||
|
}
|
@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
* Split a string up to the delimiter. If the delimiter doesn't exist the first
|
||||||
* item will have all the text and the second item will be an empty string.
|
* item will have all the text and the second item will be an empty string.
|
||||||
*/
|
*/
|
||||||
export const split = (str: string, delimiter: string): [string, string] => {
|
export const split = (str: string, delimiter: string): [string, string] => {
|
||||||
const index = str.indexOf(delimiter);
|
const index = str.indexOf(delimiter)
|
||||||
return index !== -1
|
return index !== -1 ? [str.substring(0, index).trim(), str.substring(index + 1)] : [str, ""]
|
||||||
? [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
|
||||||
|
}
|
||||||
|
}
|
||||||
|