Compare commits

...

82 Commits

Author SHA1 Message Date
Anmol Sethi
41ad0c0c4c release-github-draft.sh: Remove incorrect assets reference
I think at some point this script created the release and attached
assets but that's not the case anymore.

For some reason this would error with undefined variable reference for
joe but bash doesn't complain for me or Asher.

Not sure what the difference is.
2021-02-05 15:11:16 -07:00
Joe Previte
2a127f168c docs: update code coverage badge 2021-02-05 14:13:26 -07:00
Joe Previte
07da291d72 chore: update v to 3.8.1 in values.yaml 2021-02-05 14:11:34 -07:00
Joe Previte
55c916a987 docs: update release doc with rg instructions 2021-02-05 14:10:27 -07:00
Joe Previte
05d8b61a32 chore: update to 3.8.1 in Chart.yaml 2021-02-05 14:09:16 -07:00
Joe Previte
244775dab5 docs(helm chart readme): update to 3.8.1 2021-02-05 14:08:35 -07:00
Joe Previte
25bf871e16 docs(install.md): update to 3.8.1 2021-02-05 14:07:27 -07:00
Joe Previte
2a2dade309 feat: update version in package.json to 3.8.1 2021-02-05 14:07:00 -07:00
Anmol Sethi
cf30b536ef Merge pull request #2674 from nhooyr/absproxy
Add /absproxy to remove --proxy-path-passthrough
2021-02-05 11:45:12 -05:00
Anmol Sethi
05a0f213a7 Update proxy path passthrough documentation
Includes updated create-react-app docs.

Closes #2565
2021-02-05 11:44:38 -05:00
Anmol Sethi
c08e3bb06d Add /absproxy to remove --proxy-path-passthrough
See https://github.com/cdr/code-server/issues/2222#issuecomment-765235938

Makes way more sense.
2021-02-05 11:44:34 -05:00
Joe Previte
4bace1ae4a Merge pull request #2669 from cdr/rename-docs-dir
refactor(docs): rename doc to docs
2021-02-04 10:17:34 -07:00
Joe Previte
f7b0cea42c Merge pull request #2670 from cdr/add-code-of-conduct
docs: add CODE_OF_CONDUCT.md
2021-02-03 12:34:24 -07:00
Joe Previte
43aa0401e0 Update docs/CODE_OF_CONDUCT.md 2021-02-03 11:08:06 -07:00
Joe Previte
74dc5a881f refactor: update email address 2021-02-03 11:06:19 -07:00
Joe Previte
d7f67b80df chore: add CODE_OF_CONDUCT to fmt script 2021-02-03 09:52:59 -07:00
Joe Previte
a1a0aec472 Create CODE_OF_CONDUCT.md 2021-02-03 09:51:39 -07:00
Joe Previte
4756257207 refactor: rename doc to docs 2021-02-03 09:46:35 -07:00
Joe Previte
4c6ad046b0 Merge pull request #2643 from cdr/add-playwright
feat(testing): add playwright
2021-02-02 11:02:06 -07:00
Joe Previte
6685a3e364 feat: update workflow 2021-02-01 15:11:45 -07:00
Joe Previte
66fe663e33 feat: add playwright 2021-02-01 15:11:28 -07:00
Anmol Sethi
966e9cc238 Merge pull request #2609 from cdr/proxy-res-d629
Fix body proxying, redirect proxying and add tests
2021-02-01 11:39:44 -05:00
Anmol Sethi
a60f61f9b3 proxy.test.ts: Add POST body test and redirection tests
Closes #2377
2021-02-01 11:16:52 -05:00
Anmol Sethi
58d72d53a1 routes/index.ts: register proxy routes before body-parser
Any json or urlencoded request bodies were being consumed by body-parser
before they could be proxied. That's why requests without Content-Type
were proxied correctly as body-parser would not consume their body.

This allows the http-proxy package to passthrough the request body correctly
in all instances.

Closes #2377
2021-02-01 11:08:40 -05:00
Anmol Sethi
f5cf3fd331 proxy.ts: Do not always rewrite redirects against the base path
This breaks --proxy-path-passthrough

However, we still need this when that code is disabled as many apps will
issue absolute redirects and expect the proxy to rewrite as appropriate.

