Compare commits

...

86 Commits

Author SHA1 Message Date
Anmol Sethi
6f1309795e Rebuild all node_modules on npm install
Stuff like ripgrep needs to be refetched so we cannot bundle
node_modules at all.
2020-05-18 21:37:16 -04:00
Anmol Sethi
5f94d5a687 Release v3.3.1
This release fixes bugs introduced with the release of v3.3.0

- We've reverted to VS Code 1.45.1 due to bugs in 1.46 #1667
- Accessing code-server from a web browser on Windows has been fixed #1642
- Search in project has been fixed #1665
- The glibc requirement on static releases has been lowered to v2.19 #1656
2020-05-18 18:11:52 -04:00
Anmol Sethi
d6b1d0c7ff Merge pull request #1671 from cdr/pin-vscode
Pin to vscode 1.45.1
2020-05-18 18:09:44 -04:00
Anmol Sethi
ce9d14d55e Pin to vscode 1.45.1
1.46 isn't released yet and has bugs.

Closes #1667
2020-05-18 17:34:22 -04:00
Anmol Sethi
eccee53142 Merge pull request #1670 from cdr/windows
Fix paths from Windows client to non-Windows server
2020-05-18 16:14:48 -04:00
Asher
f7f11ad6c2 Fix paths from Windows client to non-Windows server
Fixes #1659
Fixes #1642
2020-05-18 15:06:11 -05:00
Anmol Sethi
0c2381f4ff Merge pull request #1668 from cdr/fix-ci
Workaround for GH Actions stripping permissions
2020-05-18 15:32:11 -04:00
Anmol Sethi
e4ddffd0e2 Workaround for GH Actions stripping permissions
Closes #1665
2020-05-18 13:56:53 -04:00
Anmol Sethi
f5ac262a2f Indicat how to get version in issue template 2020-05-18 13:20:13 -04:00
Anmol Sethi
04cd74cad0 Add more info to issue template
Closes #1655
2020-05-18 13:14:29 -04:00
Anmol Sethi
7b1edd5ad4 Merge pull request #1657 from cdr/downgrade-CI
Downgrade CI to Debian 8 for glibc 2.19
2020-05-18 01:54:46 -04:00
Anmol Sethi
47d50c9163 Downgrade CI to Debian 8 for glibc 2.19
Closes #1656
2020-05-18 00:38:55 -04:00
Anmol Sethi
8a3466e86f More grammar fixes for FAQ 2020-05-17 22:47:23 -04:00
Anmol Sethi
59f0128d27 Shorten static release script horizontally 2020-05-17 21:11:52 -04:00
Anmol Sethi
761c2035c7 Fix grammar in README 2020-05-17 20:47:33 -04:00
Anmol Sethi
094ca2ad97 Link to all other docs in guide.md
I expect people will frequently link to it.
2020-05-17 20:39:45 -04:00
Anmol Sethi
c33a4651c9 Improve static install script 2020-05-17 20:36:10 -04:00
Anmol Sethi
783dfe0a14 Fix typo in installation docs 2020-05-17 20:32:04 -04:00
Anmol Sethi
b9f43c3542 Fix grammar in FAQ 2020-05-17 20:29:00 -04:00
Anmol Sethi
65325eef89 Clarify location of config file in FAQ 2020-05-17 20:27:45 -04:00
Anmol Sethi
2421cab479 Fix grammar in README 2020-05-17 20:25:33 -04:00
Anmol Sethi
ec1c74c146 Add release-images to clean.sh 2020-05-17 19:52:59 -04:00
Anmol Sethi
e955da14fa Merge pull request #1628 from cdr/docs
Revamp docs
2020-05-17 19:46:57 -04:00
Anmol Sethi
52eeccaba1 v3.3.0 2020-05-17 18:35:36 -04:00
Anmol Sethi
3a1e3bc596 Final revisions for the docs before release
🚀
2020-05-17 17:55:28 -04:00
Anmol Sethi
e0dbd8f74a Rename self contained release to static release 2020-05-17 16:59:09 -04:00
Anmol Sethi
6a25b3bfa0 Document structure better
Closes #1648
2020-05-17 16:53:08 -04:00
Anmol Sethi
aee2599904 Push docker manifest in CI for multi arch image 2020-05-16 16:59:26 -04:00
Anmol Sethi
d56381666a Use .tar.gz for macOS releases
No good reason to use .zip, was just confusion on my part.
2020-05-16 10:01:26 -04:00
Anmol Sethi
611cde7202 Fix spelling of Multi-tenancy in FAQ 2020-05-14 22:33:13 -04:00
Anmol Sethi
181bad9563 Improve docker install formatting 2020-05-14 22:33:13 -04:00
Anmol Sethi
73b2ff0945 $PORT should always override port in --bind-addr 2020-05-14 22:33:12 -04:00
Anmol Sethi
89c5a4dfea Set --frozen-lockfile with yarn when necessary 2020-05-14 22:33:12 -04:00
Anmol Sethi
d4b3d21dce Require minimum node 12 2020-05-14 22:33:12 -04:00
Anmol Sethi
40778b15ca Add code-server version into VS Code about
Closes #1506
2020-05-14 22:33:12 -04:00
Anmol Sethi
d7234029e6 Use /usr/local instead of /opt in self contained release example 2020-05-14 20:27:36 -04:00
Anmol Sethi
10b06cae10 Minor typo fixes 2020-05-14 20:08:08 -04:00
Anmol Sethi
0bd2602774 3.3.0 2020-05-14 18:43:01 -04:00
Anmol Sethi
c69346a9a7 Add FAQ entry on the config file 2020-05-14 18:35:35 -04:00
Anmol Sethi
5651201643 Copy old macOS data directory if applicable 2020-05-14 06:12:33 -04:00
Anmol Sethi
f475767c2b Rename darwin releases to macos 2020-05-14 05:59:20 -04:00
Anmol Sethi
a0a77e379e Add doc/guide.md 2020-05-14 05:24:23 -04:00
Anmol Sethi
f4a78587b0 Make npm-postinstall.sh more robust 2020-05-13 22:44:43 -04:00
Anmol Sethi
b3ae4d67d3 Hide bundled node_modules to prevent them from being ignored 2020-05-13 04:17:34 -04:00
Anmol Sethi
d30f3dbdf7 Update to rc.10 2020-05-13 02:37:25 -04:00
Anmol Sethi
1739b21600 Bundle VS Code node_modules to avoid yarn dependency
Many random bizarre issues otherwise.

Also includes misc improvements to docs and scripts.
2020-05-13 02:35:11 -04:00
Anmol Sethi
a346c6d565 Document npm module install dependencies 2020-05-12 23:11:31 -04:00
Anmol Sethi
502c262c82 Mention update of versions in README install examples 2020-05-12 21:26:37 -04:00
Anmol Sethi
4aae5eaeca CI fixes
- Splits up test into fmt, lint and test
- Fixes bug in build-packages.sh
- Minor README.md fixes
2020-05-12 21:26:36 -04:00
Anmol Sethi
41d625abb6 Revamp README.md with new installation options 2020-05-12 21:26:36 -04:00
Anmol Sethi
8626bed4ef Merge pull request #1619 from cdr/config
Add support for a YAML config file
2020-05-12 21:25:44 -04:00
Anmol Sethi
dc632ac176 Remove .yarnrc from lib/vscode 2020-05-13 01:11:53 +00:00
Anmol Sethi
c0d6eb4664 Improve password handling
- Error out if auth is enabled but no password is passed in
- Indicate password location on login page
2020-05-12 19:59:55 -04:00
Anmol Sethi
524b0205e9 Workaround for GH Actions ruining file permissions 2020-05-12 19:59:55 -04:00
Anmol Sethi
1e432b25ea Comment on hash(password) 2020-05-12 19:59:54 -04:00
Anmol Sethi
d6ea9d78f6 Configuration file bug fixes based on @code-asher's review 2020-05-12 19:59:54 -04:00
Anmol Sethi
28edf4af2e Add systemd user service to .deb and .rpm 2020-05-12 19:59:54 -04:00
Anmol Sethi
d288131a33 Fix lint errors 2020-05-12 19:59:54 -04:00
Anmol Sethi
e02d94ad2f Allow password authentication in the config file 2020-05-12 19:59:54 -04:00
Anmol Sethi
4f67f4e096 Disable automatic updates 2020-05-12 19:59:54 -04:00
Anmol Sethi
00d164b67f Add default config file and improve config/data directory detection 2020-05-12 19:59:54 -04:00
Anmol Sethi
c5179c2a06 Add support for a YAML config file 2020-05-12 19:59:53 -04:00
Asher
95ac0ddfb7 Fix paths for Windows
- Fix vscode-remote-resource, #1397.
- Fix double slash on webview, was causing images not to load.
- Fix client-side tar paths.
2020-05-12 13:49:37 -05:00
Anmol Sethi
2e31e8f0c9 Merge pull request #1623 from cdr/automate-release
Automate draft release
2020-05-11 21:00:10 -04:00
Anmol Sethi
169f8c67fe Automate draft release 2020-05-11 20:59:56 -04:00
Anmol Sethi
b706e85efb Merge pull request #1611 from cdr/ci
Automate release process
2020-05-08 16:46:23 -04:00
Anmol Sethi
7c7f62d3f3 Fixes for CI from @code-asher's review 2020-05-08 16:45:59 -04:00
Anmol Sethi
231e31656a Automate release process 2020-05-08 03:26:19 -04:00
Anmol Sethi
4590c3a3db Merge pull request #1607 from cdr/ci
Switch to GH Actions
2020-05-08 02:17:16 -04:00
Anmol Sethi
e9fe4c0466 Document release process 2020-05-08 01:43:31 -04:00
Anmol Sethi
6282cd7e7b Simplify packaging and improve scripts
Much better test now as well.
2020-05-08 01:04:24 -04:00
Anmol Sethi
bc453b5f0d Switch to a single job to build the npm package
The architecture specific jobs pull it in and then build releases.

Much faster!
2020-05-08 00:09:24 -04:00
Anmol Sethi
0ec1c69c06 Switch fully to GH Actions 2020-05-07 23:13:28 -04:00
Anmol Sethi
193a45113c Add back ARM with GH Actions 2020-05-07 00:17:06 -04:00
Anmol Sethi
3e7582880e Make CI faster 2020-05-06 23:15:30 -04:00
Anmol Sethi
c63f1ea62a Merge pull request #1601 from cdr/fixes
Add npm package and document/cleanup CI and build process
2020-05-06 20:53:26 -04:00
Anmol Sethi
1a375a44e0 Disable ARM64 releases as ARM on Travis is very unreliable 2020-05-06 20:32:11 -04:00
Anmol Sethi
be032cf735 Add NPM package, debs, rpms and refactor CI/build process
Closes many issues that I'll prune after adding more docs
for users.
2020-05-06 20:25:52 -04:00
Asher
4875f6aa87 Update VS Code to fix infinite refresh
Fixes #1581.
2020-05-05 12:33:09 -05:00
Asher
0a2f06b296 Update diff command in readme 2020-05-05 12:33:08 -05:00
Asher
6a2662eeee Remove node-pty
This was for the ssh server we removed.
2020-05-05 12:33:08 -05:00
Anmol Sethi
a64f80d2d4 Merge pull request #1602 from cdr/unset-pass
Unset $PASSWORD after grabbing it
2020-05-04 22:48:17 -04:00
Anmol Sethi
1898dea314 Unset $PASSWORD after grabbing it
Closes #1583
2020-05-04 22:41:21 -04:00
Asher
81411b2af9 Fix highlighted scmviewlet items in Firefox
Fixes #1549.
2020-05-01 12:54:48 -05:00
Anmol Sethi
c4b620d69e Merge pull request #1592 from saxc/fix-macOS
fix macOS spelling
2020-05-01 12:15:36 -04:00
saxc
c04befac68 fix macOS 2020-05-01 17:55:38 +02:00
79 changed files with 2921 additions and 1276 deletions

View File

@@ -1,2 +1,3 @@
**
!release
!release-packages
!ci

View File

@@ -22,3 +22,10 @@ rules:
# For overloads.
no-dupe-class-members: off
"@typescript-eslint/no-use-before-define": off
"@typescript-eslint/no-non-null-assertion": off
settings:
# Does not work with CommonJS unfortunately.
import/ignore:
- env-paths
- xdg-basedir

View File

@@ -8,4 +8,11 @@ and include any logging information relevant to the issue.
Please search for existing issues before filing.
Please ensure you cannot reproduce on VS Code before filing.
Please fill in the issue template or we will close your issue!
-->
- Web Browser:
- Local OS:
- Remote OS:
- `code-server --version`:

148
.github/workflows/ci.yaml vendored Normal file
View File

@@ -0,0 +1,148 @@
name: ci
on: [push, pull_request]
jobs:
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run ./ci/steps/fmt.sh
uses: ./ci/container
with:
args: ./ci/steps/fmt.sh
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run ./ci/steps/lint.sh
uses: ./ci/container
with:
args: ./ci/steps/lint.sh
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run ./ci/steps/test.sh
uses: ./ci/container
with:
args: ./ci/steps/test.sh
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run ./ci/steps/release.sh
uses: ./ci/container
with:
args: ./ci/steps/release.sh
- name: Upload npm package artifact
uses: actions/upload-artifact@v2
with:
name: npm-package
path: ./release-npm-package
linux-amd64:
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Download npm package
uses: actions/download-artifact@v2
with:
name: npm-package
path: ./release-npm-package
- name: Run ./ci/steps/release-static.sh
uses: ./ci/container
with:
args: ./ci/steps/release-static.sh
- name: Upload release artifacts
uses: actions/upload-artifact@v2
with:
name: release-packages
path: ./release-packages
linux-arm64:
needs: release
runs-on: ubuntu-arm64-latest
steps:
- uses: actions/checkout@v1
- name: Download npm package
uses: actions/download-artifact@v2
with:
name: npm-package
path: ./release-npm-package
- name: Run ./ci/steps/release-static.sh
uses: ./ci/container/arm64
with:
args: ./ci/steps/release-static.sh
- name: Upload release artifacts
uses: actions/upload-artifact@v2
with:
name: release-packages
path: ./release-packages
macos-amd64:
needs: release
runs-on: macos-latest
steps:
- uses: actions/checkout@v1
- name: Download npm package
uses: actions/download-artifact@v2
with:
name: npm-package
path: ./release-npm-package
- run: brew unlink node@12
- run: brew install node
- run: ./ci/steps/release-static.sh
env:
# Otherwise we get rate limited when fetching the ripgrep binary.
# For whatever reason only MacOS needs it.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload release artifacts
uses: actions/upload-artifact@v2
with:
name: release-packages
path: ./release-packages
docker-amd64:
runs-on: ubuntu-latest
needs: linux-amd64
steps:
- uses: actions/checkout@v1
- name: Download release package
uses: actions/download-artifact@v2
with:
name: release-packages
path: ./release-packages
- name: Run ./ci/steps/build-docker-image.sh
uses: ./ci/container
with:
args: ./ci/steps/build-docker-image.sh
- name: Upload release image
uses: actions/upload-artifact@v2
with:
name: release-images
path: ./release-images
docker-arm64:
runs-on: ubuntu-arm64-latest
needs: linux-arm64
steps:
- uses: actions/checkout@v1
- name: Download release package
uses: actions/download-artifact@v2
with:
name: release-packages
path: ./release-packages
- name: Run ./ci/steps/build-docker-image.sh
uses: ./ci/container/arm64
with:
args: ./ci/steps/build-docker-image.sh
- name: Upload release image
uses: actions/upload-artifact@v2
with:
name: release-images
path: ./release-images

31
.github/workflows/publish.yaml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: publish
on:
release:
types: [published]
jobs:
npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run ./ci/steps/publish-npm.sh
uses: ./ci/container
with:
args: ./ci/steps/publish-npm.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run ./ci/steps/push-docker-manifest.sh
uses: ./ci/container
with:
args: ./ci/steps/push-docker-manifest.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

