Compare commits
254 Commits
2.1688-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 |
@@ -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,5 +1,8 @@
|
||||
node_modules
|
||||
*.tsbuildinfo
|
||||
.cache
|
||||
build
|
||||
release
|
||||
dist*
|
||||
out*
|
||||
release*
|
||||
node_modules
|
||||
binaries
|
||||
source
|
||||
|
||||
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
|
||||
91
.travis.yml
@@ -1,73 +1,58 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 10.16.0
|
||||
services:
|
||||
- docker
|
||||
language: minimal
|
||||
|
||||
before_install:
|
||||
- export MAJOR_VERSION="2"
|
||||
- export VSCODE_VERSION="1.39.2"
|
||||
- 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" PUSH_DOCKER="true"
|
||||
- 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 60 scripts/ci.bash
|
||||
|
||||
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 "$PUSH_DOCKER" ]] ; 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 build -f ./scripts/ci.dockerfile -t codercom/code-server:"$TAG" -t codercom/code-server:v2 -t codercom/code-server . && docker push codercom/code-server:"$TAG" && docker push codercom/code-server:v2 && docker push codercom/code-server
|
||||
tags: true
|
||||
- provider: gcs
|
||||
edge: true
|
||||
bucket: "codesrv-ci.cdr.sh"
|
||||
upload_dir: "releases"
|
||||
key_file: ./ci/key.json
|
||||
local_dir: release-upload
|
||||
on:
|
||||
repo: cdr/code-server
|
||||
branch: master
|
||||
condition: -n "$PUSH_DOCKER"
|
||||
tags: true
|
||||
# TODO: The gcs provider fails to install on arm64.
|
||||
condition: $TRAVIS_CPU_ARCH = amd64
|
||||
|
||||
cache:
|
||||
timeout: 600
|
||||
yarn: true
|
||||
directories:
|
||||
- source
|
||||
- lib/vscode/.build/extensions
|
||||
|
||||
61
Dockerfile
@@ -1,61 +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/binaries/code-server${codeServerVersion}-vsc${vscodeVersion}-linux-x86_64" /src/binaries/code-server \
|
||||
&& rm -r /src/build \
|
||||
&& rm -r /src/source
|
||||
|
||||
# We deploy with ubuntu so that devs have a familiar environment.
|
||||
FROM ubuntu:18.04
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
openssl \
|
||||
net-tools \
|
||||
git \
|
||||
locales \
|
||||
sudo \
|
||||
dumb-init \
|
||||
vim \
|
||||
curl \
|
||||
wget
|
||||
|
||||
RUN locale-gen en_US.UTF-8
|
||||
# We cannot use update-locale because docker will not use the env variables
|
||||
# configured in /etc/default/locale so we need to set it manually.
|
||||
ENV LC_ALL=en_US.UTF-8 \
|
||||
SHELL=/bin/bash
|
||||
|
||||
RUN adduser --gecos '' --disabled-password coder && \
|
||||
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
|
||||
|
||||
USER coder
|
||||
# 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/binaries/code-server /usr/local/bin/code-server
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["dumb-init", "code-server", "--host", "0.0.0.0"]
|
||||
175
README.md
@@ -1,4 +1,4 @@
|
||||
# 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.
|
||||
@@ -6,17 +6,17 @@ 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
|
||||
|
||||
@@ -25,173 +25,34 @@ docker run -it -p 127.0.0.1:8080:8080 -v "${HOME}/.local/share/code-server:/home
|
||||
- 64-bit host.
|
||||
- At least 1GB of RAM.
|
||||
- 2 cores or more are recommended (1 core works but not optimally).
|
||||
- Secure connection over HTTPS or localhost (required for service workers).
|
||||
- Secure connection over HTTPS or localhost (required for service workers and
|
||||
clipboard support).
|
||||
- For Linux: GLIBC 2.17 or later and GLIBCXX 3.4.15 or later.
|
||||
- Docker (for Docker versions of `code-server`).
|
||||
|
||||
### Run over SSH
|
||||
|
||||
Use [sshcode](https://github.com/codercom/sshcode) for a simple setup.
|
||||
|
||||
### Docker
|
||||
|
||||
See the Docker one-liner mentioned above. Dockerfile is at [/Dockerfile](/Dockerfile).
|
||||
|
||||
To debug Golang using the
|
||||
[ms-vscode-go extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.Go),
|
||||
you need to add `--security-opt seccomp=unconfined` to your `docker run`
|
||||
arguments when launching code-server with Docker. See
|
||||
[#725](https://github.com/cdr/code-server/issues/725) for details.
|
||||
|
||||
### Digital Ocean
|
||||
|
||||
[](https://marketplace.digitalocean.com/apps/code-server?action=deploy)
|
||||
[](https://marketplace.digitalocean.com/apps/code-server)
|
||||
|
||||
### Binaries
|
||||
### Releases
|
||||
|
||||
1. [Download a binary](https://github.com/cdr/code-server/releases). (Linux and
|
||||
OS X supported. Windows coming soon)
|
||||
2. Unpack the downloaded file then run the binary.
|
||||
1. [Download a release](https://github.com/cdr/code-server/releases). (Linux and
|
||||
OS X supported. Windows support planned.)
|
||||
2. Unpack the downloaded release then run the included `code-server` script.
|
||||
3. In your browser navigate to `localhost:8080`.
|
||||
|
||||
- For self-hosting and other information see [doc/quickstart.md](doc/quickstart.md).
|
||||
- For hosting on cloud platforms see [doc/deploy.md](doc/deploy.md).
|
||||
## FAQ
|
||||
|
||||
### Build
|
||||
|
||||
See
|
||||
[VS Code's prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
|
||||
before building.
|
||||
|
||||
```shell
|
||||
export OUT=/path/to/output/build # Optional if only building. Required if also developing.
|
||||
yarn build ${vscodeVersion} ${codeServerVersion} # See travis.yml for the VS Code version to use.
|
||||
# The code-server version can be anything you want.
|
||||
node /path/to/output/build/out/vs/server/main.js # You can run the built JavaScript with Node.
|
||||
yarn binary ${vscodeVersion} ${codeServerVersion} # Or you can package it into a binary.
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### Authentication
|
||||
By default `code-server` enables password authentication using a randomly
|
||||
generated password. You can set the `PASSWORD` environment variable to use your
|
||||
own instead or use `--auth none` to disable password authentication.
|
||||
|
||||
Do not expose `code-server` to the open internet without some form of
|
||||
authentication.
|
||||
|
||||
### Encrypting traffic with HTTPS
|
||||
If you aren't doing SSL termination elsewhere you can directly give
|
||||
`code-server` a certificate with `code-server --cert` followed by the path to
|
||||
your certificate. Additionally, you can use certificate keys with `--cert-key`
|
||||
followed by the path to your key. If you pass `--cert` without any path
|
||||
`code-server` will generate a self-signed certificate.
|
||||
|
||||
If `code-server` has been passed a certificate it will also respond to HTTPS
|
||||
requests and will redirect all HTTP requests to HTTPS. Otherwise it will respond
|
||||
only to HTTP requests.
|
||||
|
||||
You can use [Let's Encrypt](https://letsencrypt.org/) to get an SSL certificate
|
||||
for free.
|
||||
|
||||
Do not expose `code-server` to the open internet without SSL, whether built-in
|
||||
or through a proxy.
|
||||
|
||||
## Known Issues
|
||||
|
||||
- Creating custom VS Code extensions and debugging them doesn't work.
|
||||
- Extension profiling and tips are currently disabled.
|
||||
|
||||
## Future
|
||||
|
||||
- **Stay up to date!** Get notified about new releases of code-server.
|
||||

|
||||
- Windows support.
|
||||
- Electron and Chrome OS applications to bridge the gap between local<->remote.
|
||||
- Run VS Code unit tests against our builds to ensure features work as expected.
|
||||
|
||||
## Extensions
|
||||
|
||||
code-server does not provide access to the official
|
||||
[Visual Studio Marketplace](https://marketplace.visualstudio.com/vscode). Instead,
|
||||
Coder has created a custom extension marketplace that we manage for open-source
|
||||
extensions. If you want to use an extension with code-server that we do not have
|
||||
in our marketplace please look for a release in the extension’s repository,
|
||||
contact us to see if we have one in the works or, if you build an extension
|
||||
locally from open source, you can copy it to the `extensions` folder. If you
|
||||
build one locally from open-source please contribute it to the project and let
|
||||
us know so we can give you props! If you have your own custom marketplace, it is
|
||||
possible to point code-server to it by setting the `SERVICE_URL` and `ITEM_URL`
|
||||
environment variables.
|
||||
|
||||
## Telemetry
|
||||
|
||||
Use the `--disable-telemetry` flag to completely disable telemetry. We use the
|
||||
data collected to improve code-server.
|
||||
See [./doc/FAQ.md](./doc/FAQ.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
### Development
|
||||
|
||||
See
|
||||
[VS Code's prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
|
||||
before developing.
|
||||
|
||||
```shell
|
||||
git clone https://github.com/microsoft/vscode
|
||||
cd vscode
|
||||
git checkout ${vscodeVersion} # See travis.yml for the version to use.
|
||||
yarn
|
||||
git clone https://github.com/cdr/code-server src/vs/server
|
||||
cd src/vs/server
|
||||
yarn
|
||||
yarn patch:apply
|
||||
yarn watch
|
||||
# Wait for the initial compilation to complete (it will say "Finished compilation").
|
||||
# Run the next command in another shell.
|
||||
yarn start
|
||||
# Visit http://localhost:8080
|
||||
```
|
||||
|
||||
If you run into issues about a different version of Node being used, try running
|
||||
`npm rebuild` in the VS Code directory.
|
||||
|
||||
### Upgrading VS Code
|
||||
|
||||
We patch VS Code to provide and fix some functionality. As the web portion of VS
|
||||
Code matures, we'll be able to shrink and maybe even entirely eliminate our
|
||||
patch. In the meantime, however, upgrading the VS Code version requires ensuring
|
||||
that the patch still applies and has the intended effects.
|
||||
|
||||
To generate a new patch, **stage all the changes** you want to be included in
|
||||
the patch in the VS Code source, then run `yarn patch:generate` in this
|
||||
directory.
|
||||
|
||||
Our changes include:
|
||||
|
||||
- Allow multiple extension directories (both user and built-in).
|
||||
- Modify the loader, websocket, webview, service worker, and asset requests to
|
||||
use the URL of the page as a base (and TLS if necessary for the websocket).
|
||||
- Send client-side telemetry through the server.
|
||||
- Add an upload service along with a file prefix to ignore for temporary files
|
||||
created during upload.
|
||||
- Make changing the display language work.
|
||||
- Make it possible for us to load code on the client.
|
||||
- Make extensions work in the browser.
|
||||
- Fix getting permanently disconnected when you sleep or hibernate for a while.
|
||||
- Make it possible to automatically update the binary.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
See [./doc/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:v2
|
||||
imagePullPolicy: Always
|
||||
name: code-servery
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: https
|
||||
volumeMounts:
|
||||
- name: code-server-storage
|
||||
mountPath: /go/src
|
||||
volumes:
|
||||
- name: code-server-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: code-store
|
||||
@@ -1,43 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: code-server
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: code-server
|
||||
namespace: code-server
|
||||
spec:
|
||||
ports:
|
||||
- port: 8080
|
||||
name: https
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: code-server
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: code-server
|
||||
name: code-server
|
||||
namespace: code-server
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: code-server
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: code-server
|
||||
spec:
|
||||
containers:
|
||||
- image: codercom/code-server:v2
|
||||
imagePullPolicy: Always
|
||||
name: code-server
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: https
|
||||
@@ -1,35 +0,0 @@
|
||||
# Protecting code-server from bruteforce attempts
|
||||
code-server outputs all failed login attempts, along with the IP address,
|
||||
provided password, user agent and timestamp by default.
|
||||
|
||||
When using a reverse proxy such as Nginx or Apache, the remote address may
|
||||
appear to be `127.0.0.1` or a similar address so `X-Forwarded-For` should be
|
||||
used instead. Ensure that you are setting this value in your reverse proxy:
|
||||
|
||||
Nginx:
|
||||
```
|
||||
location / {
|
||||
...
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Apache:
|
||||
```
|
||||
<VirtualEnv>
|
||||
...
|
||||
SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded
|
||||
...
|
||||
</VirtualEnv>
|
||||
```
|
||||
|
||||
It is extremely important that you ensure that your code-server instance is not
|
||||
accessible from the internet (use localhost or block it in your firewall).
|
||||
|
||||
## Fail2Ban
|
||||
Fail2Ban allows for automatically banning and logging repeated failed
|
||||
authentication attempts for many applications through regex filters. A working
|
||||
filter for code-server can be found in `./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,98 +0,0 @@
|
||||
# Quickstart Guide
|
||||
1. Visit the [releases page](https://github.com/cdr/code-server/releases) and
|
||||
download the latest binary for your operating system.
|
||||
2. Unpack the downloaded file then run the binary.
|
||||
3. In your browser navigate to `localhost:8080`.
|
||||
|
||||
## Usage
|
||||
Run `code-server --help` to view available options.
|
||||
|
||||
### Nginx Reverse Proxy
|
||||
The trailing slashes are important.
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name code.example.com code.example.org;
|
||||
location /some/path/ { # Or / if hosting at the root.
|
||||
proxy_pass http://localhost:8080/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection upgrade;
|
||||
proxy_set_header Accept-Encoding gzip;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Apache Reverse Proxy
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerName code.example.com
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:Upgrade} =websocket [NC]
|
||||
RewriteRule /(.*) ws://localhost:8080/$1 [P,L]
|
||||
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
|
||||
RewriteRule /(.*) http://localhost:8080/$1 [P,L]
|
||||
|
||||
ProxyRequests off
|
||||
ProxyPass / http://localhost:8080/ nocanon
|
||||
ProxyPassReverse / http://localhost:8080/
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
### Run automatically at startup
|
||||
|
||||
In some cases you might need to run code-server automatically once the host starts. You may use your local init service to do so.
|
||||
|
||||
#### Systemd
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Code Server IDE
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=<USER>
|
||||
EnvironmentFile=$HOME/.profile
|
||||
WorkingDirectory=$HOME
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
|
||||
ExecStart=<PATH TO BINARY> $(pwd)
|
||||
|
||||
StandardOutput=file:/var/log/code-server-output.log
|
||||
StandardError=file:/var/log/code-server-error.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
#### OpenRC
|
||||
|
||||
```sh
|
||||
#!/sbin/openrc-run
|
||||
|
||||
depend() {
|
||||
after net-online
|
||||
need net
|
||||
}
|
||||
|
||||
supervisor=supervise-daemon
|
||||
name="code-server"
|
||||
command="/opt/cdr/code-server"
|
||||
command_args=""
|
||||
|
||||
pidfile="/var/run/cdr.pid"
|
||||
respawn_delay=5
|
||||
|
||||
set -o allexport
|
||||
if [ -f /etc/environment ]; then source /etc/environment; fi
|
||||
set +o allexport
|
||||
```
|
||||
|
||||
#### Kubernetes/Docker
|
||||
|
||||
Make sure you set your restart policy to always - this will ensure your container starts as the daemon starts.
|
||||
@@ -1,13 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
code-server:
|
||||
container_name: code-server
|
||||
image: codercom/code-server
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- "${PWD}:/home/coder/project"
|
||||
- "${HOME}/.local/share/code-server:/home/coder/.local/share/code-server"
|
||||
environment:
|
||||
PASSWORD: ${PASSWORD}
|
||||
1
lib/vscode
Submodule
7
main.js
@@ -1,7 +0,0 @@
|
||||
// Once our entry file is loaded we no longer need nbin to bypass normal Node
|
||||
// execution. We can still shim the fs into the binary even when bypassing. This
|
||||
// will ensure for example that a spawn like `${process.argv[0]} -e` will work
|
||||
// while still allowing us to access files within the binary.
|
||||
process.env.NBIN_BYPASS = true;
|
||||
|
||||
require("../../bootstrap-amd").load("vs/server/src/node/cli");
|
||||
68
package.json
@@ -1,41 +1,67 @@
|
||||
{
|
||||
"name": "code-server",
|
||||
"license": "MIT",
|
||||
"version": "3.1.1",
|
||||
"scripts": {
|
||||
"runner": "cd ./scripts && node --max-old-space-size=32384 -r ts-node/register ./build.ts",
|
||||
"start": "nodemon --watch ../../../out --verbose ../../../out/vs/server/main.js",
|
||||
"watch": "cd ../../../ && yarn watch",
|
||||
"build": "yarn && yarn runner build",
|
||||
"package": "yarn runner package",
|
||||
"binary": "yarn runner binary",
|
||||
"patch:generate": "cd ../../../ && git diff --staged > ./src/vs/server/scripts/vscode.patch",
|
||||
"patch:apply": "cd ../../../ && git apply ./src/vs/server/scripts/vscode.patch"
|
||||
"clean": "ci/clean.sh",
|
||||
"vscode": "ci/vscode.sh",
|
||||
"vscode:patch": "cd ./lib/vscode && git apply ../../ci/vscode.patch",
|
||||
"vscode:diff": "cd ./lib/vscode && git diff HEAD > ../../ci/vscode.patch",
|
||||
"test": "mocha -r ts-node/register ./test/*.test.ts",
|
||||
"lint": "ci/lint.sh",
|
||||
"fmt": "ci/fmt.sh",
|
||||
"runner": "cd ./ci && NODE_OPTIONS=--max_old_space_size=32384 ts-node ./build.ts",
|
||||
"build": "yarn runner build",
|
||||
"watch": "yarn runner watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coder/nbin": "^1.2.3",
|
||||
"@types/adm-zip": "^0.4.32",
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"@types/node": "^10.12.12",
|
||||
"@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",
|
||||
"fs-extra": "^8.1.0",
|
||||
"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.6"
|
||||
"typescript": "3.7.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "^10.12.12",
|
||||
"safe-buffer": "^5.1.1"
|
||||
"@types/node": "^12.12.7",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"vfile-message": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coder/logger": "^1.1.8",
|
||||
"@coder/node-browser": "^1.0.6",
|
||||
"@coder/requirefs": "^1.0.6",
|
||||
"@coder/logger": "1.1.11",
|
||||
"adm-zip": "^0.4.14",
|
||||
"fs-extra": "^8.1.0",
|
||||
"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",
|
||||
"util": "^0.12.1"
|
||||
"ws": "^7.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
415
scripts/build.ts
@@ -1,415 +0,0 @@
|
||||
import { Binary } from "@coder/nbin";
|
||||
import * as cp from "child_process";
|
||||
// import * as crypto from "crypto";
|
||||
import * as fs from "fs-extra";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import * as util from "util";
|
||||
|
||||
enum Task {
|
||||
/**
|
||||
* Use before running anything that only works inside VS Code.
|
||||
*/
|
||||
EnsureInVscode = "ensure-in-vscode",
|
||||
Binary = "binary",
|
||||
Package = "package",
|
||||
Build = "build",
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private readonly rootPath = path.resolve(__dirname, "..");
|
||||
private readonly outPath = process.env.OUT || this.rootPath;
|
||||
private _target?: "darwin" | "alpine" | "linux";
|
||||
private currentTask?: Task;
|
||||
|
||||
public run(task: Task | undefined, args: string[]): void {
|
||||
this.currentTask = task;
|
||||
this.doRun(task, args).catch((error) => {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
private async task<T>(message: string, fn: () => Promise<T>): Promise<T> {
|
||||
const time = Date.now();
|
||||
this.log(`${message}...`, true);
|
||||
try {
|
||||
const t = await fn();
|
||||
process.stdout.write(`took ${Date.now() - time}ms\n`);
|
||||
return t;
|
||||
} catch (error) {
|
||||
process.stdout.write("failed\n");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to stdout with an optional newline.
|
||||
*/
|
||||
private log(message: string, skipNewline: boolean = false): void {
|
||||
process.stdout.write(`[${this.currentTask || "default"}] ${message}`);
|
||||
if (!skipNewline) {
|
||||
process.stdout.write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
private async doRun(task: Task | undefined, args: string[]): Promise<void> {
|
||||
if (!task) {
|
||||
throw new Error("No task provided");
|
||||
}
|
||||
|
||||
if (task === Task.EnsureInVscode) {
|
||||
return process.exit(this.isInVscode(this.rootPath) ? 0 : 1);
|
||||
}
|
||||
|
||||
// If we're inside VS Code assume we want to develop. In that case we should
|
||||
// set an OUT directory and not build in this directory, otherwise when you
|
||||
// build/watch VS Code the build directory will be included.
|
||||
if (this.isInVscode(this.outPath)) {
|
||||
throw new Error("Should not build inside VS Code; set the OUT environment variable");
|
||||
}
|
||||
|
||||
this.ensureArgument("rootPath", this.rootPath);
|
||||
this.ensureArgument("outPath", this.outPath);
|
||||
|
||||
const arch = this.ensureArgument("arch", os.arch().replace(/^x/, "x86_"));
|
||||
const target = this.ensureArgument("target", await this.target());
|
||||
const vscodeVersion = this.ensureArgument("vscodeVersion", args[0]);
|
||||
const codeServerVersion = this.ensureArgument("codeServerVersion", args[1]);
|
||||
|
||||
const vscodeSourcePath = path.join(this.outPath, "source", `vscode-${vscodeVersion}-source`);
|
||||
const binariesPath = path.join(this.outPath, "binaries");
|
||||
const binaryName = `code-server${codeServerVersion}-vsc${vscodeVersion}-${target}-${arch}`;
|
||||
const finalBuildPath = path.join(this.outPath, "build", `${binaryName}-built`);
|
||||
|
||||
switch (task) {
|
||||
case Task.Binary:
|
||||
return this.binary(finalBuildPath, binariesPath, binaryName);
|
||||
case Task.Package:
|
||||
return this.package(vscodeSourcePath, binariesPath, binaryName);
|
||||
case Task.Build:
|
||||
return this.build(vscodeSourcePath, vscodeVersion, codeServerVersion, finalBuildPath);
|
||||
default:
|
||||
throw new Error(`No task matching "${task}"`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target of the system.
|
||||
*/
|
||||
private async target(): Promise<"darwin" | "alpine" | "linux"> {
|
||||
if (!this._target) {
|
||||
if (os.platform() === "darwin" || (process.env.OSTYPE && /^darwin/.test(process.env.OSTYPE))) {
|
||||
this._target = "darwin";
|
||||
} else {
|
||||
// Alpine's ldd doesn't have a version flag but if you use an invalid flag
|
||||
// (like --version) it outputs the version to stderr and exits with 1.
|
||||
const result = await util.promisify(cp.exec)("ldd --version")
|
||||
.catch((error) => ({ stderr: error.message, stdout: "" }));
|
||||
if (/musl/.test(result.stderr) || /musl/.test(result.stdout)) {
|
||||
this._target = "alpine";
|
||||
} else {
|
||||
this._target = "linux";
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the argument is set. Display the value if it is.
|
||||
*/
|
||||
private ensureArgument(name: string, arg?: string): string {
|
||||
if (!arg) {
|
||||
this.log(`${name} is missing`);
|
||||
throw new Error("Usage: <vscodeVersion> <codeServerVersion>");
|
||||
}
|
||||
this.log(`${name} is "${arg}"`);
|
||||
return arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if it looks like we're inside VS Code. This is used to prevent
|
||||
* accidentally building inside VS Code while developing which causes issues
|
||||
* because the watcher will try compiling those built files.
|
||||
*/
|
||||
private isInVscode(pathToCheck: string): boolean {
|
||||
let inside = false;
|
||||
const maybeVsCode = path.join(pathToCheck, "../../../");
|
||||
try {
|
||||
// If it has a package.json with the right name it's probably VS Code.
|
||||
inside = require(path.join(maybeVsCode, "package.json")).name === "code-oss-dev";
|
||||
} catch (error) {}
|
||||
this.log(
|
||||
inside
|
||||
? `Running inside VS Code ([${maybeVsCode}]${path.relative(maybeVsCode, pathToCheck)})`
|
||||
: "Not running inside VS Code"
|
||||
);
|
||||
return inside;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build code-server within VS Code.
|
||||
*/
|
||||
private async build(vscodeSourcePath: string, vscodeVersion: string, codeServerVersion: string, finalBuildPath: string): Promise<void> {
|
||||
// Install dependencies (should be cached by CI).
|
||||
await this.task("Installing code-server dependencies", async () => {
|
||||
await util.promisify(cp.exec)("yarn", { cwd: this.rootPath });
|
||||
});
|
||||
|
||||
// Download and prepare VS Code if necessary (should be cached by CI).
|
||||
if (fs.existsSync(vscodeSourcePath)) {
|
||||
this.log("Using existing VS Code clone");
|
||||
} else {
|
||||
await this.task("Cloning VS Code", () => {
|
||||
return util.promisify(cp.exec)(
|
||||
"git clone https://github.com/microsoft/vscode"
|
||||
+ ` --quiet --branch "${vscodeVersion}"`
|
||||
+ ` --single-branch --depth=1 "${vscodeSourcePath}"`);
|
||||
});
|
||||
}
|
||||
|
||||
if (fs.existsSync(path.join(vscodeSourcePath, "node_modules"))) {
|
||||
this.log("Using existing VS Code node_modules");
|
||||
} else {
|
||||
await this.task("Installing VS Code dependencies", () => {
|
||||
return util.promisify(cp.exec)("yarn", { cwd: vscodeSourcePath });
|
||||
});
|
||||
}
|
||||
|
||||
if (fs.existsSync(path.join(vscodeSourcePath, ".build/extensions"))) {
|
||||
this.log("Using existing built-in-extensions");
|
||||
} else {
|
||||
await this.task("Building default extensions", () => {
|
||||
return util.promisify(cp.exec)(
|
||||
"yarn gulp compile-extensions-build --max-old-space-size=32384",
|
||||
{ cwd: vscodeSourcePath },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Clean before patching or it could fail if already patched.
|
||||
await this.task("Patching VS Code", async () => {
|
||||
await util.promisify(cp.exec)("git reset --hard", { cwd: vscodeSourcePath });
|
||||
await util.promisify(cp.exec)("git clean -fd", { cwd: vscodeSourcePath });
|
||||
await util.promisify(cp.exec)(`git apply ${this.rootPath}/scripts/vscode.patch`, { cwd: vscodeSourcePath });
|
||||
});
|
||||
|
||||
const serverPath = path.join(vscodeSourcePath, "src/vs/server");
|
||||
await this.task("Copying code-server into VS Code", async () => {
|
||||
await fs.remove(serverPath);
|
||||
await fs.mkdirp(serverPath);
|
||||
await Promise.all(["main.js", "node_modules", "src", "typings"].map((fileName) => {
|
||||
return fs.copy(path.join(this.rootPath, fileName), path.join(serverPath, fileName));
|
||||
}));
|
||||
});
|
||||
|
||||
await this.task("Building VS Code", () => {
|
||||
return util.promisify(cp.exec)("yarn gulp compile-build --max-old-space-size=32384", { cwd: vscodeSourcePath });
|
||||
});
|
||||
|
||||
await this.task("Optimizing VS Code", async () => {
|
||||
await fs.copyFile(path.join(this.rootPath, "scripts/optimize.js"), path.join(vscodeSourcePath, "coder.js"));
|
||||
await util.promisify(cp.exec)(`yarn gulp optimize --max-old-space-size=32384 --gulpfile ./coder.js`, { cwd: vscodeSourcePath });
|
||||
});
|
||||
|
||||
const { productJson, packageJson } = await this.task("Generating final package.json and product.json", async () => {
|
||||
const merge = async (name: string, extraJson: { [key: string]: string } = {}): Promise<{ [key: string]: string }> => {
|
||||
const [aJson, bJson] = (await Promise.all([
|
||||
fs.readFile(path.join(vscodeSourcePath, `${name}.json`), "utf8"),
|
||||
fs.readFile(path.join(this.rootPath, `scripts/${name}.json`), "utf8"),
|
||||
])).map((raw) => {
|
||||
const json = JSON.parse(raw);
|
||||
delete json.scripts;
|
||||
delete json.dependencies;
|
||||
delete json.devDependencies;
|
||||
delete json.optionalDependencies;
|
||||
return json;
|
||||
});
|
||||
|
||||
return { ...aJson, ...bJson, ...extraJson };
|
||||
};
|
||||
|
||||
const date = new Date().toISOString();
|
||||
const commit = require(path.join(vscodeSourcePath, "build/lib/util")).getVersion(this.rootPath);
|
||||
|
||||
const [productJson, packageJson] = await Promise.all([
|
||||
merge("product", { commit, date }),
|
||||
merge("package", { codeServerVersion: `${codeServerVersion}-vsc${vscodeVersion}` }),
|
||||
]);
|
||||
|
||||
// We could do this before the optimization but then it'd be copied into
|
||||
// three files and unused in two which seems like a waste of bytes.
|
||||
const apiPath = path.join(vscodeSourcePath, "out-vscode/vs/workbench/workbench.web.api.js");
|
||||
await fs.writeFile(apiPath, (await fs.readFile(apiPath, "utf8")).replace('{ /*BUILD->INSERT_PRODUCT_CONFIGURATION*/}', JSON.stringify({
|
||||
version: packageJson.version,
|
||||
codeServerVersion: packageJson.codeServerVersion,
|
||||
...productJson,
|
||||
})));
|
||||
|
||||
return { productJson, packageJson };
|
||||
});
|
||||
|
||||
if (process.env.MINIFY) {
|
||||
await this.task("Minifying VS Code", () => {
|
||||
return util.promisify(cp.exec)("yarn gulp minify --max-old-space-size=32384 --gulpfile ./coder.js", { cwd: vscodeSourcePath });
|
||||
});
|
||||
}
|
||||
|
||||
const finalServerPath = path.join(finalBuildPath, "out/vs/server");
|
||||
await this.task("Copying into final build directory", async () => {
|
||||
await fs.remove(finalBuildPath);
|
||||
await fs.mkdirp(finalBuildPath);
|
||||
await Promise.all([
|
||||
fs.copy(path.join(vscodeSourcePath, "remote/node_modules"), path.join(finalBuildPath, "node_modules")),
|
||||
fs.copy(path.join(vscodeSourcePath, ".build/extensions"), path.join(finalBuildPath, "extensions")),
|
||||
fs.copy(path.join(vscodeSourcePath, `out-vscode${process.env.MINIFY ? "-min" : ""}`), path.join(finalBuildPath, "out")).then(() => {
|
||||
return Promise.all([
|
||||
fs.remove(path.join(finalServerPath, "node_modules")).then(() => {
|
||||
return fs.copy(path.join(serverPath, "node_modules"), path.join(finalServerPath, "node_modules"));
|
||||
}),
|
||||
fs.copy(path.join(finalServerPath, "src/browser/workbench-build.html"), path.join(finalServerPath, "src/browser/workbench.html")),
|
||||
]);
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
if (process.env.MINIFY) {
|
||||
await this.task("Restricting to production dependencies", async () => {
|
||||
await Promise.all(["package.json", "yarn.lock"].map((fileName) => {
|
||||
Promise.all([
|
||||
fs.copy(path.join(this.rootPath, fileName), path.join(finalServerPath, fileName)),
|
||||
fs.copy(path.join(path.join(vscodeSourcePath, "remote"), fileName), path.join(finalBuildPath, fileName)),
|
||||
]);
|
||||
}));
|
||||
|
||||
await Promise.all([finalServerPath, finalBuildPath].map((cwd) => {
|
||||
return util.promisify(cp.exec)("yarn --production", { cwd });
|
||||
}));
|
||||
|
||||
await Promise.all(["package.json", "yarn.lock"].map((fileName) => {
|
||||
return Promise.all([
|
||||
fs.remove(path.join(finalServerPath, fileName)),
|
||||
fs.remove(path.join(finalBuildPath, fileName)),
|
||||
]);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
await this.task("Writing final package.json and product.json", () => {
|
||||
return Promise.all([
|
||||
fs.writeFile(path.join(finalBuildPath, "package.json"), JSON.stringify(packageJson, null, 2)),
|
||||
fs.writeFile(path.join(finalBuildPath, "product.json"), JSON.stringify(productJson, null, 2)),
|
||||
]);
|
||||
});
|
||||
|
||||
// Prevent needless cache changes.
|
||||
await this.task("Cleaning for smaller cache", () => {
|
||||
return Promise.all([
|
||||
fs.remove(serverPath),
|
||||
fs.remove(path.join(vscodeSourcePath, "out-vscode")),
|
||||
fs.remove(path.join(vscodeSourcePath, "out-vscode-min")),
|
||||
fs.remove(path.join(vscodeSourcePath, "out-build")),
|
||||
util.promisify(cp.exec)("git reset --hard", { cwd: vscodeSourcePath }).then(() => {
|
||||
return util.promisify(cp.exec)("git clean -fd", { cwd: vscodeSourcePath });
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
// Prepend code to the target which enables finding files within the binary.
|
||||
const prependLoader = async (relativeFilePath: string): Promise<void> => {
|
||||
const filePath = path.join(finalBuildPath, relativeFilePath);
|
||||
const shim = `
|
||||
if (!global.NBIN_LOADED) {
|
||||
try {
|
||||
const nbin = require("nbin");
|
||||
nbin.shimNativeFs("${finalBuildPath}");
|
||||
global.NBIN_LOADED = true;
|
||||
const path = require("path");
|
||||
const rg = require("vscode-ripgrep");
|
||||
rg.binaryRgPath = rg.rgPath;
|
||||
rg.rgPath = path.join(require("os").tmpdir(), "code-server", path.basename(rg.binaryRgPath));
|
||||
} catch (error) { /* Not in the binary. */ }
|
||||
}
|
||||
`;
|
||||
await fs.writeFile(filePath, shim + (await fs.readFile(filePath, "utf8")));
|
||||
};
|
||||
|
||||
await this.task("Prepending nbin loader", () => {
|
||||
return Promise.all([
|
||||
prependLoader("out/vs/server/main.js"),
|
||||
prependLoader("out/bootstrap-fork.js"),
|
||||
prependLoader("extensions/node_modules/typescript/lib/tsserver.js"),
|
||||
]);
|
||||
});
|
||||
|
||||
// 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
|
||||
await this.task("Applying onigasm PHP fix", async () => {
|
||||
const onigasmPath = path.join(finalBuildPath, "node_modules/onigasm-umd");
|
||||
const onigasmTmpPath = `${onigasmPath}-temp`;
|
||||
await Promise.all([
|
||||
fs.remove(onigasmPath),
|
||||
fs.mkdir(onigasmTmpPath),
|
||||
]);
|
||||
await util.promisify(cp.exec)(`git clone "https://github.com/alexandrudima/onigasm-umd" "${onigasmPath}"`);
|
||||
await util.promisify(cp.exec)("yarn", { cwd: onigasmPath });
|
||||
await util.promisify(cp.exec)("yarn add --dev onigasm@2.2.1", { cwd: onigasmPath });
|
||||
await util.promisify(cp.exec)("yarn package", { cwd: onigasmPath });
|
||||
await Promise.all(["release", "LICENSE", "package.json"].map((fileName) => {
|
||||
return fs.copy(path.join(onigasmPath, fileName), path.join(onigasmTmpPath, fileName));
|
||||
}));
|
||||
await fs.remove(onigasmPath);
|
||||
await fs.move(onigasmTmpPath, onigasmPath);
|
||||
});
|
||||
|
||||
this.log(`Final build: ${finalBuildPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundles the built code into a binary.
|
||||
*/
|
||||
private async binary(targetPath: string, binariesPath: string, binaryName: string): Promise<void> {
|
||||
const bin = new Binary({
|
||||
mainFile: path.join(targetPath, "out/vs/server/main.js"),
|
||||
target: await this.target(),
|
||||
});
|
||||
|
||||
bin.writeFiles(path.join(targetPath, "**"));
|
||||
|
||||
await fs.mkdirp(binariesPath);
|
||||
|
||||
const binaryPath = path.join(binariesPath, binaryName);
|
||||
await fs.writeFile(binaryPath, await bin.build());
|
||||
await fs.chmod(binaryPath, "755");
|
||||
|
||||
this.log(`Binary: ${binaryPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Package the binary into a release archive.
|
||||
*/
|
||||
private async package(vscodeSourcePath: string, binariesPath: string, binaryName: string): Promise<void> {
|
||||
const releasePath = path.join(this.outPath, "release");
|
||||
const archivePath = path.join(releasePath, binaryName);
|
||||
|
||||
await fs.remove(archivePath);
|
||||
await fs.mkdirp(archivePath);
|
||||
|
||||
await fs.copyFile(path.join(binariesPath, binaryName), path.join(archivePath, "code-server"));
|
||||
await fs.copyFile(path.join(this.rootPath, "README.md"), path.join(archivePath, "README.md"));
|
||||
await fs.copyFile(path.join(vscodeSourcePath, "LICENSE.txt"), path.join(archivePath, "LICENSE.txt"));
|
||||
await fs.copyFile(path.join(vscodeSourcePath, "ThirdPartyNotices.txt"), path.join(archivePath, "ThirdPartyNotices.txt"));
|
||||
|
||||
if ((await this.target()) === "darwin") {
|
||||
await util.promisify(cp.exec)(`zip -r "${binaryName}.zip" "${binaryName}"`, { cwd: releasePath });
|
||||
this.log(`Archive: ${archivePath}.zip`);
|
||||
} else {
|
||||
await util.promisify(cp.exec)(`tar -czf "${binaryName}.tar.gz" "${binaryName}"`, { cwd: releasePath });
|
||||
this.log(`Archive: ${archivePath}.tar.gz`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const builder = new Builder();
|
||||
builder.run(process.argv[2] as Task, process.argv.slice(3));
|
||||
@@ -1,79 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
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
|
||||
# Use a mount so we can cache the results.
|
||||
containerId=$(docker create --network=host --rm -it -v "$(pwd)":/src "${image}")
|
||||
docker start "${containerId}"
|
||||
|
||||
# TODO: Might be better to move these dependencies to the images or create new
|
||||
# ones on top of these.
|
||||
if [[ "${image}" == "codercom/nbin-alpine" ]] ; then
|
||||
docker exec "${containerId}" apk add libxkbfile-dev libsecret-dev
|
||||
else
|
||||
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-exec build
|
||||
if [[ -n "${package}" ]] ; then
|
||||
docker-exec binary
|
||||
docker-exec package
|
||||
fi
|
||||
|
||||
docker kill "${containerId}"
|
||||
}
|
||||
|
||||
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,38 +0,0 @@
|
||||
# We deploy with ubuntu so that devs have a familiar environment.
|
||||
FROM ubuntu:18.04
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
openssl \
|
||||
net-tools \
|
||||
git \
|
||||
locales \
|
||||
sudo \
|
||||
dumb-init \
|
||||
vim \
|
||||
curl \
|
||||
wget
|
||||
|
||||
RUN locale-gen en_US.UTF-8
|
||||
# We cannot use update-locale because docker will not use the env variables
|
||||
# configured in /etc/default/locale so we need to set it manually.
|
||||
ENV LC_ALL=en_US.UTF-8 \
|
||||
SHELL=/bin/bash
|
||||
|
||||
RUN adduser --gecos '' --disabled-password coder && \
|
||||
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
|
||||
|
||||
USER coder
|
||||
# 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 ./binaries/code-server* /usr/local/bin/code-server
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["dumb-init", "code-server", "--host", "0.0.0.0"]
|
||||
@@ -1,71 +0,0 @@
|
||||
// This must be ran from VS Code's root.
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const _ = require("underscore");
|
||||
const buildfile = require("./src/buildfile");
|
||||
const common = require("./build/lib/optimize");
|
||||
const util = require("./build/lib/util");
|
||||
const deps = require("./build/dependencies");
|
||||
|
||||
const vscodeEntryPoints = _.flatten([
|
||||
buildfile.entrypoint("vs/workbench/workbench.web.api"),
|
||||
buildfile.entrypoint("vs/server/src/node/cli"),
|
||||
buildfile.base,
|
||||
buildfile.workbenchWeb,
|
||||
buildfile.workerExtensionHost,
|
||||
buildfile.keyboardMaps,
|
||||
buildfile.entrypoint('vs/platform/files/node/watcher/unix/watcherApp', ["vs/css", "vs/nls"]),
|
||||
buildfile.entrypoint('vs/platform/files/node/watcher/nsfw/watcherApp', ["vs/css", "vs/nls"]),
|
||||
buildfile.entrypoint('vs/workbench/services/extensions/node/extensionHostProcess', ["vs/css", "vs/nls"]),
|
||||
]);
|
||||
|
||||
const vscodeResources = [
|
||||
"out-build/vs/server/main.js",
|
||||
"out-build/vs/server/src/node/uriTransformer.js",
|
||||
"!out-build/vs/server/doc/**",
|
||||
"out-build/vs/server/src/media/*",
|
||||
"out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js",
|
||||
"out-build/bootstrap.js",
|
||||
"out-build/bootstrap-fork.js",
|
||||
"out-build/bootstrap-amd.js",
|
||||
"out-build/paths.js",
|
||||
'out-build/vs/**/*.{svg,png,html}',
|
||||
"!out-build/vs/code/browser/workbench/*.html",
|
||||
'!out-build/vs/code/electron-browser/**',
|
||||
"out-build/vs/base/common/performance.js",
|
||||
"out-build/vs/base/node/languagePacks.js",
|
||||
"out-build/vs/base/browser/ui/octiconLabel/octicons/**",
|
||||
"out-build/vs/base/browser/ui/codiconLabel/codicon/**",
|
||||
"out-build/vs/workbench/browser/media/*-theme.css",
|
||||
"out-build/vs/workbench/contrib/debug/**/*.json",
|
||||
"out-build/vs/workbench/contrib/externalTerminal/**/*.scpt",
|
||||
"out-build/vs/workbench/contrib/webview/browser/pre/*.js",
|
||||
"out-build/vs/**/markdown.css",
|
||||
"out-build/vs/workbench/contrib/tasks/**/*.json",
|
||||
"out-build/vs/platform/files/**/*.md",
|
||||
"!**/test/**"
|
||||
];
|
||||
|
||||
const rootPath = __dirname;
|
||||
const nodeModules = ["electron", "original-fs"]
|
||||
.concat(_.uniq(deps.getProductionDependencies(rootPath).map((d) => d.name)))
|
||||
.concat(_.uniq(deps.getProductionDependencies(path.join(rootPath, "src/vs/server")).map((d) => d.name)))
|
||||
.concat(Object.keys(process.binding("natives")).filter((n) => !/^_|\//.test(n)));
|
||||
|
||||
gulp.task("optimize", gulp.series(
|
||||
util.rimraf("out-vscode"),
|
||||
common.optimizeTask({
|
||||
src: "out-build",
|
||||
entryPoints: vscodeEntryPoints,
|
||||
resources: vscodeResources,
|
||||
loaderConfig: common.loaderConfig(nodeModules),
|
||||
out: "out-vscode",
|
||||
inlineAmdImages: true,
|
||||
bundleInfo: undefined
|
||||
}),
|
||||
));
|
||||
|
||||
gulp.task("minify", gulp.series(
|
||||
util.rimraf("out-vscode-min"),
|
||||
common.minifyTask("out-vscode")
|
||||
));
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "code-server",
|
||||
"main": "out/vs/server/main",
|
||||
"desktopName": "code-server-url-handler.desktop"
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"nameShort": "code-server",
|
||||
"nameLong": "code-server",
|
||||
"applicationName": "code-server",
|
||||
"dataFolderName": ".code-server",
|
||||
"win32MutexName": "codeserver",
|
||||
"win32DirName": "Code Server",
|
||||
"win32NameVersion": "Code Server",
|
||||
"win32RegValueName": "CodeServer",
|
||||
"win32AppId": "",
|
||||
"win32x64AppId": "",
|
||||
"win32UserAppId": "",
|
||||
"win32x64UserAppId": "",
|
||||
"win32AppUserModelId": "CodeServer",
|
||||
"win32ShellNameShort": "C&ode Server",
|
||||
"darwinBundleIdentifier": "com.code.server",
|
||||
"linuxIconName": "com.code.server",
|
||||
"urlProtocol": "code-server",
|
||||
"updateUrl": "https://api.github.com/repos/cdr/code-server/releases",
|
||||
"quality": "latest"
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"baseUrl": ".",
|
||||
"target": "esnext"
|
||||
}
|
||||
}
|
||||
@@ -1,900 +0,0 @@
|
||||
diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts
|
||||
index 6d41e85e42..64f39687a4 100644
|
||||
--- a/src/vs/base/common/network.ts
|
||||
+++ b/src/vs/base/common/network.ts
|
||||
@@ -96,12 +96,12 @@ class RemoteAuthoritiesImpl {
|
||||
if (host && host.indexOf(':') !== -1) {
|
||||
host = `[${host}]`;
|
||||
}
|
||||
- const port = this._ports[authority];
|
||||
+ // NOTE@coder: Changed this to work against the current path.
|
||||
const connectionToken = this._connectionTokens[authority];
|
||||
return URI.from({
|
||||
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
|
||||
- authority: `${host}:${port}`,
|
||||
- path: `/vscode-remote-resource`,
|
||||
+ authority: window.location.host,
|
||||
+ path: `${window.location.pathname.replace(/\/+$/, '')}/vscode-remote-resource`,
|
||||
query: `path=${encodeURIComponent(uri.path)}&tkn=${encodeURIComponent(connectionToken)}`
|
||||
});
|
||||
}
|
||||
diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts
|
||||
index a657f4a4d9..66bd13dffa 100644
|
||||
--- a/src/vs/base/common/platform.ts
|
||||
+++ b/src/vs/base/common/platform.ts
|
||||
@@ -56,6 +56,16 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
|
||||
_isWeb = true;
|
||||
_locale = navigator.language;
|
||||
_language = _locale;
|
||||
+ const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
|
||||
+ const rawNlsConfig = el && el.getAttribute('data-settings');
|
||||
+ if (rawNlsConfig) {
|
||||
+ try {
|
||||
+ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
|
||||
+ _locale = nlsConfig.locale;
|
||||
+ _translationsConfigFile = nlsConfig._translationsConfigFile;
|
||||
+ _language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT;
|
||||
+ } catch (error) { /* Oh well. */ }
|
||||
+ }
|
||||
} else if (typeof process === 'object') {
|
||||
_isWindows = (process.platform === 'win32');
|
||||
_isMacintosh = (process.platform === 'darwin');
|
||||
diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts
|
||||
index c52f7b3774..5635cfac8a 100644
|
||||
--- a/src/vs/base/common/processes.ts
|
||||
+++ b/src/vs/base/common/processes.ts
|
||||
@@ -110,7 +110,9 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve
|
||||
/^ELECTRON_.+$/,
|
||||
/^GOOGLE_API_KEY$/,
|
||||
/^VSCODE_.+$/,
|
||||
- /^SNAP(|_.*)$/
|
||||
+ /^SNAP(|_.*)$/,
|
||||
+ /^NBIN_BYPASS$/,
|
||||
+ /^LAUNCH_VSCODE$/
|
||||
];
|
||||
const envKeys = Object.keys(env);
|
||||
envKeys
|
||||
diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js
|
||||
index 3ae24454cb..fac8679290 100644
|
||||
--- a/src/vs/base/node/languagePacks.js
|
||||
+++ b/src/vs/base/node/languagePacks.js
|
||||
@@ -146,7 +146,10 @@ function factory(nodeRequire, path, fs, perf) {
|
||||
function getLanguagePackConfigurations(userDataPath) {
|
||||
const configFile = path.join(userDataPath, 'languagepacks.json');
|
||||
try {
|
||||
- return nodeRequire(configFile);
|
||||
+ // NOTE@coder: Swapped require with readFile since require is cached and
|
||||
+ // we don't restart the server-side portion of code-server when the
|
||||
+ // language changes.
|
||||
+ return JSON.parse(fs.readFileSync(configFile, "utf8"));
|
||||
} catch (err) {
|
||||
// Do nothing. If we can't read the file we have no
|
||||
// language pack config.
|
||||
diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts
|
||||
index 990755c4f3..06449bb9cb 100644
|
||||
--- a/src/vs/platform/environment/common/environment.ts
|
||||
+++ b/src/vs/platform/environment/common/environment.ts
|
||||
@@ -36,6 +36,8 @@ export interface ParsedArgs {
|
||||
logExtensionHostCommunication?: boolean;
|
||||
'extensions-dir'?: string;
|
||||
'builtin-extensions-dir'?: string;
|
||||
+ 'extra-extensions-dir'?: string[];
|
||||
+ 'extra-builtin-extensions-dir'?: string[];
|
||||
extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs
|
||||
extensionTestsPath?: string; // either a local path or a URI
|
||||
'extension-development-confirm-save'?: boolean;
|
||||
@@ -169,4 +171,6 @@ export interface IEnvironmentService {
|
||||
driverVerbose: boolean;
|
||||
|
||||
galleryMachineIdResource?: URI;
|
||||
+ extraExtensionPaths: string[];
|
||||
+ extraBuiltinExtensionPaths: string[];
|
||||
}
|
||||
diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts
|
||||
index 3e48fe4ddd..2212ff5471 100644
|
||||
--- a/src/vs/platform/environment/node/argv.ts
|
||||
+++ b/src/vs/platform/environment/node/argv.ts
|
||||
@@ -58,6 +58,8 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
||||
|
||||
'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
|
||||
'builtin-extensions-dir': { type: 'string' },
|
||||
+ 'extra-builtin-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra builtin extension directory.' },
|
||||
+ 'extra-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra user extension directory.' },
|
||||
'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
|
||||
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
|
||||
'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") },
|
||||
@@ -185,7 +187,7 @@ export function parseArgs<T>(args: string[], options: OptionDescriptions<T>, err
|
||||
delete parsedArgs[o.deprecates];
|
||||
}
|
||||
|
||||
- if (val) {
|
||||
+ if (typeof val !== 'undefined') {
|
||||
if (o.type === 'string[]') {
|
||||
if (val && !Array.isArray(val)) {
|
||||
val = [val];
|
||||
diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts
|
||||
index f7d207009d..5c37b52dab 100644
|
||||
--- a/src/vs/platform/environment/node/environmentService.ts
|
||||
+++ b/src/vs/platform/environment/node/environmentService.ts
|
||||
@@ -260,6 +260,12 @@ export class EnvironmentService implements IEnvironmentService {
|
||||
|
||||
get driverHandle(): string | undefined { return this._args['driver']; }
|
||||
get driverVerbose(): boolean { return !!this._args['driver-verbose']; }
|
||||
+ @memoize get extraExtensionPaths(): string[] {
|
||||
+ return (this._args['extra-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
|
||||
+ }
|
||||
+ @memoize get extraBuiltinExtensionPaths(): string[] {
|
||||
+ return (this._args['extra-builtin-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
|
||||
+ }
|
||||
|
||||
constructor(private _args: ParsedArgs, private _execPath: string) {
|
||||
if (!process.env['VSCODE_LOGS']) {
|
||||
diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
|
||||
index f0eaa74a59..3abf9e1752 100644
|
||||
--- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts
|
||||
+++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
|
||||
@@ -731,11 +731,15 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
|
||||
private scanSystemExtensions(): Promise<ILocalExtension[]> {
|
||||
this.logService.trace('Started scanning system extensions');
|
||||
- const systemExtensionsPromise = this.scanExtensions(this.systemExtensionsPath, ExtensionType.System)
|
||||
- .then(result => {
|
||||
- this.logService.trace('Scanned system extensions:', result.length);
|
||||
- return result;
|
||||
- });
|
||||
+ const systemExtensionsPromise = Promise.all([
|
||||
+ this.scanExtensions(this.systemExtensionsPath, ExtensionType.System),
|
||||
+ ...this.environmentService.extraBuiltinExtensionPaths
|
||||
+ .map((path) => this.scanExtensions(path, ExtensionType.System))
|
||||
+ ]).then((results) => {
|
||||
+ const result = results.reduce((flat, current) => flat.concat(current), []);
|
||||
+ this.logService.trace('Scanned system extensions:', result.length);
|
||||
+ return result;
|
||||
+ });
|
||||
if (this.environmentService.isBuilt) {
|
||||
return systemExtensionsPromise;
|
||||
}
|
||||
@@ -757,9 +761,16 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
.then(([systemExtensions, devSystemExtensions]) => [...systemExtensions, ...devSystemExtensions]);
|
||||
}
|
||||
|
||||
+ private scanAllUserExtensions(folderName: string, type: ExtensionType): Promise<ILocalExtension[]> {
|
||||
+ return Promise.all([
|
||||
+ this.scanExtensions(folderName, type),
|
||||
+ ...this.environmentService.extraExtensionPaths.map((p) => this.scanExtensions(p, ExtensionType.User))
|
||||
+ ]).then((results) => results.reduce((flat, current) => flat.concat(current), []));
|
||||
+ }
|
||||
+
|
||||
private scanUserExtensions(excludeOutdated: boolean): Promise<ILocalExtension[]> {
|
||||
this.logService.trace('Started scanning user extensions');
|
||||
- return Promise.all([this.getUninstalledExtensions(), this.scanExtensions(this.extensionsPath, ExtensionType.User)])
|
||||
+ return Promise.all([this.getUninstalledExtensions(), this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User)])
|
||||
.then(([uninstalled, extensions]) => {
|
||||
extensions = extensions.filter(e => !uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]);
|
||||
if (excludeOutdated) {
|
||||
@@ -774,6 +785,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
private scanExtensions(root: string, type: ExtensionType): Promise<ILocalExtension[]> {
|
||||
const limiter = new Limiter<any>(10);
|
||||
return pfs.readdir(root)
|
||||
+ .catch((error) => {
|
||||
+ if (error.code !== 'ENOENT') {
|
||||
+ throw error;
|
||||
+ }
|
||||
+ return [];
|
||||
+ })
|
||||
.then(extensionsFolders => Promise.all<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
|
||||
.then(extensions => extensions.filter(e => e && e.identifier));
|
||||
}
|
||||
@@ -812,7 +829,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
|
||||
private async removeUninstalledExtensions(): Promise<void> {
|
||||
const uninstalled = await this.getUninstalledExtensions();
|
||||
- const extensions = await this.scanExtensions(this.extensionsPath, ExtensionType.User); // All user extensions
|
||||
+ const extensions = await this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User); // All user extensions
|
||||
const installed: Set<string> = new Set<string>();
|
||||
for (const e of extensions) {
|
||||
if (!uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]) {
|
||||
@@ -831,7 +848,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
}
|
||||
|
||||
private removeOutdatedExtensions(): Promise<void> {
|
||||
- return this.scanExtensions(this.extensionsPath, ExtensionType.User) // All user extensions
|
||||
+ return this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User) // All user extensions
|
||||
.then(extensions => {
|
||||
const toRemove: ILocalExtension[] = [];
|
||||
|
||||
diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts
|
||||
index 2de51e8d32..837770990e 100644
|
||||
--- a/src/vs/platform/product/common/product.ts
|
||||
+++ b/src/vs/platform/product/common/product.ts
|
||||
@@ -22,10 +22,16 @@ if (isWeb) {
|
||||
if (Object.keys(product).length === 0) {
|
||||
assign(product, {
|
||||
version: '1.39.0-dev',
|
||||
+ codeServerVersion: 'dev',
|
||||
nameLong: 'Visual Studio Code Web Dev',
|
||||
nameShort: 'VSCode Web Dev'
|
||||
});
|
||||
}
|
||||
+ const el = document.getElementById('vscode-remote-product-configuration');
|
||||
+ const rawProductConfiguration = el && el.getAttribute('data-settings');
|
||||
+ if (rawProductConfiguration) {
|
||||
+ assign(product, JSON.parse(rawProductConfiguration));
|
||||
+ }
|
||||
}
|
||||
|
||||
// Node: AMD loader
|
||||
@@ -35,7 +41,7 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === '
|
||||
const rootPath = path.dirname(getPathFromAmdModule(require, ''));
|
||||
|
||||
product = assign({}, require.__$__nodeRequire(path.join(rootPath, 'product.json')) as IProductConfiguration);
|
||||
- const pkg = require.__$__nodeRequire(path.join(rootPath, 'package.json')) as { version: string; };
|
||||
+ const pkg = require.__$__nodeRequire(path.join(rootPath, 'package.json')) as { version: string; codeServerVersion: string; };
|
||||
|
||||
// Running out of sources
|
||||
if (env['VSCODE_DEV']) {
|
||||
@@ -47,7 +53,8 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === '
|
||||
}
|
||||
|
||||
assign(product, {
|
||||
- version: pkg.version
|
||||
+ version: pkg.version,
|
||||
+ codeServerVersion: pkg.codeServerVersion,
|
||||
});
|
||||
}
|
||||
|
||||
diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts
|
||||
index 5aa5c32d7e..e4e7fd4174 100644
|
||||
--- a/src/vs/platform/product/common/productService.ts
|
||||
+++ b/src/vs/platform/product/common/productService.ts
|
||||
@@ -15,6 +15,7 @@ export interface IProductService extends Readonly<IProductConfiguration> {
|
||||
|
||||
export interface IProductConfiguration {
|
||||
readonly version: string;
|
||||
+ readonly codeServerVersion: string;
|
||||
readonly date?: string;
|
||||
readonly quality?: string;
|
||||
readonly commit?: string;
|
||||
diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts
|
||||
index d0f6e6b18a..1966fd297d 100644
|
||||
--- a/src/vs/platform/remote/browser/browserSocketFactory.ts
|
||||
+++ b/src/vs/platform/remote/browser/browserSocketFactory.ts
|
||||
@@ -205,7 +205,8 @@ export class BrowserSocketFactory implements ISocketFactory {
|
||||
}
|
||||
|
||||
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
|
||||
- const socket = this._webSocketFactory.create(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`);
|
||||
+ // NOTE@coder: Modified to work against the current path.
|
||||
+ const socket = this._webSocketFactory.create(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}${window.location.pathname}?${query}&skipWebSocketFrames=false`);
|
||||
const errorListener = socket.onError((err) => callback(err, undefined));
|
||||
socket.onOpen(() => {
|
||||
errorListener.dispose();
|
||||
@@ -213,6 +214,3 @@ export class BrowserSocketFactory implements ISocketFactory {
|
||||
});
|
||||
}
|
||||
}
|
||||
-
|
||||
-
|
||||
-
|
||||
diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts
|
||||
index 8a1c95d37b..8225a85d47 100644
|
||||
--- a/src/vs/platform/update/electron-main/abstractUpdateService.ts
|
||||
+++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts
|
||||
@@ -6,7 +6,6 @@
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/configuration/common/configuration';
|
||||
-import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -44,7 +43,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
||||
}
|
||||
|
||||
constructor(
|
||||
- @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
+ _: any, // NOTE@coder: This depends on Electron so we skip it.
|
||||
@IConfigurationService protected configurationService: IConfigurationService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IRequestService protected requestService: IRequestService,
|
||||
@@ -152,15 +151,8 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
||||
|
||||
this.logService.trace('update#quitAndInstall(): before lifecycle quit()');
|
||||
|
||||
- this.lifecycleMainService.quit(true /* from update */).then(vetod => {
|
||||
- this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`);
|
||||
- if (vetod) {
|
||||
- return;
|
||||
- }
|
||||
-
|
||||
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
|
||||
this.doQuitAndInstall();
|
||||
- });
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts
|
||||
index 2905c52411..303ddf211f 100644
|
||||
--- a/src/vs/workbench/api/browser/extensionHost.contribution.ts
|
||||
+++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts
|
||||
@@ -57,6 +57,7 @@ import './mainThreadComments';
|
||||
import './mainThreadTask';
|
||||
import './mainThreadLabelService';
|
||||
import 'vs/workbench/api/common/apiCommands';
|
||||
+import 'vs/server/src/browser/mainThreadNodeProxy';
|
||||
|
||||
export class ExtensionPoints implements IWorkbenchContribution {
|
||||
|
||||
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
|
||||
index 230d09a290..84753e6ac7 100644
|
||||
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
|
||||
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
|
||||
@@ -67,6 +67,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
+import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
|
||||
|
||||
export interface IExtensionApiFactory {
|
||||
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
|
||||
@@ -86,6 +87,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const rpcProtocol = accessor.get(IExtHostRpcService);
|
||||
const extHostStorage = accessor.get(IExtHostStorage);
|
||||
const extHostLogService = accessor.get(ILogService);
|
||||
+ const extHostNodeProxy = accessor.get(IExtHostNodeProxy);
|
||||
|
||||
// register addressable instances
|
||||
rpcProtocol.set(ExtHostContext.ExtHostLogService, <ExtHostLogServiceShape><any>extHostLogService);
|
||||
@@ -93,6 +95,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage);
|
||||
+ rpcProtocol.set(ExtHostContext.ExtHostNodeProxy, extHostNodeProxy);
|
||||
|
||||
// automatically create and register addressable instances
|
||||
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations));
|
||||
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
|
||||
index 7bd6aa6cb7..7c28136366 100644
|
||||
--- a/src/vs/workbench/api/common/extHost.protocol.ts
|
||||
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
|
||||
@@ -627,6 +627,10 @@ export interface MainThreadLabelServiceShape extends IDisposable {
|
||||
$unregisterResourceLabelFormatter(handle: number): void;
|
||||
}
|
||||
|
||||
+export interface MainThreadNodeProxyShape extends IDisposable {
|
||||
+ $send(message: string): void;
|
||||
+}
|
||||
+
|
||||
export interface MainThreadSearchShape extends IDisposable {
|
||||
$registerFileSearchProvider(handle: number, scheme: string): void;
|
||||
$registerTextSearchProvider(handle: number, scheme: string): void;
|
||||
@@ -860,6 +864,13 @@ export interface ExtHostLabelServiceShape {
|
||||
$registerResourceLabelFormatter(formatter: ResourceLabelFormatter): IDisposable;
|
||||
}
|
||||
|
||||
+export interface ExtHostNodeProxyShape {
|
||||
+ $onMessage(message: string): void;
|
||||
+ $onClose(): void;
|
||||
+ $onDown(): void;
|
||||
+ $onUp(): void;
|
||||
+}
|
||||
+
|
||||
export interface ExtHostSearchShape {
|
||||
$provideFileSearchResults(handle: number, session: number, query: search.IRawQuery, token: CancellationToken): Promise<search.ISearchCompleteStats>;
|
||||
$provideTextSearchResults(handle: number, session: number, query: search.IRawTextQuery, token: CancellationToken): Promise<search.ISearchCompleteStats>;
|
||||
@@ -1384,7 +1395,8 @@ export const MainContext = {
|
||||
MainThreadSearch: createMainId<MainThreadSearchShape>('MainThreadSearch'),
|
||||
MainThreadTask: createMainId<MainThreadTaskShape>('MainThreadTask'),
|
||||
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
|
||||
- MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService')
|
||||
+ MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService'),
|
||||
+ MainThreadNodeProxy: createMainId<MainThreadNodeProxyShape>('MainThreadNodeProxy')
|
||||
};
|
||||
|
||||
export const ExtHostContext = {
|
||||
@@ -1418,5 +1430,6 @@ export const ExtHostContext = {
|
||||
ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'),
|
||||
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'),
|
||||
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
|
||||
- ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService')
|
||||
+ ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
|
||||
+ ExtHostNodeProxy: createMainId<ExtHostNodeProxyShape>('ExtHostNodeProxy')
|
||||
};
|
||||
diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts
|
||||
index b5ce835e07..22be8516c1 100644
|
||||
--- a/src/vs/workbench/api/common/extHostExtensionService.ts
|
||||
+++ b/src/vs/workbench/api/common/extHostExtensionService.ts
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
-import { originalFSPath, joinPath } from 'vs/base/common/resources';
|
||||
+import { originalFSPath } from 'vs/base/common/resources';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
@@ -32,6 +32,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData
|
||||
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
+import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
|
||||
|
||||
interface ITestRunner {
|
||||
/** Old test runner API, as exported from `vscode/lib/testrunner` */
|
||||
@@ -76,6 +77,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
protected readonly _extHostWorkspace: ExtHostWorkspace;
|
||||
protected readonly _extHostConfiguration: ExtHostConfiguration;
|
||||
protected readonly _logService: ILogService;
|
||||
+ protected readonly _nodeProxy: IExtHostNodeProxy;
|
||||
|
||||
protected readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape;
|
||||
protected readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape;
|
||||
@@ -104,7 +106,8 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
@IExtHostConfiguration extHostConfiguration: IExtHostConfiguration,
|
||||
@ILogService logService: ILogService,
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService,
|
||||
- @IExtensionStoragePaths storagePath: IExtensionStoragePaths
|
||||
+ @IExtensionStoragePaths storagePath: IExtensionStoragePaths,
|
||||
+ @IExtHostNodeProxy nodeProxy: IExtHostNodeProxy,
|
||||
) {
|
||||
this._hostUtils = hostUtils;
|
||||
this._extHostContext = extHostContext;
|
||||
@@ -113,6 +116,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
this._extHostWorkspace = extHostWorkspace;
|
||||
this._extHostConfiguration = extHostConfiguration;
|
||||
this._logService = logService;
|
||||
+ this._nodeProxy = nodeProxy;
|
||||
this._disposables = new DisposableStore();
|
||||
|
||||
this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace);
|
||||
@@ -331,14 +335,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
|
||||
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
|
||||
return Promise.all([
|
||||
- this._loadCommonJSModule<IExtensionModule>(joinPath(extensionDescription.extensionLocation, extensionDescription.main), activationTimesBuilder),
|
||||
+ this._loadCommonJSModule<IExtensionModule>(extensionDescription, activationTimesBuilder),
|
||||
this._loadExtensionContext(extensionDescription)
|
||||
]).then(values => {
|
||||
return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder);
|
||||
});
|
||||
}
|
||||
|
||||
- protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
|
||||
+ protected abstract _loadCommonJSModule<T>(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
|
||||
|
||||
private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise<vscode.ExtensionContext> {
|
||||
|
||||
diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts
|
||||
index a227d8a67b..92553a976c 100644
|
||||
--- a/src/vs/workbench/api/node/extHost.services.ts
|
||||
+++ b/src/vs/workbench/api/node/extHost.services.ts
|
||||
@@ -26,6 +26,8 @@ import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionS
|
||||
import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
|
||||
+import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
|
||||
+import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
|
||||
|
||||
// register singleton services
|
||||
registerSingleton(ILogService, ExtHostLogService);
|
||||
@@ -42,3 +44,19 @@ registerSingleton(IExtHostSearch, ExtHostSearch);
|
||||
registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
|
||||
registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
|
||||
registerSingleton(IExtHostStorage, ExtHostStorage);
|
||||
+
|
||||
+function NotImplementedProxy<T>(name: ServiceIdentifier<T>): { new(): T } {
|
||||
+ return <any>class {
|
||||
+ constructor() {
|
||||
+ return new Proxy({}, {
|
||||
+ get(target: any, prop: string | number) {
|
||||
+ if (target[prop]) {
|
||||
+ return target[prop];
|
||||
+ }
|
||||
+ throw new Error(`Not Implemented: ${name}->${String(prop)}`);
|
||||
+ }
|
||||
+ });
|
||||
+ }
|
||||
+ };
|
||||
+}
|
||||
+registerSingleton(IExtHostNodeProxy, class extends NotImplementedProxy(IExtHostNodeProxy) {});
|
||||
diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||
index 49a8e254fd..99d233aed5 100644
|
||||
--- a/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||
+++ b/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||
@@ -13,6 +13,8 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer
|
||||
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
+import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
+import { joinPath } from 'vs/base/common/resources';
|
||||
|
||||
class NodeModuleRequireInterceptor extends RequireInterceptor {
|
||||
|
||||
@@ -75,7 +77,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
};
|
||||
}
|
||||
|
||||
- protected _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
+ protected _loadCommonJSModule<T>(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
+ if (!URI.isUri(module)) {
|
||||
+ module = joinPath(module.extensionLocation, module.main!);
|
||||
+ }
|
||||
if (module.scheme !== Schemas.file) {
|
||||
throw new Error(`Cannot load URI: '${module}', must be of file-scheme`);
|
||||
}
|
||||
diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts
|
||||
index afd82468c0..289145be54 100644
|
||||
--- a/src/vs/workbench/api/worker/extHostExtensionService.ts
|
||||
+++ b/src/vs/workbench/api/worker/extHostExtensionService.ts
|
||||
@@ -9,6 +9,9 @@ import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHost
|
||||
import { endsWith } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
||||
+import { joinPath } from 'vs/base/common/resources';
|
||||
+import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
+import { loadCommonJSModule } from 'vs/server/src/browser/worker';
|
||||
|
||||
class WorkerRequireInterceptor extends RequireInterceptor {
|
||||
|
||||
@@ -41,7 +44,14 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
await this._fakeModules.install();
|
||||
}
|
||||
|
||||
- protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
+ protected async _loadCommonJSModule<T>(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
+ if (!URI.isUri(module) && module.extensionKind !== 'web') {
|
||||
+ return loadCommonJSModule(module, activationTimesBuilder, this._nodeProxy, this._logService, this._fakeModules.getModule('vscode', module.extensionLocation));
|
||||
+ }
|
||||
+
|
||||
+ if (!URI.isUri(module)) {
|
||||
+ module = joinPath(module.extensionLocation, module.main!);
|
||||
+ }
|
||||
|
||||
module = module.with({ path: ensureSuffix(module.path, '.js') });
|
||||
const response = await fetch(module.toString(true));
|
||||
@@ -57,7 +67,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
const _exports = {};
|
||||
const _module = { exports: _exports };
|
||||
const _require = (request: string) => {
|
||||
- const result = this._fakeModules.getModule(request, module);
|
||||
+ const result = this._fakeModules.getModule(request, <URI>module);
|
||||
if (result === undefined) {
|
||||
throw new Error(`Cannot load module '${request}'`);
|
||||
}
|
||||
diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts
|
||||
index 005a025aa9..ab392630c0 100644
|
||||
--- a/src/vs/workbench/browser/dnd.ts
|
||||
+++ b/src/vs/workbench/browser/dnd.ts
|
||||
@@ -32,6 +32,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
+import { IUploadService } from 'vs/server/src/browser/upload';
|
||||
|
||||
export interface IDraggedResource {
|
||||
resource: URI;
|
||||
@@ -167,14 +168,15 @@ export class ResourcesDropHandler {
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService,
|
||||
- @IHostService private readonly hostService: IHostService
|
||||
+ @IHostService private readonly hostService: IHostService,
|
||||
+ @IUploadService private readonly uploadService: IUploadService,
|
||||
) {
|
||||
}
|
||||
|
||||
async handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void> {
|
||||
const untitledOrFileResources = extractResources(event).filter(r => this.fileService.canHandleResource(r.resource) || r.resource.scheme === Schemas.untitled);
|
||||
if (!untitledOrFileResources.length) {
|
||||
- return;
|
||||
+ return this.uploadService.handleDrop(event, resolveTargetGroup, afterDrop, targetIndex);
|
||||
}
|
||||
|
||||
// Make the window active to handle the drop properly within
|
||||
diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts
|
||||
index 84c46faa36..957e8412e1 100644
|
||||
--- a/src/vs/workbench/browser/web.main.ts
|
||||
+++ b/src/vs/workbench/browser/web.main.ts
|
||||
@@ -48,6 +48,7 @@ import { toLocalISOString } from 'vs/base/common/date';
|
||||
import { IndexedDBLogProvider } from 'vs/workbench/services/log/browser/indexedDBLogProvider';
|
||||
import { InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider';
|
||||
import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
|
||||
+import { initialize } from 'vs/server/src/browser/client';
|
||||
|
||||
class BrowserMain extends Disposable {
|
||||
|
||||
@@ -84,6 +85,7 @@ class BrowserMain extends Disposable {
|
||||
|
||||
// Startup
|
||||
workbench.startup();
|
||||
+ await initialize(services.serviceCollection);
|
||||
}
|
||||
|
||||
private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void {
|
||||
@@ -238,6 +240,7 @@ class BrowserMain extends Disposable {
|
||||
const channel = connection.getChannel<IChannel>(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
|
||||
const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment()));
|
||||
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
|
||||
+ fileService.registerProvider(Schemas.file, remoteFileSystemProvider);
|
||||
|
||||
if (!this.configuration.userDataProvider) {
|
||||
const remoteUserDataUri = this.getRemoteUserDataUri();
|
||||
diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts
|
||||
index 53de865d8f..df234821a9 100644
|
||||
--- a/src/vs/workbench/common/resources.ts
|
||||
+++ b/src/vs/workbench/common/resources.ts
|
||||
@@ -15,6 +15,7 @@ import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
+import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export class ResourceContextKey extends Disposable implements IContextKey<URI> {
|
||||
|
||||
@@ -63,7 +64,7 @@ export class ResourceContextKey extends Disposable implements IContextKey<URI> {
|
||||
set(value: URI | null) {
|
||||
if (!ResourceContextKey._uriEquals(this._resourceKey.get(), value)) {
|
||||
this._resourceKey.set(value);
|
||||
- this._schemeKey.set(value ? value.scheme : null);
|
||||
+ this._schemeKey.set(value ? (value.scheme === Schemas.vscodeRemote ? Schemas.file : value.scheme) : null);
|
||||
this._filenameKey.set(value ? basename(value) : null);
|
||||
this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value) : null);
|
||||
this._extensionKey.set(value ? extname(value) : null);
|
||||
@@ -200,4 +201,4 @@ export class ResourceGlobMatcher extends Disposable {
|
||||
|
||||
return !!expressionForRoot(resourcePathToMatch);
|
||||
}
|
||||
-}
|
||||
\ No newline at end of file
|
||||
+}
|
||||
diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts
|
||||
index 1f4cd95f65..061931cbde 100644
|
||||
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts
|
||||
+++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts
|
||||
@@ -209,7 +209,7 @@ configurationRegistry.registerConfiguration({
|
||||
'files.exclude': {
|
||||
'type': 'object',
|
||||
'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."),
|
||||
- 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true },
|
||||
+ 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true, '**/.code-server-partial-upload-*': true },
|
||||
'scope': ConfigurationScope.RESOURCE,
|
||||
'additionalProperties': {
|
||||
'anyOf': [
|
||||
diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
|
||||
index cc4bcb28c5..98679a8b32 100644
|
||||
--- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
|
||||
+++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
|
||||
@@ -47,6 +47,7 @@ import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/work
|
||||
import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions';
|
||||
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
+import { IUploadService } from 'vs/server/src/browser/upload';
|
||||
|
||||
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {
|
||||
|
||||
@@ -444,7 +445,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@ITextFileService private textFileService: ITextFileService,
|
||||
@IHostService private hostService: IHostService,
|
||||
- @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService
|
||||
+ @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
|
||||
+ @IUploadService private readonly uploadService: IUploadService,
|
||||
) {
|
||||
this.toDispose = [];
|
||||
|
||||
@@ -605,6 +607,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||
}
|
||||
|
||||
private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
|
||||
+ return this.uploadService.handleExternalDrop(data, target, originalEvent);
|
||||
const droppedResources = extractResources(originalEvent, true);
|
||||
// Check for dropped external files to be folders
|
||||
const result = await this.fileService.resolveAll(droppedResources);
|
||||
diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js
|
||||
index e6b9fd854b..a3d0a46e3a 100644
|
||||
--- a/src/vs/workbench/contrib/webview/browser/pre/main.js
|
||||
+++ b/src/vs/workbench/contrib/webview/browser/pre/main.js
|
||||
@@ -308,7 +308,8 @@
|
||||
} else {
|
||||
// Rewrite vscode-resource in csp
|
||||
if (data.endpoint) {
|
||||
- csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:/g, data.endpoint));
|
||||
+ // NOTE@coder: Add back the trailing slash so it'll work for sub-paths.
|
||||
+ csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:/g, data.endpoint + "/"));
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
index 5f221e07ff..bfd592382c 100644
|
||||
--- a/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
+++ b/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
@@ -15,7 +15,6 @@ import { IPath, IPathsToWaitFor, IWindowConfiguration } from 'vs/platform/window
|
||||
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
|
||||
-import product from 'vs/platform/product/common/product';
|
||||
|
||||
export class BrowserWindowConfiguration implements IWindowConfiguration {
|
||||
|
||||
@@ -180,12 +179,13 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
|
||||
driverHandle?: string;
|
||||
driverVerbose: boolean;
|
||||
galleryMachineIdResource?: URI;
|
||||
+ extraExtensionPaths: string[];
|
||||
+ extraBuiltinExtensionPaths: string[];
|
||||
readonly logFile: URI;
|
||||
|
||||
get webviewExternalEndpoint(): string {
|
||||
- // TODO: get fallback from product.json
|
||||
- return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}')
|
||||
- .replace('{{commit}}', product.commit || '211fa02efe8c041fd7baa8ec3dce199d5185aa44');
|
||||
+ // NOTE@coder: Modified to work against the current URL.
|
||||
+ return `${window.location.origin}${window.location.pathname.replace(/\/+$/, '')}/webview/`;
|
||||
}
|
||||
|
||||
get webviewResourceRoot(): string {
|
||||
diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts
|
||||
index 000e5f7b4a..39f46e68a1 100644
|
||||
--- a/src/vs/workbench/services/extensions/browser/extensionService.ts
|
||||
+++ b/src/vs/workbench/services/extensions/browser/extensionService.ts
|
||||
@@ -119,6 +119,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
|
||||
} else {
|
||||
// remote: only enabled and none-web'ish extension
|
||||
+ localExtensions.push(...remoteEnv.extensions.filter(extension => this._isEnabled(extension) && isWebExtension(extension, this._configService)));
|
||||
remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !isWebExtension(extension, this._configService));
|
||||
this._checkEnableProposedApi(remoteEnv.extensions);
|
||||
|
||||
diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts
|
||||
index 49b2d270c0..45200ccdbb 100644
|
||||
--- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts
|
||||
+++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts
|
||||
@@ -12,7 +12,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
export function isWebExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean {
|
||||
const extensionKind = getExtensionKind(manifest, configurationService);
|
||||
- return extensionKind === 'web';
|
||||
+ return extensionKind === 'web' || manifest.name === 'vim';
|
||||
}
|
||||
|
||||
export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean {
|
||||
diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
|
||||
index 9f5a14f6cb..ca952f3d4d 100644
|
||||
--- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
|
||||
+++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
|
||||
@@ -42,12 +42,13 @@ const args = minimist(process.argv.slice(2), {
|
||||
const Module = require.__$__nodeRequire('module') as any;
|
||||
const originalLoad = Module._load;
|
||||
|
||||
- Module._load = function (request: string) {
|
||||
+ Module._load = function (request: string, parent: object, isMain: boolean) {
|
||||
if (request === 'natives') {
|
||||
throw new Error('Either the extension or a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more');
|
||||
}
|
||||
|
||||
- return originalLoad.apply(this, arguments);
|
||||
+ // NOTE@coder: Map node_module.asar requests to regular node_modules.
|
||||
+ return originalLoad.apply(this, [request.replace(/node_modules\.asar(\.unpacked)?/, 'node_modules'), parent, isMain]);
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -120,8 +121,11 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
|
||||
|
||||
// Wait for rich client to reconnect
|
||||
protocol.onSocketClose(() => {
|
||||
- // The socket has closed, let's give the renderer a certain amount of time to reconnect
|
||||
- disconnectRunner1.schedule();
|
||||
+ // NOTE@coder: Inform the server so we can manage offline
|
||||
+ // connections there instead. Our goal is to persist connections
|
||||
+ // forever (to a reasonable point) to account for things like
|
||||
+ // hibernating overnight.
|
||||
+ process.send!({ type: 'VSCODE_EXTHOST_DISCONNECTED' });
|
||||
});
|
||||
}
|
||||
}
|
||||
diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts
|
||||
index 3bdfa1a79f..ded21cf9c6 100644
|
||||
--- a/src/vs/workbench/services/extensions/worker/extHost.services.ts
|
||||
+++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts
|
||||
@@ -21,6 +21,7 @@ import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensio
|
||||
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService';
|
||||
+import { ExtHostNodeProxy, IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
|
||||
|
||||
// register singleton services
|
||||
registerSingleton(ILogService, ExtHostLogService);
|
||||
@@ -32,6 +33,7 @@ registerSingleton(IExtHostCommands, ExtHostCommands);
|
||||
registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors);
|
||||
registerSingleton(IExtHostStorage, ExtHostStorage);
|
||||
registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
|
||||
+registerSingleton(IExtHostNodeProxy, ExtHostNodeProxy);
|
||||
|
||||
// register services that only throw errors
|
||||
function NotImplementedProxy<T>(name: ServiceIdentifier<T>): { new(): T } {
|
||||
diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts
|
||||
index 3b5706ce76..f390ed35dc 100644
|
||||
--- a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts
|
||||
+++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts
|
||||
@@ -36,7 +36,9 @@ const nativeAddEventLister = addEventListener.bind(self);
|
||||
self.addEventLister = () => console.trace(`'addEventListener' has been blocked`);
|
||||
|
||||
self.indexedDB.open = () => console.trace(`'indexedDB.open' has been blocked`);
|
||||
-self.caches.open = () => console.trace(`'indexedDB.caches' has been blocked`);
|
||||
+if (self.caches) { // NOTE@coder: on insecure domains this exists in Firefox but not Chromium or Safari.
|
||||
+ self.caches.open = () => console.trace(`'indexedDB.caches' has been blocked`);
|
||||
+}
|
||||
|
||||
//#endregion ---
|
||||
|
||||
diff --git a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
|
||||
index 99394090da..4891e0fece 100644
|
||||
--- a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
|
||||
+++ b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
|
||||
@@ -5,17 +5,17 @@
|
||||
|
||||
import { createChannelSender } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||
-import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
+import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
|
||||
export class LocalizationsService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
- @ISharedProcessService sharedProcessService: ISharedProcessService,
|
||||
+ @IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
) {
|
||||
- return createChannelSender<ILocalizationsService>(sharedProcessService.getChannel('localizations'));
|
||||
+ return createChannelSender<ILocalizationsService>(remoteAgentService.getConnection()!.getChannel('localizations'));
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/src/vs/workbench/services/update/electron-browser/updateService.ts b/src/vs/workbench/services/update/electron-browser/updateService.ts
|
||||
index b8f6558b2c..b1fe6b14fd 100644
|
||||
--- a/src/vs/workbench/services/update/electron-browser/updateService.ts
|
||||
+++ b/src/vs/workbench/services/update/electron-browser/updateService.ts
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IUpdateService, State } from 'vs/platform/update/common/update';
|
||||
-import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
|
||||
+import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
|
||||
export class NativeUpdateService implements IUpdateService {
|
||||
@@ -21,8 +21,9 @@ export class NativeUpdateService implements IUpdateService {
|
||||
|
||||
private channel: IChannel;
|
||||
|
||||
- constructor(@IMainProcessService mainProcessService: IMainProcessService) {
|
||||
- this.channel = mainProcessService.getChannel('update');
|
||||
+ // NOTE@coder: patched to work in the browser.
|
||||
+ constructor(@IRemoteAgentService remoteAgentService: IRemoteAgentService) {
|
||||
+ this.channel = remoteAgentService.getConnection()!.getChannel('update');
|
||||
|
||||
// always set this._state as the state changes
|
||||
this.onStateChange(state => this._state = state);
|
||||
diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts
|
||||
index fa9c9dd7a9..688d6c1934 100644
|
||||
--- a/src/vs/workbench/workbench.web.main.ts
|
||||
+++ b/src/vs/workbench/workbench.web.main.ts
|
||||
@@ -34,11 +34,14 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService';
|
||||
import 'vs/workbench/services/keybinding/browser/keymapService';
|
||||
import 'vs/workbench/services/extensions/browser/extensionService';
|
||||
import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService';
|
||||
-import 'vs/workbench/services/telemetry/browser/telemetryService';
|
||||
+// NOTE@coder: We send it all to the server side to be processed there instead.
|
||||
+// import 'vs/workbench/services/telemetry/browser/telemetryService';
|
||||
import 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
|
||||
import 'vs/workbench/services/credentials/browser/credentialsService';
|
||||
import 'vs/workbench/services/url/browser/urlService';
|
||||
-import 'vs/workbench/services/update/browser/updateService';
|
||||
+// NOTE@coder: Use the electron-browser version since it already comes with a
|
||||
+// channel which lets us actually perform updates.
|
||||
+import 'vs/workbench/services/update/electron-browser/updateService';
|
||||
import 'vs/workbench/contrib/stats/browser/workspaceStatsService';
|
||||
import 'vs/workbench/services/workspaces/browser/workspacesService';
|
||||
import 'vs/workbench/services/workspaces/browser/workspaceEditingService';
|
||||
@@ -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/workbench/services/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;
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
import { Emitter } from "vs/base/common/event";
|
||||
import { URI } from "vs/base/common/uri";
|
||||
import { localize } from "vs/nls";
|
||||
import { Extensions, IConfigurationRegistry } from "vs/platform/configuration/common/configurationRegistry";
|
||||
import { registerSingleton } from "vs/platform/instantiation/common/extensions";
|
||||
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
|
||||
import { ILocalizationsService } from "vs/platform/localizations/common/localizations";
|
||||
import { Registry } from "vs/platform/registry/common/platform";
|
||||
import { PersistentConnectionEventType } from "vs/platform/remote/common/remoteAgentConnection";
|
||||
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
|
||||
import { coderApi, vscodeApi } from "vs/server/src/browser/api";
|
||||
import { IUploadService, UploadService } from "vs/server/src/browser/upload";
|
||||
import { INodeProxyService, NodeProxyChannelClient } from "vs/server/src/common/nodeProxy";
|
||||
import { TelemetryChannelClient } from "vs/server/src/common/telemetry";
|
||||
import { split } from "vs/server/src/common/util";
|
||||
import "vs/workbench/contrib/localizations/browser/localizations.contribution";
|
||||
import { LocalizationsService } from "vs/workbench/services/localizations/electron-browser/localizationsService";
|
||||
import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService";
|
||||
|
||||
class TelemetryService extends TelemetryChannelClient {
|
||||
public constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
) {
|
||||
super(remoteAgentService.getConnection()!.getChannel("telemetry"));
|
||||
}
|
||||
}
|
||||
|
||||
const TELEMETRY_SECTION_ID = "telemetry";
|
||||
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
|
||||
"id": TELEMETRY_SECTION_ID,
|
||||
"order": 110,
|
||||
"type": "object",
|
||||
"title": localize("telemetryConfigurationTitle", "Telemetry"),
|
||||
"properties": {
|
||||
"telemetry.enableTelemetry": {
|
||||
"type": "boolean",
|
||||
"description": localize("telemetry.enableTelemetry", "Enable usage data and errors to be sent to a Microsoft online service."),
|
||||
"default": true,
|
||||
"tags": ["usesOnlineServices"]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
class NodeProxyService extends NodeProxyChannelClient implements INodeProxyService {
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
public readonly onClose = this._onClose.event;
|
||||
private readonly _onDown = new Emitter<void>();
|
||||
public readonly onDown = this._onDown.event;
|
||||
private readonly _onUp = new Emitter<void>();
|
||||
public readonly onUp = this._onUp.event;
|
||||
|
||||
public constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
) {
|
||||
super(remoteAgentService.getConnection()!.getChannel("nodeProxy"));
|
||||
remoteAgentService.getConnection()!.onDidStateChange((state) => {
|
||||
switch (state.type) {
|
||||
case PersistentConnectionEventType.ConnectionGain:
|
||||
return this._onUp.fire();
|
||||
case PersistentConnectionEventType.ConnectionLost:
|
||||
return this._onDown.fire();
|
||||
case PersistentConnectionEventType.ReconnectionPermanentFailure:
|
||||
return this._onClose.fire();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(ILocalizationsService, LocalizationsService);
|
||||
registerSingleton(INodeProxyService, NodeProxyService);
|
||||
registerSingleton(ITelemetryService, TelemetryService);
|
||||
registerSingleton(IUploadService, UploadService, true);
|
||||
|
||||
/**
|
||||
* 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] = split(kv, "=");
|
||||
if (!(key in query)) {
|
||||
query[key] = value;
|
||||
}
|
||||
});
|
||||
return uri.with({
|
||||
query: Object.keys(query)
|
||||
.filter((k) => typeof query[k] !== "undefined")
|
||||
.map((k) => `${k}=${query[k]}`).join("&"),
|
||||
}).toString(true);
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Emitter } from "vs/base/common/event";
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ExtHostNodeProxyShape, MainContext, MainThreadNodeProxyShape } from "vs/workbench/api/common/extHost.protocol";
|
||||
import { IExtHostRpcService } from "vs/workbench/api/common/extHostRpcService";
|
||||
|
||||
export class ExtHostNodeProxy implements ExtHostNodeProxyShape {
|
||||
_serviceBrand: any;
|
||||
|
||||
private readonly _onMessage = new Emitter<string>();
|
||||
public readonly onMessage = this._onMessage.event;
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
public readonly onClose = this._onClose.event;
|
||||
private readonly _onDown = new Emitter<void>();
|
||||
public readonly onDown = this._onDown.event;
|
||||
private readonly _onUp = new Emitter<void>();
|
||||
public readonly onUp = this._onUp.event;
|
||||
|
||||
private readonly proxy: MainThreadNodeProxyShape;
|
||||
|
||||
constructor(@IExtHostRpcService rpc: IExtHostRpcService) {
|
||||
this.proxy = rpc.getProxy(MainContext.MainThreadNodeProxy);
|
||||
}
|
||||
|
||||
public $onMessage(message: string): void {
|
||||
this._onMessage.fire(message);
|
||||
}
|
||||
|
||||
public $onClose(): void {
|
||||
this._onClose.fire();
|
||||
}
|
||||
|
||||
public $onUp(): void {
|
||||
this._onUp.fire();
|
||||
}
|
||||
|
||||
public $onDown(): void {
|
||||
this._onDown.fire();
|
||||
}
|
||||
|
||||
public send(message: string): void {
|
||||
this.proxy.$send(message);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExtHostNodeProxy extends ExtHostNodeProxy { }
|
||||
export const IExtHostNodeProxy = createDecorator<IExtHostNodeProxy>('IExtHostNodeProxy');
|
||||
@@ -1,30 +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 rel="apple-touch-icon" href="./static/out/vs/server/src/media/code-server.png" />
|
||||
<link href="./static/out/vs/server/src/media/login.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<form class="login-form" method="post">
|
||||
<h4 class="title">code-server</h4>
|
||||
<h2 class="subtitle">
|
||||
Enter server password
|
||||
</h2>
|
||||
<div class="field">
|
||||
<!-- The onfocus code places the cursor at the end of the value. -->
|
||||
<input name="password" type="password" class="input" value=""
|
||||
required autofocus
|
||||
onfocus="const value=this.value;this.value='';this.value=value;">
|
||||
</div>
|
||||
<button class="button" type="submit">
|
||||
<span class="label">Enter IDE</span>
|
||||
</button>
|
||||
<div class="error-display" style="display:none">{{ERROR}}</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,37 +0,0 @@
|
||||
import { IDisposable } from "vs/base/common/lifecycle";
|
||||
import { INodeProxyService } from "vs/server/src/common/nodeProxy";
|
||||
import { ExtHostContext, IExtHostContext, MainContext, MainThreadNodeProxyShape } from "vs/workbench/api/common/extHost.protocol";
|
||||
import { extHostNamedCustomer } from "vs/workbench/api/common/extHostCustomers";
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadNodeProxy)
|
||||
export class MainThreadNodeProxy implements MainThreadNodeProxyShape {
|
||||
private disposed = false;
|
||||
private disposables = <IDisposable[]>[];
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@INodeProxyService private readonly proxyService: INodeProxyService,
|
||||
) {
|
||||
if (!extHostContext.remoteAuthority) { // HACK: A terrible way to detect if running in the worker.
|
||||
const proxy = extHostContext.getProxy(ExtHostContext.ExtHostNodeProxy);
|
||||
this.disposables = [
|
||||
this.proxyService.onMessage((message: string) => proxy.$onMessage(message)),
|
||||
this.proxyService.onClose(() => proxy.$onClose()),
|
||||
this.proxyService.onDown(() => proxy.$onDown()),
|
||||
this.proxyService.onUp(() => proxy.$onUp()),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$send(message: string): void {
|
||||
if (!this.disposed) {
|
||||
this.proxyService.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.forEach((d) => d.dispose());
|
||||
this.disposables = [];
|
||||
this.disposed = true;
|
||||
}
|
||||
}
|
||||
BIN
src/browser/media/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
40
src/browser/media/manifest.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "code-server",
|
||||
"short_name": "code-server",
|
||||
"start_url": "{{BASE}}",
|
||||
"display": "fullscreen",
|
||||
"background-color": "#fff",
|
||||
"description": "Run editors on a remote server.",
|
||||
"icons": [
|
||||
{
|
||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-96.png",
|
||||
"type": "image/png",
|
||||
"sizes": "96x96"
|
||||
},
|
||||
{
|
||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-128.png",
|
||||
"type": "image/png",
|
||||
"sizes": "128x128"
|
||||
},
|
||||
{
|
||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-256.png",
|
||||
"type": "image/png",
|
||||
"sizes": "256x256"
|
||||
},
|
||||
{
|
||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png",
|
||||
"type": "image/png",
|
||||
"sizes": "384x384"
|
||||
},
|
||||
{
|
||||
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
src/browser/media/pwa-icon-128.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/browser/media/pwa-icon-192.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
src/browser/media/pwa-icon-256.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src/browser/media/pwa-icon-384.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
src/browser/media/pwa-icon-512.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
src/browser/media/pwa-icon-96.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
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
|
||||
}
|
||||
}
|
||||
@@ -1,372 +0,0 @@
|
||||
import { DesktopDragAndDropData } from "vs/base/browser/ui/list/listView";
|
||||
import { VSBuffer, VSBufferReadableStream } from "vs/base/common/buffer";
|
||||
import { Disposable } from "vs/base/common/lifecycle";
|
||||
import * as path from "vs/base/common/path";
|
||||
import { URI } from "vs/base/common/uri";
|
||||
import { generateUuid } from "vs/base/common/uuid";
|
||||
import { IFileService } from "vs/platform/files/common/files";
|
||||
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationService, Severity } from "vs/platform/notification/common/notification";
|
||||
import { IProgress, IProgressService, IProgressStep, ProgressLocation } from "vs/platform/progress/common/progress";
|
||||
import { IWorkspaceContextService } from "vs/platform/workspace/common/workspace";
|
||||
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { ExplorerItem } from "vs/workbench/contrib/files/common/explorerModel";
|
||||
import { IEditorGroup } from "vs/workbench/services/editor/common/editorGroupsService";
|
||||
import { IEditorService } from "vs/workbench/services/editor/common/editorService";
|
||||
|
||||
export const IUploadService = createDecorator<IUploadService>("uploadService");
|
||||
|
||||
export interface IUploadService {
|
||||
_serviceBrand: undefined;
|
||||
handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void>;
|
||||
handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void>;
|
||||
}
|
||||
|
||||
export class UploadService extends Disposable implements IUploadService {
|
||||
public _serviceBrand: undefined;
|
||||
public upload: Upload;
|
||||
|
||||
public constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
) {
|
||||
super();
|
||||
this.upload = instantiationService.createInstance(Upload);
|
||||
}
|
||||
|
||||
public async handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void> {
|
||||
// TODO: should use the workspace for the editor it was dropped on?
|
||||
const target = this.contextService.getWorkspace().folders[0].uri;
|
||||
const uris = (await this.upload.uploadDropped(event, target)).map((u) => URI.file(u));
|
||||
if (uris.length > 0) {
|
||||
await this.workspacesService.addRecentlyOpened(uris.map((u) => ({ fileUri: u })));
|
||||
}
|
||||
const editors = uris.map((uri) => ({
|
||||
resource: uri,
|
||||
options: {
|
||||
pinned: true,
|
||||
index: targetIndex,
|
||||
},
|
||||
}));
|
||||
const targetGroup = resolveTargetGroup();
|
||||
this.editorService.openEditors(editors, targetGroup);
|
||||
afterDrop(targetGroup);
|
||||
}
|
||||
|
||||
public async handleExternalDrop(_data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
|
||||
await this.upload.uploadDropped(originalEvent, target.resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* There doesn't seem to be a provided type for entries, so here is an
|
||||
* incomplete version.
|
||||
*/
|
||||
interface IEntry {
|
||||
name: string;
|
||||
isFile: boolean;
|
||||
file: (cb: (file: File) => void) => void;
|
||||
createReader: () => ({
|
||||
readEntries: (cb: (entries: Array<IEntry>) => void) => void;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles file uploads.
|
||||
*/
|
||||
class Upload {
|
||||
private readonly maxParallelUploads = 100;
|
||||
private readonly uploadingFiles = new Map<string, Reader | undefined>();
|
||||
private readonly fileQueue = new Map<string, File>();
|
||||
private progress: IProgress<IProgressStep> | undefined;
|
||||
private uploadPromise: Promise<string[]> | undefined;
|
||||
private resolveUploadPromise: (() => void) | undefined;
|
||||
private uploadedFilePaths = <string[]>[];
|
||||
private _total = 0;
|
||||
private _uploaded = 0;
|
||||
private lastPercent = 0;
|
||||
|
||||
public constructor(
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IProgressService private progressService: IProgressService,
|
||||
@IFileService private fileService: IFileService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Upload dropped files. This will try to upload everything it can. Errors
|
||||
* will show via notifications. If an upload operation is ongoing, the files
|
||||
* will be added to that operation.
|
||||
*/
|
||||
public async uploadDropped(event: DragEvent, uploadDir: URI): Promise<string[]> {
|
||||
await this.queueFiles(event, uploadDir);
|
||||
if (!this.uploadPromise) {
|
||||
this.uploadPromise = this.progressService.withProgress({
|
||||
cancellable: true,
|
||||
location: ProgressLocation.Notification,
|
||||
title: "Uploading files...",
|
||||
}, (progress) => {
|
||||
return new Promise((resolve): void => {
|
||||
this.progress = progress;
|
||||
this.resolveUploadPromise = (): void => {
|
||||
const uploaded = this.uploadedFilePaths;
|
||||
this.uploadPromise = undefined;
|
||||
this.resolveUploadPromise = undefined;
|
||||
this.uploadedFilePaths = [];
|
||||
this.lastPercent = 0;
|
||||
this._uploaded = 0;
|
||||
this._total = 0;
|
||||
resolve(uploaded);
|
||||
};
|
||||
});
|
||||
}, () => this.cancel());
|
||||
}
|
||||
this.uploadFiles();
|
||||
return this.uploadPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all file uploads.
|
||||
*/
|
||||
public async cancel(): Promise<void> {
|
||||
this.fileQueue.clear();
|
||||
this.uploadingFiles.forEach((r) => r && r.abort());
|
||||
}
|
||||
|
||||
private get total(): number { return this._total; }
|
||||
private set total(total: number) {
|
||||
this._total = total;
|
||||
this.updateProgress();
|
||||
}
|
||||
|
||||
private get uploaded(): number { return this._uploaded; }
|
||||
private set uploaded(uploaded: number) {
|
||||
this._uploaded = uploaded;
|
||||
this.updateProgress();
|
||||
}
|
||||
|
||||
private updateProgress(): void {
|
||||
if (this.progress && this.total > 0) {
|
||||
const percent = Math.floor((this.uploaded / this.total) * 100);
|
||||
this.progress.report({ increment: percent - this.lastPercent });
|
||||
this.lastPercent = percent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload as many files as possible. When finished, resolve the upload
|
||||
* promise.
|
||||
*/
|
||||
private uploadFiles(): void {
|
||||
while (this.fileQueue.size > 0 && this.uploadingFiles.size < this.maxParallelUploads) {
|
||||
const [path, file] = this.fileQueue.entries().next().value;
|
||||
this.fileQueue.delete(path);
|
||||
if (this.uploadingFiles.has(path)) {
|
||||
this.notificationService.error(new Error(`Already uploading ${path}`));
|
||||
} else {
|
||||
this.uploadingFiles.set(path, undefined);
|
||||
this.uploadFile(path, file).catch((error) => {
|
||||
this.notificationService.error(error);
|
||||
}).finally(() => {
|
||||
this.uploadingFiles.delete(path);
|
||||
this.uploadFiles();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.fileQueue.size === 0 && this.uploadingFiles.size === 0) {
|
||||
this.resolveUploadPromise!();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file, asking to override if necessary.
|
||||
*/
|
||||
private async uploadFile(filePath: string, file: File): Promise<void> {
|
||||
const uri = URI.file(filePath);
|
||||
if (await this.fileService.exists(uri)) {
|
||||
const overwrite = await new Promise<boolean>((resolve): void => {
|
||||
this.notificationService.prompt(
|
||||
Severity.Error,
|
||||
`${filePath} already exists. Overwrite?`,
|
||||
[
|
||||
{ label: "Yes", run: (): void => resolve(true) },
|
||||
{ label: "No", run: (): void => resolve(false) },
|
||||
],
|
||||
{ onCancel: () => resolve(false) },
|
||||
);
|
||||
});
|
||||
if (!overwrite) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const tempUri = uri.with({
|
||||
path: path.join(
|
||||
path.dirname(uri.path),
|
||||
`.code-server-partial-upload-${path.basename(uri.path)}-${generateUuid()}`,
|
||||
),
|
||||
});
|
||||
const reader = new Reader(file);
|
||||
reader.on("data", (data) => {
|
||||
if (data && data.byteLength > 0) {
|
||||
this.uploaded += data.byteLength;
|
||||
}
|
||||
});
|
||||
this.uploadingFiles.set(filePath, reader);
|
||||
await this.fileService.writeFile(tempUri, reader);
|
||||
if (reader.aborted) {
|
||||
this.uploaded += (file.size - reader.offset);
|
||||
await this.fileService.del(tempUri);
|
||||
} else {
|
||||
await this.fileService.move(tempUri, uri, true);
|
||||
this.uploadedFilePaths.push(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue files from a drop event. We have to get the files first; we can't do
|
||||
* it in tandem with uploading or the entries will disappear.
|
||||
*/
|
||||
private async queueFiles(event: DragEvent, uploadDir: URI): Promise<void> {
|
||||
const promises: Array<Promise<void>> = [];
|
||||
for (let i = 0; event.dataTransfer && event.dataTransfer.items && i < event.dataTransfer.items.length; ++i) {
|
||||
const item = event.dataTransfer.items[i];
|
||||
if (typeof item.webkitGetAsEntry === "function") {
|
||||
promises.push(this.traverseItem(item.webkitGetAsEntry(), uploadDir.fsPath));
|
||||
} else {
|
||||
const file = item.getAsFile();
|
||||
if (file) {
|
||||
this.addFile(uploadDir.fsPath + "/" + file.name, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses an entry and add files to the queue.
|
||||
*/
|
||||
private async traverseItem(entry: IEntry, path: string): Promise<void> {
|
||||
if (entry.isFile) {
|
||||
return new Promise<void>((resolve): void => {
|
||||
entry.file((file) => {
|
||||
resolve(this.addFile(path + "/" + file.name, file));
|
||||
});
|
||||
});
|
||||
}
|
||||
path += "/" + entry.name;
|
||||
await new Promise((resolve): void => {
|
||||
const promises: Array<Promise<void>> = [];
|
||||
const dirReader = entry.createReader();
|
||||
// According to the spec, readEntries() must be called until it calls
|
||||
// the callback with an empty array.
|
||||
const readEntries = (): void => {
|
||||
dirReader.readEntries((entries) => {
|
||||
if (entries.length === 0) {
|
||||
Promise.all(promises).then(resolve).catch((error) => {
|
||||
this.notificationService.error(error);
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
promises.push(...entries.map((c) => this.traverseItem(c, path)));
|
||||
readEntries();
|
||||
}
|
||||
});
|
||||
};
|
||||
readEntries();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file to the queue.
|
||||
*/
|
||||
private addFile(path: string, file: File): void {
|
||||
this.total += file.size;
|
||||
this.fileQueue.set(path, file);
|
||||
}
|
||||
}
|
||||
|
||||
class Reader implements VSBufferReadableStream {
|
||||
private _offset = 0;
|
||||
private readonly size = 32000; // ~32kb max while reading in the file.
|
||||
private _aborted = false;
|
||||
private readonly reader = new FileReader();
|
||||
private paused = true;
|
||||
private buffer?: VSBuffer;
|
||||
private callbacks = new Map<string, Array<(...args: any[]) => void>>();
|
||||
|
||||
public constructor(private readonly file: File) {
|
||||
this.reader.addEventListener("load", this.onLoad);
|
||||
}
|
||||
|
||||
public get offset(): number { return this._offset; }
|
||||
public get aborted(): boolean { return this._aborted; }
|
||||
|
||||
public on(event: "data" | "error" | "end", callback: (...args:any[]) => void): void {
|
||||
if (!this.callbacks.has(event)) {
|
||||
this.callbacks.set(event, []);
|
||||
}
|
||||
this.callbacks.get(event)!.push(callback);
|
||||
if (this.aborted) {
|
||||
return this.emit("error", new Error("stream has been aborted"));
|
||||
} else if (this.done) {
|
||||
return this.emit("error", new Error("stream has ended"));
|
||||
} else if (event === "end") { // Once this is being listened to we can safely start outputting data.
|
||||
this.resume();
|
||||
}
|
||||
}
|
||||
|
||||
public abort = (): void => {
|
||||
this._aborted = true;
|
||||
this.reader.abort();
|
||||
this.reader.removeEventListener("load", this.onLoad);
|
||||
this.emit("end");
|
||||
}
|
||||
|
||||
public pause(): void {
|
||||
this.paused = true;
|
||||
}
|
||||
|
||||
public resume(): void {
|
||||
if (this.paused) {
|
||||
this.paused = false;
|
||||
this.readNextChunk();
|
||||
}
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.abort();
|
||||
}
|
||||
|
||||
private onLoad = (): void => {
|
||||
this.buffer = VSBuffer.wrap(new Uint8Array(this.reader.result as ArrayBuffer));
|
||||
if (!this.paused) {
|
||||
this.readNextChunk();
|
||||
}
|
||||
}
|
||||
|
||||
private readNextChunk(): void {
|
||||
if (this.buffer) {
|
||||
this._offset += this.buffer.byteLength;
|
||||
this.emit("data", this.buffer);
|
||||
this.buffer = undefined;
|
||||
}
|
||||
if (!this.paused) { // Could be paused during the data event.
|
||||
if (this.done) {
|
||||
this.emit("end");
|
||||
} else {
|
||||
this.reader.readAsArrayBuffer(this.file.slice(this.offset, this.offset + this.size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private emit(event: "data" | "error" | "end", ...args: any[]): void {
|
||||
if (this.callbacks.has(event)) {
|
||||
this.callbacks.get(event)!.forEach((cb) => cb(...args));
|
||||
}
|
||||
}
|
||||
|
||||
private get done(): boolean {
|
||||
return this.offset >= this.file.size;
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<!-- Disable pinch zooming -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||||
|
||||
<!-- Content Security Policy -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="
|
||||
default-src 'self';
|
||||
img-src 'self' https: data: blob:;
|
||||
media-src 'none';
|
||||
script-src 'self' 'unsafe-eval' https: 'sha256-bpJydy1E+3Mx9MyBtkOIA3yyzM2wdyIz115+Sgq21+0=' 'sha256-meDZW3XhN5JmdjFUrWGhTouRKBiWYtXHltaKnqn/WMo=';
|
||||
child-src 'self';
|
||||
frame-src 'self';
|
||||
worker-src 'self';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
connect-src 'self' ws: wss: https:;
|
||||
font-src 'self' blob:;
|
||||
manifest-src 'self';
|
||||
">
|
||||
|
||||
<!-- Workbench Configuration -->
|
||||
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
|
||||
|
||||
<!-- Workarounds/Hacks (remote user data uri) -->
|
||||
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}">
|
||||
<!-- NOTE@coder: Added the commit for use in caching, the product for the
|
||||
extensions gallery URL, and nls for language support. -->
|
||||
<meta id="vscode-remote-commit" data-settings="{{COMMIT}}">
|
||||
<meta id="vscode-remote-product-configuration" data-settings="{{PRODUCT_CONFIGURATION}}">
|
||||
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
|
||||
|
||||
<!-- Workbench Icon/Manifest/CSS -->
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
|
||||
<link rel="manifest" href="./manifest.json">
|
||||
<link rel="apple-touch-icon" href="./static-{{COMMIT}}/out/vs/server/src/media/code-server.png" />
|
||||
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.css">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
|
||||
<!-- Prefetch to avoid waterfall -->
|
||||
<link rel="prefetch" href="./static-{{COMMIT}}/node_modules/semver-umd/lib/semver-umd.js">
|
||||
<link rel="prefetch" href="./static-{{COMMIT}}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js">
|
||||
</head>
|
||||
|
||||
<body aria-label="">
|
||||
</body>
|
||||
|
||||
<!-- Startup (do not modify order of script tags!) -->
|
||||
<!-- NOTE:coder: Modified to work against the current path and use the commit for caching. -->
|
||||
<script>
|
||||
// NOTE: Changes to inline scripts require update of content security policy
|
||||
const basePath = window.location.pathname.replace(/\/+$/, '');
|
||||
const base = window.location.origin + basePath;
|
||||
const el = document.getElementById('vscode-remote-commit');
|
||||
const commit = el ? el.getAttribute('data-settings') : "";
|
||||
const staticBase = base + '/static-' + commit;
|
||||
let nlsConfig;
|
||||
try {
|
||||
nlsConfig = JSON.parse(document.getElementById('vscode-remote-nls-configuration').getAttribute('data-settings'));
|
||||
if (nlsConfig._resolvedLanguagePackCoreLocation) {
|
||||
const bundles = Object.create(null);
|
||||
nlsConfig.loadBundle = (bundle, language, cb) => {
|
||||
let result = bundles[bundle];
|
||||
if (result) {
|
||||
return cb(undefined, result);
|
||||
}
|
||||
// FIXME: Only works if path separators are /.
|
||||
const path = nlsConfig._resolvedLanguagePackCoreLocation
|
||||
+ '/' + bundle.replace(/\//g, '!') + '.nls.json';
|
||||
fetch(`${base}/resource/?path=${encodeURIComponent(path)}`)
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
bundles[bundle] = json;
|
||||
cb(undefined, json);
|
||||
})
|
||||
.catch(cb);
|
||||
};
|
||||
}
|
||||
} catch (error) { /* Probably fine. */ }
|
||||
self.require = {
|
||||
baseUrl: `${staticBase}/out`,
|
||||
paths: {
|
||||
'vscode-textmate': `${staticBase}/node_modules/vscode-textmate/release/main`,
|
||||
'onigasm-umd': `${staticBase}/node_modules/onigasm-umd/release/main`,
|
||||
'xterm': `${staticBase}/node_modules/xterm/lib/xterm.js`,
|
||||
'xterm-addon-search': `${staticBase}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||
'xterm-addon-web-links': `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
|
||||
'semver-umd': `${staticBase}/node_modules/semver-umd/lib/semver-umd.js`,
|
||||
'@microsoft/applicationinsights-web': `${staticBase}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`,
|
||||
},
|
||||
'vs/nls': nlsConfig,
|
||||
};
|
||||
</script>
|
||||
<script src="./static-{{COMMIT}}/out/vs/loader.js"></script>
|
||||
<script src="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.nls.js"></script>
|
||||
<script src="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.js"></script>
|
||||
<!-- TODO@coder: This errors with multiple anonymous define calls (one is
|
||||
workbench.js and one is semver-umd.js). For now use the same method found in
|
||||
workbench-dev.html. Appears related to the timing of the script load events. -->
|
||||
<!-- <script src="./static-{{COMMIT}}/out/vs/workbench/workbench.js"></script> -->
|
||||
<script>
|
||||
// NOTE: Changes to inline scripts require update of content security policy
|
||||
require(['vs/code/browser/workbench/workbench'], function() {});
|
||||
</script>
|
||||
</html>
|
||||
@@ -1,70 +0,0 @@
|
||||
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<!-- Disable pinch zooming -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||||
|
||||
<!-- Content Security Policy -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="
|
||||
default-src 'self';
|
||||
img-src 'self' https: data: blob:;
|
||||
media-src 'none';
|
||||
script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https: 'unsafe-inline';
|
||||
child-src 'self';
|
||||
frame-src 'self' https://*.vscode-webview-test.com;
|
||||
worker-src 'self';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
connect-src 'self' ws: wss: https:;
|
||||
font-src 'self' blob:;
|
||||
manifest-src 'self';
|
||||
">
|
||||
|
||||
<!-- Workbench Configuration -->
|
||||
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
|
||||
|
||||
<!-- Workarounds/Hacks (remote user data uri) -->
|
||||
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}">
|
||||
<!-- NOTE@coder: Added the commit for use in caching, the product for the
|
||||
extensions gallery URL, and nls for language support. -->
|
||||
<meta id="vscode-remote-commit" data-settings="{{COMMIT}}">
|
||||
<meta id="vscode-remote-product-configuration" data-settings="{{PRODUCT_CONFIGURATION}}">
|
||||
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
|
||||
|
||||
<!-- Workbench Icon/Manifest/CSS -->
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
|
||||
<link rel="manifest" href="./manifest.json">
|
||||
</head>
|
||||
|
||||
<body aria-label="">
|
||||
</body>
|
||||
|
||||
<!-- Startup (do not modify order of script tags!) -->
|
||||
<script>
|
||||
const basePath = window.location.pathname.replace(/\/+$/, '');
|
||||
const base = window.location.origin + basePath;
|
||||
const el = document.getElementById('vscode-remote-commit');
|
||||
const commit = el ? el.getAttribute('data-settings') : "";
|
||||
const staticBase = base + '/static-' + commit;
|
||||
self.require = {
|
||||
baseUrl: `${staticBase}/out`,
|
||||
paths: {
|
||||
'vscode-textmate': `${staticBase}/node_modules/vscode-textmate/release/main`,
|
||||
'onigasm-umd': `${staticBase}/node_modules/onigasm-umd/release/main`,
|
||||
'xterm': `${staticBase}/node_modules/xterm/lib/xterm.js`,
|
||||
'xterm-addon-search': `${staticBase}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||
'xterm-addon-web-links': `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
|
||||
'semver-umd': `${staticBase}/node_modules/semver-umd/lib/semver-umd.js`,
|
||||
'@microsoft/applicationinsights-web': `${staticBase}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<script src="./static/out/vs/loader.js"></script>
|
||||
<script>
|
||||
require(['vs/code/browser/workbench/workbench'], function() {});
|
||||
</script>
|
||||
</html>
|
||||
@@ -1,57 +0,0 @@
|
||||
import { URI } from "vs/base/common/uri";
|
||||
import { IExtensionDescription } from "vs/platform/extensions/common/extensions";
|
||||
import { ILogService } from "vs/platform/log/common/log";
|
||||
import { Client } from "vs/server/node_modules/@coder/node-browser/out/client/client";
|
||||
import { fromTar } from "vs/server/node_modules/@coder/requirefs/out/requirefs";
|
||||
import { ExtensionActivationTimesBuilder } from "vs/workbench/api/common/extHostExtensionActivator";
|
||||
import { IExtHostNodeProxy } from "./extHostNodeProxy";
|
||||
|
||||
export const loadCommonJSModule = async <T>(
|
||||
module: IExtensionDescription,
|
||||
activationTimesBuilder: ExtensionActivationTimesBuilder,
|
||||
nodeProxy: IExtHostNodeProxy,
|
||||
logService: ILogService,
|
||||
vscode: any,
|
||||
): Promise<T> => {
|
||||
const fetchUri = URI.from({
|
||||
scheme: self.location.protocol.replace(":", ""),
|
||||
authority: self.location.host,
|
||||
path: `${self.location.pathname.replace(/\/static.*\/out\/vs\/workbench\/services\/extensions\/worker\/extensionHostWorkerMain.js$/, "")}/tar`,
|
||||
query: `path=${encodeURIComponent(module.extensionLocation.path)}`,
|
||||
});
|
||||
const response = await fetch(fetchUri.toString(true));
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`Failed to download extension "${module.extensionLocation.path}"`);
|
||||
}
|
||||
const client = new Client(nodeProxy, { logger: logService });
|
||||
const init = await client.handshake();
|
||||
const buffer = new Uint8Array(await response.arrayBuffer());
|
||||
const rfs = fromTar(buffer);
|
||||
(<any>self).global = self;
|
||||
rfs.provide("vscode", vscode);
|
||||
Object.keys(client.modules).forEach((key) => {
|
||||
const mod = (client.modules as any)[key];
|
||||
if (key === "process") {
|
||||
(<any>self).process = mod;
|
||||
(<any>self).process.env = init.env;
|
||||
return;
|
||||
}
|
||||
|
||||
rfs.provide(key, mod);
|
||||
switch (key) {
|
||||
case "buffer":
|
||||
(<any>self).Buffer = mod.Buffer;
|
||||
break;
|
||||
case "timers":
|
||||
(<any>self).setImmediate = mod.setImmediate;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
activationTimesBuilder.codeLoadingStart();
|
||||
return rfs.require(".");
|
||||
} finally {
|
||||
activationTimesBuilder.codeLoadingStop();
|
||||
}
|
||||
};
|
||||
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",
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Event } from "vs/base/common/event";
|
||||
import { IChannel, IServerChannel } from "vs/base/parts/ipc/common/ipc";
|
||||
import { createDecorator } from "vs/platform/instantiation/common/instantiation";
|
||||
import { ReadWriteConnection } from "vs/server/node_modules/@coder/node-browser/out/common/connection";
|
||||
|
||||
export const INodeProxyService = createDecorator<INodeProxyService>("nodeProxyService");
|
||||
|
||||
export interface INodeProxyService extends ReadWriteConnection {
|
||||
_serviceBrand: any;
|
||||
send(message: string): void;
|
||||
onMessage: Event<string>;
|
||||
onUp: Event<void>;
|
||||
onClose: Event<void>;
|
||||
onDown: Event<void>;
|
||||
}
|
||||
|
||||
export class NodeProxyChannel implements IServerChannel {
|
||||
constructor(private service: INodeProxyService) {}
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
switch (event) {
|
||||
case "onMessage": return this.service.onMessage;
|
||||
}
|
||||
throw new Error(`Invalid listen ${event}`);
|
||||
}
|
||||
|
||||
async call(_: unknown, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case "send": return this.service.send(args[0]);
|
||||
}
|
||||
throw new Error(`Invalid call ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeProxyChannelClient {
|
||||
_serviceBrand: any;
|
||||
|
||||
public readonly onMessage: Event<string>;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
this.onMessage = this.channel.listen<string>("onMessage");
|
||||
}
|
||||
|
||||
public send(data: string): void {
|
||||
this.channel.call("send", [data]);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { ITelemetryData } from "vs/base/common/actions";
|
||||
import { Event } from "vs/base/common/event";
|
||||
import { IChannel, IServerChannel } from "vs/base/parts/ipc/common/ipc";
|
||||
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from "vs/platform/telemetry/common/gdprTypings";
|
||||
import { ITelemetryInfo, ITelemetryService } from "vs/platform/telemetry/common/telemetry";
|
||||
|
||||
export class TelemetryChannel implements IServerChannel {
|
||||
constructor(private service: ITelemetryService) {}
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Invalid listen ${event}`);
|
||||
}
|
||||
|
||||
call(_: unknown, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case "publicLog": return this.service.publicLog(args[0], args[1], args[2]);
|
||||
case "publicLog2": return this.service.publicLog2(args[0], args[1], args[2]);
|
||||
case "setEnabled": return Promise.resolve(this.service.setEnabled(args[0]));
|
||||
case "getTelemetryInfo": return this.service.getTelemetryInfo();
|
||||
}
|
||||
throw new Error(`Invalid call ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class TelemetryChannelClient implements ITelemetryService {
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(private readonly channel: IChannel) {}
|
||||
|
||||
public publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise<void> {
|
||||
return this.channel.call("publicLog", [eventName, data, anonymizeFilePaths]);
|
||||
}
|
||||
|
||||
public publicLog2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>, anonymizeFilePaths?: boolean): Promise<void> {
|
||||
return this.channel.call("publicLog2", [eventName, data, anonymizeFilePaths]);
|
||||
}
|
||||
|
||||
public setEnabled(value: boolean): void {
|
||||
this.channel.call("setEnable", [value]);
|
||||
}
|
||||
|
||||
public getTelemetryInfo(): Promise<ITelemetryInfo> {
|
||||
return this.channel.call("getTelemetryInfo");
|
||||
}
|
||||
|
||||
public get isOptedIn(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,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, ""];
|
||||
};
|
||||
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
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 39 KiB |