Compare commits
324 Commits
2.1523-vsc
...
3.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28e91ba70c | ||
|
|
5aded14b87 | ||
|
|
a288351ad4 | ||
|
|
3b39482420 | ||
|
|
a5c35af81b | ||
|
|
b78bdaf46e | ||
|
|
aefef5b0e8 | ||
|
|
ca998240a0 | ||
|
|
d2a31477c7 | ||
|
|
9c6581273e | ||
|
|
d1445a8135 | ||
|
|
5fc00acc39 | ||
|
|
363cdd02df | ||
|
|
a5d1d3b90e | ||
|
|
aaa6c279a1 | ||
|
|
498becd11f | ||
|
|
411c61fb02 | ||
|
|
74a0bacdcf | ||
|
|
e7e7b0ffb7 | ||
|
|
fd339a7433 | ||
|
|
561b6343c8 | ||
|
|
e68d72c4d6 | ||
|
|
737a8f5965 | ||
|
|
c0dd29c591 | ||
|
|
8aa5675ba2 | ||
|
|
2086648c87 | ||
|
|
3a98d856a5 | ||
|
|
90fd1f7dd1 | ||
|
|
77ad73d579 | ||
|
|
13534fa0c0 | ||
|
|
37299abcc9 | ||
|
|
e480f6527e | ||
|
|
26584f2060 | ||
|
|
a4c0fd1fdc | ||
|
|
6c104c016e | ||
|
|
599670136d | ||
|
|
ce637d318d | ||
|
|
d8654b5a19 | ||
|
|
12c3ccd6c7 | ||
|
|
7954656610 | ||
|
|
87ebf03eb7 | ||
|
|
df1c34e291 | ||
|
|
4a65b58772 | ||
|
|
11fdb8854b | ||
|
|
0a92bb1607 | ||
|
|
5bac2cbdb8 | ||
|
|
511c3e95b2 | ||
|
|
0a5687bacf | ||
|
|
27320465b7 | ||
|
|
6df454e006 | ||
|
|
216652fb31 | ||
|
|
0f066d30b4 | ||
|
|
d1687c1533 | ||
|
|
f5f29c0120 | ||
|
|
8a6faa39c9 | ||
|
|
5887c1d339 | ||
|
|
664ef17af8 | ||
|
|
004004c047 | ||
|
|
09db0ffad5 | ||
|
|
a349ea8ff9 | ||
|
|
cfebf2c67f | ||
|
|
ddd44999c6 | ||
|
|
89d78a5921 | ||
|
|
99dd2db97c | ||
|
|
b52fbb4cb9 | ||
|
|
3463d56114 | ||
|
|
5f63d2b822 | ||
|
|
db4a4f0f50 | ||
|
|
d192726e80 | ||
|
|
d832f61d5b | ||
|
|
88f4b986c5 | ||
|
|
aeb6261189 | ||
|
|
6cb228037b | ||
|
|
a00fa85d77 | ||
|
|
57de78e12a | ||
|
|
2342443368 | ||
|
|
26647c54c9 | ||
|
|
253cf1c438 | ||
|
|
f6a5eaa965 | ||
|
|
f83a57a010 | ||
|
|
92cec80b0e | ||
|
|
1f601f27a2 | ||
|
|
71f1291623 | ||
|
|
9b07078b47 | ||
|
|
8433a3d081 | ||
|
|
c8269fb54d | ||
|
|
0b9a478289 | ||
|
|
c7e6e58387 | ||
|
|
8c515029fd | ||
|
|
1f6ff2f763 | ||
|
|
04542c99fd | ||
|
|
8c47ba255a | ||
|
|
4e6f6bc2cc | ||
|
|
7c65a54fcf | ||
|
|
1f43a673df | ||
|
|
744327ffd4 | ||
|
|
a442d3e3f9 | ||
|
|
3e8a6f93a4 | ||
|
|
308a84e6ec | ||
|
|
cc139acfd1 | ||
|
|
32f8f481b6 | ||
|
|
ec55ed39ee | ||
|
|
ee4b939efa | ||
|
|
538e8d8085 | ||
|
|
2c4ca14d53 | ||
|
|
8d934be6dc | ||
|
|
c54450941c | ||
|
|
e0e019fbd5 | ||
|
|
77af2a5b0e | ||
|
|
ecac0dd751 | ||
|
|
79b4c64a03 | ||
|
|
ccd01c49b9 | ||
|
|
069c5230cd | ||
|
|
c146457de4 | ||
|
|
88cab27165 | ||
|
|
a8914b025f | ||
|
|
0f87798ed6 | ||
|
|
963ebaca5b | ||
|
|
eef2ed0e78 | ||
|
|
0e3720169f | ||
|
|
21cfeb9da0 | ||
|
|
fd65cadaea | ||
|
|
70ad2354bb | ||
|
|
01710cf6ff | ||
|
|
b5c425b3a6 | ||
|
|
b00f6bf078 | ||
|
|
a2639ac617 | ||
|
|
07fcf1be7a | ||
|
|
75ca5b2b0b | ||
|
|
082f25faf1 | ||
|
|
595ce6f5e3 | ||
|
|
b1760c8d29 | ||
|
|
c870398c86 | ||
|
|
f76c809f7d | ||
|
|
4c6e4bedeb | ||
|
|
04e449c546 | ||
|
|
c147711ade | ||
|
|
bd7583a254 | ||
|
|
33b3523bf4 | ||
|
|
cf0f11105b | ||
|
|
9b7a203fe5 | ||
|
|
e44ac0a30e | ||
|
|
319cd3f7ab | ||
|
|
815dc06118 | ||
|
|
3a2644a2bc | ||
|
|
65690fca65 | ||
|
|
25288b1afd | ||
|
|
288e794c99 | ||
|
|
c567a06ff5 | ||
|
|
e5b68a8f4c | ||
|
|
51a5c77cb8 | ||
|
|
b9e7a3daa7 | ||
|
|
80b2d9481f | ||
|
|
0e2eaa9b34 | ||
|
|
0263188431 | ||
|
|
fa30639784 | ||
|
|
015b8dcf13 | ||
|
|
9f3240346c | ||
|
|
b76364db31 | ||
|
|
a065c12e83 | ||
|
|
76831f11fc | ||
|
|
b6aa0cbcba | ||
|
|
5681c87e33 | ||
|
|
46d6e17508 | ||
|
|
1aaa53622d | ||
|
|
bdb189a9bb | ||
|
|
8793110941 | ||
|
|
16bcf59cb0 | ||
|
|
f6b092b12d | ||
|
|
d47591e253 | ||
|
|
1a91588c42 | ||
|
|
39a57700bc | ||
|
|
1a54f6b7ef | ||
|
|
eb3cf303ad | ||
|
|
0d31a51eeb | ||
|
|
61d1af0413 | ||
|
|
4aa15401c3 | ||
|
|
80b1b1b672 | ||
|
|
0ec83f8736 | ||
|
|
db54f78e8e | ||
|
|
b8fa7da972 | ||
|
|
bf1be16d11 | ||
|
|
cc79edb312 | ||
|
|
ac4f2b8215 | ||
|
|
c8fc54bfb1 | ||
|
|
d574012871 | ||
|
|
250a54220c | ||
|
|
5baf16622f | ||
|
|
256419004d | ||
|
|
6a693e7181 | ||
|
|
b38cfa473e | ||
|
|
26f8216ec8 | ||
|
|
63f3c04c57 | ||
|
|
8a0f1d846e | ||
|
|
efaeb3b110 | ||
|
|
6cebfa469d | ||
|
|
205775ac97 | ||
|
|
4cc181cedc | ||
|
|
a149c5fc60 | ||
|
|
6e809b6a31 | ||
|
|
7c6fe56043 | ||
|
|
8cc11d1688 | ||
|
|
dbc5c065f8 | ||
|
|
4a54e914fc | ||
|
|
b30aefcfb1 | ||
|
|
b29346ecdf | ||
|
|
ef8da3864f | ||
|
|
108eb297d8 | ||
|
|
19f3acd9f0 | ||
|
|
3ee6b0ff0b | ||
|
|
e270f7da1b | ||
|
|
e6117decd0 | ||
|
|
c7127cb248 | ||
|
|
50234e5f04 | ||
|
|
5f562dc113 | ||
|
|
bb8bad49dc | ||
|
|
a674d882bf | ||
|
|
f51e045cd5 | ||
|
|
8122b7f69e | ||
|
|
25f18beda4 | ||
|
|
7e7923706f | ||
|
|
ae35673489 | ||
|
|
23f142fdc6 | ||
|
|
101139fabf | ||
|
|
e2d354c8f2 | ||
|
|
7c178805ea | ||
|
|
45f70e741f | ||
|
|
1474a82c7d | ||
|
|
d97feca3ba | ||
|
|
b2669e78bf | ||
|
|
66ee6e8201 | ||
|
|
62f050fda7 | ||
|
|
57425377e5 | ||
|
|
174cb2f8a9 | ||
|
|
42bddce21f | ||
|
|
f2a15795a1 | ||
|
|
6dd5e515c5 | ||
|
|
92da02ef3e | ||
|
|
3ce7129492 | ||
|
|
336ee28888 | ||
|
|
3f2240ab65 | ||
|
|
1087037728 | ||
|
|
1959d82912 | ||
|
|
8024144381 | ||
|
|
6a1dcab7a6 | ||
|
|
e6d1f2a7c8 | ||
|
|
44c4722edf | ||
|
|
e5fc63f2c8 | ||
|
|
015a99e87d | ||
|
|
884491d72b | ||
|
|
e14362f322 | ||
|
|
917aa48072 | ||
|
|
938c6ef829 | ||
|
|
0add01d383 | ||
|
|
2018024810 | ||
|
|
a1d6bcb8e5 | ||
|
|
727ac6483b | ||
|
|
2c15c09fc0 | ||
|
|
2ad2582cc0 | ||
|
|
cee0ac213c | ||
|
|
780a673017 | ||
|
|
af71203955 | ||
|
|
fc3acfabb2 | ||
|
|
3d5db8313a | ||
|
|
73cf8f34e3 | ||
|
|
766efd6079 | ||
|
|
87485948ad | ||
|
|
7e4a73ce2d | ||
|
|
2f0878d9b7 | ||
|
|
f65c9b23fc | ||
|
|
cd859d117f | ||
|
|
e22964915a | ||
|
|
197d0b6ca9 | ||
|
|
422503ef98 | ||
|
|
ea36345d2c | ||
|
|
a89d83cbba | ||
|
|
83ff31b620 | ||
|
|
3a9b032c72 | ||
|
|
f73e9225b4 | ||
|
|
168ccb0dfc | ||
|
|
58f7f5b769 | ||
|
|
b8e6369fbe | ||
|
|
d81d5f499f | ||
|
|
4be178d234 | ||
|
|
9c40466b4b | ||
|
|
95693fb58e | ||
|
|
e7945bea94 | ||
|
|
91f49e1efd | ||
|
|
eea9c1618c | ||
|
|
f1b38e4e48 | ||
|
|
ff99a1d768 | ||
|
|
7f07b8f66c | ||
|
|
faae03da6b | ||
|
|
a6e4f96737 | ||
|
|
cc7585bbc2 | ||
|
|
14a0cd3ffd | ||
|
|
3ff83eda45 | ||
|
|
f133b00851 | ||
|
|
ece840834d | ||
|
|
76f6ff4145 | ||
|
|
2458cde498 | ||
|
|
82e2b8a169 | ||
|
|
5aa2abaf9f | ||
|
|
fdb2308c62 | ||
|
|
4cd2f2cd52 | ||
|
|
88cef85f62 | ||
|
|
bdd11f741b | ||
|
|
56ce780522 | ||
|
|
567010e163 | ||
|
|
4ae2c81157 | ||
|
|
ae43e2016f | ||
|
|
3f6cbfa4dd | ||
|
|
1c50b5285e | ||
|
|
ea9c511db8 | ||
|
|
e1e3f32643 | ||
|
|
4290cffe3b | ||
|
|
548d095611 | ||
|
|
846dcbb947 | ||
|
|
d7d3368cc2 | ||
|
|
134040fea3 | ||
|
|
65caa26d40 | ||
|
|
7812f6b75a | ||
|
|
637e58f255 | ||
|
|
6135630fc0 |
@@ -1,12 +1,2 @@
|
||||
Dockerfile
|
||||
build
|
||||
deployment
|
||||
doc
|
||||
.github
|
||||
.gitignore
|
||||
.node-version
|
||||
.travis.yml
|
||||
LICENSE
|
||||
README.md
|
||||
node_modules
|
||||
release
|
||||
**
|
||||
!release
|
||||
|
||||
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
|
||||
Dockerfile @nhooyr
|
||||
* @code-asher @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. -->
|
||||
17
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question.
|
||||
title: ''
|
||||
labels: 'question'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!-- Please search existing issues to avoid creating duplicates. -->
|
||||
|
||||
## Description
|
||||
|
||||
<!-- A description of the the question. -->
|
||||
|
||||
## Related Issues
|
||||
|
||||
<!-- Any issues related to your question. -->
|
||||
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! -->
|
||||
|
||||
### Describe in detail the problem you had and how this PR fixes it
|
||||
|
||||
### Is there an open issue you can link to?
|
||||
|
||||
<!--
|
||||
Please link to the issue this PR solves.
|
||||
If there is no existing issue, please first create one unless the fix is minor.
|
||||
-->
|
||||
|
||||
9
.gitignore
vendored
@@ -1,3 +1,8 @@
|
||||
node_modules
|
||||
*.tsbuildinfo
|
||||
.cache
|
||||
build
|
||||
release
|
||||
dist*
|
||||
out*
|
||||
release*
|
||||
node_modules
|
||||
binaries
|
||||
|
||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "lib/vscode"]
|
||||
path = lib/vscode
|
||||
url = https://github.com/microsoft/vscode
|
||||
@@ -1 +0,0 @@
|
||||
10.16.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
|
||||
98
.travis.yml
@@ -1,80 +1,58 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 10.16.0
|
||||
services:
|
||||
- docker
|
||||
language: minimal
|
||||
|
||||
before_install:
|
||||
- export MAJOR_VERSION="2"
|
||||
- export VSCODE_VERSION="1.38.1"
|
||||
- export VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER"
|
||||
- export TAG="$VERSION-vsc$VSCODE_VERSION"
|
||||
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then export MINIFY="true"; fi
|
||||
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then export PACKAGE="true"; fi
|
||||
|
||||
# Don't build on tags because we'll already have built the commit.
|
||||
jobs:
|
||||
include:
|
||||
- name: "Linux build"
|
||||
os: linux
|
||||
dist: trusty
|
||||
env: TARGET="linux"
|
||||
- name: Test
|
||||
if: tag IS blank
|
||||
script: scripts/ci.bash
|
||||
- name: "Alpine build"
|
||||
os: linux
|
||||
dist: trusty
|
||||
env: TARGET="alpine"
|
||||
if: tag IS blank
|
||||
script: scripts/ci.bash
|
||||
- name: "MacOS build"
|
||||
script: ./ci/image/run.sh "yarn && git submodule update --init && yarn vscode:patch && ./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/build-test.sh"
|
||||
- ./ci/release-image/push.sh
|
||||
- name: Linux ARM64 Release
|
||||
if: tag IS present
|
||||
script:
|
||||
- ./ci/image/run.sh "yarn && yarn vscode && ci/release.sh && ./ci/build-test.sh"
|
||||
- ./ci/release-image/push.sh
|
||||
arch: arm64
|
||||
- name: MacOS Release
|
||||
if: tag IS present
|
||||
os: osx
|
||||
if: tag IS blank
|
||||
script: travis_wait 30 scripts/ci.bash
|
||||
- name: "Docker build"
|
||||
os: linux
|
||||
dist: trusty
|
||||
env: DOCKER_BUILD="true"
|
||||
if: branch == master AND tag IS blank
|
||||
script: docker build --build-arg githubToken="$GITHUB_TOKEN" --build-arg codeServerVersion="$VERSION" --build-arg vscodeVersion="$VSCODE_VERSION" -t codercom/code-server:"$TAG" -t codercom/code-server:v2 .
|
||||
|
||||
git:
|
||||
depth: 3
|
||||
language: node_js
|
||||
node_js: 12
|
||||
script: yarn && yarn vscode && travis_wait 60 ci/release.sh && ./ci/build-test.sh
|
||||
|
||||
before_deploy:
|
||||
- echo "$TAG" "$TRAVIS_COMMIT"
|
||||
- git config --local user.name "$USER_NAME"
|
||||
- git config --local user.email "$USER_EMAIL"
|
||||
- if ! git tag "$TAG" "$TRAVIS_COMMIT" ; then echo "$TAG already exists"; fi
|
||||
- if [[ -n "$DOCKER_BUILD" ]] ; then echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin ; fi
|
||||
- echo "$JSON_KEY" | base64 --decode > ./ci/key.json
|
||||
|
||||
deploy:
|
||||
- provider: releases
|
||||
file_glob: true
|
||||
edge: true
|
||||
draft: true
|
||||
tag_name: "$TAG"
|
||||
target_commitish: "$TRAVIS_COMMIT"
|
||||
name: "$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=
|
||||
overwrite: true
|
||||
tag_name: $TRAVIS_TAG
|
||||
target_commitish: $TRAVIS_COMMIT
|
||||
name: $TRAVIS_TAG
|
||||
file:
|
||||
- release/*.tar.gz
|
||||
- release/*.zip
|
||||
on:
|
||||
repo: cdr/code-server
|
||||
branch: master
|
||||
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: docker push codercom/code-server:"$TAG" ; docker push codercom/code-server:v2
|
||||
tags: true
|
||||
- provider: gcs
|
||||
edge: true
|
||||
bucket: "codesrv-ci.cdr.sh"
|
||||
upload_dir: "releases"
|
||||
key_file: ./ci/key.json
|
||||
local_dir: release-upload
|
||||
on:
|
||||
repo: cdr/code-server
|
||||
branch: master
|
||||
condition: -n "$DOCKER_BUILD"
|
||||
tags: true
|
||||
# TODO: The gcs provider fails to install on arm64.
|
||||
condition: $TRAVIS_CPU_ARCH = amd64
|
||||
|
||||
cache:
|
||||
timeout: 600
|
||||
yarn: true
|
||||
timeout: 1000
|
||||
directories:
|
||||
- .cache
|
||||
- lib/vscode/.build/extensions
|
||||
|
||||
60
Dockerfile
@@ -1,60 +0,0 @@
|
||||
FROM node:10.16.0
|
||||
ARG codeServerVersion=docker
|
||||
ARG vscodeVersion
|
||||
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
|
||||
|
||||
# Ensure latest yarn.
|
||||
RUN npm install -g yarn@1.13
|
||||
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
RUN yarn \
|
||||
&& MINIFY=true GITHUB_TOKEN="${githubToken}" yarn build "${vscodeVersion}" "${codeServerVersion}" \
|
||||
&& yarn binary "${vscodeVersion}" "${codeServerVersion}" \
|
||||
&& mv "/src/build/code-server${codeServerVersion}-vsc${vscodeVersion}-linux-x86_64-built/code-server${codeServerVersion}-vsc${vscodeVersion}-linux-x86_64" /src/build/code-server \
|
||||
&& rm -r /src/build/vscode-* \
|
||||
&& rm -r /src/build/code-server*-linux-*
|
||||
|
||||
# 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
|
||||
|
||||
RUN adduser --gecos '' --disabled-password coder && \
|
||||
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
|
||||
|
||||
USER coder
|
||||
# We create first instead of just using WORKDIR as when WORKDIR creates, the
|
||||
# user is root.
|
||||
RUN mkdir -p /home/coder/project
|
||||
|
||||
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/build/code-server /usr/local/bin/code-server
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["dumb-init", "code-server", "--host", "0.0.0.0"]
|
||||
137
README.md
@@ -1,143 +1,58 @@
|
||||
# code-server · [](https://github.com/cdr/code-server/blob/master/LICENSE) [](https://github.com/cdr/code-server/releases/latest) [](https://github.com/cdr/code-server)
|
||||
# code-server
|
||||
|
||||
`code-server` is [VS Code](https://github.com/Microsoft/vscode) running on a
|
||||
remote server, accessible through the browser.
|
||||
|
||||
Try it out:
|
||||
|
||||
```bash
|
||||
docker run -it -p 127.0.0.1:8080:8080 -v "${HOME}/.local/share/code-server:/home/coder/.local/share/code-server" -v "$PWD:/home/coder/project" codercom/code-server:v2
|
||||
docker run -it -p 127.0.0.1:8080:8080 -v "$PWD:/home/coder/project" codercom/code-server
|
||||
```
|
||||
|
||||
- **Consistent environment:** Code on your Chromebook, tablet, and laptop with a
|
||||
consistent dev environment. develop more easily for Linux if you have a
|
||||
Windows or Mac, and pick up where you left off when switching workstations.
|
||||
- **Code anywhere:** Code on your Chromebook, tablet, and laptop with a
|
||||
consistent dev environment. Develop on a Linux machine and pick up from any
|
||||
device with a web browser.
|
||||
- **Server-powered:** Take advantage of large cloud servers to speed up tests,
|
||||
compilations, downloads, and more. Preserve battery life when you're on the go
|
||||
since all intensive computation runs on your server.
|
||||
|
||||

|
||||

|
||||
|
||||
## Getting Started
|
||||
|
||||
### Requirements
|
||||
|
||||
- Minimum GLIBC version of 2.17 and a minimum version of GLIBCXX of 3.4.15.
|
||||
- This is the main requirement for building Visual Studio Code. We cannot go lower than this.
|
||||
- A 64-bit host with at least 1GB RAM and 2 cores.
|
||||
- 1 core hosts would work but not optimally.
|
||||
- Docker (for Docker versions of `code-server`).
|
||||
- 64-bit host.
|
||||
- At least 1GB of RAM.
|
||||
- 2 cores or more are recommended (1 core works but not optimally).
|
||||
- Secure connection over HTTPS or localhost (required for service workers and
|
||||
clipboard support).
|
||||
- For Linux: GLIBC 2.17 or later and GLIBCXX 3.4.15 or later.
|
||||
|
||||
### Run over SSH
|
||||
|
||||
Use [sshcode](https://github.com/codercom/sshcode) for a simple setup.
|
||||
|
||||
### Docker
|
||||
See the Docker one-liner mentioned above. Dockerfile is at [/Dockerfile](/Dockerfile).
|
||||
|
||||
To debug Golang using the
|
||||
[ms-vscode-go extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.Go),
|
||||
you need to add `--security-opt seccomp=unconfined` to your `docker run`
|
||||
arguments when launching code-server with Docker. See
|
||||
[#725](https://github.com/cdr/code-server/issues/725) for details.
|
||||
|
||||
### Digital Ocean
|
||||
[](https://marketplace.digitalocean.com/apps/code-server?action=deploy)
|
||||
|
||||
### Binaries
|
||||
1. [Download a binary](https://github.com/cdr/code-server/releases). (Linux and
|
||||
OS X supported. Windows coming soon)
|
||||
2. Unpack the downloaded file then run the binary.
|
||||
[](https://marketplace.digitalocean.com/apps/code-server)
|
||||
|
||||
### Releases
|
||||
|
||||
1. [Download a release](https://github.com/cdr/code-server/releases). (Linux and
|
||||
OS X supported. Windows support planned.)
|
||||
2. Unpack the downloaded release then run the included `code-server` script.
|
||||
3. In your browser navigate to `localhost:8080`.
|
||||
|
||||
- For self-hosting and other information see [doc/quickstart.md](doc/quickstart.md).
|
||||
- For hosting on cloud platforms see [doc/deploy.md](doc/deploy.md).
|
||||
## FAQ
|
||||
|
||||
### Build
|
||||
- If you also plan on developing, set the `OUT` environment variable. Otherwise
|
||||
it will build in this directory which will cause issues because `yarn watch`
|
||||
will try to compile the build directory as well.
|
||||
- Run `yarn build ${vscodeVersion} ${codeServerVersion}` in this directory (for
|
||||
example: `yarn build 1.36.0 development`).
|
||||
- If you target the same VS Code version our Travis builds do everything will
|
||||
work but if you target some other version it might not (we have to do some
|
||||
patching to VS Code so different versions aren't always compatible).
|
||||
- You can run the built code with `node path/to/build/out/vs/server/main.js` or run
|
||||
`yarn binary` with the same arguments in the previous step to package the
|
||||
code into a single binary.
|
||||
|
||||
## 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.
|
||||

|
||||
- 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
|
||||
At the moment we can't use the official VS Code Marketplace. We've created a
|
||||
custom extension marketplace focused around open-sourced extensions. However,
|
||||
you can manually download the extension to your extensions directory. It's also
|
||||
possible to set your own marketplace URLs 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.
|
||||
See [./doc/FAQ.md](./doc/FAQ.md).
|
||||
|
||||
## Contributing
|
||||
### Development
|
||||
```shell
|
||||
git clone https://github.com/microsoft/vscode
|
||||
cd vscode
|
||||
git checkout <see travis.yml for the VS Code version to use here>
|
||||
git clone https://github.com/cdr/code-server src/vs/server
|
||||
cd src/vs/server
|
||||
yarn patch:apply
|
||||
yarn
|
||||
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 and ignore the error at the end from
|
||||
`vscode-ripgrep`.
|
||||
|
||||
### 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:
|
||||
- Change the remote schema to `code-server`.
|
||||
- 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 and get the initial log level
|
||||
from the server.
|
||||
- Add an upload service for use in editor windows and the explorer along with a
|
||||
file prefix to ignore for temporary files created during upload.
|
||||
- Make changing the display language work.
|
||||
- Make hiding or toggling the menu bar possible.
|
||||
- Make it possible for us to load code on the client.
|
||||
- Modify the build process to include our code.
|
||||
|
||||
## License
|
||||
[MIT](LICENSE)
|
||||
See [./doc/CONTRIBUTING.md](./doc/CONTRIBUTING.md).
|
||||
|
||||
## Enterprise
|
||||
Visit [our enterprise page](https://coder.com/enterprise) for more information
|
||||
about our enterprise offering.
|
||||
|
||||
## Commercialization
|
||||
If you would like to commercialize code-server, please contact
|
||||
contact@coder.com.
|
||||
Visit [our enterprise page](https://coder.com) for more information about our
|
||||
enterprise offerings.
|
||||
|
||||
21
ci/build-test.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# build-test.bash -- Make sure the build worked.
|
||||
# This is to make sure we don't have Node version errors or any other
|
||||
# compilation-related errors.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
function main() {
|
||||
cd "$(dirname "${0}")/.." || exit 1
|
||||
|
||||
local output
|
||||
output=$(node ./build/out/node/entry.js --list-extensions 2>&1)
|
||||
if echo "$output" | grep 'was compiled against a different Node.js version'; then
|
||||
echo "$output"
|
||||
exit 1
|
||||
else
|
||||
echo "Build ran successfully"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
372
ci/build.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
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}...`, !process.env.CI)
|
||||
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, false, {
|
||||
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.env.CI) {
|
||||
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, true, {
|
||||
commit,
|
||||
date: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
|
||||
private async copyDependencies(
|
||||
name: string,
|
||||
sourcePath: string,
|
||||
buildPath: string,
|
||||
ignoreScripts: boolean,
|
||||
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 ${ignoreScripts ? "--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.
|
||||
|
||||
dir="$(dirname "$(readlink -f "$0" || realpath "$0")")"
|
||||
|
||||
exec "$dir/node" "$dir/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 "$@"
|
||||
23
ci/image/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM centos:7
|
||||
|
||||
RUN yum update -y && yum install -y \
|
||||
devtoolset-6 \
|
||||
gcc-c++ \
|
||||
xz \
|
||||
ccache \
|
||||
git \
|
||||
wget \
|
||||
openssl \
|
||||
libxkbfile-devel \
|
||||
libsecret-devel \
|
||||
libx11-devel
|
||||
|
||||
RUN mkdir /usr/share/node && cd /usr/share/node \
|
||||
&& curl "https://nodejs.org/dist/v12.14.0/node-v12.14.0-linux-$(uname -m | sed 's/86_//; s/aarch/arm/').tar.xz" | tar xJ --strip-components=1 --
|
||||
ENV PATH "$PATH:/usr/share/node/bin"
|
||||
RUN npm install -g yarn@1.22.4
|
||||
|
||||
RUN curl -L "https://github.com/mvdan/sh/releases/download/v3.0.1/shfmt_v3.0.1_linux_$(uname -m | sed 's/x86_/amd/; s/aarch64/arm/')" > /usr/local/bin/shfmt \
|
||||
&& chmod +x /usr/local/bin/shfmt
|
||||
|
||||
ENTRYPOINT ["/bin/bash", "-c"]
|
||||
26
ci/image/run.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
main() {
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
# This, strangely enough, fixes the arm build being terminated for not having
|
||||
# output on Travis. It's as if output is buffered and only displayed once a
|
||||
# certain amount is collected. Five seconds didn't work but one second seems
|
||||
# to generate enough output to make it work.
|
||||
local pid
|
||||
while true; do
|
||||
echo 'Still running...'
|
||||
sleep 1
|
||||
done &
|
||||
pid=$!
|
||||
|
||||
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" "$*"
|
||||
|
||||
kill $pid
|
||||
}
|
||||
|
||||
main "$@"
|
||||
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 "$@"
|
||||
43
ci/release-image/Dockerfile
Normal file
@@ -0,0 +1,43 @@
|
||||
FROM debian:10
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
curl \
|
||||
dumb-init \
|
||||
htop \
|
||||
locales \
|
||||
man \
|
||||
nano \
|
||||
git \
|
||||
procps \
|
||||
ssh \
|
||||
sudo \
|
||||
vim \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# https://wiki.debian.org/Locale#Manually
|
||||
RUN sed -i "s/# en_US.UTF-8/en_US.UTF-8/" /etc/locale.gen \
|
||||
&& locale-gen
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
RUN chsh -s /bin/bash
|
||||
ENV SHELL=/bin/bash
|
||||
|
||||
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
|
||||
|
||||
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 && \
|
||||
ln -s /usr/local/lib/code-server/code-server /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 | sed 's/aarch/arm/')
|
||||
|
||||
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" "./$archive_name"
|
||||
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$ext"
|
||||
mkdir -p "./release-upload/latest"
|
||||
cp "./release/$archive_name$ext" "./release-upload/latest/$target-$arch$ext"
|
||||
}
|
||||
|
||||
# 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"]
|
||||
}
|
||||
3289
ci/vscode.patch
Normal file
22
ci/vscode.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/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
|
||||
)
|
||||
}
|
||||
|
||||
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/node/entry.js # Run the built JavaScript with Node.
|
||||
```
|
||||
148
doc/FAQ.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# 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 [#1299](https://github.com/cdr/code-server/issues/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 run 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.
|
||||
|
||||
## How do I securely access web services?
|
||||
|
||||
code-server is capable of proxying to any port using either a subdomain or a
|
||||
subpath which means you can securely access these services using code-server's
|
||||
built-in authentication.
|
||||
|
||||
### Sub-domains
|
||||
|
||||
You will need a DNS entry that points to your server for each port you want to
|
||||
access. You can either set up a wildcard DNS entry for `*.<domain>` if your domain
|
||||
name registrar supports it or you can create one for every port you want to
|
||||
access (`3000.<domain>`, `8080.<domain>`, etc).
|
||||
|
||||
You should also set up TLS certificates for these subdomains, either using a
|
||||
wildcard certificate for `*.<domain>` or individual certificates for each port.
|
||||
|
||||
Start code-server with the `--proxy-domain` flag set to your domain.
|
||||
|
||||
```
|
||||
code-server --proxy-domain <domain>
|
||||
```
|
||||
|
||||
Now you can browse to `<port>.<domain>`. Note that this uses the host header so
|
||||
ensure your reverse proxy forwards that information if you are using one.
|
||||
|
||||
### Sub-paths
|
||||
|
||||
Just browse to `/proxy/<port>/`.
|
||||
|
||||
## 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.
|
||||
|
||||
## How does code-server decide what workspace or folder to open?
|
||||
|
||||
code-server tries the following in order:
|
||||
|
||||
1. The `workspace` query parameter.
|
||||
2. The `folder` query parameter.
|
||||
3. The directory passed on the command line.
|
||||
4. The last opened workspace or folder.
|
||||
|
||||
## 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!
|
||||
|
||||

|
||||
|
||||
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 = ^INFO\s+Failed login attempt\s+{\"password\":\"(\\.|[^"])*\",\"remoteAddress\":\"<HOST>\"
|
||||
|
||||
# Use this instead for proxies (ensure the proxy is configured to send the
|
||||
# X-Forwarded-For header).
|
||||
# failregex = ^INFO\s+Failed login attempt\s+{\"password\":\"(\\.|[^"])*\",\"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
|
||||
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
|
||||
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 `./code-server.fail2ban.conf`. Once this
|
||||
is installed and configured correctly, repeated failed login attempts should
|
||||
automatically be banned from connecting to your server.
|
||||
@@ -1,111 +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.
|
||||
|
||||
### Encrypting traffic with HTTPS
|
||||
To encrypt the traffic between the browser and server use `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.
|
||||
|
||||
You can use [Let's Encrypt](https://letsencrypt.org/) to get an SSL certificate
|
||||
for free.
|
||||
|
||||
### 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=VSCode in a browser
|
||||
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
|
||||
Type=simple
|
||||
|
||||
ExecStart=/usr/bin/code-server $(pwd)
|
||||
|
||||
WorkingDirectory=$HOME/projects
|
||||
|
||||
ExecStop=/sbin/start-stop-daemon --stop -x /usr/bin/code-server
|
||||
|
||||
Restart=on-failure
|
||||
|
||||
User=1000
|
||||
|
||||
[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
lib/vscode
Submodule
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/cli");
|
||||
67
package.json
@@ -1,36 +1,67 @@
|
||||
{
|
||||
"name": "code-server",
|
||||
"license": "MIT",
|
||||
"version": "3.1.1",
|
||||
"scripts": {
|
||||
"ensure-in-vscode": "bash ./scripts/tasks.bash ensure-in-vscode",
|
||||
"preinstall": "yarn ensure-in-vscode && cd ../../../ && yarn || true",
|
||||
"postinstall": "rm -rf node_modules/@types/node",
|
||||
"start": "yarn ensure-in-vscode && nodemon --watch ../../../out --verbose ../../../out/vs/server/main.js",
|
||||
"watch": "yarn ensure-in-vscode && cd ../../../ && yarn watch",
|
||||
"build": "bash ./scripts/tasks.bash build",
|
||||
"package": "bash ./scripts/tasks.bash package",
|
||||
"package-prebuilt": "bash ./scripts/tasks.bash package-prebuilt",
|
||||
"binary": "bash ./scripts/tasks.bash binary",
|
||||
"patch:generate": "yarn ensure-in-vscode && cd ../../../ && git diff --staged > ./src/vs/server/scripts/vscode.patch",
|
||||
"patch:apply": "yarn ensure-in-vscode && cd ../../../ && git apply ./src/vs/server/scripts/vscode.patch"
|
||||
"clean": "ci/clean.sh",
|
||||
"vscode": "ci/vscode.sh",
|
||||
"vscode:patch": "cd ./lib/vscode && git apply ../../ci/vscode.patch",
|
||||
"vscode:diff": "cd ./lib/vscode && git diff HEAD > ../../ci/vscode.patch",
|
||||
"test": "mocha -r ts-node/register ./test/*.test.ts",
|
||||
"lint": "ci/lint.sh",
|
||||
"fmt": "ci/fmt.sh",
|
||||
"runner": "cd ./ci && NODE_OPTIONS=--max_old_space_size=32384 ts-node ./build.ts",
|
||||
"build": "yarn runner build",
|
||||
"watch": "yarn runner watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coder/nbin": "^1.2.2",
|
||||
"@types/adm-zip": "^0.4.32",
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^12.12.7",
|
||||
"@types/parcel-bundler": "^1.12.1",
|
||||
"@types/pem": "^1.9.5",
|
||||
"@types/safe-compare": "^1.1.0",
|
||||
"@types/tar-fs": "^1.16.1",
|
||||
"@types/semver": "^7.1.0",
|
||||
"@types/tar-fs": "^1.16.2",
|
||||
"@types/ssh2": "0.5.39",
|
||||
"@types/ssh2-streams": "^0.1.6",
|
||||
"@types/tar-stream": "^1.6.1",
|
||||
"nodemon": "^1.19.1"
|
||||
"@types/ws": "^6.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "^2.0.0",
|
||||
"@typescript-eslint/parser": "^2.0.0",
|
||||
"eslint": "^6.2.0",
|
||||
"eslint-config-prettier": "^6.0.0",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"leaked-handles": "^5.2.0",
|
||||
"mocha": "^6.2.0",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"prettier": "^1.18.2",
|
||||
"stylelint": "^13.0.0",
|
||||
"stylelint-config-recommended": "^3.0.0",
|
||||
"ts-node": "^8.4.1",
|
||||
"typescript": "3.7.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "^10.12.12",
|
||||
"safe-buffer": "^5.1.1"
|
||||
"@types/node": "^12.12.7",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"vfile-message": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coder/logger": "^1.1.8",
|
||||
"@coder/logger": "1.1.11",
|
||||
"adm-zip": "^0.4.14",
|
||||
"fs-extra": "^8.1.0",
|
||||
"http-proxy": "^1.18.0",
|
||||
"httpolyglot": "^0.1.2",
|
||||
"node-pty": "^0.9.0",
|
||||
"pem": "^1.14.2",
|
||||
"safe-compare": "^1.1.4",
|
||||
"semver": "^7.1.3",
|
||||
"ssh2": "^0.8.7",
|
||||
"tar": "^6.0.1",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tar-stream": "^2.1.0"
|
||||
"ws": "^7.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// This builds the package and product JSON files for the final build.
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const rootPath = path.resolve(__dirname, "..");
|
||||
const sourcePath = process.argv[2];
|
||||
const buildPath = process.argv[3];
|
||||
const vscodeVersion = process.argv[4];
|
||||
const codeServerVersion = process.argv[5];
|
||||
const util = require(path.join(sourcePath, "build/lib/util"));
|
||||
|
||||
function computeChecksum(filename) {
|
||||
return crypto.createHash("md5").update(fs.readFileSync(filename))
|
||||
.digest("base64").replace(/=+$/, "");
|
||||
}
|
||||
|
||||
const computeChecksums = (filenames) => {
|
||||
const result = {};
|
||||
filenames.forEach(function (filename) {
|
||||
result[filename] = computeChecksum(path.join(buildPath, "out", filename));
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
const mergeAndWrite = (name, json = {}) => {
|
||||
const aJson = JSON.parse(fs.readFileSync(path.join(sourcePath, `${name}.json`)));
|
||||
const bJson = JSON.parse(fs.readFileSync(path.join(rootPath, "scripts", `${name}.json`)));
|
||||
|
||||
delete aJson.scripts;
|
||||
delete aJson.dependencies;
|
||||
delete aJson.devDependencies;
|
||||
delete aJson.optionalDependencies;
|
||||
|
||||
fs.writeFileSync(path.join(buildPath, `${name}.json`), JSON.stringify({
|
||||
...aJson,
|
||||
...bJson,
|
||||
...json,
|
||||
}, null, 2));
|
||||
};
|
||||
|
||||
|
||||
const writeProduct = () => {
|
||||
const checksums = computeChecksums([
|
||||
"vs/workbench/workbench.web.api.js",
|
||||
"vs/workbench/workbench.web.api.css",
|
||||
"vs/code/browser/workbench/workbench.html",
|
||||
"vs/code/browser/workbench/workbench.js",
|
||||
"vs/server/src/cli.js",
|
||||
"vs/server/src/uriTransformer.js",
|
||||
"vs/server/src/login/index.html"
|
||||
]);
|
||||
const date = new Date().toISOString();
|
||||
const commit = util.getVersion(rootPath);
|
||||
mergeAndWrite("product", { commit, date, checksums });
|
||||
mergeAndWrite("package", { codeServerVersion: `${codeServerVersion}-vsc${vscodeVersion}` });
|
||||
};
|
||||
|
||||
writeProduct();
|
||||
@@ -1,84 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Build using a Docker container.
|
||||
function docker-build() {
|
||||
local target="${TARGET:-}"
|
||||
local image="codercom/nbin-${target}"
|
||||
local token="${GITHUB_TOKEN:-}"
|
||||
local minify="${MINIFY:-}"
|
||||
if [[ "${target}" == "linux" ]] ; then
|
||||
image="codercom/nbin-centos"
|
||||
fi
|
||||
|
||||
local containerId
|
||||
containerId=$(docker create --network=host --rm -it -v "$(pwd)"/.cache:/src/.cache "${image}")
|
||||
docker start "${containerId}"
|
||||
docker exec "${containerId}" mkdir -p /src
|
||||
|
||||
# TODO: temporary as long as we are rebuilding modules.
|
||||
if [[ "${image}" == "codercom/nbin-alpine" ]] ; then
|
||||
docker exec "${containerId}" apk add libxkbfile-dev libsecret-dev
|
||||
else
|
||||
# TODO: at some point git existed but it seems to have disappeared.
|
||||
docker exec "${containerId}" yum install -y libxkbfile-devel libsecret-devel git
|
||||
fi
|
||||
|
||||
function docker-exec() {
|
||||
local command="${1}" ; shift
|
||||
local args="'${vscodeVersion}' '${codeServerVersion}'"
|
||||
docker exec "${containerId}" \
|
||||
bash -c "cd /src && CI=true GITHUB_TOKEN=${token} MINIFY=${minify} yarn ${command} ${args}"
|
||||
}
|
||||
|
||||
docker cp ./. "${containerId}":/src
|
||||
docker-exec build
|
||||
if [[ -n "${package}" ]] ; then
|
||||
docker-exec binary
|
||||
docker-exec package
|
||||
mkdir -p release
|
||||
docker cp "${containerId}":/src/release/. ./release/
|
||||
fi
|
||||
|
||||
docker stop "${containerId}"
|
||||
}
|
||||
|
||||
# Build locally.
|
||||
function local-build() {
|
||||
function local-exec() {
|
||||
local command="${1}" ; shift
|
||||
CI=true yarn "${command}" "${vscodeVersion}" "${codeServerVersion}"
|
||||
}
|
||||
|
||||
local-exec build
|
||||
if [[ -n "${package}" ]] ; then
|
||||
local-exec binary
|
||||
local-exec package
|
||||
fi
|
||||
}
|
||||
|
||||
# Build code-server in the CI.
|
||||
function main() {
|
||||
cd "$(dirname "${0}")/.."
|
||||
|
||||
local codeServerVersion="${VERSION:-}"
|
||||
local vscodeVersion="${VSCODE_VERSION:-}"
|
||||
local ostype="${OSTYPE:-}"
|
||||
local package="${PACKAGE:-}"
|
||||
|
||||
if [[ -z "${codeServerVersion}" ]] ; then
|
||||
>&2 echo "Must set VERSION environment variable"; exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${vscodeVersion}" ]] ; then
|
||||
>&2 echo "Must set VSCODE_VERSION environment variable"; exit 1
|
||||
fi
|
||||
|
||||
if [[ "${ostype}" == "darwin"* ]]; then
|
||||
local-build
|
||||
else
|
||||
docker-build
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -1,16 +0,0 @@
|
||||
// This file is prepended to loader/entry code (like our main.js or VS Code's
|
||||
// bootstrap-fork.js). {{ROOT_PATH}} is replaced during the build process.
|
||||
if (!global.NBIN_LOADED) {
|
||||
try {
|
||||
const nbin = require("nbin");
|
||||
nbin.shimNativeFs("{{ROOT_PATH}}");
|
||||
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. */ }
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
const { Binary } = require("@coder/nbin");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const source = process.argv[2];
|
||||
const target = process.argv[3];
|
||||
const binaryName = process.argv[4];
|
||||
|
||||
const bin = new Binary({
|
||||
mainFile: path.join(source, "out/vs/server/main.js"),
|
||||
target: target,
|
||||
});
|
||||
|
||||
bin.writeFiles(path.join(source, "**"));
|
||||
|
||||
bin.build().then((binaryData) => {
|
||||
const outputPath = path.join(source, binaryName);
|
||||
fs.writeFileSync(outputPath, binaryData);
|
||||
fs.chmodSync(outputPath, "755");
|
||||
}).catch((ex) => {
|
||||
console.error(ex);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "code-server",
|
||||
"main": "out/vs/server/main",
|
||||
"desktopName": "code-server-url-handler.desktop"
|
||||
}
|
||||
@@ -1,19 +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"
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euox pipefail
|
||||
|
||||
function log() {
|
||||
local message="${1}" ; shift
|
||||
local level="${1:-info}"
|
||||
if [[ "${level}" == "error" ]] ; then
|
||||
>&2 echo "${message}"
|
||||
else
|
||||
echo "${message}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Copy code-server into VS Code along with its dependencies.
|
||||
function copy-server() {
|
||||
local serverPath="${sourcePath}/src/vs/server"
|
||||
rm -rf "${serverPath}"
|
||||
mkdir -p "${serverPath}"
|
||||
|
||||
cp -r "${rootPath}/src" "${serverPath}"
|
||||
cp -r "${rootPath}/typings" "${serverPath}"
|
||||
cp "${rootPath}/main.js" "${serverPath}"
|
||||
cp "${rootPath}/package.json" "${serverPath}"
|
||||
cp "${rootPath}/yarn.lock" "${serverPath}"
|
||||
|
||||
if [[ -d "${rootPath}/node_modules" ]] ; then
|
||||
cp -r "${rootPath}/node_modules" "${serverPath}"
|
||||
else
|
||||
# Ignore scripts to avoid also installing VS Code dependencies which has
|
||||
# already been done.
|
||||
cd "${serverPath}" && yarn --ignore-scripts
|
||||
rm -r node_modules/@types/node # I keep getting type conflicts
|
||||
fi
|
||||
|
||||
# TODO: Duplicate identifier issue. There must be a better way to fix this.
|
||||
if [[ "${target}" == "darwin" ]] ; then
|
||||
rm "${serverPath}/node_modules/fsevents/node_modules/safe-buffer/index.d.ts"
|
||||
fi
|
||||
}
|
||||
|
||||
# Prepend the nbin shim which enables finding files within the binary.
|
||||
function prepend-loader() {
|
||||
local filePath="${buildPath}/${1}" ; shift
|
||||
cat "${rootPath}/scripts/nbin-shim.js" "${filePath}" > "${filePath}.temp"
|
||||
mv "${filePath}.temp" "${filePath}"
|
||||
# Using : as the delimiter so the escaping here is easier to read.
|
||||
# ${parameter/pattern/string}, so the pattern is /: (if the pattern starts
|
||||
# with / it matches all instances) and the string is \\: (results in \:).
|
||||
if [[ "${target}" == "darwin" ]] ; then
|
||||
sed -i "" -e "s:{{ROOT_PATH}}:${buildPath//:/\\:}:g" "${filePath}"
|
||||
else
|
||||
sed -i "s:{{ROOT_PATH}}:${buildPath//:/\\:}:g" "${filePath}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Copy code-server into VS Code then build it.
|
||||
function build-code-server() {
|
||||
copy-server
|
||||
cd "${sourcePath}" && yarn gulp compile-build --max-old-space-size=32384
|
||||
|
||||
local min=""
|
||||
if [[ -n "${minify}" ]] ; then
|
||||
min="-min"
|
||||
yarn gulp minify-vscode --max-old-space-size=32384
|
||||
else
|
||||
yarn gulp optimize-vscode --max-old-space-size=32384
|
||||
fi
|
||||
|
||||
rm -rf "${buildPath}"
|
||||
mkdir -p "${buildPath}"
|
||||
|
||||
# Rebuild to make sure native modules work on the target system.
|
||||
cp "${sourcePath}/remote/"{package.json,yarn.lock,.yarnrc} "${buildPath}"
|
||||
cd "${buildPath}" && yarn --production --force --build-from-source
|
||||
rm "${buildPath}/"{package.json,yarn.lock,.yarnrc}
|
||||
|
||||
cp -r "${sourcePath}/.build/extensions" "${buildPath}"
|
||||
cp -r "${sourcePath}/out-vscode${min}" "${buildPath}/out"
|
||||
node "${rootPath}/scripts/build-json.js" "${sourcePath}" "${buildPath}" "${vscodeVersion}" "${codeServerVersion}"
|
||||
|
||||
# Only keep production dependencies for the server.
|
||||
cp "${rootPath}/"{package.json,yarn.lock} "${buildPath}/out/vs/server"
|
||||
cd "${buildPath}/out/vs/server" && yarn --production --ignore-scripts
|
||||
rm "${buildPath}/out/vs/server/"{package.json,yarn.lock}
|
||||
|
||||
# onigasm 2.2.2 has a bug that makes it broken for PHP files so use 2.2.1.
|
||||
# https://github.com/NeekSandhu/onigasm/issues/17
|
||||
local onigasmPath="${buildPath}/node_modules/onigasm-umd"
|
||||
rm -rf "${onigasmPath}"
|
||||
git clone "https://github.com/alexandrudima/onigasm-umd" "${onigasmPath}"
|
||||
cd "${onigasmPath}" && yarn && yarn add --dev onigasm@2.2.1 && yarn package
|
||||
mkdir "${onigasmPath}-temp"
|
||||
mv "${onigasmPath}/"{release,LICENSE} "${onigasmPath}-temp"
|
||||
rm -rf "${onigasmPath}"
|
||||
mv "${onigasmPath}-temp" "${onigasmPath}"
|
||||
|
||||
prepend-loader "out/vs/server/main.js"
|
||||
prepend-loader "out/bootstrap-fork.js"
|
||||
prepend-loader "extensions/node_modules/typescript/lib/tsserver.js"
|
||||
|
||||
log "Final build: ${buildPath}"
|
||||
}
|
||||
|
||||
# Download and extract a tar from a URL with either curl or wget depending on
|
||||
# which is available.
|
||||
function download-tar() {
|
||||
local url="${1}" ; shift
|
||||
if command -v wget &> /dev/null ; then
|
||||
wget "${url}" --quiet -O - | tar -C "${stagingPath}" -xz
|
||||
else
|
||||
curl "${url}" --silent --fail | tar -C "${stagingPath}" -xz
|
||||
fi
|
||||
}
|
||||
|
||||
# Download a pre-built package. If it doesn't exist and we are in the CI, exit.
|
||||
# Otherwise the return will be whether it existed or not. The pre-built package
|
||||
# is provided to reduce CI build time.
|
||||
function download-pre-built() {
|
||||
local archiveName="${1}" ; shift
|
||||
local url="https://codesrv-ci.cdr.sh/${archiveName}"
|
||||
if ! download-tar "${url}" ; then
|
||||
if [[ -n "${ci}" ]] ; then
|
||||
log "${url} does not exist" "error"
|
||||
exit 1
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Fully build code-server.
|
||||
function build-task() {
|
||||
mkdir -p "${stagingPath}"
|
||||
if [[ ! -d "${sourcePath}" ]] ; then
|
||||
if ! download-pre-built "vscode-${vscodeVersion}.tar.gz" ; then
|
||||
git clone https://github.com/microsoft/vscode --quiet \
|
||||
--branch "${vscodeVersion}" --single-branch --depth=1 \
|
||||
"${sourcePath}"
|
||||
fi
|
||||
fi
|
||||
cd "${sourcePath}"
|
||||
git reset --hard && git clean -fd
|
||||
git apply "${rootPath}/scripts/vscode.patch"
|
||||
if [[ ! -d "${sourcePath}/node_modules" ]] ; then
|
||||
if [[ -n "${ci}" ]] ; then
|
||||
log "Pre-built VS Code ${vscodeVersion} has no node_modules" "error"
|
||||
exit 1
|
||||
fi
|
||||
yarn
|
||||
fi
|
||||
if [[ ! -d "${sourcePath}/.build/extensions" ]] ; then
|
||||
if [[ -n "${ci}" ]] ; then
|
||||
log "Pre-built VS Code ${vscodeVersion} has no built extensions" "error"
|
||||
exit 1
|
||||
fi
|
||||
yarn gulp compile-extensions-build --max-old-space-size=32384
|
||||
fi
|
||||
build-code-server
|
||||
}
|
||||
|
||||
# Package the binary into a tar or zip for release.
|
||||
function package-task() {
|
||||
local archivePath="${releasePath}/${binaryName}"
|
||||
rm -rf "${archivePath}"
|
||||
mkdir -p "${archivePath}"
|
||||
|
||||
cp "${buildPath}/${binaryName}" "${archivePath}/code-server"
|
||||
cp "${rootPath}/README.md" "${archivePath}"
|
||||
cp "${sourcePath}/LICENSE.txt" "${archivePath}"
|
||||
cp "${sourcePath}/ThirdPartyNotices.txt" "${archivePath}"
|
||||
|
||||
cd "${releasePath}"
|
||||
if [[ "${target}" == "darwin" ]] ; then
|
||||
zip -r "${binaryName}.zip" "${binaryName}"
|
||||
log "Archive: ${archivePath}.zip"
|
||||
else
|
||||
tar -czf "${binaryName}.tar.gz" "${binaryName}"
|
||||
log "Archive: ${archivePath}.tar.gz"
|
||||
fi
|
||||
}
|
||||
|
||||
# Bundle built code into a binary.
|
||||
function binary-task() {
|
||||
cd "${rootPath}"
|
||||
node "${rootPath}/scripts/nbin.js" "${buildPath}" "${target}" "${binaryName}"
|
||||
log "Binary: ${buildPath}/${binaryName}"
|
||||
}
|
||||
|
||||
# Check if it looks like we are inside VS Code.
|
||||
function in-vscode () {
|
||||
local dir="${1}" ; shift
|
||||
local maybeVsCode
|
||||
local dirName
|
||||
maybeVsCode="$(cd "${dir}/../../.." ; pwd -P)"
|
||||
dirName="$(basename "${maybeVsCode}")"
|
||||
if [[ "${dirName}" != "vscode" ]] ; then
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -f "${maybeVsCode}/package.json" ]] ; then
|
||||
return 1
|
||||
fi
|
||||
if ! grep '"name": "code-oss-dev"' "${maybeVsCode}/package.json" -q ; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function main() {
|
||||
local rootPath
|
||||
rootPath="$(cd "$(dirname "${0}")/.." ; pwd -P)"
|
||||
|
||||
local task="${1}" ; shift
|
||||
if [[ "${task}" == "ensure-in-vscode" ]] ; then
|
||||
if ! in-vscode "${rootPath}"; then
|
||||
log "Not in VS Code" "error"
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# This lets you build in a separate directory since building within this
|
||||
# directory while developing makes it hard to keep developing since compiling
|
||||
# will compile everything in the build directory as well.
|
||||
local outPath="${OUT:-${rootPath}}"
|
||||
local releasePath="${outPath}/release"
|
||||
local stagingPath="${outPath}/build"
|
||||
|
||||
# If we're inside a VS Code directory, assume we want to develop. In that case
|
||||
# we should set an OUT directory and not build in this directory.
|
||||
if in-vscode "${outPath}" ; then
|
||||
log "Set the OUT environment variable to something outside of VS Code" "error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local vscodeVersion="${1}" ; shift
|
||||
local sourceName="vscode-${vscodeVersion}-source"
|
||||
local sourcePath="${stagingPath}/${sourceName}"
|
||||
|
||||
if [[ "${task}" == "package-prebuilt" ]] ; then
|
||||
local archiveName="vscode-${vscodeVersion}.tar.gz"
|
||||
cd "${sourcePath}"
|
||||
git reset --hard && git clean -xfd -e '.build/extensions' -e 'node_modules'
|
||||
cd "${stagingPath}"
|
||||
tar -czf "${archiveName}" "${sourceName}"
|
||||
mkdir -p "${releasePath}" && mv -f "${archiveName}" "${releasePath}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local codeServerVersion="${1}" ; shift
|
||||
local ci="${CI:-}"
|
||||
local minify="${MINIFY:-}"
|
||||
|
||||
local arch
|
||||
arch=$(uname -m)
|
||||
|
||||
local target="linux"
|
||||
local ostype="${OSTYPE:-}"
|
||||
if [[ "${ostype}" == "darwin"* ]] ; then
|
||||
target="darwin"
|
||||
else
|
||||
# On Alpine there seems no way to get the version except to use an invalid
|
||||
# command which will output the version to stderr and exit with 1.
|
||||
local output
|
||||
output=$(ldd --version 2>&1 || :)
|
||||
if [[ "${output}" == "musl"* ]] ; then
|
||||
target="alpine"
|
||||
fi
|
||||
fi
|
||||
|
||||
local binaryName="code-server${codeServerVersion}-vsc${vscodeVersion}-${target}-${arch}"
|
||||
local buildPath="${stagingPath}/${binaryName}-built"
|
||||
|
||||
"${task}-task" "$@"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
1074
scripts/vscode.patch
369
src/api.ts
@@ -1,369 +0,0 @@
|
||||
import * as vscode from "vscode";
|
||||
import { CoderApi, VSCodeApi } from "../typings/api";
|
||||
import { createCSSRule } from "vs/base/browser/dom";
|
||||
import { Emitter, Event } from "vs/base/common/event";
|
||||
import { IDisposable } from "vs/base/common/lifecycle";
|
||||
import { URI } from "vs/base/common/uri";
|
||||
import { generateUuid } from "vs/base/common/uuid";
|
||||
import { localize } from "vs/nls";
|
||||
import { SyncActionDescriptor } from "vs/platform/actions/common/actions";
|
||||
import { CommandsRegistry, ICommandService } from "vs/platform/commands/common/commands";
|
||||
import { IConfigurationService } from "vs/platform/configuration/common/configuration";
|
||||
import { IContextMenuService } from "vs/platform/contextview/browser/contextView";
|
||||
import { FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions } from "vs/platform/files/common/files";
|
||||
import { IInstantiationService, ServiceIdentifier } from "vs/platform/instantiation/common/instantiation";
|
||||
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
|
||||
import { INotificationService } from "vs/platform/notification/common/notification";
|
||||
import { Registry } from "vs/platform/registry/common/platform";
|
||||
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from "vs/platform/statusbar/common/statusbar";
|
||||
import { IStorageService } from "vs/platform/storage/common/storage";
|
||||
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
|
||||
import { IThemeService } from "vs/platform/theme/common/themeService";
|
||||
import { IWorkspaceContextService } from "vs/platform/workspace/common/workspace";
|
||||
import * as extHostTypes from "vs/workbench/api/common/extHostTypes";
|
||||
import { CustomTreeView, CustomTreeViewPanel } from "vs/workbench/browser/parts/views/customView";
|
||||
import { ViewContainerViewlet } from "vs/workbench/browser/parts/views/viewsViewlet";
|
||||
import { Extensions as ViewletExtensions, ShowViewletAction, ViewletDescriptor, ViewletRegistry } from "vs/workbench/browser/viewlet";
|
||||
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from "vs/workbench/common/actions";
|
||||
import { Extensions as ViewsExtensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewContainersRegistry, IViewsRegistry, TreeItemCollapsibleState } from "vs/workbench/common/views";
|
||||
import { IEditorGroupsService } from "vs/workbench/services/editor/common/editorGroupsService";
|
||||
import { IEditorService } from "vs/workbench/services/editor/common/editorService";
|
||||
import { IExtensionService } from "vs/workbench/services/extensions/common/extensions";
|
||||
import { IWorkbenchLayoutService } from "vs/workbench/services/layout/browser/layoutService";
|
||||
import { IViewletService } from "vs/workbench/services/viewlet/browser/viewlet";
|
||||
|
||||
/**
|
||||
* Client-side implementation of VS Code's API.
|
||||
* TODO: Views aren't quite working.
|
||||
* TODO: Implement menu items for views (for item actions).
|
||||
* TODO: File system provider doesn't work.
|
||||
*/
|
||||
export const vscodeApi = (serviceCollection: ServiceCollection): VSCodeApi => {
|
||||
const getService = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T;
|
||||
const commandService = getService(ICommandService);
|
||||
const notificationService = getService(INotificationService);
|
||||
const fileService = getService(IFileService);
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewsExtensions.ViewsRegistry);
|
||||
const statusbarService = getService(IStatusbarService);
|
||||
|
||||
// It would be nice to just export what VS Code creates but it looks to me
|
||||
// that it assumes it's running in the extension host and wouldn't work here.
|
||||
// It is probably possible to create an extension host that runs in the
|
||||
// browser's main thread, but I'm not sure how much jank that would require.
|
||||
// We could have a web worker host but we want DOM access.
|
||||
return {
|
||||
EventEmitter: <any>Emitter, // It can take T so T | undefined should work.
|
||||
FileSystemError: extHostTypes.FileSystemError,
|
||||
FileType,
|
||||
StatusBarAlignment: extHostTypes.StatusBarAlignment,
|
||||
ThemeColor: extHostTypes.ThemeColor,
|
||||
TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState,
|
||||
Uri: URI,
|
||||
commands: {
|
||||
executeCommand: <T = any>(commandId: string, ...args: any[]): Promise<T | undefined> => {
|
||||
return commandService.executeCommand(commandId, ...args);
|
||||
},
|
||||
registerCommand: (id: string, command: (...args: any[]) => any): IDisposable => {
|
||||
return CommandsRegistry.registerCommand(id, command);
|
||||
},
|
||||
},
|
||||
window: {
|
||||
createStatusBarItem(alignmentOrOptions?: extHostTypes.StatusBarAlignment | vscode.window.StatusBarItemOptions, priority?: number): StatusBarEntry {
|
||||
return new StatusBarEntry(statusbarService, alignmentOrOptions, priority);
|
||||
},
|
||||
registerTreeDataProvider: <T>(id: string, dataProvider: vscode.TreeDataProvider<T>): IDisposable => {
|
||||
const tree = new TreeViewDataProvider(dataProvider);
|
||||
const view = viewsRegistry.getView(id);
|
||||
(view as ITreeViewDescriptor).treeView.dataProvider = tree;
|
||||
return {
|
||||
dispose: () => tree.dispose(),
|
||||
};
|
||||
},
|
||||
showErrorMessage: async (message: string): Promise<string | undefined> => {
|
||||
notificationService.error(message);
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
workspace: {
|
||||
registerFileSystemProvider: (scheme: string, provider: vscode.FileSystemProvider): IDisposable => {
|
||||
return fileService.registerProvider(scheme, new FileSystemProvider(provider));
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Coder API. This should only provide functionality that can't be made
|
||||
* available through the VS Code API.
|
||||
*/
|
||||
export const coderApi = (serviceCollection: ServiceCollection): CoderApi => {
|
||||
const getService = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T;
|
||||
return {
|
||||
registerView: (viewId, viewName, containerId, containerName, icon): void => {
|
||||
const cssClass = `extensionViewlet-${containerId}`;
|
||||
const id = `workbench.view.extension.${containerId}`;
|
||||
class CustomViewlet extends ViewContainerViewlet {
|
||||
public constructor(
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IEditorService _editorService: IEditorService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
) {
|
||||
super(id, `${id}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(
|
||||
new ViewletDescriptor(CustomViewlet as any, id, containerName, cssClass, undefined, URI.parse(icon)),
|
||||
);
|
||||
|
||||
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registerWorkbenchAction(
|
||||
new SyncActionDescriptor(OpenCustomViewletAction as any, id, localize("showViewlet", "Show {0}", containerName)),
|
||||
"View: Show {0}",
|
||||
localize("view", "View"),
|
||||
);
|
||||
|
||||
// Generate CSS to show the icon in the activity bar.
|
||||
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`;
|
||||
createCSSRule(iconClass, `-webkit-mask: url('${icon}') no-repeat 50% 50%`);
|
||||
|
||||
const container = Registry.as<IViewContainersRegistry>(ViewsExtensions.ViewContainersRegistry).registerViewContainer(containerId);
|
||||
Registry.as<IViewsRegistry>(ViewsExtensions.ViewsRegistry).registerViews([{
|
||||
id: viewId,
|
||||
name: viewName,
|
||||
ctorDescriptor: { ctor: CustomTreeViewPanel },
|
||||
treeView: getService(IInstantiationService).createInstance(CustomTreeView as any, viewId, container),
|
||||
}] as ITreeViewDescriptor[], container);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
class OpenCustomViewletAction extends ShowViewletAction {
|
||||
public constructor(
|
||||
id: string, label: string,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
) {
|
||||
super(id, label, id, viewletService, editorGroupService, layoutService);
|
||||
}
|
||||
}
|
||||
|
||||
class FileSystemProvider implements IFileSystemProvider {
|
||||
private readonly _onDidChange = new Emitter<IFileChange[]>();
|
||||
|
||||
public readonly onDidChangeFile: Event<IFileChange[]> = this._onDidChange.event;
|
||||
|
||||
public readonly capabilities: FileSystemProviderCapabilities;
|
||||
public readonly onDidChangeCapabilities: Event<void> = Event.None;
|
||||
|
||||
public constructor(private readonly provider: vscode.FileSystemProvider) {
|
||||
this.capabilities = FileSystemProviderCapabilities.Readonly;
|
||||
}
|
||||
|
||||
public watch(resource: URI, opts: IWatchOptions): IDisposable {
|
||||
return this.provider.watch(resource, opts);
|
||||
}
|
||||
|
||||
public async stat(resource: URI): Promise<IStat> {
|
||||
return this.provider.stat(resource);
|
||||
}
|
||||
|
||||
public async readFile(resource: URI): Promise<Uint8Array> {
|
||||
return this.provider.readFile(resource);
|
||||
}
|
||||
|
||||
public async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
|
||||
return this.provider.writeFile(resource, content, opts);
|
||||
}
|
||||
|
||||
public async delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
|
||||
return this.provider.delete(resource, opts);
|
||||
}
|
||||
|
||||
public mkdir(_resource: URI): Promise<void> {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public async readdir(resource: URI): Promise<[string, FileType][]> {
|
||||
return this.provider.readDirectory(resource);
|
||||
}
|
||||
|
||||
public async rename(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
|
||||
return this.provider.rename(resource, target, opts);
|
||||
}
|
||||
|
||||
public async copy(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
|
||||
return this.provider.copy!(resource, target, opts);
|
||||
}
|
||||
|
||||
public open(_resource: URI, _opts: FileOpenOptions): Promise<number> {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public close(_fd: number): Promise<void> {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public read(_fd: number, _pos: number, _data: Uint8Array, _offset: number, _length: number): Promise<number> {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public write(_fd: number, _pos: number, _data: Uint8Array, _offset: number, _length: number): Promise<number> {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
class TreeViewDataProvider<T> implements ITreeViewDataProvider {
|
||||
private readonly root = Symbol("root");
|
||||
private readonly values = new Map<string, T>();
|
||||
private readonly children = new Map<T | Symbol, ITreeItem[]>();
|
||||
|
||||
public constructor(private readonly provider: vscode.TreeDataProvider<T>) {}
|
||||
|
||||
public async getChildren(item?: ITreeItem): Promise<ITreeItem[]> {
|
||||
const value = item && this.itemToValue(item);
|
||||
const children = await Promise.all(
|
||||
(await this.provider.getChildren(value) || [])
|
||||
.map(async (childValue) => {
|
||||
const treeItem = await this.provider.getTreeItem(childValue);
|
||||
const handle = this.createHandle(treeItem);
|
||||
this.values.set(handle, childValue);
|
||||
return {
|
||||
handle,
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.clear(value || this.root, item);
|
||||
this.children.set(value || this.root, children);
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
private itemToValue(item: ITreeItem): T {
|
||||
if (!this.values.has(item.handle)) {
|
||||
throw new Error(`No element found with handle ${item.handle}`);
|
||||
}
|
||||
return this.values.get(item.handle)!;
|
||||
}
|
||||
|
||||
private clear(value: T | Symbol, item?: ITreeItem): void {
|
||||
if (this.children.has(value)) {
|
||||
this.children.get(value)!.map((c) => this.clear(this.itemToValue(c), c));
|
||||
this.children.delete(value);
|
||||
}
|
||||
if (item) {
|
||||
this.values.delete(item.handle);
|
||||
}
|
||||
}
|
||||
|
||||
private createHandle(item: vscode.TreeItem): string {
|
||||
return item.id
|
||||
? `coder-tree-item-id/${item.id}`
|
||||
: `coder-tree-item-uuid/${generateUuid()}`;
|
||||
}
|
||||
}
|
||||
|
||||
interface IStatusBarEntry extends IStatusbarEntry {
|
||||
alignment: StatusbarAlignment;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
class StatusBarEntry implements vscode.StatusBarItem {
|
||||
private static ID = 0;
|
||||
|
||||
private _id: number;
|
||||
private entry: IStatusBarEntry;
|
||||
private visible: boolean;
|
||||
private disposed: boolean;
|
||||
private statusId: string;
|
||||
private statusName: string;
|
||||
private accessor?: IStatusbarEntryAccessor;
|
||||
private timeout: any;
|
||||
|
||||
constructor(private readonly statusbarService: IStatusbarService, alignmentOrOptions?: extHostTypes.StatusBarAlignment | vscode.window.StatusBarItemOptions, priority?: number) {
|
||||
this._id = StatusBarEntry.ID--;
|
||||
if (alignmentOrOptions && typeof alignmentOrOptions !== "number") {
|
||||
this.statusId = alignmentOrOptions.id;
|
||||
this.statusName = alignmentOrOptions.name;
|
||||
this.entry = {
|
||||
alignment: alignmentOrOptions.alignment === extHostTypes.StatusBarAlignment.Right
|
||||
? StatusbarAlignment.RIGHT : StatusbarAlignment.LEFT,
|
||||
priority,
|
||||
text: "",
|
||||
};
|
||||
} else {
|
||||
this.statusId = "web-api";
|
||||
this.statusName = "Web API";
|
||||
this.entry = {
|
||||
alignment: alignmentOrOptions === extHostTypes.StatusBarAlignment.Right
|
||||
? StatusbarAlignment.RIGHT : StatusbarAlignment.LEFT,
|
||||
priority,
|
||||
text: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public get alignment(): extHostTypes.StatusBarAlignment {
|
||||
return this.entry.alignment === StatusbarAlignment.RIGHT
|
||||
? extHostTypes.StatusBarAlignment.Right : extHostTypes.StatusBarAlignment.Left;
|
||||
}
|
||||
|
||||
public get id(): number { return this._id; }
|
||||
public get priority(): number | undefined { return this.entry.priority; }
|
||||
public get text(): string { return this.entry.text; }
|
||||
public get tooltip(): string | undefined { return this.entry.tooltip; }
|
||||
public get color(): string | extHostTypes.ThemeColor | undefined { return this.entry.color; }
|
||||
public get command(): string | undefined { return this.entry.command; }
|
||||
|
||||
public set text(text: string) { this.update({ text }); }
|
||||
public set tooltip(tooltip: string | undefined) { this.update({ tooltip }); }
|
||||
public set color(color: string | extHostTypes.ThemeColor | undefined) { this.update({ color }); }
|
||||
public set command(command: string | undefined) { this.update({ command }); }
|
||||
|
||||
public show(): void {
|
||||
this.visible = true;
|
||||
this.update();
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
clearTimeout(this.timeout);
|
||||
this.visible = false;
|
||||
if (this.accessor) {
|
||||
this.accessor.dispose();
|
||||
this.accessor = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private update(values?: Partial<IStatusBarEntry>): void {
|
||||
this.entry = { ...this.entry, ...values };
|
||||
if (this.disposed || !this.visible) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(() => {
|
||||
if (!this.accessor) {
|
||||
this.accessor = this.statusbarService.addEntry(this.entry, this.statusId, this.statusName, this.entry.alignment, this.priority);
|
||||
} else {
|
||||
this.accessor.update(this.entry);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.hide();
|
||||
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 |
28
src/browser/pages/app.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!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' 'unsafe-inline'; 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/pwa-icon-384.png" />
|
||||
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
|
||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||
</head>
|
||||
<body>
|
||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
|
||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
37
src/browser/pages/app.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { getOptions, normalize } from "../../common/util"
|
||||
import { ApiEndpoint } from "../../common/http"
|
||||
|
||||
import "./error.css"
|
||||
import "./global.css"
|
||||
import "./home.css"
|
||||
import "./login.css"
|
||||
import "./update.css"
|
||||
|
||||
const options = getOptions()
|
||||
|
||||
const isInput = (el: Element): el is HTMLInputElement => {
|
||||
return !!(el as HTMLInputElement).name
|
||||
}
|
||||
|
||||
document.querySelectorAll("form").forEach((form) => {
|
||||
if (!form.classList.contains("-x11")) {
|
||||
return
|
||||
}
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault()
|
||||
const values: { [key: string]: string } = {}
|
||||
Array.from(form.elements).forEach((element) => {
|
||||
if (isInput(element)) {
|
||||
values[element.name] = element.value
|
||||
}
|
||||
})
|
||||
fetch(normalize(`${options.base}/api/${ApiEndpoint.process}`), {
|
||||
method: "POST",
|
||||
body: JSON.stringify(values),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// TEMP: Until we can get the real ready event.
|
||||
const event = new CustomEvent("ide-ready")
|
||||
window.dispatchEvent(event)
|
||||
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/pwa-icon-384.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 data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
85
src/browser/pages/global.css
Normal file
@@ -0,0 +1,85 @@
|
||||
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 > .content > .none {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.card-box + .card-box {
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
canvas {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
51
src/browser/pages/home.css
Normal file
@@ -0,0 +1,51 @@
|
||||
.block-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.block-row > .item {
|
||||
flex: 1;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.block-row > button.item {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.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);
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.kill-form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.kill-form > .kill {
|
||||
border-radius: 3px;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
59
src/browser/pages/home.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!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' 'unsafe-inline'; 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/pwa-icon-384.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">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 data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
|
||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/pages/app.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/pwa-icon-384.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 data-cfasync="false" 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/pwa-icon-384.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 data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
110
src/browser/pages/vscode.html
Normal file
@@ -0,0 +1,110 @@
|
||||
<!-- 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-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/pwa-icon-384.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 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-unicode11": `${staticBase}/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.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 data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
|
||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/loader.js"></script>
|
||||
<!-- PROD_ONLY
|
||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/workbench/workbench.web.api.nls.js"></script>
|
||||
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/workbench/workbench.web.api.js"></script>
|
||||
END_PROD_ONLY -->
|
||||
<script>
|
||||
require(["vs/code/browser/workbench/workbench"], function() {})
|
||||
</script>
|
||||
<script>
|
||||
try {
|
||||
document.body.style.background = JSON.parse(localStorage.getItem("colorThemeData")).colorMap["editor.background"]
|
||||
} catch (error) {
|
||||
// Oh well.
|
||||
}
|
||||
</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",
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
204
src/browser/socket.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
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 path: 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...", field("path", this.path))
|
||||
const socket = new WebSocket(this.path)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
276
src/channel.ts
@@ -1,276 +0,0 @@
|
||||
import * as path from "path";
|
||||
import { VSBuffer } from "vs/base/common/buffer";
|
||||
import { Emitter, Event } from "vs/base/common/event";
|
||||
import { IDisposable } from "vs/base/common/lifecycle";
|
||||
import { OS } from "vs/base/common/platform";
|
||||
import { URI, UriComponents } from "vs/base/common/uri";
|
||||
import { transformOutgoingURIs } from "vs/base/common/uriIpc";
|
||||
import { IServerChannel } from "vs/base/parts/ipc/common/ipc";
|
||||
import { IDiagnosticInfo } from "vs/platform/diagnostics/common/diagnostics";
|
||||
import { IEnvironmentService } from "vs/platform/environment/common/environment";
|
||||
import { ExtensionIdentifier, IExtensionDescription } from "vs/platform/extensions/common/extensions";
|
||||
import { FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileType, IStat, IWatchOptions } from "vs/platform/files/common/files";
|
||||
import { DiskFileSystemProvider } from "vs/platform/files/node/diskFileSystemProvider";
|
||||
import { ILogService } from "vs/platform/log/common/log";
|
||||
import pkg from "vs/platform/product/node/package";
|
||||
import product from "vs/platform/product/node/product";
|
||||
import { IRemoteAgentEnvironment } from "vs/platform/remote/common/remoteAgentEnvironment";
|
||||
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
|
||||
import { getTranslations } from "vs/server/src/nls";
|
||||
import { getUriTransformer } from "vs/server/src/util";
|
||||
import { ExtensionScanner, ExtensionScannerInput } from "vs/workbench/services/extensions/node/extensionPoints";
|
||||
|
||||
/**
|
||||
* Extend the file provider to allow unwatching.
|
||||
*/
|
||||
class Watcher extends DiskFileSystemProvider {
|
||||
public readonly watches = new Map<number, IDisposable>();
|
||||
|
||||
public dispose(): void {
|
||||
this.watches.forEach((w) => w.dispose());
|
||||
this.watches.clear();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public _watch(req: number, resource: URI, opts: IWatchOptions): void {
|
||||
this.watches.set(req, this.watch(resource, opts));
|
||||
}
|
||||
|
||||
public unwatch(req: number): void {
|
||||
this.watches.get(req)!.dispose();
|
||||
this.watches.delete(req);
|
||||
}
|
||||
}
|
||||
|
||||
export class FileProviderChannel implements IServerChannel, IDisposable {
|
||||
private readonly provider: DiskFileSystemProvider;
|
||||
private readonly watchers = new Map<string, Watcher>();
|
||||
|
||||
public constructor(
|
||||
private readonly environmentService: IEnvironmentService,
|
||||
private readonly logService: ILogService,
|
||||
) {
|
||||
this.provider = new DiskFileSystemProvider(this.logService);
|
||||
}
|
||||
|
||||
public listen(context: any, event: string, args?: any): Event<any> {
|
||||
switch (event) {
|
||||
// This is where the actual file changes are sent. The watch method just
|
||||
// adds things that will fire here. That means we have to split up
|
||||
// watchers based on the session otherwise sessions would get events for
|
||||
// other sessions. There is also no point in having the watcher unless
|
||||
// something is listening. I'm not sure there is a different way to
|
||||
// dispose, anyway.
|
||||
case "filechange":
|
||||
const session = args[0];
|
||||
const emitter = new Emitter({
|
||||
onFirstListenerAdd: () => {
|
||||
const provider = new Watcher(this.logService);
|
||||
this.watchers.set(session, provider);
|
||||
const transformer = getUriTransformer(context.remoteAuthority);
|
||||
provider.onDidChangeFile((events) => {
|
||||
emitter.fire(events.map((event) => ({
|
||||
...event,
|
||||
resource: transformer.transformOutgoing(event.resource),
|
||||
})));
|
||||
});
|
||||
provider.onDidErrorOccur((event) => emitter.fire(event));
|
||||
},
|
||||
onLastListenerRemove: () => {
|
||||
this.watchers.get(session)!.dispose();
|
||||
this.watchers.delete(session);
|
||||
},
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid listen "${event}"`);
|
||||
}
|
||||
|
||||
public call(_: unknown, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case "stat": return this.stat(args[0]);
|
||||
case "open": return this.open(args[0], args[1]);
|
||||
case "close": return this.close(args[0]);
|
||||
case "read": return this.read(args[0], args[1], args[2]);
|
||||
case "write": return this.write(args[0], args[1], args[2], args[3], args[4]);
|
||||
case "delete": return this.delete(args[0], args[1]);
|
||||
case "mkdir": return this.mkdir(args[0]);
|
||||
case "readdir": return this.readdir(args[0]);
|
||||
case "rename": return this.rename(args[0], args[1], args[2]);
|
||||
case "copy": return this.copy(args[0], args[1], args[2]);
|
||||
case "watch": return this.watch(args[0], args[1], args[2], args[3]);
|
||||
case "unwatch": return this.unwatch(args[0], args[1]);
|
||||
}
|
||||
|
||||
throw new Error(`Invalid call "${command}"`);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.watchers.forEach((w) => w.dispose());
|
||||
this.watchers.clear();
|
||||
}
|
||||
|
||||
private async stat(resource: UriComponents): Promise<IStat> {
|
||||
return this.provider.stat(this.transform(resource));
|
||||
}
|
||||
|
||||
private async open(resource: UriComponents, opts: FileOpenOptions): Promise<number> {
|
||||
return this.provider.open(this.transform(resource), opts);
|
||||
}
|
||||
|
||||
private async close(fd: number): Promise<void> {
|
||||
return this.provider.close(fd);
|
||||
}
|
||||
|
||||
private async read(fd: number, pos: number, length: number): Promise<[VSBuffer, number]> {
|
||||
const buffer = VSBuffer.alloc(length);
|
||||
const bytesRead = await this.provider.read(fd, pos, buffer.buffer, 0, length);
|
||||
return [buffer, bytesRead];
|
||||
}
|
||||
|
||||
private write(fd: number, pos: number, buffer: VSBuffer, offset: number, length: number): Promise<number> {
|
||||
return this.provider.write(fd, pos, buffer.buffer, offset, length);
|
||||
}
|
||||
|
||||
private async delete(resource: UriComponents, opts: FileDeleteOptions): Promise<void> {
|
||||
return this.provider.delete(this.transform(resource), opts);
|
||||
}
|
||||
|
||||
private async mkdir(resource: UriComponents): Promise<void> {
|
||||
return this.provider.mkdir(this.transform(resource));
|
||||
}
|
||||
|
||||
private async readdir(resource: UriComponents): Promise<[string, FileType][]> {
|
||||
return this.provider.readdir(this.transform(resource));
|
||||
}
|
||||
|
||||
private async rename(resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise<void> {
|
||||
return this.provider.rename(this.transform(resource), URI.from(target), opts);
|
||||
}
|
||||
|
||||
private copy(resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise<void> {
|
||||
return this.provider.copy(this.transform(resource), URI.from(target), opts);
|
||||
}
|
||||
|
||||
private async watch(session: string, req: number, resource: UriComponents, opts: IWatchOptions): Promise<void> {
|
||||
this.watchers.get(session)!._watch(req, this.transform(resource), opts);
|
||||
}
|
||||
|
||||
private async unwatch(session: string, req: number): Promise<void> {
|
||||
this.watchers.get(session)!.unwatch(req);
|
||||
}
|
||||
|
||||
private transform(resource: UriComponents): URI {
|
||||
// Used for walkthrough content.
|
||||
if (/^\/static[^/]*\//.test(resource.path)) {
|
||||
return URI.file(this.environmentService.appRoot + resource.path.replace(/^\/static[^/]*\//, "/"));
|
||||
// Used by the webview service worker to load resources.
|
||||
} else if (resource.path === "/vscode-resource" && resource.query) {
|
||||
try {
|
||||
const query = JSON.parse(resource.query);
|
||||
if (query.requestResourcePath) {
|
||||
return URI.file(query.requestResourcePath);
|
||||
}
|
||||
} catch (error) { /* Carry on. */ }
|
||||
}
|
||||
return URI.from(resource);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionEnvironmentChannel implements IServerChannel {
|
||||
public constructor(
|
||||
private readonly environment: IEnvironmentService,
|
||||
private readonly log: ILogService,
|
||||
private readonly telemetry: ITelemetryService,
|
||||
private readonly connectionToken: string,
|
||||
) {}
|
||||
|
||||
public listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Invalid listen "${event}"`);
|
||||
}
|
||||
|
||||
public async call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case "getEnvironmentData":
|
||||
return transformOutgoingURIs(
|
||||
await this.getEnvironmentData(args.language),
|
||||
getUriTransformer(context.remoteAuthority),
|
||||
);
|
||||
case "getDiagnosticInfo": return this.getDiagnosticInfo();
|
||||
case "disableTelemetry": return this.disableTelemetry();
|
||||
}
|
||||
throw new Error(`Invalid call "${command}"`);
|
||||
}
|
||||
|
||||
private async getEnvironmentData(locale: string): Promise<IRemoteAgentEnvironment> {
|
||||
return {
|
||||
pid: process.pid,
|
||||
connectionToken: this.connectionToken,
|
||||
appRoot: URI.file(this.environment.appRoot),
|
||||
appSettingsHome: this.environment.appSettingsHome,
|
||||
settingsPath: this.environment.machineSettingsHome,
|
||||
logsPath: URI.file(this.environment.logsPath),
|
||||
extensionsPath: URI.file(this.environment.extensionsPath!),
|
||||
extensionHostLogsPath: URI.file(path.join(this.environment.logsPath, "extension-host")),
|
||||
globalStorageHome: URI.file(this.environment.globalStorageHome),
|
||||
userHome: URI.file(this.environment.userHome),
|
||||
extensions: await this.scanExtensions(locale),
|
||||
os: OS,
|
||||
};
|
||||
}
|
||||
|
||||
private async scanExtensions(locale: string): Promise<IExtensionDescription[]> {
|
||||
const translations = await getTranslations(locale, this.environment.userDataPath);
|
||||
|
||||
const scanMultiple = (isBuiltin: boolean, isUnderDevelopment: boolean, paths: string[]): Promise<IExtensionDescription[][]> => {
|
||||
return Promise.all(paths.map((path) => {
|
||||
return ExtensionScanner.scanExtensions(new ExtensionScannerInput(
|
||||
pkg.version,
|
||||
product.commit,
|
||||
locale,
|
||||
!!process.env.VSCODE_DEV,
|
||||
path,
|
||||
isBuiltin,
|
||||
isUnderDevelopment,
|
||||
translations,
|
||||
), this.log);
|
||||
}));
|
||||
};
|
||||
|
||||
const scanBuiltin = async (): Promise<IExtensionDescription[][]> => {
|
||||
return scanMultiple(true, false, [this.environment.builtinExtensionsPath, ...this.environment.extraBuiltinExtensionPaths]);
|
||||
};
|
||||
|
||||
const scanInstalled = async (): Promise<IExtensionDescription[][]> => {
|
||||
return scanMultiple(false, true, [this.environment.extensionsPath!, ...this.environment.extraExtensionPaths]);
|
||||
};
|
||||
|
||||
return Promise.all([scanBuiltin(), scanInstalled()]).then((allExtensions) => {
|
||||
const uniqueExtensions = new Map<string, IExtensionDescription>();
|
||||
allExtensions.forEach((multipleExtensions) => {
|
||||
multipleExtensions.forEach((extensions) => {
|
||||
extensions.forEach((extension) => {
|
||||
const id = ExtensionIdentifier.toKey(extension.identifier);
|
||||
if (uniqueExtensions.has(id)) {
|
||||
const oldPath = uniqueExtensions.get(id)!.extensionLocation.fsPath;
|
||||
const newPath = extension.extensionLocation.fsPath;
|
||||
this.log.warn(`${oldPath} has been overridden ${newPath}`);
|
||||
}
|
||||
uniqueExtensions.set(id, extension);
|
||||
});
|
||||
});
|
||||
});
|
||||
return Array.from(uniqueExtensions.values());
|
||||
});
|
||||
}
|
||||
|
||||
private getDiagnosticInfo(): Promise<IDiagnosticInfo> {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
private async disableTelemetry(): Promise<void> {
|
||||
this.telemetry.setEnabled(false);
|
||||
}
|
||||
}
|
||||
262
src/cli.ts
@@ -1,262 +0,0 @@
|
||||
import * as cp from "child_process";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { setUnexpectedErrorHandler } from "vs/base/common/errors";
|
||||
import { main as vsCli } from "vs/code/node/cliProcessMain";
|
||||
import { validatePaths } from "vs/code/node/paths";
|
||||
import { ParsedArgs } from "vs/platform/environment/common/environment";
|
||||
import { buildHelpMessage, buildVersionMessage, Option as VsOption, options as vsOptions } from "vs/platform/environment/node/argv";
|
||||
import { parseMainProcessArgv } from "vs/platform/environment/node/argvHelper";
|
||||
import pkg from "vs/platform/product/node/package";
|
||||
import product from "vs/platform/product/node/product";
|
||||
import { ipcMain } from "vs/server/src/ipc";
|
||||
import { enableCustomMarketplace } from "vs/server/src/marketplace";
|
||||
import { MainServer } from "vs/server/src/server";
|
||||
import { AuthType, buildAllowedMessage, enumToArray, FormatType, generateCertificate, generatePassword, localRequire, open, unpackExecutables } from "vs/server/src/util";
|
||||
|
||||
const { logger } = localRequire<typeof import("@coder/logger/out/index")>("@coder/logger/out/index");
|
||||
setUnexpectedErrorHandler((error) => logger.warn(error.message));
|
||||
|
||||
interface Args extends ParsedArgs {
|
||||
auth?: AuthType;
|
||||
"base-path"?: string;
|
||||
cert?: string;
|
||||
"cert-key"?: string;
|
||||
format?: string;
|
||||
host?: string;
|
||||
open?: string;
|
||||
port?: string;
|
||||
socket?: string;
|
||||
}
|
||||
|
||||
// @ts-ignore: Force `keyof Args` to work.
|
||||
interface Option extends VsOption {
|
||||
id: keyof Args;
|
||||
}
|
||||
|
||||
const getArgs = (): Args => {
|
||||
const options = vsOptions as Option[];
|
||||
// The last item is _ which is like -- so our options need to come before it.
|
||||
const last = options.pop()!;
|
||||
|
||||
// Remove options that won't work or don't make sense.
|
||||
let i = options.length;
|
||||
while (i--) {
|
||||
switch (options[i].id) {
|
||||
case "add":
|
||||
case "diff":
|
||||
case "file-uri":
|
||||
case "folder-uri":
|
||||
case "goto":
|
||||
case "new-window":
|
||||
case "reuse-window":
|
||||
case "wait":
|
||||
case "disable-gpu":
|
||||
// TODO: pretty sure these don't work but not 100%.
|
||||
case "max-memory":
|
||||
case "prof-startup":
|
||||
case "inspect-extensions":
|
||||
case "inspect-brk-extensions":
|
||||
options.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
options.push({ id: "base-path", type: "string", cat: "o", description: "Base path of the URL at which code-server is hosted (used for login redirects)." });
|
||||
options.push({ id: "cert", type: "string", cat: "o", description: "Path to certificate. If the path is omitted, both this and --cert-key will be generated." });
|
||||
options.push({ id: "cert-key", type: "string", cat: "o", description: "Path to the certificate's key if one was provided." });
|
||||
options.push({ id: "extra-builtin-extensions-dir", type: "string", cat: "o", description: "Path to an extra builtin extension directory." });
|
||||
options.push({ id: "extra-extensions-dir", type: "string", cat: "o", description: "Path to an extra user extension directory." });
|
||||
options.push({ id: "format", type: "string", cat: "o", description: `Format for the version. ${buildAllowedMessage(FormatType)}.` });
|
||||
options.push({ id: "host", type: "string", cat: "o", description: "Host for the server." });
|
||||
options.push({ id: "auth", type: "string", cat: "o", description: `The type of authentication to use. ${buildAllowedMessage(AuthType)}.` });
|
||||
options.push({ id: "open", type: "boolean", cat: "o", description: "Open in the browser on startup." });
|
||||
options.push({ id: "port", type: "string", cat: "o", description: "Port for the main server." });
|
||||
options.push({ id: "socket", type: "string", cat: "o", description: "Listen on a socket instead of host:port." });
|
||||
|
||||
options.push(last);
|
||||
|
||||
const args = parseMainProcessArgv(process.argv);
|
||||
if (!args["user-data-dir"]) {
|
||||
args["user-data-dir"] = path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local/share"), "code-server");
|
||||
}
|
||||
if (!args["extensions-dir"]) {
|
||||
args["extensions-dir"] = path.join(args["user-data-dir"], "extensions");
|
||||
}
|
||||
|
||||
return validatePaths(args);
|
||||
};
|
||||
|
||||
const startVscode = async (): Promise<void | void[]> => {
|
||||
const args = getArgs();
|
||||
const extra = args["_"] || [];
|
||||
const options = {
|
||||
auth: args.auth,
|
||||
basePath: args["base-path"],
|
||||
cert: args.cert,
|
||||
certKey: args["cert-key"],
|
||||
folderUri: extra.length > 1 ? extra[extra.length - 1] : undefined,
|
||||
host: args.host,
|
||||
password: process.env.PASSWORD,
|
||||
};
|
||||
|
||||
if (options.auth && enumToArray(AuthType).filter((t) => t === options.auth).length === 0) {
|
||||
throw new Error(`'${options.auth}' is not a valid authentication type.`);
|
||||
} else if (options.auth && !options.password) {
|
||||
options.password = await generatePassword();
|
||||
}
|
||||
|
||||
if (!options.certKey && typeof options.certKey !== "undefined") {
|
||||
throw new Error(`--cert-key cannot be blank`);
|
||||
} else if (options.certKey && !options.cert) {
|
||||
throw new Error(`--cert-key was provided but --cert was not`);
|
||||
} if (!options.cert && typeof options.cert !== "undefined") {
|
||||
const { cert, certKey } = await generateCertificate();
|
||||
options.cert = cert;
|
||||
options.certKey = certKey;
|
||||
}
|
||||
|
||||
enableCustomMarketplace();
|
||||
|
||||
const server = new MainServer({
|
||||
...options,
|
||||
port: typeof args.port !== "undefined" ? parseInt(args.port, 10) : 8080,
|
||||
socket: args.socket,
|
||||
}, args);
|
||||
|
||||
const [serverAddress, /* ignore */] = await Promise.all([
|
||||
server.listen(),
|
||||
unpackExecutables(),
|
||||
]);
|
||||
logger.info(`Server listening on ${serverAddress}`);
|
||||
|
||||
if (options.auth && !process.env.PASSWORD) {
|
||||
logger.info(` - Password is ${options.password}`);
|
||||
logger.info(" - To use your own password, set the PASSWORD environment variable");
|
||||
} else if (options.auth) {
|
||||
logger.info(" - Using custom password for authentication");
|
||||
} else {
|
||||
logger.info(" - No authentication");
|
||||
}
|
||||
|
||||
if (server.protocol === "https") {
|
||||
logger.info(
|
||||
args.cert
|
||||
? ` - Using provided certificate${args["cert-key"] ? " and key" : ""} for HTTPS`
|
||||
: ` - Using generated certificate and key for HTTPS`,
|
||||
);
|
||||
} else {
|
||||
logger.info(" - Not serving HTTPS");
|
||||
}
|
||||
|
||||
if (!server.options.socket && args.open) {
|
||||
// The web socket doesn't seem to work if browsing with 0.0.0.0.
|
||||
const openAddress = serverAddress.replace(/:\/\/0.0.0.0/, "://localhost");
|
||||
await open(openAddress).catch(console.error);
|
||||
logger.info(` - Opened ${openAddress}`);
|
||||
}
|
||||
};
|
||||
|
||||
const startCli = (): boolean | Promise<void> => {
|
||||
const args = getArgs();
|
||||
if (args.help) {
|
||||
const executable = `${product.applicationName}${os.platform() === "win32" ? ".exe" : ""}`;
|
||||
console.log(buildHelpMessage(product.nameLong, executable, pkg.codeServerVersion, undefined, false));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.version) {
|
||||
if (args.format === "json") {
|
||||
console.log(JSON.stringify({
|
||||
codeServerVersion: pkg.codeServerVersion,
|
||||
commit: product.commit,
|
||||
vscodeVersion: pkg.version,
|
||||
}));
|
||||
} else {
|
||||
buildVersionMessage(pkg.codeServerVersion, product.commit).split("\n").map((line) => logger.info(line));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const shouldSpawnCliProcess = (): boolean => {
|
||||
return !!args["install-source"]
|
||||
|| !!args["list-extensions"]
|
||||
|| !!args["install-extension"]
|
||||
|| !!args["uninstall-extension"]
|
||||
|| !!args["locate-extension"]
|
||||
|| !!args["telemetry"];
|
||||
};
|
||||
|
||||
if (shouldSpawnCliProcess()) {
|
||||
enableCustomMarketplace();
|
||||
return vsCli(args);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export class WrapperProcess {
|
||||
private process?: cp.ChildProcess;
|
||||
private started?: Promise<void>;
|
||||
|
||||
public constructor() {
|
||||
ipcMain.onMessage(async (message) => {
|
||||
switch (message) {
|
||||
case "relaunch":
|
||||
logger.info("Relaunching...");
|
||||
this.started = undefined;
|
||||
if (this.process) {
|
||||
this.process.kill();
|
||||
}
|
||||
try {
|
||||
await this.start();
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
process.exit(typeof error.code === "number" ? error.code : 1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.error(`Unrecognized message ${message}`);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public start(): Promise<void> {
|
||||
if (!this.started) {
|
||||
const child = this.spawn();
|
||||
this.started = ipcMain.handshake(child);
|
||||
this.process = child;
|
||||
}
|
||||
return this.started;
|
||||
}
|
||||
|
||||
private spawn(): cp.ChildProcess {
|
||||
return cp.spawn(process.argv[0], process.argv.slice(1), {
|
||||
env: {
|
||||
...process.env,
|
||||
LAUNCH_VSCODE: "true",
|
||||
},
|
||||
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const main = async(): Promise<boolean | void | void[]> => {
|
||||
if (process.env.LAUNCH_VSCODE) {
|
||||
await ipcMain.handshake();
|
||||
return startVscode();
|
||||
}
|
||||
return startCli() || new WrapperProcess().start();
|
||||
};
|
||||
|
||||
// It's possible that the pipe has closed (for example if you run code-server
|
||||
// --version | head -1). Assume that means we're done.
|
||||
if (!process.stdout.isTTY) {
|
||||
process.stdout.on("error", () => process.exit());
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
logger.error(error.message);
|
||||
process.exit(typeof error.code === "number" ? error.code : 1);
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
import { URI } from "vs/base/common/uri";
|
||||
import { registerSingleton } from "vs/platform/instantiation/common/extensions";
|
||||
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
|
||||
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
|
||||
import { ILocalizationsService } from "vs/platform/localizations/common/localizations";
|
||||
import { LocalizationsService } from "vs/platform/localizations/electron-browser/localizationsService";
|
||||
import { IUpdateService } from "vs/platform/update/common/update";
|
||||
import { UpdateService } from "vs/platform/update/electron-browser/updateService";
|
||||
import { TelemetryChannelClient } from "vs/server/src/telemetry";
|
||||
import { IUploadService, UploadService } from 'vs/server/src/upload';
|
||||
import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService";
|
||||
|
||||
class TelemetryService extends TelemetryChannelClient {
|
||||
public constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
) {
|
||||
super(remoteAgentService.getConnection()!.getChannel("telemetry"));
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(ILocalizationsService, LocalizationsService);
|
||||
registerSingleton(ITelemetryService, TelemetryService);
|
||||
registerSingleton(IUpdateService, UpdateService);
|
||||
registerSingleton(IUploadService, UploadService, true);
|
||||
|
||||
import "vs/workbench/contrib/update/electron-browser/update.contribution";
|
||||
import 'vs/workbench/contrib/localizations/browser/localizations.contribution';
|
||||
|
||||
import { coderApi, vscodeApi } from "vs/server/src/api";
|
||||
|
||||
/**
|
||||
* 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 target = window as any;
|
||||
target.ide = coderApi(services);
|
||||
target.vscode = vscodeApi(services);
|
||||
|
||||
const event = new CustomEvent("ide-ready");
|
||||
(event as any).ide = target.ide;
|
||||
(event as any).vscode = target.vscode;
|
||||
window.dispatchEvent(event);
|
||||
};
|
||||
|
||||
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] = kv.split("=", 2);
|
||||
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);
|
||||
};
|
||||
60
src/common/api.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
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
|
||||
/**
|
||||
* Path if this is a browser app (like VS Code).
|
||||
*/
|
||||
readonly path?: string
|
||||
/**
|
||||
* PID if this is a process.
|
||||
*/
|
||||
readonly pid?: number
|
||||
readonly version?: string
|
||||
}
|
||||
|
||||
export interface ApplicationsResponse {
|
||||
readonly applications: ReadonlyArray<Application>
|
||||
}
|
||||
|
||||
export enum SessionError {
|
||||
FailedToStart = 4000,
|
||||
Starting = 4001,
|
||||
InvalidState = 4002,
|
||||
Unknown = 4003,
|
||||
}
|
||||
|
||||
export interface SessionResponse {
|
||||
/**
|
||||
* Whether the process was spawned or an existing one was returned.
|
||||
*/
|
||||
created: boolean
|
||||
pid: number
|
||||
}
|
||||
|
||||
export interface RecentResponse {
|
||||
readonly paths: string[]
|
||||
readonly workspaces: string[]
|
||||
}
|
||||
|
||||
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 = []
|
||||
}
|
||||
}
|
||||
24
src/common/http.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
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",
|
||||
process = "/process",
|
||||
recent = "/recent",
|
||||
run = "/run",
|
||||
status = "/status",
|
||||
}
|
||||
77
src/common/util.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { logger, field } from "@coder/logger"
|
||||
|
||||
export interface Options {
|
||||
base: string
|
||||
commit: string
|
||||
logLevel: number
|
||||
pid?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a string up to the delimiter. If the delimiter doesn't exist the first
|
||||
* item will have all the text and the second item will be an empty string.
|
||||
*/
|
||||
export const split = (str: string, delimiter: string): [string, string] => {
|
||||
const index = str.indexOf(delimiter)
|
||||
return index !== -1 ? [str.substring(0, index).trim(), str.substring(index + 1)] : [str, ""]
|
||||
}
|
||||
|
||||
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 or query params.
|
||||
*/
|
||||
export const getOptions = <T extends Options>(): T => {
|
||||
let options: T
|
||||
try {
|
||||
const el = document.getElementById("coder-options")
|
||||
if (!el) {
|
||||
throw new Error("no options element")
|
||||
}
|
||||
const value = el.getAttribute("data-settings")
|
||||
if (!value) {
|
||||
throw new Error("no options value")
|
||||
}
|
||||
options = JSON.parse(value)
|
||||
} catch (error) {
|
||||
options = {} as T
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(location.search)
|
||||
const queryOpts = params.get("options")
|
||||
if (queryOpts) {
|
||||
options = {
|
||||
...options,
|
||||
...JSON.parse(queryOpts),
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof options.logLevel !== "undefined") {
|
||||
logger.level = options.logLevel
|
||||
}
|
||||
if (options.base) {
|
||||
const parts = location.pathname.replace(/^\//g, "").split("/")
|
||||
parts[parts.length - 1] = options.base
|
||||
const url = new URL(location.origin + "/" + parts.join("/"))
|
||||
options.base = normalize(url.pathname, true)
|
||||
}
|
||||
|
||||
logger.debug("got options", field("options", options))
|
||||
|
||||
return options
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
import * as cp from "child_process";
|
||||
import { getPathFromAmdModule } from "vs/base/common/amd";
|
||||
import { VSBuffer } from "vs/base/common/buffer";
|
||||
import { Emitter } from "vs/base/common/event";
|
||||
import { ISocket } from "vs/base/parts/ipc/common/ipc.net";
|
||||
import { NodeSocket } from "vs/base/parts/ipc/node/ipc.net";
|
||||
import { IEnvironmentService } from "vs/platform/environment/common/environment";
|
||||
import { ILogService } from "vs/platform/log/common/log";
|
||||
import { getNlsConfiguration } from "vs/server/src/nls";
|
||||
import { Protocol } from "vs/server/src/protocol";
|
||||
import { uriTransformerPath } from "vs/server/src/util";
|
||||
import { IExtHostReadyMessage } from "vs/workbench/services/extensions/common/extensionHostProtocol";
|
||||
|
||||
export abstract class Connection {
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
public readonly onClose = this._onClose.event;
|
||||
private disposed = false;
|
||||
private _offline: number | undefined;
|
||||
|
||||
public constructor(protected protocol: Protocol, public readonly token: string) {}
|
||||
|
||||
public get offline(): number | undefined {
|
||||
return this._offline;
|
||||
}
|
||||
|
||||
public reconnect(socket: ISocket, buffer: VSBuffer): void {
|
||||
this._offline = undefined;
|
||||
this.doReconnect(socket, buffer);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this.disposed) {
|
||||
this.disposed = true;
|
||||
this.doDispose();
|
||||
this._onClose.fire();
|
||||
}
|
||||
}
|
||||
|
||||
protected setOffline(): void {
|
||||
if (!this._offline) {
|
||||
this._offline = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the connection on a new socket.
|
||||
*/
|
||||
protected abstract doReconnect(socket: ISocket, buffer: VSBuffer): void;
|
||||
protected abstract doDispose(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for all the IPC channels.
|
||||
*/
|
||||
export class ManagementConnection extends Connection {
|
||||
public constructor(protected protocol: Protocol, token: string) {
|
||||
super(protocol, token);
|
||||
protocol.onClose(() => this.dispose()); // Explicit close.
|
||||
protocol.onSocketClose(() => this.setOffline()); // Might reconnect.
|
||||
}
|
||||
|
||||
protected doDispose(): void {
|
||||
this.protocol.sendDisconnect();
|
||||
this.protocol.dispose();
|
||||
this.protocol.getSocket().end();
|
||||
}
|
||||
|
||||
protected doReconnect(socket: ISocket, buffer: VSBuffer): void {
|
||||
this.protocol.beginAcceptReconnection(socket, buffer);
|
||||
this.protocol.endAcceptReconnection();
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionHostConnection extends Connection {
|
||||
private process?: cp.ChildProcess;
|
||||
|
||||
public constructor(
|
||||
locale:string, protocol: Protocol, buffer: VSBuffer, token: string,
|
||||
private readonly log: ILogService,
|
||||
private readonly environment: IEnvironmentService,
|
||||
) {
|
||||
super(protocol, token);
|
||||
this.protocol.dispose();
|
||||
this.spawn(locale, buffer).then((p) => this.process = p);
|
||||
this.protocol.getUnderlyingSocket().pause();
|
||||
}
|
||||
|
||||
protected doDispose(): void {
|
||||
if (this.process) {
|
||||
this.process.kill();
|
||||
}
|
||||
this.protocol.getSocket().end();
|
||||
}
|
||||
|
||||
protected doReconnect(socket: ISocket, buffer: VSBuffer): void {
|
||||
// This is just to set the new socket.
|
||||
this.protocol.beginAcceptReconnection(socket, null);
|
||||
this.protocol.dispose();
|
||||
this.sendInitMessage(buffer);
|
||||
}
|
||||
|
||||
private sendInitMessage(buffer: VSBuffer): void {
|
||||
const socket = this.protocol.getUnderlyingSocket();
|
||||
socket.pause();
|
||||
this.process!.send({ // Process must be set at this point.
|
||||
type: "VSCODE_EXTHOST_IPC_SOCKET",
|
||||
initialDataChunk: (buffer.buffer as Buffer).toString("base64"),
|
||||
skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
private async spawn(locale: string, buffer: VSBuffer): Promise<cp.ChildProcess> {
|
||||
const config = await getNlsConfiguration(locale, this.environment.userDataPath);
|
||||
const proc = cp.fork(
|
||||
getPathFromAmdModule(require, "bootstrap-fork"),
|
||||
[ "--type=extensionHost", `--uriTransformerPath=${uriTransformerPath}` ],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
AMD_ENTRYPOINT: "vs/workbench/services/extensions/node/extensionHostProcess",
|
||||
PIPE_LOGGING: "true",
|
||||
VERBOSE_LOGGING: "true",
|
||||
VSCODE_EXTHOST_WILL_SEND_SOCKET: "true",
|
||||
VSCODE_HANDLES_UNCAUGHT_ERRORS: "true",
|
||||
VSCODE_LOG_STACK: "false",
|
||||
VSCODE_NLS_CONFIG: JSON.stringify(config),
|
||||
},
|
||||
silent: true,
|
||||
},
|
||||
);
|
||||
|
||||
proc.on("error", () => this.dispose());
|
||||
proc.on("exit", () => this.dispose());
|
||||
proc.stdout.setEncoding("utf8").on("data", (d) => this.log.info("Extension host stdout", d));
|
||||
proc.stderr.setEncoding("utf8").on("data", (d) => this.log.error("Extension host stderr", d));
|
||||
proc.on("message", (event) => {
|
||||
if (event && event.type === "__$console") {
|
||||
const severity = (<any>this.log)[event.severity] ? event.severity : "info";
|
||||
(<any>this.log)[severity]("Extension host", event.arguments);
|
||||
}
|
||||
if (event && event.type === "VSCODE_EXTHOST_DISCONNECTED") {
|
||||
this.setOffline();
|
||||
}
|
||||
});
|
||||
|
||||
const listen = (message: IExtHostReadyMessage) => {
|
||||
if (message.type === "VSCODE_EXTHOST_IPC_READY") {
|
||||
proc.removeListener("message", listen);
|
||||
this.sendInitMessage(buffer);
|
||||
}
|
||||
};
|
||||
|
||||
return proc.on("message", listen);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import * as appInsights from "applicationinsights";
|
||||
import * as https from "https";
|
||||
import * as http from "http";
|
||||
import * as os from "os";
|
||||
|
||||
export class TelemetryClient implements appInsights.TelemetryClient {
|
||||
public config: any = {};
|
||||
|
||||
public channel = {
|
||||
setUseDiskRetryCaching: (): void => undefined,
|
||||
};
|
||||
|
||||
public trackEvent(options: appInsights.EventTelemetry): void {
|
||||
if (!options.properties) {
|
||||
options.properties = {};
|
||||
}
|
||||
if (!options.measurements) {
|
||||
options.measurements = {};
|
||||
}
|
||||
|
||||
try {
|
||||
const cpus = os.cpus();
|
||||
options.measurements.cores = cpus.length;
|
||||
options.properties["common.cpuModel"] = cpus[0].model;
|
||||
} catch (error) {}
|
||||
|
||||
try {
|
||||
options.measurements.memoryFree = os.freemem();
|
||||
options.measurements.memoryTotal = os.totalmem();
|
||||
} catch (error) {}
|
||||
|
||||
try {
|
||||
options.properties["common.shell"] = os.userInfo().shell;
|
||||
options.properties["common.release"] = os.release();
|
||||
options.properties["common.arch"] = os.arch();
|
||||
} catch (error) {}
|
||||
|
||||
try {
|
||||
const url = process.env.TELEMETRY_URL || "https://v1.telemetry.coder.com/track";
|
||||
const request = (/^http:/.test(url) ? http : https).request(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
request.on("error", () => { /* We don't care. */ });
|
||||
request.write(JSON.stringify(options));
|
||||
request.end();
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
public flush(options: appInsights.FlushOptions): void {
|
||||
if (options.callback) {
|
||||
options.callback("");
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/ipc.ts
@@ -1,52 +0,0 @@
|
||||
import * as cp from "child_process";
|
||||
import { Emitter } from "vs/base/common/event";
|
||||
|
||||
enum ControlMessage {
|
||||
okToChild = "ok>",
|
||||
okFromChild = "ok<",
|
||||
}
|
||||
|
||||
export type Message = "relaunch";
|
||||
|
||||
class IpcMain {
|
||||
protected readonly _onMessage = new Emitter<Message>();
|
||||
public readonly onMessage = this._onMessage.event;
|
||||
|
||||
public handshake(child?: cp.ChildProcess): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const target = child || process;
|
||||
if (!target.send) {
|
||||
throw new Error("Not spawned with IPC enabled");
|
||||
}
|
||||
target.on("message", (message) => {
|
||||
if (message === child ? ControlMessage.okFromChild : ControlMessage.okToChild) {
|
||||
target.removeAllListeners();
|
||||
target.on("message", (msg) => this._onMessage.fire(msg));
|
||||
if (child) {
|
||||
target.send!(ControlMessage.okToChild);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
if (child) {
|
||||
child.once("error", reject);
|
||||
child.once("exit", (code) => {
|
||||
const error = new Error(`Unexpected exit with code ${code}`);
|
||||
(error as any).code = code;
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
target.send(ControlMessage.okFromChild);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public relaunch(): void {
|
||||
if (!process.send) {
|
||||
throw new Error("Not a child process with IPC enabled");
|
||||
}
|
||||
process.send("relaunch");
|
||||
}
|
||||
}
|
||||
|
||||
export const ipcMain = new IpcMain();
|
||||
@@ -1,29 +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="./favicon.ico" type="image/x-icon" />
|
||||
<link rel="manifest" href="./manifest.json">
|
||||
<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,175 +0,0 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as util from "util";
|
||||
import { CancellationToken } from "vs/base/common/cancellation";
|
||||
import { mkdirp } from "vs/base/node/pfs";
|
||||
import * as vszip from "vs/base/node/zip";
|
||||
import * as nls from "vs/nls";
|
||||
import product from "vs/platform/product/node/product";
|
||||
import { localRequire } from "vs/server/src/util";
|
||||
|
||||
const tarStream = localRequire<typeof import("tar-stream")>("tar-stream/index");
|
||||
|
||||
// We will be overriding these, so keep a reference to the original.
|
||||
const vszipExtract = vszip.extract;
|
||||
const vszipBuffer = vszip.buffer;
|
||||
|
||||
export interface IExtractOptions {
|
||||
overwrite?: boolean;
|
||||
/**
|
||||
* Source path within the TAR/ZIP archive. Only the files
|
||||
* contained in this path will be extracted.
|
||||
*/
|
||||
sourcePath?: string;
|
||||
}
|
||||
|
||||
export interface IFile {
|
||||
path: string;
|
||||
contents?: Buffer | string;
|
||||
localPath?: string;
|
||||
}
|
||||
|
||||
export const tar = async (tarPath: string, files: IFile[]): Promise<string> => {
|
||||
const pack = tarStream.pack();
|
||||
const chunks: Buffer[] = [];
|
||||
const ended = new Promise<Buffer>((resolve) => {
|
||||
pack.on("end", () => resolve(Buffer.concat(chunks)));
|
||||
});
|
||||
pack.on("data", (chunk: Buffer) => chunks.push(chunk));
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
pack.entry({ name: file.path }, file.contents);
|
||||
}
|
||||
pack.finalize();
|
||||
await util.promisify(fs.writeFile)(tarPath, await ended);
|
||||
return tarPath;
|
||||
};
|
||||
|
||||
export const extract = async (archivePath: string, extractPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> => {
|
||||
try {
|
||||
await extractTar(archivePath, extractPath, options, token);
|
||||
} catch (error) {
|
||||
if (error.toString().includes("Invalid tar header")) {
|
||||
await vszipExtract(archivePath, extractPath, options, token);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const buffer = (targetPath: string, filePath: string): Promise<Buffer> => {
|
||||
return new Promise<Buffer>(async (resolve, reject) => {
|
||||
try {
|
||||
let done: boolean = false;
|
||||
await extractAssets(targetPath, new RegExp(filePath), (assetPath: string, data: Buffer) => {
|
||||
if (path.normalize(assetPath) === path.normalize(filePath)) {
|
||||
done = true;
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
if (!done) {
|
||||
throw new Error("couldn't find asset " + filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.toString().includes("Invalid tar header")) {
|
||||
vszipBuffer(targetPath, filePath).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const extractAssets = async (tarPath: string, match: RegExp, callback: (path: string, data: Buffer) => void): Promise<void> => {
|
||||
const buffer = await util.promisify(fs.readFile)(tarPath);
|
||||
return new Promise<void>(async (resolve, reject): Promise<void> => {
|
||||
const extractor = tarStream.extract();
|
||||
extractor.once("error", reject);
|
||||
extractor.on("entry", async (header, stream, next) => {
|
||||
const name = header.name;
|
||||
if (match.test(name)) {
|
||||
extractData(stream).then((data) => {
|
||||
callback(name, data);
|
||||
next();
|
||||
}).catch(reject);
|
||||
stream.resume();
|
||||
} else {
|
||||
stream.on("end", () => next());
|
||||
stream.resume();
|
||||
}
|
||||
});
|
||||
extractor.on("finish", resolve);
|
||||
extractor.write(buffer);
|
||||
extractor.end();
|
||||
});
|
||||
};
|
||||
|
||||
const extractData = (stream: NodeJS.ReadableStream): Promise<Buffer> => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
const fileData: Buffer[] = [];
|
||||
stream.on("data", (data) => fileData.push(data));
|
||||
stream.on("end", () => resolve(Buffer.concat(fileData)));
|
||||
stream.on("error", reject);
|
||||
});
|
||||
};
|
||||
|
||||
const extractTar = async (tarPath: string, targetPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> => {
|
||||
const buffer = await util.promisify(fs.readFile)(tarPath);
|
||||
return new Promise<void>(async (resolve, reject): Promise<void> => {
|
||||
const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : "");
|
||||
const extractor = tarStream.extract();
|
||||
extractor.once("error", reject);
|
||||
extractor.on("entry", async (header, stream, next) => {
|
||||
const rawName = path.normalize(header.name);
|
||||
|
||||
const nextEntry = (): void => {
|
||||
stream.resume();
|
||||
next();
|
||||
};
|
||||
|
||||
if (token.isCancellationRequested || !sourcePathRegex.test(rawName)) {
|
||||
return nextEntry();
|
||||
}
|
||||
|
||||
const fileName = rawName.replace(sourcePathRegex, "");
|
||||
const targetFileName = path.join(targetPath, fileName);
|
||||
if (/\/$/.test(fileName)) {
|
||||
return mkdirp(targetFileName).then(nextEntry);
|
||||
}
|
||||
|
||||
const dirName = path.dirname(fileName);
|
||||
const targetDirName = path.join(targetPath, dirName);
|
||||
if (targetDirName.indexOf(targetPath) !== 0) {
|
||||
return reject(nls.localize("invalid file", "Error extracting {0}. Invalid file.", fileName));
|
||||
}
|
||||
|
||||
return mkdirp(targetDirName, undefined, token).then(() => {
|
||||
const fstream = fs.createWriteStream(targetFileName, { mode: header.mode });
|
||||
fstream.once("close", () => next());
|
||||
fstream.once("error", reject);
|
||||
stream.pipe(fstream);
|
||||
stream.resume();
|
||||
});
|
||||
});
|
||||
extractor.once("finish", resolve);
|
||||
extractor.write(buffer);
|
||||
extractor.end();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Override original functionality so we can use a custom marketplace with
|
||||
* either tars or zips.
|
||||
*/
|
||||
export const enableCustomMarketplace = (): void => {
|
||||
(<any>product).extensionsGallery = { // Use `any` to override readonly.
|
||||
serviceUrl: process.env.SERVICE_URL || "https://v1.extapi.coder.com",
|
||||
itemUrl: process.env.ITEM_URL || "",
|
||||
controlUrl: "",
|
||||
recommendationsUrl: "",
|
||||
...(product.extensionsGallery || {}),
|
||||
};
|
||||
|
||||
const target = vszip as typeof vszip;
|
||||
target.zip = tar;
|
||||
target.extract = extract;
|
||||
target.buffer = buffer;
|
||||
};
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,94 +0,0 @@
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
html, body {
|
||||
background-color: #FFFFFF;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-family: "monospace";
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 18px 80px 10px rgba(69, 65, 78, 0.08);
|
||||
color: #575962;
|
||||
margin-top: -10%;
|
||||
max-width: 328px;
|
||||
padding: 40px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-form > .title {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1.5px;
|
||||
line-height: 15px;
|
||||
margin-bottom: 0px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.login-form > .subtitle {
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
line-height: 25px;
|
||||
margin-bottom: 45px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-form > .field {
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
color: #797E84;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.login-form > .field > .input {
|
||||
background: none !important;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-form > .button {
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 12px 17px 2px rgba(171,173,163,0.14), 0 5px 22px 4px rgba(171,173,163,0.12), 0 7px 8px -4px rgba(171,173,163,0.2);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
padding: 15px 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-form > .button:hover {
|
||||
background-color: rgb(0, 122, 204);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.error-display {
|
||||
box-sizing: border-box;
|
||||
color: #bb2d0f;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 12px;
|
||||
padding: 20px 8px 0;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "code-server",
|
||||
"short_name": "code-server",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background-color": "#fff",
|
||||
"description": "Run VS Code on a remote server.",
|
||||
"icons": [{
|
||||
"src": "static/code-server.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
}]
|
||||
}
|
||||
80
src/nls.ts
@@ -1,80 +0,0 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as util from "util";
|
||||
import { getPathFromAmdModule } from "vs/base/common/amd";
|
||||
import * as lp from "vs/base/node/languagePacks";
|
||||
import product from "vs/platform/product/node/product";
|
||||
import { Translations } from "vs/workbench/services/extensions/common/extensionPoints";
|
||||
|
||||
const configurations = new Map<string, Promise<lp.NLSConfiguration>>();
|
||||
const metadataPath = path.join(getPathFromAmdModule(require, ""), "nls.metadata.json");
|
||||
|
||||
export const isInternalConfiguration = (config: lp.NLSConfiguration): config is lp.InternalNLSConfiguration => {
|
||||
return config && !!(<lp.InternalNLSConfiguration>config)._languagePackId;
|
||||
};
|
||||
|
||||
const DefaultConfiguration = {
|
||||
locale: "en",
|
||||
availableLanguages: {},
|
||||
};
|
||||
|
||||
export const getNlsConfiguration = async (locale: string, userDataPath: string): Promise<lp.NLSConfiguration> => {
|
||||
const id = `${locale}: ${userDataPath}`;
|
||||
if (!configurations.has(id)) {
|
||||
configurations.set(id, new Promise(async (resolve) => {
|
||||
const config = product.commit && await util.promisify(fs.exists)(metadataPath)
|
||||
? await lp.getNLSConfiguration(product.commit, userDataPath, metadataPath, locale)
|
||||
: DefaultConfiguration;
|
||||
if (isInternalConfiguration(config)) {
|
||||
config._languagePackSupport = true;
|
||||
}
|
||||
resolve(config);
|
||||
}));
|
||||
}
|
||||
return configurations.get(id)!;
|
||||
};
|
||||
|
||||
export const getTranslations = async (locale: string, userDataPath: string): Promise<Translations> => {
|
||||
const config = await getNlsConfiguration(locale, userDataPath);
|
||||
if (isInternalConfiguration(config)) {
|
||||
try {
|
||||
return JSON.parse(await util.promisify(fs.readFile)(config._translationsConfigFile, "utf8"));
|
||||
} catch (error) { /* Nothing yet. */}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export const getLocaleFromConfig = async (userDataPath: string): Promise<string> => {
|
||||
let locale = "en";
|
||||
try {
|
||||
const localeConfigUri = path.join(userDataPath, "User/locale.json");
|
||||
const content = stripComments(await util.promisify(fs.readFile)(localeConfigUri, "utf8"));
|
||||
locale = JSON.parse(content).locale;
|
||||
} catch (error) { /* Ignore. */ }
|
||||
return locale;
|
||||
};
|
||||
|
||||
// Taken from src/main.js in the main VS Code source.
|
||||
const stripComments = (content: string): string => {
|
||||
const regexp = /("(?:[^\\"]*(?:\\.)?)*")|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
|
||||
|
||||
return content.replace(regexp, (match, _m1, _m2, m3, m4) => {
|
||||
// Only one of m1, m2, m3, m4 matches
|
||||
if (m3) {
|
||||
// A block comment. Replace with nothing
|
||||
return '';
|
||||
} else if (m4) {
|
||||
// A line comment. If it ends in \r?\n then keep it.
|
||||
const length_1 = m4.length;
|
||||
if (length_1 > 2 && m4[length_1 - 1] === '\n') {
|
||||
return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
} else {
|
||||
// We match a string
|
||||
return match;
|
||||
}
|
||||
});
|
||||
};
|
||||