10
.gitignore vendored
View File

@@ -1,9 +1,11 @@
*.tsbuildinfo
.tsbuildinfo
.cache
build
dist*
out*
release/
release-upload/
release-npm-package/
release-static/
release-packages/
release-gcp/
release-images/
node_modules
binaries

View File

@@ -1,58 +0,0 @@
language: minimal
jobs:
include:
- name: Test
if: tag IS blank
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
language: node_js
node_js: 12
script: yarn && yarn vscode && travis_wait 60 ci/release.sh && ./ci/build-test.sh
before_deploy:
- echo "$JSON_KEY" | base64 --decode > ./ci/key.json
deploy:
- provider: releases
edge: true
draft: true
overwrite: true
tag_name: $TRAVIS_TAG
target_commitish: $TRAVIS_COMMIT
name: $TRAVIS_TAG
file:
- release/*.tar.gz
- release/*.zip
on:
tags: true
- provider: gcs
edge: true
bucket: "codesrv-ci.cdr.sh"
upload_dir: "releases"
key_file: ./ci/key.json
local_dir: release-upload
on:
tags: true
# TODO: The gcs provider fails to install on arm64.
condition: $TRAVIS_CPU_ARCH = amd64
cache:
timeout: 600
yarn: true
directories:
- lib/vscode/.build/extensions

108
README.md
View File

@@ -1,48 +1,96 @@
# code-server
`code-server` is [VS Code](https://github.com/Microsoft/vscode) running on a
remote server, accessible through the browser.
Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and access it in the browser.
Try it out:
```bash
docker run -it -p 127.0.0.1:8080:8080 -v "$PWD:/home/coder/project" -u "$(id -u):$(id -g)" codercom/code-server:latest
```
- **Code anywhere:** Code on your Chromebook, tablet, and laptop with a
- **Code everywhere:** 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.
- **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 tasks runs on your server.
Make use of a spare computer you have lying around and turn it into a full development environment.
![Example gif](/doc/assets/code-server.gif)
![Example gif](./doc/assets/code-server.gif)
## Getting Started
### Requirements
For a full setup and walkthrough, please see [./doc/guide.md](./doc/guide.md).
- 64-bit host.
- At least 1GB of RAM.
- 2 cores or more are recommended (1 core works but not optimally).
- Secure connection over HTTPS or localhost (required for service workers and
clipboard support).
- For Linux: GLIBC 2.17 or later and GLIBCXX 3.4.15 or later.
### Debian, Ubuntu
### Run over SSH
```bash
curl -sSOL https://github.com/cdr/code-server/releases/download/v3.3.1/code-server_3.3.1_amd64.deb
sudo dpkg -i code-server_3.3.1_amd64.deb
systemctl --user enable --now code-server
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
```
Use [sshcode](https://github.com/codercom/sshcode) for a simple setup.
### Fedora, Red Hat, SUSE
### Digital Ocean
```bash
curl -sSOL https://github.com/cdr/code-server/releases/download/v3.3.1/code-server-3.3.1-amd64.rpm
sudo yum install -y code-server-3.3.1-amd64.rpm
systemctl --user enable --now code-server
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
```
[![Create a Droplet](./doc/assets/droplet.svg)](https://marketplace.digitalocean.com/apps/code-server)
### npm
### Releases
We recommend installing from `npm` if we don't have a precompiled release for your machine's
platform or architecture or your glibc < v2.19.
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`.
**note:** Installing via `npm` builds native modules on install and so requires C dependencies.
See [./doc/npm.md](./doc/npm.md) for installing these dependencies.
You will need at least node v12 installed. See [#1633](https://github.com/cdr/code-server/issues/1633).
```bash
npm install -g code-server
code-server
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
```
### macOS
```bash
brew install code-server
brew services start code-server
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
```
### Docker
```bash
# This will start a code-server container and expose it at http://127.0.0.1:8080.
# It will also mount your current directory into the container as `/home/coder/project`
# and forward your UID/GID so that all file system operations occur as your user outside
# the container.
docker run -it -p 127.0.0.1:8080:8080 \
-v "$PWD:/home/coder/project" \
-u "$(id -u):$(id -g)" \
codercom/code-server:latest
```
### Static Releases
We publish self contained `.tar.gz` archives for every release on [github](https://github.com/cdr/code-server/releases).
They bundle the node binary and node_modules.
1. Download the latest release archive for your system from [github](https://github.com/cdr/code-server/releases).
2. Unpack the release.
3. You can run code-server by executing `./bin/code-server`.
Add the code-server `bin` directory to your `$PATH` to easily execute `code-server` without the full path every time.
Here is an example script for installing and using a static `code-server` release on Linux:
```bash
curl -sSL https://github.com/cdr/code-server/releases/download/v3.3.1/code-server-3.3.1-linux-amd64.tar.gz \
| sudo tar -C /usr/local -xz
sudo mv /usr/local/code-server-3.3.1-linux-amd64 /usr/local/code-server-3.3.1
PATH="/usr/local/code-server-3.3.1/bin:$PATH"
code-server
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
```
## FAQ
@@ -54,5 +102,5 @@ See [./doc/CONTRIBUTING.md](./doc/CONTRIBUTING.md).
## Enterprise
Visit [our enterprise page](https://coder.com) for more information about our
Visit [our website](https://coder.com) for more information about our
enterprise offerings.

137
ci/README.md Normal file
View File

@@ -0,0 +1,137 @@
# ci
This directory contains scripts used for code-server's continuous integration infrastructure.
Some of these scripts contain more detailed documentation and options
in header comments.
Any file or directory in this subdirectory should be documented here.
- [./ci/lib.sh](./lib.sh)
- Contains code duplicated across these scripts.
## Publishing a release
Make sure you have `$GITHUB_TOKEN` set and [hub](https://github.com/github/hub) installed.
1. Update the version of code-server in `package.json` and README.md/guide.md install examples and make a PR.
2. GitHub actions will generate the `npm-package`, `release-packages` and `release-images` artifacts.
3. Run `yarn release:github-draft` to create a GitHub draft release from the template with
the updated version.
1. Summarize the major changes in the release notes and link to the relevant issues.
4. Wait for the artifacts in step 2 to build.
5. Run `yarn release:github-assets` to download the `release-packages` artifact and then
upload them to the draft release.
6. Run some basic sanity tests on one of the released packages.
7. Make sure the github release tag is the commit with the artifacts.
8. Publish the release and merge the PR.
1. CI will automatically grab the artifacts and then:
1. Publish the NPM package from `npm-package`.
2. Publish the Docker Hub image from `release-images`.
9. Update the homebrew and AUR packages.
## dev
This directory contains scripts used for the development of code-server.
- [./ci/dev/container](./dev/container)
- See [./doc/CONTRIBUTING.md](../doc/CONTRIBUTING.md) for docs on the development container.
- [./ci/dev/fmt.sh](./dev/fmt.sh) (`yarn fmt`)
- Runs formatters.
- [./ci/dev/lint.sh](./dev/lint.sh) (`yarn lint`)
- Runs linters.
- [./ci/dev/test.sh](./dev/test.sh) (`yarn test`)
- Runs tests.
- [./ci/dev/ci.sh](./dev/ci.sh) (`yarn ci`)
- Runs `yarn fmt`, `yarn lint` and `yarn test`.
- [./ci/dev/vscode.sh](./dev/vscode.sh) (`yarn vscode`)
- Ensures [./lib/vscode](../lib/vscode) is cloned, patched and dependencies are installed.
- [./ci/dev/patch-vscode.sh](./dev/patch-vscode.sh) (`yarn vscode:patch`)
- Applies [./ci/dev/vscode.patch](./dev/vscode.patch) to [./lib/vscode](../lib/vscode).
- [./ci/dev/diff-vscode.sh](./dev/diff-vscode.sh) (`yarn vscode:diff`)
- Diffs [./lib/vscode](../lib/vscode) into [./ci/dev/vscode.patch](./dev/vscode.patch).
- [./ci/dev/vscode.patch](./dev/vscode.patch)
- Our patch of VS Code, see [./doc/CONTRIBUTING.md](../doc/CONTRIBUTING.md#vs-code-patch).
- Generate it with `yarn vscode:diff` and apply with `yarn vscode:patch`.
- [./ci/dev/watch.ts](./dev/watch.ts) (`yarn watch`)
- Starts a process to build and launch code-server and restart on any code changes.
- Example usage in [./doc/CONTRIBUTING.md](../doc/CONTRIBUTING.md).
## build
This directory contains the scripts used to build and release code-server.
You can disable minification by setting `MINIFY=`.
- [./ci/build/build-code-server.sh](./build/build-code-server.sh) (`yarn build`)
- Builds code-server into `./out` and bundles the frontend into `./dist`.
- [./ci/build/build-vscode.sh](./build/build-vscode.sh) (`yarn build:vscode`)
- Builds vscode into `./lib/vscode/out-vscode`.
- [./ci/build/build-release.sh](./build/build-release.sh) (`yarn release`)
- Bundles the output of the above two scripts into a single node module at `./release`.
- [./ci/build/build-static-release.sh](./build/build-static-release.sh) (`yarn release:static`)
- Requires a node module already built into `./release` with the above script.
- Will build a static release with node and node_modules bundled into `./release-static`.
- [./ci/build/clean.sh](./build/clean.sh) (`yarn clean`)
- Removes all build artifacts.
- Will also `git reset --hard lib/vscode`.
- Useful to do a clean build.
- [./ci/build/code-server.sh](./build/code-server.sh)
- Copied into static releases to run code-server with the bundled node binary.
- [./ci/build/test-static-release.sh](./build/test-static-release.sh) (`yarn test:static-release`)
- Ensures code-server in the `./release-static` directory works by installing an extension.
- [./ci/build/build-packages.sh](./build/build-packages.sh) (`yarn package`)
- Packages `./release-static` into a `.tar.gz` archive in `./release-packages`.
- If on linux, [nfpm](https://github.com/goreleaser/nfpm) is used to generate `.deb` and `.rpm`.
- [./ci/build/nfpm.yaml](./build/nfpm.yaml)
- Used to configure [nfpm](https://github.com/goreleaser/nfpm) to generate `.deb` and `.rpm`.
- [./ci/build/code-server-nfpm.sh](./build/code-server-nfpm.sh)
- Entrypoint script for code-server for `.deb` and .rpm`.
- [./ci/build/code-server.service](./build/code-server.service)
- systemd user service packaged into the `.deb` and `.rpm`.
- [./ci/build/release-github-draft.sh](./build/release-github-draft.sh) (`yarn release:github-draft`)
- Uses [hub](https://github.com/github/hub) to create a draft release with a template description.
- [./ci/build/release-github-assets.sh](./build/release-github-assets.sh) (`yarn release:github-assets`)
- Downloads the release-package artifacts for the current commit from CI.
- Uses [hub](https://github.com/github/hub) to upload the artifacts to the release
specified in `package.json`.
- [./ci/build/npm-postinstall.sh](./build/npm-postinstall.sh)
- Post install script for the npm package.
- Bundled by`yarn release`.
## release-container
This directory contains the release docker container.
- [./release-container/build.sh](./release-container/build.sh)
- Builds the release container with the tag `codercom/code-server-$ARCH:$VERSION`.
- Assumes debian releases are ready in `./release-packages`.
## container
This directory contains the container for CI.
## steps
This directory contains the scripts used in CI.
Helps avoid clobbering the CI configuration.
- [./steps/fmt.sh](./steps/fmt.sh)
- Runs `yarn fmt` after ensuring VS Code is patched.
- [./steps/lint.sh](./steps/lint.sh)
- Runs `yarn lint` after ensuring VS Code is patched.
- [./steps/test.sh](./steps/test.sh)
- Runs `yarn test` after ensuring VS Code is patched.
- [./steps/release.sh](./steps/release.sh)
- Runs the release process.
- Generates the npm package at `./release`.
- [./steps/release-static.sh](./steps/release-static.sh)
- Takes the output of the previous script and generates a static release and
release packages into `release-packages`.
- [./steps/publish-npm.sh](./steps/publish-npm.sh)
- Grabs the `npm-package` release artifact for the current commit and publishes it on npm.
- [./steps/build-docker-image.sh](./steps/build-docker-image.sh)
- Builds the docker image and then saves it into `./release-images/code-server-$ARCH-$VERSION.tar`.
- [./steps/push-docker-manifest.sh](./steps/push-docker-manifest.sh)
- Loads all images in `./release-images` and then builds and pushes a multi architecture
docker manifest for the amd64 and arm64 images to `codercom/code-server:$VERSION` and
`codercom/code-server:latest`.

View File

@@ -1,21 +0,0 @@
#!/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 "$@"

View File

@@ -1,372 +0,0 @@
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)

29
ci/build/build-code-server.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
# Builds code-server into out and the frontend into dist.
# MINIFY controls whether parcel minifies dist.
MINIFY=${MINIFY-true}
main() {
cd "$(dirname "${0}")/../.."
npx tsc --outDir out --tsBuildInfoFile .cache/out.tsbuildinfo
# If out/node/entry.js does not already have the shebang,
# we make sure to add it and make it executable.
if ! grep -q -m1 "^#!/usr/bin/env node" out/node/entry.js; then
sed -i.bak "1s;^;#!/usr/bin/env node\n;" out/node/entry.js && rm out/node/entry.js.bak
chmod +x out/node/entry.js
fi
npx parcel build \
--public-url "/static/$(git rev-parse HEAD)/dist" \
--out-dir dist \
$([[ $MINIFY ]] || echo --no-minify) \
src/browser/pages/app.ts \
src/browser/register.ts \
src/browser/serviceWorker.ts
}
main "$@"

46
ci/build/build-packages.sh Executable file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -euo pipefail
# Packages code-server for the current OS and architecture into ./release-packages.
# This script assumes that a static release is built already into ./release-static.
main() {
cd "$(dirname "${0}")/../.."
source ./ci/lib.sh
local release_name="code-server-$VERSION-$OS-$ARCH"
mkdir -p release-packages
if [[ $OS == "linux" ]]; then
tar -czf "release-packages/$release_name.tar.gz" --transform "s/^\.\/release-static/$release_name/" ./release-static
else
tar -czf "release-packages/$release_name.tar.gz" -s "/^release-static/$release_name/" release-static
fi
echo "done (release-packages/$release_name)"
release_gcp
if [[ $OSTYPE == linux* ]]; then
release_nfpm
fi
}
release_gcp() {
mkdir -p "release-gcp/$VERSION"
cp "release-packages/$release_name.tar.gz" "./release-gcp/$VERSION/$OS-$ARCH.tar.gz"
mkdir -p "release-gcp/latest"
cp "./release-packages/$release_name.tar.gz" "./release-gcp/latest/$OS-$ARCH.tar.gz"
}
# Generates deb and rpm packages.
release_nfpm() {
local nfpm_config
nfpm_config=$(envsubst < ./ci/build/nfpm.yaml)
# The underscores are convention for .deb.
nfpm pkg -f <(echo "$nfpm_config") --target "release-packages/code-server_${VERSION}_${ARCH}.deb"
nfpm pkg -f <(echo "$nfpm_config") --target "release-packages/code-server-$VERSION-$ARCH.rpm"
}
main "$@"

80
ci/build/build-release.sh Executable file
View File

@@ -0,0 +1,80 @@
#!/usr/bin/env bash
set -euo pipefail
# This script requires vscode to be built with matching MINIFY.
# MINIFY controls whether minified vscode is bundled.
MINIFY="${MINIFY-true}"
main() {
cd "$(dirname "${0}")/../.."
source ./ci/lib.sh
VSCODE_SRC_PATH="lib/vscode"
VSCODE_OUT_PATH="$RELEASE_PATH/lib/vscode"
mkdir -p "$RELEASE_PATH"
bundle_code_server
bundle_vscode
rsync README.md "$RELEASE_PATH"
rsync LICENSE.txt "$RELEASE_PATH"
rsync ./lib/vscode/ThirdPartyNotices.txt "$RELEASE_PATH"
}
bundle_code_server() {
rsync out dist "$RELEASE_PATH"
# For source maps and images.
mkdir -p "$RELEASE_PATH/src/browser"
rsync src/browser/media/ "$RELEASE_PATH/src/browser/media"
mkdir -p "$RELEASE_PATH/src/browser/pages"
rsync src/browser/pages/*.html "$RELEASE_PATH/src/browser/pages"
# Adds the commit to package.json
jq --slurp '.[0] * .[1]' package.json <(
cat << EOF
{
"commit": "$(git rev-parse HEAD)",
"scripts": {
"postinstall": "./postinstall.sh"
}
}
EOF
) > "$RELEASE_PATH/package.json"
rsync yarn.lock "$RELEASE_PATH"
rsync ci/build/npm-postinstall.sh "$RELEASE_PATH/postinstall.sh"
}
bundle_vscode() {
mkdir -p "$VSCODE_OUT_PATH"
rsync "$VSCODE_SRC_PATH/yarn.lock" "$VSCODE_OUT_PATH"
rsync "$VSCODE_SRC_PATH/out-vscode${MINIFY+-min}/" "$VSCODE_OUT_PATH/out"
rsync "$VSCODE_SRC_PATH/.build/extensions/" "$VSCODE_OUT_PATH/extensions"
rm -Rf "$VSCODE_OUT_PATH/extensions/node_modules"
rsync "$VSCODE_SRC_PATH/extensions/package.json" "$VSCODE_OUT_PATH/extensions"
rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions"
rsync "$VSCODE_SRC_PATH/extensions/postinstall.js" "$VSCODE_OUT_PATH/extensions"
mkdir -p "$VSCODE_OUT_PATH/resources/linux"
rsync "$VSCODE_SRC_PATH/resources/linux/code.png" "$VSCODE_OUT_PATH/resources/linux/code.png"
# Adds the commit and date to product.json
jq --slurp '.[0] * .[1]' "$VSCODE_SRC_PATH/product.json" <(
cat << EOF
{
"commit": "$(git rev-parse HEAD)",
"date": $(jq -n 'now | todate')
}
EOF
) > "$VSCODE_OUT_PATH/product.json"
# We remove the scripts field so that later on we can run
# yarn to fetch node_modules if necessary without build scripts running.
# We cannot use --no-scripts because we still want dependant package scripts to run.
jq 'del(.scripts)' < "$VSCODE_SRC_PATH/package.json" > "$VSCODE_OUT_PATH/package.json"
}
main "$@"

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "${0}")/../.."
source ./ci/lib.sh
rsync "$RELEASE_PATH/" "$RELEASE_PATH-static"
RELEASE_PATH+=-static
# We cannot find the path to node from $PATH because yarn shims a script to ensure
# we use the same version it's using so we instead run a script with yarn that
# will print the path to node.
local node_path
node_path="$(yarn -s node <<< 'console.info(process.execPath)')"
mkdir -p "$RELEASE_PATH/bin"
rsync ./ci/build/code-server.sh "$RELEASE_PATH/bin/code-server"
rsync "$node_path" "$RELEASE_PATH/lib/node"
cd "$RELEASE_PATH"
yarn --production --frozen-lockfile
}
main "$@"

21
ci/build/build-vscode.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
# Builds vscode into lib/vscode/out-vscode.
# MINIFY controls whether a minified version of vscode is built.
MINIFY=${MINIFY-true}
main() {
cd "$(dirname "${0}")/../.."
cd lib/vscode
yarn gulp compile-build
yarn gulp compile-extensions-build
yarn gulp optimize --gulpfile ./coder.js
if [[ $MINIFY ]]; then
yarn gulp minify --gulpfile ./coder.js
fi
}
main "$@"

25
ci/build/clean.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "${0}")/../.."
source ./ci/lib.sh
rm -Rf \
out \
release \
release-static \
release-packages \
release-gcp \
release-images/ \
dist \
.tsbuildinfo \
.cache/out.tsbuildinfo
pushd lib/vscode
git clean -xffd
git reset --hard
popd
}
main "$@"

3
ci/build/code-server-nfpm.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env sh
exec /usr/lib/code-server/bin/code-server "$@"

View File

@@ -0,0 +1,11 @@
[Unit]
Description=code-server
After=network.target
[Service]
Type=exec
ExecStart=/usr/bin/code-server
Restart=always
[Install]
WantedBy=default.target

View File

@@ -1,18 +1,20 @@
#!/usr/bin/env sh
# This script is intended to be bundled into the static releases.
# Runs code-server with the bundled Node binary.
# More complicated than readlink -f or realpath to support macOS.
# See https://github.com/cdr/code-server/issues/1537
get_installation_dir() {
bin_dir() {
# We read the symlink, which may be relative from $0.
dst="$(readlink "$0")"
# We cd into the $0 directory.
cd "$(dirname "$0")"
cd "$(dirname "$0")" || exit 1
# Now we can cd into the dst directory.
cd "$(dirname "$dst")"
cd "$(dirname "$dst")" || exit 1
# Finally we use pwd -P to print the absolute path of the directory of $dst.
pwd -P
pwd -P || exit 1
}
dir=$(get_installation_dir)
exec "$dir/node" "$dir/out/node/entry.js" "$@"
BIN_DIR=$(bin_dir)
exec "$BIN_DIR/../lib/node" "$BIN_DIR/.." "$@"

17
ci/build/nfpm.yaml Normal file
View File

@@ -0,0 +1,17 @@
name: "code-server"
arch: "${ARCH}"
platform: "linux"
version: "v${VERSION}"
section: "devel"
priority: "optional"
maintainer: "Anmol Sethi <hi@nhooyr.io>"
description: |
Run VS Code in the browser.
vendor: "Coder"
homepage: "https://github.com/cdr/code-server"
license: "MIT"
bindir: "/usr/bin"
files:
./ci/build/code-server-nfpm.sh: /usr/bin/code-server
./ci/build/code-server.service: /usr/lib/systemd/user/code-server.service
./release-static/**/*: "/usr/lib/code-server/"

