Compare commits

..

223 Commits

Author SHA1 Message Date
Asher
87ebf03eb7 Skip vscode dependencies for test phase
They aren't used so we can skip them.
2020-03-27 13:40:42 -05:00
Asher
df1c34e291 Overwrite GitHub releases again
I was under the impression this was causing existing releases to become
drafts again but that happens without this flag.
2020-03-27 12:03:01 -05:00
Asher
4a65b58772 Fix arm builds 2020-03-27 12:02:56 -05:00
Asher
11fdb8854b Skip unused dependencies 2020-03-26 15:12:17 -05:00
Asher
0a92bb1607 Fix node version mismatch 2020-03-26 13:54:41 -05:00
Asher
5bac2cbdb8 Add build test 2020-03-26 13:54:40 -05:00
Asher
511c3e95b2 Remove npm rebuild 2020-03-25 17:07:26 -05:00
Asher
0a5687bacf Fix crash when unable to request an update 2020-03-25 15:00:35 -05:00
Asher
27320465b7 Merge pull request #1443 from maksimr/fix-duplication
Remove duplication in dependencies
2020-03-25 14:13:34 -05:00
Asher
6df454e006 Merge pull request #1445 from maksimr/fix-doc
Fix documentation for build process
2020-03-25 14:13:15 -05:00
Asher
216652fb31 Merge pull request #1446 from maksimr/fix-apple-touch-icon
Returns back apple touch icon
2020-03-25 14:12:44 -05:00
Asher
0f066d30b4 Add data-cfasync="false" to script tags
This prevents Cloudflare's Rocket Loader from acting on them.

Fixes #1451.
2020-03-25 14:04:36 -05:00
Asher
d1687c1533 Catch error when SSH server fails to start 2020-03-24 17:38:46 -05:00
Asher
f5f29c0120 Set GitHub token when installing VS Code deps
Should help with the ripgrep ratelimit issues.
2020-03-24 16:05:07 -05:00
Maksim Ryzhikov
8a6faa39c9 Remove duplication in dependencies 2020-03-24 09:43:14 +03:00
Maksim Ryzhikov
5887c1d339 Returns back apple touch icon 2020-03-23 14:44:31 +03:00
Maksim Ryzhikov
664ef17af8 Fix documentation for build process 2020-03-23 12:26:54 +03:00
Anmol Sethi
004004c047 Merge pull request #1432 from onilton/patch-1
Add link to contributing
2020-03-19 20:47:10 -04:00
Onilton Maciel
09db0ffad5 Add link to contributing 2020-03-19 18:53:31 -03:00
Asher
a349ea8ff9 Merge pull request #1421 from cdr/dependabot/npm_and_yarn/acorn-5.7.4
Bump acorn from 5.7.3 to 5.7.4
2020-03-19 13:17:37 -05:00
Asher
cfebf2c67f Removed unused CSS 2020-03-17 10:36:19 -05:00
dependabot[bot]
ddd44999c6 Bump acorn from 5.7.3 to 5.7.4
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-16 22:34:51 +00:00
Asher
89d78a5921 Encode query params from open dialog
Fixes #1424.
2020-03-16 15:19:06 -05:00
Asher
99dd2db97c Remove open in desktop button 2020-03-16 15:14:55 -05:00
Asher
b52fbb4cb9 Catch error when openssl isn't installed 2020-03-16 15:14:54 -05:00
Will O'Beirne
3463d56114 SSH server & endpoint 2020-03-16 15:14:53 -05:00
Asher
5f63d2b822 Fix custom socket path 2020-03-16 15:14:52 -05:00
Asher
db4a4f0f50 Don't ignore scripts for code-server prod yarn 2020-03-16 15:14:51 -05:00
Asher
d192726e80 Simplify dashboard 2020-03-16 15:14:50 -05:00
Asher
d832f61d5b Make client-side extensions work at any base 2020-03-16 12:04:09 -05:00
Asher
88f4b986c5 Remove our env vars from the shell
This enables developing code-server in code-server.
2020-03-16 11:01:46 -05:00
Asher
aeb6261189 Update VS Code to 1.43.0 2020-03-13 17:42:10 -05:00
Asher
6cb228037b Add base path to update endpoint from VS Code
This will make it work regardless of what the current URL happens to be.

Also move the telemetry setting into the options since we might as well
make use of it seeing as how we have to parse it for the base path
anyway.
2020-03-13 16:44:56 -05:00
Asher
a00fa85d77 Qualify extensions in help output as VS Code extensions
Also add a description for uninstall-extension and force.
2020-03-13 13:23:30 -05:00
Asher
57de78e12a Add show-versions flag and add help for list-extensions
Closes #1417.
2020-03-13 13:17:59 -05:00
Asher
2342443368 Set telemetry setting based on disable-telemetry flag
By design the disable-telemetry flag does not affect extension
telemetry, only the setting does, so disabling the setting when the flag
is set should cause extensions to also stop sending telemetry.

Fixes #1116.
2020-03-13 12:36:57 -05:00
Asher
26647c54c9 Restore old folder query parameter behavior
Fixes #1351.
2020-03-11 16:06:32 -05:00
Asher
253cf1c438 Remove unnecessary return types 2020-03-11 16:06:32 -05:00
Anmol Sethi
f6a5eaa965 Merge pull request #1406 from allanbowe/patch-1
chore: update README link (as no longer redirects)
2020-03-09 22:04:23 -04:00
Allan Bowe
f83a57a010 chore: update README link (as no longer redirects) 2020-03-09 20:40:41 +01:00
Anmol Sethi
92cec80b0e Merge pull request #1400 from zuchka/patch-1
verb agreement typo in FAQ
2020-03-07 22:10:21 -05:00
Anmol Sethi
1f601f27a2 Merge pull request #1399 from SuperSandro2000/docker
Optimize release Docker Image
2020-03-07 21:04:30 -05:00
Matt Abrams
71f1291623 verb agreement typo in FAQ
dear owners,

this is a simple copy-editing pull request. The issue regards verb-agreement. Thank you for your time,
best,

Dr. Matthew Jeffrey Abrams
2020-03-06 20:29:09 -05:00
Sandro Jäckel
9b07078b47 Combine two RUNs 2020-03-07 00:06:42 +01:00
Sandro Jäckel
8433a3d081 Combine all apt-get commands to really delete the cache from all layers 2020-03-07 00:00:58 +01:00
Asher
c8269fb54d Add exec to startup script
Removes an extra process. See #1388.
2020-03-06 10:31:16 -06:00
Asher
0b9a478289 Add connection type to websocket query parameters
This allows external services to distinguish between them.
2020-03-05 15:49:37 -06:00
Asher
c7e6e58387 Output newlines in CI immediately
Perhaps this is causing the output buffering issue with the arm builds.
2020-03-05 11:39:08 -06:00
Asher
8c515029fd Don't overwrite releases
This makes them drafts again which is not what I wanted.
2020-03-05 11:18:05 -06:00
Asher
1f6ff2f763 Show --install-extension in help menu
Also add --force so extensions can be updated without prompts.

Closes #1392.
2020-03-05 10:42:49 -06:00
Asher
04542c99fd Resolve passed-in directory
Makes relative paths work correctly.
2020-03-05 10:38:13 -06:00
Asher
8c47ba255a Preserve current working directory
Fixes #1388.
2020-03-05 10:26:14 -06:00
Asher
4e6f6bc2cc Fix .zip uploading to gcs as .tar.gz 2020-03-04 16:52:28 -06:00
Asher
7c65a54fcf Overwrite releases 2020-03-04 16:41:05 -06:00
Asher
1f43a673df Always build extensions on CI 2020-03-04 16:07:26 -06:00
Asher
744327ffd4 Update release dockerfile to use a symlink 2020-03-04 16:02:25 -06:00
Asher
a442d3e3f9 Make symlinking the entry script work 2020-03-04 15:22:32 -06:00
Asher
3e8a6f93a4 Build arm64 without travis_wait 2020-03-04 14:18:05 -06:00
Asher
308a84e6ec Fix centos image for arm64 2020-03-04 13:12:03 -06:00
Asher
cc139acfd1 Cache built-in extensions 2020-03-03 17:04:42 -06:00
Asher
32f8f481b6 Use Centos 7 for building
This will bring the libc requirements back down.
2020-03-03 16:19:51 -06:00
Asher
ec55ed39ee Fix json key decoding on Mac
It seems the short flag in MacOS is -D, not -d. The long flag is the
same. There are no other options with -d so I'm not sure why.
2020-03-03 16:01:22 -06:00
Asher
ee4b939efa Fix zip step for Darwin 2020-03-03 15:07:58 -06:00
Asher
538e8d8085 Store gcs key in Travis settings
Instead of encrypted in the repository.
2020-03-03 15:06:36 -06:00
Asher
2c4ca14d53 Merge pull request #1338 from cdr/restructure 2020-03-03 13:25:08 -06:00
Asher
8d934be6dc Elaborate what won't work over an insecure domain
Closes #997.
2020-03-03 13:10:32 -06:00
Kyle Carberry
c54450941c Update favicon 2020-03-03 02:25:53 +00:00
Asher
e0e019fbd5 Fix mime type for compressed client-side extensions
In Firefox using the gzip mime type will (probably correctly) cause it
not to decompress while in Chromium it still will (incorrectly).
2020-03-02 18:24:35 -06:00
Asher
77af2a5b0e Fix worker require paths when behind proxy 2020-03-02 18:04:27 -06:00
Asher
ecac0dd751 Handle unexpected string errors
Looks like sometimes VS Code throws strings. For example if ifconfig is
missing.
2020-03-02 17:22:23 -06:00
Asher
79b4c64a03 Add example gif 2020-03-02 15:13:39 -06:00
Asher
ccd01c49b9 Integrate update notifications into VS Code 2020-03-02 15:01:24 -06:00
Asher
069c5230cd Move VS Code to the root 2020-03-02 12:55:34 -06:00
Asher
c146457de4 Add recents section 2020-03-02 11:52:39 -06:00
Asher
88cab27165 Compress when sending client-side extension tars 2020-02-28 14:25:28 -06:00
Asher
a8914b025f Output code-server version on startup 2020-02-28 13:30:21 -06:00
Asher
0f87798ed6 Don't write bad password back out to input
Closes #1379.
2020-02-28 10:49:43 -06:00
Asher
963ebaca5b Register a service worker
To make installing as a PWA possible. Fixes #1181.
2020-02-27 16:37:00 -06:00
Kyle Carberry
eef2ed0e78 Update PWA icons 2020-02-27 20:43:31 +00:00
Asher
0e3720169f Add spacing between items
Also fix padding not being respected when blocks exceed container
height.
2020-02-27 12:58:40 -06:00
Asher
21cfeb9da0 Add the ability to kill running VS Code instance 2020-02-27 12:04:23 -06:00
Asher
fd65cadaea Upload into releases directory 2020-02-27 11:21:40 -06:00
Asher
70ad2354bb Remove gif link for now 2020-02-27 10:57:39 -06:00
Asher
01710cf6ff Update download instructions
We aren't providing binaries any longer.
2020-02-26 15:42:31 -06:00
Asher
b5c425b3a6 Major version -> 3.0.0 2020-02-26 15:09:56 -06:00
Asher
b00f6bf078 Improve error display when VS Code fails to load
Was looking a bit janky with the style changes.
2020-02-26 14:57:07 -06:00
Asher
a2639ac617 Add yarn vscode step to build instructions 2020-02-26 14:48:08 -06:00
Asher
07fcf1be7a Merge branch 'master' into restructure 2020-02-26 14:42:33 -06:00
Asher
75ca5b2b0b Add gcs upload to CI 2020-02-26 14:25:14 -06:00
Asher
082f25faf1 Add a note about external authentication
Closes #1369.
2020-02-26 14:25:13 -06:00
Asher
595ce6f5e3 Update styling
Just sort of winging it.
2020-02-26 14:25:12 -06:00
Anmol Sethi
b1760c8d29 Fix clean.sh 2020-02-25 21:30:58 -05:00
Asher
c870398c86 Switch to loose files
For #1306.
2020-02-25 18:23:35 -06:00
Asher
f76c809f7d Fix workspace storage creation
Fixes #1308.
2020-02-25 12:47:22 -06:00
Asher
4c6e4bedeb Fix port being randomized
Also make it a number.
2020-02-24 16:49:10 -06:00
Asher
04e449c546 Require cert-key with cert
Fixes #1312.
2020-02-24 15:01:59 -06:00
Anmol Sethi
c147711ade Remind user that generated password is in logs
Closes #446
2020-02-21 15:22:13 -05:00
Anmol Sethi
bd7583a254 Obey process.env.PORT 2020-02-21 14:49:58 -05:00
Asher
33b3523bf4 Prefer command-line directory to last visited directory
If you want to use the last visited directory you should omit the
directory from the command line.

Fixes #1132.
2020-02-21 12:45:57 -06:00
Asher
cf0f11105b Handle upgrade from binary to loose files
As best we can, anyway.
2020-02-21 12:32:58 -06:00
Anmol Sethi
9b7a203fe5 Make login page pretty 2020-02-20 20:08:25 -05:00
Asher
e44ac0a30e Use last positional argument as working directory
Instead of the first.
2020-02-20 18:48:17 -06:00
Asher
319cd3f7ab Make updating work for both binary and loose releases 2020-02-20 18:48:16 -06:00
Anmol Sethi
815dc06118 Use npm rebuild instead of yarn --no-scripts in vscode.sh 2020-02-20 19:11:01 -05:00
Anmol Sethi
3a2644a2bc Fix vscode.sh 2020-02-20 18:36:38 -05:00
Anmol Sethi
65690fca65 Fix CSP for Safari 2020-02-20 18:24:32 -05:00
Anmol Sethi
25288b1afd Cleanup FAQ and mention GPG/SSH forwarding 2020-02-20 17:45:04 -05:00
Asher
288e794c99 Update locale file location
Should make language packs work again.
2020-02-20 12:52:23 -06:00
Asher
c567a06ff5 Fix HTTPS redirects and TLS sockets
It still won't work behind a base path, but if you're using a reverse
proxy you can just redirect to HTTPS yourself. And should probably
handle TLS termination there too.