e.g. Go's http.Redirect will rewrite relative redirects as absolute!
See https://golang.org/pkg/net/http/#Redirect
2021-02-01 11:08:40 -05:00
Anmol Sethi
d7f06975a6 test: Switch from leaked-handles to wtfnode (#2604)
See my comments at
https://github.com/cdr/code-server/pull/2563#issuecomment-763394741
2021-02-01 11:06:49 -05:00
Anmol Sethi
5446e0ad43 doc/FAQ.md: Link to VSCodium's extension marketplace docs as well (#2603) 2021-02-01 09:52:16 +00:00
Anmol Sethi
22ebfdc3af doc/guide.md: Update caddy install instructions (#2601)
Caddy has new installation instructions for Debian.

Closes #2599
2021-02-01 09:51:48 +00:00
Joe Previte
aab973a795 Merge pull request #2640 from cdr/issue-1343-control-c
doc/ipad.md: add ctrl c workaround
2021-01-28 16:33:03 -07:00
Joe Previte
a4a0048b90 Merge pull request #2639 from cdr/ipad-docs-install-pwa
doc/ipad.md: add install pwa
2021-01-28 16:32:46 -07:00
Dean Sheather
1fcb0c3ddd Merge pull request #2641 from cdr/send-ready-to-all-origins
Send 'loaded' event to all parent origins
2021-01-27 05:52:13 +10:00
Dean Sheather
42dcfc94ab Send 'loaded' event to all parent origins 2021-01-27 05:06:04 +10:00
Joe Previte
db359c40c7 docs: add install pwa to ipad 2021-01-26 11:18:41 -07:00
Joe Previte
8f0066b4a8 docs: add ctrl c workaround for ipad 2021-01-26 10:44:27 -07:00
Joe Previte
fa548e95e1 Merge pull request #2564 from cdr/issue-2550-migrate-mocha-jest
refactor(tests): migrate from mocha to jest
2021-01-25 17:12:39 -07:00
Joe Previte
102f51ce1f fix: surpress console log in cli test 2021-01-25 16:34:43 -07:00
Joe Previte
14c5aecd45 chore: update ts config and jest config 2021-01-25 16:34:32 -07:00
Joe Previte
3044224729 feat: add support for code coverage shield 2021-01-25 16:21:07 -07:00
Joe Previte
05beccf671 refactor: move jest around and add code coverage 2021-01-22 14:18:45 -07:00
Joe Previte
883dd13850 refactor: move jest and add package.json to /test 2021-01-21 14:06:49 -07:00
Joe Previte
646ee3ad7f refactor: correct type signature in app.ts 2021-01-21 10:11:56 -07:00
Joe Previte
850c7e1a91 fix: add void for resolve in socket test 2021-01-21 10:11:10 -07:00
Joe Previte
6bf51caa17 fix(app.ts): resolve with server 2021-01-21 10:11:10 -07:00
Joe Previte
f13ba9401b fix(TS error): add void to promise in util 2021-01-21 10:11:10 -07:00
Joe Previte
75717749b2 refactor: upgrade TS to 4.1.3 2021-01-21 10:11:09 -07:00
Joe Previte
0a07d67c8d fix: prevent mocha/jest types conlict
Modify the tsconfig.json in lib/vscode/src/build.

This adds the flag skipLibCheck: true to tell TypeScript
to not type-check the declaration files at build time.

We need to add this because otherwise it checks the declaration
files and reports an error of duplicate type definitions
because we use Jest for our tests and they use Mocha and they
both use the global namespace "test" in their .d.ts files.
2021-01-21 10:11:09 -07:00
Joe Previte
bea8bb0519 refactor: remove mocha 2021-01-21 10:10:33 -07:00
Joe Previte
de7d0394ae refactor: tests from mocha to jest 2021-01-21 10:10:32 -07:00
Joe Previte
cef7d42652 feat: setup jest 2021-01-21 10:10:32 -07:00
Torbjørn Viem Ness
c52198f30d install.sh: Fix usage of su (#2529)
See also https://github.com/cdr/code-server/pull/2529#issuecomment-763829674
2021-01-20 13:03:13 -05:00
Anmol Sethi
28e98c0ee0 Merge pull request #2563 from cdr/proxy-path-passthrough-0bb9
pathProxy.ts: Implement --proxy-path-passthrough
2021-01-20 02:44:29 -05:00
Anmol Sethi
c32d8b155f heart.ts: Fix leak when server closes
This had me very confused for quite a while until I did a binary search
inspection on route/index.ts. Only with the heart.beat line commented
out did my tests pass without leaking.

They weren't leaking fds but just this heartbeat timer and node of
course prints just fds that are active when it detects some sort of leak
I guess and that made the whole thing very confusing. These fds are not
leaked and will close when node's event loop detects there are no more
callbacks to run.

no of handles 3

tcp stream {
  fd: 20,
  readable: false,
  writable: true,
  address: {},
  serverAddr: null
}

tcp stream {
  fd: 22,
  readable: false,
  writable: true,
  address: {},
  serverAddr: null
}

tcp stream {
  fd: 23,
  readable: true,
  writable: false,
  address: {},
  serverAddr: null
}

It kept printing the above text again and again for 60s and then the
test binary times out I think. I'm not sure if it was node printing the
stuff above or if it was a mocha thing. But it was really confusing...

cc @code-asher for thoughts on what was going on.

edit: It was the leaked-handles import in socket.test.ts!!!
Not sure if we should keep it, this was really confusing and misleading.
2021-01-20 02:06:44 -05:00
Anmol Sethi
5c06646f58 Formatting and linting fixes 2021-01-20 02:06:44 -05:00
Anmol Sethi
60233d0974 test/proxy.test.ts: Implement 2021-01-20 02:06:44 -05:00
Anmol Sethi
240c8e266e test: Implement integration.ts for near full stack integration testing 2021-01-20 02:06:44 -05:00
Anmol Sethi
64e915de4a test: Rename testutil.ts to httpserver.ts 2021-01-20 02:06:44 -05:00
Anmol Sethi
d3074278ca app.ts: Fix createApp to log all http server errors
cc @code-asher
2021-01-20 02:06:43 -05:00
Anmol Sethi
8acb2aec11 plugin.test.ts: Switch to testutil.HttpServer 2021-01-20 02:06:43 -05:00
Anmol Sethi
ea1949e440 test: Add testutil.HttpServer
The goal is to remove supertest as it does not support typescript well
and there's really no good reason for the dependency. Also no websocket
testing support.
2021-01-20 02:06:43 -05:00
Anmol Sethi
9efcf7f3ce FAQ.md: Document wds problem with create-react-app and pathProxy.ts 2021-01-20 02:06:43 -05:00
Anmol Sethi
ba4a24809c routes/index.ts: Correctly register wsErrorHandler
express requires all 4 arguments to be declared for a error handler.
It's very unfortunate that our types do not handle this.
2021-01-20 02:06:43 -05:00
Anmol Sethi
497b01bffe FAQ.md: Document --proxy-path-passthrough
And the concerns surrounding it.

Closes #2485
2021-01-20 02:06:43 -05:00
Anmol Sethi
f169e3ac66 pathProxy.ts: Implement --proxy-path-passthrough
Closes #2222
2021-01-20 02:06:43 -05:00
Anmol Sethi
c17f3ffc79 Merge pull request #2596 from cdr/rdbeach
vscode.ts: Fix close current folder
2021-01-20 01:59:12 -05:00
Anmol Sethi
d234ddc1e1 vscode.ts: Fix close current folder
Fixes VscodeProvider to correctly obey the ew parameter.

Based on changes by @rdbeach. See the previous commit.
2021-01-18 11:29:18 -05:00
Robert Beach
28c7340608 Fix Close Folder/Workspace (#2532)
When you choose to close the current folder, it doesn't close properly
because the lastVisiited setting is still use. This fixes that.
2021-01-18 11:28:29 -05:00
Joe Previte
3394ece107 Merge pull request #2501 from cdr/issue-2132-help-info-extension-search-view
feat(extensions): add helper header above extensions search
2021-01-14 16:30:28 -07:00
Joe Previte
500ba92466 fix: style links with correct colors 2021-01-14 22:40:33 +00:00
Joe Previte
d9508946b5 feat: add helper header above extensions search
Add a short message above the search box on the Extensions panel. This
helps explain the extension divergence to the user.

If they click dismiss, it stores an item in localStorage to prevent the
message from showing up on subsequent loads.

Co-authored-by: Asher <ash@coder.com>
2021-01-14 22:40:19 +00:00
SPGoding
eae285cf93 Improve hashed-password FAQ (#2533) 2021-01-14 13:00:52 -06:00
Asher
39faceeee4 Merge pull request #2539 from cdr/callback-html 2021-01-11 16:19:08 -06:00
Anmol Sethi
07bc3d9774 Merge pull request #2551 from cdr/dark-mode-favicon-b1d7
favicon: Add dark mode support
2021-01-11 12:55:20 -05:00
Anmol Sethi
f15580b28a favicon: Add dark mode support
Closes #2538

Works as expected on latest Firefox and Chromium.
2021-01-11 12:54:25 -05:00
Anmol Sethi
fa2aed6d46 gen_icons.sh: Document pwa-icon vs favicon having different design 2021-01-09 01:45:08 -05:00
Anmol Sethi
693fdbefb4 browser: Add favicon.afdesign
It requires git-lfs to pull down if you want to adjust the favicon and
also the affinity designer software available only on Windows and Mac.

Might be a good idea to switch to Figma at some point and commit a
.fig file.
2021-01-08 23:03:34 -05:00
Asher
cb11e1f750 Fix typings rsync 2021-01-08 10:37:47 -06:00
Sean Smith
9e4206aa41 Add typings to release bundle (#2544) 2021-01-06 14:25:58 -06:00
Joe Previte
5164f925ee Merge pull request #2505 from cdr/docs-update-vscode
docs: add details to updating vscode section
2021-01-05 16:27:22 -07:00
Asher
05530db20e Fix symlink_asar failing if link is broken
This can happen if you `yarn release` without keeping node modules.
2021-01-05 15:28:42 -06:00
Asher
aa05993cf0 Bundle callback.html into final build 2021-01-05 15:26:11 -06:00
Joe Previte
f599e1d72e docs: add details to updating vscode section 2020-12-22 16:39:15 -07:00
Anmol Sethi
caee285240 Merge pull request #2489 from cdr/v3.8.0
v3.8.0
2020-12-18 15:52:53 -05:00
63 changed files with 4865 additions and 762 deletions

View File

@@ -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
View File

@@ -0,0 +1 @@
*.afdesign filter=lfs diff=lfs merge=lfs -text

View File

@@ -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.

View File

@@ -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!
-->

View File

@@ -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
View File

@@ -14,3 +14,5 @@ node-*
/plugins
/lib/coder-cloud-agent
.home
coverage
**/.DS_Store

View File

@@ -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."
}
]
}

View File

@@ -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)"
}
]
}