41
ci/build/npm-postinstall.sh Executable file
View File

@@ -0,0 +1,41 @@
#!/usr/bin/env sh
set -eu
main() {
# Grabs the major version of node from $npm_config_user_agent which looks like
# yarn/1.21.1 npm/? node/v14.2.0 darwin x64
major_node_version=$(echo "$npm_config_user_agent" | sed -n 's/.*node\/v\([^.]*\).*/\1/p')
if [ "$major_node_version" -lt 12 ]; then
echo "code-server currently requires at least node v12"
echo "We have detected that you are on node v$major_node_version"
echo "See https://github.com/cdr/code-server/issues/1633"
exit 1
fi
case "${npm_config_user_agent-}" in npm*)
# We are running under npm.
if [ "${npm_config_unsafe_perm-}" != "true" ]; then
echo "Please pass --unsafe-perm to npm to install code-server"
echo "Otherwise the postinstall script does not have permissions to run"
echo "See https://docs.npmjs.com/misc/config#unsafe-perm"
echo "See https://stackoverflow.com/questions/49084929/npm-sudo-global-installation-unsafe-perm"
exit 1
fi
;;
esac
if ! vscode_yarn; then
echo "You may not have the required dependencies to build the native modules."
echo "Please see https://github.com/cdr/code-server/blob/master/doc/npm.md"
exit 1
fi
}
vscode_yarn() {
cd lib/vscode
yarn --production --frozen-lockfile
cd extensions
yarn --production --frozen-lockfile
}
main "$@"

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
# Downloads the release artifacts from CI for the current
# commit and then uploads them to the release with the version
# in package.json.
# You will need $GITHUB_TOKEN set.
main() {
cd "$(dirname "$0")/../.."
source ./ci/lib.sh
download_artifact release-packages ./release-packages
local assets=(./release-packages/*)
for i in "${!assets[@]}"; do
assets[$i]="--attach=${assets[$i]}"
done
EDITOR=true hub release edit --draft "${assets[@]}" "v$VERSION"
}
main "$@"

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
# Creates a draft release with the template for the version in package.json
main() {
cd "$(dirname "$0")/../.."
source ./ci/lib.sh
hub release create \
--file - \
-t "$(git rev-parse HEAD)" \
--draft "${assets[@]}" "v$VERSION" << EOF
v$VERSION
VS Code v$(vscode_version)
- Summarize changes here with references to issues
EOF
}
main "$@"

27
ci/build/test-static-release.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
# Makes sure the release works.
# This is to make sure we don't have Node version errors or any other
# compilation-related errors.
main() {
cd "$(dirname "${0}")/../.."
local EXTENSIONS_DIR
EXTENSIONS_DIR="$(mktemp -d)"
echo "Testing static release"
./release-static/bin/code-server --extensions-dir "$EXTENSIONS_DIR" --install-extension ms-python.python
local installed_extensions
installed_extensions="$(./release-static/bin/code-server --extensions-dir "$EXTENSIONS_DIR" --list-extensions 2>&1)"
if [[ $installed_extensions != "ms-python.python" ]]; then
echo "Unexpected output from listing extensions:"
echo "$installed_extensions"
exit 1
fi
echo "Static release works correctly"
}
main "$@"

View File

@@ -1,11 +0,0 @@
#!/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 "$@"

53
ci/container/Dockerfile Normal file
View File

@@ -0,0 +1,53 @@
FROM debian:8
RUN apt-get update
# Needed for debian repositories added below.
RUN apt-get install -y curl gnupg
# Installs node.
RUN curl -sSL https://deb.nodesource.com/setup_14.x | bash - && \
apt-get install -y nodejs
# Installs yarn.
RUN curl -sSL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && apt-get install -y yarn
# Installs VS Code build deps.
RUN apt-get install -y build-essential \
libsecret-1-dev \
libx11-dev \
libxkbfile-dev
# Installs envsubst.
RUN apt-get install -y gettext-base
# Misc build dependencies.
RUN apt-get install -y git rsync
# We need latest jq from debian buster for date support.
RUN ARCH="$(dpkg --print-architecture)" && \
curl -sSOL http://http.us.debian.org/debian/pool/main/libo/libonig/libonig5_6.9.1-1_$ARCH.deb && \
dpkg -i libonig*.deb && \
curl -sSOL http://http.us.debian.org/debian/pool/main/j/jq/libjq1_1.5+dfsg-2+b1_$ARCH.deb && \
dpkg -i libjq*.deb && \
curl -sSOL http://http.us.debian.org/debian/pool/main/j/jq/jq_1.5+dfsg-2+b1_$ARCH.deb && \
dpkg -i jq*.deb && rm *.deb
# Installs shellcheck.
# Unfortunately coredumps on debian:8 so disabled for now.
#RUN curl -sSL https://github.com/koalaman/shellcheck/releases/download/v0.7.1/shellcheck-v0.7.1.linux.$(uname -m).tar.xz | \
# tar -xJ && \
# mv shellcheck*/shellcheck /usr/local/bin && \
# rm -R shellcheck*
# Install Go dependencies
RUN ARCH="$(dpkg --print-architecture)" && \
curl -sSL "https://dl.google.com/go/go1.14.3.linux-$ARCH.tar.gz" | tar -C /usr/local -xz
ENV PATH=/usr/local/go/bin:/root/go/bin:$PATH
ENV GO111MODULE=on
RUN go get mvdan.cc/sh/v3/cmd/shfmt
RUN go get github.com/goreleaser/nfpm/cmd/nfpm
RUN curl -fsSL https://get.docker.com | sh

View File

@@ -0,0 +1,53 @@
FROM debian:9
RUN apt-get update
# Needed for debian repositories added below.
RUN apt-get install -y curl gnupg
# Installs node.
RUN curl -sSL https://deb.nodesource.com/setup_14.x | bash - && \
apt-get install -y nodejs
# Installs yarn.
RUN curl -sSL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && apt-get install -y yarn
# Installs VS Code build deps.
RUN apt-get install -y build-essential \
libsecret-1-dev \
libx11-dev \
libxkbfile-dev
# Installs envsubst.
RUN apt-get install -y gettext-base
# Misc build dependencies.
RUN apt-get install -y git rsync
# We need latest jq from debian buster for date support.
RUN ARCH="$(dpkg --print-architecture)" && \
curl -sSOL http://http.us.debian.org/debian/pool/main/libo/libonig/libonig5_6.9.1-1_$ARCH.deb && \
dpkg -i libonig*.deb && \
curl -sSOL http://http.us.debian.org/debian/pool/main/j/jq/libjq1_1.5+dfsg-2+b1_$ARCH.deb && \
dpkg -i libjq*.deb && \
curl -sSOL http://http.us.debian.org/debian/pool/main/j/jq/jq_1.5+dfsg-2+b1_$ARCH.deb && \
dpkg -i jq*.deb && rm *.deb
# Installs shellcheck.
# Unfortunately coredumps on debian:8 so disabled for now.
#RUN curl -sSL https://github.com/koalaman/shellcheck/releases/download/v0.7.1/shellcheck-v0.7.1.linux.$(uname -m).tar.xz | \
# tar -xJ && \
# mv shellcheck*/shellcheck /usr/local/bin && \
# rm -R shellcheck*
# Install Go dependencies
RUN ARCH="$(dpkg --print-architecture)" && \
curl -sSL "https://dl.google.com/go/go1.14.3.linux-$ARCH.tar.gz" | tar -C /usr/local -xz
ENV PATH=/usr/local/go/bin:/root/go/bin:$PATH
ENV GO111MODULE=on
RUN go get mvdan.cc/sh/v3/cmd/shfmt
RUN go get github.com/goreleaser/nfpm/cmd/nfpm
RUN curl -fsSL https://get.docker.com | sh

View File

@@ -0,0 +1,6 @@
# arm64
Unfortunately there is no arm64 build of `debian:8` so
we need to use `debian:9` instead.
This is just an exact copy of [../Dockerfile](../Dockerfile) with the base image change.

View File

@@ -1,49 +0,0 @@
#!/usr/bin/env bash
# exec.sh opens an interactive bash session inside of a docker container
# for improved isolation during development
# if the container exists it is restarted if necessary, then reused
set -euo pipefail
cd "$(dirname "$0")"
# Ensure submodules are cloned and up to date.
git submodule update --init
container_name=code-server-dev
enter() {
echo "--- Entering $container_name"
docker exec -it $container_id /bin/bash
}
run() {
echo "--- Spawning $container_name"
container_id=$(docker run \
-it \
--name $container_name \
"-v=$PWD:/code-server" \
"-w=/code-server" \
"-p=127.0.0.1:8080:8080" \
$([[ -t 0 ]] && echo -it || true) \
$container_name)
}
build() {
echo "--- Building $container_name"
cd ../../
docker build -t $container_name -f ./ci/dev-image/Dockerfile . > /dev/null
}
container_id=$(docker container inspect --format="{{.Id}}" $container_name 2> /dev/null) || true
if [ "$container_id" != "" ]; then
echo "-- Starting container"
docker start $container_id > /dev/null
enter
exit 0
fi
build
run
enter

View File

@@ -2,7 +2,7 @@
set -euo pipefail
main() {
cd "$(dirname "$0")/.."
cd "$(dirname "$0")/../.."
yarn fmt
yarn lint

48
ci/dev/container/exec.sh Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
# Opens an interactive bash session inside of a docker container
# for improved isolation during development.
# If the container exists it is restarted if necessary, then reused.
main() {
cd "$(dirname "${0}")/../../.."
local container_name=code-server-dev
if docker inspect $container_name &> /dev/null; then
echo "-- Starting container"
docker start "$container_name" > /dev/null
enter
exit 0
fi
build
run
enter
}
enter() {
echo "--- Entering $container_name"
docker exec -it "$container_name" /bin/bash
}
run() {
echo "--- Spawning $container_name"
docker run \
-it \
--name $container_name \
"-v=$PWD:/code-server" \
"-w=/code-server" \
"-p=127.0.0.1:8080:8080" \
$(if [[ -t 0 ]]; then echo -it; fi) \
"$container_name"
}
build() {
echo "--- Building $container_name"
docker build -t $container_name ./ci/dev/container > /dev/null
}
main "$@"

12
ci/dev/diff-vscode.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
cd ./lib/vscode
git add -A
git diff HEAD > ../../ci/dev/vscode.patch
}
main "$@"

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
shfmt -i 2 -w -s -sr $(git ls-files "*.sh")
local prettierExts
@@ -20,6 +21,9 @@ main() {
)
prettier --write --loglevel=warn $(git ls-files "${prettierExts[@]}")
doctoc --title '# FAQ' doc/FAQ.md > /dev/null
doctoc --title '# Setup Guide' doc/guide.md > /dev/null
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'

View File

@@ -1,11 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js")
stylelint $(git ls-files "*.css")
tsc --noEmit
# See comment in ./ci/container/Dockerfile
if [[ ! ${CI-} ]]; then
shellcheck -e SC2046,SC2164,SC2154 $(git ls-files "*.sh")
fi
}
main "$@"

11
ci/dev/patch-vscode.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
cd ./lib/vscode
git apply ../../ci/dev/vscode.patch
}
main "$@"