For sockets I just needed to add back the proxy call.
2020-02-19 17:59:12 -06:00
Asher
e5b68a8f4c Switch to new extensions API 2020-02-19 14:36:48 -06:00
Asher
51a5c77cb8 Add binary extraction
I temporarily removed this during the refactor so it needed to be added
back. This time I bundled it with the nbin loader code since it's all
related (will also make it easier to remove).
2020-02-19 14:15:01 -06:00
Anmol Sethi
b9e7a3daa7 Add back .editorconfig 2020-02-19 14:22:14 -05:00
Asher
80b2d9481f Don't display stack trace for cli parse failures
Just display the error message and exit. The stack trace isn't necessary
(since it's likely user error) and is potentially confusing.
2020-02-19 11:15:39 -06:00
Asher
0e2eaa9b34 Add valid values for --log 2020-02-19 11:11:29 -06:00
Asher
0263188431 Handle --long=value format in the cli parser 2020-02-19 10:54:23 -06:00
Anmol Sethi
fa30639784 Update issue template 2020-02-19 00:58:28 -05:00
Anmol Sethi
015b8dcf13 Merge branch 'ensure-release' into restructure 2020-02-19 00:28:21 -05:00
Anmol Sethi
9f3240346c Doc fixes 2020-02-19 00:27:02 -05:00
Anmol Sethi
b76364db31 Flesh out FAQ 2020-02-18 23:31:48 -05:00
Anmol Sethi
a065c12e83 CI Fixes 2020-02-18 23:31:40 -05:00
Anmol Sethi
76831f11fc Merge branch 'fix-ci' into restructure 2020-02-18 19:07:34 -05:00
Anmol Sethi
b6aa0cbcba Fix docs 2020-02-18 19:07:01 -05:00
Anmol Sethi
5681c87e33 Fix bugs in CI 2020-02-18 19:06:35 -05:00
Asher
46d6e17508 Prepare for release
- Add VS Code icon
- Trim dashboard to just display dedicated VS Code section
- Version was getting unset during build
- Add back nbin shim which I temporarily took out earlier
- Update tests for log level env var changes
2020-02-18 17:31:23 -06:00
Anmol Sethi
1aaa53622d Pass through travis tag in run.sh 2020-02-18 16:34:28 -05:00
Asher
bdb189a9bb Allow https images
Fixes extension icons not loading.
2020-02-18 14:57:12 -06:00
Asher
8793110941 Implement endpoint for getting recent directories 2020-02-18 14:13:22 -06:00
Asher
16bcf59cb0 Merge pull request #1357 2020-02-18 13:33:06 -06:00
Asher
f6b092b12d Merge branch 'restructure' 2020-02-18 13:30:37 -06:00
Asher
d47591e253 Inject base path into manifest
Might fix #1181, although not for the reasons I initially
thought (because the URLs are resolved from the manifest path, not the
path of the current page). This should ensure that the URLs used by the
manifest are always correct regardless of the manifest's path.
2020-02-18 13:01:18 -06:00
Anmol Sethi
1a91588c42 Add docker image pushing 2020-02-18 13:28:13 -05:00
Asher
39a57700bc Enable access to vscode cli 2020-02-18 12:24:12 -06:00
Anmol Sethi
1a54f6b7ef Merge remote-tracking branch 'origin/restructure' into anmol-restructure 2020-02-18 12:52:29 -05:00
Anmol Sethi
eb3cf303ad Add back travis since github actions is trash 2020-02-18 11:32:57 -05:00
Anmol Sethi
0d31a51eeb Add github release creation 2020-02-15 16:20:41 -05:00
Anmol Sethi
61d1af0413 Add macOS release step 2020-02-14 21:16:23 -05:00
Anmol Sethi
4aa15401c3 Format and lint 2020-02-14 20:00:19 -05:00
Anmol Sethi
80b1b1b672 Shake CI and docs up 2020-02-14 19:46:17 -05:00
Asher
0ec83f8736 Check updates daily instead of every time
Also add a way to force a check.
2020-02-14 16:46:22 -06:00
Asher
db54f78e8e Implement automatic updates 2020-02-14 15:58:39 -06:00
Asher
b8fa7da972 Simplify frontend
Just a login form and a list of applications. No modals or anything like
that.
2020-02-13 20:10:14 -06:00
Asher
bf1be16d11 Fix base path
Now it should work whether you have a trailing slash or not.
2020-02-13 12:41:58 -06:00
Asher
cc79edb312 Fix duplicate files opening with folder parameter
Reworked from d574012871 to fit in the
new structure.
2020-02-13 12:04:22 -06:00
Asher
ac4f2b8215 Update VS Code to 1.42.0 2020-02-13 11:55:15 -06:00
Asher
c8fc54bfb1 Update VS Code version for CI 2020-02-12 18:03:53 -06:00
Asher
d574012871 Fix duplicate files opening with folder parameter
When using a query parameter without a scheme, the scheme defaults to
`file`. This results in the files in the explorer being technically
different from the file picker files because they are file:// instead of
vscode-remote://, causing the same file to open twice and causing
numerous issues.

Normally the file explorer wouldn't even load at all in this case but we
provide a file service for file:// URLs as a failsafe for certain files
that wouldn't load correctly in the past. These files load fine now
using the vscode-remote scheme, so I'm also removing that service.

Related: #1351.
Fixes #1294.
2020-02-12 16:57:44 -06:00
Asher
250a54220c Update VS Code to 1.42.0 2020-02-12 14:31:34 -06:00
Asher
5baf16622f Fix VS Code product configuration not loading 2020-02-07 16:47:51 -06:00
Asher
256419004d Implement cli parser 2020-02-07 14:43:08 -06:00
Anmol Sethi
6a693e7181 Merge pull request #1346 from SuperSandro2000/patch-1
Dockerfile: Combine two runs
2020-02-06 16:10:50 -06:00
Sandro
b38cfa473e Dockerfile: Combine two runs 2020-02-06 23:05:40 +01:00
Asher
26f8216ec8 Un-nest a switch 2020-02-06 14:00:38 -06:00
Asher
63f3c04c57 Move user data directory logic out of patch 2020-02-06 13:12:00 -06:00
Asher
8a0f1d846e Move start path logic out of patch and fix it 2020-02-06 12:29:38 -06:00
Asher
efaeb3b110 Add hidden username field for accessibility 2020-02-06 10:26:53 -06:00
Asher
6cebfa469d Generalize initial app logic 2020-02-05 18:47:00 -06:00
Asher
205775ac97 Only serve HTML on specific index.html requests
Otherwise there is risk of an infinite loop through the iframe where the
fallback keeps loading the root HTML which itself has an iframe...
2020-02-05 17:45:24 -06:00
Asher
4cc181cedc Make routing base path agnostic 2020-02-05 17:38:21 -06:00
Asher
a149c5fc60 Pass arguments to code-server during watch 2020-02-05 14:48:15 -06:00
Asher
6e809b6a31 Handle when VS Code fails to load
This is mostly for development where VS Code might not have finished
compiling yet.
2020-02-05 14:23:42 -06:00
Asher
7c6fe56043 Add logo to background 2020-02-05 13:30:29 -06:00
Asher
8cc11d1688 Improve routing 2020-02-05 13:07:07 -06:00
Asher
dbc5c065f8 Improve HTTP provider registration 2020-02-04 16:55:27 -06:00
Asher
4a54e914fc Remove 32 bit arm builds from CI 2020-02-04 16:01:00 -06:00
Asher
b30aefcfb1 Add linting steps and improve testing steps 2020-02-04 15:42:55 -06:00
Asher
b29346ecdf Implement new structure 2020-02-04 14:31:44 -06:00
Anmol Sethi
ef8da3864f Merge pull request #1336 from cdr/revert-1334-patch-1
Revert "Decomission Travis"
2020-02-04 11:35:37 -06:00
Anmol Sethi
108eb297d8 Revert "Decomission Travis" 2020-02-04 11:35:19 -06:00
Anmol Sethi
19f3acd9f0 Merge pull request #1334 from sr229/patch-1
Decomission Travis
2020-02-04 11:35:12 -06:00
Dean Sheather
3ee6b0ff0b Automatically push releases to GCS (#1318) 2020-02-04 11:32:45 -06:00
Anmol Sethi
e270f7da1b Merge pull request #1335 from cdr/revert-1225-master
Revert "Updates manifest.json"
2020-02-04 11:30:14 -06:00
Anmol Sethi
e6117decd0 Revert "Set display property in manifest to fullscreen"
This reverts commit c7127cb248.
2020-02-04 11:30:00 -06:00
TheHllm
c7127cb248 Set display property in manifest to fullscreen 2020-02-04 11:23:21 -06:00
Ben Potter
50234e5f04 Reflects new location of vscodeVersion (#1327) 2020-02-04 11:10:32 -06:00
Ayane Satomi
5f562dc113 Decomission Travis
We won't need Travis from now on since it's only purpose is to do Mac builds, which no one uses anymore.
2020-02-04 20:43:54 +08:00
Anmol Sethi
bb8bad49dc Remvoe question issue template
Closes #1331
2020-02-03 11:35:18 -06:00
Anmol Sethi
a674d882bf Remove buggy -v flag from README.md docker run
Closes #1270

Will describe in FAQ how to keep state.
2020-01-27 12:17:59 -06:00
Asher
f51e045cd5 Use the nbin centos container to build again
This will put the glibc requirement back down to what it used to be.
2020-01-17 16:27:36 -06:00
Asher
8122b7f69e Remove unused upload service
No longer needed since VS Code has their own now.
2020-01-17 12:23:36 -06:00
Asher
25f18beda4 Fix version test 2020-01-16 18:09:19 -06:00
Asher
7e7923706f Fix version generated from Git tag 2020-01-16 17:59:11 -06:00
Asher
ae35673489 Use custom Yarn cache directory
Makes it easier to upload and restore.
2020-01-16 15:39:44 -06:00
Asher
23f142fdc6 Cache Yarn cache 2020-01-16 15:23:25 -06:00
Asher
101139fabf Fix Drone CI releases
Also skip the 32 bit arm releases since they don't currently build
anyway.
2020-01-16 14:53:59 -06:00
Asher
e2d354c8f2 Move manifest icon to the root as well 2020-01-16 12:11:56 -06:00
Asher
7c178805ea Add comment about the manifest's served location
Also for #1278.
2020-01-16 11:44:17 -06:00
Asher
45f70e741f Move manifest to the root
Fixes #1278.
2020-01-16 11:36:17 -06:00
Asher
1474a82c7d Add insecure access notification 2020-01-16 11:15:22 -06:00
Asher
d97feca3ba Add code-server version to the about dialog 2020-01-15 18:02:19 -06:00
Asher
b2669e78bf Implement ExtHostStoragePaths for the browser
This appears to make vscodevim work again.
2020-01-15 17:13:06 -06:00
Asher
66ee6e8201 Ignore 32 bit arm failures for now
Seems we are running out of memory.
2020-01-15 13:48:27 -06:00
Asher
62f050fda7 Add a simple test 2020-01-15 13:22:45 -06:00
Asher
57425377e5 Use CI dockerfile for pushing Docker image 2020-01-15 13:22:45 -06:00
Asher
174cb2f8a9 Remove unused Docker step from CI script 2020-01-15 13:22:42 -06:00
Asher
42bddce21f Add defaults for environment variables
So we don't have to keep setting them for each CI and every single step
since there doesn't seem to be a way to share them between steps in
Drone.
2020-01-15 13:21:58 -06:00
Asher
f2a15795a1 Use draft releases for Drone
This gives us a chance to review it and add notes.
2020-01-15 13:21:58 -06:00
Asher
6dd5e515c5 Travis release on tags only and remove Docker push
The manual tagging is necessary to sync up the releases of the two
different CIs.
2020-01-15 13:21:55 -06:00
Asher
92da02ef3e Add Drone CI caching 2020-01-15 13:20:58 -06:00
Ayane Satomi
3ce7129492 Drone CI migration (#1261) 2020-01-15 13:14:05 -06:00
Asher
336ee28888 Update Node to 12.14.0 2020-01-08 16:30:44 -06:00
Asher
3f2240ab65 Update logger 2020-01-08 16:30:34 -06:00
Asher
1087037728 Don't push latest and v2 Docker tags automatically
We should only push those when the version is confirmed to work.
2020-01-08 15:05:12 -06:00
Asher
1959d82912 Increase cache timeout
The Mac build seems to be terminated due to a timeout during the caching
stage.
2020-01-08 13:20:50 -06:00
Asher
8024144381 Update VS Code to 1.41.1 2020-01-07 18:27:41 -06:00
Asher
6a1dcab7a6 Update nbin
Should finally be able to build with Node v12 now.
2020-01-07 18:27:28 -06:00
Asher
e6d1f2a7c8 Update VS Code to 1.41.0 2019-12-16 16:52:29 -06:00
Asher
44c4722edf Fix data directory path in Dockerfile 2019-12-10 12:06:52 -06:00
Asher
e5fc63f2c8 Fix accessing manifest behind basic auth
Apparently the manifest spec doesn't include sending credentials in an
attempt to be secure by default.

Fixes #1212.
2019-12-09 11:25:59 -06:00
Asher
015a99e87d Always install VS Code dependencies
This fixes the case where the script is killed before all the
dependencies were fully installed.
2019-12-09 10:55:24 -06:00
Simen Eriksen
884491d72b Update Dockerfile to fix EACCES issue on mount (#1191)
https://github.com/cdr/code-server/issues/1188 
Fixes issue with permissions mounting in directories in the container. Folders are generated by root causing issues when the container user "coder" wants to create sub-folders. This fix solves it, at least on Crostini (ChromeOS)
2019-12-05 13:38:03 -06:00
Asher
e14362f322 Pass along Node options 2019-11-14 17:20:23 -06:00
Asher
917aa48072 Update enterprise link
Fixes #1172.
2019-11-14 11:16:08 -06:00
Asher
938c6ef829 Update fail2ban configuration
Fixes #1177.
2019-11-14 11:14:27 -06:00
Sandro
0add01d383 Delete apt lists from final image (#1174) 2019-11-14 11:12:21 -06:00
Asher
2018024810 Hash password
Fixes issues with unexpected characters breaking things when setting the
cookie (like semicolons).

This change as-is does not affect the security of code-server
itself (we've just replaced the static password with a static hash) but
if we were to add a salt in the future it would let us invalidate keys
by rehashing with a new salt which could be handy.
2019-11-07 15:57:57 -06:00
Asher
a1d6bcb8e5 Handle cookies more robustly
If you visit /login/ instead of /login the cookie will be set at /login
instead of / which means the cookie can't be read at the root. It will
redirect to the login page which *can* read the cookie at /login and
redirect back resulting in an infinite loop.

The previous solution relied on setting the cookie at / (any invalid
value works) which then overrode the login page cookie since
parseCookies only kept a single value. So the login page would see the
same cookie the root was seeing and not redirect back. However, that
behavior depends on the cookies being in the right order which I'm not
sure is guaranteed.

This new method tests all available cookies and always sets the cookie
so the root path will be able to read it in case the login page is
seeing a cookie the root can't.

It also goes a step further and explicitly sets the path on the cookie
which fixes the case where there is a permanent misconfiguration
redirecting /login to /login/. Otherwise the cookie would continually be
set on /login only and you'd have another loop. It also means you only
need to delete one cookie to log out.

Lastly add some properties to make the cookies a bit more secure.
2019-11-07 13:36:18 -06:00
ecrode
727ac6483b Clear password when redirecting to login
Should prevent endless redirects when the cookie is set on a different path or domain (like with a dot prefix).
2019-11-07 11:38:10 -06:00
Asher
2c15c09fc0 Add missing telemetry option 2019-11-06 15:47:34 -06:00
Asher
2ad2582cc0 Minor readme updates and fixes 2019-11-05 13:49:18 -06:00
Asher
cee0ac213c Fix error activating extensions on insecure domains
Doesn't affect Firefox but it does affect other browsers.

Fixes #1136.
2019-11-04 17:10:00 -06:00
Asher
780a673017 Add meta tag to allow full screen app on iOS
Fixes #933.
2019-11-04 16:01:01 -06:00
Asher
af71203955 Fix relaunching during an update 2019-11-01 10:51:23 -05:00
Asher
fc3acfabb2 Fix update check 2019-10-30 17:35:50 -05:00
144 changed files with 15374 additions and 6995 deletions

View File

@@ -1,12 +1,2 @@
Dockerfile
build
deployment
doc
.github
.gitignore
.node-version
.travis.yml
LICENSE
README.md
node_modules
release
**
!release

6
.editorconfig Normal file
View File

@@ -0,0 +1,6 @@
root = true
[*]
indent_style = space
trim_trailing_whitespace = true
indent_size = 2

23
.eslintrc.yaml Normal file
View File

@@ -0,0 +1,23 @@
parser: "@typescript-eslint/parser"
env:
browser: true
es6: true # Map, etc.
mocha: true
node: true
parserOptions:
ecmaVersion: 2018
sourceType: module
extends:
- eslint:recommended
- plugin:@typescript-eslint/recommended
- plugin:import/recommended
- plugin:import/typescript
- plugin:prettier/recommended
- prettier # Removes eslint rules that conflict with prettier.
- prettier/@typescript-eslint # Remove conflicts again.
rules:
# For overloads.
no-dupe-class-members: off

3
.github/CODEOWNERS vendored
View File

@@ -1,2 +1 @@
* @code-asher @kylecarbs
Dockerfile @nhooyr
* @code-asher @nhooyr

View File

@@ -1,22 +0,0 @@
---
name: Bug Report
about: Report problems and unexpected behavior.
title: ''
labels: 'bug'
assignees: ''
---
<!-- Please search existing issues to avoid creating duplicates. -->
<!-- All extension-specific issues should be created with the `Extension Bug` template. -->
- `code-server` version: <!-- The version of code-server -->
- OS Version: <!-- OS version, cloud provider, -->
## Description
<!-- Describes the problem here -->
## Steps to Reproduce
1. <!-- step 1: click ... -->
1. <!-- step 2: ... -->

View File

@@ -1,22 +0,0 @@
---
name: Extension Bug
about: Report problems and unexpected behavior with extensions.
title: ''
labels: 'extension-specific'
assignees: ''
---
<!-- Please search existing issues to avoid creating duplicates. -->
- `code-server` version: <!-- The version of code-server -->
- OS Version: <!-- OS version, cloud provider, -->
- Extension: <!-- Link to extension -->
## Description
<!-- Describes the problem here -->
## Steps to Reproduce
1. <!-- step 1: click ... -->
1. <!-- step 2: ... -->

View File

@@ -1,11 +0,0 @@
---
name: Feature Request
about: Suggest an idea for this project.
title: ''
labels: 'feature'
assignees: ''
---
<!-- Please search existing issues to avoid creating duplicates. -->
<!-- Describe the feature you'd like. -->

View File

@@ -1,17 +0,0 @@
---
name: Question
about: Ask a question.
title: ''
labels: 'question'
assignees: ''
---
<!-- Please search existing issues to avoid creating duplicates. -->
## Description
<!-- A description of the the question. -->
## Related Issues
<!-- Any issues related to your question. -->

6
.github/issue_template.md vendored Normal file
View File

@@ -0,0 +1,6 @@
<!--
Please file all questions and support requests at https://www.reddit.com/r/codeserver/
The issue tracker is only for bugs.
Please search for existing issues before filing.
-->

View File

@@ -1,6 +1,4 @@
<!-- Please answer these questions before submitting your PR. Thanks! -->
### Describe in detail the problem you had and how this PR fixes it
### Is there an open issue you can link to?
<!--
Please link to the issue this PR solves.
If there is no existing issue, please first create one unless the fix is minor.
-->

9
.gitignore vendored
View File

@@ -1,5 +1,8 @@
node_modules
*.tsbuildinfo
.cache
build
release
dist*
out*
release*
node_modules
binaries
source

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "lib/vscode"]
path = lib/vscode
url = https://github.com/microsoft/vscode

View File

@@ -1 +0,0 @@
10.16.0

1
.npmrc
View File

@@ -1 +0,0 @@
scripts-prepend-node-path=true

4
.prettierrc.yaml Normal file
View File

@@ -0,0 +1,4 @@
printWidth: 120
semi: false
trailingComma: all
arrowParens: always

2
.stylelintrc.yaml Normal file
View File

@@ -0,0 +1,2 @@
extends:
- stylelint-config-recommended

View File

@@ -1,73 +1,58 @@
language: node_js
node_js:
- 10.16.0
services:
- docker
language: minimal
before_install:
- export MAJOR_VERSION="2"
- export VSCODE_VERSION="1.39.2"
- export VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER"
- export TAG="$VERSION-vsc$VSCODE_VERSION"
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then export MINIFY="true"; fi
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then export PACKAGE="true"; fi
# Don't build on tags because we'll already have built the commit.
jobs:
include:
- name: "Linux build"
os: linux
dist: trusty
env: TARGET="linux" PUSH_DOCKER="true"
- name: Test
if: tag IS blank
script: scripts/ci.bash
- name: "Alpine build"
os: linux
dist: trusty
env: TARGET="alpine"
if: tag IS blank
script: scripts/ci.bash
- name: "MacOS build"
script: ./ci/image/run.sh "yarn && git submodule update --init && yarn vscode:patch && ./ci/ci.sh"
deploy: null
- name: Linux Release
if: tag IS present
script:
- travis_wait 60 ./ci/image/run.sh "yarn && yarn vscode && ci/release.sh && ./ci/build-test.sh"
- ./ci/release-image/push.sh
- name: Linux ARM64 Release
if: tag IS present
script:
- ./ci/image/run.sh "yarn && yarn vscode && ci/release.sh && ./ci/build-test.sh"
- ./ci/release-image/push.sh
arch: arm64
- name: MacOS Release
if: tag IS present
os: osx
if: tag IS blank
script: travis_wait 60 scripts/ci.bash
git:
depth: 3
language: node_js
node_js: 12
script: yarn && yarn vscode && travis_wait 60 ci/release.sh && ./ci/build-test.sh
before_deploy:
- echo "$TAG" "$TRAVIS_COMMIT"
- git config --local user.name "$USER_NAME"
- git config --local user.email "$USER_EMAIL"
- if ! git tag "$TAG" "$TRAVIS_COMMIT" ; then echo "$TAG already exists"; fi
- if [[ -n "$PUSH_DOCKER" ]] ; then echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin ; fi
- echo "$JSON_KEY" | base64 --decode > ./ci/key.json
deploy:
- provider: releases
file_glob: true
edge: true
draft: true
tag_name: "$TAG"
target_commitish: "$TRAVIS_COMMIT"
name: "$TAG"
skip_cleanup: true
api_key:
secure: YL/x24KjYjgYXPcJWk3FV7FGxI79Mh6gBECQEcdlf3fkLEoKFVgzHBoUNWrFPzyR4tgLyWNAgcpD9Lkme1TRWTom7UPjXcwMNyLcLa+uec7ciSAnYD9ntLTpiCuPDD1u0LtRGclSi/EHQ+F8YVq+HZJpXTsJeAmOmihma3GVbGKSZr+BRum+0YZSG4w+o4TOlYzw/4bLWS52MogZcwpjd+hemBbgXLuGU2ziKv2vEKCZFbEeA16II4x1WLI4mutDdCeh7+3aLzGLwDa49NxtsVYNjyNFF75JhCTCNA55e2YMiLz9Uq69IXe/mi5F7xUaFfhIqqLNyKBnKeEOzu3dYnc+8n3LjnQ+00PmkF05nx9kBn3UfV1kwQGh6QbyDmTtBP07rtUMyI14aeQqHjxsaVRdMnwj9Q2DjXRr8UDqESZF0rmK3pHCXS2fBhIzLE8tLVW5Heiba2pQRFMHMZW+KBE97FzcFh7is90Ait3T8enfcd/PWFPYoBejDAdjwxwOkezh5N5ZkYquEfDYuWrFi6zRFCktsruaAcA+xGtTf9oilBBzUqu8Ie+YFWH5me83xakcblJWdaW/D2rLJAJH3m6LFm8lBqyUgDX5t/etob6CpDuYHu5D1J3XINOj/+aLAcadq6qlh70PMZS3zYffUu3JlzaD2amlSHIT8b5YXFc=
overwrite: true
tag_name: $TRAVIS_TAG
target_commitish: $TRAVIS_COMMIT
name: $TRAVIS_TAG
file:
- release/*.tar.gz
- release/*.zip
on:
repo: cdr/code-server
branch: master
- provider: script
skip_cleanup: true
script: docker build -f ./scripts/ci.dockerfile -t codercom/code-server:"$TAG" -t codercom/code-server:v2 -t codercom/code-server . && docker push codercom/code-server:"$TAG" && docker push codercom/code-server:v2 && docker push codercom/code-server
tags: true
- provider: gcs
edge: true
bucket: "codesrv-ci.cdr.sh"
upload_dir: "releases"
key_file: ./ci/key.json
local_dir: release-upload
on:
repo: cdr/code-server
branch: master
condition: -n "$PUSH_DOCKER"
tags: true
# TODO: The gcs provider fails to install on arm64.
condition: $TRAVIS_CPU_ARCH = amd64
cache:
timeout: 600
yarn: true
directories:
- source
- lib/vscode/.build/extensions

View File

@@ -1,61 +0,0 @@
FROM node:10.16.0
ARG codeServerVersion=docker
ARG vscodeVersion
ARG githubToken
# Install VS Code's deps. These are the only two it seems we need.
RUN apt-get update && apt-get install -y \
libxkbfile-dev \
libsecret-1-dev
# Ensure latest yarn.
RUN npm install -g yarn@1.13
WORKDIR /src
COPY . .
RUN yarn \
&& MINIFY=true GITHUB_TOKEN="${githubToken}" yarn build "${vscodeVersion}" "${codeServerVersion}" \
&& yarn binary "${vscodeVersion}" "${codeServerVersion}" \
&& mv "/src/binaries/code-server${codeServerVersion}-vsc${vscodeVersion}-linux-x86_64" /src/binaries/code-server \
&& rm -r /src/build \
&& rm -r /src/source
# We deploy with ubuntu so that devs have a familiar environment.
FROM ubuntu:18.04
RUN apt-get update && apt-get install -y \
openssl \
net-tools \
git \
locales \
sudo \
dumb-init \
vim \
curl \
wget
RUN locale-gen en_US.UTF-8
# We cannot use update-locale because docker will not use the env variables
# configured in /etc/default/locale so we need to set it manually.
ENV LC_ALL=en_US.UTF-8 \
SHELL=/bin/bash
RUN adduser --gecos '' --disabled-password coder && \
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
USER coder
# We create first instead of just using WORKDIR as when WORKDIR creates, the
# user is root.
RUN mkdir -p /home/coder/project
WORKDIR /home/coder/project
# This ensures we have a volume mounted even if the user forgot to do bind
# mount. So that they do not lose their data if they delete the container.
VOLUME [ "/home/coder/project" ]
COPY --from=0 /src/binaries/code-server /usr/local/bin/code-server
EXPOSE 8080
ENTRYPOINT ["dumb-init", "code-server", "--host", "0.0.0.0"]

177
README.md
View File

@@ -1,4 +1,4 @@
# code-server &middot; [![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/cdr/code-server/blob/master/LICENSE) [!["Latest Release"](https://img.shields.io/github/release/cdr/code-server.svg)](https://github.com/cdr/code-server/releases/latest) [![Build Status](https://img.shields.io/travis/com/cdr/code-server/master)](https://github.com/cdr/code-server)
# code-server
`code-server` is [VS Code](https://github.com/Microsoft/vscode) running on a
remote server, accessible through the browser.
@@ -6,17 +6,17 @@ remote server, accessible through the browser.
Try it out:
```bash
docker run -it -p 127.0.0.1:8080:8080 -v "${HOME}/.local/share/code-server:/home/coder/.local/share/code-server" -v "$PWD:/home/coder/project" codercom/code-server:v2
docker run -it -p 127.0.0.1:8080:8080 -v "$PWD:/home/coder/project" codercom/code-server
```
- **Consistent environment:** Code on your Chromebook, tablet, and laptop with a
consistent dev environment. develop more easily for Linux if you have a
Windows or Mac, and pick up where you left off when switching workstations.
- **Code anywhere:** Code on your Chromebook, tablet, and laptop with a
consistent dev environment. Develop on a Linux machine and pick up from any
device with a web browser.
- **Server-powered:** Take advantage of large cloud servers to speed up tests,
compilations, downloads, and more. Preserve battery life when you're on the go
since all intensive computation runs on your server.
![Screenshot](/doc/assets/ide.gif)
![Example gif](/doc/assets/code-server.gif)
## Getting Started
@@ -25,175 +25,34 @@ docker run -it -p 127.0.0.1:8080:8080 -v "${HOME}/.local/share/code-server:/home
- 64-bit host.
- At least 1GB of RAM.
- 2 cores or more are recommended (1 core works but not optimally).
- Secure connection over HTTPS or localhost (required for service workers).
- Secure connection over HTTPS or localhost (required for service workers and
clipboard support).
- For Linux: GLIBC 2.17 or later and GLIBCXX 3.4.15 or later.
- Docker (for Docker versions of `code-server`).
### Run over SSH
Use [sshcode](https://github.com/codercom/sshcode) for a simple setup.
### Docker
See the Docker one-liner mentioned above. Dockerfile is at [/Dockerfile](/Dockerfile).
To debug Golang using the
[ms-vscode-go extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.Go),
you need to add `--security-opt seccomp=unconfined` to your `docker run`
arguments when launching code-server with Docker. See
[#725](https://github.com/cdr/code-server/issues/725) for details.
### Digital Ocean
[![Create a Droplet](./doc/assets/droplet.svg)](https://marketplace.digitalocean.com/apps/code-server?action=deploy)
[![Create a Droplet](./doc/assets/droplet.svg)](https://marketplace.digitalocean.com/apps/code-server)
### Binaries
### Releases
1. [Download a binary](https://github.com/cdr/code-server/releases). (Linux and
OS X supported. Windows coming soon)
2. Unpack the downloaded file then run the binary.
1. [Download a release](https://github.com/cdr/code-server/releases). (Linux and
OS X supported. Windows support planned.)
2. Unpack the downloaded release then run the included `code-server` script.
3. In your browser navigate to `localhost:8080`.
- For self-hosting and other information see [doc/quickstart.md](doc/quickstart.md).
- For hosting on cloud platforms see [doc/deploy.md](doc/deploy.md).
## FAQ
### Build
See
[VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
before building.
```shell
export OUT=/path/to/output/build # Optional if only building. Required if also developing.
yarn build ${vscodeVersion} ${codeServerVersion} # See travis.yml for the VS Code version to use.
# The code-server version can be anything you want.
node ~/path/to/output/build/out/vs/server/main.js # You can run the built JavaScript with Node.
yarn binary ${vscodeVersion} ${codeServerVersion} # Or you can package it into a binary.
```
## Security
### Authentication
By default `code-server` enables password authentication using a randomly
generated password. You can set the `PASSWORD` environment variable to use your
own instead or use `--auth none` to disable password authentication.
Do not expose `code-server` to the open internet without some form of
authentication.
### Encrypting traffic with HTTPS
If you aren't doing SSL termination elsewhere you can directly give
`code-server` a certificate with `code-server --cert` followed by the path to
your certificate. Additionally, you can use certificate keys with `--cert-key`
followed by the path to your key. If you pass `--cert` without any path
`code-server` will generate a self-signed certificate.
If `code-server` has been passed a certificate it will also respond to HTTPS
requests and will redirect all HTTP requests to HTTPS. Otherwise it will respond
only to HTTP requests.
You can use [Let's Encrypt](https://letsencrypt.org/) to get an SSL certificate
for free.
Do not expose `code-server` to the open internet without SSL, whether built-in
or through a proxy.
## Known Issues
- Creating custom VS Code extensions and debugging them doesn't work.
- Extension profiling and tips are currently disabled.
## Future
- **Stay up to date!** Get notified about new releases of code-server.
![Screenshot](/doc/assets/release.gif)
- Windows support.
- Electron and Chrome OS applications to bridge the gap between local<->remote.
- Run VS Code unit tests against our builds to ensure features work as expected.
## Extensions
code-server does not provide access to the official
[Visual Studio Marketplace](https://marketplace.visualstudio.com/vscode). Instead,
Coder has created a custom extension marketplace that we manage for open-source
extensions. If you want to use an extension with code-server that we do not have
in our marketplace please look for a release in the extension’s repository,
contact us to see if we have one in the works or, if you build an extension
locally from open source, you can copy it to the `extensions` folder. If you
build one locally from open-source please contribute it to the project and let
us know so we can give you props! If you have your own custom marketplace, it is
possible to point code-server to it by setting the `SERVICE_URL` and `ITEM_URL`
environment variables.
## Telemetry
Use the `--disable-telemetry` flag to completely disable telemetry. We use the
data collected to improve code-server.
See [./doc/FAQ.md](./doc/FAQ.md).
## Contributing
### Development
See
[VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
before developing.
```shell
git clone https://github.com/microsoft/vscode
cd vscode
git checkout ${vscodeVersion} # See travis.yml for the version to use.
yarn
git clone https://github.com/cdr/code-server src/vs/server
cd src/vs/server
yarn
yarn patch:apply
yarn watch
# Wait for the initial compilation to complete (it will say "Finished compilation").
# Run the next command in another shell.
yarn start
# Visit http://localhost:8080
```
If you run into issues about a different version of Node being used, try running
`npm rebuild` in the VS Code directory and ignore the error at the end from
`vscode-ripgrep`.
### Upgrading VS Code
We patch VS Code to provide and fix some functionality. As the web portion of VS
Code matures, we'll be able to shrink and maybe even entirely eliminate our
patch. In the meantime, however, upgrading the VS Code version requires ensuring
that the patch still applies and has the intended effects.
To generate a new patch, **stage all the changes** you want to be included in
the patch in the VS Code source, then run `yarn patch:generate` in this
directory.
Our changes include:
- Change the remote schema to `code-server`.
- Allow multiple extension directories (both user and built-in).
- Modify the loader, websocket, webview, service worker, and asset requests to
use the URL of the page as a base (and TLS if necessary for the websocket).
- Send client-side telemetry through the server.
- Add an upload service along with a file prefix to ignore for temporary files
created during upload.
- Make changing the display language work.
- Make it possible for us to load code on the client.
- Make extensions work in the browser.
- Fix getting permanently disconnected when you sleep or hibernate for a while.
- Make it possible to automatically update the binary.
## License
[MIT](LICENSE)
See [./doc/CONTRIBUTING.md](./doc/CONTRIBUTING.md).
## Enterprise
Visit [our enterprise page](https://coder.com/enterprise) for more information
about our enterprise offering.
## Commercialization
If you would like to commercialize code-server, please contact
contact@coder.com.
Visit [our enterprise page](https://coder.com) for more information about our
enterprise offerings.

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

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# build-test.bash -- Make sure the build worked.
# This is to make sure we don't have Node version errors or any other
# compilation-related errors.
set -euo pipefail
function main() {
cd "$(dirname "${0}")/.." || exit 1
local output
output=$(node ./build/out/node/entry.js --list-extensions 2>&1)
if echo "$output" | grep 'was compiled against a different Node.js version'; then
echo "$output"
exit 1
else
echo "Build ran successfully"
fi
}
main "$@"

372
ci/build.ts Normal file
View File

@@ -0,0 +1,372 @@
import * as cp from "child_process"
import * as fs from "fs-extra"
import Bundler from "parcel-bundler"
import * as path from "path"
import * as util from "util"
enum Task {
Build = "build",
Watch = "watch",
}
class Builder {
private readonly rootPath = path.resolve(__dirname, "..")
private readonly vscodeSourcePath = path.join(this.rootPath, "lib/vscode")
private readonly buildPath = path.join(this.rootPath, "build")
private readonly codeServerVersion: string
private currentTask?: Task
public constructor() {
this.ensureArgument("rootPath", this.rootPath)
this.codeServerVersion = this.ensureArgument(
"codeServerVersion",
process.env.VERSION || require(path.join(this.rootPath, "package.json")).version,
)
}
public run(task: Task | undefined): void {
this.currentTask = task
this.doRun(task).catch((error) => {
console.error(error.message)
process.exit(1)
})
}
private async task<T>(message: string, fn: () => Promise<T>): Promise<T> {
const time = Date.now()
this.log(`${message}...`, !process.env.CI)
try {
const t = await fn()
process.stdout.write(`took ${Date.now() - time}ms\n`)
return t
} catch (error) {
process.stdout.write("failed\n")
throw error
}
}
/**
* Writes to stdout with an optional newline.
*/
private log(message: string, skipNewline = false): void {
process.stdout.write(`[${this.currentTask || "default"}] ${message}`)
if (!skipNewline) {
process.stdout.write("\n")
}
}
private async doRun(task: Task | undefined): Promise<void> {
if (!task) {
throw new Error("No task provided")
}
switch (task) {
case Task.Watch:
return this.watch()
case Task.Build:
return this.build()
default:
throw new Error(`No task matching "${task}"`)
}
}
/**
* Make sure the argument is set. Display the value if it is.
*/
private ensureArgument(name: string, arg?: string): string {
if (!arg) {
throw new Error(`${name} is missing`)
}
this.log(`${name} is "${arg}"`)
return arg
}
/**
* Build VS Code and code-server.
*/
private async build(): Promise<void> {
process.env.NODE_OPTIONS = "--max-old-space-size=32384 " + (process.env.NODE_OPTIONS || "")
process.env.NODE_ENV = "production"
await this.task("cleaning up old build", async () => {
if (!process.env.SKIP_VSCODE) {
return fs.remove(this.buildPath)
}
// If skipping VS Code, keep the existing build if any.
try {
const files = await fs.readdir(this.buildPath)
return Promise.all(files.filter((f) => f !== "lib").map((f) => fs.remove(path.join(this.buildPath, f))))
} catch (error) {
if (error.code !== "ENOENT") {
throw error
}
}
})
const commit = require(path.join(this.vscodeSourcePath, "build/lib/util")).getVersion(this.rootPath) as string
if (!process.env.SKIP_VSCODE) {
await this.buildVscode(commit)
} else {
this.log("skipping vs code build")
}
await this.buildCodeServer(commit)
this.log(`final build: ${this.buildPath}`)
}
private async buildCodeServer(commit: string): Promise<void> {
await this.task("building code-server", async () => {
return util.promisify(cp.exec)("tsc --outDir ./out-build --tsBuildInfoFile ./.prod.tsbuildinfo", {
cwd: this.rootPath,
})
})
await this.task("bundling code-server", async () => {
return this.createBundler("dist-build", commit).bundle()
})
await this.task("copying code-server into build directory", async () => {
await fs.mkdirp(this.buildPath)
await Promise.all([
fs.copy(path.join(this.rootPath, "out-build"), path.join(this.buildPath, "out")),
fs.copy(path.join(this.rootPath, "dist-build"), path.join(this.buildPath, "dist")),
// For source maps and images.
fs.copy(path.join(this.rootPath, "src"), path.join(this.buildPath, "src")),
])
})
await this.copyDependencies("code-server", this.rootPath, this.buildPath, false, {
commit,
version: this.codeServerVersion,
})
}
private async buildVscode(commit: string): Promise<void> {
await this.task("building vs code", () => {
return util.promisify(cp.exec)("yarn gulp compile-build", { cwd: this.vscodeSourcePath })
})
await this.task("building builtin extensions", async () => {
const exists = await fs.pathExists(path.join(this.vscodeSourcePath, ".build/extensions"))
if (exists && !process.env.CI) {
process.stdout.write("already built, skipping...")
} else {
await util.promisify(cp.exec)("yarn gulp compile-extensions-build", { cwd: this.vscodeSourcePath })
}
})
await this.task("optimizing vs code", async () => {
return util.promisify(cp.exec)("yarn gulp optimize --gulpfile ./coder.js", { cwd: this.vscodeSourcePath })
})
if (process.env.MINIFY) {
await this.task("minifying vs code", () => {
return util.promisify(cp.exec)("yarn gulp minify --gulpfile ./coder.js", { cwd: this.vscodeSourcePath })
})
}
const vscodeBuildPath = path.join(this.buildPath, "lib/vscode")
await this.task("copying vs code into build directory", async () => {
await fs.mkdirp(path.join(vscodeBuildPath, "resources/linux"))
await Promise.all([
fs.move(
path.join(this.vscodeSourcePath, `out-vscode${process.env.MINIFY ? "-min" : ""}`),
path.join(vscodeBuildPath, "out"),
),
fs.copy(path.join(this.vscodeSourcePath, ".build/extensions"), path.join(vscodeBuildPath, "extensions")),
fs.copy(
path.join(this.vscodeSourcePath, "resources/linux/code.png"),
path.join(vscodeBuildPath, "resources/linux/code.png"),
),
])
})
await this.copyDependencies("vs code", this.vscodeSourcePath, vscodeBuildPath, true, {
commit,
date: new Date().toISOString(),
})
}
private async copyDependencies(
name: string,
sourcePath: string,
buildPath: string,
ignoreScripts: boolean,
merge: object,
): Promise<void> {
await this.task(`copying ${name} dependencies`, async () => {
return Promise.all(
["node_modules", "package.json", "yarn.lock"].map((fileName) => {
return fs.copy(path.join(sourcePath, fileName), path.join(buildPath, fileName))
}),
)
})
const fileName = name === "code-server" ? "package" : "product"
await this.task(`writing final ${name} ${fileName}.json`, async () => {
const json = JSON.parse(await fs.readFile(path.join(sourcePath, `${fileName}.json`), "utf8"))
return fs.writeFile(
path.join(buildPath, `${fileName}.json`),
JSON.stringify(
{
...json,
...merge,
},
null,
2,
),
)
})
if (process.env.MINIFY) {
await this.task(`restricting ${name} to production dependencies`, async () => {
await util.promisify(cp.exec)(`yarn --production ${ignoreScripts ? "--ignore-scripts" : ""}`, {
cwd: buildPath,
})
})
}
}
private async watch(): Promise<void> {
let server: cp.ChildProcess | undefined
const restartServer = (): void => {
if (server) {
server.kill()
}
const s = cp.fork(path.join(this.rootPath, "out/node/entry.js"), process.argv.slice(3))
console.log(`[server] spawned process ${s.pid}`)
s.on("exit", () => console.log(`[server] process ${s.pid} exited`))
server = s
}
const vscode = cp.spawn("yarn", ["watch"], { cwd: this.vscodeSourcePath })
const tsc = cp.spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath })
const bundler = this.createBundler()
const cleanup = (code?: number | null): void => {
this.log("killing vs code watcher")
vscode.removeAllListeners()
vscode.kill()
this.log("killing tsc")
tsc.removeAllListeners()
tsc.kill()
if (server) {
this.log("killing server")
server.removeAllListeners()
server.kill()
}
this.log("killing bundler")
process.exit(code || 0)
}
process.on("SIGINT", () => cleanup())
process.on("SIGTERM", () => cleanup())
vscode.on("exit", (code) => {
this.log("vs code watcher terminated unexpectedly")
cleanup(code)
})
tsc.on("exit", (code) => {
this.log("tsc terminated unexpectedly")
cleanup(code)
})
const bundle = bundler.bundle().catch(() => {
this.log("parcel watcher terminated unexpectedly")
cleanup(1)
})
bundler.on("buildEnd", () => {
console.log("[parcel] bundled")
})
bundler.on("buildError", (error) => {
console.error("[parcel]", error)
})
vscode.stderr.on("data", (d) => process.stderr.write(d))
tsc.stderr.on("data", (d) => process.stderr.write(d))
// From https://github.com/chalk/ansi-regex
const pattern = [
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))",
].join("|")
const re = new RegExp(pattern, "g")
/**
* Split stdout on newlines and strip ANSI codes.
*/
const onLine = (proc: cp.ChildProcess, callback: (strippedLine: string, originalLine: string) => void): void => {
let buffer = ""
if (!proc.stdout) {
throw new Error("no stdout")
}
proc.stdout.setEncoding("utf8")
proc.stdout.on("data", (d) => {
const data = buffer + d
const split = data.split("\n")
const last = split.length - 1
for (let i = 0; i < last; ++i) {
callback(split[i].replace(re, ""), split[i])
}
// The last item will either be an empty string (the data ended with a
// newline) or a partial line (did not end with a newline) and we must
// wait to parse it until we get a full line.
buffer = split[last]
})
}
let startingVscode = false
let startedVscode = false
onLine(vscode, (line, original) => {
console.log("[vscode]", original)
// Wait for watch-client since "Finished compilation" will appear multiple
// times before the client starts building.
if (!startingVscode && line.includes("Starting watch-client")) {
startingVscode = true
} else if (startingVscode && line.includes("Finished compilation")) {
if (startedVscode) {
bundle.then(restartServer)
}
startedVscode = true
}
})
onLine(tsc, (line, original) => {
// tsc outputs blank lines; skip them.
if (line !== "") {
console.log("[tsc]", original)
}
if (line.includes("Watching for file changes")) {
bundle.then(restartServer)
}
})
}
private createBundler(out = "dist", commit?: string): Bundler {
return new Bundler(
[
path.join(this.rootPath, "src/browser/pages/app.ts"),
path.join(this.rootPath, "src/browser/register.ts"),
path.join(this.rootPath, "src/browser/serviceWorker.ts"),
],
{
cache: true,
cacheDir: path.join(this.rootPath, ".cache"),
detailedReport: true,
minify: !!process.env.MINIFY,
hmr: false,
logLevel: 1,
outDir: path.join(this.rootPath, out),
publicUrl: `/static/${commit || "development"}/dist`,
target: "browser",
},
)
}
}
const builder = new Builder()
builder.run(process.argv[2] as Task)

12
ci/ci.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/.."
yarn fmt
yarn lint
yarn test
}
main "$@"

