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: env:
browser: true browser: true
es6: true # Map, etc. es6: true # Map, etc.
mocha: true jest: true
node: true node: true
parserOptions: 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. and include any logging information relevant to the issue.
Please search for existing issues before filing. Please search for existing issues before filing.

View File

@@ -9,7 +9,7 @@ assignees: ""
<!-- <!--
Details on the code-server extension marketplace are at 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! Please fill in the issue template!
--> -->

View File

@@ -22,13 +22,24 @@ jobs:
args: ./ci/steps/lint.sh args: ./ci/steps/lint.sh
test: test:
needs: linux-amd64
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Run ./ci/steps/test.sh - name: Download release packages
uses: ./ci/images/debian10 uses: actions/download-artifact@v2
with: 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: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest

2
.gitignore vendored
View File

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

View File

@@ -50,7 +50,7 @@
{ {
"file": "src/node/heart.ts", "file": "src/node/heart.ts",
"line": 7, "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", "file": "src/node/socket.ts",
@@ -80,12 +80,12 @@
{ {
"file": "src/node/routes/domainProxy.ts", "file": "src/node/routes/domainProxy.ts",
"line": 18, "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", "file": "src/node/routes/pathProxy.ts",
"line": 19, "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", "file": "src/node/proxy.ts",
@@ -95,7 +95,7 @@
{ {
"file": "src/node/routes/health.ts", "file": "src/node/routes/health.ts",
"line": 5, "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", "file": "src/node/routes/login.ts",
@@ -145,7 +145,7 @@
{ {
"directory": "lib/vscode", "directory": "lib/vscode",
"line": 1, "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", "file": "src/node/app.ts",
"line": 62, "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) # 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. 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 ## 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: 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) 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: 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. 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 ☁️ ### Cloud Program ☁️
@@ -49,11 +51,11 @@ Proxying code-server to Coder Cloud, you can access your IDE at https://valmar-j
## FAQ ## FAQ
See [./doc/FAQ.md](./doc/FAQ.md). See [./docs/FAQ.md](./docs/FAQ.md).
## Want to help? ## Want to help?
See [CONTRIBUTING](./doc/CONTRIBUTING.md) for details. See [CONTRIBUTING](./docs/CONTRIBUTING.md) for details.
## Hiring ## 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 the version of code-server and make a PR.
1. Update in `package.json` 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) 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`. - 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 - Run `rg -g '!yarn.lock' -g '!*.svg' '3\.7\.5'` to ensure all values have been
changed. Replace the numbers as needed. 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. 2. GitHub actions will generate the `npm-package`, `release-packages` and `release-images` artifacts.
1. You do not have to wait for these. 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 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. 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. - 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 ## dev
This directory contains scripts used for the development of code-server. This directory contains scripts used for the development of code-server.
- [./ci/dev/image](./dev/image) - [./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`) - [./ci/dev/fmt.sh](./dev/fmt.sh) (`yarn fmt`)
- Runs formatters. - Runs formatters.
- [./ci/dev/lint.sh](./dev/lint.sh) (`yarn lint`) - [./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`. - Runs `yarn fmt`, `yarn lint` and `yarn test`.
- [./ci/dev/watch.ts](./dev/watch.ts) (`yarn watch`) - [./ci/dev/watch.ts](./dev/watch.ts) (`yarn watch`)
- Starts a process to build and launch code-server and restart on any code changes. - 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`) - [./ci/dev/gen_icons.sh](./ci/dev/gen_icons.sh) (`yarn icons`)
- Generates the various icons from a single `.svg` favicon in - Generates the various icons from a single `.svg` favicon in
`src/browser/media/favicon.svg`. `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/pages/*.html "$RELEASE_PATH/src/browser/pages"
rsync src/browser/robots.txt "$RELEASE_PATH/src/browser" 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 # Adds the commit to package.json
jq --slurp '.[0] * .[1]' package.json <( jq --slurp '.[0] * .[1]' package.json <(
cat << EOF cat << EOF
@@ -79,8 +83,9 @@ bundle_vscode() {
rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions" rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions"
rsync "$VSCODE_SRC_PATH/extensions/postinstall.js" "$VSCODE_OUT_PATH/extensions" 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/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 # Adds the commit and date to product.json
jq --slurp '.[0] * .[1]' "$VSCODE_SRC_PATH/product.json" <( jq --slurp '.[0] * .[1]' "$VSCODE_SRC_PATH/product.json" <(

View File

@@ -33,7 +33,7 @@ main() {
if ! vscode_yarn; then if ! vscode_yarn; then
echo "You may not have the required dependencies to build the native modules." 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 exit 1
fi fi
} }

View File

@@ -10,7 +10,7 @@ main() {
hub release create \ hub release create \
--file - \ --file - \
-t "$(git rev-parse HEAD)" \ -t "$(git rev-parse HEAD)" \
--draft "${assets[@]}" "v$VERSION" << EOF --draft "v$VERSION" << EOF
v$VERSION v$VERSION
VS Code v$(vscode_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. installations.
## New Features ## New Features
- ⭐ Summarize new features here with references to issues - ⭐ Summarize new features here with references to issues
## Bug Fixes ## Bug Fixes

View File

@@ -23,12 +23,13 @@ main() {
git ls-files "${prettierExts[@]}" | grep -v "lib/vscode" | grep -v 'helm-chart' git ls-files "${prettierExts[@]}" | grep -v "lib/vscode" | grep -v 'helm-chart'
) )
doctoc --title '# FAQ' doc/FAQ.md > /dev/null doctoc --title '# FAQ' docs/FAQ.md > /dev/null
doctoc --title '# Setup Guide' doc/guide.md > /dev/null doctoc --title '# Setup Guide' docs/guide.md > /dev/null
doctoc --title '# Install' doc/install.md > /dev/null doctoc --title '# Install' docs/install.md > /dev/null
doctoc --title '# npm Install Requirements' doc/npm.md > /dev/null doctoc --title '# npm Install Requirements' docs/npm.md > /dev/null
doctoc --title '# Contributing' doc/CONTRIBUTING.md > /dev/null doctoc --title '# Contributing' docs/CONTRIBUTING.md > /dev/null
doctoc --title '# iPad' doc/ipad.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 if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then
echo "Files need generation or are formatted incorrectly:" echo "Files need generation or are formatted incorrectly:"

View File

@@ -14,10 +14,31 @@ main() {
# -background defaults to white but we want it transparent. # -background defaults to white but we want it transparent.
# https://imagemagick.org/script/command-line-options.php#background # https://imagemagick.org/script/command-line-options.php#background
convert -quiet -background transparent -resize 256x256 favicon.svg favicon.ico 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 192x192 pwa-icon.png pwa-icon-192.png
convert -quiet -background transparent -resize 512x512 pwa-icon.png pwa-icon-512.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 # 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 "$@" main "$@"

View File

@@ -5,6 +5,11 @@ main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
source ./ci/lib.sh source ./ci/lib.sh
# This installs the dependencies needed for testing
cd test
yarn
cd ..
cd lib/vscode cd lib/vscode
yarn ${CI+--frozen-lockfile} yarn ${CI+--frozen-lockfile}

View File

@@ -3,11 +3,13 @@ set -euo pipefail
main() { main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
cd test/test-plugin cd test/test-plugin
make -s out/index.js 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" cd "$OLDPWD"
mocha -r ts-node/register ./test/*.test.ts "$@" ./test/node_modules/.bin/jest "$@"
} }
main "$@" 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 # 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 # 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. # 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 # 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 [code-server](https://github.com/cdr/code-server) code-server is VS Code running
on a remote server, accessible through the browser. on a remote server, accessible through the browser.
@@ -72,7 +72,7 @@ and their default values.
| hostnameOverride | string | `""` | | | hostnameOverride | string | `""` | |
| image.pullPolicy | string | `"Always"` | | | image.pullPolicy | string | `"Always"` | |
| image.repository | string | `"codercom/code-server"` | | | image.repository | string | `"codercom/code-server"` | |
| image.tag | string | `"3.8.0"` | | | image.tag | string | `"3.8.1"` | |
| imagePullSecrets | list | `[]` | | | imagePullSecrets | list | `[]` | |
| ingress.enabled | bool | `false` | | | ingress.enabled | bool | `false` | |
| nameOverride | string | `""` | | | nameOverride | string | `""` | |

View File

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

View File

@@ -103,7 +103,7 @@ RELEASE_PATH="${RELEASE_PATH-release}"
# Code itself but also extensions will look specifically in this directory for # Code itself but also extensions will look specifically in this directory for
# files (like the ripgrep binary or the oniguruma wasm). # files (like the ripgrep binary or the oniguruma wasm).
symlink_asar() { symlink_asar() {
if [ ! -e node_modules.asar ]; then if [ ! -L node_modules.asar ]; then
if [ "${WINDIR-}" ]; then if [ "${WINDIR-}" ]; then
# mklink takes the link name first. # mklink takes the link name first.
mklink /J node_modules.asar node_modules 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 ### 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 ```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 ## Build

View File

@@ -15,6 +15,8 @@
- [How do I securely access web services?](#how-do-i-securely-access-web-services) - [How do I securely access web services?](#how-do-i-securely-access-web-services)
- [Sub-paths](#sub-paths) - [Sub-paths](#sub-paths)
- [Sub-domains](#sub-domains) - [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) - [Multi-tenancy](#multi-tenancy)
- [Docker in code-server container?](#docker-in-code-server-container) - [Docker in code-server container?](#docker-in-code-server-container)
- [How can I disable telemetry?](#how-can-i-disable-telemetry) - [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 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. These variables are most valuable to our enterprise customers for whom we have a self hosted marketplace product.
## Where are extensions stored? ## 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? ## 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? ## 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 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. 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 ## Multi-tenancy
If you want to run multiple code-servers on shared infrastructure, we recommend using virtual 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: Further docs are at:
- [README](../README.md) for a general overview - [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. - [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. 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. 3. Install caddy https://caddyserver.com/docs/download#debian-ubuntu-raspbian.
```bash ```bash
echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" \ sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
| sudo tee -a /etc/apt/sources.list.d/caddy-fury.list 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 update
sudo apt install caddy sudo apt install caddy
``` ```

View File

@@ -87,8 +87,8 @@ commands presented in the rest of this document.
## Debian, Ubuntu ## Debian, Ubuntu
```bash ```bash
curl -fOL https://github.com/cdr/code-server/releases/download/v3.8.0/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.0_amd64.deb sudo dpkg -i code-server_3.8.1_amd64.deb
sudo systemctl enable --now code-server@$USER sudo systemctl enable --now code-server@$USER
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml # 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 ## Fedora, CentOS, RHEL, SUSE
```bash ```bash
curl -fOL https://github.com/cdr/code-server/releases/download/v3.8.0/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.0-amd64.rpm sudo rpm -i code-server-3.8.1-amd64.rpm
sudo systemctl enable --now code-server@$USER sudo systemctl enable --now code-server@$USER
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml # 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 ```bash
mkdir -p ~/.local/lib ~/.local/bin 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 | tar -C ~/.local/lib -xz
mv ~/.local/lib/code-server-3.8.0-linux-amd64 ~/.local/lib/code-server-3.8.0 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.0/bin/code-server ~/.local/bin/code-server ln -s ~/.local/lib/code-server-3.8.1/bin/code-server ~/.local/bin/code-server
PATH="~/.local/bin:$PATH" PATH="~/.local/bin:$PATH"
code-server code-server
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml # Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml

View File

@@ -3,9 +3,11 @@
# iPad # iPad
- [Known Issues](#known-issues) - [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) - [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) - [Servediter iPad App](#servediter-ipad-app)
- [Raspberry Pi USB-C Network](#raspberry-pi-usb-c-network) - [Raspberry Pi USB-C Network](#raspberry-pi-usb-c-network)
- [Ctrl C Workaround](#ctrl-c-workaround)
- [Recommendations](#recommendations) - [Recommendations](#recommendations)
- [By 2022 iPad coding more desirable on Arm Macs](#by-2022-ipad-coding-more-desirable-on-arm-macs) - [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: Install line-jump extension and use keyboard to nav by jumping large amount of lines
- Alternative: Just use touch scrolling - 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. - 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? ## 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> > -- <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 ## Recommendations
Once you have code-server accessible to your iPad a few things could help save you time: 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 set -eu
# code-server's automatic install script. # 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() { usage() {
arg0="$0" arg0="$0"
@@ -67,7 +67,7 @@ Usage:
It will cache all downloaded assets into ~/.cache/code-server 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 EOF
} }
@@ -525,7 +525,7 @@ sudo_sh_c() {
elif command_exists sudo; then elif command_exists sudo; then
sh_c "sudo $*" sh_c "sudo $*"
elif command_exists su; then elif command_exists su; then
sh_c "su -c '$*'" sh_c "su - -c '$*'"
else else
echoh echoh
echoerr "This script needs to run the following command as root." echoerr "This script needs to run the following command as root."

View File

@@ -6,6 +6,6 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
export function isWeb(): boolean { 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; 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) { if (parent) {
// Tell the parent loading has completed. // 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. // Proxy or stop proxing events as requested by the parent.
const listeners = new Map<string, (event: Event) => void>(); 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 { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; 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 { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views'; import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; 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 { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys';
import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICommandService } from 'vs/platform/commands/common/commands';
import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
const DefaultViewsContext = new RawContextKey<boolean>('defaultExtensionViews', true); const DefaultViewsContext = new RawContextKey<boolean>('defaultExtensionViews', true);
const SearchMarketplaceExtensionsContext = new RawContextKey<boolean>('searchMarketplaceExtensions', false); const SearchMarketplaceExtensionsContext = new RawContextKey<boolean>('searchMarketplaceExtensions', false);
@@ -410,6 +411,42 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
overlay.style.backgroundColor = overlayBackgroundColor; overlay.style.backgroundColor = overlayBackgroundColor;
hide(overlay); 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 header = append(this.root, $('.header'));
const placeholder = localize('searchExtensions', "Search Extensions in Marketplace"); const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : ''; const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : '';

View File

@@ -1,7 +1,7 @@
{ {
"name": "code-server", "name": "code-server",
"license": "MIT", "license": "MIT",
"version": "3.8.0", "version": "3.8.1",
"description": "Run VS Code on a remote server.", "description": "Run VS Code on a remote server.",
"homepage": "https://github.com/cdr/code-server", "homepage": "https://github.com/cdr/code-server",
"bugs": { "bugs": {
@@ -26,7 +26,8 @@
"test": "./ci/dev/test.sh", "test": "./ci/dev/test.sh",
"ci": "./ci/dev/ci.sh", "ci": "./ci/dev/ci.sh",
"watch": "VSCODE_IPC_HOOK_CLI= NODE_OPTIONS=--max_old_space_size=32384 ts-node ./ci/dev/watch.ts", "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", "main": "out/node/entry.js",
"devDependencies": { "devDependencies": {
@@ -36,7 +37,6 @@
"@types/fs-extra": "^8.0.1", "@types/fs-extra": "^8.0.1",
"@types/http-proxy": "^1.17.4", "@types/http-proxy": "^1.17.4",
"@types/js-yaml": "^3.12.3", "@types/js-yaml": "^3.12.3",
"@types/mocha": "^8.0.3",
"@types/node": "^12.12.7", "@types/node": "^12.12.7",
"@types/parcel-bundler": "^1.12.1", "@types/parcel-bundler": "^1.12.1",
"@types/pem": "^1.9.5", "@types/pem": "^1.9.5",
@@ -44,10 +44,10 @@
"@types/safe-compare": "^1.1.0", "@types/safe-compare": "^1.1.0",
"@types/semver": "^7.1.0", "@types/semver": "^7.1.0",
"@types/split2": "^2.1.6", "@types/split2": "^2.1.6",
"@types/supertest": "^2.0.10",
"@types/tar-fs": "^2.0.0", "@types/tar-fs": "^2.0.0",
"@types/tar-stream": "^2.1.0", "@types/tar-stream": "^2.1.0",
"@types/ws": "^7.2.6", "@types/ws": "^7.2.6",
"@types/wtfnode": "^0.7.0",
"@typescript-eslint/eslint-plugin": "^4.7.0", "@typescript-eslint/eslint-plugin": "^4.7.0",
"@typescript-eslint/parser": "^4.7.0", "@typescript-eslint/parser": "^4.7.0",
"doctoc": "^1.4.0", "doctoc": "^1.4.0",
@@ -55,15 +55,15 @@
"eslint-config-prettier": "^6.0.0", "eslint-config-prettier": "^6.0.0",
"eslint-plugin-import": "^2.18.2", "eslint-plugin-import": "^2.18.2",
"eslint-plugin-prettier": "^3.1.0", "eslint-plugin-prettier": "^3.1.0",
"istanbul-badges-readme": "^1.2.0",
"leaked-handles": "^5.2.0", "leaked-handles": "^5.2.0",
"mocha": "^8.1.2",
"parcel-bundler": "^1.12.4", "parcel-bundler": "^1.12.4",
"prettier": "^2.0.5", "prettier": "^2.0.5",
"stylelint": "^13.0.0", "stylelint": "^13.0.0",
"stylelint-config-recommended": "^3.0.0", "stylelint-config-recommended": "^3.0.0",
"supertest": "^6.0.1",
"ts-node": "^9.0.0", "ts-node": "^9.0.0",
"typescript": "4.0.2" "wtfnode": "^0.8.4",
"typescript": "^4.1.3"
}, },
"resolutions": { "resolutions": {
"@types/node": "^12.12.7", "@types/node": "^12.12.7",
@@ -81,6 +81,7 @@
"httpolyglot": "^0.1.2", "httpolyglot": "^0.1.2",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"limiter": "^1.1.5", "limiter": "^1.1.5",
"node-fetch": "^2.6.1",
"pem": "^1.14.2", "pem": "^1.14.2",
"proxy-agent": "^4.0.0", "proxy-agent": "^4.0.0",
"proxy-from-env": "^1.1.0", "proxy-from-env": "^1.1.0",
@@ -109,5 +110,34 @@
], ],
"engines": { "engines": {
"node": ">= 12" "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 (Stored with Git 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:;" content="style-src 'self'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;"
/> />
<title>{{ERROR_TITLE}} - code-server</title> <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="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="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" /> <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:;" 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> <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="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="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" /> <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}}" /> <meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}" />
<!-- Workbench Icon/Manifest/CSS --> <!-- 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="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="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
<!-- PROD_ONLY <!-- PROD_ONLY

View File

@@ -112,3 +112,11 @@ export const getFirstString = (value: string | string[] | object | undefined): s
return typeof value === "string" ? value : undefined 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 { promises as fs } from "fs"
import http from "http" import http from "http"
import * as httpolyglot from "httpolyglot" import * as httpolyglot from "httpolyglot"
import * as util from "../common/util"
import { DefaultedArgs } from "./cli" import { DefaultedArgs } from "./cli"
import { handleUpgrade } from "./wsRouter" import { handleUpgrade } from "./wsRouter"
@@ -22,8 +23,21 @@ export const createApp = async (args: DefaultedArgs): Promise<[Express, Express,
) )
: http.createServer(app) : http.createServer(app)
await new Promise<http.Server>(async (resolve, reject) => { let resolved = false
server.on("error", reject) 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) { if (args.socket) {
try { try {
await fs.unlink(args.socket) await fs.unlink(args.socket)

View File

@@ -239,7 +239,7 @@ export const optionDescriptions = (): string[] => {
export const parse = ( export const parse = (
argv: string[], argv: string[],
opts?: { opts?: {
configFile: string configFile?: string
}, },
): Args => { ): Args => {
const error = (msg: string): Error => { const error = (msg: string): Error => {
@@ -516,7 +516,19 @@ export async function readConfigFile(configPath?: string): Promise<ConfigArgs> {
} }
const configFile = await fs.readFile(configPath) 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, filename: configPath,
}) })
if (!config || typeof config === "string") { if (!config || typeof config === "string") {

View File

@@ -45,4 +45,13 @@ export class Heart {
}) })
}, this.heartbeatInterval) }, 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. // 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) => { proxy.on("proxyRes", (res, req) => {
if (res.headers.location && res.headers.location.startsWith("/") && (req as any).base) { if (res.headers.location && res.headers.location.startsWith("/") && (req as any).base) {
res.headers.location = (req as any).base + res.headers.location 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") app.disable("x-powered-by")
wsApp.disable("x-powered-by") wsApp.disable("x-powered-by")
@@ -62,9 +65,6 @@ export const register = async (
app.use(cookieParser()) app.use(cookieParser())
wsApp.use(cookieParser()) wsApp.use(cookieParser())
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
const common: express.RequestHandler = (req, _, next) => { const common: express.RequestHandler = (req, _, next) => {
// /healthz|/healthz/ needs to be excluded otherwise health checks will make // /healthz|/healthz/ needs to be excluded otherwise health checks will make
// it look like code-server is always in use. // it look like code-server is always in use.
@@ -103,6 +103,29 @@ export const register = async (
app.use("/", domainProxy.router) app.use("/", domainProxy.router)
wsApp.use("/", domainProxy.wsRouter.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) app.use("/", vscode.router)
wsApp.use("/", vscode.wsRouter.router) wsApp.use("/", vscode.wsRouter.router)
app.use("/vscode", vscode.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("/static", _static.router)
app.use("/update", update.router) app.use("/update", update.router)
@@ -165,7 +185,7 @@ export const register = async (
app.use(errorHandler) 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}`) logger.error(`${err.message} ${err.stack}`)
;(req as WebsocketRequest).ws.end() ;(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 qs from "qs"
import { HttpCode, HttpError } from "../../common/http" import { HttpCode, HttpError } from "../../common/http"
import { normalize } from "../../common/util" import { normalize } from "../../common/util"
import { authenticated, ensureAuthenticated, redirect } from "../http" import { authenticated, ensureAuthenticated, redirect } from "../http"
import { proxy } from "../proxy" import { proxy as _proxy } from "../proxy"
import { Router as WsRouter } from "../wsRouter" import { WebsocketRequest } from "../wsRouter"
export const router = Router() const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
if (passthroughPath) {
const getProxyTarget = (req: Request, rewrite: boolean): string => { return `http://0.0.0.0:${req.params.port}/${req.originalUrl}`
if (rewrite) {
const query = qs.stringify(req.query)
return `http://0.0.0.0:${req.params.port}/${req.params[0] || ""}${query ? `?${query}` : ""}`
} }
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 (!authenticated(req)) {
// If visiting the root (/:port only) redirect to the login page. // If visiting the root (/:port only) redirect to the login page.
if (!req.params[0] || req.params[0] === "/") { if (!req.params[0] || req.params[0] === "/") {
@@ -28,20 +33,27 @@ router.all("/(:port)(/*)?", (req, res) => {
throw new HttpError("Unauthorized", HttpCode.Unauthorized) throw new HttpError("Unauthorized", HttpCode.Unauthorized)
} }
// Absolute redirects need to be based on the subpath when rewriting. if (!opts?.passthroughPath) {
;(req as any).base = `${req.baseUrl}/${req.params.port}` // 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, ignorePath: true,
target: getProxyTarget(req, true), target: getProxyTarget(req, opts?.passthroughPath),
}) })
}) }
export const wsRouter = WsRouter() export function wsProxy(
req: WebsocketRequest,
wsRouter.ws("/(:port)(/*)?", ensureAuthenticated, (req) => { opts?: {
proxy.ws(req, req.ws, req.head, { passthroughPath?: boolean
},
): void {
ensureAuthenticated(req)
_proxy.ws(req, req.ws, req.head, {
ignorePath: true, 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, "^&") url = url.replace(/&/g, "^&")
} }
const proc = cp.spawn(command, [...args, url], options) 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("error", reject)
proc.on("close", (code) => { proc.on("close", (code) => {
return code !== 0 ? reject(new Error(`Failed to open with code ${code}`)) : resolve() 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, query: ipc.Query,
): Promise<ipc.WorkbenchOptions> { ): Promise<ipc.WorkbenchOptions> {
const { lastVisited } = await settings.read() const { lastVisited } = await settings.read()
const startPath = await this.getFirstPath([ let startPath = await this.getFirstPath([
{ url: query.workspace, workspace: true }, { url: query.workspace, workspace: true },
{ url: query.folder, workspace: false }, { url: query.folder, workspace: false },
options.args._ && options.args._.length > 0 options.args._ && options.args._.length > 0
@@ -46,6 +46,10 @@ export class VscodeProvider {
!options.args["ignore-last-opened"] ? lastVisited : undefined, !options.args["ignore-last-opened"] ? lastVisited : undefined,
]) ])
if (query.ew) {
startPath = undefined
}
settings.write({ settings.write({
lastVisited: startPath, lastVisited: startPath,
query, query,

View File

@@ -1,5 +1,4 @@
import { Level, logger } from "@coder/logger" import { Level, logger } from "@coder/logger"
import * as assert from "assert"
import * as fs from "fs-extra" import * as fs from "fs-extra"
import * as net from "net" import * as net from "net"
import * as os from "os" import * as os from "os"
@@ -15,6 +14,7 @@ describe("parser", () => {
beforeEach(() => { beforeEach(() => {
delete process.env.LOG_LEVEL delete process.env.LOG_LEVEL
delete process.env.PASSWORD delete process.env.PASSWORD
console.log = jest.fn()
}) })
// The parser should not set any defaults so the caller can determine what // The parser should not set any defaults so the caller can determine what
@@ -32,11 +32,11 @@ describe("parser", () => {
} }
it("should parse nothing", () => { it("should parse nothing", () => {
assert.deepEqual(parse([]), { _: [] }) expect(parse([])).toStrictEqual({ _: [] })
}) })
it("should parse all available options", () => { it("should parse all available options", () => {
assert.deepEqual( expect(
parse([ parse([
"--bind-addr=192.169.0.1:8080", "--bind-addr=192.169.0.1:8080",
"--auth", "--auth",
@@ -74,35 +74,34 @@ describe("parser", () => {
"-5", "-5",
"--6", "--6",
]), ]),
{ ).toEqual({
_: ["1", "2", "3", "4", "-5", "--6"], _: ["1", "2", "3", "4", "-5", "--6"],
auth: "none", auth: "none",
"builtin-extensions-dir": path.resolve("foobar"), "builtin-extensions-dir": path.resolve("foobar"),
"cert-key": path.resolve("qux"), "cert-key": path.resolve("qux"),
cert: { cert: {
value: path.resolve("baz"), 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",
}, },
) "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", () => { it("should work with short options", () => {
assert.deepEqual(parse(["-vvv", "-v"]), { expect(parse(["-vvv", "-v"])).toEqual({
_: [], _: [],
verbose: true, verbose: true,
version: true, version: true,
@@ -111,102 +110,108 @@ describe("parser", () => {
it("should use log level env var", async () => { it("should use log level env var", async () => {
const args = parse([]) const args = parse([])
assert.deepEqual(args, { _: [] }) expect(args).toEqual({ _: [] })
process.env.LOG_LEVEL = "debug" process.env.LOG_LEVEL = "debug"
assert.deepEqual(await setDefaults(args), { const defaults = await setDefaults(args)
expect(defaults).toStrictEqual({
...defaults, ...defaults,
_: [], _: [],
log: "debug", log: "debug",
verbose: false, verbose: false,
}) })
assert.equal(process.env.LOG_LEVEL, "debug") expect(process.env.LOG_LEVEL).toEqual("debug")
assert.equal(logger.level, Level.Debug) expect(logger.level).toEqual(Level.Debug)
process.env.LOG_LEVEL = "trace" process.env.LOG_LEVEL = "trace"
assert.deepEqual(await setDefaults(args), { const updated = await setDefaults(args)
...defaults, expect(updated).toStrictEqual({
...updated,
_: [], _: [],
log: "trace", log: "trace",
verbose: true, verbose: true,
}) })
assert.equal(process.env.LOG_LEVEL, "trace") expect(process.env.LOG_LEVEL).toEqual("trace")
assert.equal(logger.level, Level.Trace) expect(logger.level).toEqual(Level.Trace)
}) })
it("should prefer --log to env var and --verbose to --log", async () => { it("should prefer --log to env var and --verbose to --log", async () => {
let args = parse(["--log", "info"]) let args = parse(["--log", "info"])
assert.deepEqual(args, { expect(args).toEqual({
_: [], _: [],
log: "info", log: "info",
}) })
process.env.LOG_LEVEL = "debug" process.env.LOG_LEVEL = "debug"
assert.deepEqual(await setDefaults(args), { const defaults = await setDefaults(args)
expect(defaults).toEqual({
...defaults, ...defaults,
_: [], _: [],
log: "info", log: "info",
verbose: false, verbose: false,
}) })
assert.equal(process.env.LOG_LEVEL, "info") expect(process.env.LOG_LEVEL).toEqual("info")
assert.equal(logger.level, Level.Info) expect(logger.level).toEqual(Level.Info)
process.env.LOG_LEVEL = "trace" process.env.LOG_LEVEL = "trace"
assert.deepEqual(await setDefaults(args), { const updated = await setDefaults(args)
expect(updated).toEqual({
...defaults, ...defaults,
_: [], _: [],
log: "info", log: "info",
verbose: false, verbose: false,
}) })
assert.equal(process.env.LOG_LEVEL, "info") expect(process.env.LOG_LEVEL).toEqual("info")
assert.equal(logger.level, Level.Info) expect(logger.level).toEqual(Level.Info)
args = parse(["--log", "info", "--verbose"]) args = parse(["--log", "info", "--verbose"])
assert.deepEqual(args, { expect(args).toEqual({
_: [], _: [],
log: "info", log: "info",
verbose: true, verbose: true,
}) })
process.env.LOG_LEVEL = "warn" process.env.LOG_LEVEL = "warn"
assert.deepEqual(await setDefaults(args), { const updatedAgain = await setDefaults(args)
expect(updatedAgain).toEqual({
...defaults, ...defaults,
_: [], _: [],
log: "trace", log: "trace",
verbose: true, verbose: true,
}) })
assert.equal(process.env.LOG_LEVEL, "trace") expect(process.env.LOG_LEVEL).toEqual("trace")
assert.equal(logger.level, Level.Trace) expect(logger.level).toEqual(Level.Trace)
}) })
it("should ignore invalid log level env var", async () => { it("should ignore invalid log level env var", async () => {
process.env.LOG_LEVEL = "bogus" process.env.LOG_LEVEL = "bogus"
assert.deepEqual(await setDefaults(parse([])), { const defaults = await setDefaults(parse([]))
_: [], expect(defaults).toEqual({
...defaults, ...defaults,
_: [],
}) })
}) })
it("should error if value isn't provided", () => { it("should error if value isn't provided", () => {
assert.throws(() => parse(["--auth"]), /--auth requires a value/) expect(() => parse(["--auth"])).toThrowError(/--auth requires a value/)
assert.throws(() => parse(["--auth=", "--log=debug"]), /--auth requires a value/) expect(() => parse(["--auth=", "--log=debug"])).toThrowError(/--auth requires a value/)
assert.throws(() => parse(["--auth", "--log"]), /--auth requires a value/) expect(() => parse(["--auth", "--log"])).toThrowError(/--auth requires a value/)
assert.throws(() => parse(["--auth", "--invalid"]), /--auth requires a value/) expect(() => parse(["--auth", "--invalid"])).toThrowError(/--auth requires a value/)
assert.throws(() => parse(["--bind-addr"]), /--bind-addr requires a value/) expect(() => parse(["--bind-addr"])).toThrowError(/--bind-addr requires a value/)
}) })
it("should error if value is invalid", () => { it("should error if value is invalid", () => {
assert.throws(() => parse(["--port", "foo"]), /--port must be a number/) expect(() => parse(["--port", "foo"])).toThrowError(/--port must be a number/)
assert.throws(() => parse(["--auth", "invalid"]), /--auth valid values: \[password, none\]/) expect(() => parse(["--auth", "invalid"])).toThrowError(/--auth valid values: \[password, none\]/)
assert.throws(() => parse(["--log", "invalid"]), /--log valid values: \[trace, debug, info, warn, error\]/) expect(() => parse(["--log", "invalid"])).toThrowError(/--log valid values: \[trace, debug, info, warn, error\]/)
}) })
it("should error if the option doesn't exist", () => { 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", () => { it("should not error if the value is optional", () => {
assert.deepEqual(parse(["--cert"]), { expect(parse(["--cert"])).toEqual({
_: [], _: [],
cert: { cert: {
value: undefined, value: undefined,
@@ -215,28 +220,28 @@ describe("parser", () => {
}) })
it("should not allow option-like values", () => { 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: // 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"), 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", () => { it("should allow positional arguments before options", () => {
assert.deepEqual(parse(["foo", "test", "--auth", "none"]), { expect(parse(["foo", "test", "--auth", "none"])).toEqual({
_: ["foo", "test"], _: ["foo", "test"],
auth: "none", auth: "none",
}) })
}) })
it("should support repeatable flags", () => { it("should support repeatable flags", () => {
assert.deepEqual(parse(["--proxy-domain", "*.coder.com"]), { expect(parse(["--proxy-domain", "*.coder.com"])).toEqual({
_: [], _: [],
"proxy-domain": ["*.coder.com"], "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"], "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 () => { it("should enforce cert-key with cert value or otherwise generate one", async () => {
const args = parse(["--cert"]) const args = parse(["--cert"])
assert.deepEqual(args, { expect(args).toEqual({
_: [], _: [],
cert: { cert: {
value: undefined, value: undefined,
}, },
}) })
assert.throws(() => parse(["--cert", "test"]), /--cert-key is missing/) expect(() => parse(["--cert", "test"])).toThrowError(/--cert-key is missing/)
assert.deepEqual(await setDefaults(args), { const defaultArgs = await setDefaults(args)
expect(defaultArgs).toEqual({
_: [], _: [],
...defaults, ...defaults,
cert: { cert: {
@@ -263,7 +269,8 @@ describe("parser", () => {
it("should override with --link", async () => { 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(" ")) 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, ...defaults,
auth: "none", auth: "none",
@@ -281,11 +288,12 @@ describe("parser", () => {
it("should use env var password", async () => { it("should use env var password", async () => {
process.env.PASSWORD = "test" process.env.PASSWORD = "test"
const args = parse([]) const args = parse([])
assert.deepEqual(args, { expect(args).toEqual({
_: [], _: [],
}) })
assert.deepEqual(await setDefaults(args), { const defaultArgs = await setDefaults(args)
expect(defaultArgs).toEqual({
...defaults, ...defaults,
_: [], _: [],
password: "test", password: "test",
@@ -296,11 +304,12 @@ describe("parser", () => {
it("should use env var hashed password", async () => { it("should use env var hashed password", async () => {
process.env.HASHED_PASSWORD = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" // test process.env.HASHED_PASSWORD = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" // test
const args = parse([]) const args = parse([])
assert.deepEqual(args, { expect(args).toEqual({
_: [], _: [],
}) })
assert.deepEqual(await setDefaults(args), { const defaultArgs = await setDefaults(args)
expect(defaultArgs).toEqual({
...defaults, ...defaults,
_: [], _: [],
"hashed-password": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "hashed-password": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
@@ -310,12 +319,13 @@ describe("parser", () => {
it("should filter proxy domains", async () => { it("should filter proxy domains", async () => {
const args = parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "coder.com", "--proxy-domain", "coder.org"]) 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"], "proxy-domain": ["*.coder.com", "coder.com", "coder.org"],
}) })
assert.deepEqual(await setDefaults(args), { const defaultArgs = await setDefaults(args)
expect(defaultArgs).toEqual({
...defaults, ...defaults,
_: [], _: [],
"proxy-domain": ["coder.com", "coder.org"], "proxy-domain": ["coder.com", "coder.org"],
@@ -328,7 +338,7 @@ describe("cli", () => {
const testDir = path.join(tmpdir, "tests/cli") const testDir = path.join(tmpdir, "tests/cli")
const vscodeIpcPath = path.join(os.tmpdir(), "vscode-ipc") const vscodeIpcPath = path.join(os.tmpdir(), "vscode-ipc")
before(async () => { beforeAll(async () => {
await fs.remove(testDir) await fs.remove(testDir)
await fs.mkdirp(testDir) await fs.mkdirp(testDir)
}) })
@@ -341,44 +351,44 @@ describe("cli", () => {
it("should use existing if inside code-server", async () => { it("should use existing if inside code-server", async () => {
process.env.VSCODE_IPC_HOOK_CLI = "test" process.env.VSCODE_IPC_HOOK_CLI = "test"
assert.strictEqual(await shouldOpenInExistingInstance(args), "test") expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
args.port = 8081 args.port = 8081
args._.push("./file") 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 () => { it("should use existing if --reuse-window is set", async () => {
args["reuse-window"] = true args["reuse-window"] = true
assert.strictEqual(await shouldOpenInExistingInstance(args), undefined) await expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
await fs.writeFile(vscodeIpcPath, "test") await fs.writeFile(vscodeIpcPath, "test")
assert.strictEqual(await shouldOpenInExistingInstance(args), "test") await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual("test")
args.port = 8081 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 () => { it("should use existing if --new-window is set", async () => {
args["new-window"] = true args["new-window"] = true
assert.strictEqual(await shouldOpenInExistingInstance(args), undefined) expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
await fs.writeFile(vscodeIpcPath, "test") await fs.writeFile(vscodeIpcPath, "test")
assert.strictEqual(await shouldOpenInExistingInstance(args), "test") expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
args.port = 8081 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 () => { 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") args._.push("./file")
assert.strictEqual(await shouldOpenInExistingInstance(args), undefined) expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
const socketPath = path.join(testDir, "socket") const socketPath = path.join(testDir, "socket")
await fs.writeFile(vscodeIpcPath, socketPath) await fs.writeFile(vscodeIpcPath, socketPath)
assert.strictEqual(await shouldOpenInExistingInstance(args), undefined) expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
await new Promise((resolve) => { await new Promise((resolve) => {
const server = net.createServer(() => { const server = net.createServer(() => {
@@ -389,9 +399,9 @@ describe("cli", () => {
server.listen(socketPath) server.listen(socketPath)
}) })
assert.strictEqual(await shouldOpenInExistingInstance(args), socketPath) expect(await shouldOpenInExistingInstance(args)).toStrictEqual(socketPath)
args.port = 8081 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 { logger } from "@coder/logger"
import * as express from "express" import * as express from "express"
import * as fs from "fs" import * as fs from "fs"
import { describe } from "mocha"
import * as path from "path" import * as path from "path"
import * as supertest from "supertest"
import { PluginAPI } from "../src/node/plugin" import { PluginAPI } from "../src/node/plugin"
import * as apps from "../src/node/routes/apps" import * as apps from "../src/node/routes/apps"
import * as httpserver from "./httpserver"
const fsp = fs.promises const fsp = fs.promises
/** /**
@@ -13,23 +12,30 @@ const fsp = fs.promises
*/ */
describe("plugin", () => { describe("plugin", () => {
let papi: PluginAPI let papi: PluginAPI
let app: express.Application let s: httpserver.HttpServer
let agent: supertest.SuperAgentTest
before(async () => { beforeAll(async () => {
papi = new PluginAPI(logger, path.resolve(__dirname, "test-plugin") + ":meow") papi = new PluginAPI(logger, `${path.resolve(__dirname, "test-plugin")}:meow`)
await papi.loadPlugins() await papi.loadPlugins()
app = express.default() const app = express.default()
papi.mount(app) papi.mount(app)
app.use("/api/applications", apps.router(papi)) 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 () => { 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", name: "Test App",
version: "4.0.0", version: "4.0.0",
@@ -57,6 +63,9 @@ describe("plugin", () => {
const indexHTML = await fsp.readFile(path.join(__dirname, "test-plugin/public/index.html"), { const indexHTML = await fsp.readFile(path.join(__dirname, "test-plugin/public/index.html"), {
encoding: "utf8", 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 { field, logger } from "@coder/logger"
import * as assert from "assert"
import * as fs from "fs-extra" import * as fs from "fs-extra"
import "leaked-handles"
import * as net from "net" import * as net from "net"
import * as path from "path" import * as path from "path"
import * as tls from "tls" import * as tls from "tls"
import { Emitter } from "../src/common/emitter" import { Emitter } from "../src/common/emitter"
import { SocketProxyProvider } from "../src/node/socket" import { SocketProxyProvider } from "../src/node/socket"
import { generateCertificate, tmpdir } from "../src/node/util" import { generateCertificate, tmpdir } from "../src/node/util"
import * as wtfnode from "./wtfnode"
describe("SocketProxyProvider", () => { describe("SocketProxyProvider", () => {
wtfnode.setup()
const provider = new SocketProxyProvider() const provider = new SocketProxyProvider()
const onServerError = new Emitter<{ event: string; error: Error }>() const onServerError = new Emitter<{ event: string; error: Error }>()
const onClientError = new Emitter<{ event: string; error: Error }>() const onClientError = new Emitter<{ event: string; error: Error }>()
const onProxyError = new Emitter<{ event: string; error: Error }>() const onProxyError = new Emitter<{ event: string; error: Error }>()
const fromServerToClient = new Emitter<string>() const fromServerToClient = new Emitter<Buffer>()
const fromClientToServer = new Emitter<string>() const fromClientToServer = new Emitter<Buffer>()
const fromClientToProxy = new Emitter<Buffer>() const fromClientToProxy = new Emitter<Buffer>()
let errors = 0 let errors = 0
@@ -44,7 +45,7 @@ describe("SocketProxyProvider", () => {
}) })
} }
before(async () => { beforeAll(async () => {
const cert = await generateCertificate("localhost") const cert = await generateCertificate("localhost")
const options = { const options = {
cert: fs.readFileSync(cert.cert), cert: fs.readFileSync(cert.cert),
@@ -56,7 +57,7 @@ describe("SocketProxyProvider", () => {
const socketPath = await provider.findFreeSocketPath(path.join(tmpdir, "tests/tls-socket-proxy")) const socketPath = await provider.findFreeSocketPath(path.join(tmpdir, "tests/tls-socket-proxy"))
await fs.remove(socketPath) await fs.remove(socketPath)
return new Promise((_resolve) => { return new Promise<void>((_resolve) => {
const resolved: { [key: string]: boolean } = { client: false, server: false } const resolved: { [key: string]: boolean } = { client: false, server: false }
const resolve = (type: "client" | "server"): void => { const resolve = (type: "client" | "server"): void => {
resolved[type] = true resolved[type] = true
@@ -93,14 +94,16 @@ describe("SocketProxyProvider", () => {
it("should work without a proxy", async () => { it("should work without a proxy", async () => {
server.write("server->client") 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") client.write("client->server")
assert.equal(await getData(fromClientToServer), "client->server") const dataFromClientToServer = (await getData(fromClientToServer)).toString()
assert.equal(errors, 0) expect(dataFromClientToServer).toBe("client->server")
expect(errors).toEqual(0)
}) })
it("should work with a proxy", async () => { 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)) proxy = (await provider.createProxy(server))
.on("data", (d) => fromClientToProxy.emit(d)) .on("data", (d) => fromClientToProxy.emit(d))
.on("error", (error) => onProxyError.emit({ event: "error", error })) .on("error", (error) => onProxyError.emit({ event: "error", error }))
@@ -110,10 +113,12 @@ describe("SocketProxyProvider", () => {
provider.stop() // We don't need more proxies. provider.stop() // We don't need more proxies.
proxy.write("server proxy->client") 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") client.write("client->server proxy")
assert.equal(await getData(fromClientToProxy), "client->server proxy") const dataFromClientToProxy = await (await getData(fromClientToProxy)).toString()
assert.equal(errors, 0) expect(dataFromClientToProxy).toBe("client->server proxy")
expect(errors).toEqual(0)
}) })
it("should close", async () => { it("should close", async () => {

View File

@@ -1,4 +1,3 @@
import * as assert from "assert"
import * as fs from "fs-extra" import * as fs from "fs-extra"
import * as http from "http" import * as http from "http"
import * as path from "path" import * as path from "path"
@@ -45,7 +44,7 @@ describe.skip("update", () => {
return _provider return _provider
} }
before(async () => { beforeAll(async () => {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
server.on("error", reject) server.on("error", reject)
server.on("listening", resolve) server.on("listening", resolve)
@@ -58,7 +57,7 @@ describe.skip("update", () => {
await fs.mkdirp(path.join(tmpdir, "tests/updates")) await fs.mkdirp(path.join(tmpdir, "tests/updates"))
}) })
after(() => { afterAll(() => {
server.close() server.close()
}) })
@@ -73,11 +72,11 @@ describe.skip("update", () => {
const now = Date.now() const now = Date.now()
const update = await p.getUpdate() const update = await p.getUpdate()
assert.deepEqual({ update }, await settings.read()) await expect(settings.read()).resolves.toEqual({ update })
assert.equal(isNaN(update.checked), false) expect(isNaN(update.checked)).toEqual(false)
assert.equal(update.checked < Date.now() && update.checked >= now, true) expect(update.checked < Date.now() && update.checked >= now).toEqual(true)
assert.equal(update.version, "2.1.0") expect(update.version).toBe("2.1.0")
assert.deepEqual(spy, ["/latest"]) expect(spy).toEqual(["/latest"])
}) })
it("should keep existing information", async () => { it("should keep existing information", async () => {
@@ -87,11 +86,11 @@ describe.skip("update", () => {
const now = Date.now() const now = Date.now()
const update = await p.getUpdate() const update = await p.getUpdate()
assert.deepEqual({ update }, await settings.read()) await expect(settings.read()).resolves.toEqual({ update })
assert.equal(isNaN(update.checked), false) expect(isNaN(update.checked)).toBe(false)
assert.equal(update.checked < now, true) expect(update.checked < now).toBe(true)
assert.equal(update.version, "2.1.0") expect(update.version).toBe("2.1.0")
assert.deepEqual(spy, []) expect(spy).toEqual([])
}) })
it("should force getting the latest", async () => { it("should force getting the latest", async () => {
@@ -101,29 +100,29 @@ describe.skip("update", () => {
const now = Date.now() const now = Date.now()
const update = await p.getUpdate(true) const update = await p.getUpdate(true)
assert.deepEqual({ update }, await settings.read()) await expect(settings.read()).resolves.toEqual({ update })
assert.equal(isNaN(update.checked), false) expect(isNaN(update.checked)).toBe(false)
assert.equal(update.checked < Date.now() && update.checked >= now, true) expect(update.checked < Date.now() && update.checked >= now).toBe(true)
assert.equal(update.version, "4.1.1") expect(update.version).toBe("4.1.1")
assert.deepEqual(spy, ["/latest"]) expect(spy).toBe(["/latest"])
}) })
it("should get latest after interval passes", async () => { it("should get latest after interval passes", async () => {
const p = provider() const p = provider()
await p.getUpdate() await p.getUpdate()
assert.deepEqual(spy, []) expect(spy).toEqual([])
let checked = Date.now() - 1000 * 60 * 60 * 23 let checked = Date.now() - 1000 * 60 * 60 * 23
await settings.write({ update: { checked, version } }) await settings.write({ update: { checked, version } })
await p.getUpdate() await p.getUpdate()
assert.deepEqual(spy, []) expect(spy).toEqual([])
checked = Date.now() - 1000 * 60 * 60 * 25 checked = Date.now() - 1000 * 60 * 60 * 25
await settings.write({ update: { checked, version } }) await settings.write({ update: { checked, version } })
const update = await p.getUpdate() const update = await p.getUpdate()
assert.notEqual(update.checked, checked) expect(update.checked).not.toBe(checked)
assert.deepEqual(spy, ["/latest"]) expect(spy).toBe(["/latest"])
}) })
it("should check if it's the current version", async () => { it("should check if it's the current version", async () => {
@@ -131,23 +130,24 @@ describe.skip("update", () => {
const p = provider() const p = provider()
let update = await p.getUpdate(true) let update = await p.getUpdate(true)
assert.equal(p.isLatestVersion(update), false) expect(p.isLatestVersion(update)).toBe(false)
version = "0.0.0" version = "0.0.0"
update = await p.getUpdate(true) 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. // Old version format; make sure it doesn't report as being later.
version = "999999.9999-invalid999.99.9" version = "999999.9999-invalid999.99.9"
update = await p.getUpdate(true) 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 () => { it("should not reject if unable to fetch", async () => {
expect.assertions(2)
let provider = new UpdateProvider("invalid", settings) 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) 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" import { normalize } from "../src/common/util"
describe("util", () => { describe("util", () => {
describe("normalize", () => { describe("normalize", () => {
it("should remove multiple slashes", () => { 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", () => { it("should remove trailing slashes", () => {
assert.equal(normalize("qux///"), "qux") expect(normalize("qux///")).toBe("qux")
}) })
it("should preserve trailing slash if it exists", () => { it("should preserve trailing slash if it exists", () => {
assert.equal(normalize("qux///", true), "qux/") expect(normalize("qux///", true)).toBe("qux/")
assert.equal(normalize("qux", true), "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, "sourceMap": true,
"tsBuildInfoFile": "./.cache/tsbuildinfo", "tsBuildInfoFile": "./.cache/tsbuildinfo",
"incremental": true, "incremental": true,
"rootDir": "./src", "typeRoots": ["./node_modules/@types", "./typings", "./test/node_modules/@types"],
"typeRoots": ["./node_modules/@types", "./typings"],
"downlevelIteration": true "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