10
ci/dev/test.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
mocha -r ts-node/register ./test/*.test.ts
}
main "$@"

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ set -euo pipefail
# 2. Patches it.
# 3. Installs it.
main() {
cd "$(dirname "$0")/.."
cd "$(dirname "$0")/../.."
git submodule update --init
@@ -15,7 +15,7 @@ main() {
(
cd lib/vscode
# Install VS Code dependencies.
yarn
yarn ${CI+--frozen-lockfile}
)
}

163
ci/dev/watch.ts Normal file
View File

@@ -0,0 +1,163 @@
import * as cp from "child_process"
import Bundler from "parcel-bundler"
import * as path from "path"
async function main(): Promise<void> {
try {
const watcher = new Watcher()
await watcher.watch()
} catch (error) {
console.error(error.message)
process.exit(1)
}
}
class Watcher {
private readonly rootPath = path.resolve(__dirname, "../..")
private readonly vscodeSourcePath = path.join(this.rootPath, "lib/vscode")
private static log(message: string, skipNewline = false): void {
process.stdout.write(message)
if (!skipNewline) {
process.stdout.write("\n")
}
}
public 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(2))
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 => {
Watcher.log("killing vs code watcher")
vscode.removeAllListeners()
vscode.kill()
Watcher.log("killing tsc")
tsc.removeAllListeners()
tsc.kill()
if (server) {
Watcher.log("killing server")
server.removeAllListeners()
server.kill()
}
Watcher.log("killing bundler")
process.exit(code || 0)
}
process.on("SIGINT", () => cleanup())
process.on("SIGTERM", () => cleanup())
vscode.on("exit", (code) => {
Watcher.log("vs code watcher terminated unexpectedly")
cleanup(code)
})
tsc.on("exit", (code) => {
Watcher.log("tsc terminated unexpectedly")
cleanup(code)
})
const bundle = bundler.bundle().catch(() => {
Watcher.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"): 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"),
],
{
outDir: path.join(this.rootPath, out),
cacheDir: path.join(this.rootPath, ".cache"),
minify: !!process.env.MINIFY,
logLevel: 1,
publicUrl: "/static/development/dist",
},
)
}
}
main()

View File

@@ -1,23 +0,0 @@
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.16.3/node-v12.16.3-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"]

View File

@@ -1,26 +0,0 @@
#!/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 "$@"

99
ci/lib.sh Normal file → Executable file
View File

@@ -1,10 +1,95 @@
#!/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
pushd() {
builtin pushd "$@" > /dev/null
}
popd() {
builtin popd > /dev/null
}
pkg_json_version() {
jq -r .version package.json
}
vscode_version() {
jq -r .version lib/vscode/package.json
}
os() {
local os
os=$(uname | tr '[:upper:]' '[:lower:]')
if [[ $os == "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
os="alpine"
fi
elif [[ $os == "darwin" ]]; then
os="macos"
fi
echo "$os"
}
arch() {
case "$(uname -m)" in
aarch64)
echo arm64
;;
x86_64)
echo amd64
;;
*)
echo "unknown architecture $(uname -a)"
exit 1
;;
esac
}
curl() {
command curl -H "Authorization: token $GITHUB_TOKEN" "$@"
}
# Grabs the most recent ci.yaml github workflow run that was successful and triggered from the same commit being pushd.
# This will contain the artifacts we want.
# https://developer.github.com/v3/actions/workflow-runs/#list-workflow-runs
get_artifacts_url() {
curl -sSL 'https://api.github.com/repos/cdr/code-server/actions/workflows/ci.yaml/runs?status=success&event=push' | jq -r ".workflow_runs[] | select(.head_sha == \"$(git rev-parse HEAD)\") | .artifacts_url" | head -n 1
}
# Grabs the artifact's download url.
# https://developer.github.com/v3/actions/artifacts/#list-workflow-run-artifacts
get_artifact_url() {
local artifact_name="$1"
curl -sSL "$(get_artifacts_url)" | jq -r ".artifacts[] | select(.name == \"$artifact_name\") | .archive_download_url" | head -n 1
}
# Uses the above two functions to download a artifact into a directory.
download_artifact() {
local artifact_name="$1"
local dst="$2"
local tmp_file
tmp_file="$(mktemp)"
curl -sSL "$(get_artifact_url "$artifact_name")" > "$tmp_file"
unzip -q -o "$tmp_file" -d "$dst"
rm "$tmp_file"
}
rsync() {
command rsync -a --del "$@"
}
VERSION="$(pkg_json_version)"
export VERSION
ARCH="$(arch)"
export ARCH
OS=$(os)
export OS
# RELEASE_PATH is the destination directory for the release from the root.
# Defaults to release
RELEASE_PATH="${RELEASE_PATH-release}"

View File

@@ -13,6 +13,7 @@ RUN apt-get update \
ssh \
sudo \
vim \
lsb-release \
&& rm -rf /var/lib/apt/lists/*
# https://wiki.debian.org/Locale#Manually
@@ -26,18 +27,17 @@ 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 - && \
RUN ARCH="$(dpkg --print-architecture)" && \
curl -sSL "https://github.com/boxboat/fixuid/releases/download/v0.4.1/fixuid-0.4.1-linux-$ARCH.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
COPY release-packages/code-server*.deb /tmp/
RUN dpkg -i /tmp/code-server*$(dpkg --print-architecture).deb && rm /tmp/code-server*.deb
EXPOSE 8080
USER coder
WORKDIR /home/coder
ENTRYPOINT ["dumb-init", "fixuid", "-q", "/usr/local/bin/code-server", "--bind-addr", "0.0.0.0:8080", "."]
ENTRYPOINT ["dumb-init", "fixuid", "-q", "/usr/bin/code-server", "--bind-addr", "0.0.0.0:8080", "."]

11
ci/release-container/build.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
source ./ci/lib.sh
docker build -t "codercom/code-server-$ARCH:$VERSION" -f ./ci/release-container/Dockerfile .
}
main "$@"

View File

@@ -1,22 +0,0 @@
#!/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 "$@"

View File

@@ -1,76 +0,0 @@
#!/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)"
# release-upload is for uploading to the GCP bucket whereas release is used for GitHub.
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 "$@"

14
ci/steps/build-docker-image.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
source ./ci/lib.sh
./ci/release-container/build.sh
mkdir -p release-images
docker save "codercom/code-server-$ARCH:$VERSION" > "release-images/code-server-$ARCH-$VERSION.tar"
}
main "$@"

17
ci/steps/fmt.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
yarn --frozen-lockfile
git submodule update --init
# We do not `yarn vscode` to make test.sh faster.
# If the patch fails to apply, then it's likely already applied
yarn vscode:patch &> /dev/null || true
yarn fmt
}
main "$@"

17
ci/steps/lint.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
yarn --frozen-lockfile
git submodule update --init
# We do not `yarn vscode` to make test.sh faster.
# If the patch fails to apply, then it's likely already applied
yarn vscode:patch &> /dev/null || true
yarn lint
}
main "$@"

18
ci/steps/publish-npm.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
source ./ci/lib.sh
if [[ ${CI-} ]]; then
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
fi
download_artifact npm-package ./release-npm-package
# https://github.com/actions/upload-artifact/issues/38
tar -xzf release-npm-package/package.tar.gz
yarn publish --non-interactive release
}
main "$@"

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
source ./ci/lib.sh
download_artifact release-images ./release-images
if [[ ${CI-} ]]; then
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
fi
for img in ./release-images/*; do
docker load -i "$img"
done
# We have to ensure the amd64 and arm64 images exist on the remote registry
# in order to build the manifest.
# We don't put the arch in the tag to avoid polluting the main repository.
# These other repositories are private so they don't pollute our organization namespace.
docker push "codercom/code-server-amd64:$VERSION"
docker push "codercom/code-server-arm64:$VERSION"
export DOCKER_CLI_EXPERIMENTAL=enabled
docker manifest create "codercom/code-server:$VERSION" \
"codercom/code-server-amd64:$VERSION" \
"codercom/code-server-arm64:$VERSION"
docker manifest push --purge "codercom/code-server:$VERSION"
docker manifest create "codercom/code-server:latest" \
"codercom/code-server-amd64:$VERSION" \
"codercom/code-server-arm64:$VERSION"
docker manifest push --purge "codercom/code-server:latest"
}
main "$@"

15
ci/steps/release-static.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
# https://github.com/actions/upload-artifact/issues/38
tar -xzf release-npm-package/package.tar.gz
yarn release:static
yarn test:static-release
yarn package
}
main "$@"

18
ci/steps/release.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
yarn --frozen-lockfile
yarn vscode
yarn build
yarn build:vscode
yarn release
# https://github.com/actions/upload-artifact/issues/38
mkdir -p release-npm-package
tar -czf release-npm-package/package.tar.gz release
}
main "$@"

17
ci/steps/test.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
yarn --frozen-lockfile
git submodule update --init
# We do not `yarn vscode` to make test.sh faster.
# If the patch fails to apply, then it's likely already applied
yarn vscode:patch &> /dev/null || true
yarn test
}
main "$@"

View File

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

View File

@@ -1,19 +1,30 @@
# Contributing
## Development Workflow
- [Detailed CI and build process docs](../ci)
- [VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
## Requirements
Please refer to [VS Code's prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites).
Differences:
- We require a minimum of node v12 but later versions should work.
- We use [fnpm](https://github.com/goreleaser/nfpm) to build `.deb` and `.rpm` packages.
- The [CI container](../ci/container/Dockerfile) is a useful reference for all our dependencies.
## Development Workflow
```shell
yarn
yarn vscode
yarn watch # Visit http://localhost:8080 once completed.
yarn watch
# Visit http://localhost:8080 once the build completed.
```
To develop inside of an isolated docker container:
```shell
./ci/dev-image/exec.sh
./ci/dev/container/exec.sh
root@12345:/code-server# yarn
root@12345:/code-server# yarn vscode
@@ -25,16 +36,82 @@ 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.
yarn build:vscode
yarn release
cd release
yarn --production
# Runs the built JavaScript with Node.
node .
```
Now you can make it static and build packages with:
```
yarn release:static
yarn test:static-release
yarn package
# The static release is in ./release-static
# .deb, .rpm and the static archive are in ./release-packages
```
## Structure
The `code-server` script serves an HTTP API to login and start a remote VS Code process.
The CLI code is in [./src/node](./src/node) and the HTTP routes are implemented in
[./src/node/app](./src/node/app).
Most of the meaty parts are in our VS Code patch which is described next.
### VS Code Patch
Back in v1 of code-server, we had an extensive patch of VS Code that split the codebase
into a frontend and server. The frontend consisted of all UI code and the server ran
the extensions and exposed an API to the frontend for file access and everything else
that the UI needed.
This worked but eventually Microsoft added support to VS Code to run it in the web.
They have open sourced the frontend but have kept the server closed source.
So in interest of piggy backing off their work, v2 and beyond use the VS Code
web frontend and fill in the server. This is contained in our
[./ci/dev/vscode.patch](../ci/dev/vscode.patch) under the path `src/vs/server`.
Other notable changes in our patch include:
- Add our own build file which includes our code and VS Code's web code.
- 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.
- Allow modification of the display language.
- Make it possible for us to load code on the client.
- Make extensions work in the browser.
- Make it possible to install extensions of any kind.
- Fix getting permanently disconnected when you sleep or hibernate for a while.
- Add connection type to web socket query parameters.
Some known issues presently:
- Creating custom VS Code extensions and debugging them doesn't work.
- Extension profiling and tips are currently disabled.
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 run `yarn vscode:diff`.
**note**: We have extension docs on the CI and build system at [./ci/README.md](../ci/README.md)
If functionality doesn't depend on code from VS Code then it should be moved
into code-server otherwise it should be in the patch.
In the future we'd like to run VS Code unit tests against our builds to ensure features
work as expected.

View File

@@ -1,9 +1,32 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# FAQ
- [Questions?](#questions)
- [What's the deal with extensions?](#whats-the-deal-with-extensions)
- [Where are extensions stored?](#where-are-extensions-stored)
- [How is this different from VS Code Codespaces?](#how-is-this-different-from-vs-code-codespaces)
- [How should I expose code-server to the internet?](#how-should-i-expose-code-server-to-the-internet)
- [How do I securely access web services?](#how-do-i-securely-access-web-services)
- [Sub-domains](#sub-domains)
- [Sub-paths](#sub-paths)
- [Multi-tenancy](#multi-tenancy)
- [Docker in code-server docker container?](#docker-in-code-server-docker-container)
- [Collaboration](#collaboration)
- [How can I disable telemetry?](#how-can-i-disable-telemetry)
- [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open)
- [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server)
- [Heartbeat File](#heartbeat-file)
- [How does the config file work?](#how-does-the-config-file-work)
- [Enterprise](#enterprise)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Questions?
Please file all questions and support requests at https://www.reddit.com/r/codeserver/
The issue tracker is only for bugs.
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?
@@ -23,42 +46,59 @@ Issue [#1299](https://github.com/cdr/code-server/issues/1299) is a big one in ma
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.
If an extension is not available or does not work, you can grab its VSIX from its Github releases or
build it yourself. Then run the `Extensions: Install from VSIX` command in the Command Palette and
point to the .vsix file.
## How is this different from VS Code Online?
See below for installing an extension from the cli.
VS Code Online is a closed source managed service by Microsoft and only runs on Azure.
Feel free to file an issue to add a missing extension to the marketplace.
code-server is open source and can be freely run on any machine.
If you have your own custom marketplace, it is possible to point code-server to it by setting
`$SERVICE_URL` and `$ITEM_URL` to point to it.
## Where are extensions stored?
Defaults to `~/.local/share/code-server/extensions`.
If the `XDG_DATA_HOME` environment variable is set the data directory will be
`$XDG_DATA_HOME/code-server/extensions`. In general we try to follow the XDG directory spec.
You can install an extension on the CLI with:
```bash
# From the Coder extension marketplace
code-server --install-extension ms-python.python
# From a downloaded VSIX on the file system
code-server --install-extension downloaded-ms-python.python.vsix
```
## How is this different from VS Code Codespaces?
VS Code Codespaces is a closed source and paid service by Microsoft. It also allows you to access
VS Code via the browser.
However, code-server is free, open source and can be ran on any machine without any limitations.
While you can self host environments with VS Code Codespaces, you still need to an Azure billing
account and you access VS Code via the Codespaces web dashboard instead of directly connecting to
your instance.
## 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.
Please follow [./guide.md](./guide.md) for our recommendations on setting up and using code-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.
code-server only supports password authentication natively.
**note**: code-server will rate limit password authentication attempts at 2 a minute and 12 an hour.
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).
If you want to use external authentication (i.e sign in with Google) 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`.
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
@@ -67,6 +107,8 @@ only to HTTP requests.
You can use [Let's Encrypt](https://letsencrypt.org/) to get an SSL certificate
for free.
Again, Please follow [./guide.md](./guide.md) for our recommendations on setting up and using code-server.
## How do I securely access web services?
code-server is capable of proxying to any port using either a subdomain or a
@@ -96,16 +138,7 @@ ensure your reverse proxy forwards that information if you are using one.
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
## 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
@@ -127,8 +160,9 @@ 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.
We understand the high demand but the team is swamped right now.
You can follow progress at [#33](https://github.com/cdr/code-server/issues/33).
## How can I disable telemetry?
@@ -157,21 +191,45 @@ code-server --log debug
Once this is done, replicate the issue you're having then collect logging
information from the following places:
1. stdout.
2. The most recently created directory in the `logs` directory (found in the
data directory; see below for how to find that).
1. stdout
2. The most recently created directory in the `~/.local/share/code-server/logs` directory.
3. The browser console and network tabs.
Additionally, collecting core dumps (you may need to enable them first) if
code-server crashes can be helpful.
### Where is the data directory?
## Heartbeat File
If the `XDG_DATA_HOME` environment variable is set the data directory will be
`$XDG_DATA_HOME/code-server`. Otherwise the default is:
`code-server` touches `~/.local/share/code-server/heartbeat` once a minute as long
as there is an active browser connection.
1. Linux: `~/.local/share/code-server`.
2. Mac: `~/Library/Application\ Support/code-server`.
If you want to shutdown `code-server` if there hasn't been an active connection in X minutes
you can do so by continuously checking the last modified time on the heartbeat file and if it is
older than X minutes, you should kill `code-server`.
[#1636](https://github.com/cdr/code-server/issues/1636) will make the experience here better.
## How does the config file work?
When `code-server` starts up, it creates a default config file in `~/.config/code-server/config.yaml` that looks
like this:
```yaml
bind-addr: 127.0.0.1:8080
auth: password
password: mewkmdasosafuio3422 # This is randomly generated for each config.yaml
cert: false
```
Each key in the file maps directly to a `code-server` flag. Run `code-server --help` to see
a listing of all the flags.
The default config here says to listen on the loopback IP port 8080, enable password authorization
and no TLS. Any flags passed to `code-server` will take priority over the config file.
The `--config` flag or `$CODE_SERVER_CONFIG` can be used to change the config file's location.
The default location also respects `$XDG_CONFIG_HOME`.
## Enterprise

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="200px" height="40px" viewBox="0 0 200 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
<title>do-btn-blue-ghost</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Partner-welcome-kit-Copy-3" transform="translate(-651.000000, -828.000000)">
<g id="do-btn-blue-ghost" transform="translate(651.000000, 828.000000)">
<rect id="Rectangle-Copy-4" stroke="#0069FF" x="0.5" y="0.5" width="199" height="39" rx="6"></rect>
<path d="M6,0 L47,0 L47,40 L6,40 C2.6862915,40 4.05812251e-16,37.3137085 0,34 L-8.8817842e-16,6 C-1.29399067e-15,2.6862915 2.6862915,6.08718376e-16 6,0 Z" id="Rectangle-Copy-5" fill="#0069FF"></path>
<g id="DO_Logo_horizontal_blue-Copy-3" transform="translate(13.000000, 10.000000)" fill="#FFFFFF">
<path d="M10.0098493,20 L10.0098493,16.1262429 C14.12457,16.1262429 17.2897398,12.0548452 15.7269372,7.74627862 C15.1334679,6.14538921 13.8674,4.86072487 12.2650328,4.28756693 C7.952489,2.72620566 3.87733294,5.88845634 3.87733294,9.99938223 C3.87733294,9.99938223 3.87733294,9.99938223 3.87733294,9.99938223 L0,9.99938223 C0,3.45747613 6.3303395,-1.64165309 13.1948014,0.492866119 C16.2017127,1.42177726 18.57559,3.81322933 19.5053586,6.79760341 C21.6418482,13.6754986 16.5577943,20 10.0098493,20 Z" id="XMLID_49_"></path>
<polygon id="XMLID_47_" points="9.52380952 16.1904762 5.71428571 16.1904762 5.71428571 12.3809524 5.71428571 12.3809524 9.52380952 12.3809524 9.52380952 12.3809524"></polygon>
<polygon id="XMLID_46_" points="6.66666667 19.047619 3.80952381 19.047619 3.80952381 19.047619 3.80952381 16.1904762 6.66666667 16.1904762"></polygon>
<polygon id="XMLID_45_" points="3.80952381 16.1904762 0.952380952 16.1904762 0.952380952 16.1904762 0.952380952 13.3333333 0.952380952 13.3333333 3.80952381 13.3333333 3.80952381 13.3333333"></polygon>
</g>
<!-- Modified to add GitHub font-family after DigitalOcean's font-family, otherwise it looks bad on GitHub -->
<text id="Create-a-Droplet-Copy-3" font-family="Sailec-Medium, Sailec, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol" font-size="16" font-weight="400" fill="#0069FF">
<tspan x="58" y="26">Create a Droplet</tspan>
</text>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

249
doc/guide.md Normal file
View File

@@ -0,0 +1,249 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Setup Guide
- [1. Acquire a remote machine](#1-acquire-a-remote-machine)
- [Requirements](#requirements)
- [Google Cloud](#google-cloud)
- [2. Install code-server](#2-install-code-server)
- [3. Expose code-server](#3-expose-code-server)
- [SSH forwarding](#ssh-forwarding)
- [Let's Encrypt](#lets-encrypt)
- [Self Signed Certificate](#self-signed-certificate)
- [Change the password?](#change-the-password)
- [How do I securely access development web services?](#how-do-i-securely-access-development-web-services)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
This guide demonstrates how to setup and use code-server.
To reiterate, code-server lets you run VS Code on a remote server and then access it via a browser.
Further docs are at:
- [README.md](../README.md) for a general overview
- [FAQ.md](./FAQ.md) for common questions.
- [CONTRIBUTING.md](../doc/CONTRIBUTING.md) for development docs
We'll walk you through acquiring a remote machine to run code-server on and then exposing `code-server` so you can
securely access it.
## 1. Acquire a remote machine
First, you need a machine to run code-server on. You can use a physical
machine you have lying around or use a VM on GCP/AWS.
### Requirements
For a good experience, we recommend at least:
- 1 GB of RAM
- 2 cores
You can use whatever linux distribution floats your boat but in this guide we assume Debian on Google Cloud.
### Google Cloud
For demonstration purposes, this guide assumes you're using a VM on GCP but you should be
able to easily use any machine or VM provider.
You can sign up at https://console.cloud.google.com/getting-started. You'll get a 12 month \$300
free trial.
Once you've signed up and created a GCP project, create a new Compute Engine VM Instance.
1. Navigate to `Compute Engine -> VM Instances` on the sidebar.
2. Now click `Create Instance` to create a new instance.
3. Name it whatever you want.
4. Choose the region closest to you based on [gcping.com](http://www.gcping.com).
5. Any zone is fine.
6. We'd recommend a `E2` series instance from the General-purpose family.
- Change the type to custom and set at least 2 cores and 2 GB of ram.
- Add more vCPUs and memory as you prefer, you can edit after creating the instance as well.
- https://cloud.google.com/compute/docs/machine-types#general_purpose
7. We highly recommend switching the persistent disk to a SSD of at least 32 GB.
- Click `Change` under `Boot Disk` and change the type to `SSD Persistent Disk` and the size
to `32`.
- You can always grow your disk later.
- The default OS of Debian 10 is fine.
8. Navigate to `Networking -> Network interfaces` and edit the existing interface
to use a static external IP.
- Click done to save network interface changes.
9. If you do not have a [project wide SSH key](https://cloud.google.com/compute/docs/instances/adding-removing-ssh-keys#project-wide), navigate to `Security - > SSH Keys` and add your public key there.
10. Click create!
Remember, you can shutdown your server when not in use to lower costs.
We highly recommend learning to use the [`gcloud`](https://cloud.google.com/sdk/gcloud) cli
to avoid the slow dashboard.
## 2. Install code-server
SSH into your instance and run the appropriate commands documented in [README.md](../README.md).
Assuming Debian:
```bash
curl -sSOL https://github.com/cdr/code-server/releases/download/v3.3.1/code-server_3.3.1_amd64.deb
sudo dpkg -i code-server_3.3.1_amd64.deb
systemctl --user enable --now code-server
# Now code-server is running at http://127.0.0.1:8080
# Your password is in ~/.config/code-server/config.yaml
```
## 3. Expose code-server
**Never**, **ever** expose `code-server` directly to the internet without some form of authentication
and encryption as someone can completely takeover your machine with the terminal.
There are several approaches to securely operating and exposing code-server.
By default, code-server will enable password authentication which will
require you to copy the password from the code-server config file to login. You
can also set a custom password with `$PASSWORD`.
**tip**: You can list the full set of code-server options with `code-server --help`
### SSH forwarding
We highly recommend this approach for not requiring any additional setup, you just need an
SSH server on your remote machine. The downside is you won't be able to access `code-server`
without an SSH client like an iPad. If that's important to you, skip to [Let's Encrypt](#lets-encrypt).
Recommended reading: https://help.ubuntu.com/community/SSH/OpenSSH/PortForwarding.
First, ssh into your instance and edit your code-server config file to disable password authentication.
```bash
# Replaces "auth: password" with "auth: none" in the code-server config.
sed -i.bak 's/auth: password/auth: none/' ~/.config/code-server/config.yaml
```
Restart code-server with (assuming you followed the guide):
```bash
systemctl --user restart code-server
```
Now forward local port 8080 to `127.0.0.1:8080` on the remote instance.
```bash
# -N disables executing a remote shell
ssh -N -L 8080:127.0.0.1:8080 <instance-ip>
```
Now if you access http://127.0.0.1:8080 locally, you should see code-server!
If you want to make the SSH port forwarding persistent we recommend using
[mutagen](https://mutagen.io/documentation/introduction/installation).
```
# Same as the above SSH command but runs in the background continously.
# Add `mutagen daemon start` to your ~/.bashrc to start the mutagen daemon when you open a shell.
mutagen forward create --name=code-server tcp:127.0.0.1:8080 <instance-ip>:tcp:127.0.0.1:8080
```
We also recommend adding the following lines to your `~/.ssh/config` to quickly detect bricked SSH connections:
```bash
Host *
ServerAliveInterval 5
ExitOnForwardFailure yes
```
You can also forward your SSH key and GPG agent to the instance to securely access GitHub
and sign commits without copying your keys onto the instance.
1. https://developer.github.com/v3/guides/using-ssh-agent-forwarding/
2. https://wiki.gnupg.org/AgentForwarding
### Let's Encrypt
[Let's Encrypt](https://letsencrypt.org) is a great option if you want to access code-server on an iPad
or do not want to use SSH forwarding. This does require that the remote machine is exposed to the internet.
Assuming you have been following the guide, edit your instance and checkmark the allow HTTP/HTTPS traffic options.
1. You'll need to buy a domain name. We recommend [Google Domains](https://domains.google.com).
2. Add an A record to your domain with your instance's IP.
3. Install caddy https://caddyserver.com/docs/download#debian-ubuntu-raspbian.
```bash
echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" \
| sudo tee -a /etc/apt/sources.list.d/caddy-fury.list
sudo apt update
sudo apt install caddy
```
4. Replace `/etc/caddy/Caddyfile` with sudo to look like this:
```
mydomain.com
reverse_proxy 127.0.0.1:8080
```
5. Reload caddy with:
```bash
sudo systemctl reload caddy
```
Visit `https://<your-domain-name>` to access code-server. Congratulations!
In a future release we plan to integrate Let's Encrypt directly with code-server to avoid
the dependency on caddy.
### Self Signed Certificate
**note:** Self signed certificates do not work with iPad and will cause a blank page. You'll
have to use [Let's Encrypt](#lets-encrypt) instead.
Recommended reading: https://security.stackexchange.com/a/8112.
We recommend this as a last resort as self signed certificates do not work with iPads and can
cause other bizarre issues. Not to mention all the warnings when you access code-server.
Only use this if:
1. You do not want to buy a domain.
2. You cannot expose the remote machine to the internet.
3. You do not want to use SSH forwarding.
ssh into your instance and edit your code-server config file to use a randomly generated self signed certificate:
```bash
# Replaces "cert: false" with "cert: true" in the code-server config.
sed -i.bak 's/cert: false/cert: true/' ~/.config/code-server/config.yaml
# Replaces "bind-addr: 127.0.0.1:8080" with "bind-addr: 0.0.0.0:443" in the code-server config.
sed -i.bak 's/bind-addr: 127.0.0.1:8080/bind-addr: 0.0.0.0:443/' ~/.config/code-server/config.yaml
# Allows code-server to listen on port 443.
sudo setcap cap_net_bind_service=+ep /usr/lib/code-server/lib/node
```
Assuming you have been following the guide, restart code-server with:
```bash
systemctl --user restart code-server
```
Edit your instance and checkmark the allow HTTPS traffic option.
Visit `https://<your-instance-ip>` to access code-server.
You'll get a warning when accessing but if you click through you should be good.
To avoid the warnings, you can use [mkcert](https://mkcert.dev) to create a self signed certificate
trusted by your OS and then pass it into code-server via the `cert` and `cert-key` config
fields.
### Change the password?
Edit the code-server config file at `~/.config/code-server/config.yaml` and then restart
code-server with:
```bash
systemctl --user restart code-server
```
### How do I securely access development web services?
If you're working on a web service and want to access it locally, code-server can proxy it for you.
See [FAQ.md](https://github.com/cdr/code-server/blob/master/doc/FAQ.md#how-do-i-securely-access-web-services).

34
doc/npm.md Normal file
View File

@@ -0,0 +1,34 @@
# npm Install Requirements
If you're installing the npm module you'll need certain dependencies to build
the native modules used by VS Code.
You also need at least node v12 installed. See [#1633](https://github.com/cdr/code-server/issues/1633).
## Ubuntu, Debian
```bash
sudo apt-get install -y \
build-essential \
pkg-config \
libx11-dev \
libxkbfile-dev \
libsecret-1-dev
```
## Fedora, Red Hat, SUSE
```bash
sudo yum groupinstall -y 'Development Tools'
sudo yum config-manager --set-enabled PowerTools
sudo yum install -y python2 libsecret-devel libX11-devel libxkbfile-devel
npm config set python python2
```
## macOS
Install [Xcode](https://developer.apple.com/xcode/downloads/) and run:
```bash
xcode-select --install
```

View File

@@ -1,23 +1,39 @@
{
"name": "code-server",
"license": "MIT",
"version": "3.2.0",
"scripts": {
"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"
"version": "3.3.1",
"description": "Run VS Code on a remote server.",
"homepage": "https://github.com/cdr/code-server",
"bugs": {
"url": "https://github.com/cdr/code-server/issues"
},
"repository": "https://github.com/cdr/code-server",
"scripts": {
"clean": "./ci/build/clean.sh",
"vscode": "./ci/dev/vscode.sh",
"vscode:patch": "./ci/dev/patch-vscode.sh",
"vscode:diff": "./ci/dev/diff-vscode.sh",
"build": "./ci/build/build-code-server.sh",
"build:vscode": "./ci/build/build-vscode.sh",
"release": "./ci/build/build-release.sh",
"release:static": "./ci/build/build-static-release.sh",
"release:github-draft": "./ci/build/release-github-draft.sh",
"release:github-assets": "./ci/build/release-github-assets.sh",
"test:static-release": "./ci/build/test-static-release.sh",
"package": "./ci/build/build-packages.sh",
"_____": "",
"fmt": "./ci/dev/fmt.sh",
"lint": "./ci/dev/lint.sh",
"test": "./ci/dev/test.sh",
"ci": "./ci/dev/ci.sh",
"watch": "NODE_OPTIONS=--max_old_space_size=32384 ts-node ./ci/dev/watch.ts"
},
"main": "out/node/entry.js",
"devDependencies": {
"@types/adm-zip": "^0.4.32",
"@types/fs-extra": "^8.0.1",
"@types/http-proxy": "^1.17.4",
"@types/js-yaml": "^3.12.3",
"@types/mocha": "^5.2.7",
"@types/node": "^12.12.7",
"@types/parcel-bundler": "^1.12.1",
@@ -29,6 +45,7 @@
"@types/ws": "^6.0.4",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
"doctoc": "^1.4.0",
"eslint": "^6.2.0",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-import": "^2.18.2",
@@ -36,7 +53,7 @@
"leaked-handles": "^5.2.0",
"mocha": "^6.2.0",
"parcel-bundler": "^1.12.4",
"prettier": "^1.18.2",
"prettier": "^2.0.5",
"stylelint": "^13.0.0",
"stylelint-config-recommended": "^3.0.0",
"ts-node": "^8.4.1",
@@ -50,16 +67,33 @@
"dependencies": {
"@coder/logger": "1.1.11",
"adm-zip": "^0.4.14",
"env-paths": "^2.2.0",
"fs-extra": "^8.1.0",
"http-proxy": "^1.18.0",
"httpolyglot": "^0.1.2",
"js-yaml": "^3.13.1",
"limiter": "^1.1.5",
"node-pty": "^0.9.0",
"pem": "^1.14.2",
"safe-compare": "^1.1.4",
"semver": "^7.1.3",
"tar": "^6.0.1",
"tar-fs": "^2.0.0",
"ws": "^7.2.0"
"ws": "^7.2.0",
"xdg-basedir": "^4.0.0",
"yarn": "^1.22.4"
},
"bin": {
"code-server": "out/node/entry.js"
},
"keywords": [
"vscode",
"development",
"ide",
"coder",
"vscode-remote",
"browser-ide"
],
"engines": {
"node": ">= 12"
}
}

View File

@@ -26,7 +26,7 @@
<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 class="sub">Please log in below. {{PASSWORD_MSG}}</div>
</div>
<div class="content">
<form class="login-form" method="post">

View File

@@ -80,7 +80,7 @@
baseUrl: `${staticBase}/out`,
paths: {
"vscode-textmate": `${staticBase}/node_modules/vscode-textmate/release/main`,
"onigasm-umd": `${staticBase}/node_modules/onigasm-umd/release/main`,
"vscode-oniguruma": `${staticBase}/node_modules/vscode-oniguruma/release/main`,
xterm: `${staticBase}/node_modules/xterm/lib/xterm.js`,
"xterm-addon-search": `${staticBase}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
"xterm-addon-unicode11": `${staticBase}/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
@@ -98,7 +98,7 @@
<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() {})
require(["vs/code/browser/workbench/workbench"], function () {})
</script>
<script>
try {

View File

@@ -8,7 +8,7 @@ if ("serviceWorker" in navigator) {
.register(path, {
scope: options.base || "/",
})
.then(function() {
.then(function () {
console.log("[Service Worker] registered")
})
}

View File

@@ -1,61 +0,0 @@
Implementation of [VS Code](https://code.visualstudio.com/) remote/web for use
in `code-server`.
## Docker
To debug Golang in VS Code 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.
## Known Issues
- Creating custom VS Code extensions and debugging them doesn't work.
- Extension profiling and tips are currently disabled.
## 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.
## Development: 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.
If functionality doesn't depend on code from VS Code then it should be moved
into code-server otherwise it should be in the patch.
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.
Notable changes include:
- Add our own build file which includes our code and VS Code's web code.
- Allow multiple extension directories (both user and built-in).
- Modify the loader, websocket, webview, service worker, and asset requests to
use the URL of the page as a base (and TLS if necessary for the websocket).
- Send client-side telemetry through the server.
- Make changing the display language work.
- Make it possible for us to load code on the client.
- Make extensions work in the browser.
- Make it possible to install extensions of any kind.
- Fix getting permanently disconnected when you sleep or hibernate for a while.
- Add connection type to web socket query parameters.
## Future
- Run VS Code unit tests against our builds to ensure features work as expected.

View File

@@ -2,8 +2,8 @@ import * as http from "http"
import * as limiter from "limiter"
import * as querystring from "querystring"
import { HttpCode, HttpError } from "../../common/http"
import { AuthType, HttpProvider, HttpResponse, Route } from "../http"
import { hash } from "../util"
import { AuthType, HttpProvider, HttpProviderOptions, HttpResponse, Route } from "../http"
import { hash, humanPath } from "../util"
interface LoginPayload {
password?: string
@@ -18,6 +18,14 @@ interface LoginPayload {
* Login HTTP provider.
*/
export class LoginHttpProvider extends HttpProvider {
public constructor(
options: HttpProviderOptions,
private readonly configFile: string,
private readonly envPassword: boolean,
) {
super(options)
}
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
if (this.options.auth !== AuthType.Password || !this.isRoot(route)) {
throw new HttpError("Not found", HttpCode.NotFound)
@@ -46,6 +54,11 @@ export class LoginHttpProvider extends HttpProvider {
public async getRoot(route: Route, error?: Error): Promise<HttpResponse> {
const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/login.html")
response.content = response.content.replace(/{{ERROR}}/, error ? `<div class="error">${error.message}</div>` : "")
let passwordMsg = `Check the config file at ${humanPath(this.configFile)} for the password.`
if (this.envPassword) {
passwordMsg = "Password was set from $PASSWORD."
}
response.content = response.content.replace(/{{PASSWORD_MSG}}/g, passwordMsg)
return this.replaceTemplates(route, response)
}

View File

@@ -5,6 +5,7 @@ import { Readable } from "stream"
import * as tarFs from "tar-fs"
import * as zlib from "zlib"
import { HttpProvider, HttpResponse, Route } from "../http"
import { pathToFsPath } from "../util"
/**
* Static file HTTP provider. Regular static requests (the path is the request
@@ -18,7 +19,7 @@ export class StaticHttpProvider extends HttpProvider {
if (typeof route.query.tar === "string") {
this.ensureAuthenticated(request)
return this.getTarredResource(request, route.query.tar)
return this.getTarredResource(request, pathToFsPath(route.query.tar))
}
const response = await this.getReplacedResource(route)

View File

@@ -18,6 +18,7 @@ import { generateUuid } from "../../common/util"
import { Args } from "../cli"
import { HttpProvider, HttpProviderOptions, HttpResponse, Route } from "../http"
import { settings } from "../settings"
import { pathToFsPath } from "../util"
export class VscodeHttpProvider extends HttpProvider {
private readonly serverRootPath: string
@@ -151,7 +152,7 @@ export class VscodeHttpProvider extends HttpProvider {
case "/resource":
case "/vscode-remote-resource":
if (typeof route.query.path === "string") {
return this.getResource(route.query.path)
return this.getResource(pathToFsPath(route.query.path))
}
break
case "/webview":
@@ -192,6 +193,8 @@ export class VscodeHttpProvider extends HttpProvider {
response.content = response.content.replace(/<!-- PROD_ONLY/g, "").replace(/END_PROD_ONLY -->/g, "")
}
options.productConfiguration.codeServerVersion = require("../../../package.json").version
response.content = response.content
.replace(`"{{REMOTE_USER_DATA_URI}}"`, `'${JSON.stringify(options.remoteUserDataUri)}'`)
.replace(`"{{PRODUCT_CONFIGURATION}}"`, `'${JSON.stringify(options.productConfiguration)}'`)

View File

@@ -1,8 +1,11 @@
import { field, Level, logger } from "@coder/logger"
import * as fs from "fs-extra"
import yaml from "js-yaml"
import * as os from "os"
import * as path from "path"
import { field, logger, Level } from "@coder/logger"
import { Args as VsArgs } from "../../lib/vscode/src/vs/server/ipc"
import { AuthType } from "./http"
import { xdgLocalDir } from "./util"
import { generatePassword, humanPath, paths } from "./util"
export class Optional<T> {
public constructor(public readonly value?: T) {}
@@ -19,10 +22,11 @@ export enum LogLevel {
export class OptionalString extends Optional<string> {}
export interface Args extends VsArgs {
readonly config?: string
readonly auth?: AuthType
readonly password?: string
readonly cert?: OptionalString
readonly "cert-key"?: string
readonly "disable-updates"?: boolean
readonly "disable-telemetry"?: boolean
readonly help?: boolean
readonly host?: string
@@ -81,19 +85,30 @@ type Options<T> = {
const options: Options<Required<Args>> = {
auth: { type: AuthType, description: "The type of authentication to use." },
password: {
type: "string",
description: "The password for password authentication (can only be passed in via $PASSWORD or the config file).",
},
cert: {
type: OptionalString,
path: true,
description: "Path to certificate. Generated if no path is provided.",
},
"cert-key": { type: "string", path: true, description: "Path to certificate key when using non-generated cert." },
"disable-updates": { type: "boolean", description: "Disable automatic updates." },
"disable-telemetry": { type: "boolean", description: "Disable telemetry." },
help: { type: "boolean", short: "h", description: "Show this output." },
json: { type: "boolean" },
open: { type: "boolean", description: "Open in browser on startup. Does not work remotely." },
"bind-addr": { type: "string", description: "Address to bind to in host:port." },
"bind-addr": {
type: "string",
description: "Address to bind to in host:port. You can also use $PORT to override the port.",
},
config: {
type: "string",
description: "Path to yaml config file. Every flag maps directly to a key in the config file.",
},
// These two have been deprecated by bindAddr.
host: { type: "string", description: "" },
@@ -137,7 +152,19 @@ export const optionDescriptions = (): string[] => {
)
}
export const parse = (argv: string[]): Args => {
export const parse = async (
argv: string[],
opts?: {
configFile: string
},
): Promise<Args> => {
const error = (msg: string): Error => {
if (opts?.configFile) {
msg = `error reading ${opts.configFile}: ${msg}`
}
return new Error(msg)
}
const args: Args = { _: [] }
let ended = false
@@ -167,7 +194,11 @@ export const parse = (argv: string[]): Args => {
}
if (!key || !options[key]) {
throw new Error(`Unknown option ${arg}`)
throw error(`Unknown option ${arg}`)
}
if (key === "password" && !opts?.configFile) {
throw new Error("--password can only be set in the config file or passed in via $PASSWORD")
}
const option = options[key]
@@ -186,7 +217,11 @@ export const parse = (argv: string[]): Args => {
;(args[key] as OptionalString) = new OptionalString(value)
continue
} else if (!value) {
throw new Error(`--${key} requires a value`)
throw error(`--${key} requires a value`)
}
if (option.type == OptionalString && value == "false") {
continue
}
if (option.path) {
@@ -206,7 +241,7 @@ export const parse = (argv: string[]): Args => {
case "number":
;(args[key] as number) = parseInt(value, 10)
if (isNaN(args[key] as number)) {
throw new Error(`--${key} must be a number`)
throw error(`--${key} must be a number`)
}
break
case OptionalString:
@@ -214,7 +249,7 @@ export const parse = (argv: string[]): Args => {
break
default: {
if (!Object.values(option.type).includes(value)) {
throw new Error(`--${key} valid values: [${Object.values(option.type).join(", ")}]`)
throw error(`--${key} valid values: [${Object.values(option.type).join(", ")}]`)
}
;(args[key] as string) = value
break
@@ -266,7 +301,8 @@ export const parse = (argv: string[]): Args => {
}
if (!args["user-data-dir"]) {
args["user-data-dir"] = xdgLocalDir
await copyOldMacOSDataDir()
args["user-data-dir"] = paths.data
}
if (!args["extensions-dir"]) {
@@ -275,3 +311,108 @@ export const parse = (argv: string[]): Args => {
return args
}
async function defaultConfigFile(): Promise<string> {
return `bind-addr: 127.0.0.1:8080
auth: password
password: ${await generatePassword()}
cert: false
`
}
/**
* Reads the code-server yaml config file and returns it as Args.
*
* @param configPath Read the config from configPath instead of $CODE_SERVER_CONFIG or the default.
*/
export async function readConfigFile(configPath?: string): Promise<Args> {
if (!configPath) {
configPath = process.env.CODE_SERVER_CONFIG
if (!configPath) {
configPath = path.join(paths.config, "config.yaml")
}
}
if (!(await fs.pathExists(configPath))) {
await fs.outputFile(configPath, await defaultConfigFile())
logger.info(`Wrote default config file to ${humanPath(configPath)}`)
}
logger.info(`Using config file ${humanPath(configPath)}`)
const configFile = await fs.readFile(configPath)
const config = yaml.safeLoad(configFile.toString(), {
filename: configPath,
})
// We convert the config file into a set of flags.
// This is a temporary measure until we add a proper CLI library.
const configFileArgv = Object.entries(config).map(([optName, opt]) => {
if (opt === true) {
return `--${optName}`
}
return `--${optName}=${opt}`
})
const args = await parse(configFileArgv, {
configFile: configPath,
})
return {
...args,
config: configPath,
}
}
function parseBindAddr(bindAddr: string): [string, number] {
const u = new URL(`http://${bindAddr}`)
return [u.hostname, parseInt(u.port, 10)]
}
interface Addr {
host: string
port: number
}
function bindAddrFromArgs(addr: Addr, args: Args): Addr {
addr = { ...addr }
if (args["bind-addr"]) {
;[addr.host, addr.port] = parseBindAddr(args["bind-addr"])
}
if (args.host) {
addr.host = args.host
}
if (process.env.PORT) {
addr.port = parseInt(process.env.PORT, 10)
}
if (args.port !== undefined) {
addr.port = args.port
}
return addr
}
export function bindAddrFromAllSources(cliArgs: Args, configArgs: Args): [string, number] {
let addr: Addr = {
host: "localhost",
port: 8080,
}
addr = bindAddrFromArgs(addr, configArgs)
addr = bindAddrFromArgs(addr, cliArgs)
return [addr.host, addr.port]
}
async function copyOldMacOSDataDir(): Promise<void> {
if (os.platform() !== "darwin") {
return
}
if (await fs.pathExists(paths.data)) {
return
}
// If the old data directory exists, we copy it in.
const oldDataDir = path.join(os.homedir(), "Library/Application Support", "code-server")
if (await fs.pathExists(oldDataDir)) {
await fs.copy(oldDataDir, paths.data)
}
}

View File

@@ -9,9 +9,9 @@ import { ProxyHttpProvider } from "./app/proxy"
import { StaticHttpProvider } from "./app/static"
import { UpdateHttpProvider } from "./app/update"
import { VscodeHttpProvider } from "./app/vscode"
import { Args, optionDescriptions, parse } from "./cli"
import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile } from "./cli"
import { AuthType, HttpServer, HttpServerOptions } from "./http"
import { generateCertificate, generatePassword, hash, open } from "./util"
import { generateCertificate, hash, open, humanPath } from "./util"
import { ipcMain, wrap } from "./wrapper"
process.on("uncaughtException", (error) => {
@@ -31,25 +31,37 @@ try {
const version = pkg.version || "development"
const commit = pkg.commit || "development"
const main = async (args: Args): Promise<void> => {
const auth = args.auth || AuthType.Password
const originalPassword = auth === AuthType.Password && (process.env.PASSWORD || (await generatePassword()))
const main = async (cliArgs: Args): Promise<void> => {
const configArgs = await readConfigFile(cliArgs.config)
// This prioritizes the flags set in args over the ones in the config file.
let args = Object.assign(configArgs, cliArgs)
let host = args.host
let port = args.port
if (args["bind-addr"] !== undefined) {
const u = new URL(`http://${args["bind-addr"]}`)
host = u.hostname
port = parseInt(u.port, 10)
if (!args.auth) {
args = {
...args,
auth: AuthType.Password,
}
}
logger.info(`Using user-data-dir ${humanPath(args["user-data-dir"])}`)
logger.trace(`Using extensions-dir ${humanPath(args["extensions-dir"])}`)
const envPassword = !!process.env.PASSWORD
const password = args.auth === AuthType.Password && (process.env.PASSWORD || args.password)
if (args.auth === AuthType.Password && !password) {
throw new Error("Please pass in a password via the config file or $PASSWORD")
}
const [host, port] = bindAddrFromAllSources(cliArgs, configArgs)
// Spawn the main HTTP server.
const options: HttpServerOptions = {
auth,
auth: args.auth,
commit,
host: host || (args.auth === AuthType.Password && args.cert !== undefined ? "0.0.0.0" : "localhost"),
password: originalPassword ? hash(originalPassword) : undefined,
port: port !== undefined ? port : process.env.PORT ? parseInt(process.env.PORT, 10) : 8080,
host: host,
// The hash does not add any actual security but we do it for obfuscation purposes.
password: password ? hash(password) : undefined,
port: port,
proxyDomains: args["proxy-domain"],
socket: args.socket,
...(args.cert && !args.cert.value
@@ -67,9 +79,9 @@ const main = async (args: Args): Promise<void> => {
const httpServer = new HttpServer(options)
const vscode = httpServer.registerHttpProvider("/", VscodeHttpProvider, args)
const api = httpServer.registerHttpProvider("/api", ApiHttpProvider, httpServer, vscode, args["user-data-dir"])
const update = httpServer.registerHttpProvider("/update", UpdateHttpProvider, !args["disable-updates"])
const update = httpServer.registerHttpProvider("/update", UpdateHttpProvider, false)
httpServer.registerHttpProvider("/proxy", ProxyHttpProvider)
httpServer.registerHttpProvider("/login", LoginHttpProvider)
httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword)
httpServer.registerHttpProvider("/static", StaticHttpProvider)
httpServer.registerHttpProvider("/dashboard", DashboardHttpProvider, api, update)
@@ -79,17 +91,17 @@ const main = async (args: Args): Promise<void> => {
const serverAddress = await httpServer.listen()
logger.info(`HTTP server listening on ${serverAddress}`)
if (auth === AuthType.Password && !process.env.PASSWORD) {
logger.info(` - Password is ${originalPassword}`)
logger.info(" - To use your own password set the PASSWORD environment variable")
if (!args.auth) {
logger.info(" - To disable use `--auth none`")
if (args.auth === AuthType.Password) {
if (envPassword) {
logger.info(" - Using password from $PASSWORD")
} else {
logger.info(` - Using password from ${humanPath(args.config)}`)
}
} else if (auth === AuthType.Password) {
logger.info(" - Using custom password for authentication")
logger.info(" - To disable use `--auth none`")
} else {
logger.info(" - No authentication")
}
delete process.env.PASSWORD
if (httpServer.protocol === "https") {
logger.info(
@@ -106,8 +118,6 @@ const main = async (args: Args): Promise<void> => {
httpServer.proxyDomains.forEach((domain) => logger.info(` - *.${domain}`))
}
logger.info(`Automatic updates are ${update.enabled ? "enabled" : "disabled"}`)
if (serverAddress && !options.socket && args.open) {
// The web socket doesn't seem to work if browsing with 0.0.0.0.
const openAddress = serverAddress.replace(/:\/\/0.0.0.0/, "://localhost")
@@ -116,58 +126,62 @@ const main = async (args: Args): Promise<void> => {
}
}
const tryParse = (): Args => {
try {
return parse(process.argv.slice(2))
} catch (error) {
console.error(error.message)
process.exit(1)
async function entry(): Promise<void> {
const tryParse = async (): Promise<Args> => {
try {
return await parse(process.argv.slice(2))
} catch (error) {
console.error(error.message)
process.exit(1)
}
}
const args = await tryParse()
if (args.help) {
console.log("code-server", version, commit)
console.log("")
console.log(`Usage: code-server [options] [path]`)
console.log("")
console.log("Options")
optionDescriptions().forEach((description) => {
console.log("", description)
})
} else if (args.version) {
if (args.json) {
console.log({
codeServer: version,
commit,
vscode: require("../../lib/vscode/package.json").version,
})
} else {
console.log(version, commit)
}
process.exit(0)
} else if (args["list-extensions"] || args["install-extension"] || args["uninstall-extension"]) {
logger.debug("forking vs code cli...")
const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], {
env: {
...process.env,
CODE_SERVER_PARENT_PID: process.pid.toString(),
},
})
vscode.once("message", (message) => {
logger.debug("Got message from VS Code", field("message", message))
if (message.type !== "ready") {
logger.error("Unexpected response waiting for ready response")
process.exit(1)
}
const send: CliMessage = { type: "cli", args }
vscode.send(send)
})
vscode.once("error", (error) => {
logger.error(error.message)
process.exit(1)
})
vscode.on("exit", (code) => process.exit(code || 0))
} else {
wrap(() => main(args))
}
}
const args = tryParse()
if (args.help) {
console.log("code-server", version, commit)
console.log("")
console.log(`Usage: code-server [options] [path]`)
console.log("")
console.log("Options")
optionDescriptions().forEach((description) => {
console.log("", description)
})
} else if (args.version) {
if (args.json) {
console.log({
codeServer: version,
commit,
vscode: require("../../lib/vscode/package.json").version,
})
} else {
console.log(version, commit)
}
process.exit(0)
} else if (args["list-extensions"] || args["install-extension"] || args["uninstall-extension"]) {
logger.debug("forking vs code cli...")
const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], {
env: {
...process.env,
CODE_SERVER_PARENT_PID: process.pid.toString(),
},
})
vscode.once("message", (message) => {
logger.debug("Got message from VS Code", field("message", message))
if (message.type !== "ready") {
logger.error("Unexpected response waiting for ready response")
process.exit(1)
}
const send: CliMessage = { type: "cli", args }
vscode.send(send)
})
vscode.once("error", (error) => {
logger.error(error.message)
process.exit(1)
})
vscode.on("exit", (code) => process.exit(code || 0))
} else {
wrap(() => main(args))
}
entry()

View File

@@ -14,7 +14,7 @@ import * as url from "url"
import { HttpCode, HttpError } from "../common/http"
import { normalize, Options, plural, split } from "../common/util"
import { SocketProxyProvider } from "./socket"
import { getMediaMime, xdgLocalDir } from "./util"
import { getMediaMime, paths } from "./util"
export type Cookies = { [key: string]: string[] | undefined }
export type PostData = { [key: string]: string | string[] | undefined }
@@ -473,7 +473,7 @@ export class HttpServer {
public constructor(private readonly options: HttpServerOptions) {
this.proxyDomains = new Set((options.proxyDomains || []).map((d) => d.replace(/^\*\./, "")))
this.heart = new Heart(path.join(xdgLocalDir, "heartbeat"), async () => {
this.heart = new Heart(path.join(paths.data, "heartbeat"), async () => {
const connections = await this.getConnections()
logger.trace(`${connections} active connection${plural(connections)}`)
return connections !== 0

View File

@@ -1,6 +1,6 @@
import * as fs from "fs-extra"
import * as path from "path"
import { extend, xdgLocalDir } from "./util"
import { extend, paths } from "./util"
import { logger } from "@coder/logger"
export type Settings = { [key: string]: Settings | string | boolean | number }
@@ -60,4 +60,4 @@ export interface CoderSettings extends UpdateSettings {
/**
* Global code-server settings file.
*/
export const settings = new SettingsProvider<CoderSettings>(path.join(xdgLocalDir, "coder.json"))
export const settings = new SettingsProvider<CoderSettings>(path.join(paths.data, "coder.json"))

View File

@@ -4,24 +4,54 @@ import * as fs from "fs-extra"
import * as os from "os"
import * as path from "path"
import * as util from "util"
import envPaths from "env-paths"
import xdgBasedir from "xdg-basedir"
export const tmpdir = path.join(os.tmpdir(), "code-server")
const getXdgDataDir = (): string => {
switch (process.platform) {
case "win32":
return path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), "AppData/Local"), "code-server/Data")
case "darwin":
return path.join(
process.env.XDG_DATA_HOME || path.join(os.homedir(), "Library/Application Support"),
"code-server",
)
default:
return path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local/share"), "code-server")
}
interface Paths {
data: string
config: string
}
export const xdgLocalDir = getXdgDataDir()
export const paths = getEnvPaths()
/**
* Gets the config and data paths for the current platform/configuration.
* On MacOS this function gets the standard XDG directories instead of using the native macOS
* ones. Most CLIs do this as in practice only GUI apps use the standard macOS directories.
*/
function getEnvPaths(): Paths {
let paths: Paths
if (process.platform === "win32") {
paths = envPaths("code-server", {
suffix: "",
})
} else {
if (xdgBasedir.data === undefined || xdgBasedir.config === undefined) {
throw new Error("No home folder?")
}
paths = {
data: path.join(xdgBasedir.data, "code-server"),
config: path.join(xdgBasedir.config, "code-server"),
}
}
return paths
}
/**
* humanPath replaces the home directory in p with ~.
* Makes it more readable.
*
* @param p
*/
export function humanPath(p?: string): string {
if (!p) {
return ""
}
return p.replace(os.homedir(), "~")
}
export const generateCertificate = async (): Promise<{ cert: string; certKey: string }> => {
const paths = {
@@ -51,10 +81,7 @@ export const generatePassword = async (length = 24): Promise<string> => {
}
export const hash = (str: string): string => {
return crypto
.createHash("sha256")
.update(str)
.digest("hex")
return crypto.createHash("sha256").update(str).digest("hex")
}
const mimeTypes: { [key: string]: string } = {
@@ -120,11 +147,7 @@ export const getMediaMime = (filePath?: string): string => {
export const isWsl = async (): Promise<boolean> => {
return (
(process.platform === "linux" &&
os
.release()
.toLowerCase()
.indexOf("microsoft") !== -1) ||
(process.platform === "linux" && os.release().toLowerCase().indexOf("microsoft") !== -1) ||
(await fs.readFile("/proc/version", "utf8")).toLowerCase().indexOf("microsoft") !== -1
)
}
@@ -194,3 +217,51 @@ export function extend(...args: any[]): any {
}
return c
}
/**
* Taken from vs/base/common/charCode.ts. Copied for now instead of importing so
* we don't have to set up a `vs` alias to be able to import with types (since
* the alternative is to directly import from `out`).
*/
const enum CharCode {
Slash = 47,
A = 65,
Z = 90,
a = 97,
z = 122,
Colon = 58,
}
/**
* Compute `fsPath` for the given uri.
* Taken from vs/base/common/uri.ts. It's not imported to avoid also importing
* everything that file imports.
*/
export function pathToFsPath(path: string, keepDriveLetterCasing = false): string {
const isWindows = process.platform === "win32"
const uri = { authority: undefined, path, scheme: "file" }
let value: string
if (uri.authority && uri.path.length > 1 && uri.scheme === "file") {
// unc path: file://shares/c$/far/boo
value = `//${uri.authority}${uri.path}`
} else if (
uri.path.charCodeAt(0) === CharCode.Slash &&
((uri.path.charCodeAt(1) >= CharCode.A && uri.path.charCodeAt(1) <= CharCode.Z) ||
(uri.path.charCodeAt(1) >= CharCode.a && uri.path.charCodeAt(1) <= CharCode.z)) &&
uri.path.charCodeAt(2) === CharCode.Colon
) {
if (!keepDriveLetterCasing) {
// windows drive letter: file:///c:/far/boo
value = uri.path[1].toLowerCase() + uri.path.substr(2)
} else {
value = uri.path.substr(1)
}
} else {
// other path
value = uri.path
}
if (isWindows) {
value = value.replace(/\//g, "\\")
}
return value
}

View File

@@ -38,7 +38,7 @@ export class IpcMain {
// Ensure we control when the process exits.
this.exit = process.exit
process.exit = function(code?: number) {
process.exit = function (code?: number) {
logger.warn(`process.exit() was prevented: ${code || "unknown code"}.`)
} as (code?: number) => never

View File

@@ -2,7 +2,7 @@ import { logger, Level } from "@coder/logger"
import * as assert from "assert"
import * as path from "path"
import { parse } from "../src/node/cli"
import { xdgLocalDir } from "../src/node/util"
import { paths } from "../src/node/util"
describe("cli", () => {
beforeEach(() => {
@@ -12,17 +12,17 @@ describe("cli", () => {
// The parser will always fill these out.
const defaults = {
_: [],
"extensions-dir": path.join(xdgLocalDir, "extensions"),
"user-data-dir": xdgLocalDir,
"extensions-dir": path.join(paths.data, "extensions"),
"user-data-dir": paths.data,
}
it("should set defaults", () => {
assert.deepEqual(parse([]), defaults)
it("should set defaults", async () => {
assert.deepEqual(await await parse([]), defaults)
})
it("should parse all available options", () => {
it("should parse all available options", async () => {
assert.deepEqual(
parse([
await await parse([
"--bind-addr=192.169.0.1:8080",
"--auth",
"none",
@@ -84,8 +84,8 @@ describe("cli", () => {
)
})
it("should work with short options", () => {
assert.deepEqual(parse(["-vvv", "-v"]), {
it("should work with short options", async () => {
assert.deepEqual(await parse(["-vvv", "-v"]), {
...defaults,
log: "trace",
verbose: true,
@@ -95,9 +95,9 @@ describe("cli", () => {
assert.equal(logger.level, Level.Trace)
})
it("should use log level env var", () => {
it("should use log level env var", async () => {
process.env.LOG_LEVEL = "debug"
assert.deepEqual(parse([]), {
assert.deepEqual(await parse([]), {
...defaults,
log: "debug",
})
@@ -105,7 +105,7 @@ describe("cli", () => {
assert.equal(logger.level, Level.Debug)
process.env.LOG_LEVEL = "trace"
assert.deepEqual(parse([]), {
assert.deepEqual(await parse([]), {
...defaults,
log: "trace",
verbose: true,
@@ -114,9 +114,9 @@ describe("cli", () => {
assert.equal(logger.level, Level.Trace)
})
it("should prefer --log to env var and --verbose to --log", () => {
it("should prefer --log to env var and --verbose to --log", async () => {
process.env.LOG_LEVEL = "debug"
assert.deepEqual(parse(["--log", "info"]), {
assert.deepEqual(await parse(["--log", "info"]), {
...defaults,
log: "info",
})
@@ -124,7 +124,7 @@ describe("cli", () => {
assert.equal(logger.level, Level.Info)
process.env.LOG_LEVEL = "trace"
assert.deepEqual(parse(["--log", "info"]), {
assert.deepEqual(await parse(["--log", "info"]), {
...defaults,
log: "info",
})
@@ -132,7 +132,7 @@ describe("cli", () => {
assert.equal(logger.level, Level.Info)
process.env.LOG_LEVEL = "warn"
assert.deepEqual(parse(["--log", "info", "--verbose"]), {
assert.deepEqual(await parse(["--log", "info", "--verbose"]), {
...defaults,
log: "trace",
verbose: true,
@@ -141,31 +141,34 @@ describe("cli", () => {
assert.equal(logger.level, Level.Trace)
})
it("should ignore invalid log level env var", () => {
it("should ignore invalid log level env var", async () => {
process.env.LOG_LEVEL = "bogus"
assert.deepEqual(parse([]), defaults)
assert.deepEqual(await parse([]), defaults)
})
it("should error if value isn't provided", () => {
assert.throws(() => parse(["--auth"]), /--auth requires a value/)
assert.throws(() => parse(["--auth=", "--log=debug"]), /--auth requires a value/)
assert.throws(() => parse(["--auth", "--log"]), /--auth requires a value/)
assert.throws(() => parse(["--auth", "--invalid"]), /--auth requires a value/)
assert.throws(() => parse(["--bind-addr"]), /--bind-addr requires a value/)
it("should error if value isn't provided", async () => {
await assert.rejects(async () => await parse(["--auth"]), /--auth requires a value/)
await assert.rejects(async () => await parse(["--auth=", "--log=debug"]), /--auth requires a value/)
await assert.rejects(async () => await parse(["--auth", "--log"]), /--auth requires a value/)
await assert.rejects(async () => await parse(["--auth", "--invalid"]), /--auth requires a value/)
await assert.rejects(async () => await parse(["--bind-addr"]), /--bind-addr requires a value/)
})
it("should error if value is invalid", () => {
assert.throws(() => parse(["--port", "foo"]), /--port must be a number/)
assert.throws(() => parse(["--auth", "invalid"]), /--auth valid values: \[password, none\]/)
assert.throws(() => parse(["--log", "invalid"]), /--log valid values: \[trace, debug, info, warn, error\]/)
it("should error if value is invalid", async () => {
await assert.rejects(async () => await parse(["--port", "foo"]), /--port must be a number/)
await assert.rejects(async () => await parse(["--auth", "invalid"]), /--auth valid values: \[password, none\]/)
await assert.rejects(
async () => await parse(["--log", "invalid"]),
/--log valid values: \[trace, debug, info, warn, error\]/,
)
})
it("should error if the option doesn't exist", () => {
assert.throws(() => parse(["--foo"]), /Unknown option --foo/)
it("should error if the option doesn't exist", async () => {
await assert.rejects(async () => await parse(["--foo"]), /Unknown option --foo/)
})
it("should not error if the value is optional", () => {
assert.deepEqual(parse(["--cert"]), {
it("should not error if the value is optional", async () => {
assert.deepEqual(await parse(["--cert"]), {
...defaults,
cert: {
value: undefined,
@@ -173,30 +176,33 @@ describe("cli", () => {
})
})
it("should not allow option-like values", () => {
assert.throws(() => parse(["--socket", "--socket-path-value"]), /--socket requires a value/)
it("should not allow option-like values", async () => {
await assert.rejects(async () => await parse(["--socket", "--socket-path-value"]), /--socket requires a value/)
// If you actually had a path like this you would do this instead:
assert.deepEqual(parse(["--socket", "./--socket-path-value"]), {
assert.deepEqual(await parse(["--socket", "./--socket-path-value"]), {
...defaults,
socket: path.resolve("--socket-path-value"),
})
assert.throws(() => parse(["--cert", "--socket-path-value"]), /Unknown option --socket-path-value/)
await assert.rejects(
async () => await parse(["--cert", "--socket-path-value"]),
/Unknown option --socket-path-value/,
)
})
it("should allow positional arguments before options", () => {
assert.deepEqual(parse(["foo", "test", "--auth", "none"]), {
it("should allow positional arguments before options", async () => {
assert.deepEqual(await parse(["foo", "test", "--auth", "none"]), {
...defaults,
_: ["foo", "test"],
auth: "none",
})
})
it("should support repeatable flags", () => {
assert.deepEqual(parse(["--proxy-domain", "*.coder.com"]), {
it("should support repeatable flags", async () => {
assert.deepEqual(await parse(["--proxy-domain", "*.coder.com"]), {
...defaults,
"proxy-domain": ["*.coder.com"],
})
assert.deepEqual(parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "test.com"]), {
assert.deepEqual(await parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "test.com"]), {
...defaults,
"proxy-domain": ["*.coder.com", "test.com"],
})

244
yarn.lock
View File

@@ -892,6 +892,24 @@
remark "^12.0.0"
unist-util-find-all-after "^3.0.1"
"@textlint/ast-node-types@^4.0.3":
version "4.2.5"
resolved "https://registry.yarnpkg.com/@textlint/ast-node-types/-/ast-node-types-4.2.5.tgz#ae13981bc8711c98313a6ac1c361194d6bf2d39b"
integrity sha512-+rEx4jLOeZpUcdvll7jEg/7hNbwYvHWFy4IGW/tk2JdbyB3SJVyIP6arAwzTH/sp/pO9jftfyZnRj4//sLbLvQ==
"@textlint/markdown-to-ast@~6.0.9":
version "6.0.9"
resolved "https://registry.yarnpkg.com/@textlint/markdown-to-ast/-/markdown-to-ast-6.0.9.tgz#e7c89e5ad15d17dcd8e5a62758358936827658fa"
integrity sha512-hfAWBvTeUGh5t5kTn2U3uP3qOSM1BSrxzl1jF3nn0ywfZXpRBZr5yRjXnl4DzIYawCtZOshmRi/tI3/x4TE1jQ==
dependencies:
"@textlint/ast-node-types" "^4.0.3"
debug "^2.1.3"
remark-frontmatter "^1.2.0"
remark-parse "^5.0.0"
structured-source "^3.0.2"
traverse "^0.6.6"
unified "^6.1.6"
"@types/adm-zip@^0.4.32":
version "0.4.33"
resolved "https://registry.yarnpkg.com/@types/adm-zip/-/adm-zip-0.4.33.tgz#ea5b94f771443f655613b64f920c0555867200dd"
@@ -931,6 +949,11 @@
dependencies:
"@types/node" "*"
"@types/js-yaml@^3.12.3":
version "3.12.3"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.3.tgz#abf383c5b639d0aa8b8c4a420d6a85f703357d6c"
integrity sha512-otRe77JNNWzoVGLKw8TCspKswRoQToys4tuL6XYVBFxjgeM0RUrx7m3jkaTdxILxeGry3zM8mGYkGXMeQ02guA==
"@types/json-schema@^7.0.3":
version "7.0.4"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
@@ -1127,6 +1150,13 @@ alphanum-sort@^1.0.0:
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=
anchor-markdown-header@^0.5.5:
version "0.5.7"
resolved "https://registry.yarnpkg.com/anchor-markdown-header/-/anchor-markdown-header-0.5.7.tgz#045063d76e6a1f9cd327a57a0126aa0fdec371a7"
integrity sha1-BFBj125qH5zTJ6V6ASaqD97Dcac=
dependencies:
emoji-regex "~6.1.0"
ansi-colors@3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813"
@@ -1440,6 +1470,11 @@ boolbase@^1.0.0, boolbase@~1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
boundary@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/boundary/-/boundary-1.0.1.tgz#4d67dc2602c0cc16dd9bce7ebf87e948290f5812"
integrity sha1-TWfcJgLAzBbdm85+v4fpSCkPWBI=
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -2281,7 +2316,7 @@ deasync@^0.1.14:
bindings "^1.5.0"
node-addon-api "^1.7.1"
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
debug@2.6.9, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -2410,6 +2445,18 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
doctoc@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/doctoc/-/doctoc-1.4.0.tgz#3115aa61d0a92f0abb0672036918ea904f5b9e02"
integrity sha512-8IAq3KdMkxhXCUF+xdZxdJxwuz8N2j25sMgqiu4U4JWluN9tRKMlAalxGASszQjlZaBprdD2YfXpL3VPWUD4eg==
dependencies:
"@textlint/markdown-to-ast" "~6.0.9"
anchor-markdown-header "^0.5.5"
htmlparser2 "~3.9.2"
minimist "~1.2.0"
underscore "~1.8.3"
update-section "^0.3.0"
doctrine@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@@ -2438,7 +2485,7 @@ domain-browser@^1.1.1:
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
domelementtype@1, domelementtype@^1.3.1:
domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
@@ -2535,6 +2582,11 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
emoji-regex@~6.1.0:
version "6.1.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.3.tgz#ec79a3969b02d2ecf2b72254279bf99bc7a83932"
integrity sha1-7HmjlpsC0uzytyJUJ5v5m8eoOTI=
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
@@ -2557,6 +2609,11 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
env-paths@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
envinfo@^7.3.1:
version "7.5.1"
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.1.tgz#93c26897225a00457c75e734d354ea9106a72236"
@@ -2962,6 +3019,13 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
fault@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13"
integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==
dependencies:
format "^0.2.0"
figures@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -3077,6 +3141,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
format@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=
fragment-cache@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
@@ -3465,6 +3534,18 @@ htmlparser2@^3.10.0, htmlparser2@^3.9.2:
inherits "^2.0.1"
readable-stream "^3.1.1"
htmlparser2@~3.9.2:
version "3.9.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
integrity sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=
dependencies:
domelementtype "^1.3.0"
domhandler "^2.3.0"
domutils "^1.5.1"
entities "^1.1.1"
inherits "^2.0.1"
readable-stream "^2.0.2"
http-errors@~1.7.2:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
@@ -3680,7 +3761,7 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
is-buffer@^1.1.5, is-buffer@~1.1.1:
is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffer@~1.1.1:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
@@ -4414,7 +4495,7 @@ minimist-options@^4.0.1:
arrify "^1.0.1"
is-plain-obj "^1.1.0"
minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.0:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
@@ -4515,7 +4596,7 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
nan@^2.12.1, nan@^2.14.0:
nan@^2.12.1:
version "2.14.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
@@ -4594,13 +4675,6 @@ node-libs-browser@^2.0.0:
util "^0.11.0"
vm-browserify "^1.0.1"
node-pty@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0.tgz#8f9bcc0d1c5b970a3184ffd533d862c7eb6590a6"
integrity sha512-MBnCQl83FTYOu7B4xWw10AW77AAh7ThCE1VXEv+JeWj8mSpGo+0bwgsV+b23ljBFwEM9OmsOv3kM27iUPPm84g==
dependencies:
nan "^2.14.0"
node-releases@^1.1.53:
version "1.1.53"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4"
@@ -4949,6 +5023,18 @@ parse-asn1@^5.0.0:
pbkdf2 "^3.0.3"
safe-buffer "^5.1.1"
parse-entities@^1.1.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50"
integrity sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==
dependencies:
character-entities "^1.0.0"
character-entities-legacy "^1.0.0"
character-reference-invalid "^1.0.0"
is-alphanumerical "^1.0.0"
is-decimal "^1.0.0"
is-hexadecimal "^1.0.0"
parse-entities@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
@@ -5547,10 +5633,10 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
prettier@^1.18.2:
version "1.19.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
prettier@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4"
integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==
private@^0.1.8:
version "0.1.8"
@@ -5828,6 +5914,35 @@ regjsparser@^0.6.4:
dependencies:
jsesc "~0.5.0"
remark-frontmatter@^1.2.0:
version "1.3.3"
resolved "https://registry.yarnpkg.com/remark-frontmatter/-/remark-frontmatter-1.3.3.tgz#67ec63c89da5a84bb793ecec166e11b4eb47af10"
integrity sha512-fM5eZPBvu2pVNoq3ZPW22q+5Ativ1oLozq2qYt9I2oNyxiUd/tDl0iLLntEVAegpZIslPWg1brhcP1VsaSVUag==
dependencies:
fault "^1.0.1"
xtend "^4.0.1"
remark-parse@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95"
integrity sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==
dependencies:
collapse-white-space "^1.0.2"
is-alphabetical "^1.0.0"
is-decimal "^1.0.0"
is-whitespace-character "^1.0.0"
is-word-character "^1.0.0"
markdown-escapes "^1.0.0"
parse-entities "^1.1.0"
repeat-string "^1.5.4"
state-toggle "^1.0.0"
trim "0.0.1"
trim-trailing-lines "^1.0.0"
unherit "^1.0.4"
unist-util-remove-position "^1.0.0"
vfile-location "^2.0.0"
xtend "^4.0.1"
remark-parse@^8.0.0:
version "8.0.2"
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.2.tgz#5999bc0b9c2e3edc038800a64ff103d0890b318b"
@@ -6555,6 +6670,13 @@ strip-json-comments@^3.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180"
integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==
structured-source@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/structured-source/-/structured-source-3.0.2.tgz#dd802425e0f53dc4a6e7aca3752901a1ccda7af5"
integrity sha1-3YAkJeD1PcSm56yjdSkBoczaevU=
dependencies:
boundary "^1.0.1"
style-search@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
@@ -6874,6 +6996,11 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
traverse@^0.6.6:
version "0.6.6"
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=
trim-newlines@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30"
@@ -6988,6 +7115,11 @@ uncss@^0.17.2:
postcss-selector-parser "6.0.2"
request "^2.88.0"
underscore@~1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=
unherit@^1.0.4:
version "1.1.3"
resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22"
@@ -7027,6 +7159,18 @@ unicode-trie@^0.3.1:
pako "^0.2.5"
tiny-inflate "^1.0.0"
unified@^6.1.6:
version "6.2.0"
resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba"
integrity sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==
dependencies:
bail "^1.0.0"
extend "^3.0.0"
is-plain-obj "^1.1.0"
trough "^1.0.0"
vfile "^2.0.0"
x-is-string "^0.1.0"
unified@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/unified/-/unified-9.0.0.tgz#12b099f97ee8b36792dbad13d278ee2f696eed1d"
@@ -7066,11 +7210,23 @@ unist-util-find-all-after@^3.0.1:
dependencies:
unist-util-is "^4.0.0"
unist-util-is@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd"
integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==
unist-util-is@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.2.tgz#c7d1341188aa9ce5b3cff538958de9895f14a5de"
integrity sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==
unist-util-remove-position@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz#ec037348b6102c897703eee6d0294ca4755a2020"
integrity sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==
dependencies:
unist-util-visit "^1.1.0"
unist-util-remove-position@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc"
@@ -7078,6 +7234,11 @@ unist-util-remove-position@^2.0.0:
dependencies:
unist-util-visit "^2.0.0"
unist-util-stringify-position@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6"
integrity sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==
unist-util-stringify-position@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da"
@@ -7085,6 +7246,13 @@ unist-util-stringify-position@^2.0.0:
dependencies:
"@types/unist" "^2.0.2"
unist-util-visit-parents@^2.0.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz#25e43e55312166f3348cae6743588781d112c1e9"
integrity sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==
dependencies:
unist-util-is "^3.0.0"
unist-util-visit-parents@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.0.2.tgz#d4076af3011739c71d2ce99d05de37d545f4351d"
@@ -7093,6 +7261,13 @@ unist-util-visit-parents@^3.0.0:
"@types/unist" "^2.0.0"
unist-util-is "^4.0.0"
unist-util-visit@^1.1.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3"
integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==
dependencies:
unist-util-visit-parents "^2.0.0"
unist-util-visit@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.2.tgz#3843782a517de3d2357b4c193b24af2d9366afb7"
@@ -7125,6 +7300,11 @@ upath@^1.1.1:
resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
update-section@^0.3.0:
version "0.3.3"
resolved "https://registry.yarnpkg.com/update-section/-/update-section-0.3.3.tgz#458f17820d37820dc60e20b86d94391b00123158"
integrity sha1-RY8Xgg03gg3GDiC4bZQ5GwASMVg=
uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
@@ -7211,12 +7391,17 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
vfile-location@^2.0.0:
version "2.0.6"
resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.6.tgz#8a274f39411b8719ea5728802e10d9e0dff1519e"
integrity sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==
vfile-location@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.0.1.tgz#d78677c3546de0f7cd977544c367266764d31bb3"
integrity sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==
vfile-message@^2.0.0, vfile-message@^2.0.2:
vfile-message@^1.0.0, vfile-message@^2.0.0, vfile-message@^2.0.2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a"
integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==
@@ -7224,6 +7409,16 @@ vfile-message@^2.0.0, vfile-message@^2.0.2:
"@types/unist" "^2.0.0"
unist-util-stringify-position "^2.0.0"
vfile@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/vfile/-/vfile-2.3.0.tgz#e62d8e72b20e83c324bc6c67278ee272488bf84a"
integrity sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==
dependencies:
is-buffer "^1.1.4"
replace-ext "1.0.0"
unist-util-stringify-position "^1.0.0"
vfile-message "^1.0.0"
vfile@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.1.0.tgz#d79248957f43225d57ff67a56effc67bef08946e"
@@ -7380,6 +7575,16 @@ ws@^7.2.0:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.5.tgz#abb1370d4626a5a9cd79d8de404aa18b3465d10d"
integrity sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==
x-is-string@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"
integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=
xdg-basedir@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
@@ -7478,6 +7683,11 @@ yargs@^14.0.0:
y18n "^4.0.0"
yargs-parser "^15.0.1"
yarn@^1.22.4:
version "1.22.4"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.4.tgz#01c1197ca5b27f21edc8bc472cd4c8ce0e5a470e"
integrity sha512-oYM7hi/lIWm9bCoDMEWgffW8aiNZXCWeZ1/tGy0DWrN6vmzjCXIKu2Y21o8DYVBUtiktwKcNoxyGl/2iKLUNGA==
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"