mirror of
https://github.com/coder/code-server.git
synced 2026-04-13 21:32:52 -05:00
Compare commits
90 Commits
v4.12.0-rc
...
v4.17.1-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6275520348 | ||
|
|
2bb51a25a9 | ||
|
|
7a213161f2 | ||
|
|
a1131fadf2 | ||
|
|
468cf5c6ce | ||
|
|
800bf364d6 | ||
|
|
5428442628 | ||
|
|
020f280458 | ||
|
|
58f6e24a07 | ||
|
|
7868f4db23 | ||
|
|
acc50a5d36 | ||
|
|
47ee7ae670 | ||
|
|
70d0c603cc | ||
|
|
2e29c233ea | ||
|
|
331a017309 | ||
|
|
f915d1e2c8 | ||
|
|
95bcf101d7 | ||
|
|
d80568df20 | ||
|
|
03dc8cd808 | ||
|
|
3e1c00e017 | ||
|
|
a76e5241b6 | ||
|
|
913fc30866 | ||
|
|
eb498b0d6d | ||
|
|
eb8099ff89 | ||
|
|
56d3548fa2 | ||
|
|
2d335bd1ea | ||
|
|
291682e1c0 | ||
|
|
97aeb77abe | ||
|
|
76fd2389e4 | ||
|
|
3cb92edc76 | ||
|
|
94ef3776ad | ||
|
|
a82f1ceaa5 | ||
|
|
25254214fb | ||
|
|
feb5e6770c | ||
|
|
e37b35278d | ||
|
|
2ffbcfdfcb | ||
|
|
74da5167a2 | ||
|
|
daac46b3cf | ||
|
|
ea3c79ffd1 | ||
|
|
93e60f7b0e | ||
|
|
7926647058 | ||
|
|
d27cd43dce | ||
|
|
1befd37310 | ||
|
|
2b3b5e7051 | ||
|
|
788da5d495 | ||
|
|
070a5a3179 | ||
|
|
7cbb6a8b09 | ||
|
|
7fb54073db | ||
|
|
e72e8bb6b5 | ||
|
|
411846528b | ||
|
|
7f024eda6e | ||
|
|
2617623ed6 | ||
|
|
70aa1b7722 | ||
|
|
90a4147381 | ||
|
|
5c19962930 | ||
|
|
56d10d82bf | ||
|
|
cef2aa22dc | ||
|
|
b5a9ef80e7 | ||
|
|
5d3c9edce4 | ||
|
|
9955cd91a4 | ||
|
|
fdeaba9581 | ||
|
|
40ff2e6049 | ||
|
|
73d42f7ea0 | ||
|
|
fb73742b2b | ||
|
|
ccb0d3a34f | ||
|
|
4a121edd16 | ||
|
|
f6db985712 | ||
|
|
6d00fc7f46 | ||
|
|
ee024f3f2d | ||
|
|
0703ef008c | ||
|
|
2109d1cf6a | ||
|
|
74af05dfbe | ||
|
|
2798322b03 | ||
|
|
b3b971480f | ||
|
|
6745a46034 | ||
|
|
43ef50b404 | ||
|
|
45c89856fd | ||
|
|
5f4ae75431 | ||
|
|
521ff44aed | ||
|
|
5708e6ce32 | ||
|
|
3f7db15fde | ||
|
|
8c99f41b90 | ||
|
|
e02d56dbfd | ||
|
|
aac5efa046 | ||
|
|
ff2764f7b1 | ||
|
|
bda6b631ab | ||
|
|
9bac70ea0d | ||
|
|
7722ef1437 | ||
|
|
951d8ac45e | ||
|
|
2e17735795 |
45
.github/workflows/build.yaml
vendored
45
.github/workflows/build.yaml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run prettier with actionsx/prettier
|
||||
uses: actionsx/prettier@v2
|
||||
uses: actionsx/prettier@v3
|
||||
with:
|
||||
args: --check --loglevel=warn .
|
||||
|
||||
@@ -45,16 +45,16 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v35
|
||||
uses: tj-actions/changed-files@v38
|
||||
with:
|
||||
files: |
|
||||
docs/**
|
||||
|
||||
- name: Install Node.js v16
|
||||
- name: Install Node.js v18
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18"
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install doctoc
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v35
|
||||
uses: tj-actions/changed-files@v38
|
||||
with:
|
||||
files: |
|
||||
ci/helm-chart/**
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v35
|
||||
uses: tj-actions/changed-files@v38
|
||||
with:
|
||||
files: |
|
||||
**/*.ts
|
||||
@@ -115,11 +115,11 @@ jobs:
|
||||
files_ignore: |
|
||||
lib/vscode/**
|
||||
|
||||
- name: Install Node.js v16
|
||||
- name: Install Node.js v18
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18"
|
||||
|
||||
- name: Fetch dependencies from cache
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
@@ -163,18 +163,18 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v35
|
||||
uses: tj-actions/changed-files@v38
|
||||
with:
|
||||
files: |
|
||||
**/*.ts
|
||||
files_ignore: |
|
||||
lib/vscode/**
|
||||
|
||||
- name: Install Node.js v16
|
||||
- name: Install Node.js v18
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18"
|
||||
|
||||
- name: Fetch dependencies from cache
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
@@ -203,7 +203,7 @@ jobs:
|
||||
build:
|
||||
name: Build code-server
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
steps:
|
||||
@@ -212,6 +212,9 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install system dependencies
|
||||
run: sudo apt update && sudo apt install -y libkrb5-dev
|
||||
|
||||
- name: Install quilt
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
@@ -221,10 +224,10 @@ jobs:
|
||||
- name: Patch Code
|
||||
run: quilt push -a
|
||||
|
||||
- name: Install Node.js v16
|
||||
- name: Install Node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18"
|
||||
|
||||
- name: Fetch dependencies from cache
|
||||
id: cache-node-modules
|
||||
@@ -292,10 +295,13 @@ jobs:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js v16
|
||||
- name: Install system dependencies
|
||||
run: sudo apt update && sudo apt install -y libkrb5-dev
|
||||
|
||||
- name: Install Node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18"
|
||||
|
||||
- name: Fetch dependencies from cache
|
||||
id: cache-node-modules
|
||||
@@ -348,10 +354,13 @@ jobs:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js v16
|
||||
- name: Install system dependencies
|
||||
run: sudo apt update && sudo apt install -y libkrb5-dev
|
||||
|
||||
- name: Install Node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18"
|
||||
|
||||
- name: Fetch dependencies from cache
|
||||
id: cache-node-modules
|
||||
|
||||
8
.github/workflows/publish.yaml
vendored
8
.github/workflows/publish.yaml
vendored
@@ -29,14 +29,14 @@ jobs:
|
||||
- name: Checkout code-server
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js v16
|
||||
- name: Install Node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18"
|
||||
cache: "yarn"
|
||||
|
||||
- name: Download npm package from release artifacts
|
||||
uses: robinraju/release-downloader@v1.7
|
||||
uses: robinraju/release-downloader@v1.8
|
||||
with:
|
||||
repository: "coder/code-server"
|
||||
tag: ${{ github.event.inputs.version || github.ref_name }}
|
||||
@@ -184,7 +184,7 @@ jobs:
|
||||
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
|
||||
|
||||
- name: Download release artifacts
|
||||
uses: robinraju/release-downloader@v1.7
|
||||
uses: robinraju/release-downloader@v1.8
|
||||
with:
|
||||
repository: "coder/code-server"
|
||||
tag: v${{ env.VERSION }}
|
||||
|
||||
99
.github/workflows/release.yaml
vendored
99
.github/workflows/release.yaml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: npm-version
|
||||
container: "centos:7"
|
||||
container: "centos:8"
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -35,17 +35,17 @@ jobs:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js v16
|
||||
- name: Install Node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18.15.0"
|
||||
|
||||
- name: Install development tools
|
||||
run: |
|
||||
yum install -y epel-release centos-release-scl make
|
||||
yum install -y devtoolset-9-{make,gcc,gcc-c++} jq rsync python3
|
||||
# for keytar
|
||||
yum install -y libsecret-devel
|
||||
cd /etc/yum.repos.d/
|
||||
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
|
||||
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
|
||||
yum install -y gcc-c++ make jq rsync python3 libsecret-devel krb5-devel
|
||||
|
||||
- name: Install nfpm and envsubst
|
||||
run: |
|
||||
@@ -67,10 +67,8 @@ jobs:
|
||||
- name: Decompress npm package
|
||||
run: tar -xzf package.tar.gz
|
||||
|
||||
# NOTE: && here is deliberate - GitHub puts each line in its own `.sh`
|
||||
# file when running inside a docker container.
|
||||
- name: Build standalone release
|
||||
run: source scl_source enable devtoolset-9 && npm run release:standalone
|
||||
run: npm run release:standalone
|
||||
|
||||
- name: Install test dependencies
|
||||
run: SKIP_SUBMODULE_DEPS=1 yarn --frozen-lockfile
|
||||
@@ -102,54 +100,60 @@ jobs:
|
||||
discussion_category_name: "📣 Announcements"
|
||||
files: ./release-packages/*
|
||||
|
||||
# NOTE@oxy:
|
||||
# We use Ubuntu 16.04 here, so that our build is more compatible
|
||||
# with older libc versions. We used to (Q1'20) use CentOS 7 here,
|
||||
# but it has a full update EOL of Q4'20 and a 'critical security'
|
||||
# update EOL of 2024. We're dropping full support a few years before
|
||||
# the final EOL, but I don't believe CentOS 7 has a large arm64 userbase.
|
||||
# It is not feasible to cross-compile with CentOS.
|
||||
|
||||
# Cross-compile notes: To compile native dependencies for arm64,
|
||||
# we install the aarch64/armv7l cross toolchain and then set it as the default
|
||||
# compiler/linker/etc. with the AR/CC/CXX/LINK environment variables.
|
||||
# qemu-user-static on ubuntu-16.04 currently doesn't run Node correctly,
|
||||
# so we just build with "native"/x86_64 node, then download arm64/armv7l node
|
||||
# and then put it in our release. We can't smoke test the cross build this way,
|
||||
# but this means we don't need to maintain a self-hosted runner!
|
||||
|
||||
# NOTE@jsjoeio:
|
||||
# We used to use 18.04 until GitHub browned it out on December 15, 2022
|
||||
# See here: https://github.com/actions/runner-images/issues/6002
|
||||
package-linux-cross:
|
||||
name: Linux cross-compile builds
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: npm-version
|
||||
container: "debian:buster"
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- prefix: aarch64-linux-gnu
|
||||
arch: arm64
|
||||
npm_arch: arm64
|
||||
apt_arch: arm64
|
||||
- prefix: arm-linux-gnueabihf
|
||||
arch: armv7l
|
||||
npm_arch: armv7l
|
||||
apt_arch: armhf
|
||||
|
||||
env:
|
||||
AR: ${{ format('{0}-ar', matrix.prefix) }}
|
||||
AS: ${{ format('{0}-as', matrix.prefix) }}
|
||||
CC: ${{ format('{0}-gcc', matrix.prefix) }}
|
||||
CPP: ${{ format('{0}-cpp', matrix.prefix) }}
|
||||
CXX: ${{ format('{0}-g++', matrix.prefix) }}
|
||||
LINK: ${{ format('{0}-g++', matrix.prefix) }}
|
||||
NPM_CONFIG_ARCH: ${{ matrix.arch }}
|
||||
NODE_VERSION: v16.13.0
|
||||
FC: ${{ format('{0}-gfortran', matrix.prefix) }}
|
||||
LD: ${{ format('{0}-ld', matrix.prefix) }}
|
||||
STRIP: ${{ format('{0}-strip', matrix.prefix) }}
|
||||
PKG_CONFIG_PATH: ${{ format('/usr/lib/{0}/pkgconfig', matrix.prefix) }}
|
||||
TARGET_ARCH: ${{ matrix.apt_arch }}
|
||||
npm_config_arch: ${{ matrix.npm_arch }}
|
||||
NODE_VERSION: v18.15.0
|
||||
# Not building from source results in an x86_64 argon2, as if
|
||||
# npm_config_arch is being ignored.
|
||||
npm_config_build_from_source: true
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js v16
|
||||
- name: Install Node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18.15.0"
|
||||
|
||||
- name: Install cross-compiler and system dependencies
|
||||
run: |
|
||||
dpkg --add-architecture $TARGET_ARCH
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
crossbuild-essential-$TARGET_ARCH \
|
||||
libx11-dev:$TARGET_ARCH \
|
||||
libx11-xcb-dev:$TARGET_ARCH \
|
||||
libxkbfile-dev:$TARGET_ARCH \
|
||||
libsecret-1-dev:$TARGET_ARCH \
|
||||
libkrb5-dev:$TARGET_ARCH \
|
||||
ca-certificates \
|
||||
curl wget rsync gettext-base
|
||||
|
||||
- name: Install nfpm
|
||||
run: |
|
||||
@@ -157,11 +161,6 @@ jobs:
|
||||
curl -sSfL https://github.com/goreleaser/nfpm/releases/download/v2.3.1/nfpm_2.3.1_`uname -s`_`uname -m`.tar.gz | tar -C ~/.local/bin -zxv nfpm
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install cross-compiler
|
||||
run: sudo apt update && sudo apt install $PACKAGE
|
||||
env:
|
||||
PACKAGE: ${{ format('g++-{0}', matrix.prefix) }}
|
||||
|
||||
- name: Download npm package
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
@@ -170,15 +169,13 @@ jobs:
|
||||
- name: Decompress npm package
|
||||
run: tar -xzf package.tar.gz
|
||||
|
||||
# NOTE@jsjoeio - npm fails here
|
||||
# so use yarn
|
||||
- name: Build standalone release
|
||||
run: yarn release:standalone
|
||||
run: npm run release:standalone
|
||||
|
||||
- name: Replace node with cross-compile equivalent
|
||||
run: |
|
||||
wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-${NPM_CONFIG_ARCH}.tar.xz
|
||||
tar -xf node-${NODE_VERSION}-linux-${NPM_CONFIG_ARCH}.tar.xz node-${NODE_VERSION}-linux-${NPM_CONFIG_ARCH}/bin/node --strip-components=2
|
||||
wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-${npm_config_arch}.tar.xz
|
||||
tar -xf node-${NODE_VERSION}-linux-${npm_config_arch}.tar.xz node-${NODE_VERSION}-linux-${npm_config_arch}/bin/node --strip-components=2
|
||||
mv ./node ./release-standalone/lib/node
|
||||
|
||||
# NOTE@jsjoeio - we do this so we can strip out the v
|
||||
@@ -191,7 +188,7 @@ jobs:
|
||||
- name: Build packages with nfpm
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
run: yarn package ${NPM_CONFIG_ARCH}
|
||||
run: npm run package ${npm_config_arch}
|
||||
|
||||
- uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
@@ -208,10 +205,10 @@ jobs:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js v16
|
||||
- name: Install Node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18.15.0"
|
||||
|
||||
- name: Install nfpm
|
||||
run: |
|
||||
@@ -305,7 +302,7 @@ jobs:
|
||||
npm version --prefix release "$VERSION"
|
||||
|
||||
echo "Updating version in lib/vscode/product.json"
|
||||
tmp=$(mktemp)
|
||||
tmp=$(mktemp)
|
||||
jq ".codeServerVersion = \"$VERSION\"" release/lib/vscode/product.json > "$tmp" && mv "$tmp" release/lib/vscode/product.json
|
||||
# Ensure it has the same permissions as before
|
||||
chmod 644 release/lib/vscode/product.json
|
||||
|
||||
25
.github/workflows/security.yaml
vendored
25
.github/workflows/security.yaml
vendored
@@ -29,26 +29,17 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Node.js v16
|
||||
- name: Install Node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18"
|
||||
|
||||
- name: Fetch dependencies from cache
|
||||
id: cache-yarn
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: yarn-build-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
yarn-build-
|
||||
- name: Audit yarn for vulnerabilities
|
||||
run: yarn audit
|
||||
if: success()
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cache-yarn.outputs.cache-hit != 'true'
|
||||
run: SKIP_SUBMODULE_DEPS=1 yarn --frozen-lockfile
|
||||
|
||||
- name: Audit for vulnerabilities
|
||||
run: yarn _audit
|
||||
- name: Audit npm for vulnerabilities
|
||||
run: npm shrinkwrap && npm audit
|
||||
if: success()
|
||||
|
||||
trivy-scan-repo:
|
||||
@@ -64,7 +55,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run Trivy vulnerability scanner in repo mode
|
||||
uses: aquasecurity/trivy-action@1f0aa582c8c8f5f7639610d6d38baddfea4fdcee
|
||||
uses: aquasecurity/trivy-action@41f05d9ecffa2ed3f1580af306000f734b733e54
|
||||
with:
|
||||
scan-type: "fs"
|
||||
scan-ref: "."
|
||||
|
||||
2
.github/workflows/trivy-docker.yaml
vendored
2
.github/workflows/trivy-docker.yaml
vendored
@@ -51,7 +51,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run Trivy vulnerability scanner in image mode
|
||||
uses: aquasecurity/trivy-action@1f0aa582c8c8f5f7639610d6d38baddfea4fdcee
|
||||
uses: aquasecurity/trivy-action@41f05d9ecffa2ed3f1580af306000f734b733e54
|
||||
with:
|
||||
image-ref: "docker.io/codercom/code-server:latest"
|
||||
ignore-unfixed: true
|
||||
|
||||
@@ -1 +1 @@
|
||||
16
|
||||
18
|
||||
|
||||
123
CHANGELOG.md
123
CHANGELOG.md
@@ -20,6 +20,129 @@ Code v99.99.999
|
||||
|
||||
-->
|
||||
|
||||
## Unreleased
|
||||
|
||||
## [4.17.0](https://github.com/coder/code-server/releases/tag/v4.17.0) - 2023-09-22
|
||||
|
||||
Code v1.82.2
|
||||
|
||||
### Added
|
||||
|
||||
- Japanese locale.
|
||||
- `CODE_SERVER_HOST` environment variable.
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Code 1.82.2. This includes an update to Node 18, which also means
|
||||
that the minimum glibc is now 2.28. If you need to maintain a lower glibc then
|
||||
you can take a version of Node 18 that is compiled with a lower glibc and use
|
||||
that to build code-server (or at a minimum rebuild the native modules).
|
||||
- Display paths to config files in full rather than abbreviated. If you have
|
||||
trouble with the password not working please update and make sure the
|
||||
displayed config paths are what you expect.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix some dependency issues for the standalone arm64 and armv7l releases. If
|
||||
you had issues with missing or failing modules please try these new builds.
|
||||
|
||||
## [4.16.1](https://github.com/coder/code-server/releases/tag/v4.16.1) - 2023-07-31
|
||||
|
||||
Code v1.80.2
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Code 1.80.2.
|
||||
|
||||
## [4.16.0](https://github.com/coder/code-server/releases/tag/v4.16.0) - 2023-07-28
|
||||
|
||||
Code v1.80.1
|
||||
|
||||
### Added
|
||||
|
||||
- `--disable-proxy` flag. This disables the domain and path proxies but it does
|
||||
not disable the ports panel in Code. That can be disabled by using
|
||||
`remote.autoForwardPorts=false` in your settings.
|
||||
|
||||
## [4.15.0](https://github.com/coder/code-server/releases/tag/v4.15.0) - 2023-07-21
|
||||
|
||||
Code v1.80.1
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Code 1.80.1.
|
||||
|
||||
### Added
|
||||
|
||||
- `--trusted-origin` flag for specifying origins that you trust but do not
|
||||
control (for example a reverse proxy).
|
||||
|
||||
Code v1.79.2
|
||||
|
||||
## [4.14.1](https://github.com/coder/code-server/releases/tag/v4.14.1) - 2023-06-26
|
||||
|
||||
Code v1.79.2
|
||||
|
||||
### Security
|
||||
|
||||
- Remove extra write permissions on the Node binary bundled with the linux-amd64
|
||||
tarball. If you extract the tar without a umask this could mean the Node
|
||||
binary would be unexpectedly writable.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Inability to launch multiple instances of code-server for different users.
|
||||
|
||||
### Added
|
||||
|
||||
- `--session-socket` CLI flag to configure the location of the session socket.
|
||||
By default it will be placed in `<user data dir>/code-server-ipc.sock`.
|
||||
|
||||
## [4.14.0](https://github.com/coder/code-server/releases/tag/v4.14.0) - 2023-06-16
|
||||
|
||||
Code v1.79.2
|
||||
|
||||
### Added
|
||||
|
||||
- `--domain-proxy` now supports `{{port}}` and `{{host}}` template variables.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Code 1.79.2
|
||||
- Files opened from an external terminal will now open in the most closely
|
||||
related window rather than in the last opened window.
|
||||
|
||||
## [4.13.0](https://github.com/coder/code-server/releases/tag/v4.13.0) - 2023-05-19
|
||||
|
||||
Code v1.78.2
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Code 1.78.2.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Proxying files that contain non-ASCII characters.
|
||||
- Origin check when X-Forwarded-Host contains comma-separated hosts.
|
||||
|
||||
## [4.12.0](https://github.com/coder/code-server/releases/tag/v4.12.0) - 2023-04-21
|
||||
|
||||
Code v1.77.3
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Code 1.77.3
|
||||
- Ports panel will use domain-based proxy (instead of the default path-based
|
||||
proxy) when set via --proxy-domain.
|
||||
- Apply --app-name to the PWA title.
|
||||
|
||||
### Added
|
||||
|
||||
- Thai translation for login page.
|
||||
- Debug logs around the origin security check. If you are getting forbidden
|
||||
errors on web sockets please run code-server with `--log debug` to see why the
|
||||
requests are being blocked.
|
||||
|
||||
## [4.11.0](https://github.com/coder/code-server/releases/tag/v4.11.0) - 2023-03-16
|
||||
|
||||
Code v1.76.1
|
||||
|
||||
@@ -29,7 +29,7 @@ This directory contains scripts used for the development of code-server.
|
||||
- [./ci/dev/watch.ts](./dev/watch.ts) (`yarn watch`)
|
||||
- Starts a process to build and launch code-server and restart on any code changes.
|
||||
- Example usage in [./docs/CONTRIBUTING.md](../docs/CONTRIBUTING.md).
|
||||
- [./ci/dev/gen_icons.sh](./ci/dev/gen_icons.sh) (`yarn icons`)
|
||||
- [./ci/dev/gen_icons.sh](./dev/gen_icons.sh) (`yarn icons`)
|
||||
- Generates the various icons from a single `.svg` favicon in
|
||||
`src/browser/media/favicon.svg`.
|
||||
- Requires [imagemagick](https://imagemagick.org/index.php)
|
||||
@@ -75,7 +75,7 @@ You can disable minification by setting `MINIFY=`.
|
||||
|
||||
This directory contains the release docker container image.
|
||||
|
||||
- [./ci/steps/build-docker-buildx-push.sh](./ci/steps/docker-buildx-push.sh)
|
||||
- [./ci/steps/build-docker-buildx-push.sh](./steps/docker-buildx-push.sh)
|
||||
- Builds the release containers with tags `codercom/code-server-$ARCH:$VERSION` for amd64 and arm64 with `docker buildx` and pushes them.
|
||||
- Assumes debian releases are ready in `./release-packages`.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ main() {
|
||||
release_archive() {
|
||||
local release_name="code-server-$VERSION-$OS-$ARCH"
|
||||
if [[ $OS == "linux" ]]; then
|
||||
tar -czf "release-packages/$release_name.tar.gz" --transform "s/^\.\/release-standalone/$release_name/" ./release-standalone
|
||||
tar -czf "release-packages/$release_name.tar.gz" --owner=0 --group=0 --transform "s/^\.\/release-standalone/$release_name/" ./release-standalone
|
||||
else
|
||||
tar -czf "release-packages/$release_name.tar.gz" -s "/^release-standalone/$release_name/" release-standalone
|
||||
fi
|
||||
|
||||
@@ -56,7 +56,6 @@ bundle_code_server() {
|
||||
}
|
||||
EOF
|
||||
) > "$RELEASE_PATH/package.json"
|
||||
rsync yarn.lock "$RELEASE_PATH"
|
||||
mv npm-shrinkwrap.json "$RELEASE_PATH"
|
||||
|
||||
rsync ci/build/npm-postinstall.sh "$RELEASE_PATH/postinstall.sh"
|
||||
@@ -95,12 +94,10 @@ bundle_vscode() {
|
||||
"$VSCODE_SRC_PATH/remote/package.json" \
|
||||
"$VSCODE_SRC_PATH/package.json" > "$VSCODE_OUT_PATH/package.json"
|
||||
|
||||
rsync "$VSCODE_SRC_PATH/remote/yarn.lock" "$VSCODE_OUT_PATH/yarn.lock"
|
||||
mv "$VSCODE_SRC_PATH/remote/npm-shrinkwrap.json" "$VSCODE_OUT_PATH/npm-shrinkwrap.json"
|
||||
|
||||
# Include global extension dependencies as well.
|
||||
rsync "$VSCODE_SRC_PATH/extensions/package.json" "$VSCODE_OUT_PATH/extensions/package.json"
|
||||
rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions/yarn.lock"
|
||||
mv "$VSCODE_SRC_PATH/extensions/npm-shrinkwrap.json" "$VSCODE_OUT_PATH/extensions/npm-shrinkwrap.json"
|
||||
rsync "$VSCODE_SRC_PATH/extensions/postinstall.mjs" "$VSCODE_OUT_PATH/extensions/postinstall.mjs"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# This is due to an upstream issue with RHEL7/CentOS 7 comptability with node-argon2
|
||||
# See: https://github.com/cdr/code-server/pull/3422#pullrequestreview-677765057
|
||||
export npm_config_build_from_source=true
|
||||
|
||||
main() {
|
||||
cd "$(dirname "${0}")/../.."
|
||||
|
||||
@@ -13,17 +9,19 @@ main() {
|
||||
rsync "$RELEASE_PATH/" "$RELEASE_PATH-standalone"
|
||||
RELEASE_PATH+=-standalone
|
||||
|
||||
# We cannot find the path to node from $PATH because yarn shims a script to ensure
|
||||
# we use the same version it's using so we instead run a script with yarn that
|
||||
# will print the path to node.
|
||||
# We cannot get the path to Node from $PATH (for example via `which node`)
|
||||
# because Yarn shims a script called `node` and we would end up just copying
|
||||
# that script. Instead we run Node and have it print its actual path.
|
||||
local node_path
|
||||
node_path="$(yarn -s node <<< 'console.info(process.execPath)')"
|
||||
node_path="$(node <<< 'console.info(process.execPath)')"
|
||||
|
||||
mkdir -p "$RELEASE_PATH/bin"
|
||||
mkdir -p "$RELEASE_PATH/lib"
|
||||
rsync ./ci/build/code-server.sh "$RELEASE_PATH/bin/code-server"
|
||||
rsync "$node_path" "$RELEASE_PATH/lib/node"
|
||||
|
||||
chmod 755 "$RELEASE_PATH/lib/node"
|
||||
|
||||
pushd "$RELEASE_PATH"
|
||||
npm install --unsafe-perm --omit=dev
|
||||
popd
|
||||
|
||||
@@ -15,7 +15,7 @@ copy-bin-script() {
|
||||
local dest="lib/vscode-reh-web-linux-x64/bin/$script"
|
||||
cp "lib/vscode/resources/server/bin/$script" "$dest"
|
||||
sed -i.bak "s/@@VERSION@@/$(vscode_version)/g" "$dest"
|
||||
sed -i.bak "s/@@COMMIT@@/$VSCODE_DISTRO_COMMIT/g" "$dest"
|
||||
sed -i.bak "s/@@COMMIT@@/$BUILD_SOURCEVERSION/g" "$dest"
|
||||
sed -i.bak "s/@@APPNAME@@/code-server/g" "$dest"
|
||||
|
||||
# Fix Node path on Darwin and Linux.
|
||||
@@ -51,8 +51,8 @@ main() {
|
||||
# Set the commit Code will embed into the product.json. We need to do this
|
||||
# since Code tries to get the commit from the `.git` directory which will fail
|
||||
# as it is a submodule.
|
||||
export VSCODE_DISTRO_COMMIT
|
||||
VSCODE_DISTRO_COMMIT=$(git rev-parse HEAD)
|
||||
export BUILD_SOURCEVERSION
|
||||
BUILD_SOURCEVERSION=$(git rev-parse HEAD)
|
||||
|
||||
# Add the date, our name, links, and enable telemetry (this just makes
|
||||
# telemetry available; telemetry can still be disabled by flag or setting).
|
||||
@@ -109,6 +109,15 @@ EOF
|
||||
|
||||
popd
|
||||
|
||||
pushd lib/vscode-reh-web-linux-x64
|
||||
# Make sure Code took the version we set in the environment variable. Not
|
||||
# having a version will break display languages.
|
||||
if ! jq -e .commit product.json; then
|
||||
echo "'commit' is missing from product.json"
|
||||
exit 1
|
||||
fi
|
||||
popd
|
||||
|
||||
# These provide a `code-server` command in the integrated terminal to open
|
||||
# files in the current instance.
|
||||
delete-bin-script remote-cli/code-server
|
||||
|
||||
@@ -53,10 +53,6 @@ symlink_bin_script() {
|
||||
|
||||
OS="$(os)"
|
||||
|
||||
# This is due to an upstream issue with RHEL7/CentOS 7 comptability with node-argon2
|
||||
# See: https://github.com/cdr/code-server/pull/3422#pullrequestreview-677765057
|
||||
export npm_config_build_from_source=true
|
||||
|
||||
main() {
|
||||
# Grabs the major version of node from $npm_config_user_agent which looks like
|
||||
# yarn/1.21.1 npm/? node/v14.2.0 darwin x64
|
||||
@@ -68,8 +64,8 @@ main() {
|
||||
echo "USE AT YOUR OWN RISK!"
|
||||
fi
|
||||
|
||||
if [ "$major_node_version" -ne "${FORCE_NODE_VERSION:-16}" ]; then
|
||||
echo "ERROR: code-server currently requires node v16."
|
||||
if [ "$major_node_version" -ne "${FORCE_NODE_VERSION:-18}" ]; then
|
||||
echo "ERROR: code-server currently requires node v18."
|
||||
if [ -n "$FORCE_NODE_VERSION" ]; then
|
||||
echo "However, you have overrided the version check to use v$FORCE_NODE_VERSION."
|
||||
fi
|
||||
@@ -113,29 +109,40 @@ install_with_yarn_or_npm() {
|
||||
# HACK: NPM's use of semver doesn't like resolving some peerDependencies that vscode (upstream) brings in the form of pre-releases.
|
||||
# The legacy behavior doesn't complain about pre-releases being used, falling back to that for now.
|
||||
# See https://github.com//pull/5071
|
||||
npm install --unsafe-perm --legacy-peer-deps --omit=dev
|
||||
if ! npm install --unsafe-perm --legacy-peer-deps --omit=dev; then
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
yarn*)
|
||||
yarn --production --frozen-lockfile --no-default-rc
|
||||
if ! yarn --production --frozen-lockfile --no-default-rc; then
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Could not determine which package manager is being used to install code-server"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
vscode_install() {
|
||||
echo 'Installing Code dependencies...'
|
||||
cd lib/vscode
|
||||
install_with_yarn_or_npm
|
||||
if ! install_with_yarn_or_npm; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
symlink_asar
|
||||
symlink_bin_script remote-cli code code-server
|
||||
symlink_bin_script helpers browser browser .sh
|
||||
|
||||
cd extensions
|
||||
install_with_yarn_or_npm
|
||||
if ! install_with_yarn_or_npm; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -15,9 +15,9 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 3.7.0
|
||||
version: 3.13.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
appVersion: 4.11.0
|
||||
appVersion: 4.17.0
|
||||
|
||||
@@ -20,6 +20,9 @@ spec:
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "code-server.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- if .Values.podAnnotations }}
|
||||
annotations: {{- toYaml .Values.podAnnotations | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }}
|
||||
{{- if .Values.hostnameOverride }}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{{- if not .Values.existingSecret }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
@@ -11,8 +12,9 @@ metadata:
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
type: Opaque
|
||||
data:
|
||||
{{ if .Values.password }}
|
||||
{{- if .Values.password }}
|
||||
password: "{{ .Values.password | b64enc }}"
|
||||
{{ else }}
|
||||
{{- else }}
|
||||
password: "{{ randAlphaNum 24 | b64enc }}"
|
||||
{{ end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -6,7 +6,7 @@ replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: codercom/code-server
|
||||
tag: '4.11.0'
|
||||
tag: '4.17.0'
|
||||
pullPolicy: Always
|
||||
|
||||
# Specifies one or more secrets to be used when pulling images from a
|
||||
|
||||
@@ -37,7 +37,7 @@ for [VS
|
||||
Code](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites).
|
||||
Here is what is needed:
|
||||
|
||||
- `node` v16.x
|
||||
- `node` v18.x
|
||||
- `git` v2.x or greater
|
||||
- [`git-lfs`](https://git-lfs.github.com)
|
||||
- [`yarn`](https://classic.yarnpkg.com/en/)
|
||||
@@ -63,7 +63,7 @@ Here is what is needed:
|
||||
If you're developing code-server on Linux, make sure you have installed or install the following dependencies:
|
||||
|
||||
```shell
|
||||
sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev python-is-python3
|
||||
sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev python-is-python3
|
||||
```
|
||||
|
||||
These are required by Code. See [their Wiki](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites) for more information.
|
||||
|
||||
31
docs/FAQ.md
31
docs/FAQ.md
@@ -14,6 +14,7 @@
|
||||
- [How do I install an extension manually?](#how-do-i-install-an-extension-manually)
|
||||
- [How do I use my own extensions marketplace?](#how-do-i-use-my-own-extensions-marketplace)
|
||||
- [Where are extensions stored?](#where-are-extensions-stored)
|
||||
- [Where is VS Code configuration stored?](#where-is-vs-code-configuration-stored)
|
||||
- [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration)
|
||||
- [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open)
|
||||
- [How do I access my Documents/Downloads/Desktop folders in code-server on macOS?](#how-do-i-access-my-documentsdownloadsdesktop-folders-in-code-server-on-macos)
|
||||
@@ -34,6 +35,7 @@
|
||||
- [Are there community projects involving code-server?](#are-there-community-projects-involving-code-server)
|
||||
- [How do I change the port?](#how-do-i-change-the-port)
|
||||
- [How do I hide the coder/coder promotion in Help: Getting Started?](#how-do-i-hide-the-codercoder-promotion-in-help-getting-started)
|
||||
- [How do I disable the proxy?](#how-do-i-disable-the-proxy)
|
||||
- [How do I disable file download?](#how-do-i-disable-file-download)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
@@ -191,10 +193,20 @@ docs](https://github.com/VSCodium/vscodium/blob/master/DOCS.md#extensions--marke
|
||||
|
||||
## Where are extensions stored?
|
||||
|
||||
Extensions are store, by default, to `~/.local/share/code-server/extensions`.
|
||||
Extensions are stored in `~/.local/share/code-server/extensions` by default.
|
||||
|
||||
If you set the `XDG_DATA_HOME` environment variable, the data directory will be
|
||||
`$XDG_DATA_HOME/code-server/extensions`. In general, we try to follow the XDG directory spec.
|
||||
On Linux and macOS if you set the `XDG_DATA_HOME` environment variable, the
|
||||
extensions directory will be `$XDG_DATA_HOME/code-server/extensions`. In
|
||||
general, we try to follow the XDG directory spec.
|
||||
|
||||
## Where is VS Code configuration stored?
|
||||
|
||||
VS Code configuration such as settings and keybindings are stored in
|
||||
`~/.local/share/code-server` by default.
|
||||
|
||||
On Linux and macOS if you set the `XDG_DATA_HOME` environment variable, the data
|
||||
directory will be `$XDG_DATA_HOME/code-server`. In general, we try to follow the
|
||||
XDG directory spec.
|
||||
|
||||
## How can I reuse my VS Code configuration?
|
||||
|
||||
@@ -453,6 +465,19 @@ You can pass the flag `--disable-getting-started-override` to `code-server` or
|
||||
you can set the environment variable `CS_DISABLE_GETTING_STARTED_OVERRIDE=1` or
|
||||
`CS_DISABLE_GETTING_STARTED_OVERRIDE=true`.
|
||||
|
||||
## How do I disable the proxy?
|
||||
|
||||
You can pass the flag `--disable-proxy` to `code-server` or
|
||||
you can set the environment variable `CS_DISABLE_PROXY=1` or
|
||||
`CS_DISABLE_PROXY=true`.
|
||||
|
||||
Note, this option currently only disables the proxy routes to forwarded ports, including
|
||||
the domain and path proxy routes over HTTP and WebSocket; however, it does not
|
||||
disable the automatic port forwarding in the VS Code workbench itself. In other words,
|
||||
user will still see the Ports tab and notifications, but will not be able to actually
|
||||
use access the ports. It is recommended to set `remote.autoForwardPorts` to `false`
|
||||
when using the option.
|
||||
|
||||
## How do I disable file download?
|
||||
|
||||
You can pass the flag `--disable-file-downloads` to `code-server`
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and
|
||||
access it in the browser.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Highlights
|
||||
|
||||
|
||||
@@ -11,13 +11,21 @@ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
||||
```
|
||||
|
||||
6. Exit the terminal using `exit` and then reopen the terminal
|
||||
7. Install and use Node.js 16:
|
||||
7. Install and use Node.js 18:
|
||||
|
||||
```shell
|
||||
nvm install 16
|
||||
nvm use 16
|
||||
nvm install 18
|
||||
nvm use 18
|
||||
```
|
||||
|
||||
8. Install code-server globally on device with: `npm install --global code-server --unsafe-perm`
|
||||
9. Run code-server with `code-server`
|
||||
10. Access on localhost:8080 in your browser
|
||||
|
||||
# Running code-server using Nix-on-Droid
|
||||
|
||||
1. Install Nix-on-Droid from [F-Droid](https://f-droid.org/packages/com.termux.nix/)
|
||||
2. Start app
|
||||
3. Spawn a shell with code-server by running `nix-shell -p code-server`
|
||||
4. Run code-server with `code-server`
|
||||
5. Access on localhost:8080 in your browser
|
||||
|
||||
BIN
docs/assets/screenshot-1.png
Normal file
BIN
docs/assets/screenshot-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
BIN
docs/assets/screenshot-2.png
Normal file
BIN
docs/assets/screenshot-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 267 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 357 KiB |
@@ -19,6 +19,7 @@
|
||||
- [Proxying to create a React app](#proxying-to-create-a-react-app)
|
||||
- [Proxying to a Vue app](#proxying-to-a-vue-app)
|
||||
- [Proxying to an Angular app](#proxying-to-an-angular-app)
|
||||
- [Proxying to a Svelte app](#proxying-to-a-svelte-app)
|
||||
- [SSH into code-server on VS Code](#ssh-into-code-server-on-vs-code)
|
||||
- [Option 1: cloudflared tunnel](#option-1-cloudflared-tunnel)
|
||||
- [Option 2: ngrok tunnel](#option-2-ngrok-tunnel)
|
||||
@@ -138,9 +139,9 @@ sudo apt install caddy
|
||||
1. Replace `/etc/caddy/Caddyfile` using `sudo` so that the file looks like this:
|
||||
|
||||
```text
|
||||
mydomain.com
|
||||
|
||||
reverse_proxy 127.0.0.1:8080
|
||||
mydomain.com {
|
||||
reverse_proxy 127.0.0.1:8080
|
||||
}
|
||||
```
|
||||
|
||||
If you want to serve code-server from a sub-path, you can do so as follows:
|
||||
@@ -414,6 +415,27 @@ In order to use code-server's built-in proxy with Angular, you need to make the
|
||||
|
||||
For additional context, see [this GitHub Discussion](https://github.com/coder/code-server/discussions/5439#discussioncomment-3371983).
|
||||
|
||||
### Proxying to a Svelte app
|
||||
|
||||
In order to use code-server's built-in proxy with Svelte, you need to make the following changes in your app:
|
||||
|
||||
1. Add `svelte.config.js` if you don't already have one
|
||||
2. Update the values to match this (you can use any free port):
|
||||
|
||||
```js
|
||||
const config = {
|
||||
kit: {
|
||||
paths: {
|
||||
base: "/absproxy/5173",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
3. Access app at `<code-server-root>/absproxy/5173/` e.g. `http://localhost:8080/absproxy/5173/
|
||||
|
||||
For additional context, see [this Github Issue](https://github.com/sveltejs/kit/issues/2958)
|
||||
|
||||
## SSH into code-server on VS Code
|
||||
|
||||
[](https://ohmyz.sh/) [](https://img.shields.io/badge/Terminal-2E2E2E?style=for-the-badge&logo=Windows+Terminal&logoColor=ffffff) [](vscode:extension/ms-vscode-remote.remote-ssh)
|
||||
|
||||
@@ -103,10 +103,9 @@ _exact_ same commands presented in the rest of this document.
|
||||
We recommend installing with `npm` when:
|
||||
|
||||
1. You aren't using a machine with `amd64` or `arm64`.
|
||||
1. You are installing code-server on Windows
|
||||
1. You're on Linux with `glibc` < v2.17, `glibcxx` < v3.4.18 on `amd64`, `glibc`
|
||||
< v2.23, or `glibcxx` < v3.4.21 on `arm64`.
|
||||
1. You're running Alpine Linux or are using a non-glibc libc. See
|
||||
2. You are installing code-server on Windows.
|
||||
3. You're on Linux with `glibc` < v2.28 or `glibcxx` < v3.4.21.
|
||||
4. You're running Alpine Linux or are using a non-glibc libc. See
|
||||
[#1430](https://github.com/coder/code-server/issues/1430#issuecomment-629883198)
|
||||
for more information.
|
||||
|
||||
@@ -123,8 +122,8 @@ node binary and node modules.
|
||||
We create the standalone releases using the [npm package](#npm), and we
|
||||
then create the remaining releases using the standalone version.
|
||||
|
||||
The only requirement to use the standalone release is `glibc` >= 2.17 and
|
||||
`glibcxx` >= v3.4.18 on Linux (for macOS, there is no minimum system
|
||||
The only requirement to use the standalone release is `glibc` >= 2.28 and
|
||||
`glibcxx` >= v3.4.21 on Linux (for macOS, there is no minimum system
|
||||
requirement).
|
||||
|
||||
To use a standalone release:
|
||||
@@ -280,6 +279,7 @@ brew services start code-server
|
||||
# outside the container.
|
||||
mkdir -p ~/.config
|
||||
docker run -it --name code-server -p 127.0.0.1:8080:8080 \
|
||||
-v "$HOME/.local:/home/coder/.local" \
|
||||
-v "$HOME/.config:/home/coder/.config" \
|
||||
-v "$PWD:/home/coder/project" \
|
||||
-u "$(id -u):$(id -g)" \
|
||||
|
||||
@@ -30,7 +30,7 @@ includes installing instructions based on your operating system.
|
||||
## Node.js version
|
||||
|
||||
We use the same major version of Node.js shipped with Code's remote, which is
|
||||
currently `16.x`. VS Code also [lists Node.js
|
||||
currently `18.x`. VS Code also [lists Node.js
|
||||
requirements](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites).
|
||||
|
||||
Using other versions of Node.js [may lead to unexpected
|
||||
@@ -79,7 +79,7 @@ Proceed to [installing](#installing)
|
||||
## FreeBSD
|
||||
|
||||
```sh
|
||||
pkg install -y git python npm-node16 pkgconf
|
||||
pkg install -y git python npm-node18 pkgconf
|
||||
pkg install -y libinotify
|
||||
```
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
- [Upgrade](#upgrade)
|
||||
- [Known Issues](#known-issues)
|
||||
- [Git won't work in `/sdcard`](#git-wont-work-in-sdcard)
|
||||
- [Many extensions including language packs fail to install](#many-extensions-including-language-packs-fail-to-install)
|
||||
- [Extra](#extra)
|
||||
- [Create a new user](#create-a-new-user)
|
||||
- [Install Go](#install-go)
|
||||
@@ -55,7 +56,7 @@ npm config set python python3
|
||||
node -v
|
||||
```
|
||||
|
||||
you will get node version `v16.15.0`
|
||||
you will get Node version `v18`
|
||||
|
||||
5. Now install code-server following our guide on [installing with npm](./npm.md)
|
||||
|
||||
@@ -87,6 +88,37 @@ Potential Workaround :
|
||||
1. Create a soft-link from the debian-fs to your folder in `/sdcard`
|
||||
2. Use git from termux (preferred)
|
||||
|
||||
### Many extensions including language packs fail to install
|
||||
|
||||
Issue: Android is not seen as a Linux environment but as a separate, unsupported platform, so code-server only allows [Web Extensions](https://code.visualstudio.com/api/extension-guides/web-extensions), refusing to download extensions that run on the server.\
|
||||
Fix: None\
|
||||
Potential workarounds :
|
||||
|
||||
Either
|
||||
|
||||
- Manually download extensions as `.vsix` file and install them via `Extensions: Install from VSIX...` in the Command Palette.
|
||||
|
||||
- Use an override to pretend the platform is Linux:
|
||||
|
||||
Create a JS script that patches `process.platform`:
|
||||
|
||||
```js
|
||||
// android-as-linux.js
|
||||
Object.defineProperty(process, "platform", {
|
||||
get() {
|
||||
return "linux"
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Then use Node's `--require` option to make sure it is loaded before `code-server` starts:
|
||||
|
||||
```sh
|
||||
NODE_OPTIONS="--require /path/to/android-as-linux.js" code-server
|
||||
```
|
||||
|
||||
⚠️ Note that Android and Linux are not 100% compatible, so use these workarounds at your own risk. Extensions that have native dependencies other than Node or that directly interact with the OS might cause issues.
|
||||
|
||||
## Extra
|
||||
|
||||
### Create a new user
|
||||
|
||||
30
flake.lock
generated
30
flake.lock
generated
@@ -1,12 +1,15 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -17,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1660639432,
|
||||
"narHash": "sha256-2WDiboOCfB0LhvnDVMXOAr8ZLDfm3WdO54CkoDPwN1A=",
|
||||
"lastModified": 1683594133,
|
||||
"narHash": "sha256-iUhLhEAgOCnexSGDsYT2ouydis09uDoNzM7UC685XGE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6c6409e965a6c883677be7b9d87a95fab6c3472e",
|
||||
"rev": "8d447c5626cfefb9b129d5b30103344377fe09bc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -34,6 +37,21 @@
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
flake-utils.lib.eachDefaultSystem
|
||||
(system:
|
||||
let pkgs = nixpkgs.legacyPackages.${system};
|
||||
nodejs = pkgs.nodejs-16_x;
|
||||
nodejs = pkgs.nodejs-18_x;
|
||||
yarn' = pkgs.yarn.override { inherit nodejs; };
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
nodejs yarn' python pkg-config git rsync jq moreutils quilt bats
|
||||
nodejs yarn' python3 pkg-config git rsync jq moreutils quilt bats openssl
|
||||
];
|
||||
buildInputs = with pkgs; (lib.optionals (!stdenv.isDarwin) [ libsecret ]
|
||||
buildInputs = with pkgs; (lib.optionals (!stdenv.isDarwin) [ libsecret libkrb5 ]
|
||||
++ (with xorg; [ libX11 libxkbfile ])
|
||||
++ lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [
|
||||
++ lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [
|
||||
AppKit Cocoa CoreServices Security xcbuild
|
||||
]));
|
||||
};
|
||||
|
||||
@@ -441,7 +441,7 @@ install_npm() {
|
||||
return
|
||||
fi
|
||||
echoerr "Please install npm to install code-server!"
|
||||
echoerr "You will need at least node v12 and a few C dependencies."
|
||||
echoerr "You will need at least node v18 and a few C dependencies."
|
||||
echoerr "See the docs https://coder.com/docs/code-server/latest/install#npm"
|
||||
|
||||
exit 1
|
||||
|
||||
Submodule lib/vscode updated: 704ed70d4f...abd2f3db4b
107
package.json
107
package.json
@@ -38,77 +38,62 @@
|
||||
},
|
||||
"main": "out/node/entry.js",
|
||||
"devDependencies": {
|
||||
"@schemastore/package": "^0.0.6",
|
||||
"@types/compression": "^1.7.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express": "^4.17.8",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"@types/js-yaml": "^4.0.0",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/pem": "^1.9.5",
|
||||
"@schemastore/package": "^0.0.10",
|
||||
"@types/compression": "^1.7.3",
|
||||
"@types/cookie-parser": "^1.4.4",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/http-proxy": "1.17.7",
|
||||
"@types/js-yaml": "^4.0.6",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/pem": "^1.14.1",
|
||||
"@types/proxy-from-env": "^1.0.1",
|
||||
"@types/safe-compare": "^1.1.0",
|
||||
"@types/semver": "^7.1.0",
|
||||
"@types/split2": "^3.2.0",
|
||||
"@types/trusted-types": "^2.0.2",
|
||||
"@types/ws": "^8.5.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
"@typescript-eslint/parser": "^5.41.0",
|
||||
"audit-ci": "^6.0.0",
|
||||
"doctoc": "2.2.1",
|
||||
"eslint": "^8.26.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.2",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"prettier": "2.8.0",
|
||||
"prettier-plugin-sh": "^0.12.8",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^4.6.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"normalize-package-data": "^5.0.0",
|
||||
"doctoc/underscore": "^1.13.1",
|
||||
"doctoc/**/trim": "^1.0.0",
|
||||
"postcss": "^8.2.1",
|
||||
"browserslist": "^4.16.5",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"vfile-message": "^2.0.2",
|
||||
"tar": "^6.1.9",
|
||||
"path-parse": "^1.0.7",
|
||||
"vm2": "^3.9.11",
|
||||
"follow-redirects": "^1.14.8",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nanoid": "^3.1.31",
|
||||
"minimist": "npm:minimist-lite@2.2.1",
|
||||
"glob-parent": "^6.0.1",
|
||||
"@types/node": "^16.0.0",
|
||||
"qs": "^6.7.3"
|
||||
"@types/semver": "^7.5.2",
|
||||
"@types/trusted-types": "^2.0.4",
|
||||
"@types/ws": "^8.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||
"@typescript-eslint/parser": "^6.7.2",
|
||||
"audit-ci": "^6.6.1",
|
||||
"doctoc": "^2.2.1",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier-plugin-sh": "^0.13.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coder/logger": "^3.0.0",
|
||||
"argon2": "0.30.3",
|
||||
"@coder/logger": "^3.0.1",
|
||||
"argon2": "^0.31.1",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"env-paths": "^2.2.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"env-paths": "^2.2.1",
|
||||
"express": "5.0.0-alpha.8",
|
||||
"http-proxy": "^1.18.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"httpolyglot": "^0.1.2",
|
||||
"i18next": "^22.4.6",
|
||||
"js-yaml": "^4.0.0",
|
||||
"i18next": "^23.5.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"limiter": "^2.1.0",
|
||||
"pem": "^1.14.2",
|
||||
"proxy-agent": "^5.0.0",
|
||||
"qs": "6.11.0",
|
||||
"rotating-file-stream": "^3.0.0",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"pem": "^1.14.8",
|
||||
"proxy-agent": "^6.3.1",
|
||||
"qs": "6.9.7",
|
||||
"rotating-file-stream": "^3.1.1",
|
||||
"safe-buffer": "^5.2.1",
|
||||
"safe-compare": "^1.1.4",
|
||||
"semver": "^7.1.3",
|
||||
"split2": "^4.0.0",
|
||||
"ws": "^8.0.0",
|
||||
"semver": "^7.5.4",
|
||||
"ws": "^8.14.2",
|
||||
"xdg-basedir": "^4.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "^18.0.0",
|
||||
"qs": "6.9.7"
|
||||
},
|
||||
"overrides": {
|
||||
"qs": "6.9.7"
|
||||
},
|
||||
"bin": {
|
||||
"code-server": "out/node/entry.js"
|
||||
},
|
||||
@@ -122,7 +107,7 @@
|
||||
"remote-development"
|
||||
],
|
||||
"engines": {
|
||||
"node": "16"
|
||||
"node": "18"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
|
||||
@@ -10,7 +10,7 @@ Index: code-server/lib/vscode/src/vs/base/common/network.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/base/common/network.ts
|
||||
+++ code-server/lib/vscode/src/vs/base/common/network.ts
|
||||
@@ -166,7 +166,9 @@ class RemoteAuthoritiesImpl {
|
||||
@@ -168,7 +168,9 @@ class RemoteAuthoritiesImpl {
|
||||
return URI.from({
|
||||
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
|
||||
authority: `${host}:${port}`,
|
||||
@@ -99,26 +99,19 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactor
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts
|
||||
+++ code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts
|
||||
@@ -274,6 +274,7 @@ export class BrowserSocketFactory implem
|
||||
|
||||
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
|
||||
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
|
||||
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
|
||||
const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
|
||||
const errorListener = socket.onError((err) => callback(err, undefined));
|
||||
socket.onOpen(() => {
|
||||
@@ -282,6 +283,3 @@ export class BrowserSocketFactory implem
|
||||
});
|
||||
}
|
||||
}
|
||||
-
|
||||
-
|
||||
-
|
||||
@@ -280,6 +280,7 @@ export class BrowserSocketFactory implem
|
||||
connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
|
||||
return new Promise<ISocket>((resolve, reject) => {
|
||||
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
|
||||
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
|
||||
const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
|
||||
const errorListener = socket.onError(reject);
|
||||
socket.onOpen(() => {
|
||||
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -267,12 +267,11 @@ export class WebClientServer {
|
||||
@@ -269,16 +269,15 @@ export class WebClientServer {
|
||||
return void res.end();
|
||||
}
|
||||
|
||||
@@ -127,33 +120,33 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
- return Array.isArray(val) ? val[0] : val;
|
||||
- };
|
||||
-
|
||||
- const remoteAuthority = getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host;
|
||||
const useTestResolver = (!this._environmentService.isBuilt && this._environmentService.args['use-test-resolver']);
|
||||
+ // For now we are getting the remote authority from the client to avoid
|
||||
+ // needing specific configuration for reverse proxies to work. Set this to
|
||||
+ // something invalid to make sure we catch code that is using this value
|
||||
+ // from the backend when it should not.
|
||||
+ const remoteAuthority = 'remote';
|
||||
const remoteAuthority = (
|
||||
useTestResolver
|
||||
? 'test+test'
|
||||
- : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host)
|
||||
+ : 'remote'
|
||||
);
|
||||
if (!remoteAuthority) {
|
||||
return serveError(req, res, 400, `Bad request.`);
|
||||
}
|
||||
@@ -298,6 +297,8 @@ export class WebClientServer {
|
||||
@@ -305,8 +304,12 @@ export class WebClientServer {
|
||||
scopes: [['user:email'], ['repo']]
|
||||
} : undefined;
|
||||
|
||||
+ const base = relativeRoot(getOriginalUrl(req))
|
||||
+ const vscodeBase = relativePath(getOriginalUrl(req))
|
||||
|
||||
const workbenchWebConfiguration = {
|
||||
remoteAuthority,
|
||||
@@ -309,6 +310,7 @@ export class WebClientServer {
|
||||
workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']),
|
||||
productConfiguration: <Partial<IProductConfiguration>>{
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
+ rootEndpoint: base,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
...this._productService.extensionsGallery,
|
||||
@@ -326,8 +328,10 @@ export class WebClientServer {
|
||||
+
|
||||
const productConfiguration = <Partial<IProductConfiguration>>{
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
+ rootEndpoint: base,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
...this._productService.extensionsGallery,
|
||||
@@ -341,8 +344,10 @@ export class WebClientServer {
|
||||
const values: { [key: string]: string } = {
|
||||
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
|
||||
WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '',
|
||||
@@ -165,17 +158,17 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+ VS_BASE: vscodeBase,
|
||||
};
|
||||
|
||||
|
||||
@@ -344,7 +348,7 @@ export class WebClientServer {
|
||||
if (useTestResolver) {
|
||||
@@ -367,7 +372,7 @@ export class WebClientServer {
|
||||
'default-src \'self\';',
|
||||
'img-src \'self\' https: data: blob:;',
|
||||
'media-src \'self\';',
|
||||
- `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' http://${remoteAuthority};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
|
||||
+ `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=';`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
|
||||
- `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
|
||||
+ `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' ${useTestResolver ? '' : ''};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
|
||||
'child-src \'self\';',
|
||||
`frame-src 'self' https://*.vscode-cdn.net data:;`,
|
||||
'worker-src \'self\' data: blob:;',
|
||||
@@ -417,3 +421,70 @@ export class WebClientServer {
|
||||
@@ -440,3 +445,70 @@ export class WebClientServer {
|
||||
return void res.end(data);
|
||||
}
|
||||
}
|
||||
@@ -262,23 +255,45 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
@@ -489,6 +489,7 @@ function doCreateUri(path: string, query
|
||||
});
|
||||
@@ -304,7 +304,8 @@ class LocalStorageURLCallbackProvider ex
|
||||
this.startListening();
|
||||
}
|
||||
|
||||
- return URI.parse(window.location.href).with({ path: this._callbackRoute, query: queryParams.join('&') });
|
||||
+ const path = (window.location.pathname + "/" + this._callbackRoute).replace(/\/\/+/g, "/");
|
||||
+ return URI.parse(window.location.href).with({ path: path, query: queryParams.join('&') });
|
||||
}
|
||||
|
||||
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
|
||||
return URI.parse(window.location.href).with({ path, query });
|
||||
private startListening(): void {
|
||||
@@ -550,17 +551,6 @@ class WorkspaceProvider implements IWork
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,7 +501,7 @@ function doCreateUri(path: string, query
|
||||
-function readCookie(name: string): string | undefined {
|
||||
- const cookies = document.cookie.split('; ');
|
||||
- for (const cookie of cookies) {
|
||||
- if (cookie.startsWith(name + '=')) {
|
||||
- return cookie.substring(name.length + 1);
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- return undefined;
|
||||
-}
|
||||
-
|
||||
(function () {
|
||||
|
||||
// Find config by checking for DOM
|
||||
@@ -569,8 +559,8 @@ function readCookie(name: string): strin
|
||||
if (!configElement || !configElementAttribute) {
|
||||
throw new Error('Missing web configuration element');
|
||||
}
|
||||
- const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents; callbackRoute: string } = JSON.parse(configElementAttribute);
|
||||
- const secretStorageKeyPath = readCookie('vscode-secret-key-path');
|
||||
+ const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents; callbackRoute: string } = { ...JSON.parse(configElementAttribute), remoteAuthority: location.host }
|
||||
+ const secretStorageKeyPath = (window.location.pathname + "/mint-key").replace(/\/\/+/g, "/");
|
||||
const secretStorageCrypto = secretStorageKeyPath && ServerKeyedAESCrypto.supported()
|
||||
? new ServerKeyedAESCrypto(secretStorageKeyPath) : new TransparentCrypto();
|
||||
|
||||
// Create workbench
|
||||
create(document.body, {
|
||||
Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
|
||||
@@ -289,12 +304,12 @@ Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/ext
|
||||
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
-import { RemoteAuthorities } from 'vs/base/common/network';
|
||||
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { TargetPlatform } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource';
|
||||
@@ -75,7 +74,7 @@ export abstract class AbstractExtensionR
|
||||
public getExtensionGalleryResourceURL(galleryExtension: { publisher: string; name: string; version: string }, path?: string): URI | undefined {
|
||||
if (this._extensionGalleryResourceUrlTemplate) {
|
||||
const uri = URI.parse(format2(this._extensionGalleryResourceUrlTemplate, { publisher: galleryExtension.publisher, name: galleryExtension.name, version: galleryExtension.version, path: 'extension' }));
|
||||
@@ -102,7 +101,7 @@ export abstract class AbstractExtensionR
|
||||
: version,
|
||||
path: 'extension'
|
||||
}));
|
||||
- return this._isWebExtensionResourceEndPoint(uri) ? uri.with({ scheme: RemoteAuthorities.getPreferredWebSchema() }) : uri;
|
||||
+ return this._isWebExtensionResourceEndPoint(uri) ? URI.joinPath(URI.parse(window.location.href), uri.path) : uri;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTe
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
|
||||
@@ -100,10 +100,14 @@ class RemoteTerminalBackend extends Base
|
||||
@@ -104,10 +104,14 @@ class RemoteTerminalBackend extends Base
|
||||
}
|
||||
const reqId = e.reqId;
|
||||
const commandId = e.commandId;
|
||||
|
||||
@@ -7,7 +7,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
|
||||
@@ -243,6 +243,10 @@ export class Extension implements IExten
|
||||
@@ -244,6 +244,10 @@ export class Extension implements IExten
|
||||
if (this.type === ExtensionType.System && this.productService.quality === 'stable') {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
@@ -260,6 +260,11 @@ export interface IWorkbenchConstructionO
|
||||
@@ -288,6 +288,11 @@ export interface IWorkbenchConstructionO
|
||||
*/
|
||||
readonly userDataPath?: string
|
||||
|
||||
@@ -28,19 +28,19 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
@@ -32,6 +32,11 @@ export interface IBrowserWorkbenchEnviro
|
||||
* Options used to configure the workbench.
|
||||
*/
|
||||
@@ -34,6 +34,11 @@ export interface IBrowserWorkbenchEnviro
|
||||
readonly options?: IWorkbenchConstructionOptions;
|
||||
+
|
||||
+ /**
|
||||
|
||||
/**
|
||||
+ * Enable downloading files via menu actions.
|
||||
+ */
|
||||
+ readonly isEnabledFileDownloads?: boolean;
|
||||
}
|
||||
|
||||
export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
|
||||
@@ -101,6 +106,13 @@ export class BrowserWorkbenchEnvironment
|
||||
+
|
||||
+ /**
|
||||
* Gets whether a resolver extension is expected for the environment.
|
||||
*/
|
||||
readonly expectsResolverExtension: boolean;
|
||||
@@ -111,6 +116,13 @@ export class BrowserWorkbenchEnvironment
|
||||
return this.options.userDataPath;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
@@ -14,6 +14,7 @@ export const serverOptions: OptionDescri
|
||||
@@ -16,6 +16,7 @@ export const serverOptions: OptionDescri
|
||||
/* ----- code-server ----- */
|
||||
'disable-update-check': { type: 'boolean' },
|
||||
'auth': { type: 'string' },
|
||||
@@ -66,7 +66,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
|
||||
/* ----- server setup ----- */
|
||||
|
||||
@@ -94,6 +95,7 @@ export interface ServerParsedArgs {
|
||||
@@ -97,6 +98,7 @@ export interface ServerParsedArgs {
|
||||
/* ----- code-server ----- */
|
||||
'disable-update-check'?: boolean;
|
||||
'auth'?: string
|
||||
@@ -78,7 +78,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -304,6 +304,7 @@ export class WebClientServer {
|
||||
@@ -332,6 +332,7 @@ export class WebClientServer {
|
||||
remoteAuthority,
|
||||
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
|
||||
userDataPath: this._environmentService.userDataPath,
|
||||
@@ -90,29 +90,22 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/contextkeys.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
|
||||
@@ -7,12 +7,11 @@ import { Event } from 'vs/base/common/ev
|
||||
@@ -7,12 +7,12 @@ import { Event } from 'vs/base/common/ev
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext } from 'vs/workbench/common/contextkeys';
|
||||
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys';
|
||||
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
|
||||
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys';
|
||||
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys';
|
||||
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
-import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
+import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { WorkbenchState, IWorkspaceContextService, isTemporaryWorkspace } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
@@ -25,6 +24,7 @@ import { IPaneCompositePartService } fro
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
+import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
|
||||
|
||||
export class WorkbenchContextKeysHandler extends Disposable {
|
||||
private inputFocusedContext: IContextKey<boolean>;
|
||||
@@ -77,7 +77,7 @@ export class WorkbenchContextKeysHandler
|
||||
@@ -79,7 +79,7 @@ export class WorkbenchContextKeysHandler
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@@ -121,7 +114,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IEditorResolverService private readonly editorResolverService: IEditorResolverService,
|
||||
@@ -205,6 +205,9 @@ export class WorkbenchContextKeysHandler
|
||||
@@ -209,6 +209,9 @@ export class WorkbenchContextKeysHandler
|
||||
this.auxiliaryBarVisibleContext = AuxiliaryBarVisibleContext.bindTo(this.contextKeyService);
|
||||
this.auxiliaryBarVisibleContext.set(this.layoutService.isVisible(Parts.AUXILIARYBAR_PART));
|
||||
|
||||
@@ -144,7 +137,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions
|
||||
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
@@ -485,13 +485,16 @@ MenuRegistry.appendMenuItem(MenuId.Explo
|
||||
@@ -489,13 +489,16 @@ MenuRegistry.appendMenuItem(MenuId.Explo
|
||||
id: DOWNLOAD_COMMAND_ID,
|
||||
title: DOWNLOAD_LABEL
|
||||
},
|
||||
@@ -172,7 +165,7 @@ Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
@@ -35,6 +35,8 @@ export const HasWebFileSystemAccess = ne
|
||||
@@ -38,6 +38,8 @@ export const HasWebFileSystemAccess = ne
|
||||
|
||||
export const EmbedderIdentifierContext = new RawContextKey<string | undefined>('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined'));
|
||||
|
||||
|
||||
@@ -21,15 +21,15 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
@@ -233,6 +233,9 @@ export async function setupServerService
|
||||
@@ -231,6 +231,9 @@ export async function setupServerService
|
||||
const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority));
|
||||
socketServer.registerChannel('extensions', channel);
|
||||
|
||||
+ const languagePackChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILanguagePackService));
|
||||
+ socketServer.registerChannel('languagePacks', languagePackChannel);
|
||||
+
|
||||
const encryptionChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(IEncryptionMainService));
|
||||
socketServer.registerChannel('encryption', encryptionChannel);
|
||||
const credentialsChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ICredentialsMainService));
|
||||
socketServer.registerChannel('credentials', credentialsChannel);
|
||||
|
||||
Index: code-server/lib/vscode/src/vs/base/common/platform.ts
|
||||
===================================================================
|
||||
@@ -89,10 +89,10 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
|
||||
<!-- Workbench Icon/Manifest/CSS -->
|
||||
<link rel="icon" href="{{BASE}}/_static/src/browser/media/favicon-dark-support.svg" />
|
||||
<link rel="alternate icon" href="{{BASE}}/_static/src/browser/media/favicon.ico" type="image/x-icon" />
|
||||
@@ -46,15 +49,26 @@
|
||||
// Set up nls if the user is not using the default language (English)
|
||||
const nlsConfig = {};
|
||||
const locale = window.localStorage.getItem('vscode.nls.locale') || navigator.language;
|
||||
@@ -48,15 +51,26 @@
|
||||
// Normalize locale to lowercase because translationServiceUrl is case-sensitive.
|
||||
// ref: https://github.com/microsoft/vscode/issues/187795
|
||||
const locale = window.localStorage.getItem('vscode.nls.locale') || navigator.language.toLowerCase();
|
||||
- if (!locale.startsWith('en')) {
|
||||
- nlsConfig['vs/nls'] = {
|
||||
- availableLanguages: {
|
||||
@@ -203,36 +203,36 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -26,6 +26,7 @@ import { URI } from 'vs/base/common/uri'
|
||||
@@ -27,6 +27,7 @@ import { URI } from 'vs/base/common/uri'
|
||||
import { streamToBuffer } from 'vs/base/common/buffer';
|
||||
import { IProductConfiguration } from 'vs/base/common/product';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
+import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
@@ -344,6 +345,8 @@ export class WebClientServer {
|
||||
callbackRoute: this._callbackRoute
|
||||
};
|
||||
|
||||
@@ -299,6 +300,8 @@ export class WebClientServer {
|
||||
|
||||
const base = relativeRoot(getOriginalUrl(req))
|
||||
const vscodeBase = relativePath(getOriginalUrl(req))
|
||||
+ const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath);
|
||||
+ const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath)
|
||||
|
||||
const workbenchWebConfiguration = {
|
||||
remoteAuthority,
|
||||
@@ -336,6 +339,7 @@ export class WebClientServer {
|
||||
const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl;
|
||||
const values: { [key: string]: string } = {
|
||||
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
|
||||
@@ -352,6 +355,7 @@ export class WebClientServer {
|
||||
WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''),
|
||||
BASE: base,
|
||||
VS_BASE: vscodeBase,
|
||||
+ NLS_CONFIGURATION: asJSON(nlsConfiguration),
|
||||
};
|
||||
|
||||
|
||||
if (useTestResolver) {
|
||||
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
@@ -15,6 +15,7 @@ export const serverOptions: OptionDescri
|
||||
@@ -17,6 +17,7 @@ export const serverOptions: OptionDescri
|
||||
'disable-update-check': { type: 'boolean' },
|
||||
'auth': { type: 'string' },
|
||||
'disable-file-downloads': { type: 'boolean' },
|
||||
@@ -240,7 +240,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
|
||||
/* ----- server setup ----- */
|
||||
|
||||
@@ -96,6 +97,7 @@ export interface ServerParsedArgs {
|
||||
@@ -99,6 +100,7 @@ export interface ServerParsedArgs {
|
||||
'disable-update-check'?: boolean;
|
||||
'auth'?: string
|
||||
'disable-file-downloads'?: boolean;
|
||||
@@ -261,9 +261,9 @@ Index: code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts
|
||||
import 'vs/workbench/services/path/browser/pathService';
|
||||
import 'vs/workbench/services/themes/browser/browserHostColorSchemeService';
|
||||
import 'vs/workbench/services/encryption/browser/encryptionService';
|
||||
@@ -119,8 +119,9 @@ import 'vs/workbench/contrib/logs/browse
|
||||
// Explorer
|
||||
import 'vs/workbench/contrib/files/browser/files.web.contribution';
|
||||
@@ -117,8 +117,9 @@ registerSingleton(ILanguagePackService,
|
||||
// Logs
|
||||
import 'vs/workbench/contrib/logs/browser/logs.contribution';
|
||||
|
||||
-// Localization
|
||||
-import 'vs/workbench/contrib/localization/browser/localization.contribution';
|
||||
@@ -339,7 +339,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
|
||||
@@ -335,9 +335,6 @@ export abstract class AbstractInstallAct
|
||||
@@ -321,9 +321,6 @@ export class InstallAction extends Exten
|
||||
if (this.extension.isBuiltin) {
|
||||
return;
|
||||
}
|
||||
@@ -349,7 +349,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
|
||||
if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) {
|
||||
this.enabled = this.options.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion;
|
||||
this.updateLabel();
|
||||
@@ -715,7 +712,7 @@ export abstract class InstallInOtherServ
|
||||
@@ -591,7 +588,7 @@ export abstract class InstallInOtherServ
|
||||
}
|
||||
|
||||
if (isLanguagePackExtension(this.extension.local.manifest)) {
|
||||
@@ -358,7 +358,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
|
||||
}
|
||||
|
||||
// Prefers to run on UI
|
||||
@@ -1803,17 +1800,6 @@ export class SetLanguageAction extends E
|
||||
@@ -1683,17 +1680,6 @@ export class SetLanguageAction extends E
|
||||
update(): void {
|
||||
this.enabled = false;
|
||||
this.class = SetLanguageAction.DisabledClass;
|
||||
@@ -376,7 +376,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
|
||||
}
|
||||
|
||||
override async run(): Promise<any> {
|
||||
@@ -1830,7 +1816,6 @@ export class ClearLanguageAction extends
|
||||
@@ -1710,7 +1696,6 @@ export class ClearLanguageAction extends
|
||||
private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`;
|
||||
|
||||
constructor(
|
||||
@@ -384,7 +384,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
|
||||
@ILocaleService private readonly localeService: ILocaleService,
|
||||
) {
|
||||
super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false);
|
||||
@@ -1840,17 +1825,6 @@ export class ClearLanguageAction extends
|
||||
@@ -1720,17 +1705,6 @@ export class ClearLanguageAction extends
|
||||
update(): void {
|
||||
this.enabled = false;
|
||||
this.class = ClearLanguageAction.DisabledClass;
|
||||
|
||||
@@ -10,16 +10,25 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
|
||||
@@ -60,7 +60,7 @@ import { GettingStartedIndexList } from
|
||||
@@ -10,7 +10,7 @@ import { IInstantiationService } from 'v
|
||||
import { IEditorSerializer, IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
-import { $, addDisposableListener, append, clearNode, Dimension, reset } from 'vs/base/browser/dom';
|
||||
+import { $, addDisposableListener, append, clearNode, Dimension, reset, prepend } from 'vs/base/browser/dom';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { hiddenEntriesConfigurationKey, IResolvedWalkthrough, IResolvedWalkthroughStep, IWalkthroughsService } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService';
|
||||
@@ -59,7 +59,7 @@ import { GettingStartedIndexList } from
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
-import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys';
|
||||
+import { IsEnabledCoderGettingStarted, WorkbenchStateContext } from 'vs/workbench/common/contextkeys';
|
||||
import { OpenFolderViaWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { OpenFolderAction, OpenFileFolderAction, OpenFolderViaWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { OpenRecentAction } from 'vs/workbench/browser/actions/windowActions';
|
||||
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
|
||||
@@ -796,6 +796,72 @@ export class GettingStartedPage extends
|
||||
@@ -783,6 +783,72 @@ export class GettingStartedPage extends
|
||||
$('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))
|
||||
);
|
||||
|
||||
@@ -92,62 +101,16 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
|
||||
const leftColumn = $('.categories-column.categories-column-left', {},);
|
||||
const rightColumn = $('.categories-column.categories-column-right', {},);
|
||||
|
||||
@@ -814,13 +880,23 @@ export class GettingStartedPage extends
|
||||
const layoutLists = () => {
|
||||
if (gettingStartedList.itemCount) {
|
||||
this.container.classList.remove('noWalkthroughs');
|
||||
- reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
- reset(rightColumn, featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
|
||||
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
|
||||
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement(), featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
|
||||
+ reset(rightColumn, gettingStartedCoder);
|
||||
+ } else {
|
||||
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
+ reset(rightColumn, featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
|
||||
+ }
|
||||
@@ -832,6 +898,9 @@ export class GettingStartedPage extends
|
||||
recentList.setLimit(5);
|
||||
reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
}
|
||||
else {
|
||||
this.container.classList.add('noWalkthroughs');
|
||||
- reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
- reset(rightColumn, featuredExtensionList.getDomElement());
|
||||
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
|
||||
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement(), featuredExtensionList.getDomElement());
|
||||
+ reset(rightColumn, gettingStartedCoder);
|
||||
+ } else {
|
||||
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
+ reset(rightColumn, featuredExtensionList.getDomElement());
|
||||
+ }
|
||||
}
|
||||
setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50);
|
||||
};
|
||||
@@ -828,13 +904,23 @@ export class GettingStartedPage extends
|
||||
const layoutFeaturedExtension = () => {
|
||||
if (featuredExtensionList.itemCount) {
|
||||
this.container.classList.remove('noExtensions');
|
||||
- reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
- reset(rightColumn, featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
|
||||
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
|
||||
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement(), featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
|
||||
+ reset(rightColumn, gettingStartedCoder);
|
||||
+ } else {
|
||||
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
+ reset(rightColumn, featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
|
||||
+ }
|
||||
}
|
||||
else {
|
||||
this.container.classList.add('noExtensions');
|
||||
- reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
- reset(rightColumn, gettingStartedList.getDomElement());
|
||||
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
|
||||
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement(), gettingStartedList.getDomElement());
|
||||
+ reset(rightColumn, gettingStartedCoder);
|
||||
+ } else {
|
||||
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
+ reset(rightColumn, gettingStartedList.getDomElement());
|
||||
+ }
|
||||
}
|
||||
setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50);
|
||||
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
|
||||
+ prepend(rightColumn, gettingStartedCoder)
|
||||
+ }
|
||||
};
|
||||
|
||||
featuredExtensionList.onDidChange(layoutFeaturedExtension);
|
||||
Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
|
||||
@@ -172,7 +135,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
@@ -265,6 +265,11 @@ export interface IWorkbenchConstructionO
|
||||
@@ -293,6 +293,11 @@ export interface IWorkbenchConstructionO
|
||||
*/
|
||||
readonly isEnabledFileDownloads?: boolean
|
||||
|
||||
@@ -188,19 +151,19 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
@@ -37,6 +37,11 @@ export interface IBrowserWorkbenchEnviro
|
||||
* Enable downloading files via menu actions.
|
||||
*/
|
||||
@@ -39,6 +39,11 @@ export interface IBrowserWorkbenchEnviro
|
||||
readonly isEnabledFileDownloads?: boolean;
|
||||
+
|
||||
+ /**
|
||||
|
||||
/**
|
||||
+ * Enable Coder's custom getting started text.
|
||||
+ */
|
||||
+ readonly isEnabledCoderGettingStarted?: boolean;
|
||||
}
|
||||
|
||||
export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
|
||||
@@ -113,6 +118,13 @@ export class BrowserWorkbenchEnvironment
|
||||
+
|
||||
+ /**
|
||||
* Gets whether a resolver extension is expected for the environment.
|
||||
*/
|
||||
readonly expectsResolverExtension: boolean;
|
||||
@@ -123,6 +128,13 @@ export class BrowserWorkbenchEnvironment
|
||||
return this.options.isEnabledFileDownloads;
|
||||
}
|
||||
|
||||
@@ -218,7 +181,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
@@ -16,6 +16,7 @@ export const serverOptions: OptionDescri
|
||||
@@ -18,6 +18,7 @@ export const serverOptions: OptionDescri
|
||||
'auth': { type: 'string' },
|
||||
'disable-file-downloads': { type: 'boolean' },
|
||||
'locale': { type: 'string' },
|
||||
@@ -226,7 +189,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
|
||||
/* ----- server setup ----- */
|
||||
|
||||
@@ -98,6 +99,7 @@ export interface ServerParsedArgs {
|
||||
@@ -101,6 +102,7 @@ export interface ServerParsedArgs {
|
||||
'auth'?: string
|
||||
'disable-file-downloads'?: boolean;
|
||||
'locale'?: string
|
||||
@@ -238,7 +201,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -308,6 +308,7 @@ export class WebClientServer {
|
||||
@@ -335,6 +335,7 @@ export class WebClientServer {
|
||||
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
|
||||
userDataPath: this._environmentService.userDataPath,
|
||||
isEnabledFileDownloads: !this._environmentService.args['disable-file-downloads'],
|
||||
@@ -254,12 +217,12 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys';
|
||||
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, IsEnabledCoderGettingStarted } from 'vs/workbench/common/contextkeys';
|
||||
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
|
||||
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys';
|
||||
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, IsEnabledCoderGettingStarted, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys';
|
||||
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
@@ -207,6 +207,7 @@ export class WorkbenchContextKeysHandler
|
||||
@@ -211,6 +211,7 @@ export class WorkbenchContextKeysHandler
|
||||
|
||||
// code-server
|
||||
IsEnabledFileDownloads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileDownloads ?? true)
|
||||
@@ -271,7 +234,7 @@ Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
@@ -36,6 +36,7 @@ export const HasWebFileSystemAccess = ne
|
||||
@@ -39,6 +39,7 @@ export const HasWebFileSystemAccess = ne
|
||||
export const EmbedderIdentifierContext = new RawContextKey<string | undefined>('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined'));
|
||||
|
||||
export const IsEnabledFileDownloads = new RawContextKey<boolean>('isEnabledFileDownloads', true, true);
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
Add the ability to provide a GitHub token
|
||||
|
||||
To test install the GitHub PR extension and start code-server with GITHUB_TOKEN
|
||||
or set github-auth in the config file. The extension should be authenticated.
|
||||
|
||||
Index: code-server/lib/vscode/src/vs/platform/credentials/node/credentialsMainService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/platform/credentials/node/credentialsMainService.ts
|
||||
+++ code-server/lib/vscode/src/vs/platform/credentials/node/credentialsMainService.ts
|
||||
@@ -5,9 +5,18 @@
|
||||
|
||||
import { InMemoryCredentialsProvider } from 'vs/platform/credentials/common/credentials';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
-import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
+import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { BaseCredentialsMainService, KeytarModule } from 'vs/platform/credentials/common/credentialsMainService';
|
||||
+import { generateUuid } from 'vs/base/common/uuid';
|
||||
+import { equals as arrayEquals } from 'vs/base/common/arrays';
|
||||
+
|
||||
+interface IToken {
|
||||
+ accessToken: string
|
||||
+ account?: { label: string }
|
||||
+ id: string
|
||||
+ scopes: string[]
|
||||
+}
|
||||
|
||||
export class CredentialsWebMainService extends BaseCredentialsMainService {
|
||||
// Since we fallback to the in-memory credentials provider, we do not need to surface any Keytar load errors
|
||||
@@ -16,10 +25,15 @@ export class CredentialsWebMainService e
|
||||
|
||||
constructor(
|
||||
@ILogService logService: ILogService,
|
||||
- @INativeEnvironmentService private readonly environmentMainService: INativeEnvironmentService,
|
||||
+ @IServerEnvironmentService private readonly environmentMainService: IServerEnvironmentService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
) {
|
||||
super(logService);
|
||||
+ if (this.environmentMainService.args["github-auth"]) {
|
||||
+ this.storeGitHubToken(this.environmentMainService.args["github-auth"]).catch((error) => {
|
||||
+ this.logService.error('Failed to store provided GitHub token', error)
|
||||
+ })
|
||||
+ }
|
||||
}
|
||||
|
||||
// If the credentials service is running on the server, we add a suffix -server to differentiate from the location that the
|
||||
@@ -48,4 +62,59 @@ export class CredentialsWebMainService e
|
||||
}
|
||||
return this._keytarCache;
|
||||
}
|
||||
+
|
||||
+ private async storeGitHubToken(githubToken: string): Promise<void> {
|
||||
+ const extensionId = 'vscode.github-authentication';
|
||||
+ const service = `${await this.getSecretStoragePrefix()}${extensionId}`;
|
||||
+ const account = 'github.auth';
|
||||
+ const scopes = [['read:user', 'user:email', 'repo']]
|
||||
+
|
||||
+ // Oddly the scopes need to match exactly so we cannot just have one token
|
||||
+ // with all the scopes, instead we have to duplicate the token for each
|
||||
+ // expected set of scopes.
|
||||
+ const tokens: IToken[] = scopes.map((scopes) => ({
|
||||
+ id: generateUuid(),
|
||||
+ scopes: scopes.sort(), // Sort for comparing later.
|
||||
+ accessToken: githubToken,
|
||||
+ }));
|
||||
+
|
||||
+ const raw = await this.getPassword(service, account)
|
||||
+
|
||||
+ let existing: {
|
||||
+ content: IToken[]
|
||||
+ } | undefined;
|
||||
+
|
||||
+ if (raw) {
|
||||
+ try {
|
||||
+ const json = JSON.parse(raw);
|
||||
+ json.content = JSON.parse(json.content);
|
||||
+ existing = json;
|
||||
+ } catch (error) {
|
||||
+ this.logService.error('Failed to parse existing GitHub credentials', error)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // Keep tokens for account and scope combinations we do not have in case
|
||||
+ // there is an extension that uses scopes we have not accounted for (in
|
||||
+ // these cases the user will need to manually authenticate the extension
|
||||
+ // through the UI) or the user has tokens for other accounts.
|
||||
+ if (existing?.content) {
|
||||
+ existing.content = existing.content.filter((existingToken) => {
|
||||
+ const scopes = existingToken.scopes.sort();
|
||||
+ return !(tokens.find((token) => {
|
||||
+ return arrayEquals(scopes, token.scopes)
|
||||
+ && token.account?.label === existingToken.account?.label;
|
||||
+ }))
|
||||
+ })
|
||||
+ }
|
||||
+
|
||||
+ return this.setPassword(service, account, JSON.stringify({
|
||||
+ extensionId,
|
||||
+ ...(existing || {}),
|
||||
+ content: JSON.stringify([
|
||||
+ ...tokens,
|
||||
+ ...(existing?.content || []),
|
||||
+ ])
|
||||
+ }));
|
||||
+ }
|
||||
}
|
||||
@@ -95,7 +95,7 @@ Index: code-server/lib/vscode/src/vs/base/common/processes.ts
|
||||
--- code-server.orig/lib/vscode/src/vs/base/common/processes.ts
|
||||
+++ code-server/lib/vscode/src/vs/base/common/processes.ts
|
||||
@@ -111,6 +111,8 @@ export function sanitizeProcessEnvironme
|
||||
/^VSCODE_(?!(PORTABLE|SHELL_LOGIN)).+$/,
|
||||
/^VSCODE_(?!(PORTABLE|SHELL_LOGIN|ENV_REPLACE|ENV_APPEND|ENV_PREPEND)).+$/,
|
||||
/^SNAP(|_.*)$/,
|
||||
/^GDK_PIXBUF_.+$/,
|
||||
+ /^CODE_SERVER_.+$/,
|
||||
@@ -107,7 +107,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandl
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts
|
||||
@@ -76,8 +76,11 @@ export class BrowserDialogHandler extend
|
||||
@@ -77,8 +77,11 @@ export class BrowserDialogHandler extend
|
||||
|
||||
async about(): Promise<void> {
|
||||
const detailString = (useAgo: boolean): string => {
|
||||
@@ -176,7 +176,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
@@ -69,6 +69,7 @@ import { IndexedDB } from 'vs/base/brows
|
||||
@@ -67,6 +67,7 @@ import { IndexedDB } from 'vs/base/brows
|
||||
import { BrowserCredentialsService } from 'vs/workbench/services/credentials/browser/credentialsService';
|
||||
import { IWorkspace } from 'vs/workbench/services/host/browser/browserHostService';
|
||||
import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess';
|
||||
@@ -184,7 +184,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { DelayedLogChannel } from 'vs/workbench/services/output/common/delayedLogChannel';
|
||||
@@ -119,6 +120,9 @@ export class BrowserMain extends Disposa
|
||||
@@ -132,6 +133,9 @@ export class BrowserMain extends Disposa
|
||||
// Startup
|
||||
const instantiationService = workbench.startup();
|
||||
|
||||
@@ -264,11 +264,11 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -308,6 +308,7 @@ export class WebClientServer {
|
||||
folderUri: resolveWorkspaceURI(this._environmentService.args['default-folder']),
|
||||
workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']),
|
||||
productConfiguration: <Partial<IProductConfiguration>>{
|
||||
+ codeServerVersion: this._productService.codeServerVersion,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
...this._productService.extensionsGallery,
|
||||
@@ -306,6 +306,7 @@ export class WebClientServer {
|
||||
} : undefined;
|
||||
|
||||
const productConfiguration = <Partial<IProductConfiguration>>{
|
||||
+ codeServerVersion: this._productService.codeServerVersion,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
...this._productService.extensionsGallery,
|
||||
|
||||
@@ -20,7 +20,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -303,6 +303,7 @@ export class WebClientServer {
|
||||
@@ -327,6 +327,7 @@ export class WebClientServer {
|
||||
const workbenchWebConfiguration = {
|
||||
remoteAuthority,
|
||||
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
|
||||
@@ -32,7 +32,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
@@ -255,6 +255,11 @@ export interface IWorkbenchConstructionO
|
||||
@@ -283,6 +283,11 @@ export interface IWorkbenchConstructionO
|
||||
*/
|
||||
readonly configurationDefaults?: Record<string, any>;
|
||||
|
||||
@@ -48,7 +48,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
@@ -92,7 +92,14 @@ export class BrowserWorkbenchEnvironment
|
||||
@@ -102,7 +102,14 @@ export class BrowserWorkbenchEnvironment
|
||||
get logFile(): URI { return joinPath(this.windowLogsPath, 'window.log'); }
|
||||
|
||||
@memoize
|
||||
|
||||
@@ -20,7 +20,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
@@ -13,6 +13,7 @@ import { IEnvironmentService, INativeEnv
|
||||
@@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri'
|
||||
export const serverOptions: OptionDescriptions<Required<ServerParsedArgs>> = {
|
||||
/* ----- code-server ----- */
|
||||
'disable-update-check': { type: 'boolean' },
|
||||
@@ -28,7 +28,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
|
||||
/* ----- server setup ----- */
|
||||
|
||||
@@ -92,6 +93,7 @@ export const serverOptions: OptionDescri
|
||||
@@ -95,6 +96,7 @@ export const serverOptions: OptionDescri
|
||||
export interface ServerParsedArgs {
|
||||
/* ----- code-server ----- */
|
||||
'disable-update-check'?: boolean;
|
||||
@@ -40,14 +40,14 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -313,6 +313,7 @@ export class WebClientServer {
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
+ logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
},
|
||||
@@ -311,6 +311,7 @@ export class WebClientServer {
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
+ logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
Index: code-server/lib/vscode/src/vs/workbench/browser/client.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/client.ts
|
||||
|
||||
@@ -40,7 +40,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -111,7 +111,7 @@ export class WebClientServer {
|
||||
@@ -113,7 +113,7 @@ export class WebClientServer {
|
||||
const serverRootPath = getRemoteServerRootPath(_productService);
|
||||
this._staticRoute = `${serverRootPath}/static`;
|
||||
this._callbackRoute = `${serverRootPath}/callback`;
|
||||
@@ -49,22 +49,22 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,14 +312,7 @@ export class WebClientServer {
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
embedderIdentifier: 'server-distro',
|
||||
- extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
- ...this._productService.extensionsGallery,
|
||||
- 'resourceUrlTemplate': this._webExtensionResourceUrlTemplate.with({
|
||||
- scheme: 'http',
|
||||
- authority: remoteAuthority,
|
||||
- path: `${this._webExtensionRoute}/${this._webExtensionResourceUrlTemplate.authority}${this._webExtensionResourceUrlTemplate.path}`
|
||||
- }).toString(true)
|
||||
- } : undefined
|
||||
+ extensionsGallery: this._productService.extensionsGallery,
|
||||
},
|
||||
callbackRoute: this._callbackRoute
|
||||
@@ -311,14 +311,7 @@ export class WebClientServer {
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
embedderIdentifier: 'server-distro',
|
||||
- extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
- ...this._productService.extensionsGallery,
|
||||
- 'resourceUrlTemplate': this._webExtensionResourceUrlTemplate.with({
|
||||
- scheme: 'http',
|
||||
- authority: remoteAuthority,
|
||||
- path: `${this._webExtensionRoute}/${this._webExtensionResourceUrlTemplate.authority}${this._webExtensionResourceUrlTemplate.path}`
|
||||
- }).toString(true)
|
||||
- } : undefined
|
||||
+ extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
|
||||
if (!this._environmentService.isBuilt) {
|
||||
Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
|
||||
@@ -74,10 +74,10 @@ Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/ext
|
||||
import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
-import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { TargetPlatform } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource';
|
||||
|
||||
@@ -60,7 +59,7 @@ export abstract class AbstractExtensionR
|
||||
@@ -77,7 +76,7 @@ export abstract class AbstractExtensionR
|
||||
private readonly _environmentService: IEnvironmentService,
|
||||
private readonly _configurationService: IConfigurationService,
|
||||
) {
|
||||
|
||||
@@ -42,17 +42,8 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityReso
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts
|
||||
+++ code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts
|
||||
@@ -11,7 +11,7 @@ import { StopWatch } from 'vs/base/commo
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
-import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
+import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolvedOptions, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { getRemoteServerRootPath, parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts';
|
||||
|
||||
export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService {
|
||||
@@ -29,7 +29,7 @@ export class RemoteAuthorityResolverServ
|
||||
constructor(
|
||||
@@ -33,7 +33,7 @@ export class RemoteAuthorityResolverServ
|
||||
isWorkbenchOptionsBasedResolution: boolean,
|
||||
connectionToken: Promise<string> | string | undefined,
|
||||
resourceUriProvider: ((uri: URI) => URI) | undefined,
|
||||
- @IProductService productService: IProductService,
|
||||
@@ -60,7 +51,7 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityReso
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
@@ -75,9 +75,14 @@ export class RemoteAuthorityResolverServ
|
||||
@@ -84,9 +84,14 @@ export class RemoteAuthorityResolverServ
|
||||
const connectionToken = await Promise.resolve(this._connectionTokens.get(authority) || this._connectionToken);
|
||||
performance.mark(`code/didResolveConnectionToken/${authorityPrefix}`);
|
||||
this._logService.info(`Resolved connection token (${authorityPrefix}) after ${sw.elapsed()} ms`);
|
||||
@@ -71,28 +62,28 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityReso
|
||||
+ }
|
||||
const defaultPort = (/^https:/.test(window.location.href) ? 443 : 80);
|
||||
const { host, port } = parseAuthorityWithOptionalPort(authority, defaultPort);
|
||||
- const result: ResolverResult = { authority: { authority, host: host, port: port, connectionToken } };
|
||||
+ const result: ResolverResult = { authority: { authority, host: host, port: port, connectionToken }, options };
|
||||
RemoteAuthorities.set(authority, result.authority.host, result.authority.port);
|
||||
- const result: ResolverResult = { authority: { authority, connectTo: new WebSocketRemoteConnection(host, port), connectionToken } };
|
||||
+ const result: ResolverResult = { authority: { authority, connectTo: new WebSocketRemoteConnection(host, port), connectionToken }, options };
|
||||
RemoteAuthorities.set(authority, host, port);
|
||||
this._cache.set(authority, result);
|
||||
this._onDidChangeConnectionData.fire();
|
||||
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -314,6 +314,7 @@ export class WebClientServer {
|
||||
rootEndpoint: base,
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
+ proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
},
|
||||
@@ -312,6 +312,7 @@ export class WebClientServer {
|
||||
rootEndpoint: base,
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
+ proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
Index: code-server/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
|
||||
@@ -383,7 +383,7 @@ export async function createTerminalEnvi
|
||||
@@ -271,7 +271,7 @@ export async function createTerminalEnvi
|
||||
|
||||
// Sanitize the environment, removing any undesirable VS Code and Electron environment
|
||||
// variables
|
||||
@@ -105,33 +96,32 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
@@ -21,6 +21,7 @@ import type { ICredentialsProvider } fro
|
||||
import type { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService';
|
||||
@@ -19,6 +19,7 @@ import { isFolderToOpen, isWorkspaceToOp
|
||||
import type { IWorkbenchConstructionOptions } from 'vs/workbench/browser/web.api';
|
||||
import { AuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import type { IWorkspace, IWorkspaceProvider } from 'vs/workbench/services/host/browser/browserHostService';
|
||||
+import { extractLocalHostUriMetaDataForPortMapping, TunnelOptions, TunnelCreationOptions } from 'vs/platform/tunnel/common/tunnel';
|
||||
import type { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService';
|
||||
import { create } from 'vs/workbench/workbench.web.main';
|
||||
|
||||
interface ICredential {
|
||||
service: string;
|
||||
@@ -511,6 +512,38 @@ function doCreateUri(path: string, query
|
||||
} : undefined,
|
||||
@@ -571,6 +572,39 @@ class WorkspaceProvider implements IWork
|
||||
settingsSyncOptions: config.settingsSyncOptions ? { enabled: config.settingsSyncOptions.enabled, } : undefined,
|
||||
workspaceProvider: WorkspaceProvider.create(config),
|
||||
urlCallbackProvider: new LocalStorageURLCallbackProvider(config.callbackRoute),
|
||||
- credentialsProvider: config.remoteAuthority ? undefined : new LocalStorageCredentialsProvider() // with a remote, we don't use a local credentials provider
|
||||
+ credentialsProvider: config.remoteAuthority ? undefined : new LocalStorageCredentialsProvider(), // with a remote, we don't use a local credentials provider
|
||||
+ resolveExternalUri: (uri: URI): Promise<URI> => {
|
||||
+ let resolvedUri = uri
|
||||
+ const localhostMatch = extractLocalHostUriMetaDataForPortMapping(resolvedUri)
|
||||
+
|
||||
+ if (localhostMatch && resolvedUri.authority !== location.host) {
|
||||
+ if (config.productConfiguration && config.productConfiguration.proxyEndpointTemplate) {
|
||||
+ resolvedUri = URI.parse(new URL(config.productConfiguration.proxyEndpointTemplate.replace('{{port}}', localhostMatch.port.toString()), window.location.href).toString())
|
||||
+ const renderedTemplate = config.productConfiguration.proxyEndpointTemplate
|
||||
+ .replace('{{port}}', localhostMatch.port.toString())
|
||||
+ .replace('{{host}}', window.location.host)
|
||||
+ resolvedUri = URI.parse(new URL(renderedTemplate, window.location.href).toString())
|
||||
+ } else {
|
||||
+ throw new Error(`Failed to resolve external URI: ${uri.toString()}. Could not determine base url because productConfiguration missing.`)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // If not localhost, return unmodified
|
||||
+ // If not localhost, return unmodified.
|
||||
+ return Promise.resolve(resolvedUri)
|
||||
+ },
|
||||
+ tunnelProvider: {
|
||||
@@ -150,19 +140,20 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
+ }
|
||||
+ })
|
||||
+ }
|
||||
+ }
|
||||
});
|
||||
})();
|
||||
+ },
|
||||
secretStorageProvider: config.remoteAuthority && !secretStorageKeyPath
|
||||
? undefined /* with a remote without embedder-preferred storage, store on the remote */
|
||||
: new LocalStorageSecretStorageProvider(secretStorageCrypto),
|
||||
Index: code-server/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
|
||||
@@ -73,7 +73,7 @@ export class ForwardedPortsView extends
|
||||
@@ -76,7 +76,7 @@ export class ForwardedPortsView extends
|
||||
this.contextKeyListener = undefined;
|
||||
}
|
||||
|
||||
- const viewEnabled: boolean = !!forwardedPortsViewEnabled.getValue(this.contextKeyService);
|
||||
+ const viewEnabled: boolean = true;
|
||||
|
||||
if (this.environmentService.remoteAuthority && viewEnabled) {
|
||||
if (viewEnabled) {
|
||||
const viewContainer = await this.getViewContainer();
|
||||
|
||||
68
patches/safari.diff
Normal file
68
patches/safari.diff
Normal file
@@ -0,0 +1,68 @@
|
||||
Revert back to es2020
|
||||
|
||||
es2022 outputs static blocks when using static properties that are not
|
||||
compatible with Safari, or at least not older versions of Safari.
|
||||
|
||||
Index: code-server/lib/vscode/src/tsconfig.base.json
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/tsconfig.base.json
|
||||
+++ code-server/lib/vscode/src/tsconfig.base.json
|
||||
@@ -17,9 +17,30 @@
|
||||
"./vs/*"
|
||||
]
|
||||
},
|
||||
- "target": "es2022",
|
||||
- "useDefineForClassFields": false,
|
||||
+ "target": "es2020",
|
||||
"lib": [
|
||||
+ "ES2016",
|
||||
+ "ES2017.Object",
|
||||
+ "ES2017.String",
|
||||
+ "ES2017.Intl",
|
||||
+ "ES2017.TypedArrays",
|
||||
+ "ES2018.AsyncIterable",
|
||||
+ "ES2018.AsyncGenerator",
|
||||
+ "ES2018.Promise",
|
||||
+ "ES2018.Regexp",
|
||||
+ "ES2018.Intl",
|
||||
+ "ES2019.Array",
|
||||
+ "ES2019.Object",
|
||||
+ "ES2019.String",
|
||||
+ "ES2019.Symbol",
|
||||
+ "ES2020.BigInt",
|
||||
+ "ES2020.Promise",
|
||||
+ "ES2020.String",
|
||||
+ "ES2020.Symbol.WellKnown",
|
||||
+ "ES2020.Intl",
|
||||
+ "ES2021.Promise",
|
||||
+ "ES2021.String",
|
||||
+ "ES2021.WeakRef",
|
||||
"ES2022",
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
Index: code-server/lib/vscode/build/lib/tsb/transpiler.js
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/build/lib/tsb/transpiler.js
|
||||
+++ code-server/lib/vscode/build/lib/tsb/transpiler.js
|
||||
@@ -293,7 +293,7 @@ class SwcTranspiler {
|
||||
tsx: false,
|
||||
decorators: true
|
||||
},
|
||||
- target: 'es2022',
|
||||
+ target: 'es2020',
|
||||
loose: false,
|
||||
minify: {
|
||||
compress: false,
|
||||
Index: code-server/lib/vscode/build/lib/tsb/transpiler.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/build/lib/tsb/transpiler.ts
|
||||
+++ code-server/lib/vscode/build/lib/tsb/transpiler.ts
|
||||
@@ -376,7 +376,7 @@ export class SwcTranspiler implements IT
|
||||
tsx: false,
|
||||
decorators: true
|
||||
},
|
||||
- target: 'es2022',
|
||||
+ target: 'es2020',
|
||||
loose: false,
|
||||
minify: {
|
||||
compress: false,
|
||||
@@ -9,7 +9,6 @@ update-check.diff
|
||||
logout.diff
|
||||
store-socket.diff
|
||||
proxy-uri.diff
|
||||
github-auth.diff
|
||||
unique-db.diff
|
||||
local-storage.diff
|
||||
service-worker.diff
|
||||
@@ -19,3 +18,4 @@ telemetry.diff
|
||||
display-language.diff
|
||||
cli-window-open.diff
|
||||
getting-started.diff
|
||||
safari.diff
|
||||
|
||||
@@ -54,14 +54,14 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -316,6 +316,10 @@ export class WebClientServer {
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
|
||||
+ serviceWorker: {
|
||||
+ scope: vscodeBase + '/',
|
||||
+ path: base + '/_static/out/browser/serviceWorker.js',
|
||||
+ },
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
},
|
||||
@@ -313,6 +313,10 @@ export class WebClientServer {
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
|
||||
+ serviceWorker: {
|
||||
+ scope: vscodeBase + '/',
|
||||
+ path: base + '/_static/out/browser/serviceWorker.js',
|
||||
+ },
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ Index: code-server/lib/vscode/build/gulpfile.reh.js
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/build/gulpfile.reh.js
|
||||
+++ code-server/lib/vscode/build/gulpfile.reh.js
|
||||
@@ -191,8 +191,7 @@ function packageTask(type, platform, arc
|
||||
@@ -238,8 +238,7 @@ function packageTask(type, platform, arc
|
||||
|
||||
const src = gulp.src(sourceFolderName + '/**', { base: '.' })
|
||||
.pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); }))
|
||||
@@ -20,7 +20,7 @@ Index: code-server/lib/vscode/build/gulpfile.reh.js
|
||||
|
||||
const workspaceExtensionPoints = ['debuggers', 'jsonValidation'];
|
||||
const isUIExtension = (manifest) => {
|
||||
@@ -231,9 +230,9 @@ function packageTask(type, platform, arc
|
||||
@@ -278,9 +277,9 @@ function packageTask(type, platform, arc
|
||||
.map(name => `.build/extensions/${name}/**`);
|
||||
|
||||
const extensions = gulp.src(extensionPaths, { base: '.build', dot: true });
|
||||
@@ -32,7 +32,7 @@ Index: code-server/lib/vscode/build/gulpfile.reh.js
|
||||
|
||||
let version = packageJson.version;
|
||||
const quality = product.quality;
|
||||
@@ -388,7 +387,7 @@ function tweakProductForServerWeb(produc
|
||||
@@ -427,7 +426,7 @@ function tweakProductForServerWeb(produc
|
||||
const minifyTask = task.define(`minify-vscode-${type}`, task.series(
|
||||
optimizeTask,
|
||||
util.rimraf(`out-vscode-${type}-min`),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Store a static reference to the IPC socket
|
||||
Store the IPC socket with workspace metadata.
|
||||
|
||||
This lets us use it to open files inside code-server from outside of
|
||||
code-server.
|
||||
@@ -9,29 +9,121 @@ To test this:
|
||||
|
||||
It should open in your existing code-server instance.
|
||||
|
||||
When the extension host is terminated, the socket is unregistered.
|
||||
|
||||
Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||
@@ -2,7 +2,9 @@
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-
|
||||
+import { promises as fs } from 'fs';
|
||||
+import * as os from 'os'
|
||||
+import * as path from 'vs/base/common/path';
|
||||
+import * as _http from 'http';
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
|
||||
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
||||
@@ -72,6 +74,10 @@ export class ExtHostExtensionService ext
|
||||
if (this._initData.remote.isRemote && this._initData.remote.authority) {
|
||||
const cliServer = this._instaService.createInstance(CLIServer);
|
||||
process.env['VSCODE_IPC_HOOK_CLI'] = cliServer.ipcHandlePath;
|
||||
+
|
||||
+ fs.writeFile(path.join(os.tmpdir(), 'vscode-ipc'), cliServer.ipcHandlePath).catch((error) => {
|
||||
+ this._logService.error(error);
|
||||
+ });
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { ExtensionRuntime } from 'vs/wor
|
||||
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
|
||||
import { realpathSync } from 'vs/base/node/extpath';
|
||||
import { ExtHostConsoleForwarder } from 'vs/workbench/api/node/extHostConsoleForwarder';
|
||||
+import { IExtHostWorkspace } from '../common/extHostWorkspace';
|
||||
import { ExtHostDiskFileSystemProvider } from 'vs/workbench/api/node/extHostDiskFileSystemProvider';
|
||||
|
||||
// Module loading tricks
|
||||
class NodeModuleRequireInterceptor extends RequireInterceptor {
|
||||
@@ -83,6 +84,52 @@ export class ExtHostExtensionService ext
|
||||
await interceptor.install();
|
||||
performance.mark('code/extHost/didInitAPI');
|
||||
|
||||
+ (async () => {
|
||||
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
|
||||
+ const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
|
||||
+ if (!socketPath || !codeServerSocketPath) {
|
||||
+ return;
|
||||
+ }
|
||||
+ const workspace = this._instaService.invokeFunction((accessor) => {
|
||||
+ const workspaceService = accessor.get(IExtHostWorkspace);
|
||||
+ return workspaceService.workspace;
|
||||
+ });
|
||||
+ const entry = {
|
||||
+ workspace,
|
||||
+ socketPath
|
||||
+ };
|
||||
+ const message = JSON.stringify({entry});
|
||||
+ await new Promise<void>((resolve, reject) => {
|
||||
+ const opts: _http.RequestOptions = {
|
||||
+ path: '/add-session',
|
||||
+ socketPath: codeServerSocketPath,
|
||||
+ method: 'POST',
|
||||
+ headers: {
|
||||
+ 'content-type': 'application/json',
|
||||
+ }
|
||||
+ };
|
||||
+ const req = _http.request(opts, (res) => {
|
||||
+ res.on('error', reject);
|
||||
+ res.on('end', () => {
|
||||
+ try {
|
||||
+ if (res.statusCode === 200) {
|
||||
+ resolve();
|
||||
+ } else {
|
||||
+ reject(new Error('Unexpected status code: ' + res.statusCode));
|
||||
+ }
|
||||
+ } catch (e: unknown) {
|
||||
+ reject(e);
|
||||
+ }
|
||||
+ });
|
||||
+ });
|
||||
+ req.on('error', reject);
|
||||
+ req.write(message);
|
||||
+ req.end();
|
||||
+ });
|
||||
+ })().catch(error => {
|
||||
+ this._logService.error(error);
|
||||
+ });
|
||||
+
|
||||
// Do this when extension service exists, but extensions are not being activated yet.
|
||||
const configProvider = await this._extHostConfiguration.getConfigProvider();
|
||||
await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData);
|
||||
Index: code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
+import * as _http from 'http';
|
||||
import * as nativeWatchdog from 'native-watchdog';
|
||||
import * as net from 'net';
|
||||
import * as minimist from 'minimist';
|
||||
@@ -400,7 +401,28 @@ async function startExtensionHostProcess
|
||||
);
|
||||
|
||||
// rewrite onTerminate-function to be a proper shutdown
|
||||
- onTerminate = (reason: string) => extensionHostMain.terminate(reason);
|
||||
+ onTerminate = (reason: string) => {
|
||||
+ extensionHostMain.terminate(reason);
|
||||
+
|
||||
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
|
||||
+ const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
|
||||
+ if (!socketPath || !codeServerSocketPath) {
|
||||
+ return;
|
||||
+ }
|
||||
+ const message = JSON.stringify({socketPath});
|
||||
+ const opts: _http.RequestOptions = {
|
||||
+ path: '/delete-session',
|
||||
+ socketPath: codeServerSocketPath,
|
||||
+ method: 'POST',
|
||||
+ headers: {
|
||||
+ 'content-type': 'application/json',
|
||||
+ 'accept': 'application/json'
|
||||
+ }
|
||||
+ };
|
||||
+ const req = _http.request(opts);
|
||||
+ req.write(message);
|
||||
+ req.end();
|
||||
+ };
|
||||
}
|
||||
|
||||
startExtensionHostProcess().catch((err) => console.log(err));
|
||||
|
||||
@@ -12,7 +12,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
@@ -69,6 +69,7 @@ import { IExtensionsScannerService } fro
|
||||
@@ -67,6 +67,7 @@ import { IExtensionsScannerService } fro
|
||||
import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService';
|
||||
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
|
||||
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
@@ -20,22 +20,18 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
import { NullPolicyService } from 'vs/platform/policy/common/policy';
|
||||
import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
|
||||
import { LoggerService } from 'vs/platform/log/node/loggerService';
|
||||
@@ -150,10 +151,13 @@ export async function setupServerService
|
||||
@@ -150,7 +151,10 @@ export async function setupServerService
|
||||
let oneDsAppender: ITelemetryAppender = NullAppender;
|
||||
const isInternal = isInternalTelemetry(productService, configurationService);
|
||||
if (supportsTelemetry(productService, environmentService)) {
|
||||
- if (productService.aiConfig && productService.aiConfig.ariaKey) {
|
||||
+ const telemetryEndpoint = process.env.CS_TELEMETRY_URL || "https://v1.telemetry.coder.com/track";
|
||||
+ if (telemetryEndpoint) {
|
||||
+ oneDsAppender = new OneDataSystemAppender(false, eventPrefix, null, () => new TelemetryClient(telemetryEndpoint));
|
||||
+ oneDsAppender = new OneDataSystemAppender(requestService, false, eventPrefix, null, () => new TelemetryClient(telemetryEndpoint));
|
||||
+ } else if (productService.aiConfig && productService.aiConfig.ariaKey) {
|
||||
oneDsAppender = new OneDataSystemAppender(isInternal, eventPrefix, null, productService.aiConfig.ariaKey);
|
||||
- disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
|
||||
oneDsAppender = new OneDataSystemAppender(requestService, isInternal, eventPrefix, null, productService.aiConfig.ariaKey);
|
||||
disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
|
||||
}
|
||||
+ disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
|
||||
|
||||
const config: ITelemetryServiceConfig = {
|
||||
appenders: [oneDsAppender],
|
||||
Index: code-server/lib/vscode/src/vs/server/node/telemetryClient.ts
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
@@ -94,11 +90,11 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -321,6 +321,7 @@ export class WebClientServer {
|
||||
scope: vscodeBase + '/',
|
||||
path: base + '/_static/out/browser/serviceWorker.js',
|
||||
},
|
||||
+ enableTelemetry: this._productService.enableTelemetry,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
@@ -317,6 +317,7 @@ export class WebClientServer {
|
||||
scope: vscodeBase + '/',
|
||||
path: base + '/_static/out/browser/serviceWorker.js',
|
||||
},
|
||||
+ enableTelemetry: this._productService.enableTelemetry,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
|
||||
@@ -105,20 +105,20 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -312,6 +312,7 @@ export class WebClientServer {
|
||||
productConfiguration: <Partial<IProductConfiguration>>{
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
+ updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
},
|
||||
@@ -310,6 +310,7 @@ export class WebClientServer {
|
||||
const productConfiguration = <Partial<IProductConfiguration>>{
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
+ updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
@@ -11,6 +11,8 @@ import { refineServiceDecorator } from '
|
||||
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -13,6 +13,8 @@ import { memoize } from 'vs/base/common/
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export const serverOptions: OptionDescriptions<Required<ServerParsedArgs>> = {
|
||||
+ /* ----- code-server ----- */
|
||||
@@ -126,7 +126,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
|
||||
/* ----- server setup ----- */
|
||||
|
||||
@@ -88,6 +90,8 @@ export const serverOptions: OptionDescri
|
||||
@@ -91,6 +93,8 @@ export const serverOptions: OptionDescri
|
||||
};
|
||||
|
||||
export interface ServerParsedArgs {
|
||||
|
||||
@@ -41,7 +41,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
@@ -215,7 +215,7 @@ export class BrowserWorkbenchEnvironment
|
||||
@@ -225,7 +225,7 @@ export class BrowserWorkbenchEnvironment
|
||||
|
||||
@memoize
|
||||
get webviewExternalEndpoint(): string {
|
||||
@@ -54,7 +54,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -302,6 +302,7 @@ export class WebClientServer {
|
||||
@@ -323,6 +323,7 @@ export class WebClientServer {
|
||||
|
||||
const workbenchWebConfiguration = {
|
||||
remoteAuthority,
|
||||
@@ -70,12 +70,12 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
- content="default-src 'none'; script-src 'sha256-4A7BC3A+gc8e1t5I0rfDNtBiGlA0/GTojKdfdDHAWYw=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
+ content="default-src 'none'; script-src 'sha256-8I6aYsGyWES1C043+SeEOyeZlC0Y0HN5WevEDh97c1Y=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
- content="default-src 'none'; script-src 'sha256-QA1gXilHYAUFCvp7MpjgcmyBCFzSKV0SpiecMU8aUVc=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
+ content="default-src 'none'; script-src 'sha256-5X5RiKYn8NTJVx919WStPrAmsV80rIIBbePhKquPcAQ=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
|
||||
<!-- Disable pinch zooming -->
|
||||
<meta name="viewport"
|
||||
@@ -323,6 +323,12 @@
|
||||
@@ -335,6 +335,12 @@
|
||||
|
||||
const hostname = location.hostname;
|
||||
|
||||
@@ -92,7 +92,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
|
||||
@@ -322,6 +322,12 @@
|
||||
@@ -334,6 +334,12 @@
|
||||
|
||||
const hostname = location.hostname;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
@@ -46,7 +46,9 @@ button {
|
||||
.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;
|
||||
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%;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
@@ -14,7 +14,11 @@ export enum HttpCode {
|
||||
* used in the HTTP response.
|
||||
*/
|
||||
export class HttpError extends Error {
|
||||
public constructor(message: string, public readonly statusCode: HttpCode, public readonly details?: object) {
|
||||
public constructor(
|
||||
message: string,
|
||||
public readonly statusCode: HttpCode,
|
||||
public readonly details?: object,
|
||||
) {
|
||||
super(message)
|
||||
this.name = this.constructor.name
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@ import * as util from "../common/util"
|
||||
import { DefaultedArgs } from "./cli"
|
||||
import { disposer } from "./http"
|
||||
import { isNodeJSErrnoException } from "./util"
|
||||
import { EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSocket"
|
||||
import { handleUpgrade } from "./wsRouter"
|
||||
|
||||
type ListenOptions = Pick<DefaultedArgs, "socket-mode" | "socket" | "port" | "host">
|
||||
type SocketOptions = { socket: string; "socket-mode"?: string }
|
||||
type ListenOptions = DefaultedArgs | SocketOptions
|
||||
|
||||
export interface App extends Disposable {
|
||||
/** Handles regular HTTP requests. */
|
||||
@@ -20,12 +22,18 @@ export interface App extends Disposable {
|
||||
wsRouter: Express
|
||||
/** The underlying HTTP server. */
|
||||
server: http.Server
|
||||
/** Handles requests to the editor session management API. */
|
||||
editorSessionManagerServer: http.Server
|
||||
}
|
||||
|
||||
export const listen = async (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
|
||||
if (socket) {
|
||||
const isSocketOpts = (opts: ListenOptions): opts is SocketOptions => {
|
||||
return !!(opts as SocketOptions).socket || !(opts as DefaultedArgs).host
|
||||
}
|
||||
|
||||
export const listen = async (server: http.Server, opts: ListenOptions) => {
|
||||
if (isSocketOpts(opts)) {
|
||||
try {
|
||||
await fs.unlink(socket)
|
||||
await fs.unlink(opts.socket)
|
||||
} catch (error: any) {
|
||||
handleArgsSocketCatchError(error)
|
||||
}
|
||||
@@ -38,18 +46,20 @@ export const listen = async (server: http.Server, { host, port, socket, "socket-
|
||||
server.on("error", (err) => util.logError(logger, "http server error", err))
|
||||
resolve()
|
||||
}
|
||||
if (socket) {
|
||||
server.listen(socket, onListen)
|
||||
if (isSocketOpts(opts)) {
|
||||
server.listen(opts.socket, onListen)
|
||||
} else {
|
||||
// [] is the correct format when using :: but Node errors with them.
|
||||
server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
|
||||
server.listen(opts.port, opts.host.replace(/^\[|\]$/g, ""), onListen)
|
||||
}
|
||||
})
|
||||
|
||||
// NOTE@jsjoeio: we need to chmod after the server is finished
|
||||
// listening. Otherwise, the socket may not have been created yet.
|
||||
if (socket && mode) {
|
||||
await fs.chmod(socket, mode)
|
||||
if (isSocketOpts(opts)) {
|
||||
if (opts["socket-mode"]) {
|
||||
await fs.chmod(opts.socket, opts["socket-mode"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,14 +80,22 @@ export const createApp = async (args: DefaultedArgs): Promise<App> => {
|
||||
)
|
||||
: http.createServer(router)
|
||||
|
||||
const dispose = disposer(server)
|
||||
const disposeServer = disposer(server)
|
||||
|
||||
await listen(server, args)
|
||||
|
||||
const wsRouter = express()
|
||||
handleUpgrade(wsRouter, server)
|
||||
|
||||
return { router, wsRouter, server, dispose }
|
||||
const editorSessionManager = new EditorSessionManager()
|
||||
const editorSessionManagerServer = await makeEditorSessionManagerServer(args["session-socket"], editorSessionManager)
|
||||
const disposeEditorSessionManagerServer = disposer(editorSessionManagerServer)
|
||||
|
||||
const dispose = async () => {
|
||||
await Promise.all([disposeServer(), disposeEditorSessionManagerServer()])
|
||||
}
|
||||
|
||||
return { router, wsRouter, server, dispose, editorSessionManagerServer }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
137
src/node/cli.ts
137
src/node/cli.ts
@@ -1,19 +1,9 @@
|
||||
import { field, Level, logger } from "@coder/logger"
|
||||
import { promises as fs } from "fs"
|
||||
import { load } from "js-yaml"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import {
|
||||
canConnect,
|
||||
generateCertificate,
|
||||
generatePassword,
|
||||
humanPath,
|
||||
paths,
|
||||
isNodeJSErrnoException,
|
||||
splitOnFirstEquals,
|
||||
} from "./util"
|
||||
|
||||
const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc")
|
||||
import { generateCertificate, generatePassword, paths, splitOnFirstEquals } from "./util"
|
||||
import { EditorSessionManagerClient } from "./vscodeSocket"
|
||||
|
||||
export enum Feature {
|
||||
// No current experimental features!
|
||||
@@ -60,6 +50,8 @@ export interface UserProvidedCodeArgs {
|
||||
"disable-file-downloads"?: boolean
|
||||
"disable-workspace-trust"?: boolean
|
||||
"disable-getting-started-override"?: boolean
|
||||
"disable-proxy"?: boolean
|
||||
"session-socket"?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,6 +79,7 @@ export interface UserProvidedArgs extends UserProvidedCodeArgs {
|
||||
"bind-addr"?: string
|
||||
socket?: string
|
||||
"socket-mode"?: string
|
||||
"trusted-origins"?: string[]
|
||||
version?: boolean
|
||||
"proxy-domain"?: string[]
|
||||
"reuse-window"?: boolean
|
||||
@@ -169,6 +162,9 @@ export const options: Options<Required<UserProvidedArgs>> = {
|
||||
"Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" +
|
||||
"then notifies you once every week that a new release is available.",
|
||||
},
|
||||
"session-socket": {
|
||||
type: "string",
|
||||
},
|
||||
"disable-file-downloads": {
|
||||
type: "boolean",
|
||||
description:
|
||||
@@ -182,6 +178,10 @@ export const options: Options<Required<UserProvidedArgs>> = {
|
||||
type: "boolean",
|
||||
description: "Disable the coder/coder override in the Help: Getting Started page.",
|
||||
},
|
||||
"disable-proxy": {
|
||||
type: "boolean",
|
||||
description: "Disable domain and path proxy routes.",
|
||||
},
|
||||
// --enable can be used to enable experimental features. These features
|
||||
// provide no guarantees.
|
||||
enable: { type: "string[]" },
|
||||
@@ -213,6 +213,11 @@ export const options: Options<Required<UserProvidedArgs>> = {
|
||||
|
||||
socket: { type: "string", path: true, description: "Path to a socket (bind-addr will be ignored)." },
|
||||
"socket-mode": { type: "string", description: "File mode of the socket." },
|
||||
"trusted-origins": {
|
||||
type: "string[]",
|
||||
description:
|
||||
"Disables authenticate origin check for trusted origin. Useful if not able to access reverse proxy configuration.",
|
||||
},
|
||||
version: { type: "boolean", short: "v", description: "Display version information." },
|
||||
_: { type: "string[]" },
|
||||
|
||||
@@ -433,19 +438,23 @@ export const parse = (
|
||||
throw new Error("--cert-key is missing")
|
||||
}
|
||||
|
||||
logger.debug(() => [
|
||||
`parsed ${opts?.configFile ? "config" : "command line"}`,
|
||||
field("args", {
|
||||
...args,
|
||||
password: args.password ? "<redacted>" : undefined,
|
||||
"hashed-password": args["hashed-password"] ? "<redacted>" : undefined,
|
||||
"github-auth": args["github-auth"] ? "<redacted>" : undefined,
|
||||
}),
|
||||
])
|
||||
logger.debug(() => [`parsed ${opts?.configFile ? "config" : "command line"}`, field("args", redactArgs(args))])
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
/**
|
||||
* Redact sensitive information from arguments for logging.
|
||||
*/
|
||||
export const redactArgs = (args: UserProvidedArgs): UserProvidedArgs => {
|
||||
return {
|
||||
...args,
|
||||
password: args.password ? "<redacted>" : undefined,
|
||||
"hashed-password": args["hashed-password"] ? "<redacted>" : undefined,
|
||||
"github-auth": args["github-auth"] ? "<redacted>" : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User-provided arguments with defaults. The distinction between user-provided
|
||||
* args and defaulted args exists so we can tell the difference between end
|
||||
@@ -464,6 +473,7 @@ export interface DefaultedArgs extends ConfigArgs {
|
||||
usingEnvHashedPassword: boolean
|
||||
"extensions-dir": string
|
||||
"user-data-dir": string
|
||||
"session-socket": string
|
||||
/* Positional arguments. */
|
||||
_: string[]
|
||||
}
|
||||
@@ -484,6 +494,11 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
||||
args["extensions-dir"] = path.join(args["user-data-dir"], "extensions")
|
||||
}
|
||||
|
||||
if (!args["session-socket"]) {
|
||||
args["session-socket"] = path.join(args["user-data-dir"], "code-server-ipc.sock")
|
||||
}
|
||||
process.env.CODE_SERVER_SESSION_SOCKET = args["session-socket"]
|
||||
|
||||
// --verbose takes priority over --log and --log takes priority over the
|
||||
// environment variable.
|
||||
if (args.verbose) {
|
||||
@@ -553,6 +568,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
||||
args["disable-getting-started-override"] = true
|
||||
}
|
||||
|
||||
if (process.env.CS_DISABLE_PROXY?.match(/^(1|true)$/)) {
|
||||
args["disable-proxy"] = true
|
||||
}
|
||||
|
||||
const usingEnvHashedPassword = !!process.env.HASHED_PASSWORD
|
||||
if (process.env.HASHED_PASSWORD) {
|
||||
args["hashed-password"] = process.env.HASHED_PASSWORD
|
||||
@@ -570,14 +589,24 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
||||
|
||||
// Filter duplicate proxy domains and remove any leading `*.`.
|
||||
const proxyDomains = new Set((args["proxy-domain"] || []).map((d) => d.replace(/^\*\./, "")))
|
||||
args["proxy-domain"] = Array.from(proxyDomains)
|
||||
if (args["proxy-domain"].length > 0 && !process.env.VSCODE_PROXY_URI) {
|
||||
process.env.VSCODE_PROXY_URI = `{{port}}.${args["proxy-domain"][0]}`
|
||||
const finalProxies = []
|
||||
|
||||
for (const proxyDomain of proxyDomains) {
|
||||
if (!proxyDomain.includes("{{port}}")) {
|
||||
finalProxies.push("{{port}}." + proxyDomain)
|
||||
} else {
|
||||
finalProxies.push(proxyDomain)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof args._ === "undefined") {
|
||||
args._ = []
|
||||
// all proxies are of format anyprefix-{{port}}-anysuffix.{{host}}, where {{host}} is optional
|
||||
// e.g. code-8080.domain.tld would match for code-{{port}}.domain.tld and code-{{port}}.{{host}}
|
||||
if (finalProxies.length > 0 && !process.env.VSCODE_PROXY_URI) {
|
||||
process.env.VSCODE_PROXY_URI = `//${finalProxies[0]}`
|
||||
}
|
||||
args["proxy-domain"] = finalProxies
|
||||
|
||||
args._ = getResolvedPathsFromArgs(args)
|
||||
|
||||
return {
|
||||
...args,
|
||||
@@ -586,6 +615,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
||||
} as DefaultedArgs // TODO: Technically no guarantee this is fulfilled.
|
||||
}
|
||||
|
||||
export function getResolvedPathsFromArgs(args: UserProvidedArgs): string[] {
|
||||
return (args._ ?? []).map((p) => path.resolve(p))
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return the default config file.
|
||||
*
|
||||
@@ -629,7 +662,7 @@ export async function readConfigFile(configPath?: string): Promise<ConfigArgs> {
|
||||
await fs.writeFile(configPath, defaultConfigFile(generatedPassword), {
|
||||
flag: "wx", // wx means to fail if the path exists.
|
||||
})
|
||||
logger.info(`Wrote default config file to ${humanPath(os.homedir(), configPath)}`)
|
||||
logger.info(`Wrote default config file to ${configPath}`)
|
||||
} catch (error: any) {
|
||||
// EEXIST is fine; we don't want to overwrite existing configurations.
|
||||
if (error.code !== "EEXIST") {
|
||||
@@ -699,6 +732,9 @@ export function bindAddrFromArgs(addr: Addr, args: UserProvidedArgs): Addr {
|
||||
if (args["bind-addr"]) {
|
||||
addr = parseBindAddr(args["bind-addr"])
|
||||
}
|
||||
if (process.env.CODE_SERVER_HOST) {
|
||||
addr.host = process.env.CODE_SERVER_HOST
|
||||
}
|
||||
if (args.host) {
|
||||
addr.host = args.host
|
||||
}
|
||||
@@ -725,47 +761,38 @@ function bindAddrFromAllSources(...argsConfig: UserProvidedArgs[]): Addr {
|
||||
return addr
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the socketPath based on path passed in.
|
||||
*
|
||||
* The one usually passed in is the DEFAULT_SOCKET_PATH.
|
||||
*
|
||||
* If it can't read the path, it throws an error and returns undefined.
|
||||
*/
|
||||
export async function readSocketPath(path: string): Promise<string | undefined> {
|
||||
try {
|
||||
return await fs.readFile(path, "utf8")
|
||||
} catch (error) {
|
||||
// If it doesn't exist, we don't care.
|
||||
// But if it fails for some reason, we should throw.
|
||||
// We want to surface that to the user.
|
||||
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if it looks like the user is trying to open a file or folder in an
|
||||
* existing instance. The arguments here should be the arguments the user
|
||||
* explicitly passed on the command line, *NOT DEFAULTS* or the configuration.
|
||||
*/
|
||||
export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Promise<string | undefined> => {
|
||||
export const shouldOpenInExistingInstance = async (
|
||||
args: UserProvidedArgs,
|
||||
sessionSocket: string,
|
||||
): Promise<string | undefined> => {
|
||||
// Always use the existing instance if we're running from VS Code's terminal.
|
||||
if (process.env.VSCODE_IPC_HOOK_CLI) {
|
||||
logger.debug("Found VSCODE_IPC_HOOK_CLI")
|
||||
return process.env.VSCODE_IPC_HOOK_CLI
|
||||
}
|
||||
|
||||
const paths = getResolvedPathsFromArgs(args)
|
||||
const client = new EditorSessionManagerClient(sessionSocket)
|
||||
|
||||
// If these flags are set then assume the user is trying to open in an
|
||||
// existing instance since these flags have no effect otherwise.
|
||||
// existing instance since these flags have no effect otherwise. That means
|
||||
// if there is no existing instance we should error rather than falling back
|
||||
// to spawning code-server normally.
|
||||
const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => {
|
||||
return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev
|
||||
}, 0)
|
||||
if (openInFlagCount > 0) {
|
||||
logger.debug("Found --reuse-window or --new-window")
|
||||
return readSocketPath(DEFAULT_SOCKET_PATH)
|
||||
const socketPath = await client.getConnectedSocketPath(paths[0])
|
||||
if (!socketPath) {
|
||||
throw new Error(`No opened code-server instances found to handle ${paths[0]}`)
|
||||
}
|
||||
return socketPath
|
||||
}
|
||||
|
||||
// It's possible the user is trying to spawn another instance of code-server.
|
||||
@@ -773,9 +800,13 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
|
||||
// code-server is invoked exactly like this: `code-server my-file`).
|
||||
// 2. That a file or directory was passed.
|
||||
// 3. That the socket is active.
|
||||
// 4. That an instance exists to handle the path (implied by #3).
|
||||
if (Object.keys(args).length === 1 && typeof args._ !== "undefined" && args._.length > 0) {
|
||||
const socketPath = await readSocketPath(DEFAULT_SOCKET_PATH)
|
||||
if (socketPath && (await canConnect(socketPath))) {
|
||||
if (!(await client.canConnect())) {
|
||||
return undefined
|
||||
}
|
||||
const socketPath = await client.getConnectedSocketPath(paths[0])
|
||||
if (socketPath) {
|
||||
logger.debug("Found existing code-server socket")
|
||||
return socketPath
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ async function entry(): Promise<void> {
|
||||
return runCodeCli(args)
|
||||
}
|
||||
|
||||
const socketPath = await shouldOpenInExistingInstance(cliArgs)
|
||||
const socketPath = await shouldOpenInExistingInstance(cliArgs, args["session-socket"])
|
||||
if (socketPath) {
|
||||
logger.debug("Trying to open in existing instance")
|
||||
return openInExistingInstance(args, socketPath)
|
||||
|
||||
@@ -9,7 +9,10 @@ export class Heart {
|
||||
private heartbeatInterval = 60000
|
||||
public lastHeartbeat = 0
|
||||
|
||||
public constructor(private readonly heartbeatPath: string, private readonly isActive: () => Promise<boolean>) {
|
||||
public constructor(
|
||||
private readonly heartbeatPath: string,
|
||||
private readonly isActive: () => Promise<boolean>,
|
||||
) {
|
||||
this.beat = this.beat.bind(this)
|
||||
this.alive = this.alive.bind(this)
|
||||
}
|
||||
|
||||
@@ -75,6 +75,25 @@ export const replaceTemplates = <T extends object>(
|
||||
.replace("{{OPTIONS}}", () => escapeJSON(serverOptions))
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an error if proxy is not enabled. Call `next` if provided.
|
||||
*/
|
||||
export const ensureProxyEnabled = (req: express.Request, _?: express.Response, next?: express.NextFunction): void => {
|
||||
if (!proxyEnabled(req)) {
|
||||
throw new HttpError("Forbidden", HttpCode.Forbidden)
|
||||
}
|
||||
if (next) {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if proxy is enabled.
|
||||
*/
|
||||
export const proxyEnabled = (req: express.Request): boolean => {
|
||||
return !req.args["disable-proxy"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an error if not authorized. Call `next` if provided.
|
||||
*/
|
||||
@@ -355,6 +374,11 @@ export function authenticateOrigin(req: express.Request): void {
|
||||
throw new Error(`unable to parse malformed origin "${originRaw}"`)
|
||||
}
|
||||
|
||||
const trustedOrigins = req.args["trusted-origins"] || []
|
||||
if (trustedOrigins.includes(origin) || trustedOrigins.includes("*")) {
|
||||
return
|
||||
}
|
||||
|
||||
const host = getHost(req)
|
||||
if (typeof host === "undefined") {
|
||||
// A missing host likely means the reverse proxy has not been configured to
|
||||
@@ -373,7 +397,7 @@ export function authenticateOrigin(req: express.Request): void {
|
||||
/**
|
||||
* Get the host from headers. It will be trimmed and lowercased.
|
||||
*/
|
||||
function getHost(req: express.Request): string | undefined {
|
||||
export function getHost(req: express.Request): string | undefined {
|
||||
// Honor Forwarded if present.
|
||||
const forwardedRaw = getFirstHeader(req, "forwarded")
|
||||
if (forwardedRaw) {
|
||||
@@ -386,10 +410,14 @@ function getHost(req: express.Request): string | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
// Honor X-Forwarded-Host if present.
|
||||
// Honor X-Forwarded-Host if present. Some reverse proxies will set multiple
|
||||
// comma-separated hosts.
|
||||
const xHost = getFirstHeader(req, "x-forwarded-host")
|
||||
if (xHost) {
|
||||
return xHost.trim().toLowerCase()
|
||||
const firstXHost = xHost.split(",")[0]
|
||||
if (firstXHost) {
|
||||
return firstXHost.trim().toLowerCase()
|
||||
}
|
||||
}
|
||||
|
||||
const host = getFirstHeader(req, "host")
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import i18next, { init } from "i18next"
|
||||
import * as en from "./locales/en.json"
|
||||
import * as zhCn from "./locales/zh-cn.json"
|
||||
import * as ja from "./locales/ja.json"
|
||||
import * as th from "./locales/th.json"
|
||||
import * as ur from "./locales/ur.json"
|
||||
import * as zhCn from "./locales/zh-cn.json"
|
||||
|
||||
init({
|
||||
lng: "en",
|
||||
fallbackLng: "en", // language to use if translations in user language are not available.
|
||||
@@ -18,6 +21,12 @@ init({
|
||||
th: {
|
||||
translation: th,
|
||||
},
|
||||
ja: {
|
||||
translation: ja,
|
||||
},
|
||||
ur: {
|
||||
translation: ur,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
13
src/node/i18n/locales/ja.json
Normal file
13
src/node/i18n/locales/ja.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"LOGIN_TITLE": "{{app}} ログイン",
|
||||
"LOGIN_BELOW": "以下によりログインしてください。",
|
||||
"WELCOME": "ようこそ {{app}} へ!",
|
||||
"LOGIN_PASSWORD": "パスワードは設定ファイル( {{configFile}} )を確認してください。",
|
||||
"LOGIN_USING_ENV_PASSWORD": "パスワードは環境変数 $PASSWORD で設定されています。",
|
||||
"LOGIN_USING_HASHED_PASSWORD": "パスワードは環境変数 $HASHED_PASSWORD で設定されています。",
|
||||
"SUBMIT": "実行",
|
||||
"PASSWORD_PLACEHOLDER": "パスワード",
|
||||
"LOGIN_RATE_LIMIT": "ログイン制限を超えました!",
|
||||
"MISS_PASSWORD": "パスワードを入力してください。",
|
||||
"INCORRECT_PASSWORD": "パスワードが間違っています。"
|
||||
}
|
||||
13
src/node/i18n/locales/ur.json
Normal file
13
src/node/i18n/locales/ur.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"LOGIN_TITLE": "{{app}} لاگ ان کریں",
|
||||
"LOGIN_BELOW": "براہ کرم نیچے لاگ ان کریں۔",
|
||||
"WELCOME": "میں خوش آمدید {{app}}",
|
||||
"LOGIN_PASSWORD": "پاس ورڈ کے لیے {{configFile}} پر کنفگ فائل چیک کریں۔",
|
||||
"LOGIN_USING_ENV_PASSWORD": "پاس ورڈ $PASSWORD سے سیٹ کیا گیا تھا۔",
|
||||
"LOGIN_USING_HASHED_PASSWORD": "پاس ورڈ $HASHED_PASSWORD سے سیٹ کیا گیا تھا۔",
|
||||
"SUBMIT": "جمع کرائیں",
|
||||
"PASSWORD_PLACEHOLDER": "پاس ورڈ",
|
||||
"LOGIN_RATE_LIMIT": "لاگ ان کی شرح محدود!",
|
||||
"MISS_PASSWORD": "پاس ورڈ غائب ہے۔",
|
||||
"INCORRECT_PASSWORD": "غلط پاس ورڈ"
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
import { field, logger } from "@coder/logger"
|
||||
import http from "http"
|
||||
import * as os from "os"
|
||||
import path from "path"
|
||||
import { Disposable } from "../common/emitter"
|
||||
import { plural } from "../common/util"
|
||||
import { createApp, ensureAddress } from "./app"
|
||||
import { AuthType, DefaultedArgs, Feature, SpawnCodeCli, toCodeArgs, UserProvidedArgs } from "./cli"
|
||||
import { commit, version } from "./constants"
|
||||
import { register } from "./routes"
|
||||
import { humanPath, isDirectory, loadAMDModule, open } from "./util"
|
||||
import { isDirectory, loadAMDModule, open } from "./util"
|
||||
|
||||
/**
|
||||
* Return true if the user passed an extension-related VS Code flag.
|
||||
@@ -70,9 +68,8 @@ export const openInExistingInstance = async (args: DefaultedArgs, socketPath: st
|
||||
forceNewWindow: args["new-window"],
|
||||
gotoLineMode: true,
|
||||
}
|
||||
const paths = args._ || []
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const fp = path.resolve(paths[i])
|
||||
for (let i = 0; i < args._.length; i++) {
|
||||
const fp = args._[i]
|
||||
if (await isDirectory(fp)) {
|
||||
pipeArgs.folderURIs.push(fp)
|
||||
} else {
|
||||
@@ -111,8 +108,8 @@ export const runCodeServer = async (
|
||||
): Promise<{ dispose: Disposable["dispose"]; server: http.Server }> => {
|
||||
logger.info(`code-server ${version} ${commit}`)
|
||||
|
||||
logger.info(`Using user-data-dir ${humanPath(os.homedir(), args["user-data-dir"])}`)
|
||||
logger.trace(`Using extensions-dir ${humanPath(os.homedir(), args["extensions-dir"])}`)
|
||||
logger.info(`Using user-data-dir ${args["user-data-dir"]}`)
|
||||
logger.trace(`Using extensions-dir ${args["extensions-dir"]}`)
|
||||
|
||||
if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) {
|
||||
throw new Error(
|
||||
@@ -125,9 +122,8 @@ export const runCodeServer = async (
|
||||
const serverAddress = ensureAddress(app.server, protocol)
|
||||
const disposeRoutes = await register(app, args)
|
||||
|
||||
logger.info(`Using config file ${humanPath(os.homedir(), args.config)}`)
|
||||
logger.info(`Using config file ${args.config}`)
|
||||
logger.info(`${protocol.toUpperCase()} server listening on ${serverAddress.toString()}`)
|
||||
|
||||
if (args.auth === AuthType.Password) {
|
||||
logger.info(" - Authentication is enabled")
|
||||
if (args.usingEnvPassword) {
|
||||
@@ -135,21 +131,31 @@ export const runCodeServer = async (
|
||||
} else if (args.usingEnvHashedPassword) {
|
||||
logger.info(" - Using password from $HASHED_PASSWORD")
|
||||
} else {
|
||||
logger.info(` - Using password from ${humanPath(os.homedir(), args.config)}`)
|
||||
logger.info(` - Using password from ${args.config}`)
|
||||
}
|
||||
} else {
|
||||
logger.info(" - Authentication is disabled")
|
||||
}
|
||||
|
||||
if (args.cert) {
|
||||
logger.info(` - Using certificate for HTTPS: ${humanPath(os.homedir(), args.cert.value)}`)
|
||||
logger.info(` - Using certificate for HTTPS: ${args.cert.value}`)
|
||||
} else {
|
||||
logger.info(" - Not serving HTTPS")
|
||||
}
|
||||
|
||||
if (args["proxy-domain"].length > 0) {
|
||||
if (args["disable-proxy"]) {
|
||||
logger.info(" - Proxy disabled")
|
||||
} else if (args["proxy-domain"].length > 0) {
|
||||
logger.info(` - ${plural(args["proxy-domain"].length, "Proxying the following domain")}:`)
|
||||
args["proxy-domain"].forEach((domain) => logger.info(` - *.${domain}`))
|
||||
args["proxy-domain"].forEach((domain) => logger.info(` - ${domain}`))
|
||||
}
|
||||
if (process.env.VSCODE_PROXY_URI) {
|
||||
logger.info(`Using proxy URI in PORTS tab: ${process.env.VSCODE_PROXY_URI}`)
|
||||
}
|
||||
|
||||
const sessionServerAddress = app.editorSessionManagerServer.address()
|
||||
if (sessionServerAddress) {
|
||||
logger.info(`Session server listening on ${sessionServerAddress.toString()}`)
|
||||
}
|
||||
|
||||
if (args.enable && args.enable.length > 0) {
|
||||
|
||||
@@ -1,34 +1,56 @@
|
||||
import { Request, Router } from "express"
|
||||
import { HttpCode, HttpError } from "../../common/http"
|
||||
import { authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
|
||||
import { getHost, ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
|
||||
import { proxy } from "../proxy"
|
||||
import { Router as WsRouter } from "../wsRouter"
|
||||
|
||||
export const router = Router()
|
||||
|
||||
const proxyDomainToRegex = (matchString: string): RegExp => {
|
||||
const escapedMatchString = matchString.replace(/[.*+?^$()|[\]\\]/g, "\\$&")
|
||||
|
||||
// Replace {{port}} with a regex group to capture the port
|
||||
// Replace {{host}} with .+ to allow any host match (so rely on DNS record here)
|
||||
let regexString = escapedMatchString.replace("{{port}}", "(\\d+)")
|
||||
regexString = regexString.replace("{{host}}", ".+")
|
||||
|
||||
regexString = regexString.replace(/[{}]/g, "\\$&") //replace any '{}' that might be left
|
||||
|
||||
return new RegExp("^" + regexString + "$")
|
||||
}
|
||||
|
||||
let proxyRegexes: RegExp[] = []
|
||||
const proxyDomainsToRegex = (proxyDomains: string[]): RegExp[] => {
|
||||
if (proxyDomains.length !== proxyRegexes.length) {
|
||||
proxyRegexes = proxyDomains.map(proxyDomainToRegex)
|
||||
}
|
||||
return proxyRegexes
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the port if the request should be proxied. Anything that ends in a
|
||||
* proxy domain and has a *single* subdomain should be proxied. Anything else
|
||||
* should return `undefined` and will be handled as normal.
|
||||
* Return the port if the request should be proxied.
|
||||
*
|
||||
* The proxy-domain should be of format anyprefix-{{port}}-anysuffix.{{host}}, where {{host}} is optional
|
||||
* e.g. code-8080.domain.tld would match for code-{{port}}.domain.tld and code-{{port}}.{{host}}.
|
||||
*
|
||||
* For example if `coder.com` is specified `8080.coder.com` will be proxied
|
||||
* but `8080.test.coder.com` and `test.8080.coder.com` will not.
|
||||
*/
|
||||
const maybeProxy = (req: Request): string | undefined => {
|
||||
// Split into parts.
|
||||
const host = req.headers.host || ""
|
||||
const idx = host.indexOf(":")
|
||||
const domain = idx !== -1 ? host.substring(0, idx) : host
|
||||
const parts = domain.split(".")
|
||||
|
||||
// There must be an exact match.
|
||||
const port = parts.shift()
|
||||
const proxyDomain = parts.join(".")
|
||||
if (!port || !req.args["proxy-domain"].includes(proxyDomain)) {
|
||||
const reqDomain = getHost(req)
|
||||
if (reqDomain === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return port
|
||||
const regexs = proxyDomainsToRegex(req.args["proxy-domain"])
|
||||
|
||||
for (const regex of regexs) {
|
||||
const match = reqDomain.match(regex)
|
||||
|
||||
if (match) {
|
||||
return match[1] // match[1] contains the port
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
router.all("*", async (req, res, next) => {
|
||||
@@ -37,6 +59,8 @@ router.all("*", async (req, res, next) => {
|
||||
return next()
|
||||
}
|
||||
|
||||
ensureProxyEnabled(req)
|
||||
|
||||
// Must be authenticated to use the proxy.
|
||||
const isAuthenticated = await authenticated(req)
|
||||
if (!isAuthenticated) {
|
||||
@@ -78,6 +102,8 @@ wsRouter.ws("*", async (req, _, next) => {
|
||||
if (!port) {
|
||||
return next()
|
||||
}
|
||||
|
||||
ensureProxyEnabled(req)
|
||||
ensureOrigin(req)
|
||||
await ensureAuthenticated(req)
|
||||
proxy.ws(req, req.ws, req.head, {
|
||||
|
||||
@@ -62,12 +62,16 @@ export const errorHandler: express.ErrorRequestHandler = async (err, req, res, n
|
||||
}
|
||||
|
||||
export const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
|
||||
logger.error(`${err.message} ${err.stack}`)
|
||||
let statusCode = 500
|
||||
if (errorHasStatusCode(err)) {
|
||||
statusCode = err.statusCode
|
||||
} else if (errorHasCode(err) && notFoundCodes.includes(err.code)) {
|
||||
statusCode = HttpCode.NotFound
|
||||
}
|
||||
if (statusCode >= 500) {
|
||||
logger.error(`${err.message} ${err.stack}`)
|
||||
} else {
|
||||
logger.debug(`${err.message} ${err.stack}`)
|
||||
}
|
||||
;(req as WebsocketRequest).ws.end(`HTTP/1.1 ${statusCode} ${err.message}\r\n\r\n`)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Router, Request } from "express"
|
||||
import { promises as fs } from "fs"
|
||||
import { RateLimiter as Limiter } from "limiter"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import { CookieKeys } from "../../common/http"
|
||||
import { rootPath } from "../constants"
|
||||
import { authenticated, getCookieOptions, redirect, replaceTemplates } from "../http"
|
||||
import { getPasswordMethod, handlePasswordValidation, humanPath, sanitizeString, escapeHtml } from "../util"
|
||||
import i18n from "../i18n"
|
||||
import { getPasswordMethod, handlePasswordValidation, sanitizeString, escapeHtml } from "../util"
|
||||
|
||||
// RateLimiter wraps around the limiter library for logins.
|
||||
// It allows 2 logins every minute plus 12 logins every hour.
|
||||
@@ -33,7 +32,7 @@ const getRoot = async (req: Request, error?: Error): Promise<string> => {
|
||||
i18n.changeLanguage(locale)
|
||||
const appName = req.args["app-name"] || "code-server"
|
||||
const welcomeText = req.args["welcome-text"] || (i18n.t("WELCOME", { app: appName }) as string)
|
||||
let passwordMsg = i18n.t("LOGIN_PASSWORD", { configFile: humanPath(os.homedir(), req.args.config) })
|
||||
let passwordMsg = i18n.t("LOGIN_PASSWORD", { configFile: req.args.config })
|
||||
if (req.args.usingEnvPassword) {
|
||||
passwordMsg = i18n.t("LOGIN_USING_ENV_PASSWORD")
|
||||
} else if (req.args.usingEnvHashedPassword) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as path from "path"
|
||||
import * as qs from "qs"
|
||||
import * as pluginapi from "../../../typings/pluginapi"
|
||||
import { HttpCode, HttpError } from "../../common/http"
|
||||
import { authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
|
||||
import { ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
|
||||
import { proxy as _proxy } from "../proxy"
|
||||
|
||||
const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
|
||||
@@ -11,7 +11,7 @@ const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
|
||||
return `http://0.0.0.0:${req.params.port}/${req.originalUrl}`
|
||||
}
|
||||
const query = qs.stringify(req.query)
|
||||
return `http://0.0.0.0:${req.params.port}/${req.params[0] || ""}${query ? `?${query}` : ""}`
|
||||
return encodeURI(`http://0.0.0.0:${req.params.port}${req.params[0] || ""}${query ? `?${query}` : ""}`)
|
||||
}
|
||||
|
||||
export async function proxy(
|
||||
@@ -21,6 +21,8 @@ export async function proxy(
|
||||
passthroughPath?: boolean
|
||||
},
|
||||
): Promise<void> {
|
||||
ensureProxyEnabled(req)
|
||||
|
||||
if (!(await authenticated(req))) {
|
||||
// If visiting the root (/:port only) redirect to the login page.
|
||||
if (!req.params[0] || req.params[0] === "/") {
|
||||
@@ -50,6 +52,7 @@ export async function wsProxy(
|
||||
passthroughPath?: boolean
|
||||
},
|
||||
): Promise<void> {
|
||||
ensureProxyEnabled(req)
|
||||
ensureOrigin(req)
|
||||
await ensureAuthenticated(req)
|
||||
_proxy.ws(req, req.ws, req.head, {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { logger } from "@coder/logger"
|
||||
import * as crypto from "crypto"
|
||||
import * as express from "express"
|
||||
import { promises as fs } from "fs"
|
||||
import * as http from "http"
|
||||
import * as net from "net"
|
||||
import * as path from "path"
|
||||
@@ -32,6 +34,7 @@ export class CodeServerRouteWrapper {
|
||||
private _wsRouterWrapper = WsRouter()
|
||||
private _socketProxyProvider = new SocketProxyProvider()
|
||||
public router = express.Router()
|
||||
private mintKeyPromise: Promise<Buffer> | undefined
|
||||
|
||||
public get wsRouter() {
|
||||
return this._wsRouterWrapper.router
|
||||
@@ -66,6 +69,33 @@ export class CodeServerRouteWrapper {
|
||||
)
|
||||
}
|
||||
|
||||
private mintKey: express.Handler = async (req, res, next) => {
|
||||
if (!this.mintKeyPromise) {
|
||||
this.mintKeyPromise = new Promise(async (resolve) => {
|
||||
const keyPath = path.join(req.args["user-data-dir"], "serve-web-key-half")
|
||||
logger.debug(`Reading server web key half from ${keyPath}`)
|
||||
try {
|
||||
resolve(await fs.readFile(keyPath))
|
||||
return
|
||||
} catch (error: any) {
|
||||
if (error.code !== "ENOENT") {
|
||||
logError(logger, `read ${keyPath}`, error)
|
||||
}
|
||||
}
|
||||
// VS Code wants 256 bits.
|
||||
const key = crypto.randomBytes(32)
|
||||
try {
|
||||
await fs.writeFile(keyPath, key)
|
||||
} catch (error: any) {
|
||||
logError(logger, `write ${keyPath}`, error)
|
||||
}
|
||||
resolve(key)
|
||||
})
|
||||
}
|
||||
const key = await this.mintKeyPromise
|
||||
res.end(key)
|
||||
}
|
||||
|
||||
private $root: express.Handler = async (req, res, next) => {
|
||||
const isAuthenticated = await authenticated(req)
|
||||
const NO_FOLDER_OR_WORKSPACE_QUERY = !req.query.folder && !req.query.workspace
|
||||
@@ -173,6 +203,7 @@ export class CodeServerRouteWrapper {
|
||||
constructor() {
|
||||
this.router.get("/", this.ensureCodeServerLoaded, this.$root)
|
||||
this.router.get("/manifest.json", this.manifest)
|
||||
this.router.post("/mint-key", this.mintKey)
|
||||
this.router.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest)
|
||||
this._wsRouterWrapper.ws("*", ensureOrigin, ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { field, logger } from "@coder/logger"
|
||||
import * as http from "http"
|
||||
import * as https from "https"
|
||||
import ProxyAgent from "proxy-agent"
|
||||
import { ProxyAgent } from "proxy-agent"
|
||||
import * as semver from "semver"
|
||||
import * as url from "url"
|
||||
import { httpProxyUri, version } from "./constants"
|
||||
@@ -104,7 +104,9 @@ export class UpdateProvider {
|
||||
const request = (uri: string): void => {
|
||||
logger.debug("Making request", field("uri", uri))
|
||||
const isHttps = uri.startsWith("https")
|
||||
const agent = httpProxyUri ? new ProxyAgent(httpProxyUri) : undefined
|
||||
const agent = new ProxyAgent({
|
||||
getProxyForUrl: () => httpProxyUri || "",
|
||||
})
|
||||
const httpx = isHttps ? https : http
|
||||
const client = httpx.get(uri, { headers: { "User-Agent": "code-server" }, agent }, (response) => {
|
||||
if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 400) {
|
||||
|
||||
@@ -87,20 +87,6 @@ export function getEnvPaths(platform = process.platform): Paths {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* humanPath replaces the home directory in path with ~.
|
||||
* Makes it more readable.
|
||||
*
|
||||
* @param homedir - the home directory(i.e. `os.homedir()`)
|
||||
* @param path - a file path
|
||||
*/
|
||||
export function humanPath(homedir: string, path?: string): string {
|
||||
if (!path) {
|
||||
return ""
|
||||
}
|
||||
return path.replace(homedir, "~")
|
||||
}
|
||||
|
||||
export const generateCertificate = async (hostname: string): Promise<{ cert: string; certKey: string }> => {
|
||||
const certPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.crt`)
|
||||
const certKeyPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.key`)
|
||||
|
||||
206
src/node/vscodeSocket.ts
Normal file
206
src/node/vscodeSocket.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import { logger } from "@coder/logger"
|
||||
import express from "express"
|
||||
import * as http from "http"
|
||||
import * as path from "path"
|
||||
import { HttpCode } from "../common/http"
|
||||
import { listen } from "./app"
|
||||
import { canConnect } from "./util"
|
||||
|
||||
export interface EditorSessionEntry {
|
||||
workspace: {
|
||||
id: string
|
||||
folders: {
|
||||
uri: {
|
||||
path: string
|
||||
}
|
||||
}[]
|
||||
}
|
||||
|
||||
socketPath: string
|
||||
}
|
||||
|
||||
interface DeleteSessionRequest {
|
||||
socketPath: string
|
||||
}
|
||||
|
||||
interface AddSessionRequest {
|
||||
entry: EditorSessionEntry
|
||||
}
|
||||
|
||||
interface GetSessionResponse {
|
||||
socketPath?: string
|
||||
}
|
||||
|
||||
export async function makeEditorSessionManagerServer(
|
||||
codeServerSocketPath: string,
|
||||
editorSessionManager: EditorSessionManager,
|
||||
): Promise<http.Server> {
|
||||
const router = express()
|
||||
|
||||
// eslint-disable-next-line import/no-named-as-default-member
|
||||
router.use(express.json())
|
||||
|
||||
router.get("/session", async (req, res) => {
|
||||
const filePath = req.query.filePath as string
|
||||
if (!filePath) {
|
||||
res.status(HttpCode.BadRequest).send("filePath is required")
|
||||
return
|
||||
}
|
||||
try {
|
||||
const socketPath = await editorSessionManager.getConnectedSocketPath(filePath)
|
||||
const response: GetSessionResponse = { socketPath }
|
||||
res.json(response)
|
||||
} catch (error: unknown) {
|
||||
res.status(HttpCode.ServerError).send(error)
|
||||
}
|
||||
})
|
||||
|
||||
router.post("/add-session", async (req, res) => {
|
||||
const request = req.body as AddSessionRequest
|
||||
if (!request.entry) {
|
||||
res.status(400).send("entry is required")
|
||||
}
|
||||
editorSessionManager.addSession(request.entry)
|
||||
res.status(200).send()
|
||||
})
|
||||
|
||||
router.post("/delete-session", async (req, res) => {
|
||||
const request = req.body as DeleteSessionRequest
|
||||
if (!request.socketPath) {
|
||||
res.status(400).send("socketPath is required")
|
||||
}
|
||||
editorSessionManager.deleteSession(request.socketPath)
|
||||
res.status(200).send()
|
||||
})
|
||||
|
||||
const server = http.createServer(router)
|
||||
try {
|
||||
await listen(server, { socket: codeServerSocketPath })
|
||||
} catch (e) {
|
||||
logger.warn(`Could not create socket at ${codeServerSocketPath}`)
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
export class EditorSessionManager {
|
||||
// Map from socket path to EditorSessionEntry.
|
||||
private entries = new Map<string, EditorSessionEntry>()
|
||||
|
||||
addSession(entry: EditorSessionEntry): void {
|
||||
logger.debug(`Adding session to session registry: ${entry.socketPath}`)
|
||||
this.entries.set(entry.socketPath, entry)
|
||||
}
|
||||
|
||||
getCandidatesForFile(filePath: string): EditorSessionEntry[] {
|
||||
const matchCheckResults = new Map<string, boolean>()
|
||||
|
||||
const checkMatch = (entry: EditorSessionEntry): boolean => {
|
||||
if (matchCheckResults.has(entry.socketPath)) {
|
||||
return matchCheckResults.get(entry.socketPath)!
|
||||
}
|
||||
const result = entry.workspace.folders.some((folder) => filePath.startsWith(folder.uri.path + path.sep))
|
||||
matchCheckResults.set(entry.socketPath, result)
|
||||
return result
|
||||
}
|
||||
|
||||
return Array.from(this.entries.values())
|
||||
.reverse() // Most recently registered first.
|
||||
.sort((a, b) => {
|
||||
// Matches first.
|
||||
const aMatch = checkMatch(a)
|
||||
const bMatch = checkMatch(b)
|
||||
if (aMatch === bMatch) {
|
||||
return 0
|
||||
}
|
||||
if (aMatch) {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
})
|
||||
}
|
||||
|
||||
deleteSession(socketPath: string): void {
|
||||
logger.debug(`Deleting session from session registry: ${socketPath}`)
|
||||
this.entries.delete(socketPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best socket path that we can connect to.
|
||||
* We also delete any sockets that we can't connect to.
|
||||
*/
|
||||
async getConnectedSocketPath(filePath: string): Promise<string | undefined> {
|
||||
const candidates = this.getCandidatesForFile(filePath)
|
||||
let match: EditorSessionEntry | undefined = undefined
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (await canConnect(candidate.socketPath)) {
|
||||
match = candidate
|
||||
break
|
||||
}
|
||||
this.deleteSession(candidate.socketPath)
|
||||
}
|
||||
|
||||
return match?.socketPath
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorSessionManagerClient {
|
||||
constructor(private codeServerSocketPath: string) {}
|
||||
|
||||
async canConnect() {
|
||||
return canConnect(this.codeServerSocketPath)
|
||||
}
|
||||
|
||||
async getConnectedSocketPath(filePath: string): Promise<string | undefined> {
|
||||
const response = await new Promise<GetSessionResponse>((resolve, reject) => {
|
||||
const opts = {
|
||||
path: "/session?filePath=" + encodeURIComponent(filePath),
|
||||
socketPath: this.codeServerSocketPath,
|
||||
method: "GET",
|
||||
}
|
||||
const req = http.request(opts, (res) => {
|
||||
let rawData = ""
|
||||
res.setEncoding("utf8")
|
||||
res.on("data", (chunk) => {
|
||||
rawData += chunk
|
||||
})
|
||||
res.on("end", () => {
|
||||
try {
|
||||
const obj = JSON.parse(rawData)
|
||||
if (res.statusCode === 200) {
|
||||
resolve(obj)
|
||||
} else {
|
||||
reject(new Error("Unexpected status code: " + res.statusCode))
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
})
|
||||
req.on("error", reject)
|
||||
req.end()
|
||||
})
|
||||
return response.socketPath
|
||||
}
|
||||
|
||||
// Currently only used for tests.
|
||||
async addSession(request: AddSessionRequest): Promise<void> {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const opts = {
|
||||
path: "/add-session",
|
||||
socketPath: this.codeServerSocketPath,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
accept: "application/json",
|
||||
},
|
||||
}
|
||||
const req = http.request(opts, () => {
|
||||
resolve()
|
||||
})
|
||||
req.on("error", reject)
|
||||
req.write(JSON.stringify(request))
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import * as cp from "child_process"
|
||||
import * as path from "path"
|
||||
import * as rfs from "rotating-file-stream"
|
||||
import { Emitter } from "../common/emitter"
|
||||
import { DefaultedArgs } from "./cli"
|
||||
import { DefaultedArgs, redactArgs } from "./cli"
|
||||
import { paths } from "./util"
|
||||
|
||||
const timeoutInterval = 10000 // 10s, matches VS Code's timeouts.
|
||||
@@ -44,10 +44,11 @@ export function onMessage<M, T extends M>(
|
||||
}
|
||||
|
||||
const onMessage = (message: M) => {
|
||||
;(customLogger || logger).trace("got message", field("message", message))
|
||||
if (fn(message)) {
|
||||
cleanup()
|
||||
resolve(message)
|
||||
} else {
|
||||
;(customLogger || logger).debug("got unhandled message", field("message", message))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +78,10 @@ type ChildMessage = RelaunchMessage | ChildHandshakeMessage
|
||||
type ParentMessage = ParentHandshakeMessage
|
||||
|
||||
class ProcessError extends Error {
|
||||
public constructor(message: string, public readonly code: number | undefined) {
|
||||
public constructor(
|
||||
message: string,
|
||||
public readonly code: number | undefined,
|
||||
) {
|
||||
super(message)
|
||||
this.name = this.constructor.name
|
||||
Error.captureStackTrace(this, this.constructor)
|
||||
@@ -172,6 +176,7 @@ export class ChildProcess extends Process {
|
||||
* Initiate the handshake and wait for a response from the parent.
|
||||
*/
|
||||
public async handshake(): Promise<DefaultedArgs> {
|
||||
this.logger.debug("initiating handshake")
|
||||
this.send({ type: "handshake" })
|
||||
const message = await onMessage<ParentMessage, ParentHandshakeMessage>(
|
||||
process,
|
||||
@@ -180,6 +185,13 @@ export class ChildProcess extends Process {
|
||||
},
|
||||
this.logger,
|
||||
)
|
||||
this.logger.debug(
|
||||
"got message",
|
||||
field("message", {
|
||||
type: message.type,
|
||||
args: redactArgs(message.args),
|
||||
}),
|
||||
)
|
||||
return message.args
|
||||
}
|
||||
|
||||
@@ -280,6 +292,10 @@ export class ParentProcess extends Process {
|
||||
}
|
||||
|
||||
public start(args: DefaultedArgs): Promise<void> {
|
||||
// Our logger was created before we parsed CLI arguments so update the level
|
||||
// in case it has changed.
|
||||
this.logger.level = logger.level
|
||||
|
||||
// Store for relaunches.
|
||||
this.args = args
|
||||
if (!this.started) {
|
||||
@@ -306,7 +322,7 @@ export class ParentProcess extends Process {
|
||||
})
|
||||
}
|
||||
|
||||
this.logger.debug(`spawned inner process ${child.pid}`)
|
||||
this.logger.debug(`spawned child process ${child.pid}`)
|
||||
|
||||
await this.handshake(child)
|
||||
|
||||
@@ -334,13 +350,14 @@ export class ParentProcess extends Process {
|
||||
if (!this.args) {
|
||||
throw new Error("started without args")
|
||||
}
|
||||
await onMessage<ChildMessage, ChildHandshakeMessage>(
|
||||
const message = await onMessage<ChildMessage, ChildHandshakeMessage>(
|
||||
child,
|
||||
(message): message is ChildHandshakeMessage => {
|
||||
return message.type === "handshake"
|
||||
},
|
||||
this.logger,
|
||||
)
|
||||
this.logger.debug("got message", field("message", message))
|
||||
this.send(child, { type: "handshake", args: this.args })
|
||||
}
|
||||
|
||||
|
||||
@@ -117,40 +117,26 @@ export class CodeServer {
|
||||
* directories.
|
||||
*/
|
||||
private async spawn(): Promise<CodeServerProcess> {
|
||||
// This will be used both as the workspace and data directory to ensure
|
||||
// instances don't bleed into each other.
|
||||
const dir = await this.createWorkspace()
|
||||
|
||||
const args = await this.argsWithDefaults([
|
||||
"--auth",
|
||||
"none",
|
||||
// The workspace to open.
|
||||
...(this.args.includes("--ignore-last-opened") ? [] : [dir]),
|
||||
...this.args,
|
||||
// Using port zero will spawn on a random port.
|
||||
"--bind-addr",
|
||||
"127.0.0.1:0",
|
||||
])
|
||||
return new Promise((resolve, reject) => {
|
||||
const args = [
|
||||
this.entry,
|
||||
"--extensions-dir",
|
||||
path.join(dir, "extensions"),
|
||||
"--auth",
|
||||
"none",
|
||||
// The workspace to open.
|
||||
...(this.args.includes("--ignore-last-opened") ? [] : [dir]),
|
||||
...this.args,
|
||||
// Using port zero will spawn on a random port.
|
||||
"--bind-addr",
|
||||
"127.0.0.1:0",
|
||||
// Setting the XDG variables would be easier and more thorough but the
|
||||
// modules we import ignores those variables for non-Linux operating
|
||||
// systems so use these flags instead.
|
||||
"--config",
|
||||
path.join(dir, "config.yaml"),
|
||||
"--user-data-dir",
|
||||
dir,
|
||||
]
|
||||
this.logger.debug("spawning `node " + args.join(" ") + "`")
|
||||
const proc = cp.spawn("node", args, {
|
||||
cwd: path.join(__dirname, "../../.."),
|
||||
env: {
|
||||
...process.env,
|
||||
...this.env,
|
||||
// Set to empty string to prevent code-server from
|
||||
// using the existing instance when running the e2e tests
|
||||
// from an integrated terminal.
|
||||
// Prevent code-server from using the existing instance when running
|
||||
// the e2e tests from an integrated terminal.
|
||||
VSCODE_IPC_HOOK_CLI: "",
|
||||
PASSWORD,
|
||||
},
|
||||
@@ -173,11 +159,15 @@ export class CodeServer {
|
||||
reject(error)
|
||||
})
|
||||
|
||||
// Tracks when the HTTP and session servers are ready.
|
||||
let httpAddress: string | undefined
|
||||
let sessionAddress: string | undefined
|
||||
|
||||
let resolved = false
|
||||
proc.stdout.setEncoding("utf8")
|
||||
onLine(proc, (line) => {
|
||||
// As long as we are actively getting input reset the timer. If we stop
|
||||
// getting input and still have not found the address the timer will
|
||||
// getting input and still have not found the addresses the timer will
|
||||
// reject.
|
||||
timer.reset()
|
||||
|
||||
@@ -186,20 +176,69 @@ export class CodeServer {
|
||||
if (resolved) {
|
||||
return
|
||||
}
|
||||
const match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
|
||||
|
||||
let match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
|
||||
if (match) {
|
||||
// Cookies don't seem to work on IP address so swap to localhost.
|
||||
// Cookies don't seem to work on IP addresses so swap to localhost.
|
||||
// TODO: Investigate whether this is a bug with code-server.
|
||||
const address = match[1].replace("127.0.0.1", "localhost")
|
||||
this.logger.debug(`spawned on ${address}`)
|
||||
httpAddress = match[1].replace("127.0.0.1", "localhost")
|
||||
}
|
||||
|
||||
match = line.trim().match(/Session server listening on (.+)$/)
|
||||
if (match) {
|
||||
sessionAddress = match[1]
|
||||
}
|
||||
|
||||
if (typeof httpAddress !== "undefined" && typeof sessionAddress !== "undefined") {
|
||||
resolved = true
|
||||
timer.dispose()
|
||||
resolve({ process: proc, address })
|
||||
this.logger.debug(`code-server is ready: ${httpAddress} ${sessionAddress}`)
|
||||
resolve({ process: proc, address: httpAddress })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a short-lived command.
|
||||
*/
|
||||
async run(args: string[]): Promise<void> {
|
||||
args = await this.argsWithDefaults(args)
|
||||
this.logger.debug("executing `node " + args.join(" ") + "`")
|
||||
await util.promisify(cp.exec)("node " + args.join(" "), {
|
||||
cwd: path.join(__dirname, "../../.."),
|
||||
env: {
|
||||
...process.env,
|
||||
...this.env,
|
||||
// Prevent code-server from using the existing instance when running
|
||||
// the e2e tests from an integrated terminal.
|
||||
VSCODE_IPC_HOOK_CLI: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine arguments with defaults.
|
||||
*/
|
||||
private async argsWithDefaults(args: string[]): Promise<string[]> {
|
||||
// This will be used both as the workspace and data directory to ensure
|
||||
// instances don't bleed into each other.
|
||||
const dir = await this.workspaceDir
|
||||
return [
|
||||
this.entry,
|
||||
"--extensions-dir",
|
||||
path.join(dir, "extensions"),
|
||||
...args,
|
||||
// Setting the XDG variables would be easier and more thorough but the
|
||||
// modules we import ignores those variables for non-Linux operating
|
||||
// systems so use these flags instead.
|
||||
"--config",
|
||||
path.join(dir, "config.yaml"),
|
||||
"--user-data-dir",
|
||||
dir,
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the code-server process.
|
||||
*/
|
||||
@@ -230,7 +269,10 @@ export class CodeServer {
|
||||
export class CodeServerPage {
|
||||
private readonly editorSelector = "div.monaco-workbench"
|
||||
|
||||
constructor(private readonly codeServer: CodeServer, public readonly page: Page) {
|
||||
constructor(
|
||||
private readonly codeServer: CodeServer,
|
||||
public readonly page: Page,
|
||||
) {
|
||||
this.page.on("console", (message) => {
|
||||
this.codeServer.logger.debug(message.text())
|
||||
})
|
||||
@@ -364,6 +406,13 @@ export class CodeServerPage {
|
||||
await this.waitForTab(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a file through an external command.
|
||||
*/
|
||||
async openFileExternally(file: string) {
|
||||
await this.codeServer.run(["--reuse-window", file])
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a tab to open for the specified file.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, test, expect } from "./baseFixture"
|
||||
import { clean, getMaybeProxiedPathname } from "../utils/helpers"
|
||||
import { describe, test, expect } from "./baseFixture"
|
||||
|
||||
const routes = ["/", "/vscode", "/vscode/"]
|
||||
|
||||
@@ -103,7 +103,7 @@ describe("VS Code Routes with no workspace or folder", ["--disable-workspace-tru
|
||||
|
||||
// Closing the folder should stop the redirecting.
|
||||
await codeServerPage.navigate("/?ew=true")
|
||||
let url = new URL(codeServerPage.page.url())
|
||||
const url = new URL(codeServerPage.page.url())
|
||||
const pathname = getMaybeProxiedPathname(url)
|
||||
expect(pathname).toBe("/")
|
||||
expect(url.search).toBe("?ew=true")
|
||||
|
||||
@@ -35,13 +35,19 @@ describe("Integrated Terminal", ["--disable-workspace-trust"], {}, () => {
|
||||
const tmpFolderPath = await tmpdir(testName)
|
||||
const tmpFile = path.join(tmpFolderPath, "test-file")
|
||||
await fs.writeFile(tmpFile, "test")
|
||||
const fileName = path.basename(tmpFile)
|
||||
|
||||
await codeServerPage.focusTerminal()
|
||||
|
||||
await codeServerPage.page.keyboard.type(`code-server ${tmpFile}`)
|
||||
await codeServerPage.page.keyboard.press("Enter")
|
||||
|
||||
await codeServerPage.waitForTab(fileName)
|
||||
await codeServerPage.waitForTab(path.basename(tmpFile))
|
||||
|
||||
const externalTmpFile = path.join(tmpFolderPath, "test-external-file")
|
||||
await fs.writeFile(externalTmpFile, "foobar")
|
||||
|
||||
await codeServerPage.openFileExternally(externalTmpFile)
|
||||
|
||||
await codeServerPage.waitForTab(path.basename(externalTmpFile))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("--install-extension", () => {
|
||||
setupFlags = ["--extensions-dir", tempDir]
|
||||
})
|
||||
it("should use EXTENSIONS_GALLERY when set", async () => {
|
||||
const extName = `author.extension-1.0.0`
|
||||
const extName = "author.extension"
|
||||
const { stderr } = await runCodeServerCommand([...setupFlags, "--install-extension", extName], {
|
||||
EXTENSIONS_GALLERY: "{}",
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@ describe("createApp", () => {
|
||||
beforeAll(async () => {
|
||||
mockLogger()
|
||||
|
||||
const testName = "unlink-socket"
|
||||
const testName = "app"
|
||||
await clean(testName)
|
||||
tmpDirPath = await tmpdir(testName)
|
||||
tmpFilePath = path.join(tmpDirPath, "unlink-socket-file")
|
||||
@@ -92,7 +92,7 @@ describe("createApp", () => {
|
||||
app.dispose()
|
||||
}
|
||||
|
||||
expect(() => masterBall()).rejects.toThrow(`listen EACCES: permission denied 127.0.0.1:${port}`)
|
||||
expect(() => masterBall()).rejects.toThrow("listen EACCES: permission denied")
|
||||
})
|
||||
|
||||
it("should unlink a socket before listening on the socket", async () => {
|
||||
@@ -103,7 +103,7 @@ describe("createApp", () => {
|
||||
|
||||
const app = await createApp(defaultArgs)
|
||||
|
||||
expect(unlinkSpy).toHaveBeenCalledTimes(1)
|
||||
expect(unlinkSpy).toHaveBeenCalledWith(tmpFilePath)
|
||||
app.dispose()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { Level, logger } from "@coder/logger"
|
||||
import { promises as fs } from "fs"
|
||||
import * as net from "net"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import {
|
||||
UserProvidedArgs,
|
||||
bindAddrFromArgs,
|
||||
defaultConfigFile,
|
||||
parse,
|
||||
readSocketPath,
|
||||
setDefaults,
|
||||
shouldOpenInExistingInstance,
|
||||
toCodeArgs,
|
||||
@@ -20,7 +17,12 @@ import {
|
||||
} from "../../../src/node/cli"
|
||||
import { shouldSpawnCliProcess } from "../../../src/node/main"
|
||||
import { generatePassword, paths } from "../../../src/node/util"
|
||||
import { clean, useEnv, tmpdir } from "../../utils/helpers"
|
||||
import {
|
||||
EditorSessionManager,
|
||||
EditorSessionManagerClient,
|
||||
makeEditorSessionManagerServer,
|
||||
} from "../../../src/node/vscodeSocket"
|
||||
import { clean, useEnv, tmpdir, listenOn } from "../../utils/helpers"
|
||||
|
||||
// The parser should not set any defaults so the caller can determine what
|
||||
// values the user actually set. These are only set after explicitly calling
|
||||
@@ -34,6 +36,7 @@ const defaults = {
|
||||
usingEnvHashedPassword: false,
|
||||
"extensions-dir": path.join(paths.data, "extensions"),
|
||||
"user-data-dir": paths.data,
|
||||
"session-socket": path.join(paths.data, "code-server-ipc.sock"),
|
||||
_: [],
|
||||
}
|
||||
|
||||
@@ -44,6 +47,7 @@ describe("parser", () => {
|
||||
delete process.env.CS_DISABLE_FILE_DOWNLOADS
|
||||
delete process.env.CS_DISABLE_GETTING_STARTED_OVERRIDE
|
||||
delete process.env.VSCODE_PROXY_URI
|
||||
delete process.env.CS_DISABLE_PROXY
|
||||
console.log = jest.fn()
|
||||
})
|
||||
|
||||
@@ -100,6 +104,10 @@ describe("parser", () => {
|
||||
|
||||
"--disable-getting-started-override",
|
||||
|
||||
"--disable-proxy",
|
||||
|
||||
["--session-socket", "/tmp/override-code-server-ipc-socket"],
|
||||
|
||||
["--host", "0.0.0.0"],
|
||||
"4",
|
||||
"--",
|
||||
@@ -118,6 +126,7 @@ describe("parser", () => {
|
||||
},
|
||||
"disable-file-downloads": true,
|
||||
"disable-getting-started-override": true,
|
||||
"disable-proxy": true,
|
||||
enable: ["feature1", "feature2"],
|
||||
help: true,
|
||||
host: "0.0.0.0",
|
||||
@@ -133,6 +142,7 @@ describe("parser", () => {
|
||||
"welcome-text": "welcome to code",
|
||||
version: true,
|
||||
"bind-addr": "192.169.0.1:8080",
|
||||
"session-socket": "/tmp/override-code-server-ipc-socket",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -386,6 +396,30 @@ describe("parser", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("should use env var CS_DISABLE_PROXY", async () => {
|
||||
process.env.CS_DISABLE_PROXY = "1"
|
||||
const args = parse([])
|
||||
expect(args).toEqual({})
|
||||
|
||||
const defaultArgs = await setDefaults(args)
|
||||
expect(defaultArgs).toEqual({
|
||||
...defaults,
|
||||
"disable-proxy": true,
|
||||
})
|
||||
})
|
||||
|
||||
it("should use env var CS_DISABLE_PROXY set to true", async () => {
|
||||
process.env.CS_DISABLE_PROXY = "true"
|
||||
const args = parse([])
|
||||
expect(args).toEqual({})
|
||||
|
||||
const defaultArgs = await setDefaults(args)
|
||||
expect(defaultArgs).toEqual({
|
||||
...defaults,
|
||||
"disable-proxy": true,
|
||||
})
|
||||
})
|
||||
|
||||
it("should error if password passed in", () => {
|
||||
expect(() => parse(["--password", "supersecret123"])).toThrowError(
|
||||
"--password can only be set in the config file or passed in via $PASSWORD",
|
||||
@@ -413,7 +447,7 @@ describe("parser", () => {
|
||||
const defaultArgs = await setDefaults(args)
|
||||
expect(defaultArgs).toEqual({
|
||||
...defaults,
|
||||
"proxy-domain": ["coder.com", "coder.org"],
|
||||
"proxy-domain": ["{{port}}.coder.com", "{{port}}.coder.org"],
|
||||
})
|
||||
})
|
||||
it("should allow '=,$/' in strings", async () => {
|
||||
@@ -466,14 +500,14 @@ describe("parser", () => {
|
||||
|
||||
it("should set proxy uri", async () => {
|
||||
await setDefaults(parse(["--proxy-domain", "coder.org"]))
|
||||
expect(process.env.VSCODE_PROXY_URI).toEqual("{{port}}.coder.org")
|
||||
expect(process.env.VSCODE_PROXY_URI).toEqual("//{{port}}.coder.org")
|
||||
})
|
||||
|
||||
it("should set proxy uri to first domain", async () => {
|
||||
await setDefaults(
|
||||
parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "coder.com", "--proxy-domain", "coder.org"]),
|
||||
)
|
||||
expect(process.env.VSCODE_PROXY_URI).toEqual("{{port}}.coder.com")
|
||||
expect(process.env.VSCODE_PROXY_URI).toEqual("//{{port}}.coder.com")
|
||||
})
|
||||
|
||||
it("should not override existing proxy uri", async () => {
|
||||
@@ -487,7 +521,7 @@ describe("parser", () => {
|
||||
|
||||
describe("cli", () => {
|
||||
const testName = "cli"
|
||||
const vscodeIpcPath = path.join(os.tmpdir(), "vscode-ipc")
|
||||
let tmpDirPath: string
|
||||
|
||||
beforeAll(async () => {
|
||||
await clean(testName)
|
||||
@@ -495,68 +529,170 @@ describe("cli", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
delete process.env.VSCODE_IPC_HOOK_CLI
|
||||
await fs.rm(vscodeIpcPath, { force: true, recursive: true })
|
||||
tmpDirPath = await tmpdir(testName)
|
||||
})
|
||||
|
||||
it("should use existing if inside code-server", async () => {
|
||||
process.env.VSCODE_IPC_HOOK_CLI = "test"
|
||||
const args: UserProvidedArgs = {}
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
expect(await shouldOpenInExistingInstance(args, "")).toStrictEqual("test")
|
||||
|
||||
args.port = 8081
|
||||
args._ = ["./file"]
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
expect(await shouldOpenInExistingInstance(args, "")).toStrictEqual("test")
|
||||
})
|
||||
|
||||
it("should use existing if --reuse-window is set", async () => {
|
||||
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||
|
||||
const args: UserProvidedArgs = {}
|
||||
args["reuse-window"] = true
|
||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual(undefined)
|
||||
await expect(shouldOpenInExistingInstance(args, sessionSocket)).rejects.toThrow()
|
||||
|
||||
await fs.writeFile(vscodeIpcPath, "test")
|
||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual("test")
|
||||
const socketPath = path.join(tmpDirPath, "socket")
|
||||
const client = new EditorSessionManagerClient(sessionSocket)
|
||||
await client.addSession({
|
||||
entry: {
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath,
|
||||
},
|
||||
})
|
||||
const vscodeSockets = listenOn(socketPath)
|
||||
|
||||
await expect(shouldOpenInExistingInstance(args, sessionSocket)).resolves.toStrictEqual(socketPath)
|
||||
|
||||
args.port = 8081
|
||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual("test")
|
||||
await expect(shouldOpenInExistingInstance(args, sessionSocket)).resolves.toStrictEqual(socketPath)
|
||||
|
||||
server.close()
|
||||
vscodeSockets.close()
|
||||
})
|
||||
|
||||
it("should use existing if --new-window is set", async () => {
|
||||
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||
|
||||
const args: UserProvidedArgs = {}
|
||||
args["new-window"] = true
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
await expect(shouldOpenInExistingInstance(args, sessionSocket)).rejects.toThrow()
|
||||
|
||||
await fs.writeFile(vscodeIpcPath, "test")
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
const socketPath = path.join(tmpDirPath, "socket")
|
||||
const client = new EditorSessionManagerClient(sessionSocket)
|
||||
await client.addSession({
|
||||
entry: {
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath,
|
||||
},
|
||||
})
|
||||
const vscodeSockets = listenOn(socketPath)
|
||||
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
|
||||
|
||||
args.port = 8081
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
|
||||
|
||||
server.close()
|
||||
vscodeSockets.close()
|
||||
})
|
||||
|
||||
it("should use existing if no unrelated flags are set, has positional, and socket is active", async () => {
|
||||
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||
|
||||
const args: UserProvidedArgs = {}
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
|
||||
|
||||
args._ = ["./file"]
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
|
||||
|
||||
const testDir = await tmpdir(testName)
|
||||
const socketPath = path.join(testDir, "socket")
|
||||
await fs.writeFile(vscodeIpcPath, socketPath)
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const server = net.createServer(() => {
|
||||
// Close after getting the first connection.
|
||||
server.close()
|
||||
})
|
||||
server.once("listening", () => resolve(server))
|
||||
server.listen(socketPath)
|
||||
const client = new EditorSessionManagerClient(sessionSocket)
|
||||
const socketPath = path.join(tmpDirPath, "socket")
|
||||
await client.addSession({
|
||||
entry: {
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath,
|
||||
},
|
||||
})
|
||||
const vscodeSockets = listenOn(socketPath)
|
||||
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(socketPath)
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
|
||||
|
||||
args.port = 8081
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
|
||||
|
||||
server.close()
|
||||
vscodeSockets.close()
|
||||
})
|
||||
|
||||
it("should prefer matching sessions for only the first path", async () => {
|
||||
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||
const client = new EditorSessionManagerClient(sessionSocket)
|
||||
await client.addSession({
|
||||
entry: {
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
},
|
||||
})
|
||||
await client.addSession({
|
||||
entry: {
|
||||
workspace: {
|
||||
id: "bbb",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/bbb",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
},
|
||||
})
|
||||
listenOn(`${tmpDirPath}/vscode-ipc-aaa.sock`, `${tmpDirPath}/vscode-ipc-bbb.sock`)
|
||||
|
||||
const args: UserProvidedArgs = {}
|
||||
args._ = ["/aaa/file", "/bbb/file"]
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(`${tmpDirPath}/vscode-ipc-aaa.sock`)
|
||||
|
||||
server.close()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -653,6 +789,50 @@ describe("bindAddrFromArgs", () => {
|
||||
expect(actual).toStrictEqual(expected)
|
||||
})
|
||||
|
||||
it("should use process.env.CODE_SERVER_HOST if set", () => {
|
||||
const [setValue, resetValue] = useEnv("CODE_SERVER_HOST")
|
||||
setValue("coder")
|
||||
|
||||
const args: UserProvidedArgs = {}
|
||||
|
||||
const addr = {
|
||||
host: "localhost",
|
||||
port: 8080,
|
||||
}
|
||||
|
||||
const actual = bindAddrFromArgs(addr, args)
|
||||
const expected = {
|
||||
host: "coder",
|
||||
port: 8080,
|
||||
}
|
||||
|
||||
expect(actual).toStrictEqual(expected)
|
||||
resetValue()
|
||||
})
|
||||
|
||||
it("should use the args.host over process.env.CODE_SERVER_HOST if both set", () => {
|
||||
const [setValue, resetValue] = useEnv("CODE_SERVER_HOST")
|
||||
setValue("coder")
|
||||
|
||||
const args: UserProvidedArgs = {
|
||||
host: "123.123.123.123",
|
||||
}
|
||||
|
||||
const addr = {
|
||||
host: "localhost",
|
||||
port: 8080,
|
||||
}
|
||||
|
||||
const actual = bindAddrFromArgs(addr, args)
|
||||
const expected = {
|
||||
host: "123.123.123.123",
|
||||
port: 8080,
|
||||
}
|
||||
|
||||
expect(actual).toStrictEqual(expected)
|
||||
resetValue()
|
||||
})
|
||||
|
||||
it("should use process.env.PORT if set", () => {
|
||||
const [setValue, resetValue] = useEnv("PORT")
|
||||
setValue("8000")
|
||||
@@ -729,44 +909,6 @@ cert: false`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("readSocketPath", () => {
|
||||
const fileContents = "readSocketPath file contents"
|
||||
let tmpDirPath: string
|
||||
let tmpFilePath: string
|
||||
|
||||
const testName = "readSocketPath"
|
||||
beforeAll(async () => {
|
||||
await clean(testName)
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDirPath = await tmpdir(testName)
|
||||
tmpFilePath = path.join(tmpDirPath, "readSocketPath.txt")
|
||||
await fs.writeFile(tmpFilePath, fileContents)
|
||||
})
|
||||
|
||||
it("should throw an error if it can't read the file", async () => {
|
||||
// TODO@jsjoeio - implement
|
||||
// Test it on a directory.... ESDIR
|
||||
// TODO@jsjoeio - implement
|
||||
expect(() => readSocketPath(tmpDirPath)).rejects.toThrow("EISDIR")
|
||||
})
|
||||
it("should return undefined if it can't read the file", async () => {
|
||||
// TODO@jsjoeio - implement
|
||||
const socketPath = await readSocketPath(path.join(tmpDirPath, "not-a-file"))
|
||||
expect(socketPath).toBeUndefined()
|
||||
})
|
||||
it("should return the file contents", async () => {
|
||||
const contents = await readSocketPath(tmpFilePath)
|
||||
expect(contents).toBe(fileContents)
|
||||
})
|
||||
it("should return the same file contents for two different calls", async () => {
|
||||
const contents1 = await readSocketPath(tmpFilePath)
|
||||
const contents2 = await readSocketPath(tmpFilePath)
|
||||
expect(contents2).toBe(contents1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("toCodeArgs", () => {
|
||||
const vscodeDefaults = {
|
||||
...defaults,
|
||||
|
||||
@@ -58,6 +58,7 @@ describe("http", () => {
|
||||
;[
|
||||
["host", test.host],
|
||||
["x-forwarded-host", test.host],
|
||||
["x-forwarded-host", `${test.host}, ${test.host}`],
|
||||
["forwarded", `for=127.0.0.1, host=${test.host}, proto=http`],
|
||||
["forwarded", `for=127.0.0.1;proto=http;host=${test.host}`],
|
||||
["forwarded", `proto=http;host=${test.host}, for=127.0.0.1`],
|
||||
@@ -69,6 +70,7 @@ describe("http", () => {
|
||||
origin: test.origin,
|
||||
[key]: value,
|
||||
},
|
||||
args: {},
|
||||
})
|
||||
if (typeof test.expected === "string") {
|
||||
expect(() => http.authenticateOrigin(req)).toThrow(test.expected)
|
||||
|
||||
@@ -43,6 +43,7 @@ describe("plugin", () => {
|
||||
usingEnvHashedPassword: false,
|
||||
"extensions-dir": "",
|
||||
"user-data-dir": "",
|
||||
"session-socket": "",
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as bodyParser from "body-parser"
|
||||
import * as express from "express"
|
||||
import * as http from "http"
|
||||
import nodeFetch from "node-fetch"
|
||||
@@ -45,6 +44,17 @@ describe("proxy", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should return 403 Forbidden if proxy is disabled", async () => {
|
||||
e.get("/wsup", (req, res) => {
|
||||
res.json("you cannot see this")
|
||||
})
|
||||
codeServer = await integration.setup(["--auth=none", "--disable-proxy"], "")
|
||||
const resp = await codeServer.fetch(proxyPath)
|
||||
expect(resp.status).toBe(403)
|
||||
const json = await resp.json()
|
||||
expect(json).toEqual({ error: "Forbidden" })
|
||||
})
|
||||
|
||||
it("should rewrite the base path", async () => {
|
||||
e.get("/wsup", (req, res) => {
|
||||
res.json("asher is the best")
|
||||
@@ -99,7 +109,7 @@ describe("proxy", () => {
|
||||
})
|
||||
|
||||
it("should allow post bodies", async () => {
|
||||
e.use(bodyParser.json({ strict: false }))
|
||||
e.use(express.json({ strict: false }))
|
||||
e.post("/wsup", (req, res) => {
|
||||
res.json(req.body)
|
||||
})
|
||||
@@ -116,7 +126,7 @@ describe("proxy", () => {
|
||||
})
|
||||
|
||||
it("should handle bad requests", async () => {
|
||||
e.use(bodyParser.json({ strict: false }))
|
||||
e.use(express.json({ strict: false }))
|
||||
e.post("/wsup", (req, res) => {
|
||||
res.json(req.body)
|
||||
})
|
||||
@@ -143,7 +153,7 @@ describe("proxy", () => {
|
||||
})
|
||||
|
||||
it("should handle errors", async () => {
|
||||
e.use(bodyParser.json({ strict: false }))
|
||||
e.use(express.json({ strict: false }))
|
||||
e.post("/wsup", (req, res) => {
|
||||
throw new Error("BROKEN")
|
||||
})
|
||||
@@ -187,6 +197,17 @@ describe("proxy", () => {
|
||||
})
|
||||
}).rejects.toThrow()
|
||||
})
|
||||
|
||||
it("should proxy non-ASCII", async () => {
|
||||
e.get("*", (req, res) => {
|
||||
res.json("ほげ")
|
||||
})
|
||||
codeServer = await integration.setup(["--auth=none"], "")
|
||||
const resp = await codeServer.fetch(proxyPath.replace("wsup", "ほげ"))
|
||||
expect(resp.status).toBe(200)
|
||||
const json = await resp.json()
|
||||
expect(json).toBe("ほげ")
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE@jsjoeio
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mockLogger } from "../../../utils/helpers"
|
||||
import * as httpserver from "../../../utils/httpserver"
|
||||
import * as integration from "../../../utils/integration"
|
||||
import { mockLogger } from "../../../utils/helpers"
|
||||
|
||||
describe("vscode", () => {
|
||||
let codeServer: httpserver.HttpServer | undefined
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { logger } from "@coder/logger"
|
||||
import * as http from "http"
|
||||
import { AddressInfo } from "net"
|
||||
import * as path from "path"
|
||||
import { ensureAddress } from "../../../src/node/app"
|
||||
import { SettingsProvider, UpdateSettings } from "../../../src/node/settings"
|
||||
import { LatestResponse, UpdateProvider } from "../../../src/node/update"
|
||||
import { clean, isAddressInfo, mockLogger, tmpdir } from "../../utils/helpers"
|
||||
import { clean, mockLogger, tmpdir } from "../../utils/helpers"
|
||||
|
||||
describe("update", () => {
|
||||
let version = "1.0.0"
|
||||
@@ -79,7 +79,6 @@ describe("update", () => {
|
||||
}
|
||||
|
||||
let _provider: UpdateProvider | undefined
|
||||
let _address: string | AddressInfo | null
|
||||
const provider = (): UpdateProvider => {
|
||||
if (!_provider) {
|
||||
throw new Error("Update provider has not been created")
|
||||
@@ -87,6 +86,7 @@ describe("update", () => {
|
||||
return _provider
|
||||
}
|
||||
|
||||
let address = new URL("http://localhost")
|
||||
beforeAll(async () => {
|
||||
mockLogger()
|
||||
|
||||
@@ -105,12 +105,13 @@ describe("update", () => {
|
||||
})
|
||||
})
|
||||
|
||||
_address = server.address()
|
||||
if (!isAddressInfo(_address)) {
|
||||
throw new Error("unexpected address")
|
||||
const addr = ensureAddress(server, "http")
|
||||
if (typeof addr === "string") {
|
||||
throw new Error("unable to run update tests with unix sockets")
|
||||
}
|
||||
|
||||
_provider = new UpdateProvider(`http://${_address?.address}:${_address?.port}/latest`, _settings)
|
||||
address = addr
|
||||
address.pathname = "/latest"
|
||||
_provider = new UpdateProvider(address.toString(), _settings)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@@ -220,59 +221,51 @@ describe("update", () => {
|
||||
})
|
||||
|
||||
it("should reject if response has status code 500", async () => {
|
||||
if (isAddressInfo(_address)) {
|
||||
const mockURL = `http://${_address.address}:${_address.port}/reject-status-code`
|
||||
const provider = new UpdateProvider(mockURL, settings())
|
||||
const update = await provider.getUpdate(true)
|
||||
address.pathname = "/reject-status-code"
|
||||
const provider = new UpdateProvider(address.toString(), settings())
|
||||
const update = await provider.getUpdate(true)
|
||||
|
||||
expect(update.version).toBe("unknown")
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
|
||||
identifier: "error",
|
||||
value: `${mockURL}: 500`,
|
||||
})
|
||||
}
|
||||
expect(update.version).toBe("unknown")
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
|
||||
identifier: "error",
|
||||
value: `${address.toString()}: 500`,
|
||||
})
|
||||
})
|
||||
|
||||
it("should reject if no location header provided", async () => {
|
||||
if (isAddressInfo(_address)) {
|
||||
const mockURL = `http://${_address.address}:${_address.port}/no-location-header`
|
||||
const provider = new UpdateProvider(mockURL, settings())
|
||||
const update = await provider.getUpdate(true)
|
||||
address.pathname = "/no-location-header"
|
||||
const provider = new UpdateProvider(address.toString(), settings())
|
||||
const update = await provider.getUpdate(true)
|
||||
|
||||
expect(update.version).toBe("unknown")
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
|
||||
identifier: "error",
|
||||
value: `received redirect with no location header`,
|
||||
})
|
||||
}
|
||||
expect(update.version).toBe("unknown")
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
|
||||
identifier: "error",
|
||||
value: `received redirect with no location header`,
|
||||
})
|
||||
})
|
||||
|
||||
it("should resolve the request with response.headers.location", async () => {
|
||||
version = "4.1.1"
|
||||
if (isAddressInfo(_address)) {
|
||||
const mockURL = `http://${_address.address}:${_address.port}/with-location-header`
|
||||
const provider = new UpdateProvider(mockURL, settings())
|
||||
const update = await provider.getUpdate(true)
|
||||
address.pathname = "/with-location-header"
|
||||
const provider = new UpdateProvider(address.toString(), settings())
|
||||
const update = await provider.getUpdate(true)
|
||||
|
||||
expect(logger.error).not.toHaveBeenCalled()
|
||||
expect(update.version).toBe("4.1.1")
|
||||
}
|
||||
expect(logger.error).not.toHaveBeenCalled()
|
||||
expect(update.version).toBe("4.1.1")
|
||||
})
|
||||
|
||||
it("should reject if more than 10 redirects", async () => {
|
||||
if (isAddressInfo(_address)) {
|
||||
const mockURL = `http://${_address.address}:${_address.port}/redirect/11`
|
||||
const provider = new UpdateProvider(mockURL, settings())
|
||||
const update = await provider.getUpdate(true)
|
||||
address.pathname = "/redirect/11"
|
||||
const provider = new UpdateProvider(address.toString(), settings())
|
||||
const update = await provider.getUpdate(true)
|
||||
|
||||
expect(update.version).toBe("unknown")
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
|
||||
identifier: "error",
|
||||
value: `reached max redirects`,
|
||||
})
|
||||
}
|
||||
expect(update.version).toBe("unknown")
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
|
||||
identifier: "error",
|
||||
value: `reached max redirects`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -491,22 +491,6 @@ describe("isDirectory", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("humanPath", () => {
|
||||
it("should return an empty string if no path provided", () => {
|
||||
const mockHomedir = "/home/coder"
|
||||
const actual = util.humanPath(mockHomedir)
|
||||
const expected = ""
|
||||
expect(actual).toBe(expected)
|
||||
})
|
||||
it("should replace the homedir with ~", () => {
|
||||
const mockHomedir = "/home/coder"
|
||||
const path = `${mockHomedir}/code-server`
|
||||
const actual = util.humanPath(mockHomedir, path)
|
||||
const expected = "~/code-server"
|
||||
expect(actual).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe("isWsl", () => {
|
||||
const testName = "wsl"
|
||||
|
||||
|
||||
277
test/unit/node/vscodeSocket.test.ts
Normal file
277
test/unit/node/vscodeSocket.test.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
import { logger } from "@coder/logger"
|
||||
import * as app from "../../../src/node/app"
|
||||
import { EditorSessionManager, makeEditorSessionManagerServer } from "../../../src/node/vscodeSocket"
|
||||
import { clean, tmpdir, listenOn, mockLogger } from "../../utils/helpers"
|
||||
|
||||
describe("makeEditorSessionManagerServer", () => {
|
||||
let tmpDirPath: string
|
||||
|
||||
const testName = "mesms"
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
mockLogger()
|
||||
await clean(testName)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.resetModules()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDirPath = await tmpdir(testName)
|
||||
})
|
||||
|
||||
it("warns if socket cannot be created", async () => {
|
||||
jest.spyOn(app, "listen").mockImplementation(() => {
|
||||
throw new Error()
|
||||
})
|
||||
const server = await makeEditorSessionManagerServer(
|
||||
`${tmpDirPath}/code-server-ipc.sock`,
|
||||
new EditorSessionManager(),
|
||||
)
|
||||
expect(logger.warn).toHaveBeenCalledWith(`Could not create socket at ${tmpDirPath}/code-server-ipc.sock`)
|
||||
server.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe("EditorSessionManager", () => {
|
||||
let tmpDirPath: string
|
||||
|
||||
const testName = "esm"
|
||||
|
||||
beforeAll(async () => {
|
||||
await clean(testName)
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDirPath = await tmpdir(testName)
|
||||
})
|
||||
|
||||
describe("getCandidatesForFile", () => {
|
||||
it("should prefer the last added socket path for a matching path", async () => {
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa-1.sock`,
|
||||
})
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa-2.sock`,
|
||||
})
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "bbb",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/bbb",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
})
|
||||
const socketPaths = manager.getCandidatesForFile("/aaa/some-file:1:1")
|
||||
expect(socketPaths.map((x) => x.socketPath)).toEqual([
|
||||
// Matches
|
||||
`${tmpDirPath}/vscode-ipc-aaa-2.sock`,
|
||||
`${tmpDirPath}/vscode-ipc-aaa-1.sock`,
|
||||
// Non-matches
|
||||
`${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
])
|
||||
})
|
||||
|
||||
it("should return the last added socketPath if there are no matches", async () => {
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
})
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "bbb",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/bbb",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
})
|
||||
const socketPaths = manager.getCandidatesForFile("/ccc/some-file:1:1")
|
||||
expect(socketPaths.map((x) => x.socketPath)).toEqual([
|
||||
`${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
`${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
])
|
||||
})
|
||||
|
||||
it("does not just directly do a substring match", async () => {
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
})
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "bbb",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/bbb",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
})
|
||||
const entries = manager.getCandidatesForFile("/aaaxxx/some-file:1:1")
|
||||
expect(entries.map((x) => x.socketPath)).toEqual([
|
||||
`${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
`${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("getConnectedSocketPath", () => {
|
||||
it("should return socket path if socket is active", async () => {
|
||||
listenOn(`${tmpDirPath}/vscode-ipc-aaa.sock`).once()
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
})
|
||||
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
|
||||
expect(socketPath).toBe(`${tmpDirPath}/vscode-ipc-aaa.sock`)
|
||||
})
|
||||
|
||||
it("should return undefined if socket is inactive", async () => {
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
})
|
||||
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
|
||||
expect(socketPath).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should return undefined given no matching active sockets", async () => {
|
||||
const vscodeSockets = listenOn(`${tmpDirPath}/vscode-ipc-bbb.sock`)
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
})
|
||||
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
|
||||
expect(socketPath).toBeUndefined()
|
||||
vscodeSockets.close()
|
||||
})
|
||||
|
||||
it("should return undefined if there are no entries", async () => {
|
||||
const manager = new EditorSessionManager()
|
||||
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
|
||||
expect(socketPath).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should return most recently used socket path available", async () => {
|
||||
listenOn(`${tmpDirPath}/vscode-ipc-aaa-1.sock`).once()
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa-1.sock`,
|
||||
})
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa-2.sock`,
|
||||
})
|
||||
|
||||
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
|
||||
expect(socketPath).toBe(`${tmpDirPath}/vscode-ipc-aaa-1.sock`)
|
||||
// Failed sockets should be removed from the entries.
|
||||
expect((manager as any).entries.has(`${tmpDirPath}/vscode-ipc-aaa-2.sock`)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,8 @@
|
||||
import { promises as fs } from "fs"
|
||||
import * as path from "path"
|
||||
import { workspaceDir } from "./constants"
|
||||
import { clean, tmpdir } from "./helpers"
|
||||
import * as wtfnode from "./wtfnode"
|
||||
import * as path from "path"
|
||||
import { promises as fs } from "fs"
|
||||
|
||||
/**
|
||||
* Perform workspace cleanup and authenticate. This should be ran before e2e
|
||||
|
||||
@@ -108,20 +108,6 @@ export function idleTimer(message: string, reject: (error: Error) => void, delay
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function which returns a boolean indicating whether
|
||||
* the given address is AddressInfo and has .address
|
||||
* and a .port property.
|
||||
*/
|
||||
export function isAddressInfo(address: unknown): address is net.AddressInfo {
|
||||
return (
|
||||
address !== null &&
|
||||
typeof address !== "string" &&
|
||||
(address as net.AddressInfo).port !== undefined &&
|
||||
(address as net.AddressInfo).address !== undefined
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* If using a proxy, return the address of the proxy.
|
||||
*
|
||||
@@ -150,3 +136,52 @@ export function getMaybeProxiedPathname(url: URL): string {
|
||||
|
||||
return url.pathname
|
||||
}
|
||||
|
||||
interface FakeVscodeSockets {
|
||||
/* If called, closes all servers after the first connection. */
|
||||
once(): FakeVscodeSockets
|
||||
|
||||
/* Manually close all servers. */
|
||||
close(): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates servers for each socketPath specified.
|
||||
*/
|
||||
export function listenOn(...socketPaths: string[]): FakeVscodeSockets {
|
||||
let once = false
|
||||
const servers = socketPaths.map((socketPath) => {
|
||||
const server = net.createServer(() => {
|
||||
if (once) {
|
||||
close()
|
||||
}
|
||||
})
|
||||
server.listen(socketPath)
|
||||
return server
|
||||
})
|
||||
|
||||
async function close() {
|
||||
await Promise.all(
|
||||
servers.map(
|
||||
(server) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
server.close((err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
const fakeVscodeSockets = {
|
||||
close,
|
||||
once: () => {
|
||||
once = true
|
||||
return fakeVscodeSockets
|
||||
},
|
||||
}
|
||||
return fakeVscodeSockets
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export class HttpServer {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.hs.on("error", reject)
|
||||
|
||||
this.hs.listen(0, "localhost", () => {
|
||||
this.hs.listen(0, "127.0.0.1", () => {
|
||||
this.hs.off("error", reject)
|
||||
resolve()
|
||||
|
||||
|
||||
@@ -2856,6 +2856,11 @@ qs@^6.10.1:
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||
|
||||
react-is@^17.0.1:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
@@ -2875,6 +2880,11 @@ require-directory@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
|
||||
|
||||
requires-port@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
|
||||
|
||||
resolve-cwd@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
|
||||
@@ -2936,16 +2946,16 @@ saxes@^5.0.1:
|
||||
xmlchars "^2.2.0"
|
||||
|
||||
semver@7.x, semver@^7.3.2, semver@^7.3.5:
|
||||
version "7.3.5"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
|
||||
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
|
||||
version "7.5.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^6.0.0, semver@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
version "6.3.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
set-blocking@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -3222,13 +3232,14 @@ to-regex-range@^5.0.1:
|
||||
is-number "^7.0.0"
|
||||
|
||||
tough-cookie@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4"
|
||||
integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"
|
||||
integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==
|
||||
dependencies:
|
||||
psl "^1.1.33"
|
||||
punycode "^2.1.1"
|
||||
universalify "^0.1.2"
|
||||
universalify "^0.2.0"
|
||||
url-parse "^1.5.3"
|
||||
|
||||
tr46@^2.1.0:
|
||||
version "2.1.0"
|
||||
@@ -3280,10 +3291,18 @@ typedarray-to-buffer@^3.1.5:
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
universalify@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||
universalify@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
|
||||
|
||||
url-parse@^1.5.3:
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
|
||||
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
util-deprecate@^1.0.1:
|
||||
version "1.0.2"
|
||||
@@ -3379,9 +3398,9 @@ wide-align@^1.1.2:
|
||||
string-width "^1.0.2 || 2 || 3 || 4"
|
||||
|
||||
word-wrap@~1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f"
|
||||
integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
|
||||
Reference in New Issue
Block a user