11
ci/clean.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
git clean -Xffd
git submodule foreach --recursive git clean -xffd
git submodule foreach --recursive git reset --hard
}
main "$@"

6
ci/code-server.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env sh
# code-server.sh -- Run code-server with the bundled Node binary.
dir="$(dirname "$(readlink -f "$0" || realpath "$0")")"
exec "$dir/node" "$dir/out/node/entry.js" "$@"

32
ci/fmt.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
shfmt -i 2 -w -s -sr $(git ls-files "*.sh")
local prettierExts
prettierExts=(
"*.js"
"*.ts"
"*.tsx"
"*.html"
"*.json"
"*.css"
"*.md"
"*.toml"
"*.yaml"
"*.yml"
)
prettier --write --loglevel=warn $(git ls-files "${prettierExts[@]}")
if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then
echo "Files need generation or are formatted incorrectly:"
git -c color.ui=always status | grep --color=no '\[31m'
echo "Please run the following locally:"
echo " yarn fmt"
exit 1
fi
}
main "$@"

23
ci/image/Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM centos:7
RUN yum update -y && yum install -y \
devtoolset-6 \
gcc-c++ \
xz \
ccache \
git \
wget \
openssl \
libxkbfile-devel \
libsecret-devel \
libx11-devel
RUN mkdir /usr/share/node && cd /usr/share/node \
&& curl "https://nodejs.org/dist/v12.14.0/node-v12.14.0-linux-$(uname -m | sed 's/86_//; s/aarch/arm/').tar.xz" | tar xJ --strip-components=1 --
ENV PATH "$PATH:/usr/share/node/bin"
RUN npm install -g yarn
RUN curl -L "https://github.com/mvdan/sh/releases/download/v3.0.1/shfmt_v3.0.1_linux_$(uname -m | sed 's/x86_/amd/; s/aarch64/arm/')" > /usr/local/bin/shfmt \
&& chmod +x /usr/local/bin/shfmt
ENTRYPOINT ["/bin/bash", "-c"]

26
ci/image/run.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
# This, strangely enough, fixes the arm build being terminated for not having
# output on Travis. It's as if output is buffered and only displayed once a
# certain amount is collected. Five seconds didn't work but one second seems
# to generate enough output to make it work.
local pid
while true; do
echo 'Still running...'
sleep 1
done &
pid=$!
docker build ci/image
imageTag="$(docker build -q ci/image)"
docker run -t --rm -e CI -e GITHUB_TOKEN -e TRAVIS_TAG -v "$(yarn cache dir):/usr/local/share/.cache/yarn/v6" -v "$PWD:/repo" -w /repo "$imageTag" "$*"
kill $pid
}
main "$@"

10
ci/lib.sh Normal file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
set_version() {
local code_server_version=${VERSION:-${TRAVIS_TAG:-}}
if [[ -z $code_server_version ]]; then
code_server_version=$(grep version ./package.json | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[:space:]')
fi
export VERSION=$code_server_version
}

11
ci/lint.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js")
stylelint $(git ls-files "*.css")
tsc --noEmit
}
main "$@"

View File