View File

@@ -1,8 +1,10 @@
# code-server &middot; [!["GitHub Discussions"](https://img.shields.io/badge/%20GitHub-%20Discussions-gray.svg?longCache=true&logo=github&colorB=purple)](https://github.com/cdr/code-server/discussions) [!["Join us on Slack"](https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen)](https://cdr.co/join-community) [![Twitter Follow](https://img.shields.io/twitter/follow/CoderHQ?label=%40CoderHQ&style=social)](https://twitter.com/coderhq)
![Lines](https://img.shields.io/badge/Coverage-40.7%25-green.svg)
Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and access it in the browser.
![Screenshot](./doc/assets/screenshot.png)
![Screenshot](./docs/assets/screenshot.png)
## 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

View File

@@ -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:
```
![Lines](https://img.shields.io/badge/Coverage-46.71%25-red.svg)
```
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`.

View File

@@ -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" <(

View File

@@ -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
}

View File

@@ -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

View File

@@ -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:"

View File

@@ -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 "$@"

View File

@@ -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}

View File

@@ -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 "$@"

View File

@@ -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

View File

@@ -1,6 +1,6 @@
# code-server
![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.8.0](https://img.shields.io/badge/AppVersion-3.8.0-informational?style=flat-square)
![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.8.1](https://img.shields.io/badge/AppVersion-3.8.1-informational?style=flat-square)
[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 | `""` | |

View File

@@ -6,7 +6,7 @@ replicaCount: 1
image:
repository: codercom/code-server
tag: '3.8.0'
tag: '3.8.1'
pullPolicy: Always
imagePullSecrets: []

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View File

Before

Width:  |  Height:  |  Size: 974 KiB

After

Width:  |  Height:  |  Size: 974 KiB

View File

@@ -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
```

View File

@@ -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

View File

@@ -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:

View File

@@ -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."

View File

@@ -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;
}

View File

@@ -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>();

View File

@@ -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'] : '';

View File

@@ -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

Binary file not shown.

View 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

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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

View File

@@ -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}`)
}
}

View File

@@ -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)

View File

@@ -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") {

View File

@@ -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)
}
}
}

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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),
})
})
}

View File

@@ -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()

View File

@@ -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,

View File

@@ -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
View 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
View 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
View 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
View 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"
}
}

View File

@@ -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
View 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")
})
})

View File

@@ -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 () => {

View File

@@ -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)
})
})

View File

@@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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"]
}

571
yarn.lock

File diff suppressed because it is too large Load Diff