mirror of
https://github.com/coder/code-server.git
synced 2026-04-13 21:32:52 -05:00
Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41ad0c0c4c | ||
|
|
2a127f168c | ||
|
|
07da291d72 | ||
|
|
55c916a987 | ||
|
|
05d8b61a32 | ||
|
|
244775dab5 | ||
|
|
25bf871e16 | ||
|
|
2a2dade309 | ||
|
|
cf30b536ef | ||
|
|
05a0f213a7 | ||
|
|
c08e3bb06d | ||
|
|
4bace1ae4a | ||
|
|
f7b0cea42c | ||
|
|
43aa0401e0 | ||
|
|
74dc5a881f | ||
|
|
d7f67b80df | ||
|
|
a1a0aec472 | ||
|
|
4756257207 | ||
|
|
4c6ad046b0 | ||
|
|
6685a3e364 | ||
|
|
66fe663e33 | ||
|
|
966e9cc238 | ||
|
|
a60f61f9b3 | ||
|
|
58d72d53a1 | ||
|
|
f5cf3fd331 | ||
|
|
d7f06975a6 | ||
|
|
5446e0ad43 | ||
|
|
22ebfdc3af | ||
|
|
aab973a795 | ||
|
|
a4a0048b90 | ||
|
|
1fcb0c3ddd | ||
|
|
42dcfc94ab | ||
|
|
db359c40c7 | ||
|
|
8f0066b4a8 | ||
|
|
fa548e95e1 | ||
|
|
102f51ce1f | ||
|
|
14c5aecd45 | ||
|
|
3044224729 | ||
|
|
05beccf671 | ||
|
|
883dd13850 | ||
|
|
646ee3ad7f | ||
|
|
850c7e1a91 | ||
|
|
6bf51caa17 | ||
|
|
f13ba9401b | ||
|
|
75717749b2 | ||
|
|
0a07d67c8d | ||
|
|
bea8bb0519 | ||
|
|
de7d0394ae | ||
|
|
cef7d42652 | ||
|
|
c52198f30d | ||
|
|
28e98c0ee0 | ||
|
|
c32d8b155f | ||
|
|
5c06646f58 | ||
|
|
60233d0974 | ||
|
|
240c8e266e | ||
|
|
64e915de4a | ||
|
|
d3074278ca | ||
|
|
8acb2aec11 | ||
|
|
ea1949e440 | ||
|
|
9efcf7f3ce | ||
|
|
ba4a24809c | ||
|
|
497b01bffe | ||
|
|
f169e3ac66 | ||
|
|
c17f3ffc79 | ||
|
|
d234ddc1e1 | ||
|
|
28c7340608 | ||
|
|
3394ece107 | ||
|
|
500ba92466 | ||
|
|
d9508946b5 | ||
|
|
eae285cf93 | ||
|
|
39faceeee4 | ||
|
|
07bc3d9774 | ||
|
|
f15580b28a | ||
|
|
fa2aed6d46 | ||
|
|
693fdbefb4 | ||
|
|
cb11e1f750 | ||
|
|
9e4206aa41 | ||
|
|
5164f925ee | ||
|
|
05530db20e | ||
|
|
aa05993cf0 | ||
|
|
f599e1d72e | ||
|
|
caee285240 |
@@ -2,7 +2,7 @@ parser: "@typescript-eslint/parser"
|
||||
env:
|
||||
browser: true
|
||||
es6: true # Map, etc.
|
||||
mocha: true
|
||||
jest: true
|
||||
node: true
|
||||
|
||||
parserOptions:
|
||||
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.afdesign filter=lfs diff=lfs merge=lfs -text
|
||||
2
.github/ISSUE_TEMPLATE/bug-report.md
vendored
2
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -7,7 +7,7 @@ assignees: ""
|
||||
---
|
||||
|
||||
<!--
|
||||
Please see https://github.com/cdr/code-server/blob/master/doc/FAQ.md#how-do-i-debug-issues-with-code-server
|
||||
Please see https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-debug-issues-with-code-server
|
||||
and include any logging information relevant to the issue.
|
||||
|
||||
Please search for existing issues before filing.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/extension-request.md
vendored
2
.github/ISSUE_TEMPLATE/extension-request.md
vendored
@@ -9,7 +9,7 @@ assignees: ""
|
||||
<!--
|
||||
Details on the code-server extension marketplace are at
|
||||
|
||||
https://github.com/cdr/code-server/blob/master/doc/FAQ.md#whats-the-deal-with-extensions
|
||||
https://github.com/cdr/code-server/blob/master/docs/FAQ.md#whats-the-deal-with-extensions
|
||||
|
||||
Please fill in the issue template!
|
||||
-->
|
||||
|
||||
17
.github/workflows/ci.yaml
vendored
17
.github/workflows/ci.yaml
vendored
@@ -22,13 +22,24 @@ jobs:
|
||||
args: ./ci/steps/lint.sh
|
||||
|
||||
test:
|
||||
needs: linux-amd64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Run ./ci/steps/test.sh
|
||||
uses: ./ci/images/debian10
|
||||
- name: Download release packages
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
args: ./ci/steps/test.sh
|
||||
name: release-packages
|
||||
path: ./release-packages
|
||||
- name: Untar code-server file
|
||||
run: |
|
||||
cd release-packages && tar -xzf code-server*-linux-amd64.tar.gz
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- name: Install dependencies and run tests
|
||||
run: |
|
||||
node ./release-packages/code-server*-linux-amd64 &
|
||||
yarn --frozen-lockfile
|
||||
yarn test
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,3 +14,5 @@ node-*
|
||||
/plugins
|
||||
/lib/coder-cloud-agent
|
||||
.home
|
||||
coverage
|
||||
**/.DS_Store
|
||||
@@ -50,7 +50,7 @@
|
||||
{
|
||||
"file": "src/node/heart.ts",
|
||||
"line": 7,
|
||||
"description": "code-server's heart beats to indicate recent activity.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/doc/FAQ.md#heartbeat-file](https://github.com/cdr/code-server/blob/master/doc/FAQ.md#heartbeat-file)"
|
||||
"description": "code-server's heart beats to indicate recent activity.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#heartbeat-file](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#heartbeat-file)"
|
||||
},
|
||||
{
|
||||
"file": "src/node/socket.ts",
|
||||
@@ -80,12 +80,12 @@
|
||||
{
|
||||
"file": "src/node/routes/domainProxy.ts",
|
||||
"line": 18,
|
||||
"description": "code-server provides a built-in proxy to help in developing web-based applications. This is the code for the domain-based proxy.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/doc/FAQ.md#how-do-i-securely-access-web-services](https://github.com/cdr/code-server/blob/master/doc/FAQ.md#how-do-i-securely-access-web-services)"
|
||||
"description": "code-server provides a built-in proxy to help in developing web-based applications. This is the code for the domain-based proxy.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services)"
|
||||
},
|
||||
{
|
||||
"file": "src/node/routes/pathProxy.ts",
|
||||
"line": 19,
|
||||
"description": "Here is the path-based version of the proxy.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/doc/FAQ.md#how-do-i-securely-access-web-services](https://github.com/cdr/code-server/blob/master/doc/FAQ.md#how-do-i-securely-access-web-services)"
|
||||
"description": "Here is the path-based version of the proxy.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services)"
|
||||
},
|
||||
{
|
||||
"file": "src/node/proxy.ts",
|
||||
@@ -95,7 +95,7 @@
|
||||
{
|
||||
"file": "src/node/routes/health.ts",
|
||||
"line": 5,
|
||||
"description": "A simple endpoint that lets you see if code-server is up.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/doc/FAQ.md#healthz-endpoint](https://github.com/cdr/code-server/blob/master/doc/FAQ.md#healthz-endpoint)"
|
||||
"description": "A simple endpoint that lets you see if code-server is up.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#healthz-endpoint](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#healthz-endpoint)"
|
||||
},
|
||||
{
|
||||
"file": "src/node/routes/login.ts",
|
||||
@@ -145,7 +145,7 @@
|
||||
{
|
||||
"directory": "lib/vscode",
|
||||
"line": 1,
|
||||
"description": "code-server makes use of VS Code's frontend web/remote support. Most of the modifications implement the remote server since that portion of the code is closed source and not released with VS Code.\n\nWe also have a few bug fixes and have added some features (like client-side extensions). See [https://github.com/cdr/code-server/blob/master/doc/CONTRIBUTING.md#modifications-to-vs-code](https://github.com/cdr/code-server/blob/master/doc/CONTRIBUTING.md#modifications-to-vs-code) for a list.\n\nWe make an effort to keep the modifications as few as possible."
|
||||
"description": "code-server makes use of VS Code's frontend web/remote support. Most of the modifications implement the remote server since that portion of the code is closed source and not released with VS Code.\n\nWe also have a few bug fixes and have added some features (like client-side extensions). See [https://github.com/cdr/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code](https://github.com/cdr/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code) for a list.\n\nWe make an effort to keep the modifications as few as possible."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
{
|
||||
"file": "src/node/app.ts",
|
||||
"line": 62,
|
||||
"description": "## That's it!\n\n\nThat's all there is to it! When this tour ends, your terminal session may stop, but just use `yarn watch` to start developing from here on out!\n\n\nIf you haven't already, be sure to check out these resources:\n- [Tour: Contributing](command:codetour.startTourByTitle?[\"Contributing\")\n- [Docs: FAQ.md](https://github.com/cdr/code-server/blob/master/doc/FAQ.md)\n- [Docs: CONTRIBUTING.md](https://github.com/cdr/code-server/blob/master/doc/CONTRIBUTING.md)\n- [Community: GitHub Discussions](https://github.com/cdr/code-server/discussions)\n- [Community: Slack](https://community.coder.com)"
|
||||
"description": "## That's it!\n\n\nThat's all there is to it! When this tour ends, your terminal session may stop, but just use `yarn watch` to start developing from here on out!\n\n\nIf you haven't already, be sure to check out these resources:\n- [Tour: Contributing](command:codetour.startTourByTitle?[\"Contributing\")\n- [Docs: FAQ.md](https://github.com/cdr/code-server/blob/master/docs/FAQ.md)\n- [Docs: CONTRIBUTING.md](https://github.com/cdr/code-server/blob/master/docs/CONTRIBUTING.md)\n- [Community: GitHub Discussions](https://github.com/cdr/code-server/discussions)\n- [Community: Slack](https://community.coder.com)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
12
README.md
12
README.md
@@ -1,8 +1,10 @@
|
||||
# code-server · [](https://github.com/cdr/code-server/discussions) [](https://cdr.co/join-community) [](https://twitter.com/coderhq)
|
||||
|
||||

|
||||
|
||||
Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and access it in the browser.
|
||||
|
||||

|
||||

|
||||
|
||||
## Highlights
|
||||
|
||||
@@ -15,7 +17,7 @@ Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and a
|
||||
There are two ways to get started:
|
||||
|
||||
1. Using the [install script](./install.sh), which automates most of the process. The script uses the system package manager (if possible)
|
||||
2. Manually installing code-server; see [Installation](./doc/install.md) for instructions applicable to most use cases
|
||||
2. Manually installing code-server; see [Installation](./docs/install.md) for instructions applicable to most use cases
|
||||
|
||||
If you choose to use the install script, you can preview what occurs during the install process:
|
||||
|
||||
@@ -31,7 +33,7 @@ curl -fsSL https://code-server.dev/install.sh | sh
|
||||
|
||||
When done, the install script prints out instructions for running and starting code-server.
|
||||
|
||||
We also have an in-depth [setup and configuration](./doc/guide.md) guide.
|
||||
We also have an in-depth [setup and configuration](./docs/guide.md) guide.
|
||||
|
||||
### Cloud Program ☁️
|
||||
|
||||
@@ -49,11 +51,11 @@ Proxying code-server to Coder Cloud, you can access your IDE at https://valmar-j
|
||||
|
||||
## FAQ
|
||||
|
||||
See [./doc/FAQ.md](./doc/FAQ.md).
|
||||
See [./docs/FAQ.md](./docs/FAQ.md).
|
||||
|
||||
## Want to help?
|
||||
|
||||
See [CONTRIBUTING](./doc/CONTRIBUTING.md) for details.
|
||||
See [CONTRIBUTING](./docs/CONTRIBUTING.md) for details.
|
||||
|
||||
## Hiring
|
||||
|
||||
|
||||
21
ci/README.md
21
ci/README.md
@@ -16,11 +16,13 @@ Make sure you have `$GITHUB_TOKEN` set and [hub](https://github.com/github/hub)
|
||||
|
||||
1. Update the version of code-server and make a PR.
|
||||
1. Update in `package.json`
|
||||
2. Update in [./doc/install.md](../doc/install.md)
|
||||
2. Update in [./docs/install.md](../docs/install.md)
|
||||
3. Update in [./ci/helm-chart/README.md](../ci/helm-chart/README.md)
|
||||
- Remember to update the chart version as well on top of appVersion in `Chart.yaml`.
|
||||
- Run `rg -g '!yarn.lock' -g '!*.svg' '3\.7\.5'` to ensure all values have been
|
||||
changed. Replace the numbers as needed.
|
||||
- You can install `rg` or `ripgrep` on macOS [here](https://formulae.brew.sh/formula/ripgrep).
|
||||
4. Update the code coverage badge (see [here](#updating-code-coverage-in-readme) for instructions)
|
||||
2. GitHub actions will generate the `npm-package`, `release-packages` and `release-images` artifacts.
|
||||
1. You do not have to wait for these.
|
||||
3. Run `yarn release:github-draft` to create a GitHub draft release from the template with
|
||||
@@ -43,12 +45,25 @@ Make sure you have `$GITHUB_TOKEN` set and [hub](https://github.com/github/hub)
|
||||
11. Update the homebrew package.
|
||||
- Send a pull request to [homebrew-core](https://github.com/Homebrew/homebrew-core) with the URL in the [formula](https://github.com/Homebrew/homebrew-core/blob/master/Formula/code-server.rb) updated.
|
||||
|
||||
## Updating Code Coverage in README
|
||||
|
||||
Currently, we run a command to manually generate the code coverage shield. Follow these steps:
|
||||
|
||||
1. Run `yarn badges`
|
||||
2. Go into the README and change the color from `red` to `green` in this line:
|
||||
|
||||
```
|
||||

|
||||
```
|
||||
|
||||
NOTE: we have to manually change the color because the default is red if coverage is less than 80. See code [here](https://github.com/olavoparno/istanbul-badges-readme/blob/develop/src/editor.ts#L24-L33).
|
||||
|
||||
## dev
|
||||
|
||||
This directory contains scripts used for the development of code-server.
|
||||
|
||||
- [./ci/dev/image](./dev/image)
|
||||
- See [./doc/CONTRIBUTING.md](../doc/CONTRIBUTING.md) for docs on the development container.
|
||||
- See [./docs/CONTRIBUTING.md](../docs/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`)
|
||||
@@ -59,7 +74,7 @@ This directory contains scripts used for the development of code-server.
|
||||
- Runs `yarn fmt`, `yarn lint` and `yarn test`.
|
||||
- [./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).
|
||||
- Example usage in [./docs/CONTRIBUTING.md](../docs/CONTRIBUTING.md).
|
||||
- [./ci/dev/gen_icons.sh](./ci/dev/gen_icons.sh) (`yarn icons`)
|
||||
- Generates the various icons from a single `.svg` favicon in
|
||||
`src/browser/media/favicon.svg`.
|
||||
|
||||
@@ -43,6 +43,10 @@ bundle_code_server() {
|
||||
rsync src/browser/pages/*.html "$RELEASE_PATH/src/browser/pages"
|
||||
rsync src/browser/robots.txt "$RELEASE_PATH/src/browser"
|
||||
|
||||
# Add typings for plugins
|
||||
mkdir -p "$RELEASE_PATH/typings"
|
||||
rsync typings/pluginapi.d.ts "$RELEASE_PATH/typings"
|
||||
|
||||
# Adds the commit to package.json
|
||||
jq --slurp '.[0] * .[1]' package.json <(
|
||||
cat << EOF
|
||||
@@ -79,8 +83,9 @@ bundle_vscode() {
|
||||
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"
|
||||
mkdir -p "$VSCODE_OUT_PATH/resources/"{linux,web}
|
||||
rsync "$VSCODE_SRC_PATH/resources/linux/code.png" "$VSCODE_OUT_PATH/resources/linux/code.png"
|
||||
rsync "$VSCODE_SRC_PATH/resources/web/callback.html" "$VSCODE_OUT_PATH/resources/web/callback.html"
|
||||
|
||||
# Adds the commit and date to product.json
|
||||
jq --slurp '.[0] * .[1]' "$VSCODE_SRC_PATH/product.json" <(
|
||||
|
||||
@@ -33,7 +33,7 @@ main() {
|
||||
|
||||
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"
|
||||
echo "Please see https://github.com/cdr/code-server/blob/master/docs/npm.md"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ main() {
|
||||
hub release create \
|
||||
--file - \
|
||||
-t "$(git rev-parse HEAD)" \
|
||||
--draft "${assets[@]}" "v$VERSION" << EOF
|
||||
--draft "v$VERSION" << EOF
|
||||
v$VERSION
|
||||
|
||||
VS Code v$(vscode_version)
|
||||
@@ -20,6 +20,7 @@ maintains all user data in \`~/.local/share/code-server\` so that it is preserve
|
||||
installations.
|
||||
|
||||
## New Features
|
||||
|
||||
- ⭐ Summarize new features here with references to issues
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
@@ -23,12 +23,13 @@ main() {
|
||||
git ls-files "${prettierExts[@]}" | grep -v "lib/vscode" | grep -v 'helm-chart'
|
||||
)
|
||||
|
||||
doctoc --title '# FAQ' doc/FAQ.md > /dev/null
|
||||
doctoc --title '# Setup Guide' doc/guide.md > /dev/null
|
||||
doctoc --title '# Install' doc/install.md > /dev/null
|
||||
doctoc --title '# npm Install Requirements' doc/npm.md > /dev/null
|
||||
doctoc --title '# Contributing' doc/CONTRIBUTING.md > /dev/null
|
||||
doctoc --title '# iPad' doc/ipad.md > /dev/null
|
||||
doctoc --title '# FAQ' docs/FAQ.md > /dev/null
|
||||
doctoc --title '# Setup Guide' docs/guide.md > /dev/null
|
||||
doctoc --title '# Install' docs/install.md > /dev/null
|
||||
doctoc --title '# npm Install Requirements' docs/npm.md > /dev/null
|
||||
doctoc --title '# Contributing' docs/CONTRIBUTING.md > /dev/null
|
||||
doctoc --title '# Contributor Covenant Code of Conduct' docs/CODE_OF_CONDUCT.md > /dev/null
|
||||
doctoc --title '# iPad' docs/ipad.md > /dev/null
|
||||
|
||||
if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then
|
||||
echo "Files need generation or are formatted incorrectly:"
|
||||
|
||||
@@ -14,10 +14,31 @@ main() {
|
||||
# -background defaults to white but we want it transparent.
|
||||
# https://imagemagick.org/script/command-line-options.php#background
|
||||
convert -quiet -background transparent -resize 256x256 favicon.svg favicon.ico
|
||||
# We do not generate the pwa-icon from the favicon as they are slightly different
|
||||
# designs and sizes.
|
||||
# See favicon.afdesign and #2401 for details on the differences.
|
||||
convert -quiet -background transparent -resize 192x192 pwa-icon.png pwa-icon-192.png
|
||||
convert -quiet -background transparent -resize 512x512 pwa-icon.png pwa-icon-512.png
|
||||
|
||||
# We use -quiet above to avoid https://github.com/ImageMagick/ImageMagick/issues/884
|
||||
|
||||
# The following adds dark mode support for the favicon as favicon-dark-support.svg
|
||||
# There is no similar capability for pwas or .ico so we can only add support to the svg.
|
||||
favicon_dark_style="<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
* {
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
</style>"
|
||||
# See https://stackoverflow.com/a/22901380/4283659
|
||||
# This escapes all newlines so that sed will accept them.
|
||||
favicon_dark_style="$(printf "%s\n" "$favicon_dark_style" | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/\\n/g')"
|
||||
sed "$(
|
||||
cat -n << EOF
|
||||
s%<rect id="favicon"%$favicon_dark_style<rect id="favicon"%
|
||||
EOF
|
||||
)" favicon.svg > favicon-dark-support.svg
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -5,6 +5,11 @@ main() {
|
||||
cd "$(dirname "$0")/../.."
|
||||
source ./ci/lib.sh
|
||||
|
||||
# This installs the dependencies needed for testing
|
||||
cd test
|
||||
yarn
|
||||
cd ..
|
||||
|
||||
cd lib/vscode
|
||||
yarn ${CI+--frozen-lockfile}
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@ set -euo pipefail
|
||||
|
||||
main() {
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
cd test/test-plugin
|
||||
make -s out/index.js
|
||||
# We must keep jest in a sub-directory. See ../../test/package.json for more
|
||||
# information. We must also run it from the root otherwise coverage will not
|
||||
# include our source files.
|
||||
cd "$OLDPWD"
|
||||
mocha -r ts-node/register ./test/*.test.ts "$@"
|
||||
./test/node_modules/.bin/jest "$@"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -20,4 +20,4 @@ version: 1.0.3
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
appVersion: 3.8.0
|
||||
appVersion: 3.8.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# code-server
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
[code-server](https://github.com/cdr/code-server) code-server is VS Code running
|
||||
on a remote server, accessible through the browser.
|
||||
@@ -72,7 +72,7 @@ and their default values.
|
||||
| hostnameOverride | string | `""` | |
|
||||
| image.pullPolicy | string | `"Always"` | |
|
||||
| image.repository | string | `"codercom/code-server"` | |
|
||||
| image.tag | string | `"3.8.0"` | |
|
||||
| image.tag | string | `"3.8.1"` | |
|
||||
| imagePullSecrets | list | `[]` | |
|
||||
| ingress.enabled | bool | `false` | |
|
||||
| nameOverride | string | `""` | |
|
||||
|
||||
@@ -6,7 +6,7 @@ replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: codercom/code-server
|
||||
tag: '3.8.0'
|
||||
tag: '3.8.1'
|
||||
pullPolicy: Always
|
||||
|
||||
imagePullSecrets: []
|
||||
|
||||
@@ -103,7 +103,7 @@ RELEASE_PATH="${RELEASE_PATH-release}"
|
||||
# Code itself but also extensions will look specifically in this directory for
|
||||
# files (like the ripgrep binary or the oniguruma wasm).
|
||||
symlink_asar() {
|
||||
if [ ! -e node_modules.asar ]; then
|
||||
if [ ! -L node_modules.asar ]; then
|
||||
if [ "${WINDIR-}" ]; then
|
||||
# mklink takes the link name first.
|
||||
mklink /J node_modules.asar node_modules
|
||||
|
||||
79
docs/CODE_OF_CONDUCT.md
Normal file
79
docs/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,79 @@
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at opensource@coder.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -58,10 +58,13 @@ To develop inside an isolated Docker container:
|
||||
|
||||
### Updating VS Code
|
||||
|
||||
If you need to update VS Code, you can update the subtree with one line. Here's an example using the version 1.52.1
|
||||
If you need to update VS Code, you can update the subtree with one line. Here's an example using the version 1.52:
|
||||
|
||||
```shell
|
||||
git subtree pull --prefix lib/vscode vscode release/1.52 --squash --message "Update VS Code to 1.52.1"
|
||||
# Add vscode as a new remote if you haven't already and fetch
|
||||
git remote add -f vscode https://github.com/microsoft/vscode.git
|
||||
|
||||
git subtree pull --prefix lib/vscode vscode release/1.52 --squash --message "Update VS Code to 1.52"
|
||||
```
|
||||
|
||||
## Build
|
||||
@@ -15,6 +15,8 @@
|
||||
- [How do I securely access web services?](#how-do-i-securely-access-web-services)
|
||||
- [Sub-paths](#sub-paths)
|
||||
- [Sub-domains](#sub-domains)
|
||||
- [Why does the code-server proxy strip `/proxy/<port>` from the request path?](#why-does-the-code-server-proxy-strip-proxyport-from-the-request-path)
|
||||
- [Proxying to Create React App](#proxying-to-create-react-app)
|
||||
- [Multi-tenancy](#multi-tenancy)
|
||||
- [Docker in code-server container?](#docker-in-code-server-container)
|
||||
- [How can I disable telemetry?](#how-can-i-disable-telemetry)
|
||||
@@ -105,6 +107,8 @@ discussion regarding the use of the Microsoft URLs in forks:
|
||||
|
||||
https://github.com/microsoft/vscode/issues/31168#issue-244533026
|
||||
|
||||
See also [VSCodium's docs](https://github.com/VSCodium/vscodium/blob/master/DOCS.md#extensions--marketplace).
|
||||
|
||||
These variables are most valuable to our enterprise customers for whom we have a self hosted marketplace product.
|
||||
|
||||
## Where are extensions stored?
|
||||
@@ -164,13 +168,20 @@ Again, please follow [./guide.md](./guide.md) for our recommendations on setting
|
||||
|
||||
## Can I store my password hashed?
|
||||
|
||||
Yes you can! Use `hashed-password` instead of `password`. Generate the hash with:
|
||||
Yes you can! Set the value of `hashed-password` instead of `password`. Generate the hash with:
|
||||
|
||||
```
|
||||
echo "thisismypassword" | sha256sum | cut -d' ' -f1
|
||||
printf "thisismypassword" | sha256sum | cut -d' ' -f1
|
||||
```
|
||||
|
||||
Of course replace `"thisismypassword"` with your actual password.
|
||||
Of course replace `thisismypassword` with your actual password.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
auth: password
|
||||
hashed-password: 1da9133ab9dbd11d2937ec8d312e1e2569857059e73cc72df92e670928983ab5 # You got this from the command above
|
||||
```
|
||||
|
||||
## How do I securely access web services?
|
||||
|
||||
@@ -201,6 +212,45 @@ code-server --proxy-domain <domain>
|
||||
Now you can browse to `<port>.<domain>`. Note that this uses the host header so
|
||||
ensure your reverse proxy forwards that information if you are using one.
|
||||
|
||||
## Why does the code-server proxy strip `/proxy/<port>` from the request path?
|
||||
|
||||
HTTP servers should strive to use relative URLs to avoid needed to be coupled to the
|
||||
absolute path at which they are served. This means you must use trailing slashes on all
|
||||
paths with subpaths. See https://blog.cdivilly.com/2019/02/28/uri-trailing-slashes
|
||||
|
||||
This is really the "correct" way things work and why the striping of the base path is the
|
||||
default. If your application uses relative URLs and does not assume the absolute path at
|
||||
which it is being served, it will just work no matter what port you decide to serve it off
|
||||
or if you put it in behind code-server or any other proxy!
|
||||
|
||||
However many people prefer the cleaner aesthetic of no trailing slashes. This couples you
|
||||
to the base path as you cannot use relative redirects correctly anymore. See the above
|
||||
link.
|
||||
|
||||
For users who are ok with this tradeoff, use `/absproxy` instead and the path will be
|
||||
passed as is. e.g. `/absproxy/3000/my-app-path`
|
||||
|
||||
### Proxying to Create React App
|
||||
|
||||
You must use `/absproxy/<port>` with create-react-app.
|
||||
See [#2565](https://github.com/cdr/code-server/issues/2565) and
|
||||
[#2222](https://github.com/cdr/code-server/issues/2222). You will need to inform
|
||||
create-react-app of the path at which you are serving via `$PUBLIC_URL` and webpack
|
||||
via `$WDS_SOCKET_PATH`.
|
||||
|
||||
e.g.
|
||||
|
||||
```sh
|
||||
PUBLIC_URL=/absproxy/3000 \
|
||||
WDS_SOCKET_PATH=$PUBLIC_URL/sockjs-node \
|
||||
BROWSER=none yarn start
|
||||
```
|
||||
|
||||
Then visit `https://my-code-server-address.io/absproxy/3000` to see your app exposed through
|
||||
code-server!
|
||||
|
||||
Highly recommend using the subdomain approach instead to avoid this class of issue.
|
||||
|
||||
## Multi-tenancy
|
||||
|
||||
If you want to run multiple code-servers on shared infrastructure, we recommend using virtual
|
||||
|
Before Width: | Height: | Size: 974 KiB After Width: | Height: | Size: 974 KiB |
@@ -22,9 +22,9 @@ To reiterate, `code-server` lets you run VS Code on a remote server and then acc
|
||||
Further docs are at:
|
||||
|
||||
- [README](../README.md) for a general overview
|
||||
- [INSTALL](../doc/install.md) for installation
|
||||
- [INSTALL](../docs/install.md) for installation
|
||||
- [FAQ](./FAQ.md) for common questions.
|
||||
- [CONTRIBUTING](../doc/CONTRIBUTING.md) for development docs
|
||||
- [CONTRIBUTING](../docs/CONTRIBUTING.md) for development docs
|
||||
|
||||
We highly recommend reading the [FAQ](./FAQ.md) on the [Differences compared to VS Code](./FAQ.md#differences-compared-to-vs-code) before beginning.
|
||||
|
||||
@@ -180,8 +180,9 @@ Assuming you have been following the guide, edit your instance and checkmark the
|
||||
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 install -y debian-keyring debian-archive-keyring apt-transport-https
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/cfg/gpg/gpg.155B6D79CA56EA34.key' | sudo apt-key add -
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/cfg/setup/config.deb.txt?distro=debian&version=any-version' | sudo tee -a /etc/apt/sources.list.d/caddy-stable.list
|
||||
sudo apt update
|
||||
sudo apt install caddy
|
||||
```
|
||||
@@ -87,8 +87,8 @@ commands presented in the rest of this document.
|
||||
## Debian, Ubuntu
|
||||
|
||||
```bash
|
||||
curl -fOL https://github.com/cdr/code-server/releases/download/v3.8.0/code-server_3.8.0_amd64.deb
|
||||
sudo dpkg -i code-server_3.8.0_amd64.deb
|
||||
curl -fOL https://github.com/cdr/code-server/releases/download/v3.8.1/code-server_3.8.1_amd64.deb
|
||||
sudo dpkg -i code-server_3.8.1_amd64.deb
|
||||
sudo systemctl enable --now code-server@$USER
|
||||
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
||||
```
|
||||
@@ -96,8 +96,8 @@ sudo systemctl enable --now code-server@$USER
|
||||
## Fedora, CentOS, RHEL, SUSE
|
||||
|
||||
```bash
|
||||
curl -fOL https://github.com/cdr/code-server/releases/download/v3.8.0/code-server-3.8.0-amd64.rpm
|
||||
sudo rpm -i code-server-3.8.0-amd64.rpm
|
||||
curl -fOL https://github.com/cdr/code-server/releases/download/v3.8.1/code-server-3.8.1-amd64.rpm
|
||||
sudo rpm -i code-server-3.8.1-amd64.rpm
|
||||
sudo systemctl enable --now code-server@$USER
|
||||
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
||||
```
|
||||
@@ -166,10 +166,10 @@ Here is an example script for installing and using a standalone `code-server` re
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.local/lib ~/.local/bin
|
||||
curl -fL https://github.com/cdr/code-server/releases/download/v3.8.0/code-server-3.8.0-linux-amd64.tar.gz \
|
||||
curl -fL https://github.com/cdr/code-server/releases/download/v3.8.1/code-server-3.8.1-linux-amd64.tar.gz \
|
||||
| tar -C ~/.local/lib -xz
|
||||
mv ~/.local/lib/code-server-3.8.0-linux-amd64 ~/.local/lib/code-server-3.8.0
|
||||
ln -s ~/.local/lib/code-server-3.8.0/bin/code-server ~/.local/bin/code-server
|
||||
mv ~/.local/lib/code-server-3.8.1-linux-amd64 ~/.local/lib/code-server-3.8.1
|
||||
ln -s ~/.local/lib/code-server-3.8.1/bin/code-server ~/.local/bin/code-server
|
||||
PATH="~/.local/bin:$PATH"
|
||||
code-server
|
||||
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
||||
@@ -3,9 +3,11 @@
|
||||
# iPad
|
||||
|
||||
- [Known Issues](#known-issues)
|
||||
- [How to install PWA](#how-to-install-pwa)
|
||||
- [How to access code-server with a self signed certificate on iPad?](#how-to-access-code-server-with-a-self-signed-certificate-on-ipad)
|
||||
- [Servediter iPad App](#servediter-ipad-app)
|
||||
- [Raspberry Pi USB-C Network](#raspberry-pi-usb-c-network)
|
||||
- [Ctrl C Workaround](#ctrl-c-workaround)
|
||||
- [Recommendations](#recommendations)
|
||||
- [By 2022 iPad coding more desirable on Arm Macs](#by-2022-ipad-coding-more-desirable-on-arm-macs)
|
||||
|
||||
@@ -27,6 +29,34 @@
|
||||
- Alternative: Install line-jump extension and use keyboard to nav by jumping large amount of lines
|
||||
- Alternative: Just use touch scrolling
|
||||
- See [issues tagged with the iPad label](https://github.com/cdr/code-server/issues?q=is%3Aopen+is%3Aissue+label%3AiPad) for more.
|
||||
- `ctrl+c` does not stop a long-running process in the browser
|
||||
- Tracking upstream issue here: [#114009](https://github.com/microsoft/vscode/issues/114009)
|
||||
- See [workaround](#ctrl-c-workaround)
|
||||
|
||||
## How to install PWA
|
||||
|
||||
To install the code-server PWA, follow these steps:
|
||||
|
||||
1. Open code-server in Safari
|
||||
2. Click the Share icon
|
||||
3. Click "Add to Home Screen"
|
||||
|
||||
Now when you open code-server from the home screen, you will be using the PWA.
|
||||
The advantages of this are more screen real estate and access to top-level keyboard shortcuts because it's running like an app.
|
||||
An example shortcut is the `cmd+w` to close an active file in the workbench. You can add this to your `keybindings.json` by doing the following:
|
||||
|
||||
1. Open up code-serer
|
||||
2. `Command Palette > Open Keyboard Shortcuts (JSON)`
|
||||
3. Add the following to your `keybindings.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "cmd+w",
|
||||
"command": "workbench.action.closeActiveEditor"
|
||||
}
|
||||
```
|
||||
|
||||
Test out command by hitting `cmd+w` to close an active file
|
||||
|
||||
## How to access code-server with a self signed certificate on iPad?
|
||||
|
||||
@@ -91,6 +121,27 @@ Resources worthy of review:
|
||||
>
|
||||
> -- <cite>[Acker Apple](http://github.com/ackerapple/)</cite>
|
||||
|
||||
## Ctrl C Workaround
|
||||
|
||||
There is currently an issue with `ctrl+c` not stopping a running process in the integrated terminal. We have filed an issue upstream and are tracking [here](https://github.com/microsoft/vscode/issues/114009). As a temporary workaround, it works if you manually define the shortcut like so:
|
||||
|
||||
1. Open Command Palette
|
||||
2. Look for "Preferences: Open Keyboard Shortcuts (JSON)"
|
||||
3. Add this:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "ctrl+c",
|
||||
"command": "workbench.action.terminal.sendSequence",
|
||||
"args": {
|
||||
"text": "\u0003"
|
||||
},
|
||||
"when": "terminalFocus"
|
||||
}
|
||||
```
|
||||
|
||||
Source: [StackOverflow](https://stackoverflow.com/a/52735954/3015595)
|
||||
|
||||
## Recommendations
|
||||
|
||||
Once you have code-server accessible to your iPad a few things could help save you time:
|
||||
@@ -2,7 +2,7 @@
|
||||
set -eu
|
||||
|
||||
# code-server's automatic install script.
|
||||
# See https://github.com/cdr/code-server/blob/master/doc/install.md
|
||||
# See https://github.com/cdr/code-server/blob/master/docs/install.md
|
||||
|
||||
usage() {
|
||||
arg0="$0"
|
||||
@@ -67,7 +67,7 @@ Usage:
|
||||
|
||||
It will cache all downloaded assets into ~/.cache/code-server
|
||||
|
||||
More installation docs are at https://github.com/cdr/code-server/blob/master/doc/install.md
|
||||
More installation docs are at https://github.com/cdr/code-server/blob/master/docs/install.md
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -525,7 +525,7 @@ sudo_sh_c() {
|
||||
elif command_exists sudo; then
|
||||
sh_c "sudo $*"
|
||||
elif command_exists su; then
|
||||
sh_c "su -c '$*'"
|
||||
sh_c "su - -c '$*'"
|
||||
else
|
||||
echoh
|
||||
echoerr "This script needs to run the following command as root."
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function isWeb(): boolean {
|
||||
// NOTE@coder: Remove unused ts-expect-error directive which causes tsc to error.
|
||||
// @ts-expect-error
|
||||
return typeof navigator !== 'undefined' && vscode.env.uiKind === vscode.UIKind.Web;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ export const initialize = async (services: ServiceCollection): Promise<void> =>
|
||||
|
||||
if (parent) {
|
||||
// Tell the parent loading has completed.
|
||||
parent.postMessage({ event: 'loaded' }, window.location.origin);
|
||||
parent.postMessage({ event: 'loaded' }, '*');
|
||||
|
||||
// Proxy or stop proxing events as requested by the parent.
|
||||
const listeners = new Map<string, (event: Event) => void>();
|
||||
|
||||
@@ -32,7 +32,7 @@ import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
@@ -61,6 +61,7 @@ import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
const DefaultViewsContext = new RawContextKey<boolean>('defaultExtensionViews', true);
|
||||
const SearchMarketplaceExtensionsContext = new RawContextKey<boolean>('searchMarketplaceExtensions', false);
|
||||
@@ -410,6 +411,42 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
overlay.style.backgroundColor = overlayBackgroundColor;
|
||||
hide(overlay);
|
||||
|
||||
// NOTE@coder this UI element helps users understand the extension marketplace divergence
|
||||
const extensionHelperLocalStorageKey = 'coder.extension-help-message';
|
||||
|
||||
if (localStorage.getItem(extensionHelperLocalStorageKey) === null) {
|
||||
const helperHeader = append(this.root, $('.header'));
|
||||
helperHeader.id = 'codeServerMarketplaceHelper';
|
||||
helperHeader.style.height = 'auto';
|
||||
helperHeader.style.fontWeight = '600';
|
||||
helperHeader.style.padding = 'padding: 5px 16px';
|
||||
helperHeader.style.position = 'relative';
|
||||
// We call this function because it gives us access to the current theme
|
||||
// Then we can apply the link color to the links in the helper header
|
||||
registerThemingParticipant((theme) => {
|
||||
const linkColor = theme.getColor(textLinkForeground);
|
||||
helperHeader.innerHTML = `
|
||||
<div style="margin-bottom: 8px;">
|
||||
<p style="margin-bottom: 0; display: flex; align-items: center"><span class="codicon codicon-warning" style="margin-right: 2px; color: #C4A103"></span>WARNING</p>
|
||||
<p style="margin-top: 0; margin-bottom: 4px">
|
||||
These extensions are not official. Find additional open-source extensions
|
||||
<a style="color: ${linkColor}" href="https://open-vsx.org/" target="_blank">here</a>.
|
||||
See <a style="color: ${linkColor}" href="https://github.com/cdr/code-server/blob/master/doc/FAQ.md#differences-compared-to-vs-code" target="_blank">docs</a>.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
const dismiss = append(helperHeader, $('span'));
|
||||
dismiss.innerHTML = 'Dismiss';
|
||||
dismiss.style.display = 'block';
|
||||
dismiss.style.textAlign = 'right';
|
||||
dismiss.style.cursor = 'pointer';
|
||||
dismiss.onclick = () => {
|
||||
helperHeader.remove();
|
||||
localStorage.setItem(extensionHelperLocalStorageKey, 'viewed');
|
||||
};
|
||||
}
|
||||
|
||||
const header = append(this.root, $('.header'));
|
||||
const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
|
||||
const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : '';
|
||||
|
||||
44
package.json
44
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-server",
|
||||
"license": "MIT",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.1",
|
||||
"description": "Run VS Code on a remote server.",
|
||||
"homepage": "https://github.com/cdr/code-server",
|
||||
"bugs": {
|
||||
@@ -26,7 +26,8 @@
|
||||
"test": "./ci/dev/test.sh",
|
||||
"ci": "./ci/dev/ci.sh",
|
||||
"watch": "VSCODE_IPC_HOOK_CLI= NODE_OPTIONS=--max_old_space_size=32384 ts-node ./ci/dev/watch.ts",
|
||||
"icons": "./ci/dev/gen_icons.sh"
|
||||
"icons": "./ci/dev/gen_icons.sh",
|
||||
"badges": "istanbul-badges-readme"
|
||||
},
|
||||
"main": "out/node/entry.js",
|
||||
"devDependencies": {
|
||||
@@ -36,7 +37,6 @@
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"@types/js-yaml": "^3.12.3",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"@types/node": "^12.12.7",
|
||||
"@types/parcel-bundler": "^1.12.1",
|
||||
"@types/pem": "^1.9.5",
|
||||
@@ -44,10 +44,10 @@
|
||||
"@types/safe-compare": "^1.1.0",
|
||||
"@types/semver": "^7.1.0",
|
||||
"@types/split2": "^2.1.6",
|
||||
"@types/supertest": "^2.0.10",
|
||||
"@types/tar-fs": "^2.0.0",
|
||||
"@types/tar-stream": "^2.1.0",
|
||||
"@types/ws": "^7.2.6",
|
||||
"@types/wtfnode": "^0.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.7.0",
|
||||
"@typescript-eslint/parser": "^4.7.0",
|
||||
"doctoc": "^1.4.0",
|
||||
@@ -55,15 +55,15 @@
|
||||
"eslint-config-prettier": "^6.0.0",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"istanbul-badges-readme": "^1.2.0",
|
||||
"leaked-handles": "^5.2.0",
|
||||
"mocha": "^8.1.2",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"prettier": "^2.0.5",
|
||||
"stylelint": "^13.0.0",
|
||||
"stylelint-config-recommended": "^3.0.0",
|
||||
"supertest": "^6.0.1",
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "4.0.2"
|
||||
"wtfnode": "^0.8.4",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "^12.12.7",
|
||||
@@ -81,6 +81,7 @@
|
||||
"httpolyglot": "^0.1.2",
|
||||
"js-yaml": "^3.13.1",
|
||||
"limiter": "^1.1.5",
|
||||
"node-fetch": "^2.6.1",
|
||||
"pem": "^1.14.2",
|
||||
"proxy-agent": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0",
|
||||
@@ -109,5 +110,34 @@
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.ts$": "<rootDir>/test/node_modules/ts-jest"
|
||||
},
|
||||
"testEnvironment": "node",
|
||||
"testPathIgnorePatterns": [
|
||||
"node_modules",
|
||||
"lib",
|
||||
"out"
|
||||
],
|
||||
"collectCoverage": true,
|
||||
"collectCoverageFrom": [
|
||||
"<rootDir>/src/**/*.ts"
|
||||
],
|
||||
"coverageDirectory": "<rootDir>/coverage",
|
||||
"coverageReporters": [
|
||||
"json",
|
||||
"json-summary",
|
||||
"text"
|
||||
],
|
||||
"coveragePathIgnorePatterns": [
|
||||
"out"
|
||||
],
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"lines": 40
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/browser/favicon.afdesign
LFS
Normal file
BIN
src/browser/favicon.afdesign
LFS
Normal file
Binary file not shown.
7
src/browser/media/favicon-dark-support.svg
Normal file
7
src/browser/media/favicon-dark-support.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 2250 2250" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
* {
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
</style><rect id="favicon" x="0" y="0" width="2250" height="2250" style="fill:none;"/><g id="favicon1" serif:id="favicon"><path d="M1991.66,1034.72c-38.493,0 -64.144,-22.57 -64.144,-68.897l-0,-266.084c-0,-169.867 -69.982,-263.709 -250.762,-263.709l-83.976,0l-0,179.368l25.661,0c71.144,0 104.967,39.201 104.967,109.285l0,235.201c0,102.156 30.324,143.733 96.806,165.114c-66.482,20.196 -96.806,62.958 -96.806,165.114l0,174.621c0,48.7 0,96.216 -12.829,144.917c-12.829,45.141 -33.823,87.903 -62.98,124.726c-16.329,21.386 -34.991,39.202 -55.981,55.835l-0,23.755l83.971,-0c180.781,-0 250.763,-93.843 250.763,-263.709l-0,-266.084c-0,-47.516 24.485,-68.897 64.144,-68.897l47.822,-0l-0,-179.37l-46.656,-0l0,-1.186Z" style="fill-rule:nonzero;"/><path d="M1420.16,706.904l-258.923,0c-5.833,0 -10.495,-4.752 -10.495,-10.691l-0,-20.192c-0,-5.941 4.662,-10.692 10.495,-10.692l260.089,0c5.83,0 10.495,4.751 10.495,10.692l0,20.192c0,5.939 -5.833,10.691 -11.661,10.691Z" style="fill-rule:nonzero;"/><path d="M1464.48,963.474l-188.942,0c-5.833,0 -10.501,-4.754 -10.501,-10.693l0,-20.192c0,-5.938 4.668,-10.691 10.501,-10.691l188.942,-0c5.833,-0 10.495,4.753 10.495,10.691l-0,20.192c-0,4.754 -4.662,10.693 -10.495,10.693Z" style="fill-rule:nonzero;"/><path d="M1539.12,835.188l-377.885,0c-5.833,0 -10.495,-4.75 -10.495,-10.689l-0,-20.196c-0,-5.939 4.662,-10.69 10.495,-10.69l376.719,0c5.833,0 10.499,4.751 10.499,10.69l-0,20.196c-0,4.75 -3.5,10.689 -9.333,10.689Z" style="fill-rule:nonzero;"/><path d="M861.493,765.074c25.658,0 51.319,2.376 75.811,8.316l0,-48.705c0,-68.897 34.989,-109.285 104.971,-109.285l25.658,0l-0,-179.368l-83.977,0c-180.781,0 -250.758,93.842 -250.758,263.709l0,87.901c40.819,-14.252 83.977,-22.568 128.295,-22.568Z" style="fill-rule:nonzero;"/><path d="M1618.44,1411.25c-18.662,-150.861 -132.962,-276.776 -279.919,-305.285c-40.818,-8.314 -81.642,-9.504 -121.295,-2.376c-1.166,-0 -1.166,-1.189 -2.332,-1.189c-64.148,-136.605 -201.772,-226.884 -351.063,-226.884c-149.289,-0 -285.747,87.905 -351.062,224.51c-1.166,-0 -1.166,1.188 -2.332,1.188c-41.987,-4.753 -83.975,-2.379 -125.963,8.314c-144.623,35.634 -254.257,159.175 -274.085,308.847c-2.332,15.441 -3.499,30.883 -3.499,45.141c0,45.136 30.325,86.713 74.645,92.652c54.817,8.317 102.636,-34.448 101.469,-89.089c0,-8.317 0,-17.821 1.167,-26.134c9.331,-76.025 66.48,-140.168 141.123,-157.99c23.328,-5.939 46.654,-7.124 68.814,-3.559c71.146,9.502 141.124,-27.324 171.449,-91.467c22.162,-47.516 57.151,-89.094 103.804,-111.664c51.314,-24.946 109.633,-28.506 163.286,-9.499c55.979,20.192 97.966,62.954 123.627,116.409c26.824,52.27 39.653,89.093 96.805,96.221c23.325,3.559 88.639,2.374 113.132,1.185c47.82,0 95.64,16.631 129.463,51.079c22.156,23.757 38.485,53.455 45.486,86.715c10.495,53.455 -2.334,106.908 -33.825,147.296c-22.162,28.509 -52.485,49.89 -86.308,59.394c-16.329,4.754 -32.657,5.939 -48.986,5.939l-257.757,0c-51.314,0 -92.138,-41.573 -92.138,-93.842l0,-348.049c0,-14.251 -11.661,-26.13 -25.658,-26.13l-36.156,0c-71.148,1.185 -128.295,81.964 -128.295,167.488l-0,312.415c-0,92.652 73.476,167.488 164.451,167.488c0,0 404.714,-1.19 410.544,-1.19c93.304,-9.503 179.614,-58.204 237.927,-133.04c58.319,-72.46 85.142,-167.492 73.481,-264.894Z" style="fill-rule:nonzero;"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -11,7 +11,7 @@
|
||||
content="style-src 'self'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;"
|
||||
/>
|
||||
<title>{{ERROR_TITLE}} - code-server</title>
|
||||
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.svg" />
|
||||
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
|
||||
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
|
||||
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;"
|
||||
/>
|
||||
<title>code-server login</title>
|
||||
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.svg" />
|
||||
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
|
||||
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
|
||||
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" />
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}" />
|
||||
|
||||
<!-- Workbench Icon/Manifest/CSS -->
|
||||
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.svg" />
|
||||
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
|
||||
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
|
||||
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
||||
<!-- PROD_ONLY
|
||||
|
||||
@@ -112,3 +112,11 @@ export const getFirstString = (value: string | string[] | object | undefined): s
|
||||
|
||||
return typeof value === "string" ? value : undefined
|
||||
}
|
||||
|
||||
export function logError(prefix: string, err: any): void {
|
||||
if (err instanceof Error) {
|
||||
logger.error(`${prefix}: ${err.message} ${err.stack}`)
|
||||
} else {
|
||||
logger.error(`${prefix}: ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import express, { Express } from "express"
|
||||
import { promises as fs } from "fs"
|
||||
import http from "http"
|
||||
import * as httpolyglot from "httpolyglot"
|
||||
import * as util from "../common/util"
|
||||
import { DefaultedArgs } from "./cli"
|
||||
import { handleUpgrade } from "./wsRouter"
|
||||
|
||||
@@ -22,8 +23,21 @@ export const createApp = async (args: DefaultedArgs): Promise<[Express, Express,
|
||||
)
|
||||
: http.createServer(app)
|
||||
|
||||
await new Promise<http.Server>(async (resolve, reject) => {
|
||||
server.on("error", reject)
|
||||
let resolved = false
|
||||
await new Promise<void>(async (resolve2, reject) => {
|
||||
const resolve = () => {
|
||||
resolved = true
|
||||
resolve2()
|
||||
}
|
||||
server.on("error", (err) => {
|
||||
if (!resolved) {
|
||||
reject(err)
|
||||
} else {
|
||||
// Promise resolved earlier so this is an unrelated error.
|
||||
util.logError("http server error", err)
|
||||
}
|
||||
})
|
||||
|
||||
if (args.socket) {
|
||||
try {
|
||||
await fs.unlink(args.socket)
|
||||
|
||||
@@ -239,7 +239,7 @@ export const optionDescriptions = (): string[] => {
|
||||
export const parse = (
|
||||
argv: string[],
|
||||
opts?: {
|
||||
configFile: string
|
||||
configFile?: string
|
||||
},
|
||||
): Args => {
|
||||
const error = (msg: string): Error => {
|
||||
@@ -516,7 +516,19 @@ export async function readConfigFile(configPath?: string): Promise<ConfigArgs> {
|
||||
}
|
||||
|
||||
const configFile = await fs.readFile(configPath)
|
||||
const config = yaml.safeLoad(configFile.toString(), {
|
||||
return parseConfigFile(configFile.toString(), configPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* parseConfigFile parses configFile into ConfigArgs.
|
||||
* configPath is used as the filename in error messages
|
||||
*/
|
||||
export function parseConfigFile(configFile: string, configPath: string): ConfigArgs {
|
||||
if (!configFile) {
|
||||
return { _: [], config: configPath }
|
||||
}
|
||||
|
||||
const config = yaml.safeLoad(configFile, {
|
||||
filename: configPath,
|
||||
})
|
||||
if (!config || typeof config === "string") {
|
||||
|
||||
@@ -45,4 +45,13 @@ export class Heart {
|
||||
})
|
||||
}, this.heartbeatInterval)
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to clear any heartbeatTimer for shutdown.
|
||||
*/
|
||||
public dispose(): void {
|
||||
if (typeof this.heartbeatTimer !== "undefined") {
|
||||
clearTimeout(this.heartbeatTimer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ proxy.on("error", (error, _, res) => {
|
||||
})
|
||||
|
||||
// Intercept the response to rewrite absolute redirects against the base path.
|
||||
// Is disabled when the request has no base path which means /absproxy is in use.
|
||||
proxy.on("proxyRes", (res, req) => {
|
||||
if (res.headers.location && res.headers.location.startsWith("/") && (req as any).base) {
|
||||
res.headers.location = (req as any).base + res.headers.location
|
||||
|
||||
@@ -55,6 +55,9 @@ export const register = async (
|
||||
})
|
||||
})
|
||||
})
|
||||
server.on("close", () => {
|
||||
heart.dispose()
|
||||
})
|
||||
|
||||
app.disable("x-powered-by")
|
||||
wsApp.disable("x-powered-by")
|
||||
@@ -62,9 +65,6 @@ export const register = async (
|
||||
app.use(cookieParser())
|
||||
wsApp.use(cookieParser())
|
||||
|
||||
app.use(bodyParser.json())
|
||||
app.use(bodyParser.urlencoded({ extended: true }))
|
||||
|
||||
const common: express.RequestHandler = (req, _, next) => {
|
||||
// /healthz|/healthz/ needs to be excluded otherwise health checks will make
|
||||
// it look like code-server is always in use.
|
||||
@@ -103,6 +103,29 @@ export const register = async (
|
||||
app.use("/", domainProxy.router)
|
||||
wsApp.use("/", domainProxy.wsRouter.router)
|
||||
|
||||
app.all("/proxy/(:port)(/*)?", (req, res) => {
|
||||
proxy.proxy(req, res)
|
||||
})
|
||||
wsApp.get("/proxy/(:port)(/*)?", (req, res) => {
|
||||
proxy.wsProxy(req as WebsocketRequest)
|
||||
})
|
||||
// These two routes pass through the path directly.
|
||||
// So the proxied app must be aware it is running
|
||||
// under /absproxy/<someport>/
|
||||
app.all("/absproxy/(:port)(/*)?", (req, res) => {
|
||||
proxy.proxy(req, res, {
|
||||
passthroughPath: true,
|
||||
})
|
||||
})
|
||||
wsApp.get("/absproxy/(:port)(/*)?", (req, res) => {
|
||||
proxy.wsProxy(req as WebsocketRequest, {
|
||||
passthroughPath: true,
|
||||
})
|
||||
})
|
||||
|
||||
app.use(bodyParser.json())
|
||||
app.use(bodyParser.urlencoded({ extended: true }))
|
||||
|
||||
app.use("/", vscode.router)
|
||||
wsApp.use("/", vscode.wsRouter.router)
|
||||
app.use("/vscode", vscode.router)
|
||||
@@ -118,9 +141,6 @@ export const register = async (
|
||||
})
|
||||
}
|
||||
|
||||
app.use("/proxy", proxy.router)
|
||||
wsApp.use("/proxy", proxy.wsRouter.router)
|
||||
|
||||
app.use("/static", _static.router)
|
||||
app.use("/update", update.router)
|
||||
|
||||
@@ -165,7 +185,7 @@ export const register = async (
|
||||
|
||||
app.use(errorHandler)
|
||||
|
||||
const wsErrorHandler: express.ErrorRequestHandler = async (err, req) => {
|
||||
const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
|
||||
logger.error(`${err.message} ${err.stack}`)
|
||||
;(req as WebsocketRequest).ws.end()
|
||||
}
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import { Request, Router } from "express"
|
||||
import { Request, Response } from "express"
|
||||
import * as path from "path"
|
||||
import qs from "qs"
|
||||
import { HttpCode, HttpError } from "../../common/http"
|
||||
import { normalize } from "../../common/util"
|
||||
import { authenticated, ensureAuthenticated, redirect } from "../http"
|
||||
import { proxy } from "../proxy"
|
||||
import { Router as WsRouter } from "../wsRouter"
|
||||
import { proxy as _proxy } from "../proxy"
|
||||
import { WebsocketRequest } from "../wsRouter"
|
||||
|
||||
export const router = Router()
|
||||
|
||||
const getProxyTarget = (req: Request, rewrite: boolean): string => {
|
||||
if (rewrite) {
|
||||
const query = qs.stringify(req.query)
|
||||
return `http://0.0.0.0:${req.params.port}/${req.params[0] || ""}${query ? `?${query}` : ""}`
|
||||
const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
|
||||
if (passthroughPath) {
|
||||
return `http://0.0.0.0:${req.params.port}/${req.originalUrl}`
|
||||
}
|
||||
return `http://0.0.0.0:${req.params.port}/${req.originalUrl}`
|
||||
const query = qs.stringify(req.query)
|
||||
return `http://0.0.0.0:${req.params.port}/${req.params[0] || ""}${query ? `?${query}` : ""}`
|
||||
}
|
||||
|
||||
router.all("/(:port)(/*)?", (req, res) => {
|
||||
export function proxy(
|
||||
req: Request,
|
||||
res: Response,
|
||||
opts?: {
|
||||
passthroughPath?: boolean
|
||||
},
|
||||
): void {
|
||||
if (!authenticated(req)) {
|
||||
// If visiting the root (/:port only) redirect to the login page.
|
||||
if (!req.params[0] || req.params[0] === "/") {
|
||||
@@ -28,20 +33,27 @@ router.all("/(:port)(/*)?", (req, res) => {
|
||||
throw new HttpError("Unauthorized", HttpCode.Unauthorized)
|
||||
}
|
||||
|
||||
// Absolute redirects need to be based on the subpath when rewriting.
|
||||
;(req as any).base = `${req.baseUrl}/${req.params.port}`
|
||||
if (!opts?.passthroughPath) {
|
||||
// Absolute redirects need to be based on the subpath when rewriting.
|
||||
// See proxy.ts.
|
||||
;(req as any).base = req.path.split(path.sep).slice(0, 3).join(path.sep)
|
||||
}
|
||||
|
||||
proxy.web(req, res, {
|
||||
_proxy.web(req, res, {
|
||||
ignorePath: true,
|
||||
target: getProxyTarget(req, true),
|
||||
target: getProxyTarget(req, opts?.passthroughPath),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const wsRouter = WsRouter()
|
||||
|
||||
wsRouter.ws("/(:port)(/*)?", ensureAuthenticated, (req) => {
|
||||
proxy.ws(req, req.ws, req.head, {
|
||||
export function wsProxy(
|
||||
req: WebsocketRequest,
|
||||
opts?: {
|
||||
passthroughPath?: boolean
|
||||
},
|
||||
): void {
|
||||
ensureAuthenticated(req)
|
||||
_proxy.ws(req, req.ws, req.head, {
|
||||
ignorePath: true,
|
||||
target: getProxyTarget(req, true),
|
||||
target: getProxyTarget(req, opts?.passthroughPath),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ export const open = async (url: string): Promise<void> => {
|
||||
url = url.replace(/&/g, "^&")
|
||||
}
|
||||
const proc = cp.spawn(command, [...args, url], options)
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
proc.on("error", reject)
|
||||
proc.on("close", (code) => {
|
||||
return code !== 0 ? reject(new Error(`Failed to open with code ${code}`)) : resolve()
|
||||
|
||||
@@ -37,7 +37,7 @@ export class VscodeProvider {
|
||||
query: ipc.Query,
|
||||
): Promise<ipc.WorkbenchOptions> {
|
||||
const { lastVisited } = await settings.read()
|
||||
const startPath = await this.getFirstPath([
|
||||
let startPath = await this.getFirstPath([
|
||||
{ url: query.workspace, workspace: true },
|
||||
{ url: query.folder, workspace: false },
|
||||
options.args._ && options.args._.length > 0
|
||||
@@ -46,6 +46,10 @@ export class VscodeProvider {
|
||||
!options.args["ignore-last-opened"] ? lastVisited : undefined,
|
||||
])
|
||||
|
||||
if (query.ew) {
|
||||
startPath = undefined
|
||||
}
|
||||
|
||||
settings.write({
|
||||
lastVisited: startPath,
|
||||
query,
|
||||
|
||||
188
test/cli.test.ts
188
test/cli.test.ts
@@ -1,5 +1,4 @@
|
||||
import { Level, logger } from "@coder/logger"
|
||||
import * as assert from "assert"
|
||||
import * as fs from "fs-extra"
|
||||
import * as net from "net"
|
||||
import * as os from "os"
|
||||
@@ -15,6 +14,7 @@ describe("parser", () => {
|
||||
beforeEach(() => {
|
||||
delete process.env.LOG_LEVEL
|
||||
delete process.env.PASSWORD
|
||||
console.log = jest.fn()
|
||||
})
|
||||
|
||||
// The parser should not set any defaults so the caller can determine what
|
||||
@@ -32,11 +32,11 @@ describe("parser", () => {
|
||||
}
|
||||
|
||||
it("should parse nothing", () => {
|
||||
assert.deepEqual(parse([]), { _: [] })
|
||||
expect(parse([])).toStrictEqual({ _: [] })
|
||||
})
|
||||
|
||||
it("should parse all available options", () => {
|
||||
assert.deepEqual(
|
||||
expect(
|
||||
parse([
|
||||
"--bind-addr=192.169.0.1:8080",
|
||||
"--auth",
|
||||
@@ -74,35 +74,34 @@ describe("parser", () => {
|
||||
"-5",
|
||||
"--6",
|
||||
]),
|
||||
{
|
||||
_: ["1", "2", "3", "4", "-5", "--6"],
|
||||
auth: "none",
|
||||
"builtin-extensions-dir": path.resolve("foobar"),
|
||||
"cert-key": path.resolve("qux"),
|
||||
cert: {
|
||||
value: path.resolve("baz"),
|
||||
},
|
||||
"extensions-dir": path.resolve("foo"),
|
||||
"extra-builtin-extensions-dir": [path.resolve("bazzle")],
|
||||
"extra-extensions-dir": [path.resolve("nozzle")],
|
||||
help: true,
|
||||
home: "http://localhost:8080/",
|
||||
host: "0.0.0.0",
|
||||
json: true,
|
||||
log: "error",
|
||||
open: true,
|
||||
port: 8081,
|
||||
socket: path.resolve("mumble"),
|
||||
"user-data-dir": path.resolve("bar"),
|
||||
verbose: true,
|
||||
version: true,
|
||||
"bind-addr": "192.169.0.1:8080",
|
||||
).toEqual({
|
||||
_: ["1", "2", "3", "4", "-5", "--6"],
|
||||
auth: "none",
|
||||
"builtin-extensions-dir": path.resolve("foobar"),
|
||||
"cert-key": path.resolve("qux"),
|
||||
cert: {
|
||||
value: path.resolve("baz"),
|
||||
},
|
||||
)
|
||||
"extensions-dir": path.resolve("foo"),
|
||||
"extra-builtin-extensions-dir": [path.resolve("bazzle")],
|
||||
"extra-extensions-dir": [path.resolve("nozzle")],
|
||||
help: true,
|
||||
home: "http://localhost:8080/",
|
||||
host: "0.0.0.0",
|
||||
json: true,
|
||||
log: "error",
|
||||
open: true,
|
||||
port: 8081,
|
||||
socket: path.resolve("mumble"),
|
||||
"user-data-dir": path.resolve("bar"),
|
||||
verbose: true,
|
||||
version: true,
|
||||
"bind-addr": "192.169.0.1:8080",
|
||||
})
|
||||
})
|
||||
|
||||
it("should work with short options", () => {
|
||||
assert.deepEqual(parse(["-vvv", "-v"]), {
|
||||
expect(parse(["-vvv", "-v"])).toEqual({
|
||||
_: [],
|
||||
verbose: true,
|
||||
version: true,
|
||||
@@ -111,102 +110,108 @@ describe("parser", () => {
|
||||
|
||||
it("should use log level env var", async () => {
|
||||
const args = parse([])
|
||||
assert.deepEqual(args, { _: [] })
|
||||
expect(args).toEqual({ _: [] })
|
||||
|
||||
process.env.LOG_LEVEL = "debug"
|
||||
assert.deepEqual(await setDefaults(args), {
|
||||
const defaults = await setDefaults(args)
|
||||
expect(defaults).toStrictEqual({
|
||||
...defaults,
|
||||
_: [],
|
||||
log: "debug",
|
||||
verbose: false,
|
||||
})
|
||||
assert.equal(process.env.LOG_LEVEL, "debug")
|
||||
assert.equal(logger.level, Level.Debug)
|
||||
expect(process.env.LOG_LEVEL).toEqual("debug")
|
||||
expect(logger.level).toEqual(Level.Debug)
|
||||
|
||||
process.env.LOG_LEVEL = "trace"
|
||||
assert.deepEqual(await setDefaults(args), {
|
||||
...defaults,
|
||||
const updated = await setDefaults(args)
|
||||
expect(updated).toStrictEqual({
|
||||
...updated,
|
||||
_: [],
|
||||
log: "trace",
|
||||
verbose: true,
|
||||
})
|
||||
assert.equal(process.env.LOG_LEVEL, "trace")
|
||||
assert.equal(logger.level, Level.Trace)
|
||||
expect(process.env.LOG_LEVEL).toEqual("trace")
|
||||
expect(logger.level).toEqual(Level.Trace)
|
||||
})
|
||||
|
||||
it("should prefer --log to env var and --verbose to --log", async () => {
|
||||
let args = parse(["--log", "info"])
|
||||
assert.deepEqual(args, {
|
||||
expect(args).toEqual({
|
||||
_: [],
|
||||
log: "info",
|
||||
})
|
||||
|
||||
process.env.LOG_LEVEL = "debug"
|
||||
assert.deepEqual(await setDefaults(args), {
|
||||
const defaults = await setDefaults(args)
|
||||
expect(defaults).toEqual({
|
||||
...defaults,
|
||||
_: [],
|
||||
log: "info",
|
||||
verbose: false,
|
||||
})
|
||||
assert.equal(process.env.LOG_LEVEL, "info")
|
||||
assert.equal(logger.level, Level.Info)
|
||||
expect(process.env.LOG_LEVEL).toEqual("info")
|
||||
expect(logger.level).toEqual(Level.Info)
|
||||
|
||||
process.env.LOG_LEVEL = "trace"
|
||||
assert.deepEqual(await setDefaults(args), {
|
||||
const updated = await setDefaults(args)
|
||||
expect(updated).toEqual({
|
||||
...defaults,
|
||||
_: [],
|
||||
log: "info",
|
||||
verbose: false,
|
||||
})
|
||||
assert.equal(process.env.LOG_LEVEL, "info")
|
||||
assert.equal(logger.level, Level.Info)
|
||||
expect(process.env.LOG_LEVEL).toEqual("info")
|
||||
expect(logger.level).toEqual(Level.Info)
|
||||
|
||||
args = parse(["--log", "info", "--verbose"])
|
||||
assert.deepEqual(args, {
|
||||
expect(args).toEqual({
|
||||
_: [],
|
||||
log: "info",
|
||||
verbose: true,
|
||||
})
|
||||
|
||||
process.env.LOG_LEVEL = "warn"
|
||||
assert.deepEqual(await setDefaults(args), {
|
||||
const updatedAgain = await setDefaults(args)
|
||||
expect(updatedAgain).toEqual({
|
||||
...defaults,
|
||||
_: [],
|
||||
log: "trace",
|
||||
verbose: true,
|
||||
})
|
||||
assert.equal(process.env.LOG_LEVEL, "trace")
|
||||
assert.equal(logger.level, Level.Trace)
|
||||
expect(process.env.LOG_LEVEL).toEqual("trace")
|
||||
expect(logger.level).toEqual(Level.Trace)
|
||||
})
|
||||
|
||||
it("should ignore invalid log level env var", async () => {
|
||||
process.env.LOG_LEVEL = "bogus"
|
||||
assert.deepEqual(await setDefaults(parse([])), {
|
||||
_: [],
|
||||
const defaults = await setDefaults(parse([]))
|
||||
expect(defaults).toEqual({
|
||||
...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/)
|
||||
expect(() => parse(["--auth"])).toThrowError(/--auth requires a value/)
|
||||
expect(() => parse(["--auth=", "--log=debug"])).toThrowError(/--auth requires a value/)
|
||||
expect(() => parse(["--auth", "--log"])).toThrowError(/--auth requires a value/)
|
||||
expect(() => parse(["--auth", "--invalid"])).toThrowError(/--auth requires a value/)
|
||||
expect(() => parse(["--bind-addr"])).toThrowError(/--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\]/)
|
||||
expect(() => parse(["--port", "foo"])).toThrowError(/--port must be a number/)
|
||||
expect(() => parse(["--auth", "invalid"])).toThrowError(/--auth valid values: \[password, none\]/)
|
||||
expect(() => parse(["--log", "invalid"])).toThrowError(/--log valid values: \[trace, debug, info, warn, error\]/)
|
||||
})
|
||||
|
||||
it("should error if the option doesn't exist", () => {
|
||||
assert.throws(() => parse(["--foo"]), /Unknown option --foo/)
|
||||
expect(() => parse(["--foo"])).toThrowError(/Unknown option --foo/)
|
||||
})
|
||||
|
||||
it("should not error if the value is optional", () => {
|
||||
assert.deepEqual(parse(["--cert"]), {
|
||||
expect(parse(["--cert"])).toEqual({
|
||||
_: [],
|
||||
cert: {
|
||||
value: undefined,
|
||||
@@ -215,28 +220,28 @@ describe("parser", () => {
|
||||
})
|
||||
|
||||
it("should not allow option-like values", () => {
|
||||
assert.throws(() => parse(["--socket", "--socket-path-value"]), /--socket requires a value/)
|
||||
expect(() => parse(["--socket", "--socket-path-value"])).toThrowError(/--socket requires a value/)
|
||||
// If you actually had a path like this you would do this instead:
|
||||
assert.deepEqual(parse(["--socket", "./--socket-path-value"]), {
|
||||
expect(parse(["--socket", "./--socket-path-value"])).toEqual({
|
||||
_: [],
|
||||
socket: path.resolve("--socket-path-value"),
|
||||
})
|
||||
assert.throws(() => parse(["--cert", "--socket-path-value"]), /Unknown option --socket-path-value/)
|
||||
expect(() => parse(["--cert", "--socket-path-value"])).toThrowError(/Unknown option --socket-path-value/)
|
||||
})
|
||||
|
||||
it("should allow positional arguments before options", () => {
|
||||
assert.deepEqual(parse(["foo", "test", "--auth", "none"]), {
|
||||
expect(parse(["foo", "test", "--auth", "none"])).toEqual({
|
||||
_: ["foo", "test"],
|
||||
auth: "none",
|
||||
})
|
||||
})
|
||||
|
||||
it("should support repeatable flags", () => {
|
||||
assert.deepEqual(parse(["--proxy-domain", "*.coder.com"]), {
|
||||
expect(parse(["--proxy-domain", "*.coder.com"])).toEqual({
|
||||
_: [],
|
||||
"proxy-domain": ["*.coder.com"],
|
||||
})
|
||||
assert.deepEqual(parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "test.com"]), {
|
||||
expect(parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "test.com"])).toEqual({
|
||||
_: [],
|
||||
"proxy-domain": ["*.coder.com", "test.com"],
|
||||
})
|
||||
@@ -244,14 +249,15 @@ describe("parser", () => {
|
||||
|
||||
it("should enforce cert-key with cert value or otherwise generate one", async () => {
|
||||
const args = parse(["--cert"])
|
||||
assert.deepEqual(args, {
|
||||
expect(args).toEqual({
|
||||
_: [],
|
||||
cert: {
|
||||
value: undefined,
|
||||
},
|
||||
})
|
||||
assert.throws(() => parse(["--cert", "test"]), /--cert-key is missing/)
|
||||
assert.deepEqual(await setDefaults(args), {
|
||||
expect(() => parse(["--cert", "test"])).toThrowError(/--cert-key is missing/)
|
||||
const defaultArgs = await setDefaults(args)
|
||||
expect(defaultArgs).toEqual({
|
||||
_: [],
|
||||
...defaults,
|
||||
cert: {
|
||||
@@ -263,7 +269,8 @@ describe("parser", () => {
|
||||
|
||||
it("should override with --link", async () => {
|
||||
const args = parse("--cert test --cert-key test --socket test --host 0.0.0.0 --port 8888 --link test".split(" "))
|
||||
assert.deepEqual(await setDefaults(args), {
|
||||
const defaultArgs = await setDefaults(args)
|
||||
expect(defaultArgs).toEqual({
|
||||
_: [],
|
||||
...defaults,
|
||||
auth: "none",
|
||||
@@ -281,11 +288,12 @@ describe("parser", () => {
|
||||
it("should use env var password", async () => {
|
||||
process.env.PASSWORD = "test"
|
||||
const args = parse([])
|
||||
assert.deepEqual(args, {
|
||||
expect(args).toEqual({
|
||||
_: [],
|
||||
})
|
||||
|
||||
assert.deepEqual(await setDefaults(args), {
|
||||
const defaultArgs = await setDefaults(args)
|
||||
expect(defaultArgs).toEqual({
|
||||
...defaults,
|
||||
_: [],
|
||||
password: "test",
|
||||
@@ -296,11 +304,12 @@ describe("parser", () => {
|
||||
it("should use env var hashed password", async () => {
|
||||
process.env.HASHED_PASSWORD = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" // test
|
||||
const args = parse([])
|
||||
assert.deepEqual(args, {
|
||||
expect(args).toEqual({
|
||||
_: [],
|
||||
})
|
||||
|
||||
assert.deepEqual(await setDefaults(args), {
|
||||
const defaultArgs = await setDefaults(args)
|
||||
expect(defaultArgs).toEqual({
|
||||
...defaults,
|
||||
_: [],
|
||||
"hashed-password": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
|
||||
@@ -310,12 +319,13 @@ describe("parser", () => {
|
||||
|
||||
it("should filter proxy domains", async () => {
|
||||
const args = parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "coder.com", "--proxy-domain", "coder.org"])
|
||||
assert.deepEqual(args, {
|
||||
expect(args).toEqual({
|
||||
_: [],
|
||||
"proxy-domain": ["*.coder.com", "coder.com", "coder.org"],
|
||||
})
|
||||
|
||||
assert.deepEqual(await setDefaults(args), {
|
||||
const defaultArgs = await setDefaults(args)
|
||||
expect(defaultArgs).toEqual({
|
||||
...defaults,
|
||||
_: [],
|
||||
"proxy-domain": ["coder.com", "coder.org"],
|
||||
@@ -328,7 +338,7 @@ describe("cli", () => {
|
||||
const testDir = path.join(tmpdir, "tests/cli")
|
||||
const vscodeIpcPath = path.join(os.tmpdir(), "vscode-ipc")
|
||||
|
||||
before(async () => {
|
||||
beforeAll(async () => {
|
||||
await fs.remove(testDir)
|
||||
await fs.mkdirp(testDir)
|
||||
})
|
||||
@@ -341,44 +351,44 @@ describe("cli", () => {
|
||||
|
||||
it("should use existing if inside code-server", async () => {
|
||||
process.env.VSCODE_IPC_HOOK_CLI = "test"
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), "test")
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
|
||||
args.port = 8081
|
||||
args._.push("./file")
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), "test")
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
})
|
||||
|
||||
it("should use existing if --reuse-window is set", async () => {
|
||||
args["reuse-window"] = true
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), undefined)
|
||||
await expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
|
||||
await fs.writeFile(vscodeIpcPath, "test")
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), "test")
|
||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual("test")
|
||||
|
||||
args.port = 8081
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), "test")
|
||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual("test")
|
||||
})
|
||||
|
||||
it("should use existing if --new-window is set", async () => {
|
||||
args["new-window"] = true
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), undefined)
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
|
||||
await fs.writeFile(vscodeIpcPath, "test")
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), "test")
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
|
||||
args.port = 8081
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), "test")
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
})
|
||||
|
||||
it("should use existing if no unrelated flags are set, has positional, and socket is active", async () => {
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), undefined)
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
|
||||
args._.push("./file")
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), undefined)
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
|
||||
const socketPath = path.join(testDir, "socket")
|
||||
await fs.writeFile(vscodeIpcPath, socketPath)
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), undefined)
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const server = net.createServer(() => {
|
||||
@@ -389,9 +399,9 @@ describe("cli", () => {
|
||||
server.listen(socketPath)
|
||||
})
|
||||
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), socketPath)
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(socketPath)
|
||||
|
||||
args.port = 8081
|
||||
assert.strictEqual(await shouldOpenInExistingInstance(args), undefined)
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
23
test/e2e.test.ts
Normal file
23
test/e2e.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { chromium, Page, Browser } from "playwright"
|
||||
|
||||
let browser: Browser
|
||||
let page: Page
|
||||
|
||||
beforeAll(async () => {
|
||||
browser = await chromium.launch()
|
||||
})
|
||||
afterAll(async () => {
|
||||
await browser.close()
|
||||
})
|
||||
beforeEach(async () => {
|
||||
page = await browser.newPage()
|
||||
})
|
||||
afterEach(async () => {
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it("should see the login page", async () => {
|
||||
await page.goto("http://localhost:8080")
|
||||
// It should send us to the login page
|
||||
expect(await page.title()).toBe("code-server login")
|
||||
})
|
||||
72
test/httpserver.ts
Normal file
72
test/httpserver.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import * as http from "http"
|
||||
import * as nodeFetch from "node-fetch"
|
||||
import * as util from "../src/common/util"
|
||||
import { ensureAddress } from "../src/node/app"
|
||||
|
||||
// Perhaps an abstraction similar to this should be used in app.ts as well.
|
||||
export class HttpServer {
|
||||
private hs = http.createServer()
|
||||
|
||||
public constructor(hs?: http.Server) {
|
||||
// See usage in test/integration.ts
|
||||
if (hs) {
|
||||
this.hs = hs
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* listen starts the server on a random localhost port.
|
||||
* Use close to cleanup when done.
|
||||
*/
|
||||
public listen(fn: http.RequestListener): Promise<void> {
|
||||
this.hs.on("request", fn)
|
||||
|
||||
let resolved = false
|
||||
return new Promise((res, rej) => {
|
||||
this.hs.listen(0, "localhost", () => {
|
||||
res()
|
||||
resolved = true
|
||||
})
|
||||
|
||||
this.hs.on("error", (err) => {
|
||||
if (!resolved) {
|
||||
rej(err)
|
||||
} else {
|
||||
// Promise resolved earlier so this is some other error.
|
||||
util.logError("http server error", err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* close cleans up the server.
|
||||
*/
|
||||
public close(): Promise<void> {
|
||||
return new Promise((res, rej) => {
|
||||
this.hs.close((err) => {
|
||||
if (err) {
|
||||
rej(err)
|
||||
return
|
||||
}
|
||||
res()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch fetches the request path.
|
||||
* The request path must be rooted!
|
||||
*/
|
||||
public fetch(requestPath: string, opts?: nodeFetch.RequestInit): Promise<nodeFetch.Response> {
|
||||
return nodeFetch.default(`${ensureAddress(this.hs)}${requestPath}`, opts)
|
||||
}
|
||||
|
||||
public port(): number {
|
||||
const addr = this.hs.address()
|
||||
if (addr && typeof addr === "object") {
|
||||
return addr.port
|
||||
}
|
||||
throw new Error("server not listening or listening on unix socket")
|
||||
}
|
||||
}
|
||||
21
test/integration.ts
Normal file
21
test/integration.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as express from "express"
|
||||
import { createApp } from "../src/node/app"
|
||||
import { parse, setDefaults, parseConfigFile, DefaultedArgs } from "../src/node/cli"
|
||||
import { register } from "../src/node/routes"
|
||||
import * as httpserver from "./httpserver"
|
||||
|
||||
export async function setup(
|
||||
argv: string[],
|
||||
configFile?: string,
|
||||
): Promise<[express.Application, express.Application, httpserver.HttpServer, DefaultedArgs]> {
|
||||
argv = ["--bind-addr=localhost:0", ...argv]
|
||||
|
||||
const cliArgs = parse(argv)
|
||||
const configArgs = parseConfigFile(configFile || "", "test/integration.ts")
|
||||
const args = await setDefaults(cliArgs, configArgs)
|
||||
|
||||
const [app, wsApp, server] = await createApp(args)
|
||||
await register(app, wsApp, server, args)
|
||||
|
||||
return [app, wsApp, new httpserver.HttpServer(server), args]
|
||||
}
|
||||
14
test/package.json
Normal file
14
test/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"#": "We must put jest in a sub-directory otherwise VS Code somehow picks up",
|
||||
"#": "the types and generates conflicts with mocha.",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/node-fetch": "^2.5.8",
|
||||
"@types/supertest": "^2.0.10",
|
||||
"jest": "^26.6.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"playwright": "^1.8.0",
|
||||
"supertest": "^6.1.1",
|
||||
"ts-jest": "^26.4.4"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import { logger } from "@coder/logger"
|
||||
import * as express from "express"
|
||||
import * as fs from "fs"
|
||||
import { describe } from "mocha"
|
||||
import * as path from "path"
|
||||
import * as supertest from "supertest"
|
||||
import { PluginAPI } from "../src/node/plugin"
|
||||
import * as apps from "../src/node/routes/apps"
|
||||
import * as httpserver from "./httpserver"
|
||||
const fsp = fs.promises
|
||||
|
||||
/**
|
||||
@@ -13,23 +12,30 @@ const fsp = fs.promises
|
||||
*/
|
||||
describe("plugin", () => {
|
||||
let papi: PluginAPI
|
||||
let app: express.Application
|
||||
let agent: supertest.SuperAgentTest
|
||||
let s: httpserver.HttpServer
|
||||
|
||||
before(async () => {
|
||||
papi = new PluginAPI(logger, path.resolve(__dirname, "test-plugin") + ":meow")
|
||||
beforeAll(async () => {
|
||||
papi = new PluginAPI(logger, `${path.resolve(__dirname, "test-plugin")}:meow`)
|
||||
await papi.loadPlugins()
|
||||
|
||||
app = express.default()
|
||||
const app = express.default()
|
||||
papi.mount(app)
|
||||
|
||||
app.use("/api/applications", apps.router(papi))
|
||||
|
||||
agent = supertest.agent(app)
|
||||
s = new httpserver.HttpServer()
|
||||
await s.listen(app)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await s.close()
|
||||
})
|
||||
|
||||
it("/api/applications", async () => {
|
||||
await agent.get("/api/applications").expect(200, [
|
||||
const resp = await s.fetch("/api/applications")
|
||||
expect(resp.status).toBe(200)
|
||||
const body = await resp.json()
|
||||
logger.debug(`${JSON.stringify(body)}`)
|
||||
expect(body).toStrictEqual([
|
||||
{
|
||||
name: "Test App",
|
||||
version: "4.0.0",
|
||||
@@ -57,6 +63,9 @@ describe("plugin", () => {
|
||||
const indexHTML = await fsp.readFile(path.join(__dirname, "test-plugin/public/index.html"), {
|
||||
encoding: "utf8",
|
||||
})
|
||||
await agent.get("/test-plugin/test-app").expect(200, indexHTML)
|
||||
const resp = await s.fetch("/test-plugin/test-app")
|
||||
expect(resp.status).toBe(200)
|
||||
const body = await resp.text()
|
||||
expect(body).toBe(indexHTML)
|
||||
})
|
||||
})
|
||||
|
||||
105
test/proxy.test.ts
Normal file
105
test/proxy.test.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import bodyParser from "body-parser"
|
||||
import * as express from "express"
|
||||
import * as httpserver from "./httpserver"
|
||||
import * as integration from "./integration"
|
||||
|
||||
describe("proxy", () => {
|
||||
const nhooyrDevServer = new httpserver.HttpServer()
|
||||
let codeServer: httpserver.HttpServer | undefined
|
||||
let proxyPath: string
|
||||
let absProxyPath: string
|
||||
let e: express.Express
|
||||
|
||||
beforeAll(async () => {
|
||||
await nhooyrDevServer.listen((req, res) => {
|
||||
e(req, res)
|
||||
})
|
||||
proxyPath = `/proxy/${nhooyrDevServer.port()}/wsup`
|
||||
absProxyPath = proxyPath.replace("/proxy/", "/absproxy/")
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await nhooyrDevServer.close()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
e = express.default()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
if (codeServer) {
|
||||
await codeServer.close()
|
||||
codeServer = undefined
|
||||
}
|
||||
})
|
||||
|
||||
it("should rewrite the base path", async () => {
|
||||
e.get("/wsup", (req, res) => {
|
||||
res.json("asher is the best")
|
||||
})
|
||||
;[, , codeServer] = await integration.setup(["--auth=none"], "")
|
||||
const resp = await codeServer.fetch(proxyPath)
|
||||
expect(resp.status).toBe(200)
|
||||
const json = await resp.json()
|
||||
expect(json).toBe("asher is the best")
|
||||
})
|
||||
|
||||
it("should not rewrite the base path", async () => {
|
||||
e.get(absProxyPath, (req, res) => {
|
||||
res.json("joe is the best")
|
||||
})
|
||||
;[, , codeServer] = await integration.setup(["--auth=none"], "")
|
||||
const resp = await codeServer.fetch(absProxyPath)
|
||||
expect(resp.status).toBe(200)
|
||||
const json = await resp.json()
|
||||
expect(json).toBe("joe is the best")
|
||||
})
|
||||
|
||||
it("should rewrite redirects", async () => {
|
||||
e.post("/wsup", (req, res) => {
|
||||
res.redirect(307, "/finale")
|
||||
})
|
||||
e.post("/finale", (req, res) => {
|
||||
res.json("redirect success")
|
||||
})
|
||||
;[, , codeServer] = await integration.setup(["--auth=none"], "")
|
||||
const resp = await codeServer.fetch(proxyPath, {
|
||||
method: "POST",
|
||||
})
|
||||
expect(resp.status).toBe(200)
|
||||
expect(await resp.json()).toBe("redirect success")
|
||||
})
|
||||
|
||||
it("should not rewrite redirects", async () => {
|
||||
const finalePath = absProxyPath.replace("/wsup", "/finale")
|
||||
e.post(absProxyPath, (req, res) => {
|
||||
res.redirect(307, finalePath)
|
||||
})
|
||||
e.post(finalePath, (req, res) => {
|
||||
res.json("redirect success")
|
||||
})
|
||||
;[, , codeServer] = await integration.setup(["--auth=none"], "")
|
||||
const resp = await codeServer.fetch(absProxyPath, {
|
||||
method: "POST",
|
||||
})
|
||||
expect(resp.status).toBe(200)
|
||||
expect(await resp.json()).toBe("redirect success")
|
||||
})
|
||||
|
||||
it("should allow post bodies", async () => {
|
||||
e.use(bodyParser.json({ strict: false }))
|
||||
e.post("/wsup", (req, res) => {
|
||||
res.json(req.body)
|
||||
})
|
||||
;[, , codeServer] = await integration.setup(["--auth=none"], "")
|
||||
const resp = await codeServer.fetch(proxyPath, {
|
||||
method: "post",
|
||||
body: JSON.stringify("coder is the best"),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
expect(resp.status).toBe(200)
|
||||
expect(await resp.json()).toBe("coder is the best")
|
||||
})
|
||||
})
|
||||
@@ -1,22 +1,23 @@
|
||||
import { field, logger } from "@coder/logger"
|
||||
import * as assert from "assert"
|
||||
import * as fs from "fs-extra"
|
||||
import "leaked-handles"
|
||||
import * as net from "net"
|
||||
import * as path from "path"
|
||||
import * as tls from "tls"
|
||||
import { Emitter } from "../src/common/emitter"
|
||||
import { SocketProxyProvider } from "../src/node/socket"
|
||||
import { generateCertificate, tmpdir } from "../src/node/util"
|
||||
import * as wtfnode from "./wtfnode"
|
||||
|
||||
describe("SocketProxyProvider", () => {
|
||||
wtfnode.setup()
|
||||
|
||||
const provider = new SocketProxyProvider()
|
||||
|
||||
const onServerError = new Emitter<{ event: string; error: Error }>()
|
||||
const onClientError = new Emitter<{ event: string; error: Error }>()
|
||||
const onProxyError = new Emitter<{ event: string; error: Error }>()
|
||||
const fromServerToClient = new Emitter<string>()
|
||||
const fromClientToServer = new Emitter<string>()
|
||||
const fromServerToClient = new Emitter<Buffer>()
|
||||
const fromClientToServer = new Emitter<Buffer>()
|
||||
const fromClientToProxy = new Emitter<Buffer>()
|
||||
|
||||
let errors = 0
|
||||
@@ -44,7 +45,7 @@ describe("SocketProxyProvider", () => {
|
||||
})
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
beforeAll(async () => {
|
||||
const cert = await generateCertificate("localhost")
|
||||
const options = {
|
||||
cert: fs.readFileSync(cert.cert),
|
||||
@@ -56,7 +57,7 @@ describe("SocketProxyProvider", () => {
|
||||
const socketPath = await provider.findFreeSocketPath(path.join(tmpdir, "tests/tls-socket-proxy"))
|
||||
await fs.remove(socketPath)
|
||||
|
||||
return new Promise((_resolve) => {
|
||||
return new Promise<void>((_resolve) => {
|
||||
const resolved: { [key: string]: boolean } = { client: false, server: false }
|
||||
const resolve = (type: "client" | "server"): void => {
|
||||
resolved[type] = true
|
||||
@@ -93,14 +94,16 @@ describe("SocketProxyProvider", () => {
|
||||
|
||||
it("should work without a proxy", async () => {
|
||||
server.write("server->client")
|
||||
assert.equal(await getData(fromServerToClient), "server->client")
|
||||
const dataFromServerToClient = (await getData(fromServerToClient)).toString()
|
||||
expect(dataFromServerToClient).toBe("server->client")
|
||||
client.write("client->server")
|
||||
assert.equal(await getData(fromClientToServer), "client->server")
|
||||
assert.equal(errors, 0)
|
||||
const dataFromClientToServer = (await getData(fromClientToServer)).toString()
|
||||
expect(dataFromClientToServer).toBe("client->server")
|
||||
expect(errors).toEqual(0)
|
||||
})
|
||||
|
||||
it("should work with a proxy", async () => {
|
||||
assert.equal(server instanceof tls.TLSSocket, true)
|
||||
expect(server instanceof tls.TLSSocket).toBe(true)
|
||||
proxy = (await provider.createProxy(server))
|
||||
.on("data", (d) => fromClientToProxy.emit(d))
|
||||
.on("error", (error) => onProxyError.emit({ event: "error", error }))
|
||||
@@ -110,10 +113,12 @@ describe("SocketProxyProvider", () => {
|
||||
provider.stop() // We don't need more proxies.
|
||||
|
||||
proxy.write("server proxy->client")
|
||||
assert.equal(await getData(fromServerToClient), "server proxy->client")
|
||||
const dataFromServerToClient = await (await getData(fromServerToClient)).toString()
|
||||
expect(dataFromServerToClient).toBe("server proxy->client")
|
||||
client.write("client->server proxy")
|
||||
assert.equal(await getData(fromClientToProxy), "client->server proxy")
|
||||
assert.equal(errors, 0)
|
||||
const dataFromClientToProxy = await (await getData(fromClientToProxy)).toString()
|
||||
expect(dataFromClientToProxy).toBe("client->server proxy")
|
||||
expect(errors).toEqual(0)
|
||||
})
|
||||
|
||||
it("should close", async () => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as assert from "assert"
|
||||
import * as fs from "fs-extra"
|
||||
import * as http from "http"
|
||||
import * as path from "path"
|
||||
@@ -45,7 +44,7 @@ describe.skip("update", () => {
|
||||
return _provider
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
beforeAll(async () => {
|
||||
await new Promise((resolve, reject) => {
|
||||
server.on("error", reject)
|
||||
server.on("listening", resolve)
|
||||
@@ -58,7 +57,7 @@ describe.skip("update", () => {
|
||||
await fs.mkdirp(path.join(tmpdir, "tests/updates"))
|
||||
})
|
||||
|
||||
after(() => {
|
||||
afterAll(() => {
|
||||
server.close()
|
||||
})
|
||||
|
||||
@@ -73,11 +72,11 @@ describe.skip("update", () => {
|
||||
const now = Date.now()
|
||||
const update = await p.getUpdate()
|
||||
|
||||
assert.deepEqual({ update }, await settings.read())
|
||||
assert.equal(isNaN(update.checked), false)
|
||||
assert.equal(update.checked < Date.now() && update.checked >= now, true)
|
||||
assert.equal(update.version, "2.1.0")
|
||||
assert.deepEqual(spy, ["/latest"])
|
||||
await expect(settings.read()).resolves.toEqual({ update })
|
||||
expect(isNaN(update.checked)).toEqual(false)
|
||||
expect(update.checked < Date.now() && update.checked >= now).toEqual(true)
|
||||
expect(update.version).toBe("2.1.0")
|
||||
expect(spy).toEqual(["/latest"])
|
||||
})
|
||||
|
||||
it("should keep existing information", async () => {
|
||||
@@ -87,11 +86,11 @@ describe.skip("update", () => {
|
||||
const now = Date.now()
|
||||
const update = await p.getUpdate()
|
||||
|
||||
assert.deepEqual({ update }, await settings.read())
|
||||
assert.equal(isNaN(update.checked), false)
|
||||
assert.equal(update.checked < now, true)
|
||||
assert.equal(update.version, "2.1.0")
|
||||
assert.deepEqual(spy, [])
|
||||
await expect(settings.read()).resolves.toEqual({ update })
|
||||
expect(isNaN(update.checked)).toBe(false)
|
||||
expect(update.checked < now).toBe(true)
|
||||
expect(update.version).toBe("2.1.0")
|
||||
expect(spy).toEqual([])
|
||||
})
|
||||
|
||||
it("should force getting the latest", async () => {
|
||||
@@ -101,29 +100,29 @@ describe.skip("update", () => {
|
||||
const now = Date.now()
|
||||
const update = await p.getUpdate(true)
|
||||
|
||||
assert.deepEqual({ update }, await settings.read())
|
||||
assert.equal(isNaN(update.checked), false)
|
||||
assert.equal(update.checked < Date.now() && update.checked >= now, true)
|
||||
assert.equal(update.version, "4.1.1")
|
||||
assert.deepEqual(spy, ["/latest"])
|
||||
await expect(settings.read()).resolves.toEqual({ update })
|
||||
expect(isNaN(update.checked)).toBe(false)
|
||||
expect(update.checked < Date.now() && update.checked >= now).toBe(true)
|
||||
expect(update.version).toBe("4.1.1")
|
||||
expect(spy).toBe(["/latest"])
|
||||
})
|
||||
|
||||
it("should get latest after interval passes", async () => {
|
||||
const p = provider()
|
||||
await p.getUpdate()
|
||||
assert.deepEqual(spy, [])
|
||||
expect(spy).toEqual([])
|
||||
|
||||
let checked = Date.now() - 1000 * 60 * 60 * 23
|
||||
await settings.write({ update: { checked, version } })
|
||||
await p.getUpdate()
|
||||
assert.deepEqual(spy, [])
|
||||
expect(spy).toEqual([])
|
||||
|
||||
checked = Date.now() - 1000 * 60 * 60 * 25
|
||||
await settings.write({ update: { checked, version } })
|
||||
|
||||
const update = await p.getUpdate()
|
||||
assert.notEqual(update.checked, checked)
|
||||
assert.deepEqual(spy, ["/latest"])
|
||||
expect(update.checked).not.toBe(checked)
|
||||
expect(spy).toBe(["/latest"])
|
||||
})
|
||||
|
||||
it("should check if it's the current version", async () => {
|
||||
@@ -131,23 +130,24 @@ describe.skip("update", () => {
|
||||
|
||||
const p = provider()
|
||||
let update = await p.getUpdate(true)
|
||||
assert.equal(p.isLatestVersion(update), false)
|
||||
expect(p.isLatestVersion(update)).toBe(false)
|
||||
|
||||
version = "0.0.0"
|
||||
update = await p.getUpdate(true)
|
||||
assert.equal(p.isLatestVersion(update), true)
|
||||
expect(p.isLatestVersion(update)).toBe(true)
|
||||
|
||||
// Old version format; make sure it doesn't report as being later.
|
||||
version = "999999.9999-invalid999.99.9"
|
||||
update = await p.getUpdate(true)
|
||||
assert.equal(p.isLatestVersion(update), true)
|
||||
expect(p.isLatestVersion(update)).toBe(true)
|
||||
})
|
||||
|
||||
it("should not reject if unable to fetch", async () => {
|
||||
expect.assertions(2)
|
||||
let provider = new UpdateProvider("invalid", settings)
|
||||
await assert.doesNotReject(() => provider.getUpdate(true))
|
||||
await expect(() => provider.getUpdate(true)).resolves.toBe(undefined)
|
||||
|
||||
provider = new UpdateProvider("http://probably.invalid.dev.localhost/latest", settings)
|
||||
await assert.doesNotReject(() => provider.getUpdate(true))
|
||||
await expect(() => provider.getUpdate(true)).resolves.toBe(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import * as assert from "assert"
|
||||
import { normalize } from "../src/common/util"
|
||||
|
||||
describe("util", () => {
|
||||
describe("normalize", () => {
|
||||
it("should remove multiple slashes", () => {
|
||||
assert.equal(normalize("//foo//bar//baz///mumble"), "/foo/bar/baz/mumble")
|
||||
expect(normalize("//foo//bar//baz///mumble")).toBe("/foo/bar/baz/mumble")
|
||||
})
|
||||
|
||||
it("should remove trailing slashes", () => {
|
||||
assert.equal(normalize("qux///"), "qux")
|
||||
expect(normalize("qux///")).toBe("qux")
|
||||
})
|
||||
|
||||
it("should preserve trailing slash if it exists", () => {
|
||||
assert.equal(normalize("qux///", true), "qux/")
|
||||
assert.equal(normalize("qux", true), "qux")
|
||||
expect(normalize("qux///", true)).toBe("qux/")
|
||||
expect(normalize("qux", true)).toBe("qux")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
19
test/wtfnode.ts
Normal file
19
test/wtfnode.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as wtfnode from "wtfnode"
|
||||
|
||||
let active = false
|
||||
|
||||
export function setup(): void {
|
||||
if (active) {
|
||||
return
|
||||
}
|
||||
active = true
|
||||
|
||||
const interval = 5000
|
||||
const wtfnodeDump = () => {
|
||||
wtfnode.dump()
|
||||
const t = setTimeout(wtfnodeDump, interval)
|
||||
t.unref()
|
||||
}
|
||||
const t = setTimeout(wtfnodeDump, interval)
|
||||
t.unref()
|
||||
}
|
||||
3870
test/yarn.lock
Normal file
3870
test/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,9 +15,9 @@
|
||||
"sourceMap": true,
|
||||
"tsBuildInfoFile": "./.cache/tsbuildinfo",
|
||||
"incremental": true,
|
||||
"rootDir": "./src",
|
||||
"typeRoots": ["./node_modules/@types", "./typings"],
|
||||
"typeRoots": ["./node_modules/@types", "./typings", "./test/node_modules/@types"],
|
||||
"downlevelIteration": true
|
||||
},
|
||||
"include": ["./src/**/*.ts"]
|
||||
"include": ["./src/**/*.ts"],
|
||||
"exclude": ["/test", "/lib", "/ci", "/doc"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user