@@ -0,0 +1,43 @@
FROM debian:10
RUN apt-get update \
&& apt-get install -y \
curl \
dumb-init \
htop \
locales \
man \
nano \
git \
procps \
ssh \
sudo \
vim \
&& rm -rf /var/lib/apt/lists/*
# https://wiki.debian.org/Locale#Manually
RUN sed -i "s/# en_US.UTF-8/en_US.UTF-8/" /etc/locale.gen \
&& locale-gen
ENV LANG=en_US.UTF-8
RUN chsh -s /bin/bash
ENV SHELL=/bin/bash
RUN adduser --gecos '' --disabled-password coder && \
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
RUN curl -SsL https://github.com/boxboat/fixuid/releases/download/v0.4/fixuid-0.4-linux-amd64.tar.gz | tar -C /usr/local/bin -xzf - && \
chown root:root /usr/local/bin/fixuid && \
chmod 4755 /usr/local/bin/fixuid && \
mkdir -p /etc/fixuid && \
printf "user: coder\ngroup: coder\n" > /etc/fixuid/config.yml
COPY release/code-server*.tar.gz /tmp/
RUN cd /tmp && tar -xzf code-server*.tar.gz && rm code-server*.tar.gz && \
mv code-server* /usr/local/lib/code-server && \
ln -s /usr/local/lib/code-server/code-server /usr/local/bin/code-server
EXPOSE 8080
USER coder
WORKDIR /home/coder
ENTRYPOINT ["dumb-init", "fixuid", "-q", "/usr/local/bin/code-server", "--host", "0.0.0.0", "."]

22
ci/release-image/push.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
source ./ci/lib.sh
set_version
if [[ ${CI:-} ]]; then
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
fi
imageTag="codercom/code-server:$VERSION"
if [[ ${TRAVIS_CPU_ARCH:-} == "arm64" ]]; then
imageTag+="-arm64"
fi
docker build -t "$imageTag" -f ./ci/release-image/Dockerfile .
docker push codercom/code-server
}
main "$@"

75
ci/release.sh Executable file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# ci.bash -- Build code-server in the CI.
set -euo pipefail
function package() {
local target
target=$(uname | tr '[:upper:]' '[:lower:]')
if [[ $target == "linux" ]]; then
# Alpine's ldd doesn't have a version flag but if you use an invalid flag
# (like --version) it outputs the version to stderr and exits with 1.
local ldd_output
ldd_output=$(ldd --version 2>&1 || true)
if echo "$ldd_output" | grep -iq musl; then
target="alpine"
fi
fi
local arch
arch=$(uname -m | sed 's/aarch/arm/')
echo -n "Creating release..."
cp "$(command -v node)" ./build
cp README.md ./build
cp LICENSE.txt ./build
cp ./lib/vscode/ThirdPartyNotices.txt ./build
cp ./ci/code-server.sh ./build/code-server
local archive_name="code-server-$VERSION-$target-$arch"
mkdir -p ./release
local ext
if [[ $target == "linux" ]]; then
ext=".tar.gz"
tar -czf "release/$archive_name$ext" --transform "s/^\.\/build/$archive_name/" ./build
else
mv ./build "./$archive_name"
ext=".zip"
zip -r "release/$archive_name$ext" "./$archive_name"
mv "./$archive_name" ./build
fi
echo "done (release/$archive_name)"
mkdir -p "./release-upload/$VERSION"
cp "./release/$archive_name$ext" "./release-upload/$VERSION/$target-$arch$ext"
mkdir -p "./release-upload/latest"
cp "./release/$archive_name$ext" "./release-upload/latest/$target-$arch$ext"
}
# This script assumes that yarn has already ran.
function build() {
# Always minify and package on CI.
if [[ ${CI:-} ]]; then
export MINIFY="true"
fi
yarn build
}
function main() {
cd "$(dirname "${0}")/.."
source ./ci/lib.sh
set_version
build
if [[ ${CI:-} ]]; then
package
fi
}
main "$@"

4
ci/tsconfig.json Normal file
View File

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

3218
ci/vscode.patch Normal file

File diff suppressed because it is too large Load Diff

22
ci/vscode.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
# 1. Ensures VS Code is cloned.
# 2. Patches it.
# 3. Installs it.
main() {
cd "$(dirname "$0")/.."
git submodule update --init
# If the patch fails to apply, then it's likely already applied
yarn vscode:patch &> /dev/null || true
(
cd lib/vscode
# Install VS Code dependencies.
yarn
)
}
main "$@"

30
doc/CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,30 @@
# Contributing
## Development Workflow
- [VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
```shell
yarn
yarn vscode
yarn watch # Visit http://localhost:8080 once completed.
```
Any changes made to the source will be live reloaded.
If changes are made to the patch and you've built previously you must manually
reset VS Code then run `yarn vscode:patch`.
Some docs are available at [../src/node/app](../src/node/app) on how code-server
works internally.
## Build
- [VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
```shell
yarn
yarn vscode
yarn build
node ./build/out/node/entry.js # Run the built JavaScript with Node.
```

110
doc/FAQ.md Normal file
View File

@@ -0,0 +1,110 @@
# FAQ
## Questions?
Please file all questions and support requests at https://www.reddit.com/r/codeserver/
The issue tracker is only for bugs.
## What's the deal with extensions?
Unfortunately, the Microsoft VS Code Marketplace license prohibits use with any non Microsoft
product.
See https://cdn.vsassets.io/v/M146_20190123.39/_content/Microsoft-Visual-Studio-Marketplace-Terms-of-Use.pdf
> Marketplace Offerings are intended for use only with Visual Studio Products and Services
> and you may only install and use Marketplace Offerings with Visual Studio Products and Services.
As a result, Coder has created its own marketplace for open source extensions. It works by scraping
GitHub for VS Code extensions and building them. It's not perfect but getting better by the day with
more and more extensions.
Issue [https://github.com/cdr/code-server/issues/1299](#1299) is a big one in making the experience here
better by allowing the community to submit extensions and repos to avoid waiting until the scraper finds
an extension.
If an extension does not work, try to grab its VSIX from its Github releases or build it yourself and
copy it to the extensions folder.
## How is this different from VS Code Online?
VS Code Online is a closed source managed service by Microsoft and only runs on Azure.
code-server is open source and can be freely run on any machine.
## How should I expose code-server to the internet?
By far the most secure method of using code-server is via
[sshcode](https://github.com/codercom/sshcode) as it runs code-server and then forwards
its port over SSH and requires no setup on your part other than having a working SSH server.
You can also forward your SSH key and GPG agent to the remote machine to securely access GitHub
and securely sign commits without duplicating your keys onto the the remote machine.
1. https://developer.github.com/v3/guides/using-ssh-agent-forwarding/
1. https://wiki.gnupg.org/AgentForwarding
If you cannot use sshcode, then you will need to ensure there is some sort of authorization in
front of code-server and that you are using HTTPS to secure all connections.
By default when listening externally, code-server enables password authentication using a
randomly generated password so you can use that. You can set the `PASSWORD` environment variable
to use your own instead. If you want to handle authentication yourself, use `--auth none`
to disable password authentication.
If you want to use external authentication you should handle this with a reverse
proxy using something like [oauth2_proxy](https://github.com/pusher/oauth2_proxy).
For HTTPS, you can use a self signed certificate by passing in just `--cert` or pass in an existing
certificate by providing the path to `--cert` and the path to its key with `--cert-key`.
If `code-server` has been passed a certificate it will also respond to HTTPS
requests and will redirect all HTTP requests to HTTPS. Otherwise it will respond
only to HTTP requests.
You can use [Let's Encrypt](https://letsencrypt.org/) to get an SSL certificate
for free.
## x86 releases?
node has dropped support for x86 and so we decided to as well. See
[nodejs/build/issues/885](https://github.com/nodejs/build/issues/885).
## Alpine builds?
Just install `libc-dev` and code-server should work.
## Multi Tenancy
If you want to run multiple code-server's on shared infrastructure, we recommend using virtual
machines with a VM per user. This will easily allow users to run a docker daemon. If you want
to use kubernetes, you'll definitely want to use [kubevirt](https://kubevirt.io) to give each
user a virtual machine instead of just a container. Docker in docker while supported requires
privileged containers which are a security risk in a multi tenant infrastructure.
## Docker in code-server docker container?
If you'd like to access docker inside of code-server, we'd recommend running a docker:dind container
and mounting in a directory to share between dind and the code-server container at /var/run. After, install
the docker CLI in the code-server container and you should be able to access the daemon as the socket
will be shared at /var/run/docker.sock.
In order to make volume mounts work, mount the home directory in the code-server container and the
dind container at the same path. i.e you'd volume mount a directory from the host to `/home/coder`
on both. This will allow any volume mounts in the home directory to work. Similar process
to make volume mounts in any other directory work.
## Collaboration
At the moment we have no plans for multi user collaboration on code-server but we understand there is strong
demand and will work on it when the time is right.
## How can I disable telemetry?
Use the `--disable-telemetry` flag to completely disable telemetry. We use the
data collected only to improve code-server.
## Enterprise
Visit [our enterprise page](https://coder.com) for more information about our
enterprise offerings.

BIN
doc/assets/code-server.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -1,75 +0,0 @@
# Installing code-server in your ChromiumOS/ChromeOS/CloudReady machine
This guide will show you how to install code-server into your CrOS machine.
## Using Crostini
One of the easier ways to run code-server is via
[Crostini](https://www.aboutchromebooks.com/tag/project-crostini/), the Linux
apps support feature in CrOS. Make sure you have enough RAM, HDD space and your
CPU has VT-x/ AMD-V support. If your chromebook has this, then you are
qualified to use Crostini.
If you are running R69, you might want to enable this on
[Chrome Flags](chrome://flags/#enable-experimental-crostini-ui).
If you run R72, however, this is already enabled for you.
After checking your prerequisites, follow the steps in [the self-host install guide](index.md)
on installing code-server. Once done, make sure code-server works by running
it. After running it, simply go to `penguin.linux.test:8080` to access
code-server. Now you should be greeted with this screen. If you did,
congratulations, you have installed code-server in your Chromebook!
![code-server on Chromebook](assets/cros.png)
Alternatively, if you ran code-server in another container and you need the IP
for that specific container, simply go to Termina's shell via `crosh` and type
`vsh termina`.
```bash
Loading extra module: /usr/share/crosh/dev.d/50-crosh.sh
Welcome to crosh, the Chrome OS developer shell.
If you got here by mistake, don't panic! Just close this tab and carry on.
Type 'help' for a list of commands.
If you want to customize the look/behavior, you can use the options page.
Load it by using the Ctrl+Shift+P keyboard shortcut.
crosh> vsh termina
(termina) chronos@localhost ~ $
```
While in termina, run `lxc list`. It should output the list of running containers.
```bash
(termina) chronos@localhost ~ $ lxc list
+---------|---------|-----------------------|------|------------|-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+---------|---------|-----------------------|------|------------|-----------+
| penguin | RUNNING | 100.115.92.199 (eth0) | | PERSISTENT | 0 |
+---------|---------|-----------------------|------|------------|-----------+
(termina) chronos@localhost ~ $
```
For this example, we show the default `penguin` container, which is exposed on
`eth0` at 100.115.92.199. Simply enter the IP of the container where the
code-server runs to Chrome.
## Using Crouton
[Crouton](https://github.com/dnschneid/crouton) is one of the old ways to get a
running full Linux via `chroot` on a Chromebook. To use crouton, enable
developer mode and go to `crosh`. This time, run `shell`, which should drop you
to `bash`.
Make sure you downloaded `crouton`, if so, go ahead and run it under
`~/Downloads`. After installing your chroot container via crouton, go ahead and
enter `enter-chroot` to enter your container.
Follow the instructions set in [the self-host install guide](index.md) to
install code-server. After that is done, run `code-server` and verify it works
by going to `localhost:8080`.
> At this point in writing, `localhost` seems to work in this method. However,
> the author is not sure if it applies still to newer Chromebooks.

View File

@@ -1,73 +0,0 @@
# Set up instance
## EC2 on AWS
- Click **Launch Instance** from your [EC2 dashboard](https://console.aws.amazon.com/ec2/v2/home).
- Select the Ubuntu Server 18.04 LTS (HVM), SSD Volume Type
- Select an appropriate instance size (we recommend t2.medium/large, depending
on team size and number of repositories/languages enabled), then
**Next: Configure Instance Details**.
- Select **Next: ...** until you get to the **Configure Security Group** page,
then add a **Custom TCP Rule** rule with port range set to `8080` and source
set to "Anywhere".
> Rules with source of 0.0.0.0/0 allow all IP addresses to access your
> instance. We recommend setting [security group rules](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html?icmpid=docs_ec2_console)
> to allow access from known IP addresses only.
- Click **Launch**.
- You will be prompted to create a key pair.
- From the dropdown choose "create a new pair", give the key pair a name.
- Click **Download Key Pair** and store the file in a safe place.
- Click **Launch Instances**.
- Head to your [EC2 dashboard](https://console.aws.amazon.com/ec2/v2/home)
and choose instances from the left panel.
- In the description of your EC2 instance copy the public DNS (iPv4) address
using the copy to clipboard button.
- Open a terminal on your computer and SSH into your instance:
```
ssh -i ${path to key pair} ubuntu@${public address}
```
## DigitalOcean
[Open your DigitalOcean dashboard](https://cloud.digitalocean.com/droplets/new)
to create a new droplet
- **Choose an image -** Select the **Distributions** tab and then choose Ubuntu.
- **Choose a size -** We recommend at least 4GB RAM and 2 CPU, more depending
on team size and number of repositories/languages enabled.
- Launch your instance.
- Open a terminal on your computer and SSH into your instance:
```
ssh root@${instance ip}
```
## Google Cloud
> Pre-requisite: Set up the [Google Cloud SDK](https://cloud.google.com/sdk/docs/)
> on your local machine
- [Open your Google Cloud console](https://console.cloud.google.com/compute/instances)
to create a new VM instance and click **Create Instance**.
- Choose an appropriate machine type (we recommend 2 vCPU and 7.5 GB RAM, more
depending on team size and number of repositories/languages enabled).
- Choose Ubuntu 16.04 LTS as your boot disk.
- Expand the "Management, security, disks, networking, sole tenancy" section,
go to the "Networking" tab, then under network tags add "code-server".
- Create your VM, and **take note** of its public IP address.
- Visit "VPC network" in the console and go to "Firewall rules". Create a new
firewall rule called "http-8080". Under "Target tags" add "code-server", and
under "Protocols and ports" tick "Specified protocols and ports" and "tcp".
Beside "tcp", add "8080", then create the rule.
- Open a terminal on your computer and SSH into your Google Cloud VM:
```
gcloud compute ssh --zone ${region} ${instance name}
```
# Run code-server
- Download the latest code-server release from the
[releases page](https://github.com/cdr/code-server/releases/latest)
to the instance, extract the file, then run the code-server binary:
```
wget https://github.com/cdr/code-server/releases/download/{version}/code-server{version}-linux-x64.tar.gz
tar -xvzf code-server{version}-linux-x64.tar.gz
cd code-server{version}-linux-x64
./code-server
```
- Open your browser and visit http://$public_ip:8080/ where `$public_ip` is
your instance's public IP address.
- For long-term use, set up a systemd service to run code-server.

View File

@@ -1,15 +0,0 @@
# Fail2Ban filter for code-server
[Definition]
failregex = ^INFO\s+Failed login attempt\s+{\"password\":\"(\\.|[^"])*\",\"remoteAddress\":\"<HOST>\"
# Use this instead for proxies (ensure the proxy is configured to send the
# X-Forwarded-For header).
# failregex = ^INFO\s+Failed login attempt\s+{\"password\":\"(\\.|[^"])*\",\"xForwardedFor\":\"<HOST>\"
ignoreregex =
datepattern = "timestamp":{EPOCH}}$
# Author: Dean Sheather

View File

@@ -1,73 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: code-server
---
apiVersion: v1
kind: Service
metadata:
name: code-server
namespace: code-server
spec:
ports:
- port: 8080
name: https
protocol: TCP
selector:
app: code-server
type: ClusterIP
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: gp2
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
fsType: ext4
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: code-store
namespace: code-server
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 60Gi
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: code-server
name: code-server
namespace: code-server
spec:
selector:
matchLabels:
app: code-server
replicas: 1
template:
metadata:
labels:
app: code-server
spec:
containers:
- image: codercom/code-server:v2
imagePullPolicy: Always
name: code-servery
ports:
- containerPort: 8080
name: https
volumeMounts:
- name: code-server-storage
mountPath: /go/src
volumes:
- name: code-server-storage
persistentVolumeClaim:
claimName: code-store

View File

@@ -1,43 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: code-server
---
apiVersion: v1
kind: Service
metadata:
name: code-server
namespace: code-server
spec:
ports:
- port: 8080
name: https
protocol: TCP
selector:
app: code-server
type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: code-server
name: code-server
namespace: code-server
spec:
selector:
matchLabels:
app: code-server
replicas: 1
template:
metadata:
labels:
app: code-server
spec:
containers:
- image: codercom/code-server:v2
imagePullPolicy: Always
name: code-server
ports:
- containerPort: 8080
name: https

View File

@@ -1,35 +0,0 @@
# Protecting code-server from bruteforce attempts
code-server outputs all failed login attempts, along with the IP address,
provided password, user agent and timestamp by default.
When using a reverse proxy such as Nginx or Apache, the remote address may
appear to be `127.0.0.1` or a similar address so `X-Forwarded-For` should be
used instead. Ensure that you are setting this value in your reverse proxy:
Nginx:
```
location / {
...
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
...
}
```
Apache:
```
<VirtualEnv>
...
SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded
...
</VirtualEnv>
```
It is extremely important that you ensure that your code-server instance is not
accessible from the internet (use localhost or block it in your firewall).
## Fail2Ban
Fail2Ban allows for automatically banning and logging repeated failed
authentication attempts for many applications through regex filters. A working
filter for code-server can be found in `./code-server.fail2ban.conf`. Once this
is installed and configured correctly, repeated failed login attempts should
automatically be banned from connecting to your server.

View File

@@ -1,98 +0,0 @@
# Quickstart Guide
1. Visit the [releases page](https://github.com/cdr/code-server/releases) and
download the latest binary for your operating system.
2. Unpack the downloaded file then run the binary.
3. In your browser navigate to `localhost:8080`.
## Usage
Run `code-server --help` to view available options.
### Nginx Reverse Proxy
The trailing slashes are important.
```
server {
listen 80;
listen [::]:80;
server_name code.example.com code.example.org;
location /some/path/ { # Or / if hosting at the root.
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Accept-Encoding gzip;
}
}
```
### Apache Reverse Proxy
```
<VirtualHost *:80>
ServerName code.example.com
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://localhost:8080/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) http://localhost:8080/$1 [P,L]
ProxyRequests off
ProxyPass / http://localhost:8080/ nocanon
ProxyPassReverse / http://localhost:8080/
</VirtualHost>
```
### Run automatically at startup
In some cases you might need to run code-server automatically once the host starts. You may use your local init service to do so.
#### Systemd
```ini
[Unit]
Description=Code Server IDE
After=network.target
[Service]
Type=simple
User=<USER>
EnvironmentFile=$HOME/.profile
WorkingDirectory=$HOME
Restart=on-failure
RestartSec=10
ExecStart=<PATH TO BINARY> $(pwd)
StandardOutput=file:/var/log/code-server-output.log
StandardError=file:/var/log/code-server-error.log
[Install]
WantedBy=multi-user.target
```
#### OpenRC
```sh
#!/sbin/openrc-run
depend() {
after net-online
need net
}
supervisor=supervise-daemon
name="code-server"
command="/opt/cdr/code-server"
command_args=""
pidfile="/var/run/cdr.pid"
respawn_delay=5
set -o allexport
if [ -f /etc/environment ]; then source /etc/environment; fi
set +o allexport
```
#### Kubernetes/Docker
Make sure you set your restart policy to always - this will ensure your container starts as the daemon starts.

View File

@@ -1,13 +0,0 @@
version: "3"
services:
code-server:
container_name: code-server
image: codercom/code-server
ports:
- "8080:8080"
volumes:
- "${PWD}:/home/coder/project"
- "${HOME}/.local/share/code-server:/home/coder/.local/share/code-server"
environment:
PASSWORD: ${PASSWORD}

1
lib/vscode Submodule

Submodule lib/vscode added at 78a4c91400

View File

@@ -1,7 +0,0 @@
// Once our entry file is loaded we no longer need nbin to bypass normal Node
// execution. We can still shim the fs into the binary even when bypassing. This
// will ensure for example that a spawn like `${process.argv[0]} -e` will work
// while still allowing us to access files within the binary.
process.env.NBIN_BYPASS = true;
require("../../bootstrap-amd").load("vs/server/src/node/cli");

View File

@@ -1,41 +1,65 @@
{
"name": "code-server",
"license": "MIT",
"version": "3.0.1",
"scripts": {
"runner": "cd ./scripts && node --max-old-space-size=32384 -r ts-node/register ./build.ts",
"start": "nodemon --watch ../../../out --verbose ../../../out/vs/server/main.js",
"watch": "cd ../../../ && yarn watch",
"build": "yarn && yarn runner build",
"package": "yarn runner package",
"binary": "yarn runner binary",
"patch:generate": "cd ../../../ && git diff --staged > ./src/vs/server/scripts/vscode.patch",
"patch:apply": "cd ../../../ && git apply ./src/vs/server/scripts/vscode.patch"
"clean": "ci/clean.sh",
"vscode": "ci/vscode.sh",
"vscode:patch": "cd ./lib/vscode && git apply ../../ci/vscode.patch",
"vscode:diff": "cd ./lib/vscode && git diff HEAD > ../../ci/vscode.patch",
"test": "mocha -r ts-node/register ./test/*.test.ts",
"lint": "ci/lint.sh",
"fmt": "ci/fmt.sh",
"runner": "cd ./ci && NODE_OPTIONS=--max_old_space_size=32384 ts-node ./build.ts",
"build": "yarn runner build",
"watch": "yarn runner watch"
},
"devDependencies": {
"@coder/nbin": "^1.2.2",
"@types/adm-zip": "^0.4.32",
"@types/fs-extra": "^8.0.1",
"@types/node": "^10.12.12",
"@types/mocha": "^5.2.7",
"@types/node": "^12.12.7",
"@types/parcel-bundler": "^1.12.1",
"@types/pem": "^1.9.5",
"@types/safe-compare": "^1.1.0",
"@types/tar-fs": "^1.16.1",
"@types/semver": "^7.1.0",
"@types/tar-fs": "^1.16.2",
"@types/ssh2": "0.5.39",
"@types/ssh2-streams": "^0.1.6",
"@types/tar-stream": "^1.6.1",
"fs-extra": "^8.1.0",
"nodemon": "^1.19.1",
"@types/ws": "^6.0.4",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
"eslint": "^6.2.0",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-prettier": "^3.1.0",
"leaked-handles": "^5.2.0",
"mocha": "^6.2.0",
"parcel-bundler": "^1.12.4",
"prettier": "^1.18.2",
"stylelint": "^13.0.0",
"stylelint-config-recommended": "^3.0.0",
"ts-node": "^8.4.1",
"typescript": "3.6"
"typescript": "3.7.2"
},
"resolutions": {
"@types/node": "^10.12.12",
"safe-buffer": "^5.1.1"
"@types/node": "^12.12.7",
"safe-buffer": "^5.1.1",
"vfile-message": "^2.0.2"
},
"dependencies": {
"@coder/logger": "^1.1.8",
"@coder/node-browser": "^1.0.6",
"@coder/requirefs": "^1.0.6",
"@coder/logger": "1.1.11",
"adm-zip": "^0.4.14",
"fs-extra": "^8.1.0",
"httpolyglot": "^0.1.2",
"node-pty": "^0.9.0",
"pem": "^1.14.2",
"safe-compare": "^1.1.4",
"semver": "^7.1.3",
"tar": "^6.0.1",
"ssh2": "^0.8.7",
"tar-fs": "^2.0.0",
"tar-stream": "^2.1.0",
"util": "^0.12.1"
"ws": "^7.2.0"
}
}

View File

@@ -1,415 +0,0 @@
import { Binary } from "@coder/nbin";
import * as cp from "child_process";
// import * as crypto from "crypto";
import * as fs from "fs-extra";
import * as os from "os";
import * as path from "path";
import * as util from "util";
enum Task {
/**
* Use before running anything that only works inside VS Code.
*/
EnsureInVscode = "ensure-in-vscode",
Binary = "binary",
Package = "package",
Build = "build",
}
class Builder {
private readonly rootPath = path.resolve(__dirname, "..");
private readonly outPath = process.env.OUT || this.rootPath;
private _target?: "darwin" | "alpine" | "linux";
private currentTask?: Task;
public run(task: Task | undefined, args: string[]): void {
this.currentTask = task;
this.doRun(task, args).catch((error) => {
console.error(error.message);
process.exit(1);
});
}
private async task<T>(message: string, fn: () => Promise<T>): Promise<T> {
const time = Date.now();
this.log(`${message}...`, true);
try {
const t = await fn();
process.stdout.write(`took ${Date.now() - time}ms\n`);
return t;
} catch (error) {
process.stdout.write("failed\n");
throw error;
}
}
/**
* Writes to stdout with an optional newline.
*/
private log(message: string, skipNewline: boolean = false): void {
process.stdout.write(`[${this.currentTask || "default"}] ${message}`);
if (!skipNewline) {
process.stdout.write("\n");
}
}
private async doRun(task: Task | undefined, args: string[]): Promise<void> {
if (!task) {
throw new Error("No task provided");
}
if (task === Task.EnsureInVscode) {
return process.exit(this.isInVscode(this.rootPath) ? 0 : 1);
}
// If we're inside VS Code assume we want to develop. In that case we should
// set an OUT directory and not build in this directory, otherwise when you
// build/watch VS Code the build directory will be included.
if (this.isInVscode(this.outPath)) {
throw new Error("Should not build inside VS Code; set the OUT environment variable");
}
this.ensureArgument("rootPath", this.rootPath);
this.ensureArgument("outPath", this.outPath);
const arch = this.ensureArgument("arch", os.arch().replace(/^x/, "x86_"));
const target = this.ensureArgument("target", await this.target());
const vscodeVersion = this.ensureArgument("vscodeVersion", args[0]);
const codeServerVersion = this.ensureArgument("codeServerVersion", args[1]);
const vscodeSourcePath = path.join(this.outPath, "source", `vscode-${vscodeVersion}-source`);
const binariesPath = path.join(this.outPath, "binaries");
const binaryName = `code-server${codeServerVersion}-vsc${vscodeVersion}-${target}-${arch}`;
const finalBuildPath = path.join(this.outPath, "build", `${binaryName}-built`);
switch (task) {
case Task.Binary:
return this.binary(finalBuildPath, binariesPath, binaryName);
case Task.Package:
return this.package(vscodeSourcePath, binariesPath, binaryName);
case Task.Build:
return this.build(vscodeSourcePath, vscodeVersion, codeServerVersion, finalBuildPath);
default:
throw new Error(`No task matching "${task}"`);
}
}
/**
* Get the target of the system.
*/
private async target(): Promise<"darwin" | "alpine" | "linux"> {
if (!this._target) {
if (os.platform() === "darwin" || (process.env.OSTYPE && /^darwin/.test(process.env.OSTYPE))) {
this._target = "darwin";
} else {
// Alpine's ldd doesn't have a version flag but if you use an invalid flag
// (like --version) it outputs the version to stderr and exits with 1.
const result = await util.promisify(cp.exec)("ldd --version")
.catch((error) => ({ stderr: error.message, stdout: "" }));
if (/musl/.test(result.stderr) || /musl/.test(result.stdout)) {
this._target = "alpine";
} else {
this._target = "linux";
}
}
}
return this._target;
}
/**
* Make sure the argument is set. Display the value if it is.
*/
private ensureArgument(name: string, arg?: string): string {
if (!arg) {
this.log(`${name} is missing`);
throw new Error("Usage: <vscodeVersion> <codeServerVersion>");
}
this.log(`${name} is "${arg}"`);
return arg;
}
/**
* Return true if it looks like we're inside VS Code. This is used to prevent
* accidentally building inside VS Code while developing which causes issues
* because the watcher will try compiling those built files.
*/
private isInVscode(pathToCheck: string): boolean {
let inside = false;
const maybeVsCode = path.join(pathToCheck, "../../../");
try {
// If it has a package.json with the right name it's probably VS Code.
inside = require(path.join(maybeVsCode, "package.json")).name === "code-oss-dev";
} catch (error) {}
this.log(
inside
? `Running inside VS Code ([${maybeVsCode}]${path.relative(maybeVsCode, pathToCheck)})`
: "Not running inside VS Code"
);
return inside;
}
/**
* Build code-server within VS Code.
*/
private async build(vscodeSourcePath: string, vscodeVersion: string, codeServerVersion: string, finalBuildPath: string): Promise<void> {
// Install dependencies (should be cached by CI).
await this.task("Installing code-server dependencies", async () => {
await util.promisify(cp.exec)("yarn", { cwd: this.rootPath });
});
// Download and prepare VS Code if necessary (should be cached by CI).
if (fs.existsSync(vscodeSourcePath)) {
this.log("Using existing VS Code clone");
} else {
await this.task("Cloning VS Code", () => {
return util.promisify(cp.exec)(
"git clone https://github.com/microsoft/vscode"
+ ` --quiet --branch "${vscodeVersion}"`
+ ` --single-branch --depth=1 "${vscodeSourcePath}"`);
});
}
if (fs.existsSync(path.join(vscodeSourcePath, "node_modules"))) {
this.log("Using existing VS Code node_modules");
} else {
await this.task("Installing VS Code dependencies", () => {
return util.promisify(cp.exec)("yarn", { cwd: vscodeSourcePath });
});
}
if (fs.existsSync(path.join(vscodeSourcePath, ".build/extensions"))) {
this.log("Using existing built-in-extensions");
} else {
await this.task("Building default extensions", () => {
return util.promisify(cp.exec)(
"yarn gulp compile-extensions-build --max-old-space-size=32384",
{ cwd: vscodeSourcePath },
);
});
}
// Clean before patching or it could fail if already patched.
await this.task("Patching VS Code", async () => {
await util.promisify(cp.exec)("git reset --hard", { cwd: vscodeSourcePath });
await util.promisify(cp.exec)("git clean -fd", { cwd: vscodeSourcePath });
await util.promisify(cp.exec)(`git apply ${this.rootPath}/scripts/vscode.patch`, { cwd: vscodeSourcePath });
});
const serverPath = path.join(vscodeSourcePath, "src/vs/server");
await this.task("Copying code-server into VS Code", async () => {
await fs.remove(serverPath);
await fs.mkdirp(serverPath);
await Promise.all(["main.js", "node_modules", "src", "typings"].map((fileName) => {
return fs.copy(path.join(this.rootPath, fileName), path.join(serverPath, fileName));
}));
});
await this.task("Building VS Code", () => {
return util.promisify(cp.exec)("yarn gulp compile-build --max-old-space-size=32384", { cwd: vscodeSourcePath });
});
await this.task("Optimizing VS Code", async () => {
await fs.copyFile(path.join(this.rootPath, "scripts/optimize.js"), path.join(vscodeSourcePath, "coder.js"));
await util.promisify(cp.exec)(`yarn gulp optimize --max-old-space-size=32384 --gulpfile ./coder.js`, { cwd: vscodeSourcePath });
});
const { productJson, packageJson } = await this.task("Generating final package.json and product.json", async () => {
const merge = async (name: string, extraJson: { [key: string]: string } = {}): Promise<{ [key: string]: string }> => {
const [aJson, bJson] = (await Promise.all([
fs.readFile(path.join(vscodeSourcePath, `${name}.json`), "utf8"),
fs.readFile(path.join(this.rootPath, `scripts/${name}.json`), "utf8"),
])).map((raw) => {
const json = JSON.parse(raw);
delete json.scripts;
delete json.dependencies;
delete json.devDependencies;
delete json.optionalDependencies;
return json;
});
return { ...aJson, ...bJson, ...extraJson };
};
const date = new Date().toISOString();
const commit = require(path.join(vscodeSourcePath, "build/lib/util")).getVersion(this.rootPath);
const [productJson, packageJson] = await Promise.all([
merge("product", { commit, date }),
merge("package", { codeServerVersion: `${codeServerVersion}-vsc${vscodeVersion}` }),
]);
// We could do this before the optimization but then it'd be copied into
// three files and unused in two which seems like a waste of bytes.
const apiPath = path.join(vscodeSourcePath, "out-vscode/vs/workbench/workbench.web.api.js");
await fs.writeFile(apiPath, (await fs.readFile(apiPath, "utf8")).replace('{ /*BUILD->INSERT_PRODUCT_CONFIGURATION*/}', JSON.stringify({
version: packageJson.version,
codeServerVersion: packageJson.codeServerVersion,
...productJson,
})));
return { productJson, packageJson };
});
if (process.env.MINIFY) {
await this.task("Minifying VS Code", () => {
return util.promisify(cp.exec)("yarn gulp minify --max-old-space-size=32384 --gulpfile ./coder.js", { cwd: vscodeSourcePath });
});
}
const finalServerPath = path.join(finalBuildPath, "out/vs/server");
await this.task("Copying into final build directory", async () => {
await fs.remove(finalBuildPath);
await fs.mkdirp(finalBuildPath);
await Promise.all([
fs.copy(path.join(vscodeSourcePath, "remote/node_modules"), path.join(finalBuildPath, "node_modules")),
fs.copy(path.join(vscodeSourcePath, ".build/extensions"), path.join(finalBuildPath, "extensions")),
fs.copy(path.join(vscodeSourcePath, `out-vscode${process.env.MINIFY ? "-min" : ""}`), path.join(finalBuildPath, "out")).then(() => {
return Promise.all([
fs.remove(path.join(finalServerPath, "node_modules")).then(() => {
return fs.copy(path.join(serverPath, "node_modules"), path.join(finalServerPath, "node_modules"));
}),
fs.copy(path.join(finalServerPath, "src/browser/workbench-build.html"), path.join(finalServerPath, "src/browser/workbench.html")),
]);
}),
]);
});
if (process.env.MINIFY) {
await this.task("Restricting to production dependencies", async () => {
await Promise.all(["package.json", "yarn.lock"].map((fileName) => {
Promise.all([
fs.copy(path.join(this.rootPath, fileName), path.join(finalServerPath, fileName)),
fs.copy(path.join(path.join(vscodeSourcePath, "remote"), fileName), path.join(finalBuildPath, fileName)),
]);
}));
await Promise.all([finalServerPath, finalBuildPath].map((cwd) => {
return util.promisify(cp.exec)("yarn --production", { cwd });
}));
await Promise.all(["package.json", "yarn.lock"].map((fileName) => {
return Promise.all([
fs.remove(path.join(finalServerPath, fileName)),
fs.remove(path.join(finalBuildPath, fileName)),
]);
}));
});
}
await this.task("Writing final package.json and product.json", () => {
return Promise.all([
fs.writeFile(path.join(finalBuildPath, "package.json"), JSON.stringify(packageJson, null, 2)),
fs.writeFile(path.join(finalBuildPath, "product.json"), JSON.stringify(productJson, null, 2)),
]);
});
// Prevent needless cache changes.
await this.task("Cleaning for smaller cache", () => {
return Promise.all([
fs.remove(serverPath),
fs.remove(path.join(vscodeSourcePath, "out-vscode")),
fs.remove(path.join(vscodeSourcePath, "out-vscode-min")),
fs.remove(path.join(vscodeSourcePath, "out-build")),
util.promisify(cp.exec)("git reset --hard", { cwd: vscodeSourcePath }).then(() => {
return util.promisify(cp.exec)("git clean -fd", { cwd: vscodeSourcePath });
}),
]);
});
// Prepend code to the target which enables finding files within the binary.
const prependLoader = async (relativeFilePath: string): Promise<void> => {
const filePath = path.join(finalBuildPath, relativeFilePath);
const shim = `
if (!global.NBIN_LOADED) {
try {
const nbin = require("nbin");
nbin.shimNativeFs("${finalBuildPath}");
global.NBIN_LOADED = true;
const path = require("path");
const rg = require("vscode-ripgrep");
rg.binaryRgPath = rg.rgPath;
rg.rgPath = path.join(require("os").tmpdir(), "code-server", path.basename(rg.binaryRgPath));
} catch (error) { /* Not in the binary. */ }
}
`;
await fs.writeFile(filePath, shim + (await fs.readFile(filePath, "utf8")));
};
await this.task("Prepending nbin loader", () => {
return Promise.all([
prependLoader("out/vs/server/main.js"),
prependLoader("out/bootstrap-fork.js"),
prependLoader("extensions/node_modules/typescript/lib/tsserver.js"),
]);
});
// onigasm 2.2.2 has a bug that makes it broken for PHP files so use 2.2.1.
// https://github.com/NeekSandhu/onigasm/issues/17
await this.task("Applying onigasm PHP fix", async () => {
const onigasmPath = path.join(finalBuildPath, "node_modules/onigasm-umd");
const onigasmTmpPath = `${onigasmPath}-temp`;
await Promise.all([
fs.remove(onigasmPath),
fs.mkdir(onigasmTmpPath),
]);
await util.promisify(cp.exec)(`git clone "https://github.com/alexandrudima/onigasm-umd" "${onigasmPath}"`);
await util.promisify(cp.exec)("yarn", { cwd: onigasmPath });
await util.promisify(cp.exec)("yarn add --dev onigasm@2.2.1", { cwd: onigasmPath });
await util.promisify(cp.exec)("yarn package", { cwd: onigasmPath });
await Promise.all(["release", "LICENSE", "package.json"].map((fileName) => {
return fs.copy(path.join(onigasmPath, fileName), path.join(onigasmTmpPath, fileName));
}));
await fs.remove(onigasmPath);
await fs.move(onigasmTmpPath, onigasmPath);
});
this.log(`Final build: ${finalBuildPath}`);
}
/**
* Bundles the built code into a binary.
*/
private async binary(targetPath: string, binariesPath: string, binaryName: string): Promise<void> {
const bin = new Binary({
mainFile: path.join(targetPath, "out/vs/server/main.js"),
target: await this.target(),
});
bin.writeFiles(path.join(targetPath, "**"));
await fs.mkdirp(binariesPath);
const binaryPath = path.join(binariesPath, binaryName);
await fs.writeFile(binaryPath, await bin.build());
await fs.chmod(binaryPath, "755");
this.log(`Binary: ${binaryPath}`);
}
/**
* Package the binary into a release archive.
*/
private async package(vscodeSourcePath: string, binariesPath: string, binaryName: string): Promise<void> {
const releasePath = path.join(this.outPath, "release");
const archivePath = path.join(releasePath, binaryName);
await fs.remove(archivePath);
await fs.mkdirp(archivePath);
await fs.copyFile(path.join(binariesPath, binaryName), path.join(archivePath, "code-server"));
await fs.copyFile(path.join(this.rootPath, "README.md"), path.join(archivePath, "README.md"));
await fs.copyFile(path.join(vscodeSourcePath, "LICENSE.txt"), path.join(archivePath, "LICENSE.txt"));
await fs.copyFile(path.join(vscodeSourcePath, "ThirdPartyNotices.txt"), path.join(archivePath, "ThirdPartyNotices.txt"));
if ((await this.target()) === "darwin") {
await util.promisify(cp.exec)(`zip -r "${binaryName}.zip" "${binaryName}"`, { cwd: releasePath });
this.log(`Archive: ${archivePath}.zip`);
} else {
await util.promisify(cp.exec)(`tar -czf "${binaryName}.tar.gz" "${binaryName}"`, { cwd: releasePath });
this.log(`Archive: ${archivePath}.tar.gz`);
}
}
}
const builder = new Builder();
builder.run(process.argv[2] as Task, process.argv.slice(3));

View File

@@ -1,79 +0,0 @@
#!/bin/bash
set -euo pipefail
function docker-build() {
local target="${TARGET:-}"
local image="codercom/nbin-${target}"
local token="${GITHUB_TOKEN:-}"
local minify="${MINIFY:-}"
if [[ "${target}" == "linux" ]] ; then
image="codercom/nbin-centos"
fi
local containerId
# Use a mount so we can cache the results.
containerId=$(docker create --network=host --rm -it -v "$(pwd)":/src "${image}")
docker start "${containerId}"
# TODO: Might be better to move these dependencies to the images or create new
# ones on top of these.
if [[ "${image}" == "codercom/nbin-alpine" ]] ; then
docker exec "${containerId}" apk add libxkbfile-dev libsecret-dev
else
docker exec "${containerId}" yum install -y libxkbfile-devel libsecret-devel git
fi
function docker-exec() {
local command="${1}" ; shift
local args="'${vscodeVersion}' '${codeServerVersion}'"
docker exec "${containerId}" \
bash -c "cd /src && CI=true GITHUB_TOKEN=${token} MINIFY=${minify} yarn ${command} ${args}"
}
docker-exec build
if [[ -n "${package}" ]] ; then
docker-exec binary
docker-exec package
fi
docker kill "${containerId}"
}
function local-build() {
function local-exec() {
local command="${1}" ; shift
CI=true yarn "${command}" "${vscodeVersion}" "${codeServerVersion}"
}
local-exec build
if [[ -n "${package}" ]] ; then
local-exec binary
local-exec package
fi
}
# Build code-server in the CI.
function main() {
cd "$(dirname "${0}")/.."
local codeServerVersion="${VERSION:-}"
local vscodeVersion="${VSCODE_VERSION:-}"
local ostype="${OSTYPE:-}"
local package="${PACKAGE:-}"
if [[ -z "${codeServerVersion}" ]] ; then
>&2 echo "Must set VERSION environment variable"; exit 1
fi
if [[ -z "${vscodeVersion}" ]] ; then
>&2 echo "Must set VSCODE_VERSION environment variable"; exit 1
fi
if [[ "${ostype}" == "darwin"* ]]; then
local-build
else
docker-build
fi
}
main "$@"

View File

@@ -1,38 +0,0 @@
# We deploy with ubuntu so that devs have a familiar environment.
FROM ubuntu:18.04
RUN apt-get update && apt-get install -y \
openssl \
net-tools \
git \
locales \
sudo \
dumb-init \
vim \
curl \
wget
RUN locale-gen en_US.UTF-8
# We cannot use update-locale because docker will not use the env variables
# configured in /etc/default/locale so we need to set it manually.
ENV LC_ALL=en_US.UTF-8 \
SHELL=/bin/bash
RUN adduser --gecos '' --disabled-password coder && \
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
USER coder
# We create first instead of just using WORKDIR as when WORKDIR creates, the
# user is root.
RUN mkdir -p /home/coder/project
WORKDIR /home/coder/project
# This ensures we have a volume mounted even if the user forgot to do bind
# mount. So that they do not lose their data if they delete the container.
VOLUME [ "/home/coder/project" ]
COPY ./binaries/code-server* /usr/local/bin/code-server
EXPOSE 8080
ENTRYPOINT ["dumb-init", "code-server", "--host", "0.0.0.0"]

View File

@@ -1,71 +0,0 @@
// This must be ran from VS Code's root.
const gulp = require("gulp");
const path = require("path");
const _ = require("underscore");
const buildfile = require("./src/buildfile");
const common = require("./build/lib/optimize");
const util = require("./build/lib/util");
const deps = require("./build/dependencies");
const vscodeEntryPoints = _.flatten([
buildfile.entrypoint("vs/workbench/workbench.web.api"),
buildfile.entrypoint("vs/server/src/node/cli"),
buildfile.base,
buildfile.workbenchWeb,
buildfile.workerExtensionHost,
buildfile.keyboardMaps,
buildfile.entrypoint('vs/platform/files/node/watcher/unix/watcherApp', ["vs/css", "vs/nls"]),
buildfile.entrypoint('vs/platform/files/node/watcher/nsfw/watcherApp', ["vs/css", "vs/nls"]),
buildfile.entrypoint('vs/workbench/services/extensions/node/extensionHostProcess', ["vs/css", "vs/nls"]),
]);
const vscodeResources = [
"out-build/vs/server/main.js",
"out-build/vs/server/src/node/uriTransformer.js",
"!out-build/vs/server/doc/**",
"out-build/vs/server/src/media/*",
"out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js",
"out-build/bootstrap.js",
"out-build/bootstrap-fork.js",
"out-build/bootstrap-amd.js",
"out-build/paths.js",
'out-build/vs/**/*.{svg,png,html}',
"!out-build/vs/code/browser/workbench/*.html",
'!out-build/vs/code/electron-browser/**',
"out-build/vs/base/common/performance.js",
"out-build/vs/base/node/languagePacks.js",
"out-build/vs/base/browser/ui/octiconLabel/octicons/**",
"out-build/vs/base/browser/ui/codiconLabel/codicon/**",
"out-build/vs/workbench/browser/media/*-theme.css",
"out-build/vs/workbench/contrib/debug/**/*.json",
"out-build/vs/workbench/contrib/externalTerminal/**/*.scpt",
"out-build/vs/workbench/contrib/webview/browser/pre/*.js",
"out-build/vs/**/markdown.css",
"out-build/vs/workbench/contrib/tasks/**/*.json",
"out-build/vs/platform/files/**/*.md",
"!**/test/**"
];
const rootPath = __dirname;
const nodeModules = ["electron", "original-fs"]
.concat(_.uniq(deps.getProductionDependencies(rootPath).map((d) => d.name)))
.concat(_.uniq(deps.getProductionDependencies(path.join(rootPath, "src/vs/server")).map((d) => d.name)))
.concat(Object.keys(process.binding("natives")).filter((n) => !/^_|\//.test(n)));
gulp.task("optimize", gulp.series(
util.rimraf("out-vscode"),
common.optimizeTask({
src: "out-build",
entryPoints: vscodeEntryPoints,
resources: vscodeResources,
loaderConfig: common.loaderConfig(nodeModules),
out: "out-vscode",
inlineAmdImages: true,
bundleInfo: undefined
}),
));
gulp.task("minify", gulp.series(
util.rimraf("out-vscode-min"),
common.minifyTask("out-vscode")
));

View File

@@ -1,5 +0,0 @@
{
"name": "code-server",
"main": "out/vs/server/main",
"desktopName": "code-server-url-handler.desktop"
}

View File

@@ -1,21 +0,0 @@
{
"nameShort": "code-server",
"nameLong": "code-server",
"applicationName": "code-server",
"dataFolderName": ".code-server",
"win32MutexName": "codeserver",
"win32DirName": "Code Server",
"win32NameVersion": "Code Server",
"win32RegValueName": "CodeServer",
"win32AppId": "",
"win32x64AppId": "",
"win32UserAppId": "",
"win32x64UserAppId": "",
"win32AppUserModelId": "CodeServer",
"win32ShellNameShort": "C&ode Server",
"darwinBundleIdentifier": "com.code.server",
"linuxIconName": "com.code.server",
"urlProtocol": "code-server",
"updateUrl": "https://api.github.com/repos/cdr/code-server/releases",
"quality": "latest"
}

View File

@@ -1,17 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": true,
"experimentalDecorators": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noImplicitThis": true,
"alwaysStrict": true,
"strictBindCallApply": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"target": "esnext"
}
}

View File

@@ -1,885 +0,0 @@
diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts
index 6d41e85e42..64f39687a4 100644
--- a/src/vs/base/common/network.ts
+++ b/src/vs/base/common/network.ts
@@ -96,12 +96,12 @@ class RemoteAuthoritiesImpl {
if (host && host.indexOf(':') !== -1) {
host = `[${host}]`;
}
- const port = this._ports[authority];
+ // NOTE@coder: Changed this to work against the current path.
const connectionToken = this._connectionTokens[authority];
return URI.from({
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
- authority: `${host}:${port}`,
- path: `/vscode-remote-resource`,
+ authority: window.location.host,
+ path: `${window.location.pathname.replace(/\/+$/, '')}/vscode-remote-resource`,
query: `path=${encodeURIComponent(uri.path)}&tkn=${encodeURIComponent(connectionToken)}`
});
}
diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts
index a657f4a4d9..66bd13dffa 100644
--- a/src/vs/base/common/platform.ts
+++ b/src/vs/base/common/platform.ts
@@ -56,6 +56,16 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
_isWeb = true;
_locale = navigator.language;
_language = _locale;
+ const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
+ const rawNlsConfig = el && el.getAttribute('data-settings');
+ if (rawNlsConfig) {
+ try {
+ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
+ _locale = nlsConfig.locale;
+ _translationsConfigFile = nlsConfig._translationsConfigFile;
+ _language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT;
+ } catch (error) { /* Oh well. */ }
+ }
} else if (typeof process === 'object') {
_isWindows = (process.platform === 'win32');
_isMacintosh = (process.platform === 'darwin');
diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts
index c52f7b3774..5635cfac8a 100644
--- a/src/vs/base/common/processes.ts
+++ b/src/vs/base/common/processes.ts
@@ -110,7 +110,9 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve
/^ELECTRON_.+$/,
/^GOOGLE_API_KEY$/,
/^VSCODE_.+$/,
- /^SNAP(|_.*)$/
+ /^SNAP(|_.*)$/,
+ /^NBIN_BYPASS$/,
+ /^LAUNCH_VSCODE$/
];
const envKeys = Object.keys(env);
envKeys
diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js
index 3ae24454cb..fac8679290 100644
--- a/src/vs/base/node/languagePacks.js
+++ b/src/vs/base/node/languagePacks.js
@@ -146,7 +146,10 @@ function factory(nodeRequire, path, fs, perf) {
function getLanguagePackConfigurations(userDataPath) {
const configFile = path.join(userDataPath, 'languagepacks.json');
try {
- return nodeRequire(configFile);
+ // NOTE@coder: Swapped require with readFile since require is cached and
+ // we don't restart the server-side portion of code-server when the
+ // language changes.
+ return JSON.parse(fs.readFileSync(configFile, "utf8"));
} catch (err) {
// Do nothing. If we can't read the file we have no
// language pack config.
diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts
index 990755c4f3..06449bb9cb 100644
--- a/src/vs/platform/environment/common/environment.ts
+++ b/src/vs/platform/environment/common/environment.ts
@@ -36,6 +36,8 @@ export interface ParsedArgs {
logExtensionHostCommunication?: boolean;
'extensions-dir'?: string;
'builtin-extensions-dir'?: string;
+ 'extra-extensions-dir'?: string[];
+ 'extra-builtin-extensions-dir'?: string[];
extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs
extensionTestsPath?: string; // either a local path or a URI
'extension-development-confirm-save'?: boolean;
@@ -169,4 +171,6 @@ export interface IEnvironmentService {
driverVerbose: boolean;
galleryMachineIdResource?: URI;
+ extraExtensionPaths: string[];
+ extraBuiltinExtensionPaths: string[];
}
diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts
index 3e48fe4ddd..2212ff5471 100644
--- a/src/vs/platform/environment/node/argv.ts
+++ b/src/vs/platform/environment/node/argv.ts
@@ -58,6 +58,8 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
'builtin-extensions-dir': { type: 'string' },
+ 'extra-builtin-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra builtin extension directory.' },
+ 'extra-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra user extension directory.' },
'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") },
@@ -185,7 +187,7 @@ export function parseArgs<T>(args: string[], options: OptionDescriptions<T>, err
delete parsedArgs[o.deprecates];
}
- if (val) {
+ if (typeof val !== 'undefined') {
if (o.type === 'string[]') {
if (val && !Array.isArray(val)) {
val = [val];
diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts
index f7d207009d..5c37b52dab 100644
--- a/src/vs/platform/environment/node/environmentService.ts
+++ b/src/vs/platform/environment/node/environmentService.ts
@@ -260,6 +260,12 @@ export class EnvironmentService implements IEnvironmentService {
get driverHandle(): string | undefined { return this._args['driver']; }
get driverVerbose(): boolean { return !!this._args['driver-verbose']; }
+ @memoize get extraExtensionPaths(): string[] {
+ return (this._args['extra-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
+ }
+ @memoize get extraBuiltinExtensionPaths(): string[] {
+ return (this._args['extra-builtin-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
+ }
constructor(private _args: ParsedArgs, private _execPath: string) {
if (!process.env['VSCODE_LOGS']) {
diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
index f0eaa74a59..3abf9e1752 100644
--- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts
+++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
@@ -731,11 +731,15 @@ export class ExtensionManagementService extends Disposable implements IExtension
private scanSystemExtensions(): Promise<ILocalExtension[]> {
this.logService.trace('Started scanning system extensions');
- const systemExtensionsPromise = this.scanExtensions(this.systemExtensionsPath, ExtensionType.System)
- .then(result => {
- this.logService.trace('Scanned system extensions:', result.length);
- return result;
- });
+ const systemExtensionsPromise = Promise.all([
+ this.scanExtensions(this.systemExtensionsPath, ExtensionType.System),
+ ...this.environmentService.extraBuiltinExtensionPaths
+ .map((path) => this.scanExtensions(path, ExtensionType.System))
+ ]).then((results) => {
+ const result = results.reduce((flat, current) => flat.concat(current), []);
+ this.logService.trace('Scanned system extensions:', result.length);
+ return result;
+ });
if (this.environmentService.isBuilt) {
return systemExtensionsPromise;
}
@@ -757,9 +761,16 @@ export class ExtensionManagementService extends Disposable implements IExtension
.then(([systemExtensions, devSystemExtensions]) => [...systemExtensions, ...devSystemExtensions]);
}
+ private scanAllUserExtensions(folderName: string, type: ExtensionType): Promise<ILocalExtension[]> {
+ return Promise.all([
+ this.scanExtensions(folderName, type),
+ ...this.environmentService.extraExtensionPaths.map((p) => this.scanExtensions(p, ExtensionType.User))
+ ]).then((results) => results.reduce((flat, current) => flat.concat(current), []));
+ }
+
private scanUserExtensions(excludeOutdated: boolean): Promise<ILocalExtension[]> {
this.logService.trace('Started scanning user extensions');
- return Promise.all([this.getUninstalledExtensions(), this.scanExtensions(this.extensionsPath, ExtensionType.User)])
+ return Promise.all([this.getUninstalledExtensions(), this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User)])
.then(([uninstalled, extensions]) => {
extensions = extensions.filter(e => !uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]);
if (excludeOutdated) {
@@ -774,6 +785,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
private scanExtensions(root: string, type: ExtensionType): Promise<ILocalExtension[]> {
const limiter = new Limiter<any>(10);
return pfs.readdir(root)
+ .catch((error) => {
+ if (error.code !== 'ENOENT') {
+ throw error;
+ }
+ return [];
+ })
.then(extensionsFolders => Promise.all<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
.then(extensions => extensions.filter(e => e && e.identifier));
}
@@ -812,7 +829,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
private async removeUninstalledExtensions(): Promise<void> {
const uninstalled = await this.getUninstalledExtensions();
- const extensions = await this.scanExtensions(this.extensionsPath, ExtensionType.User); // All user extensions
+ const extensions = await this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User); // All user extensions
const installed: Set<string> = new Set<string>();
for (const e of extensions) {
if (!uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]) {
@@ -831,7 +848,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
private removeOutdatedExtensions(): Promise<void> {
- return this.scanExtensions(this.extensionsPath, ExtensionType.User) // All user extensions
+ return this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User) // All user extensions
.then(extensions => {
const toRemove: ILocalExtension[] = [];
diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts
index 2de51e8d32..837770990e 100644
--- a/src/vs/platform/product/common/product.ts
+++ b/src/vs/platform/product/common/product.ts
@@ -22,10 +22,16 @@ if (isWeb) {
if (Object.keys(product).length === 0) {
assign(product, {
version: '1.39.0-dev',
+ codeServerVersion: 'dev',
nameLong: 'Visual Studio Code Web Dev',
nameShort: 'VSCode Web Dev'
});
}
+ const el = document.getElementById('vscode-remote-product-configuration');
+ const rawProductConfiguration = el && el.getAttribute('data-settings');
+ if (rawProductConfiguration) {
+ assign(product, JSON.parse(rawProductConfiguration));
+ }
}
// Node: AMD loader
@@ -35,7 +41,7 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === '
const rootPath = path.dirname(getPathFromAmdModule(require, ''));
product = assign({}, require.__$__nodeRequire(path.join(rootPath, 'product.json')) as IProductConfiguration);
- const pkg = require.__$__nodeRequire(path.join(rootPath, 'package.json')) as { version: string; };
+ const pkg = require.__$__nodeRequire(path.join(rootPath, 'package.json')) as { version: string; codeServerVersion: string; };
// Running out of sources
if (env['VSCODE_DEV']) {
@@ -47,7 +53,8 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === '
}
assign(product, {
- version: pkg.version
+ version: pkg.version,
+ codeServerVersion: pkg.codeServerVersion,
});
}
diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts
index 5aa5c32d7e..e4e7fd4174 100644
--- a/src/vs/platform/product/common/productService.ts
+++ b/src/vs/platform/product/common/productService.ts
@@ -15,6 +15,7 @@ export interface IProductService extends Readonly<IProductConfiguration> {
export interface IProductConfiguration {
readonly version: string;
+ readonly codeServerVersion: string;
readonly date?: string;
readonly quality?: string;
readonly commit?: string;
diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts
index d0f6e6b18a..1966fd297d 100644
--- a/src/vs/platform/remote/browser/browserSocketFactory.ts
+++ b/src/vs/platform/remote/browser/browserSocketFactory.ts
@@ -205,7 +205,8 @@ export class BrowserSocketFactory implements ISocketFactory {
}
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
- const socket = this._webSocketFactory.create(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`);
+ // NOTE@coder: Modified to work against the current path.
+ const socket = this._webSocketFactory.create(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}${window.location.pathname}?${query}&skipWebSocketFrames=false`);
const errorListener = socket.onError((err) => callback(err, undefined));
socket.onOpen(() => {
errorListener.dispose();
@@ -213,6 +214,3 @@ export class BrowserSocketFactory implements ISocketFactory {
});
}
}
-
-
-
diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts
index 8a1c95d37b..8225a85d47 100644
--- a/src/vs/platform/update/electron-main/abstractUpdateService.ts
+++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts
@@ -6,7 +6,6 @@
import { Event, Emitter } from 'vs/base/common/event';
import { timeout } from 'vs/base/common/async';
import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/configuration/common/configuration';
-import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import product from 'vs/platform/product/common/product';
import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -44,7 +43,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
}
constructor(
- @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
+ _: any, // NOTE@coder: This depends on Electron so we skip it.
@IConfigurationService protected configurationService: IConfigurationService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IRequestService protected requestService: IRequestService,
@@ -152,15 +151,8 @@ export abstract class AbstractUpdateService implements IUpdateService {
this.logService.trace('update#quitAndInstall(): before lifecycle quit()');
- this.lifecycleMainService.quit(true /* from update */).then(vetod => {
- this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`);
- if (vetod) {
- return;
- }
-
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
this.doQuitAndInstall();
- });
return Promise.resolve(undefined);
}
diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts
index 2905c52411..303ddf211f 100644
--- a/src/vs/workbench/api/browser/extensionHost.contribution.ts
+++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts
@@ -57,6 +57,7 @@ import './mainThreadComments';
import './mainThreadTask';
import './mainThreadLabelService';
import 'vs/workbench/api/common/apiCommands';
+import 'vs/server/src/browser/mainThreadNodeProxy';
export class ExtensionPoints implements IWorkbenchContribution {
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
index 230d09a290..84753e6ac7 100644
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
@@ -67,6 +67,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
+import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
export interface IExtensionApiFactory {
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
@@ -86,6 +87,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const rpcProtocol = accessor.get(IExtHostRpcService);
const extHostStorage = accessor.get(IExtHostStorage);
const extHostLogService = accessor.get(ILogService);
+ const extHostNodeProxy = accessor.get(IExtHostNodeProxy);
// register addressable instances
rpcProtocol.set(ExtHostContext.ExtHostLogService, <ExtHostLogServiceShape><any>extHostLogService);
@@ -93,6 +95,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration);
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage);
+ rpcProtocol.set(ExtHostContext.ExtHostNodeProxy, extHostNodeProxy);
// automatically create and register addressable instances
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations));
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
index 7bd6aa6cb7..7c28136366 100644
--- a/src/vs/workbench/api/common/extHost.protocol.ts
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
@@ -627,6 +627,10 @@ export interface MainThreadLabelServiceShape extends IDisposable {
$unregisterResourceLabelFormatter(handle: number): void;
}
+export interface MainThreadNodeProxyShape extends IDisposable {
+ $send(message: string): void;
+}
+
export interface MainThreadSearchShape extends IDisposable {
$registerFileSearchProvider(handle: number, scheme: string): void;
$registerTextSearchProvider(handle: number, scheme: string): void;
@@ -860,6 +864,13 @@ export interface ExtHostLabelServiceShape {
$registerResourceLabelFormatter(formatter: ResourceLabelFormatter): IDisposable;
}
+export interface ExtHostNodeProxyShape {
+ $onMessage(message: string): void;
+ $onClose(): void;
+ $onDown(): void;
+ $onUp(): void;
+}
+
export interface ExtHostSearchShape {
$provideFileSearchResults(handle: number, session: number, query: search.IRawQuery, token: CancellationToken): Promise<search.ISearchCompleteStats>;
$provideTextSearchResults(handle: number, session: number, query: search.IRawTextQuery, token: CancellationToken): Promise<search.ISearchCompleteStats>;
@@ -1384,7 +1395,8 @@ export const MainContext = {
MainThreadSearch: createMainId<MainThreadSearchShape>('MainThreadSearch'),
MainThreadTask: createMainId<MainThreadTaskShape>('MainThreadTask'),
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
- MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService')
+ MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService'),
+ MainThreadNodeProxy: createMainId<MainThreadNodeProxyShape>('MainThreadNodeProxy')
};
export const ExtHostContext = {
@@ -1418,5 +1430,6 @@ export const ExtHostContext = {
ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'),
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'),
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
- ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService')
+ ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
+ ExtHostNodeProxy: createMainId<ExtHostNodeProxyShape>('ExtHostNodeProxy')
};
diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts
index b5ce835e07..22be8516c1 100644
--- a/src/vs/workbench/api/common/extHostExtensionService.ts
+++ b/src/vs/workbench/api/common/extHostExtensionService.ts
@@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
-import { originalFSPath, joinPath } from 'vs/base/common/resources';
+import { originalFSPath } from 'vs/base/common/resources';
import { Barrier } from 'vs/base/common/async';
import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { TernarySearchTree } from 'vs/base/common/map';
@@ -32,6 +32,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
+import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
interface ITestRunner {
/** Old test runner API, as exported from `vscode/lib/testrunner` */
@@ -76,6 +77,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
protected readonly _extHostWorkspace: ExtHostWorkspace;
protected readonly _extHostConfiguration: ExtHostConfiguration;
protected readonly _logService: ILogService;
+ protected readonly _nodeProxy: IExtHostNodeProxy;
protected readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape;
protected readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape;
@@ -104,7 +106,8 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
@IExtHostConfiguration extHostConfiguration: IExtHostConfiguration,
@ILogService logService: ILogService,
@IExtHostInitDataService initData: IExtHostInitDataService,
- @IExtensionStoragePaths storagePath: IExtensionStoragePaths
+ @IExtensionStoragePaths storagePath: IExtensionStoragePaths,
+ @IExtHostNodeProxy nodeProxy: IExtHostNodeProxy,
) {
this._hostUtils = hostUtils;
this._extHostContext = extHostContext;
@@ -113,6 +116,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
this._extHostWorkspace = extHostWorkspace;
this._extHostConfiguration = extHostConfiguration;
this._logService = logService;
+ this._nodeProxy = nodeProxy;
this._disposables = new DisposableStore();
this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace);
@@ -331,14 +335,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
return Promise.all([
- this._loadCommonJSModule<IExtensionModule>(joinPath(extensionDescription.extensionLocation, extensionDescription.main), activationTimesBuilder),
+ this._loadCommonJSModule<IExtensionModule>(extensionDescription, activationTimesBuilder),
this._loadExtensionContext(extensionDescription)
]).then(values => {
return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder);
});
}
- protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
+ protected abstract _loadCommonJSModule<T>(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise<vscode.ExtensionContext> {
diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts
index a227d8a67b..92553a976c 100644
--- a/src/vs/workbench/api/node/extHost.services.ts
+++ b/src/vs/workbench/api/node/extHost.services.ts
@@ -26,6 +26,8 @@ import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionS
import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
+import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
+import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
// register singleton services
registerSingleton(ILogService, ExtHostLogService);
@@ -42,3 +44,19 @@ registerSingleton(IExtHostSearch, ExtHostSearch);
registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
registerSingleton(IExtHostStorage, ExtHostStorage);
+
+function NotImplementedProxy<T>(name: ServiceIdentifier<T>): { new(): T } {
+ return <any>class {
+ constructor() {
+ return new Proxy({}, {
+ get(target: any, prop: string | number) {
+ if (target[prop]) {
+ return target[prop];
+ }
+ throw new Error(`Not Implemented: ${name}->${String(prop)}`);
+ }
+ });
+ }
+ };
+}
+registerSingleton(IExtHostNodeProxy, class extends NotImplementedProxy(IExtHostNodeProxy) {});
diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts
index 49a8e254fd..99d233aed5 100644
--- a/src/vs/workbench/api/node/extHostExtensionService.ts
+++ b/src/vs/workbench/api/node/extHostExtensionService.ts
@@ -13,6 +13,8 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
+import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
+import { joinPath } from 'vs/base/common/resources';
class NodeModuleRequireInterceptor extends RequireInterceptor {
@@ -75,7 +77,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
};
}
- protected _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
+ protected _loadCommonJSModule<T>(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
+ if (!URI.isUri(module)) {
+ module = joinPath(module.extensionLocation, module.main!);
+ }
if (module.scheme !== Schemas.file) {
throw new Error(`Cannot load URI: '${module}', must be of file-scheme`);
}
diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts
index afd82468c0..289145be54 100644
--- a/src/vs/workbench/api/worker/extHostExtensionService.ts
+++ b/src/vs/workbench/api/worker/extHostExtensionService.ts
@@ -9,6 +9,9 @@ import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHost
import { endsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
+import { joinPath } from 'vs/base/common/resources';
+import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
+import { loadCommonJSModule } from 'vs/server/src/browser/worker';
class WorkerRequireInterceptor extends RequireInterceptor {
@@ -41,7 +44,14 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
await this._fakeModules.install();
}
- protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
+ protected async _loadCommonJSModule<T>(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
+ if (!URI.isUri(module) && module.extensionKind !== 'web') {
+ return loadCommonJSModule(module, activationTimesBuilder, this._nodeProxy, this._logService, this._fakeModules.getModule('vscode', module.extensionLocation));
+ }
+
+ if (!URI.isUri(module)) {
+ module = joinPath(module.extensionLocation, module.main!);
+ }
module = module.with({ path: ensureSuffix(module.path, '.js') });
const response = await fetch(module.toString(true));
@@ -57,7 +67,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
const _exports = {};
const _module = { exports: _exports };
const _require = (request: string) => {
- const result = this._fakeModules.getModule(request, module);
+ const result = this._fakeModules.getModule(request, <URI>module);
if (result === undefined) {
throw new Error(`Cannot load module '${request}'`);
}
diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts
index 005a025aa9..ab392630c0 100644
--- a/src/vs/workbench/browser/dnd.ts
+++ b/src/vs/workbench/browser/dnd.ts
@@ -32,6 +32,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo
import { withNullAsUndefined } from 'vs/base/common/types';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
+import { IUploadService } from 'vs/server/src/browser/upload';
export interface IDraggedResource {
resource: URI;
@@ -167,14 +168,15 @@ export class ResourcesDropHandler {
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService,
- @IHostService private readonly hostService: IHostService
+ @IHostService private readonly hostService: IHostService,
+ @IUploadService private readonly uploadService: IUploadService,
) {
}
async handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void> {
const untitledOrFileResources = extractResources(event).filter(r => this.fileService.canHandleResource(r.resource) || r.resource.scheme === Schemas.untitled);
if (!untitledOrFileResources.length) {
- return;
+ return this.uploadService.handleDrop(event, resolveTargetGroup, afterDrop, targetIndex);
}
// Make the window active to handle the drop properly within
diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts
index 84c46faa36..957e8412e1 100644
--- a/src/vs/workbench/browser/web.main.ts
+++ b/src/vs/workbench/browser/web.main.ts
@@ -48,6 +48,7 @@ import { toLocalISOString } from 'vs/base/common/date';
import { IndexedDBLogProvider } from 'vs/workbench/services/log/browser/indexedDBLogProvider';
import { InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider';
import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
+import { initialize } from 'vs/server/src/browser/client';
class BrowserMain extends Disposable {
@@ -84,6 +85,7 @@ class BrowserMain extends Disposable {
// Startup
workbench.startup();
+ await initialize(services.serviceCollection);
}
private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void {
@@ -238,6 +240,7 @@ class BrowserMain extends Disposable {
const channel = connection.getChannel<IChannel>(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment()));
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
+ fileService.registerProvider(Schemas.file, remoteFileSystemProvider);
if (!this.configuration.userDataProvider) {
const remoteUserDataUri = this.getRemoteUserDataUri();
diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts
index 53de865d8f..df234821a9 100644
--- a/src/vs/workbench/common/resources.ts
+++ b/src/vs/workbench/common/resources.ts
@@ -15,6 +15,7 @@ import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { withNullAsUndefined } from 'vs/base/common/types';
+import { Schemas } from 'vs/base/common/network';
export class ResourceContextKey extends Disposable implements IContextKey<URI> {
@@ -63,7 +64,7 @@ export class ResourceContextKey extends Disposable implements IContextKey<URI> {
set(value: URI | null) {
if (!ResourceContextKey._uriEquals(this._resourceKey.get(), value)) {
this._resourceKey.set(value);
- this._schemeKey.set(value ? value.scheme : null);
+ this._schemeKey.set(value ? (value.scheme === Schemas.vscodeRemote ? Schemas.file : value.scheme) : null);
this._filenameKey.set(value ? basename(value) : null);
this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value) : null);
this._extensionKey.set(value ? extname(value) : null);
@@ -200,4 +201,4 @@ export class ResourceGlobMatcher extends Disposable {
return !!expressionForRoot(resourcePathToMatch);
}
-}
\ No newline at end of file
+}
diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts
index 1f4cd95f65..061931cbde 100644
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts
+++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts
@@ -209,7 +209,7 @@ configurationRegistry.registerConfiguration({
'files.exclude': {
'type': 'object',
'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."),
- 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true },
+ 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true, '**/.code-server-partial-upload-*': true },
'scope': ConfigurationScope.RESOURCE,
'additionalProperties': {
'anyOf': [
diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
index cc4bcb28c5..98679a8b32 100644
--- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
+++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
@@ -47,6 +47,7 @@ import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/work
import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { Emitter } from 'vs/base/common/event';
+import { IUploadService } from 'vs/server/src/browser/upload';
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {
@@ -444,7 +445,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
@IInstantiationService private instantiationService: IInstantiationService,
@ITextFileService private textFileService: ITextFileService,
@IHostService private hostService: IHostService,
- @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService
+ @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
+ @IUploadService private readonly uploadService: IUploadService,
) {
this.toDispose = [];
@@ -605,6 +607,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
}
private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
+ return this.uploadService.handleExternalDrop(data, target, originalEvent);
const droppedResources = extractResources(originalEvent, true);
// Check for dropped external files to be folders
const result = await this.fileService.resolveAll(droppedResources);
diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js
index e6b9fd854b..a3d0a46e3a 100644
--- a/src/vs/workbench/contrib/webview/browser/pre/main.js
+++ b/src/vs/workbench/contrib/webview/browser/pre/main.js
@@ -308,7 +308,8 @@
} else {
// Rewrite vscode-resource in csp
if (data.endpoint) {
- csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:/g, data.endpoint));
+ // NOTE@coder: Add back the trailing slash so it'll work for sub-paths.
+ csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:/g, data.endpoint + "/"));
}
}
diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts
index 5f221e07ff..bfd592382c 100644
--- a/src/vs/workbench/services/environment/browser/environmentService.ts
+++ b/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -15,7 +15,6 @@ import { IPath, IPathsToWaitFor, IWindowConfiguration } from 'vs/platform/window
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
-import product from 'vs/platform/product/common/product';
export class BrowserWindowConfiguration implements IWindowConfiguration {
@@ -180,12 +179,13 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
driverHandle?: string;
driverVerbose: boolean;
galleryMachineIdResource?: URI;
+ extraExtensionPaths: string[];
+ extraBuiltinExtensionPaths: string[];
readonly logFile: URI;
get webviewExternalEndpoint(): string {
- // TODO: get fallback from product.json
- return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}')
- .replace('{{commit}}', product.commit || '211fa02efe8c041fd7baa8ec3dce199d5185aa44');
+ // NOTE@coder: Modified to work against the current URL.
+ return `${window.location.origin}${window.location.pathname.replace(/\/+$/, '')}/webview/`;
}
get webviewResourceRoot(): string {
diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts
index 000e5f7b4a..39f46e68a1 100644
--- a/src/vs/workbench/services/extensions/browser/extensionService.ts
+++ b/src/vs/workbench/services/extensions/browser/extensionService.ts
@@ -119,6 +119,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
} else {
// remote: only enabled and none-web'ish extension
+ localExtensions.push(...remoteEnv.extensions.filter(extension => this._isEnabled(extension) && isWebExtension(extension, this._configService)));
remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !isWebExtension(extension, this._configService));
this._checkEnableProposedApi(remoteEnv.extensions);
diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts
index 49b2d270c0..45200ccdbb 100644
--- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts
+++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts
@@ -12,7 +12,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
export function isWebExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean {
const extensionKind = getExtensionKind(manifest, configurationService);
- return extensionKind === 'web';
+ return extensionKind === 'web' || manifest.name === 'vim';
}
export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean {
diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
index 9f5a14f6cb..ca952f3d4d 100644
--- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
+++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
@@ -42,12 +42,13 @@ const args = minimist(process.argv.slice(2), {
const Module = require.__$__nodeRequire('module') as any;
const originalLoad = Module._load;
- Module._load = function (request: string) {
+ Module._load = function (request: string, parent: object, isMain: boolean) {
if (request === 'natives') {
throw new Error('Either the extension or a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more');
}
- return originalLoad.apply(this, arguments);
+ // NOTE@coder: Map node_module.asar requests to regular node_modules.
+ return originalLoad.apply(this, [request.replace(/node_modules\.asar(\.unpacked)?/, 'node_modules'), parent, isMain]);
};
})();
@@ -120,8 +121,11 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
// Wait for rich client to reconnect
protocol.onSocketClose(() => {
- // The socket has closed, let's give the renderer a certain amount of time to reconnect
- disconnectRunner1.schedule();
+ // NOTE@coder: Inform the server so we can manage offline
+ // connections there instead. Our goal is to persist connections
+ // forever (to a reasonable point) to account for things like
+ // hibernating overnight.
+ process.send!({ type: 'VSCODE_EXTHOST_DISCONNECTED' });
});
}
}
diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts
index 3bdfa1a79f..ded21cf9c6 100644
--- a/src/vs/workbench/services/extensions/worker/extHost.services.ts
+++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts
@@ -21,6 +21,7 @@ import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensio
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService';
+import { ExtHostNodeProxy, IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
// register singleton services
registerSingleton(ILogService, ExtHostLogService);
@@ -32,6 +33,7 @@ registerSingleton(IExtHostCommands, ExtHostCommands);
registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors);
registerSingleton(IExtHostStorage, ExtHostStorage);
registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
+registerSingleton(IExtHostNodeProxy, ExtHostNodeProxy);
// register services that only throw errors
function NotImplementedProxy<T>(name: ServiceIdentifier<T>): { new(): T } {
diff --git a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
index 99394090da..4891e0fece 100644
--- a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
+++ b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
@@ -5,17 +5,17 @@
import { createChannelSender } from 'vs/base/parts/ipc/node/ipc';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
-import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
+import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export class LocalizationsService {
_serviceBrand: undefined;
constructor(
- @ISharedProcessService sharedProcessService: ISharedProcessService,
+ @IRemoteAgentService remoteAgentService: IRemoteAgentService,
) {
- return createChannelSender<ILocalizationsService>(sharedProcessService.getChannel('localizations'));
+ return createChannelSender<ILocalizationsService>(remoteAgentService.getConnection()!.getChannel('localizations'));
}
}
diff --git a/src/vs/workbench/services/update/electron-browser/updateService.ts b/src/vs/workbench/services/update/electron-browser/updateService.ts
index b8f6558b2c..b1fe6b14fd 100644
--- a/src/vs/workbench/services/update/electron-browser/updateService.ts
+++ b/src/vs/workbench/services/update/electron-browser/updateService.ts
@@ -6,7 +6,7 @@
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event, Emitter } from 'vs/base/common/event';
import { IUpdateService, State } from 'vs/platform/update/common/update';
-import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
+import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export class NativeUpdateService implements IUpdateService {
@@ -21,8 +21,9 @@ export class NativeUpdateService implements IUpdateService {
private channel: IChannel;
- constructor(@IMainProcessService mainProcessService: IMainProcessService) {
- this.channel = mainProcessService.getChannel('update');
+ // NOTE@coder: patched to work in the browser.
+ constructor(@IRemoteAgentService remoteAgentService: IRemoteAgentService) {
+ this.channel = remoteAgentService.getConnection()!.getChannel('update');
// always set this._state as the state changes
this.onStateChange(state => this._state = state);
diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts
index fa9c9dd7a9..688d6c1934 100644
--- a/src/vs/workbench/workbench.web.main.ts
+++ b/src/vs/workbench/workbench.web.main.ts
@@ -34,11 +34,14 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService';
import 'vs/workbench/services/keybinding/browser/keymapService';
import 'vs/workbench/services/extensions/browser/extensionService';
import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService';
-import 'vs/workbench/services/telemetry/browser/telemetryService';
+// NOTE@coder: We send it all to the server side to be processed there instead.
+// import 'vs/workbench/services/telemetry/browser/telemetryService';
import 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
import 'vs/workbench/services/credentials/browser/credentialsService';
import 'vs/workbench/services/url/browser/urlService';
-import 'vs/workbench/services/update/browser/updateService';
+// NOTE@coder: Use the electron-browser version since it already comes with a
+// channel which lets us actually perform updates.
+import 'vs/workbench/services/update/electron-browser/updateService';
import 'vs/workbench/contrib/stats/browser/workspaceStatsService';
import 'vs/workbench/services/workspaces/browser/workspacesService';
import 'vs/workbench/services/workspaces/browser/workspaceEditingService';

View File

@@ -1,369 +0,0 @@
import * as vscode from "vscode";
import { CoderApi, VSCodeApi } from "../../typings/api";
import { createCSSRule } from "vs/base/browser/dom";
import { Emitter, Event } from "vs/base/common/event";
import { IDisposable } from "vs/base/common/lifecycle";
import { URI } from "vs/base/common/uri";
import { generateUuid } from "vs/base/common/uuid";
import { localize } from "vs/nls";
import { SyncActionDescriptor } from "vs/platform/actions/common/actions";
import { CommandsRegistry, ICommandService } from "vs/platform/commands/common/commands";
import { IConfigurationService } from "vs/platform/configuration/common/configuration";
import { IContextMenuService } from "vs/platform/contextview/browser/contextView";
import { FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions } from "vs/platform/files/common/files";
import { IInstantiationService, ServiceIdentifier } from "vs/platform/instantiation/common/instantiation";
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
import { INotificationService } from "vs/platform/notification/common/notification";
import { Registry } from "vs/platform/registry/common/platform";
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from "vs/workbench/services/statusbar/common/statusbar";
import { IStorageService } from "vs/platform/storage/common/storage";
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
import { IThemeService } from "vs/platform/theme/common/themeService";
import { IWorkspaceContextService } from "vs/platform/workspace/common/workspace";
import * as extHostTypes from "vs/workbench/api/common/extHostTypes";
import { CustomTreeView, CustomTreeViewPanel } from "vs/workbench/browser/parts/views/customView";
import { ViewContainerViewlet } from "vs/workbench/browser/parts/views/viewsViewlet";
import { Extensions as ViewletExtensions, ShowViewletAction, ViewletDescriptor, ViewletRegistry } from "vs/workbench/browser/viewlet";
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from "vs/workbench/common/actions";
import { Extensions as ViewsExtensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewContainersRegistry, IViewsRegistry, TreeItemCollapsibleState } from "vs/workbench/common/views";
import { IEditorGroupsService } from "vs/workbench/services/editor/common/editorGroupsService";
import { IEditorService } from "vs/workbench/services/editor/common/editorService";
import { IExtensionService } from "vs/workbench/services/extensions/common/extensions";
import { IWorkbenchLayoutService } from "vs/workbench/services/layout/browser/layoutService";
import { IViewletService } from "vs/workbench/services/viewlet/browser/viewlet";
/**
* Client-side implementation of VS Code's API.
* TODO: Views aren't quite working.
* TODO: Implement menu items for views (for item actions).
* TODO: File system provider doesn't work.
*/
export const vscodeApi = (serviceCollection: ServiceCollection): VSCodeApi => {
const getService = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T;
const commandService = getService(ICommandService);
const notificationService = getService(INotificationService);
const fileService = getService(IFileService);
const viewsRegistry = Registry.as<IViewsRegistry>(ViewsExtensions.ViewsRegistry);
const statusbarService = getService(IStatusbarService);
// It would be nice to just export what VS Code creates but it looks to me
// that it assumes it's running in the extension host and wouldn't work here.
// It is probably possible to create an extension host that runs in the
// browser's main thread, but I'm not sure how much jank that would require.
// We could have a web worker host but we want DOM access.
return {
EventEmitter: <any>Emitter, // It can take T so T | undefined should work.
FileSystemError: extHostTypes.FileSystemError,
FileType,
StatusBarAlignment: extHostTypes.StatusBarAlignment,
ThemeColor: extHostTypes.ThemeColor,
TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState,
Uri: URI,
commands: {
executeCommand: <T = any>(commandId: string, ...args: any[]): Promise<T | undefined> => {
return commandService.executeCommand(commandId, ...args);
},
registerCommand: (id: string, command: (...args: any[]) => any): IDisposable => {
return CommandsRegistry.registerCommand(id, command);
},
},
window: {
createStatusBarItem(alignmentOrOptions?: extHostTypes.StatusBarAlignment | vscode.window.StatusBarItemOptions, priority?: number): StatusBarEntry {
return new StatusBarEntry(statusbarService, alignmentOrOptions, priority);
},
registerTreeDataProvider: <T>(id: string, dataProvider: vscode.TreeDataProvider<T>): IDisposable => {
const tree = new TreeViewDataProvider(dataProvider);
const view = viewsRegistry.getView(id);
(view as ITreeViewDescriptor).treeView.dataProvider = tree;
return {
dispose: () => tree.dispose(),
};
},
showErrorMessage: async (message: string): Promise<string | undefined> => {
notificationService.error(message);
return undefined;
},
},
workspace: {
registerFileSystemProvider: (scheme: string, provider: vscode.FileSystemProvider): IDisposable => {
return fileService.registerProvider(scheme, new FileSystemProvider(provider));
},
},
};
};
/**
* Coder API. This should only provide functionality that can't be made
* available through the VS Code API.
*/
export const coderApi = (serviceCollection: ServiceCollection): CoderApi => {
const getService = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T;
return {
registerView: (viewId, viewName, containerId, containerName, icon): void => {
const cssClass = `extensionViewlet-${containerId}`;
const id = `workbench.view.extension.${containerId}`;
class CustomViewlet extends ViewContainerViewlet {
public constructor(
@IConfigurationService configurationService: IConfigurationService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@ITelemetryService telemetryService: ITelemetryService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IStorageService storageService: IStorageService,
@IEditorService _editorService: IEditorService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService,
) {
super(id, `${id}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
}
}
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(
new ViewletDescriptor(CustomViewlet as any, id, containerName, cssClass, undefined, URI.parse(icon)),
);
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registerWorkbenchAction(
new SyncActionDescriptor(OpenCustomViewletAction as any, id, localize("showViewlet", "Show {0}", containerName)),
"View: Show {0}",
localize("view", "View"),
);
// Generate CSS to show the icon in the activity bar.
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`;
createCSSRule(iconClass, `-webkit-mask: url('${icon}') no-repeat 50% 50%`);
const container = Registry.as<IViewContainersRegistry>(ViewsExtensions.ViewContainersRegistry).registerViewContainer(containerId);
Registry.as<IViewsRegistry>(ViewsExtensions.ViewsRegistry).registerViews([{
id: viewId,
name: viewName,
ctorDescriptor: { ctor: CustomTreeViewPanel },
treeView: getService(IInstantiationService).createInstance(CustomTreeView as any, viewId, container),
}] as ITreeViewDescriptor[], container);
},
};
};
class OpenCustomViewletAction extends ShowViewletAction {
public constructor(
id: string, label: string,
@IViewletService viewletService: IViewletService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
) {
super(id, label, id, viewletService, editorGroupService, layoutService);
}
}
class FileSystemProvider implements IFileSystemProvider {
private readonly _onDidChange = new Emitter<IFileChange[]>();
public readonly onDidChangeFile: Event<IFileChange[]> = this._onDidChange.event;
public readonly capabilities: FileSystemProviderCapabilities;
public readonly onDidChangeCapabilities: Event<void> = Event.None;
public constructor(private readonly provider: vscode.FileSystemProvider) {
this.capabilities = FileSystemProviderCapabilities.Readonly;
}
public watch(resource: URI, opts: IWatchOptions): IDisposable {
return this.provider.watch(resource, opts);
}
public async stat(resource: URI): Promise<IStat> {
return this.provider.stat(resource);
}
public async readFile(resource: URI): Promise<Uint8Array> {
return this.provider.readFile(resource);
}
public async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
return this.provider.writeFile(resource, content, opts);
}
public async delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
return this.provider.delete(resource, opts);
}
public mkdir(_resource: URI): Promise<void> {
throw new Error("not implemented");
}
public async readdir(resource: URI): Promise<[string, FileType][]> {
return this.provider.readDirectory(resource);
}
public async rename(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
return this.provider.rename(resource, target, opts);
}
public async copy(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
return this.provider.copy!(resource, target, opts);
}
public open(_resource: URI, _opts: FileOpenOptions): Promise<number> {
throw new Error("not implemented");
}
public close(_fd: number): Promise<void> {
throw new Error("not implemented");
}
public read(_fd: number, _pos: number, _data: Uint8Array, _offset: number, _length: number): Promise<number> {
throw new Error("not implemented");
}
public write(_fd: number, _pos: number, _data: Uint8Array, _offset: number, _length: number): Promise<number> {
throw new Error("not implemented");
}
}
class TreeViewDataProvider<T> implements ITreeViewDataProvider {
private readonly root = Symbol("root");
private readonly values = new Map<string, T>();
private readonly children = new Map<T | Symbol, ITreeItem[]>();
public constructor(private readonly provider: vscode.TreeDataProvider<T>) {}
public async getChildren(item?: ITreeItem): Promise<ITreeItem[]> {
const value = item && this.itemToValue(item);
const children = await Promise.all(
(await this.provider.getChildren(value) || [])
.map(async (childValue) => {
const treeItem = await this.provider.getTreeItem(childValue);
const handle = this.createHandle(treeItem);
this.values.set(handle, childValue);
return {
handle,
collapsibleState: TreeItemCollapsibleState.Collapsed,
};
})
);
this.clear(value || this.root, item);
this.children.set(value || this.root, children);
return children;
}
public dispose(): void {
throw new Error("not implemented");
}
private itemToValue(item: ITreeItem): T {
if (!this.values.has(item.handle)) {
throw new Error(`No element found with handle ${item.handle}`);
}
return this.values.get(item.handle)!;
}
private clear(value: T | Symbol, item?: ITreeItem): void {
if (this.children.has(value)) {
this.children.get(value)!.map((c) => this.clear(this.itemToValue(c), c));
this.children.delete(value);
}
if (item) {
this.values.delete(item.handle);
}
}
private createHandle(item: vscode.TreeItem): string {
return item.id
? `coder-tree-item-id/${item.id}`
: `coder-tree-item-uuid/${generateUuid()}`;
}
}
interface IStatusBarEntry extends IStatusbarEntry {
alignment: StatusbarAlignment;
priority?: number;
}
class StatusBarEntry implements vscode.StatusBarItem {
private static ID = 0;
private _id: number;
private entry: IStatusBarEntry;
private visible: boolean;
private disposed: boolean;
private statusId: string;
private statusName: string;
private accessor?: IStatusbarEntryAccessor;
private timeout: any;
constructor(private readonly statusbarService: IStatusbarService, alignmentOrOptions?: extHostTypes.StatusBarAlignment | vscode.window.StatusBarItemOptions, priority?: number) {
this._id = StatusBarEntry.ID--;
if (alignmentOrOptions && typeof alignmentOrOptions !== "number") {
this.statusId = alignmentOrOptions.id;
this.statusName = alignmentOrOptions.name;
this.entry = {
alignment: alignmentOrOptions.alignment === extHostTypes.StatusBarAlignment.Right
? StatusbarAlignment.RIGHT : StatusbarAlignment.LEFT,
priority,
text: "",
};
} else {
this.statusId = "web-api";
this.statusName = "Web API";
this.entry = {
alignment: alignmentOrOptions === extHostTypes.StatusBarAlignment.Right
? StatusbarAlignment.RIGHT : StatusbarAlignment.LEFT,
priority,
text: "",
};
}
}
public get alignment(): extHostTypes.StatusBarAlignment {
return this.entry.alignment === StatusbarAlignment.RIGHT
? extHostTypes.StatusBarAlignment.Right : extHostTypes.StatusBarAlignment.Left;
}
public get id(): number { return this._id; }
public get priority(): number | undefined { return this.entry.priority; }
public get text(): string { return this.entry.text; }
public get tooltip(): string | undefined { return this.entry.tooltip; }
public get color(): string | extHostTypes.ThemeColor | undefined { return this.entry.color; }
public get command(): string | undefined { return this.entry.command; }
public set text(text: string) { this.update({ text }); }
public set tooltip(tooltip: string | undefined) { this.update({ tooltip }); }
public set color(color: string | extHostTypes.ThemeColor | undefined) { this.update({ color }); }
public set command(command: string | undefined) { this.update({ command }); }
public show(): void {
this.visible = true;
this.update();
}
public hide(): void {
clearTimeout(this.timeout);
this.visible = false;
if (this.accessor) {
this.accessor.dispose();
this.accessor = undefined;
}
}
private update(values?: Partial<IStatusBarEntry>): void {
this.entry = { ...this.entry, ...values };
if (this.disposed || !this.visible) {
return;
}
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
if (!this.accessor) {
this.accessor = this.statusbarService.addEntry(this.entry, this.statusId, this.statusName, this.entry.alignment, this.priority);
} else {
this.accessor.update(this.entry);
}
}, 0);
}
public dispose(): void {
this.hide();
this.disposed = true;
}
}

View File

@@ -1,93 +0,0 @@
import { Emitter } from "vs/base/common/event";
import { URI } from "vs/base/common/uri";
import { registerSingleton } from "vs/platform/instantiation/common/extensions";
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
import { ILocalizationsService } from "vs/platform/localizations/common/localizations";
import { PersistentConnectionEventType } from "vs/platform/remote/common/remoteAgentConnection";
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
import { coderApi, vscodeApi } from "vs/server/src/browser/api";
import { IUploadService, UploadService } from "vs/server/src/browser/upload";
import { INodeProxyService, NodeProxyChannelClient } from "vs/server/src/common/nodeProxy";
import { TelemetryChannelClient } from "vs/server/src/common/telemetry";
import { split } from "vs/server/src/common/util";
import "vs/workbench/contrib/localizations/browser/localizations.contribution";
import { LocalizationsService } from "vs/workbench/services/localizations/electron-browser/localizationsService";
import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService";
class TelemetryService extends TelemetryChannelClient {
public constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
) {
super(remoteAgentService.getConnection()!.getChannel("telemetry"));
}
}
class NodeProxyService extends NodeProxyChannelClient implements INodeProxyService {
private readonly _onClose = new Emitter<void>();
public readonly onClose = this._onClose.event;
private readonly _onDown = new Emitter<void>();
public readonly onDown = this._onDown.event;
private readonly _onUp = new Emitter<void>();
public readonly onUp = this._onUp.event;
public constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
) {
super(remoteAgentService.getConnection()!.getChannel("nodeProxy"));
remoteAgentService.getConnection()!.onDidStateChange((state) => {
switch (state.type) {
case PersistentConnectionEventType.ConnectionGain:
return this._onUp.fire();
case PersistentConnectionEventType.ConnectionLost:
return this._onDown.fire();
case PersistentConnectionEventType.ReconnectionPermanentFailure:
return this._onClose.fire();
}
});
}
}
registerSingleton(ILocalizationsService, LocalizationsService);
registerSingleton(INodeProxyService, NodeProxyService);
registerSingleton(ITelemetryService, TelemetryService);
registerSingleton(IUploadService, UploadService, true);
/**
* This is called by vs/workbench/browser/web.main.ts after the workbench has
* been initialized so we can initialize our own client-side code.
*/
export const initialize = async (services: ServiceCollection): Promise<void> => {
const target = window as any;
target.ide = coderApi(services);
target.vscode = vscodeApi(services);
const event = new CustomEvent("ide-ready");
(event as any).ide = target.ide;
(event as any).vscode = target.vscode;
window.dispatchEvent(event);
};
export interface Query {
[key: string]: string | undefined;
}
/**
* Return the URL modified with the specified query variables. It's pretty
* stupid so it probably doesn't cover any edge cases. Undefined values will
* unset existing values. Doesn't allow duplicates.
*/
export const withQuery = (url: string, replace: Query): string => {
const uri = URI.parse(url);
const query = { ...replace };
uri.query.split("&").forEach((kv) => {
const [key, value] = split(kv, "=");
if (!(key in query)) {
query[key] = value;
}
});
return uri.with({
query: Object.keys(query)
.filter((k) => typeof query[k] !== "undefined")
.map((k) => `${k}=${query[k]}`).join("&"),
}).toString(true);
};

View File

@@ -1,46 +0,0 @@
import { Emitter } from "vs/base/common/event";
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ExtHostNodeProxyShape, MainContext, MainThreadNodeProxyShape } from "vs/workbench/api/common/extHost.protocol";
import { IExtHostRpcService } from "vs/workbench/api/common/extHostRpcService";
export class ExtHostNodeProxy implements ExtHostNodeProxyShape {
_serviceBrand: any;
private readonly _onMessage = new Emitter<string>();
public readonly onMessage = this._onMessage.event;
private readonly _onClose = new Emitter<void>();
public readonly onClose = this._onClose.event;
private readonly _onDown = new Emitter<void>();
public readonly onDown = this._onDown.event;
private readonly _onUp = new Emitter<void>();
public readonly onUp = this._onUp.event;
private readonly proxy: MainThreadNodeProxyShape;
constructor(@IExtHostRpcService rpc: IExtHostRpcService) {
this.proxy = rpc.getProxy(MainContext.MainThreadNodeProxy);
}
public $onMessage(message: string): void {
this._onMessage.fire(message);
}
public $onClose(): void {
this._onClose.fire();
}
public $onUp(): void {
this._onUp.fire();
}
public $onDown(): void {
this._onDown.fire();
}
public send(message: string): void {
this.proxy.$send(message);
}
}
export interface IExtHostNodeProxy extends ExtHostNodeProxy { }
export const IExtHostNodeProxy = createDecorator<IExtHostNodeProxy>('IExtHostNodeProxy');

View File

@@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'self' 'unsafe-inline'; script-src 'unsafe-inline'; manifest-src 'self'; img-src 'self';">
<title>Authenticate: code-server</title>
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./manifest.json">
<link rel="apple-touch-icon" href="./static/out/vs/server/src/media/code-server.png" />
<link href="./static/out/vs/server/src/media/login.css" rel="stylesheet">
</head>
<body>
<form class="login-form" method="post">
<h4 class="title">code-server</h4>
<h2 class="subtitle">
Enter server password
</h2>
<div class="field">
<!-- The onfocus code places the cursor at the end of the value. -->
<input name="password" type="password" class="input" value=""
required autofocus
onfocus="const value=this.value;this.value='';this.value=value;">
</div>
<button class="button" type="submit">
<span class="label">Enter IDE</span>
</button>
<div class="error-display" style="display:none">{{ERROR}}</div>
</form>
</body>
</html>

View File

@@ -1,37 +0,0 @@
import { IDisposable } from "vs/base/common/lifecycle";
import { INodeProxyService } from "vs/server/src/common/nodeProxy";
import { ExtHostContext, IExtHostContext, MainContext, MainThreadNodeProxyShape } from "vs/workbench/api/common/extHost.protocol";
import { extHostNamedCustomer } from "vs/workbench/api/common/extHostCustomers";
@extHostNamedCustomer(MainContext.MainThreadNodeProxy)
export class MainThreadNodeProxy implements MainThreadNodeProxyShape {
private disposed = false;
private disposables = <IDisposable[]>[];
constructor(
extHostContext: IExtHostContext,
@INodeProxyService private readonly proxyService: INodeProxyService,
) {
if (!extHostContext.remoteAuthority) { // HACK: A terrible way to detect if running in the worker.
const proxy = extHostContext.getProxy(ExtHostContext.ExtHostNodeProxy);
this.disposables = [
this.proxyService.onMessage((message: string) => proxy.$onMessage(message)),
this.proxyService.onClose(() => proxy.$onClose()),
this.proxyService.onDown(() => proxy.$onDown()),
this.proxyService.onUp(() => proxy.$onUp()),
];
}
}
$send(message: string): void {
if (!this.disposed) {
this.proxyService.send(message);
}
}
dispose(): void {
this.disposables.forEach((d) => d.dispose());
this.disposables = [];
this.disposed = true;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,40 @@
{
"name": "code-server",
"short_name": "code-server",
"start_url": "{{BASE}}",
"display": "fullscreen",
"background-color": "#fff",
"description": "Run editors on a remote server.",
"icons": [
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-96.png",
"type": "image/png",
"sizes": "96x96"
},
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-128.png",
"type": "image/png",
"sizes": "128x128"
},
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-256.png",
"type": "image/png",
"sizes": "256x256"
},
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png",
"type": "image/png",
"sizes": "384x384"
},
{
"src": "{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-512.png",
"type": "image/png",
"sizes": "512x512"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<meta
http-equiv="Content-Security-Policy"
content="style-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;"
/>
<title>code-server</title>
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png" />
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body>
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/pages/app.js"></script>
</body>
</html>

37
src/browser/pages/app.ts Normal file
View File

@@ -0,0 +1,37 @@
import { getOptions, normalize } from "../../common/util"
import { ApiEndpoint } from "../../common/http"
import "./error.css"
import "./global.css"
import "./home.css"
import "./login.css"
import "./update.css"
const options = getOptions()
const isInput = (el: Element): el is HTMLInputElement => {
return !!(el as HTMLInputElement).name
}
document.querySelectorAll("form").forEach((form) => {
if (!form.classList.contains("-x11")) {
return
}
form.addEventListener("submit", (event) => {
event.preventDefault()
const values: { [key: string]: string } = {}
Array.from(form.elements).forEach((element) => {
if (isInput(element)) {
values[element.name] = element.value
}
})
fetch(normalize(`${options.base}/api/${ApiEndpoint.process}`), {
method: "POST",
body: JSON.stringify(values),
})
})
})
// TEMP: Until we can get the real ready event.
const event = new CustomEvent("ide-ready")
window.dispatchEvent(event)

View File

@@ -0,0 +1,32 @@
.error-display {
box-sizing: border-box;
padding: 20px;
text-align: center;
}
.error-display > .header {
font-size: 6rem;
margin: 0;
}
.error-display > .body {
color: #444;
font-size: 1.2rem;
}
.error-display > .links {
margin-top: 16px;
}
.error-display > .links > .link {
color: rgb(87, 114, 245);
text-decoration: none;
}
.error-display > .links > .link:hover {
text-decoration: underline;
}
.error-display .success {
color: green;
}

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<meta http-equiv="Content-Security-Policy" content="style-src 'self'; manifest-src 'self'; img-src 'self' data:;" />
<title>{{ERROR_TITLE}} - code-server</title>
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png" />
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body>
<div class="center-container">
<div class="error-display">
<h2 class="header">{{ERROR_HEADER}}</h2>
<div class="body">
{{ERROR_BODY}}
</div>
<div class="links">
<a class="link" href="{{BASE}}{{TO}}">go home</a>
</div>
</div>
</div>
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
</body>
</html>

View File

@@ -0,0 +1,85 @@
html,
body,
#root {
height: 100%;
width: 100%;
}
body {
background: rgb(244, 247, 252);
color: #111;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol";
overflow: hidden;
}
input,
button {
font-family: inherit;
font-size: 1rem;
line-height: 1rem;
}
.-button {
background-color: rgb(87, 114, 245);
border-radius: 5px;
border: none;
box-sizing: border-box;
color: white;
cursor: pointer;
padding: 18px 20px;
text-decoration: none;
}
.center-container {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
min-height: 100%;
padding: 20px;
width: 100%;
}
.card-box {
background-color: rgb(250, 253, 258);
border-radius: 5px;
box-shadow: rgba(60, 66, 87, 0.117647) 0px 7px 14px 0px, rgba(0, 0, 0, 0.117647) 0px 3px 6px 0px;
max-width: 650px;
width: 100%;
}
.card-box > .header {
border-bottom: 1px solid #ddd;
color: #444;
padding: 30px;
}
.card-box > .header > .main {
margin: 0;
font-size: 1.5rem;
}
.card-box > .header > .sub {
color: #555;
margin-top: 10px;
}
.card-box > .content {
padding: 40px;
}
.card-box > .content > .none {
margin: 2px 0;
}
.card-box + .card-box {
margin-top: 26px;
}
canvas {
top: 0;
left: 0;
}

View File

@@ -0,0 +1,51 @@
.block-row {
display: flex;
}
.block-row > .item {
flex: 1;
margin: 2px 0;
}
.block-row > button.item {
background: none;
border: none;
cursor: pointer;
text-align: left;
}
.block-row > .item > .sub {
font-size: 0.95em;
}
.block-row .-link {
color: rgb(87, 114, 245);
display: block;
text-decoration: none;
}
.block-row .-link:hover {
text-decoration: underline;
}
.block-row > .item > .icon {
height: 1rem;
margin-right: 5px;
vertical-align: top;
width: 1rem;
}
.block-row > .item > .icon.-missing {
background-color: rgba(87, 114, 245, 0.2);
display: inline-block;
text-align: center;
}
.kill-form {
display: inline-block;
}
.kill-form > .kill {
border-radius: 3px;
padding: 2px 5px;
}

View File

@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<meta
http-equiv="Content-Security-Policy"
content="style-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;"
/>
<title>code-server</title>
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.pnggg" />
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body>
<div class="center-container">
<div class="card-box">
<div class="header">
<h2 class="main">Editors</h2>
<div class="sub">Choose an editor to launch below.</div>
</div>
<div class="content">
{{APP_LIST:EDITORS}}
</div>
</div>
<div class="card-box">
<div class="header">
<h2 class="main">Other</h2>
<div class="sub">Choose an application to launch below.</div>
</div>
<div class="content">
{{APP_LIST:OTHER}}
</div>
</div>
<div class="card-box">
<div class="header">
<h2 class="main">Version</h2>
<div class="sub">Version information and updates.</div>
</div>
<div class="content">
{{UPDATE:NAME}}
</div>
</div>
</div>
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/pages/app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,39 @@
body {
overflow: auto;
}
.login-form {
display: flex;
flex-direction: column;
flex: 1;
justify-content: center;
}
.login-form > .field {
display: flex;
flex-direction: row;
width: 100%;
}
.login-form > .error {
color: red;
margin-top: 16px;
}
.login-form > .field > .password {
background-color: rgb(244, 247, 252);
border-radius: 5px;
border: 1px solid #ddd;
box-sizing: border-box;
color: black;
flex: 1;
padding: 16px;
}
.login-form > .user {
display: none;
}
.login-form > .field > .submit {
margin-left: 20px;
}

View File

@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<meta
http-equiv="Content-Security-Policy"
content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;"
/>
<title>code-server login</title>
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png" />
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body>
<div class="center-container">
<div class="card-box">
<div class="header">
<h1 class="main">Welcome to code-server</h1>
<div class="sub">Please log in below. Check code-server's logs for the generated password.</div>
</div>
<div class="content">
<form class="login-form" method="post">
<input class="user" type="text" autocomplete="username" />
<input id="base" type="hidden" name="base" value="/" />
<div class="field">
<input
required
autofocus
class="password"
type="password"
placeholder="PASSWORD"
name="password"
autocomplete="current-password"
/>
<input class="submit -button" value="SUBMIT" type="submit" />
</div>
{{ERROR}}
</form>
</div>
</div>
</div>
</body>
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
<script>
const parts = window.location.pathname.replace(/^\//g, "").split("/")
parts[parts.length - 1] = "{{BASE}}"
const url = new URL(window.location.origin + "/" + parts.join("/"))
document.getElementById("base").value = url.pathname
</script>
</html>

View File

@@ -0,0 +1,26 @@
.update-form {
text-align: center;
}
.update-form > .cancel {
background-color: red;
}
.update-form > .error {
color: red;
margin-top: 16px;
}
.update-form > .links {
margin-top: 20px;
}
.update-form > .links > .link {
color: rgb(87, 114, 245);
text-align: center;
text-decoration: none;
}
.update-form > .links > .link:hover {
text-decoration: underline;
}

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<meta http-equiv="Content-Security-Policy" content="style-src 'self'; manifest-src 'self'; img-src 'self' data:;" />
<title>code-server</title>
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png" />
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body>
<div class="center-container">
<div class="card-box">
<div class="header">
<h1 class="main">Update</h1>
<div class="sub">Update code-server.</div>
</div>
<div class="content">
<form class="update-form" action="{{BASE}}/update/apply">
{{UPDATE_STATUS}} {{ERROR}}
<div class="links">
<a class="link" href="{{BASE}}{{TO}}">go home</a>
</div>
</form>
</div>
</div>
</div>
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
</body>
</html>

View File

@@ -0,0 +1,103 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
http-equiv="Content-Security-Policy"
content="font-src 'self'; connect-src ws: wss: 'self' https:; default-src ws: wss: 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; manifest-src 'self'; img-src 'self' data: https:;"
/>
<!-- Disable pinch zooming -->
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}" />
<!-- Workarounds/Hacks (remote user data uri) -->
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}" />
<meta id="vscode-remote-product-configuration" data-settings="{{PRODUCT_CONFIGURATION}}" />
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}" />
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link
rel="manifest"
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
<!-- PROD_ONLY
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/workbench/workbench.web.api.css">
END_PROD_ONLY -->
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Prefetch to avoid waterfall -->
<!-- PROD_ONLY
<link rel="prefetch" href="{{BASE}}/static/{{COMMIT}}/lib/vscode/node_modules/semver-umd/lib/semver-umd.js">
END_PROD_ONLY -->
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body aria-label=""></body>
<!-- Startup (do not modify order of script tags!) -->
<script>
const parts = window.location.pathname.replace(/^\//g, "").split("/")
parts[parts.length - 1] = "{{BASE}}"
const url = new URL(window.location.origin + "/" + parts.join("/"))
const staticBase = url.href.replace(/\/+$/, "") + "/static/{{COMMIT}}/lib/vscode"
let nlsConfig
try {
nlsConfig = JSON.parse(document.getElementById("vscode-remote-nls-configuration").getAttribute("data-settings"))
if (nlsConfig._resolvedLanguagePackCoreLocation) {
const bundles = Object.create(null)
nlsConfig.loadBundle = (bundle, language, cb) => {
let result = bundles[bundle]
if (result) {
return cb(undefined, result)
}
// FIXME: Only works if path separators are /.
const path = nlsConfig._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json"
fetch(`${url.href}/resource/?path=${encodeURIComponent(path)}`)
.then((response) => response.json())
.then((json) => {
bundles[bundle] = json
cb(undefined, json)
})
.catch(cb)
}
}
} catch (error) {
/* Probably fine. */
}
self.require = {
baseUrl: `${staticBase}/out`,
paths: {
"vscode-textmate": `${staticBase}/node_modules/vscode-textmate/release/main`,
"onigasm-umd": `${staticBase}/node_modules/onigasm-umd/release/main`,
xterm: `${staticBase}/node_modules/xterm/lib/xterm.js`,
"xterm-addon-search": `${staticBase}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
"xterm-addon-unicode11": `${staticBase}/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
"xterm-addon-web-links": `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
"xterm-addon-webgl": `${staticBase}/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
"semver-umd": `${staticBase}/node_modules/semver-umd/lib/semver-umd.js`,
},
"vs/nls": nlsConfig,
}
</script>
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/dist/register.js"></script>
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/loader.js"></script>
<!-- PROD_ONLY
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/workbench/workbench.web.api.nls.js"></script>
<script data-cfasync="false" src="{{BASE}}/static/{{COMMIT}}/lib/vscode/out/vs/workbench/workbench.web.api.js"></script>
END_PROD_ONLY -->
<script>
require(["vs/code/browser/workbench/workbench"], function() {})
</script>
</html>

14
src/browser/register.ts Normal file
View File

@@ -0,0 +1,14 @@
import { getOptions, normalize } from "../common/util"
const options = getOptions()
if ("serviceWorker" in navigator) {
const path = normalize(`${options.base}/static/${options.commit}/dist/serviceWorker.js`)
navigator.serviceWorker
.register(path, {
scope: options.base || "/",
})
.then(function() {
console.log("[Service Worker] registered")
})
}

View File

@@ -0,0 +1,24 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
self.addEventListener("install", () => {
console.log("[Service Worker] install")
})
self.addEventListener("activate", (event: any) => {
event.waitUntil((self as any).clients.claim())
})
self.addEventListener("fetch", (event: any) => {
if (!navigator.onLine) {
event.respondWith(
new Promise((resolve) => {
resolve(
new Response("OFFLINE", {
status: 200,
statusText: "OK",
}),
)
}),
)
}
})

204
src/browser/socket.ts Normal file
View File

@@ -0,0 +1,204 @@
import { field, logger, Logger } from "@coder/logger"
import { Emitter } from "../common/emitter"
import { generateUuid } from "../common/util"
const decoder = new TextDecoder("utf8")
export const decode = (buffer: string | ArrayBuffer): string => {
return typeof buffer !== "string" ? decoder.decode(buffer) : buffer
}
/**
* A web socket that reconnects itself when it closes. Sending messages while
* disconnected will throw an error.
*/
export class ReconnectingSocket {
protected readonly _onMessage = new Emitter<string | ArrayBuffer>()
public readonly onMessage = this._onMessage.event
protected readonly _onDisconnect = new Emitter<number | undefined>()
public readonly onDisconnect = this._onDisconnect.event
protected readonly _onClose = new Emitter<number | undefined>()
public readonly onClose = this._onClose.event
protected readonly _onConnect = new Emitter<void>()
public readonly onConnect = this._onConnect.event
// This helps distinguish messages between sockets.
private readonly logger: Logger
private socket?: WebSocket
private connecting?: Promise<void>
private closed = false
private readonly openTimeout = 10000
// Every time the socket fails to connect, the retry will be increasingly
// delayed up to a maximum.
private readonly retryBaseDelay = 1000
private readonly retryMaxDelay = 10000
private retryDelay?: number
private readonly retryDelayFactor = 1.5
// The socket must be connected for this amount of time before resetting the
// retry delay. This prevents rapid retries when the socket does connect but
// is closed shortly after.
private resetRetryTimeout?: NodeJS.Timeout
private readonly resetRetryDelay = 10000
private _binaryType: typeof WebSocket.prototype.binaryType = "arraybuffer"
public constructor(private path: string, public readonly id: string = generateUuid(4)) {
// On Firefox the socket seems to somehow persist a page reload so the close
// event runs and we see "attempting to reconnect".
if (typeof window !== "undefined") {
window.addEventListener("beforeunload", () => this.close())
}
this.logger = logger.named(this.id)
}
public set binaryType(b: typeof WebSocket.prototype.binaryType) {
this._binaryType = b
if (this.socket) {
this.socket.binaryType = b
}
}
/**
* Permanently close the connection. Will not attempt to reconnect. Will
* remove event listeners.
*/
public close(code?: number): void {
if (this.closed) {
return
}
if (code) {
this.logger.info(`closing with code ${code}`)
}
if (this.resetRetryTimeout) {
clearTimeout(this.resetRetryTimeout)
}
this.closed = true
if (this.socket) {
this.socket.close()
} else {
this._onClose.emit(code)
}
}
public dispose(): void {
this._onMessage.dispose()
this._onDisconnect.dispose()
this._onClose.dispose()
this._onConnect.dispose()
this.logger.debug("disposed handlers")
}
/**
* Send a message on the socket. Logs an error if currently disconnected.
*/
public send(message: string | ArrayBuffer): void {
this.logger.trace(() => ["sending message", field("message", decode(message))])
if (!this.socket) {
return logger.error("tried to send message on closed socket")
}
this.socket.send(message)
}
/**
* Connect to the socket. Can also be called to wait until the connection is
* established in the case of disconnections. Multiple calls will be handled
* correctly.
*/
public async connect(): Promise<void> {
if (!this.connecting) {
this.connecting = new Promise((resolve, reject) => {
const tryConnect = (): void => {
if (this.closed) {
return reject(new Error("disconnected")) // Don't keep trying if we've closed permanently.
}
if (typeof this.retryDelay === "undefined") {
this.retryDelay = 0
} else {
this.retryDelay = this.retryDelay * this.retryDelayFactor || this.retryBaseDelay
if (this.retryDelay > this.retryMaxDelay) {
this.retryDelay = this.retryMaxDelay
}
}
this._connect()
.then((socket) => {
this.logger.info("connected")
this.socket = socket
this.socket.binaryType = this._binaryType
if (this.resetRetryTimeout) {
clearTimeout(this.resetRetryTimeout)
}
this.resetRetryTimeout = setTimeout(() => (this.retryDelay = undefined), this.resetRetryDelay)
this.connecting = undefined
this._onConnect.emit()
resolve()
})
.catch((error) => {
this.logger.error(`failed to connect: ${error.message}`)
tryConnect()
})
}
tryConnect()
})
}
return this.connecting
}
private async _connect(): Promise<WebSocket> {
const socket = await new Promise<WebSocket>((resolve, _reject) => {
if (this.retryDelay) {
this.logger.info(`retrying in ${this.retryDelay}ms...`)
}
setTimeout(() => {
this.logger.info("connecting...", field("path", this.path))
const socket = new WebSocket(this.path)
const reject = (): void => {
_reject(new Error("socket closed"))
}
const timeout = setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
socket.removeEventListener("open", open)
socket.removeEventListener("close", reject)
_reject(new Error("timeout"))
}, this.openTimeout)
const open = (): void => {
clearTimeout(timeout)
socket.removeEventListener("close", reject)
resolve(socket)
}
socket.addEventListener("open", open)
socket.addEventListener("close", reject)
}, this.retryDelay)
})
socket.addEventListener("message", (event) => {
this.logger.trace(() => ["got message", field("message", decode(event.data))])
this._onMessage.emit(event.data)
})
socket.addEventListener("close", (event) => {
this.socket = undefined
if (!this.closed) {
this._onDisconnect.emit(event.code)
// It might be closed in the event handler.
if (!this.closed) {
this.logger.info("connection closed; attempting to reconnect")
this.connect()
}
} else {
this._onClose.emit(event.code)
this.logger.info("connection closed permanently")
}
})
return socket
}
}

View File

@@ -1,372 +0,0 @@
import { DesktopDragAndDropData } from "vs/base/browser/ui/list/listView";
import { VSBuffer, VSBufferReadableStream } from "vs/base/common/buffer";
import { Disposable } from "vs/base/common/lifecycle";
import * as path from "vs/base/common/path";
import { URI } from "vs/base/common/uri";
import { generateUuid } from "vs/base/common/uuid";
import { IFileService } from "vs/platform/files/common/files";
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from "vs/platform/notification/common/notification";
import { IProgress, IProgressService, IProgressStep, ProgressLocation } from "vs/platform/progress/common/progress";
import { IWorkspaceContextService } from "vs/platform/workspace/common/workspace";
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { ExplorerItem } from "vs/workbench/contrib/files/common/explorerModel";
import { IEditorGroup } from "vs/workbench/services/editor/common/editorGroupsService";
import { IEditorService } from "vs/workbench/services/editor/common/editorService";
export const IUploadService = createDecorator<IUploadService>("uploadService");
export interface IUploadService {
_serviceBrand: undefined;
handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void>;
handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void>;
}
export class UploadService extends Disposable implements IUploadService {
public _serviceBrand: undefined;
public upload: Upload;
public constructor(
@IInstantiationService instantiationService: IInstantiationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@IEditorService private readonly editorService: IEditorService,
) {
super();
this.upload = instantiationService.createInstance(Upload);
}
public async handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void> {
// TODO: should use the workspace for the editor it was dropped on?
const target = this.contextService.getWorkspace().folders[0].uri;
const uris = (await this.upload.uploadDropped(event, target)).map((u) => URI.file(u));
if (uris.length > 0) {
await this.workspacesService.addRecentlyOpened(uris.map((u) => ({ fileUri: u })));
}
const editors = uris.map((uri) => ({
resource: uri,
options: {
pinned: true,
index: targetIndex,
},
}));
const targetGroup = resolveTargetGroup();
this.editorService.openEditors(editors, targetGroup);
afterDrop(targetGroup);
}
public async handleExternalDrop(_data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
await this.upload.uploadDropped(originalEvent, target.resource);
}
}
/**
* There doesn't seem to be a provided type for entries, so here is an
* incomplete version.
*/
interface IEntry {
name: string;
isFile: boolean;
file: (cb: (file: File) => void) => void;
createReader: () => ({
readEntries: (cb: (entries: Array<IEntry>) => void) => void;
});
}
/**
* Handles file uploads.
*/
class Upload {
private readonly maxParallelUploads = 100;
private readonly uploadingFiles = new Map<string, Reader | undefined>();
private readonly fileQueue = new Map<string, File>();
private progress: IProgress<IProgressStep> | undefined;
private uploadPromise: Promise<string[]> | undefined;
private resolveUploadPromise: (() => void) | undefined;
private uploadedFilePaths = <string[]>[];
private _total = 0;
private _uploaded = 0;
private lastPercent = 0;
public constructor(
@INotificationService private notificationService: INotificationService,
@IProgressService private progressService: IProgressService,
@IFileService private fileService: IFileService,
) {}
/**
* Upload dropped files. This will try to upload everything it can. Errors
* will show via notifications. If an upload operation is ongoing, the files
* will be added to that operation.
*/
public async uploadDropped(event: DragEvent, uploadDir: URI): Promise<string[]> {
await this.queueFiles(event, uploadDir);
if (!this.uploadPromise) {
this.uploadPromise = this.progressService.withProgress({
cancellable: true,
location: ProgressLocation.Notification,
title: "Uploading files...",
}, (progress) => {
return new Promise((resolve): void => {
this.progress = progress;
this.resolveUploadPromise = (): void => {
const uploaded = this.uploadedFilePaths;
this.uploadPromise = undefined;
this.resolveUploadPromise = undefined;
this.uploadedFilePaths = [];
this.lastPercent = 0;
this._uploaded = 0;
this._total = 0;
resolve(uploaded);
};
});
}, () => this.cancel());
}
this.uploadFiles();
return this.uploadPromise;
}
/**
* Cancel all file uploads.
*/
public async cancel(): Promise<void> {
this.fileQueue.clear();
this.uploadingFiles.forEach((r) => r && r.abort());
}
private get total(): number { return this._total; }
private set total(total: number) {
this._total = total;
this.updateProgress();
}
private get uploaded(): number { return this._uploaded; }
private set uploaded(uploaded: number) {
this._uploaded = uploaded;
this.updateProgress();
}
private updateProgress(): void {
if (this.progress && this.total > 0) {
const percent = Math.floor((this.uploaded / this.total) * 100);
this.progress.report({ increment: percent - this.lastPercent });
this.lastPercent = percent;
}
}
/**
* Upload as many files as possible. When finished, resolve the upload
* promise.
*/
private uploadFiles(): void {
while (this.fileQueue.size > 0 && this.uploadingFiles.size < this.maxParallelUploads) {
const [path, file] = this.fileQueue.entries().next().value;
this.fileQueue.delete(path);
if (this.uploadingFiles.has(path)) {
this.notificationService.error(new Error(`Already uploading ${path}`));
} else {
this.uploadingFiles.set(path, undefined);
this.uploadFile(path, file).catch((error) => {
this.notificationService.error(error);
}).finally(() => {
this.uploadingFiles.delete(path);
this.uploadFiles();
});
}
}
if (this.fileQueue.size === 0 && this.uploadingFiles.size === 0) {
this.resolveUploadPromise!();
}
}
/**
* Upload a file, asking to override if necessary.
*/
private async uploadFile(filePath: string, file: File): Promise<void> {
const uri = URI.file(filePath);
if (await this.fileService.exists(uri)) {
const overwrite = await new Promise<boolean>((resolve): void => {
this.notificationService.prompt(
Severity.Error,
`${filePath} already exists. Overwrite?`,
[
{ label: "Yes", run: (): void => resolve(true) },
{ label: "No", run: (): void => resolve(false) },
],
{ onCancel: () => resolve(false) },
);
});
if (!overwrite) {
return;
}
}
const tempUri = uri.with({
path: path.join(
path.dirname(uri.path),
`.code-server-partial-upload-${path.basename(uri.path)}-${generateUuid()}`,
),
});
const reader = new Reader(file);
reader.on("data", (data) => {
if (data && data.byteLength > 0) {
this.uploaded += data.byteLength;
}
});
this.uploadingFiles.set(filePath, reader);
await this.fileService.writeFile(tempUri, reader);
if (reader.aborted) {
this.uploaded += (file.size - reader.offset);
await this.fileService.del(tempUri);
} else {
await this.fileService.move(tempUri, uri, true);
this.uploadedFilePaths.push(filePath);
}
}
/**
* Queue files from a drop event. We have to get the files first; we can't do
* it in tandem with uploading or the entries will disappear.
*/
private async queueFiles(event: DragEvent, uploadDir: URI): Promise<void> {
const promises: Array<Promise<void>> = [];
for (let i = 0; event.dataTransfer && event.dataTransfer.items && i < event.dataTransfer.items.length; ++i) {
const item = event.dataTransfer.items[i];
if (typeof item.webkitGetAsEntry === "function") {
promises.push(this.traverseItem(item.webkitGetAsEntry(), uploadDir.fsPath));
} else {
const file = item.getAsFile();
if (file) {
this.addFile(uploadDir.fsPath + "/" + file.name, file);
}
}
}
await Promise.all(promises);
}
/**
* Traverses an entry and add files to the queue.
*/
private async traverseItem(entry: IEntry, path: string): Promise<void> {
if (entry.isFile) {
return new Promise<void>((resolve): void => {
entry.file((file) => {
resolve(this.addFile(path + "/" + file.name, file));
});
});
}
path += "/" + entry.name;
await new Promise((resolve): void => {
const promises: Array<Promise<void>> = [];
const dirReader = entry.createReader();
// According to the spec, readEntries() must be called until it calls
// the callback with an empty array.
const readEntries = (): void => {
dirReader.readEntries((entries) => {
if (entries.length === 0) {
Promise.all(promises).then(resolve).catch((error) => {
this.notificationService.error(error);
resolve();
});
} else {
promises.push(...entries.map((c) => this.traverseItem(c, path)));
readEntries();
}
});
};
readEntries();
});
}
/**
* Add a file to the queue.
*/
private addFile(path: string, file: File): void {
this.total += file.size;
this.fileQueue.set(path, file);
}
}
class Reader implements VSBufferReadableStream {
private _offset = 0;
private readonly size = 32000; // ~32kb max while reading in the file.
private _aborted = false;
private readonly reader = new FileReader();
private paused = true;
private buffer?: VSBuffer;
private callbacks = new Map<string, Array<(...args: any[]) => void>>();
public constructor(private readonly file: File) {
this.reader.addEventListener("load", this.onLoad);
}
public get offset(): number { return this._offset; }
public get aborted(): boolean { return this._aborted; }
public on(event: "data" | "error" | "end", callback: (...args:any[]) => void): void {
if (!this.callbacks.has(event)) {
this.callbacks.set(event, []);
}
this.callbacks.get(event)!.push(callback);
if (this.aborted) {
return this.emit("error", new Error("stream has been aborted"));
} else if (this.done) {
return this.emit("error", new Error("stream has ended"));
} else if (event === "end") { // Once this is being listened to we can safely start outputting data.
this.resume();
}
}
public abort = (): void => {
this._aborted = true;
this.reader.abort();
this.reader.removeEventListener("load", this.onLoad);
this.emit("end");
}
public pause(): void {
this.paused = true;
}
public resume(): void {
if (this.paused) {
this.paused = false;
this.readNextChunk();
}
}
public destroy(): void {
this.abort();
}
private onLoad = (): void => {
this.buffer = VSBuffer.wrap(new Uint8Array(this.reader.result as ArrayBuffer));
if (!this.paused) {
this.readNextChunk();
}
}
private readNextChunk(): void {
if (this.buffer) {
this._offset += this.buffer.byteLength;
this.emit("data", this.buffer);
this.buffer = undefined;
}
if (!this.paused) { // Could be paused during the data event.
if (this.done) {
this.emit("end");
} else {
this.reader.readAsArrayBuffer(this.file.slice(this.offset, this.offset + this.size));
}
}
}
private emit(event: "data" | "error" | "end", ...args: any[]): void {
if (this.callbacks.has(event)) {
this.callbacks.get(event)!.forEach((cb) => cb(...args));
}
}
private get done(): boolean {
return this.offset >= this.file.size;
}
}

View File

@@ -1,109 +0,0 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- Content Security Policy -->
<meta
http-equiv="Content-Security-Policy"
content="
default-src 'self';
img-src 'self' https: data: blob:;
media-src 'none';
script-src 'self' 'unsafe-eval' https: 'sha256-bpJydy1E+3Mx9MyBtkOIA3yyzM2wdyIz115+Sgq21+0=' 'sha256-meDZW3XhN5JmdjFUrWGhTouRKBiWYtXHltaKnqn/WMo=';
child-src 'self';
frame-src 'self';
worker-src 'self';
style-src 'self' 'unsafe-inline';
connect-src 'self' ws: wss: https:;
font-src 'self' blob:;
manifest-src 'self';
">
<!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
<!-- Workarounds/Hacks (remote user data uri) -->
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}">
<!-- NOTE@coder: Added the commit for use in caching, the product for the
extensions gallery URL, and nls for language support. -->
<meta id="vscode-remote-commit" data-settings="{{COMMIT}}">
<meta id="vscode-remote-product-configuration" data-settings="{{PRODUCT_CONFIGURATION}}">
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./manifest.json">
<link rel="apple-touch-icon" href="./static-{{COMMIT}}/out/vs/server/src/media/code-server.png" />
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.css">
<!-- Prefetch to avoid waterfall -->
<link rel="prefetch" href="./static-{{COMMIT}}/node_modules/semver-umd/lib/semver-umd.js">
<link rel="prefetch" href="./static-{{COMMIT}}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js">
</head>
<body aria-label="">
</body>
<!-- Startup (do not modify order of script tags!) -->
<!-- NOTE:coder: Modified to work against the current path and use the commit for caching. -->
<script>
// NOTE: Changes to inline scripts require update of content security policy
const basePath = window.location.pathname.replace(/\/+$/, '');
const base = window.location.origin + basePath;
const el = document.getElementById('vscode-remote-commit');
const commit = el ? el.getAttribute('data-settings') : "";
const staticBase = base + '/static-' + commit;
let nlsConfig;
try {
nlsConfig = JSON.parse(document.getElementById('vscode-remote-nls-configuration').getAttribute('data-settings'));
if (nlsConfig._resolvedLanguagePackCoreLocation) {
const bundles = Object.create(null);
nlsConfig.loadBundle = (bundle, language, cb) => {
let result = bundles[bundle];
if (result) {
return cb(undefined, result);
}
// FIXME: Only works if path separators are /.
const path = nlsConfig._resolvedLanguagePackCoreLocation
+ '/' + bundle.replace(/\//g, '!') + '.nls.json';
fetch(`${base}/resource/?path=${encodeURIComponent(path)}`)
.then((response) => response.json())
.then((json) => {
bundles[bundle] = json;
cb(undefined, json);
})
.catch(cb);
};
}
} catch (error) { /* Probably fine. */ }
self.require = {
baseUrl: `${staticBase}/out`,
paths: {
'vscode-textmate': `${staticBase}/node_modules/vscode-textmate/release/main`,
'onigasm-umd': `${staticBase}/node_modules/onigasm-umd/release/main`,
'xterm': `${staticBase}/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${staticBase}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-web-links': `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
'semver-umd': `${staticBase}/node_modules/semver-umd/lib/semver-umd.js`,
'@microsoft/applicationinsights-web': `${staticBase}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`,
},
'vs/nls': nlsConfig,
};
</script>
<script src="./static-{{COMMIT}}/out/vs/loader.js"></script>
<script src="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.nls.js"></script>
<script src="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.js"></script>
<!-- TODO@coder: This errors with multiple anonymous define calls (one is
workbench.js and one is semver-umd.js). For now use the same method found in
workbench-dev.html. Appears related to the timing of the script load events. -->
<!-- <script src="./static-{{COMMIT}}/out/vs/workbench/workbench.js"></script> -->
<script>
// NOTE: Changes to inline scripts require update of content security policy
require(['vs/code/browser/workbench/workbench'], function() {});
</script>
</html>

View File

@@ -1,70 +0,0 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- Content Security Policy -->
<meta
http-equiv="Content-Security-Policy"
content="
default-src 'self';
img-src 'self' https: data: blob:;
media-src 'none';
script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https: 'unsafe-inline';
child-src 'self';
frame-src 'self' https://*.vscode-webview-test.com;
worker-src 'self';
style-src 'self' 'unsafe-inline';
connect-src 'self' ws: wss: https:;
font-src 'self' blob:;
manifest-src 'self';
">
<!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
<!-- Workarounds/Hacks (remote user data uri) -->
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}">
<!-- NOTE@coder: Added the commit for use in caching, the product for the
extensions gallery URL, and nls for language support. -->
<meta id="vscode-remote-commit" data-settings="{{COMMIT}}">
<meta id="vscode-remote-product-configuration" data-settings="{{PRODUCT_CONFIGURATION}}">
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./manifest.json">
</head>
<body aria-label="">
</body>
<!-- Startup (do not modify order of script tags!) -->
<script>
const basePath = window.location.pathname.replace(/\/+$/, '');
const base = window.location.origin + basePath;
const el = document.getElementById('vscode-remote-commit');
const commit = el ? el.getAttribute('data-settings') : "";
const staticBase = base + '/static-' + commit;
self.require = {
baseUrl: `${staticBase}/out`,
paths: {
'vscode-textmate': `${staticBase}/node_modules/vscode-textmate/release/main`,
'onigasm-umd': `${staticBase}/node_modules/onigasm-umd/release/main`,
'xterm': `${staticBase}/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${staticBase}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-web-links': `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
'semver-umd': `${staticBase}/node_modules/semver-umd/lib/semver-umd.js`,
'@microsoft/applicationinsights-web': `${staticBase}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`,
},
};
</script>
<script src="./static/out/vs/loader.js"></script>
<script>
require(['vs/code/browser/workbench/workbench'], function() {});
</script>
</html>

View File

@@ -1,57 +0,0 @@
import { URI } from "vs/base/common/uri";
import { IExtensionDescription } from "vs/platform/extensions/common/extensions";
import { ILogService } from "vs/platform/log/common/log";
import { Client } from "vs/server/node_modules/@coder/node-browser/out/client/client";
import { fromTar } from "vs/server/node_modules/@coder/requirefs/out/requirefs";
import { ExtensionActivationTimesBuilder } from "vs/workbench/api/common/extHostExtensionActivator";
import { IExtHostNodeProxy } from "./extHostNodeProxy";
export const loadCommonJSModule = async <T>(
module: IExtensionDescription,
activationTimesBuilder: ExtensionActivationTimesBuilder,
nodeProxy: IExtHostNodeProxy,
logService: ILogService,
vscode: any,
): Promise<T> => {
const fetchUri = URI.from({
scheme: self.location.protocol.replace(":", ""),
authority: self.location.host,
path: `${self.location.pathname.replace(/\/static.*\/out\/vs\/workbench\/services\/extensions\/worker\/extensionHostWorkerMain.js$/, "")}/tar`,
query: `path=${encodeURIComponent(module.extensionLocation.path)}`,
});
const response = await fetch(fetchUri.toString(true));
if (response.status !== 200) {
throw new Error(`Failed to download extension "${module.extensionLocation.path}"`);
}
const client = new Client(nodeProxy, { logger: logService });
const init = await client.handshake();
const buffer = new Uint8Array(await response.arrayBuffer());
const rfs = fromTar(buffer);
(<any>self).global = self;
rfs.provide("vscode", vscode);
Object.keys(client.modules).forEach((key) => {
const mod = (client.modules as any)[key];
if (key === "process") {
(<any>self).process = mod;
(<any>self).process.env = init.env;
return;
}
rfs.provide(key, mod);
switch (key) {
case "buffer":
(<any>self).Buffer = mod.Buffer;
break;
case "timers":
(<any>self).setImmediate = mod.setImmediate;
break;
}
});
try {
activationTimesBuilder.codeLoadingStart();
return rfs.require(".");
} finally {
activationTimesBuilder.codeLoadingStop();
}
};

60
src/common/api.ts Normal file
View File

@@ -0,0 +1,60 @@
export interface Application {
readonly categories?: string[]
readonly comment?: string
readonly directory?: string
readonly exec?: string
readonly genericName?: string
readonly icon?: string
readonly installed?: boolean
readonly name: string
/**
* Path if this is a browser app (like VS Code).
*/
readonly path?: string
/**
* PID if this is a process.
*/
readonly pid?: number
readonly version?: string
}
export interface ApplicationsResponse {
readonly applications: ReadonlyArray<Application>
}
export enum SessionError {
FailedToStart = 4000,
Starting = 4001,
InvalidState = 4002,
Unknown = 4003,
}
export interface SessionResponse {
/**
* Whether the process was spawned or an existing one was returned.
*/
created: boolean
pid: number
}
export interface RecentResponse {
readonly paths: string[]
readonly workspaces: string[]
}
export interface HealthRequest {
readonly event: "health"
}
export type ClientMessage = HealthRequest
export interface HealthResponse {
readonly event: "health"
readonly connections: number
}
export type ServerMessage = HealthResponse
export interface ReadyMessage {
protocol: string
}

40
src/common/emitter.ts Normal file
View File

@@ -0,0 +1,40 @@
export interface Disposable {
dispose(): void
}
export interface Event<T> {
(listener: (value: T) => void): Disposable
}
/**
* Emitter typecasts for a single event type.
*/
export class Emitter<T> {
private listeners: Array<(value: T) => void> = []
public get event(): Event<T> {
return (cb: (value: T) => void): Disposable => {
this.listeners.push(cb)
return {
dispose: (): void => {
const i = this.listeners.indexOf(cb)
if (i !== -1) {
this.listeners.splice(i, 1)
}
},
}
}
}
/**
* Emit an event with a value.
*/
public emit(value: T): void {
this.listeners.forEach((cb) => cb(value))
}
public dispose(): void {
this.listeners = []
}
}

24
src/common/http.ts Normal file
View File

@@ -0,0 +1,24 @@
export enum HttpCode {
Ok = 200,
Redirect = 302,
NotFound = 404,
BadRequest = 400,
Unauthorized = 401,
LargePayload = 413,
ServerError = 500,
}
export class HttpError extends Error {
public constructor(message: string, public readonly code: number) {
super(message)
this.name = this.constructor.name
}
}
export enum ApiEndpoint {
applications = "/applications",
process = "/process",
recent = "/recent",
run = "/run",
status = "/status",
}

View File

@@ -1,47 +0,0 @@
import { Event } from "vs/base/common/event";
import { IChannel, IServerChannel } from "vs/base/parts/ipc/common/ipc";
import { createDecorator } from "vs/platform/instantiation/common/instantiation";
import { ReadWriteConnection } from "vs/server/node_modules/@coder/node-browser/out/common/connection";
export const INodeProxyService = createDecorator<INodeProxyService>("nodeProxyService");
export interface INodeProxyService extends ReadWriteConnection {
_serviceBrand: any;
send(message: string): void;
onMessage: Event<string>;
onUp: Event<void>;
onClose: Event<void>;
onDown: Event<void>;
}
export class NodeProxyChannel implements IServerChannel {
constructor(private service: INodeProxyService) {}
listen(_: unknown, event: string): Event<any> {
switch (event) {
case "onMessage": return this.service.onMessage;
}
throw new Error(`Invalid listen ${event}`);
}
async call(_: unknown, command: string, args?: any): Promise<any> {
switch (command) {
case "send": return this.service.send(args[0]);
}
throw new Error(`Invalid call ${command}`);
}
}
export class NodeProxyChannelClient {
_serviceBrand: any;
public readonly onMessage: Event<string>;
constructor(private readonly channel: IChannel) {
this.onMessage = this.channel.listen<string>("onMessage");
}
public send(data: string): void {
this.channel.call("send", [data]);
}
}

View File

@@ -1,49 +0,0 @@
import { ITelemetryData } from "vs/base/common/actions";
import { Event } from "vs/base/common/event";
import { IChannel, IServerChannel } from "vs/base/parts/ipc/common/ipc";
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from "vs/platform/telemetry/common/gdprTypings";
import { ITelemetryInfo, ITelemetryService } from "vs/platform/telemetry/common/telemetry";
export class TelemetryChannel implements IServerChannel {
constructor(private service: ITelemetryService) {}
listen(_: unknown, event: string): Event<any> {
throw new Error(`Invalid listen ${event}`);
}
call(_: unknown, command: string, args?: any): Promise<any> {
switch (command) {
case "publicLog": return this.service.publicLog(args[0], args[1], args[2]);
case "publicLog2": return this.service.publicLog2(args[0], args[1], args[2]);
case "setEnabled": return Promise.resolve(this.service.setEnabled(args[0]));
case "getTelemetryInfo": return this.service.getTelemetryInfo();
}
throw new Error(`Invalid call ${command}`);
}
}
export class TelemetryChannelClient implements ITelemetryService {
_serviceBrand: any;
constructor(private readonly channel: IChannel) {}
public publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise<void> {
return this.channel.call("publicLog", [eventName, data, anonymizeFilePaths]);
}
public publicLog2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>, anonymizeFilePaths?: boolean): Promise<void> {
return this.channel.call("publicLog2", [eventName, data, anonymizeFilePaths]);
}
public setEnabled(value: boolean): void {
this.channel.call("setEnable", [value]);
}
public getTelemetryInfo(): Promise<ITelemetryInfo> {
return this.channel.call("getTelemetryInfo");
}
public get isOptedIn(): boolean {
return true;
}
}

View File

@@ -1,10 +1,77 @@
import { logger, field } from "@coder/logger"
export interface Options {
base: string
commit: string
logLevel: number
pid?: number
}
/**
* Split a string up to the delimiter. If the delimiter doesn't exist the first
* item will have all the text and the second item will be an empty string.
*/
export const split = (str: string, delimiter: string): [string, string] => {
const index = str.indexOf(delimiter);
return index !== -1
? [str.substring(0, index).trim(), str.substring(index + 1)]
: [str, ""];
};
const index = str.indexOf(delimiter)
return index !== -1 ? [str.substring(0, index).trim(), str.substring(index + 1)] : [str, ""]
}
export const plural = (count: number): string => (count === 1 ? "" : "s")
export const generateUuid = (length = 24): string => {
const possible = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
return Array(length)
.fill(1)
.map(() => possible[Math.floor(Math.random() * possible.length)])
.join("")
}
/**
* Remove extra slashes in a URL.
*/
export const normalize = (url: string, keepTrailing = false): string => {
return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "")
}
/**
* Get options embedded in the HTML or query params.
*/
export const getOptions = <T extends Options>(): T => {
let options: T
try {
const el = document.getElementById("coder-options")
if (!el) {
throw new Error("no options element")
}
const value = el.getAttribute("data-settings")
if (!value) {
throw new Error("no options value")
}
options = JSON.parse(value)
} catch (error) {
options = {} as T
}
const params = new URLSearchParams(location.search)
const queryOpts = params.get("options")
if (queryOpts) {
options = {
...options,
...JSON.parse(queryOpts),
}
}
if (typeof options.logLevel !== "undefined") {
logger.level = options.logLevel
}
if (options.base) {
const parts = location.pathname.replace(/^\//g, "").split("/")
parts[parts.length - 1] = options.base
const url = new URL(location.origin + "/" + parts.join("/"))
options.base = normalize(url.pathname, true)
}
logger.debug("got options", field("options", options))
return options
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Some files were not shown because too many files have changed in this diff Show More