Compare commits

..

21 Commits

Author SHA1 Message Date
Joe Previte
8ed7498b3c fixup: include description ts-expect-error comment 2022-12-06 08:55:24 -07:00
Joe Previte
73b61ecfb6 feat(wrapper): add tests for isChild 2022-12-05 16:11:45 -07:00
Joe Previte
292a63dccd Merge branch 'main' into release/v4.9.0 2022-12-05 15:43:33 -07:00
Joe Previte
60ab23f008 chore: pin all jobs in build to ubuntu 20.04 2022-12-05 15:41:08 -07:00
Joe Previte
40396cc810 chore: use ubuntu-22.04 for e2e 2022-12-02 15:20:36 -07:00
Joe Previte
0c7a60e407 chore: add notes to changelog 2022-12-02 13:56:17 -07:00
Joe Previte
e701b74952 chore: update prettierignore 2022-12-02 13:56:10 -07:00
Joe Previte
c761199923 chore: pin ubuntu runner in build code-server 2022-12-02 13:48:55 -07:00
Joe Previte
956ed96a2c Revert "fix: add +x to product.json in build-vscode"
This reverts commit fc4d2b532f.
2022-12-02 13:44:13 -07:00
Joe Previte
2b32b10e87 fixup!: fix: keep product.json file permissions in release 2022-12-02 13:42:37 -07:00
Joe Previte
ace09103a2 chore: update package.json bust cache 2022-12-02 11:31:53 -07:00
Joe Previte
f7db2a0dd8 trigger ci 2022-12-02 11:25:57 -07:00
Joe Previte
0f66360646 fix: keep product.json file permissions in release
When we added the change to modify the `package.json` version using `mv`
and `jq` we didn't account for lost file permissions.

This caused a bug only happening in CI.

This should fix it by giving it 755 via `chmod`.
2022-12-02 11:03:20 -07:00
Joe Previte
130f52e6b7 chore: increase timeout 2022-12-01 12:28:58 -07:00
Joe Previte
c9279ccbe4 Merge branch 'main' into release/v4.9.0 2022-12-01 11:07:47 -07:00
Joe Previte
fc4d2b532f fix: add +x to product.json in build-vscode
While testing a pre-release, there seems to be a bug with the file
permissions for `product.json`. Adding `chmod +x` to see if that fixes
it.
2022-12-01 10:19:47 -07:00
Joe Previte
9efbd2d59c Merge branch 'main' into release/v4.9.0 2022-11-30 12:36:16 -07:00
Joe Previte
ccf882f830 Merge branch 'main' into release/v4.9.0 2022-11-10 13:49:07 -07:00
Joe Previte
96b3c3e024 Merge branch 'main' into release/v4.9.0 2022-11-10 12:46:39 -07:00
Joe Previte
17d41135f3 fixup 2022-11-10 11:51:50 -07:00
Joe Previte
9694242b2f wip: changelog 2022-11-10 11:51:50 -07:00
125 changed files with 3127 additions and 4826 deletions

2
.github/CODEOWNERS vendored
View File

@@ -3,5 +3,3 @@
ci/helm-chart/ @Matthew-Beckett @alexgorbatchev ci/helm-chart/ @Matthew-Beckett @alexgorbatchev
docs/install.md @GNUxeava docs/install.md @GNUxeava
src/node/i18n/locales/zh-cn.json @zhaozhiming

View File

@@ -75,10 +75,10 @@ body:
required: true required: true
- type: checkboxes - type: checkboxes
attributes: attributes:
label: Are you accessing code-server over a secure context? label: Are you accessing code-server over HTTPS?
description: code-server relies on service workers (which only work in secure contexts) for many features. Double-check that you are using a secure context like HTTPS or localhost. description: code-server relies on service workers for many features. Double-check that you are using HTTPS.
options: options:
- label: I am using a secure context. - label: I am using HTTPS.
required: true required: true
- type: textarea - type: textarea
attributes: attributes:

View File

@@ -1,31 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
time: "06:00"
timezone: "America/Chicago"
labels: []
commit-message:
prefix: "chore"
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "monthly"
time: "06:00"
timezone: "America/Chicago"
commit-message:
prefix: "chore"
labels: []
ignore:
# Ignore patch updates for all dependencies
- dependency-name: "*"
update-types:
- version-update:semver-patch
# Ignore major updates to Node.js types, because they need to
# correspond to the Node.js engine version
- dependency-name: "@types/node"
update-types:
- version-update:semver-major

View File

@@ -28,10 +28,10 @@ jobs:
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Run prettier with actionsx/prettier - name: Run prettier with actionsx/prettier
uses: actionsx/prettier@v3 uses: actionsx/prettier@v2
with: with:
args: --check --loglevel=warn . args: --check --loglevel=warn .
@@ -41,20 +41,20 @@ jobs:
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Get changed files - name: Get changed files
id: changed-files id: changed-files
uses: tj-actions/changed-files@v39 uses: tj-actions/changed-files@v26.1
with: with:
files: | files: |
docs/** docs/**
- name: Install Node.js v18 - name: Install Node.js v16
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: "18" node-version: "16"
cache: "yarn" cache: "yarn"
- name: Install doctoc - name: Install doctoc
@@ -70,20 +70,20 @@ jobs:
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
fetch-depth: 2 fetch-depth: 2
- name: Get changed files - name: Get changed files
id: changed-files id: changed-files
uses: tj-actions/changed-files@v39 uses: tj-actions/changed-files@v26.1
with: with:
files: | files: |
ci/helm-chart/** ci/helm-chart/**
- name: Install helm - name: Install helm
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'
uses: azure/setup-helm@v3.5 uses: azure/setup-helm@v3.4
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -101,13 +101,13 @@ jobs:
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
fetch-depth: 2 fetch-depth: 2
- name: Get changed files - name: Get changed files
id: changed-files id: changed-files
uses: tj-actions/changed-files@v39 uses: tj-actions/changed-files@v26.1
with: with:
files: | files: |
**/*.ts **/*.ts
@@ -115,11 +115,11 @@ jobs:
files_ignore: | files_ignore: |
lib/vscode/** lib/vscode/**
- name: Install Node.js v18 - name: Install Node.js v16
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: "18" node-version: "16"
- name: Fetch dependencies from cache - name: Fetch dependencies from cache
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'
@@ -139,82 +139,18 @@ jobs:
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'
run: yarn lint:ts run: yarn lint:ts
lint-actions:
name: Lint GitHub Actions
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Check workflow files
run: |
bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/7fdc9630cc360ea1a469eed64ac6d78caeda1234/scripts/download-actionlint.bash)
./actionlint -color -shellcheck= -ignore "set-output"
shell: bash
test-unit:
name: Run unit tests
runs-on: ubuntu-20.04
timeout-minutes: 5
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v39
with:
files: |
**/*.ts
files_ignore: |
lib/vscode/**
- name: Install Node.js v18
if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Fetch dependencies from cache
if: steps.changed-files.outputs.any_changed == 'true'
id: cache-node-modules
uses: actions/cache@v3
with:
path: "**/node_modules"
key: yarn-build-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
yarn-build-
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true' && steps.cache-node-modules.outputs.cache-hit != 'true'
run: SKIP_SUBMODULE_DEPS=1 yarn --frozen-lockfile
- name: Run unit tests
if: steps.changed-files.outputs.any_changed == 'true'
run: yarn test:unit
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
if: success()
build: build:
name: Build code-server name: Build code-server
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 60 timeout-minutes: 30
env: env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- name: Install system dependencies
run: sudo apt update && sudo apt install -y libkrb5-dev
- name: Install quilt - name: Install quilt
uses: awalsh128/cache-apt-pkgs-action@latest uses: awalsh128/cache-apt-pkgs-action@latest
with: with:
@@ -224,34 +160,32 @@ jobs:
- name: Patch Code - name: Patch Code
run: quilt push -a run: quilt push -a
- name: Install Node.js v18 - name: Install Node.js v16
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: "18" node-version: "16"
- name: Fetch dependencies from cache - name: Fetch dependencies from cache
id: cache-node-modules id: cache-node-modules
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: "**/node_modules" path: "**/node_modules"
key: yarn-build-code-server-${{ hashFiles('**/yarn.lock') }} key: yarn-build-${{ hashFiles('**/yarn.lock') }}
restore-keys: | restore-keys: |
yarn-build-code-server- yarn-build-
- name: Install dependencies - name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true' if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile run: yarn --frozen-lockfile
- name: Build code-server - name: Build code-server
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: yarn build run: yarn build
# Get Code's git hash. When this changes it means the content is # Get Code's git hash. When this changes it means the content is
# different and we need to rebuild. # different and we need to rebuild.
- name: Get latest lib/vscode rev - name: Get latest lib/vscode rev
id: vscode-rev id: vscode-rev
run: echo "rev=$(git rev-parse HEAD:./lib/vscode)" >> $GITHUB_OUTPUT run: echo "::set-output name=rev::$(git rev-parse HEAD:./lib/vscode)"
# We need to rebuild when we have a new version of Code, when any of # We need to rebuild when we have a new version of Code, when any of
# the patches changed, or when the code-server version changes (since # the patches changed, or when the code-server version changes (since
@@ -270,6 +204,19 @@ jobs:
if: steps.cache-vscode.outputs.cache-hit != 'true' if: steps.cache-vscode.outputs.cache-hit != 'true'
run: yarn build:vscode run: yarn build:vscode
# Our code imports code from VS Code's `out` directory meaning VS Code
# must be built before running these tests.
# TODO: Move to its own step?
- name: Run code-server unit tests
run: yarn test:unit
if: success()
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
if: success()
# The release package does not contain any native modules # The release package does not contain any native modules
# and is neutral to architecture/os/libc version. # and is neutral to architecture/os/libc version.
- name: Create release package - name: Create release package
@@ -286,6 +233,65 @@ jobs:
name: npm-package name: npm-package
path: ./package.tar.gz path: ./package.tar.gz
npm:
name: Publish npm package
# the npm-package gets uploaded as an artifact in Build
# so we need that to complete before this runs
needs: build
# This environment "npm" requires someone from
# coder/code-server-reviewers to approve the PR before this job runs.
environment: npm
# Only run if PR comes from base repo or event is not a PR
# Reason: forks cannot access secrets and this will always fail
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Download artifact
uses: actions/download-artifact@v3
id: download
with:
name: "npm-package"
path: release-npm-package
- name: Run ./ci/steps/publish-npm.sh
run: yarn publish:npm
env:
# NOTE@jsjoeio
# This is because npm enforces semantic versioning
# so it has to be a valid version. We only use this
# to publish dev versions from prs
# and beta versions from main.
VERSION: "0.0.0"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# NOTE@jsjoeio
# NPM_ENVIRONMENT intentionally not set here.
# Instead, itis determined in publish-npm.sh script
# using GITHUB environment variables
- name: Comment npm information
uses: marocchino/sticky-pull-request-comment@v2
with:
GITHUB_TOKEN: ${{ github.token }}
header: npm-dev-build
message: |
✨ code-server dev build published to npm for PR #${{ github.event.number }}!
* _Last publish status_: success
* _Commit_: ${{ github.event.pull_request.head.sha }}
To install in a local project, run:
```shell-session
npm install @coder/code-server-pr@${{ github.event.number }}
```
To install globally, run:
```shell-session
npm install -g @coder/code-server-pr@${{ github.event.number }}
```
test-e2e: test-e2e:
name: Run e2e tests name: Run e2e tests
needs: build needs: build
@@ -293,15 +299,12 @@ jobs:
timeout-minutes: 25 timeout-minutes: 25
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Install system dependencies - name: Install Node.js v16
run: sudo apt update && sudo apt install -y libkrb5-dev
- name: Install Node.js v18
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: "18" node-version: "16"
- name: Fetch dependencies from cache - name: Fetch dependencies from cache
id: cache-node-modules id: cache-node-modules
@@ -321,7 +324,7 @@ jobs:
run: tar -xzf package.tar.gz run: tar -xzf package.tar.gz
- name: Install release package dependencies - name: Install release package dependencies
run: cd release && npm install --unsafe-perm --omit=dev run: cd release && yarn install
- name: Install dependencies - name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true' if: steps.cache-node-modules.outputs.cache-hit != 'true'
@@ -333,7 +336,7 @@ jobs:
./test/node_modules/.bin/playwright install ./test/node_modules/.bin/playwright install
- name: Run end-to-end tests - name: Run end-to-end tests
run: CODE_SERVER_TEST_ENTRY=./release yarn test:e2e run: CODE_SERVER_TEST_ENTRY=./release yarn test:e2e --global-timeout 840000
- name: Upload test artifacts - name: Upload test artifacts
if: always() if: always()
@@ -352,15 +355,12 @@ jobs:
timeout-minutes: 25 timeout-minutes: 25
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Install system dependencies - name: Install Node.js v16
run: sudo apt update && sudo apt install -y libkrb5-dev
- name: Install Node.js v18
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: "18" node-version: "16"
- name: Fetch dependencies from cache - name: Fetch dependencies from cache
id: cache-node-modules id: cache-node-modules
@@ -380,7 +380,7 @@ jobs:
run: tar -xzf package.tar.gz run: tar -xzf package.tar.gz
- name: Install release package dependencies - name: Install release package dependencies
run: cd release && npm install --unsafe-perm --omit=dev run: cd release && yarn install
- name: Install dependencies - name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true' if: steps.cache-node-modules.outputs.cache-hit != 'true'
@@ -392,7 +392,7 @@ jobs:
./test/node_modules/.bin/playwright install ./test/node_modules/.bin/playwright install
- name: Cache Caddy - name: Cache Caddy
uses: actions/cache@v3 uses: actions/cache@v2
id: caddy-cache id: caddy-cache
with: with:
path: | path: |
@@ -405,7 +405,7 @@ jobs:
if: steps.caddy-cache.outputs.cache-hit != 'true' if: steps.caddy-cache.outputs.cache-hit != 'true'
run: | run: |
gh release download v2.5.2 --repo caddyserver/caddy --pattern "caddy_2.5.2_linux_amd64.tar.gz" gh release download v2.5.2 --repo caddyserver/caddy --pattern "caddy_2.5.2_linux_amd64.tar.gz"
mkdir -p ~/.cache/caddy mkdir -p ~/.cache/caddy
tar -xzf caddy_2.5.2_linux_amd64.tar.gz --directory ~/.cache/caddy tar -xzf caddy_2.5.2_linux_amd64.tar.gz --directory ~/.cache/caddy
- name: Start Caddy - name: Start Caddy

46
.github/workflows/docs-preview.yaml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Docs preview
on:
pull_request:
branches:
- main
paths:
- "docs/**"
permissions:
actions: none
checks: none
contents: read
deployments: none
issues: none
packages: none
pull-requests: write
repository-projects: none
security-events: none
statuses: none
jobs:
preview:
name: Docs preview
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Set outputs
id: vars
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: Comment Credentials
uses: marocchino/sticky-pull-request-comment@v2
# Only run if PR comes from base repo
# Reason: forks cannot access secrets and this will always fail
if: github.event.pull_request.head.repo.full_name == github.repository
with:
GITHUB_TOKEN: ${{ github.token }}
header: codercom-preview-docs
message: |
✨ code-server docs for PR #${{ github.event.number }} is ready! It will be updated on every commit.
* _Host_: https://coder.com/docs/code-server/${{ steps.vars.outputs.sha_short }}
* _Last deploy status_: success
* _Commit_: ${{ github.event.pull_request.head.sha }}
* _Workflow status_: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}

View File

@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Install code-server - name: Install code-server
run: ./install.sh run: ./install.sh
@@ -44,7 +44,7 @@ jobs:
container: "alpine:3.17" container: "alpine:3.17"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Install curl - name: Install curl
run: apk add curl run: apk add curl
@@ -67,7 +67,7 @@ jobs:
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Install code-server - name: Install code-server
run: ./install.sh run: ./install.sh

View File

@@ -27,16 +27,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code-server - name: Checkout code-server
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Install Node.js v18
uses: actions/setup-node@v3
with:
node-version: "18"
cache: "yarn"
- name: Download npm package from release artifacts - name: Download npm package from release artifacts
uses: robinraju/release-downloader@v1.8 uses: robinraju/release-downloader@v1.6
with: with:
repository: "coder/code-server" repository: "coder/code-server"
tag: ${{ github.event.inputs.version || github.ref_name }} tag: ${{ github.event.inputs.version || github.ref_name }}
@@ -70,7 +64,7 @@ jobs:
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@master
- name: Checkout code-server - name: Checkout code-server
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Configure git - name: Configure git
run: | run: |
@@ -101,13 +95,13 @@ jobs:
steps: steps:
# We need to checkout code-server so we can get the version # We need to checkout code-server so we can get the version
- name: Checkout code-server - name: Checkout code-server
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
path: "./code-server" path: "./code-server"
- name: Checkout code-server-aur repo - name: Checkout code-server-aur repo
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
repository: "cdrci/code-server-aur" repository: "cdrci/code-server-aur"
token: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }} token: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
@@ -133,8 +127,6 @@ jobs:
- name: Validate package - name: Validate package
uses: hapakaien/archlinux-package-action@v2 uses: hapakaien/archlinux-package-action@v2
env:
VERSION: ${{ env.VERSION }}
with: with:
pkgver: ${{ env.VERSION }} pkgver: ${{ env.VERSION }}
updpkgsums: true updpkgsums: true
@@ -143,25 +135,23 @@ jobs:
- name: Open PR - name: Open PR
# We need to git push -u otherwise gh will prompt # We need to git push -u otherwise gh will prompt
# asking where to push the branch. # asking where to push the branch.
env:
VERSION: ${{ env.VERSION }}
run: | run: |
git checkout -b update-version-${{ env.VERSION }} git checkout -b update-version-${{ steps.version.outputs.version }}
git add . git add .
git commit -m "chore: updating version to ${{ env.VERSION }}" git commit -m "chore: updating version to ${{ steps.version.outputs.version }}"
git push -u origin $(git branch --show) git push -u origin $(git branch --show)
gh pr create --repo coder/code-server-aur --title "chore: bump version to ${{ env.VERSION }}" --body "PR opened by @$GITHUB_ACTOR" --assignee $GITHUB_ACTOR gh pr create --repo coder/code-server-aur --title "chore: bump version to ${{ steps.version.outputs.version }}" --body "PR opened by @$GITHUB_ACTOR" --assignee $GITHUB_ACTOR
docker: docker:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout code-server - name: Checkout code-server
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v2
@@ -184,7 +174,7 @@ jobs:
echo "VERSION=${TAG#v}" >> $GITHUB_ENV echo "VERSION=${TAG#v}" >> $GITHUB_ENV
- name: Download release artifacts - name: Download release artifacts
uses: robinraju/release-downloader@v1.8 uses: robinraju/release-downloader@v1.6
with: with:
repository: "coder/code-server" repository: "coder/code-server"
tag: v${{ env.VERSION }} tag: v${{ env.VERSION }}
@@ -192,7 +182,7 @@ jobs:
out-file-path: "release-packages" out-file-path: "release-packages"
- name: Publish to Docker - name: Publish to Docker
run: ./ci/steps/docker-buildx-push.sh run: yarn publish:docker
env: env:
VERSION: ${{ env.VERSION }} VERSION: ${{ env.VERSION }}
GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }}

View File

@@ -27,30 +27,28 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 15
needs: npm-version needs: npm-version
container: "centos:8" container: "centos:7"
env: env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Install Node.js v18 - name: Install Node.js v16
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: "18.15.0" node-version: "16"
- name: Install development tools - name: Install development tools
run: | run: |
cd /etc/yum.repos.d/ yum install -y epel-release centos-release-scl make
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* yum install -y devtoolset-9-{make,gcc,gcc-c++} jq rsync python3
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 - name: Install nfpm and envsubst
run: | run: |
mkdir -p ~/.local/bin mkdir -p ~/.local/bin
curl -sSfL https://github.com/goreleaser/nfpm/releases/download/v2.22.2/nfpm_2.22.2_`uname -s`_`uname -m`.tar.gz | tar -C ~/.local/bin -zxv nfpm 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
curl -sSfL https://github.com/a8m/envsubst/releases/download/v1.1.0/envsubst-`uname -s`-`uname -m` -o envsubst curl -sSfL https://github.com/a8m/envsubst/releases/download/v1.1.0/envsubst-`uname -s`-`uname -m` -o envsubst
chmod +x envsubst chmod +x envsubst
mv envsubst ~/.local/bin mv envsubst ~/.local/bin
@@ -67,10 +65,22 @@ jobs:
- name: Decompress npm package - name: Decompress npm package
run: tar -xzf package.tar.gz 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 - name: Build standalone release
run: npm run release:standalone run: source scl_source enable devtoolset-9 && yarn release:standalone
- name: Fetch dependencies from cache
id: cache-node-modules
uses: actions/cache@v3
with:
path: "**/node_modules"
key: yarn-build-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
yarn-build-
- name: Install test dependencies - name: Install test dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: SKIP_SUBMODULE_DEPS=1 yarn --frozen-lockfile run: SKIP_SUBMODULE_DEPS=1 yarn --frozen-lockfile
- name: Run integration tests on standalone release - name: Run integration tests on standalone release
@@ -100,60 +110,54 @@ jobs:
discussion_category_name: "📣 Announcements" discussion_category_name: "📣 Announcements"
files: ./release-packages/* 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 16.04 until GitHub deprecated it on September 20, 2021
# See here: https://github.com/actions/virtual-environments/pull/3862/files
package-linux-cross: package-linux-cross:
name: Linux cross-compile builds name: Linux cross-compile builds
runs-on: ubuntu-latest runs-on: ubuntu-18.04
timeout-minutes: 15 timeout-minutes: 15
needs: npm-version needs: npm-version
container: "debian:buster"
strategy: strategy:
matrix: matrix:
include: include:
- prefix: aarch64-linux-gnu - prefix: aarch64-linux-gnu
npm_arch: arm64 arch: arm64
apt_arch: arm64
- prefix: arm-linux-gnueabihf - prefix: arm-linux-gnueabihf
npm_arch: armv7l arch: armv7l
apt_arch: armhf
env: env:
AR: ${{ format('{0}-ar', matrix.prefix) }} AR: ${{ format('{0}-ar', matrix.prefix) }}
AS: ${{ format('{0}-as', matrix.prefix) }}
CC: ${{ format('{0}-gcc', matrix.prefix) }} CC: ${{ format('{0}-gcc', matrix.prefix) }}
CPP: ${{ format('{0}-cpp', matrix.prefix) }}
CXX: ${{ format('{0}-g++', matrix.prefix) }} CXX: ${{ format('{0}-g++', matrix.prefix) }}
FC: ${{ format('{0}-gfortran', matrix.prefix) }} LINK: ${{ format('{0}-g++', matrix.prefix) }}
LD: ${{ format('{0}-ld', matrix.prefix) }} NPM_CONFIG_ARCH: ${{ matrix.arch }}
STRIP: ${{ format('{0}-strip', matrix.prefix) }} NODE_VERSION: v16.13.0
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: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Install Node.js v18 - name: Install Node.js v16
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: "18.15.0" node-version: "16"
- 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 - name: Install nfpm
run: | run: |
@@ -161,6 +165,11 @@ 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 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 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 - name: Download npm package
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
@@ -170,12 +179,12 @@ jobs:
run: tar -xzf package.tar.gz run: tar -xzf package.tar.gz
- name: Build standalone release - name: Build standalone release
run: npm run release:standalone run: yarn release:standalone
- name: Replace node with cross-compile equivalent - name: Replace node with cross-compile equivalent
run: | run: |
wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-${npm_config_arch}.tar.xz 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 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 mv ./node ./release-standalone/lib/node
# NOTE@jsjoeio - we do this so we can strip out the v # NOTE@jsjoeio - we do this so we can strip out the v
@@ -188,7 +197,7 @@ jobs:
- name: Build packages with nfpm - name: Build packages with nfpm
env: env:
VERSION: ${{ env.VERSION }} VERSION: ${{ env.VERSION }}
run: npm run package ${npm_config_arch} run: yarn package ${NPM_CONFIG_ARCH}
- uses: softprops/action-gh-release@v1 - uses: softprops/action-gh-release@v1
with: with:
@@ -203,12 +212,12 @@ jobs:
needs: npm-version needs: npm-version
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Install Node.js v18 - name: Install Node.js v16
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: "18.15.0" node-version: "16"
- name: Install nfpm - name: Install nfpm
run: | run: |
@@ -225,9 +234,19 @@ jobs:
run: tar -xzf package.tar.gz run: tar -xzf package.tar.gz
- name: Build standalone release - name: Build standalone release
run: npm run release:standalone run: yarn release:standalone
- name: Fetch dependencies from cache
id: cache-node-modules
uses: actions/cache@v3
with:
path: "**/node_modules"
key: yarn-build-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
yarn-build-
- name: Install test dependencies - name: Install test dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: SKIP_SUBMODULE_DEPS=1 yarn install run: SKIP_SUBMODULE_DEPS=1 yarn install
- name: Run native module tests on standalone release - name: Run native module tests on standalone release
@@ -280,9 +299,8 @@ jobs:
branch: ${{ github.ref }} branch: ${{ github.ref }}
workflow: build.yaml workflow: build.yaml
workflow_conclusion: completed workflow_conclusion: completed
check_artifacts: true
name: npm-package name: npm-package
check_artifacts: false
if_no_artifact_found: fail
- name: Decompress npm package - name: Decompress npm package
run: tar -xzf package.tar.gz run: tar -xzf package.tar.gz
@@ -302,8 +320,8 @@ jobs:
npm version --prefix release "$VERSION" npm version --prefix release "$VERSION"
echo "Updating version in lib/vscode/product.json" 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 jq '.codeServerVersion = "$VERSION"' release/lib/vscode/product.json > "$tmp" && mv "$tmp" release/lib/vscode/product.json
# Ensure it has the same permissions as before # Ensure it has the same permissions as before
chmod 644 release/lib/vscode/product.json chmod 644 release/lib/vscode/product.json

View File

@@ -41,7 +41,7 @@ jobs:
container: "alpine:3.17" container: "alpine:3.17"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Install test utilities - name: Install test utilities
run: apk add bats checkbashisms run: apk add bats checkbashisms
@@ -58,7 +58,7 @@ jobs:
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Install lint utilities - name: Install lint utilities
run: sudo apt install shellcheck run: sudo apt install shellcheck

View File

@@ -12,8 +12,9 @@ on:
# Runs every Monday morning PST # Runs every Monday morning PST
- cron: "17 15 * * 1" - cron: "17 15 * * 1"
# Cancel in-progress runs for pull requests when developers push additional # Cancel in-progress runs for pull requests when developers push
# changes, and serialize builds in branches. # additional changes, and serialize builds in branches.
# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-concurrency-to-cancel-any-in-progress-job-or-run
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }} cancel-in-progress: ${{ github.event_name == 'pull_request' }}
@@ -25,21 +26,30 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install Node.js v18 - name: Install Node.js v16
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: "18" node-version: "16"
- name: Audit yarn for vulnerabilities - name: Fetch dependencies from cache
run: yarn audit id: cache-yarn
if: success() uses: actions/cache@v3
with:
path: "**/node_modules"
key: yarn-build-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
yarn-build-
- name: Audit npm for vulnerabilities - name: Install dependencies
run: npm shrinkwrap && npm audit if: steps.cache-yarn.outputs.cache-hit != 'true'
run: SKIP_SUBMODULE_DEPS=1 yarn --frozen-lockfile
- name: Audit for vulnerabilities
run: yarn _audit
if: success() if: success()
trivy-scan-repo: trivy-scan-repo:
@@ -50,12 +60,12 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Run Trivy vulnerability scanner in repo mode - name: Run Trivy vulnerability scanner in repo mode
uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f uses: aquasecurity/trivy-action@9ab158e8597f3b310480b9a69402b419bc03dbd5
with: with:
scan-type: "fs" scan-type: "fs"
scan-ref: "." scan-ref: "."
@@ -80,7 +90,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL

View File

@@ -48,10 +48,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Run Trivy vulnerability scanner in image mode - name: Run Trivy vulnerability scanner in image mode
uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f uses: aquasecurity/trivy-action@9ab158e8597f3b310480b9a69402b419bc03dbd5
with: with:
image-ref: "docker.io/codercom/code-server:latest" image-ref: "docker.io/codercom/code-server:latest"
ignore-unfixed: true ignore-unfixed: true

View File

@@ -1 +1 @@
18 16

View File

@@ -20,207 +20,14 @@ Code v99.99.999
--> -->
## Unreleased ## [4.9.0](https://github.com/coder/code-server/releases/tag/v4.9.0) - 2022-11-14
## [4.17.1](https://github.com/coder/code-server/releases/tag/v4.17.1) - 2023-09-29 Code v1.73.0
Code v1.82.2 WIP
### Fixed known issues: https://github.com/adobe/fetch/pull/318#issuecomment-1306070259
cert won't work for anyone using Ubuntu 22.04
- Make secret storage persistent. For example, logging in with GitHub should
persist between browser refreshes and code-server restarts.
- Issues with argon2 on arm builds should be fixed now.
## [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
### Changed
- Updated to Code 1.76.1
## [4.10.1](https://github.com/coder/code-server/releases/tag/v4.10.1) - 2023-03-04
Code v1.75.1
### Security
Added an origin check to web sockets to prevent cross-site hijacking attacks on
users using older or niche browser that do not support SameSite cookies and
attacks across sub-domains that share the same root domain.
The check requires the host header to be set so if you use a reverse proxy
ensure it forwards that information otherwise web sockets will be blocked.
## [4.10.0](https://github.com/coder/code-server/releases/tag/v4.10.0) - 2023-02-15
Code v1.75.1
### Changed
- Updated to Code 1.75.1
### Removed
- Removed `--link` (was deprecated over thirteen months ago in 4.0.1).
## [4.9.1](https://github.com/coder/code-server/releases/tag/v4.9.1) - 2022-12-15
Code v1.73.1
### Changed
- Updated a couple steps in the build and release process to ensure we're using
`npm` and `yarn` consistently depending on the step.
### Fixed
- Fixed an issue with code-server version not displaying in the Help > About window.
- Fixed terminal not loading on macOS clients.
## [4.9.0](https://github.com/coder/code-server/releases/tag/v4.9.0) - 2022-12-06
Code v1.73.1
### Changed
- Upgraded to Code 1.73.1
### Added
- `/security.txt` added as a route with info on our security policy information thanks to @ghuntley
### Fixed
- Installing on majaro images should now work thanks to @MrPeacockNLB for
adding the `--noconfirm` flag in `install.sh`
### Known Issues
- `--cert` on Ubuntu 22.04: OpenSSL v3 is used which breaks `pem` meaning the
`--cert` feature will not work. [Reference](https://github.com/adobe/fetch/pull/318#issuecomment-1306070259)
## [4.8.3](https://github.com/coder/code-server/releases/tag/v4.8.3) - 2022-11-07 ## [4.8.3](https://github.com/coder/code-server/releases/tag/v4.8.3) - 2022-11-07
@@ -308,7 +115,7 @@ Code v1.71.0
### Fixed ### Fixed
- Add flags --unsafe-perm --legacy-peer-deps in `npm-postinstall.sh` which ensures installing with npm works correctly - Add flags --unsafe-perm --legacy-peer-deps in `npm-postinstsall.sh` which ensures installing with npm works correctly
## [4.6.1](https://github.com/coder/code-server/releases/tag/v4.6.1) - 2022-09-31 ## [4.6.1](https://github.com/coder/code-server/releases/tag/v4.6.1) - 2022-09-31

View File

@@ -29,7 +29,7 @@ This directory contains scripts used for the development of code-server.
- [./ci/dev/watch.ts](./dev/watch.ts) (`yarn watch`) - [./ci/dev/watch.ts](./dev/watch.ts) (`yarn watch`)
- Starts a process to build and launch code-server and restart on any code changes. - Starts a process to build and launch code-server and restart on any code changes.
- Example usage in [./docs/CONTRIBUTING.md](../docs/CONTRIBUTING.md). - Example usage in [./docs/CONTRIBUTING.md](../docs/CONTRIBUTING.md).
- [./ci/dev/gen_icons.sh](./dev/gen_icons.sh) (`yarn icons`) - [./ci/dev/gen_icons.sh](./ci/dev/gen_icons.sh) (`yarn icons`)
- Generates the various icons from a single `.svg` favicon in - Generates the various icons from a single `.svg` favicon in
`src/browser/media/favicon.svg`. `src/browser/media/favicon.svg`.
- Requires [imagemagick](https://imagemagick.org/index.php) - 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. This directory contains the release docker container image.
- [./ci/steps/build-docker-buildx-push.sh](./steps/docker-buildx-push.sh) - [./ci/steps/build-docker-buildx-push.sh](./ci/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. - 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`. - Assumes debian releases are ready in `./release-packages`.

View File

@@ -14,6 +14,22 @@ main() {
sed -i.bak "1s;^;#!/usr/bin/env node\n;" out/node/entry.js && rm out/node/entry.js.bak sed -i.bak "1s;^;#!/usr/bin/env node\n;" out/node/entry.js && rm out/node/entry.js.bak
chmod +x out/node/entry.js chmod +x out/node/entry.js
fi fi
# for arch; we do not use OS from lib.sh and get our own.
# lib.sh normalizes macos to darwin - but cloud-agent's binaries do not
source ./ci/lib.sh
OS="$(uname | tr '[:upper:]' '[:lower:]')"
mkdir -p ./lib
if ! [ -f ./lib/coder-cloud-agent ]; then
echo "Downloading the cloud agent..."
set +e
curl -fsSL "https://github.com/coder/cloud-agent/releases/latest/download/cloud-agent-$OS-$ARCH" -o ./lib/coder-cloud-agent
chmod +x ./lib/coder-cloud-agent
set -e
fi
} }
main "$@" main "$@"

View File

@@ -27,7 +27,7 @@ main() {
release_archive() { release_archive() {
local release_name="code-server-$VERSION-$OS-$ARCH" local release_name="code-server-$VERSION-$OS-$ARCH"
if [[ $OS == "linux" ]]; then if [[ $OS == "linux" ]]; then
tar -czf "release-packages/$release_name.tar.gz" --owner=0 --group=0 --transform "s/^\.\/release-standalone/$release_name/" ./release-standalone tar -czf "release-packages/$release_name.tar.gz" --transform "s/^\.\/release-standalone/$release_name/" ./release-standalone
else else
tar -czf "release-packages/$release_name.tar.gz" -s "/^release-standalone/$release_name/" release-standalone tar -czf "release-packages/$release_name.tar.gz" -s "/^release-standalone/$release_name/" release-standalone
fi fi

View File

@@ -56,12 +56,15 @@ bundle_code_server() {
} }
EOF EOF
) > "$RELEASE_PATH/package.json" ) > "$RELEASE_PATH/package.json"
rsync yarn.lock "$RELEASE_PATH"
mv npm-shrinkwrap.json "$RELEASE_PATH" mv npm-shrinkwrap.json "$RELEASE_PATH"
rsync ci/build/npm-postinstall.sh "$RELEASE_PATH/postinstall.sh" rsync ci/build/npm-postinstall.sh "$RELEASE_PATH/postinstall.sh"
if [ "$KEEP_MODULES" = 1 ]; then if [ "$KEEP_MODULES" = 1 ]; then
rsync node_modules/ "$RELEASE_PATH/node_modules" rsync node_modules/ "$RELEASE_PATH/node_modules"
mkdir -p "$RELEASE_PATH/lib"
rsync ./lib/coder-cloud-agent "$RELEASE_PATH/lib"
fi fi
} }
@@ -94,10 +97,12 @@ bundle_vscode() {
"$VSCODE_SRC_PATH/remote/package.json" \ "$VSCODE_SRC_PATH/remote/package.json" \
"$VSCODE_SRC_PATH/package.json" > "$VSCODE_OUT_PATH/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" mv "$VSCODE_SRC_PATH/remote/npm-shrinkwrap.json" "$VSCODE_OUT_PATH/npm-shrinkwrap.json"
# Include global extension dependencies as well. # Include global extension dependencies as well.
rsync "$VSCODE_SRC_PATH/extensions/package.json" "$VSCODE_OUT_PATH/extensions/package.json" 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" 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" rsync "$VSCODE_SRC_PATH/extensions/postinstall.mjs" "$VSCODE_OUT_PATH/extensions/postinstall.mjs"
} }

View File

@@ -1,6 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail 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() { main() {
cd "$(dirname "${0}")/../.." cd "$(dirname "${0}")/../.."
@@ -9,19 +13,17 @@ main() {
rsync "$RELEASE_PATH/" "$RELEASE_PATH-standalone" rsync "$RELEASE_PATH/" "$RELEASE_PATH-standalone"
RELEASE_PATH+=-standalone RELEASE_PATH+=-standalone
# We cannot get the path to Node from $PATH (for example via `which node`) # We cannot find the path to node from $PATH because yarn shims a script to ensure
# because Yarn shims a script called `node` and we would end up just copying # we use the same version it's using so we instead run a script with yarn that
# that script. Instead we run Node and have it print its actual path. # will print the path to node.
local node_path local node_path
node_path="$(node <<< 'console.info(process.execPath)')" node_path="$(yarn -s node <<< 'console.info(process.execPath)')"
mkdir -p "$RELEASE_PATH/bin" mkdir -p "$RELEASE_PATH/bin"
mkdir -p "$RELEASE_PATH/lib" mkdir -p "$RELEASE_PATH/lib"
rsync ./ci/build/code-server.sh "$RELEASE_PATH/bin/code-server" rsync ./ci/build/code-server.sh "$RELEASE_PATH/bin/code-server"
rsync "$node_path" "$RELEASE_PATH/lib/node" rsync "$node_path" "$RELEASE_PATH/lib/node"
chmod 755 "$RELEASE_PATH/lib/node"
pushd "$RELEASE_PATH" pushd "$RELEASE_PATH"
npm install --unsafe-perm --omit=dev npm install --unsafe-perm --omit=dev
popd popd

View File

@@ -15,7 +15,7 @@ copy-bin-script() {
local dest="lib/vscode-reh-web-linux-x64/bin/$script" local dest="lib/vscode-reh-web-linux-x64/bin/$script"
cp "lib/vscode/resources/server/bin/$script" "$dest" cp "lib/vscode/resources/server/bin/$script" "$dest"
sed -i.bak "s/@@VERSION@@/$(vscode_version)/g" "$dest" sed -i.bak "s/@@VERSION@@/$(vscode_version)/g" "$dest"
sed -i.bak "s/@@COMMIT@@/$BUILD_SOURCEVERSION/g" "$dest" sed -i.bak "s/@@COMMIT@@/$VSCODE_DISTRO_COMMIT/g" "$dest"
sed -i.bak "s/@@APPNAME@@/code-server/g" "$dest" sed -i.bak "s/@@APPNAME@@/code-server/g" "$dest"
# Fix Node path on Darwin and Linux. # Fix Node path on Darwin and Linux.
@@ -40,16 +40,6 @@ main() {
source ./ci/lib.sh source ./ci/lib.sh
# 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.
#
# Also, we use code-server's commit rather than VS Code's otherwise it would
# not update when only our patch files change, and that will cause caching
# issues where the browser keeps using outdated code.
export BUILD_SOURCEVERSION
BUILD_SOURCEVERSION=$(git rev-parse HEAD)
pushd lib/vscode pushd lib/vscode
if [[ ! ${VERSION-} ]]; then if [[ ! ${VERSION-} ]]; then
@@ -58,11 +48,14 @@ main() {
exit 1 exit 1
fi fi
# Add the date, our name, links, enable telemetry (this just makes telemetry # Set the commit Code will embed into the product.json. We need to do this
# available; telemetry can still be disabled by flag or setting), and # since Code tries to get the commit from the `.git` directory which will fail
# configure trusted extensions (since some, like github.copilot-chat, never # as it is a submodule.
# ask to be trusted and this is the only way to get auth working). export VSCODE_DISTRO_COMMIT
# VSCODE_DISTRO_COMMIT=$(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).
# This needs to be done before building as Code will read this file and embed # This needs to be done before building as Code will read this file and embed
# it into the client-side code. # it into the client-side code.
git checkout product.json # Reset in case the script exited early. git checkout product.json # Reset in case the script exited early.
@@ -96,11 +89,6 @@ main() {
"linkProtectionTrustedDomains": [ "linkProtectionTrustedDomains": [
"https://open-vsx.org" "https://open-vsx.org"
], ],
"trustedExtensionAuthAccess": [
"vscode.git", "vscode.github",
"github.vscode-pull-request-github",
"github.copilot", "github.copilot-chat"
],
"aiConfig": { "aiConfig": {
"ariaKey": "code-server" "ariaKey": "code-server"
} }
@@ -121,15 +109,6 @@ EOF
popd 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 # These provide a `code-server` command in the integrated terminal to open
# files in the current instance. # files in the current instance.
delete-bin-script remote-cli/code-server delete-bin-script remote-cli/code-server

View File

@@ -22,4 +22,4 @@ contents:
dst: /usr/lib/systemd/user/code-server.service dst: /usr/lib/systemd/user/code-server.service
- src: ./release-standalone/* - src: ./release-standalone/*
dst: /usr/lib/code-server dst: /usr/lib/code-server/

View File

@@ -1,8 +1,18 @@
#!/usr/bin/env sh #!/usr/bin/env sh
set -eu set -eu
# Copied from ../lib.sh except we do not rename Darwin and we do not need to # Copied from ../lib.sh.
# detect Alpine. arch() {
cpu="$(uname -m)"
case "$cpu" in
aarch64) cpu=arm64 ;;
x86_64) cpu=amd64 ;;
esac
echo "$cpu"
}
# Copied from ../lib.sh except we do not rename Darwin since the cloud agent
# uses "darwin" in the release names and we do not need to detect Alpine.
os() { os() {
osname=$(uname | tr '[:upper:]' '[:lower:]') osname=$(uname | tr '[:upper:]' '[:lower:]')
case $osname in case $osname in
@@ -51,8 +61,13 @@ symlink_bin_script() {
cd "$oldpwd" cd "$oldpwd"
} }
ARCH="${NPM_CONFIG_ARCH:-$(arch)}"
OS="$(os)" 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() { main() {
# Grabs the major version of node from $npm_config_user_agent which looks like # 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 # yarn/1.21.1 npm/? node/v14.2.0 darwin x64
@@ -64,8 +79,8 @@ main() {
echo "USE AT YOUR OWN RISK!" echo "USE AT YOUR OWN RISK!"
fi fi
if [ "$major_node_version" -ne "${FORCE_NODE_VERSION:-18}" ]; then if [ "$major_node_version" -ne "${FORCE_NODE_VERSION:-16}" ]; then
echo "ERROR: code-server currently requires node v18." echo "ERROR: code-server currently requires node v16."
if [ -n "$FORCE_NODE_VERSION" ]; then if [ -n "$FORCE_NODE_VERSION" ]; then
echo "However, you have overrided the version check to use v$FORCE_NODE_VERSION." echo "However, you have overrided the version check to use v$FORCE_NODE_VERSION."
fi fi
@@ -87,6 +102,14 @@ main() {
;; ;;
esac esac
mkdir -p ./lib
if curl -fsSL "https://github.com/coder/cloud-agent/releases/latest/download/cloud-agent-$OS-$ARCH" -o ./lib/coder-cloud-agent; then
chmod +x ./lib/coder-cloud-agent
else
echo "Failed to download cloud agent; --link will not work"
fi
if ! vscode_install; then if ! vscode_install; then
echo "You may not have the required dependencies to build the native modules." echo "You may not have the required dependencies to build the native modules."
echo "Please see https://github.com/coder/code-server/blob/main/docs/npm.md" echo "Please see https://github.com/coder/code-server/blob/main/docs/npm.md"
@@ -101,21 +124,26 @@ main() {
} }
install_with_yarn_or_npm() { install_with_yarn_or_npm() {
echo "User agent: ${npm_config_user_agent-none}"
# NOTE@edvincent: We want to keep using the package manager that the end-user was using to install the package. # NOTE@edvincent: We want to keep using the package manager that the end-user was using to install the package.
# This also ensures that when *we* run `yarn` in the development process, the yarn.lock file is used. # This also ensures that when *we* run `yarn` in the development process, the yarn.lock file is used.
case "${npm_config_user_agent-}" in case "${npm_config_user_agent-}" in
npm*) yarn*)
# HACK: NPM's use of semver doesn't like resolving some peerDependencies that vscode (upstream) brings in the form of pre-releases. if [ -f "yarn.lock" ]; then
# The legacy behavior doesn't complain about pre-releases being used, falling back to that for now. yarn --production --frozen-lockfile --no-default-rc
# See https://github.com//pull/5071 else
if ! npm install --unsafe-perm --legacy-peer-deps --omit=dev; then echo "yarn.lock file not present, not running in development mode. use npm to install code-server!"
return 1 exit 1
fi fi
;; ;;
yarn*) npm*)
if ! yarn --production --frozen-lockfile --no-default-rc; then if [ -f "yarn.lock" ]; then
return 1 echo "yarn.lock file present, running in development mode. use yarn to install code-server!"
exit 1
else
# 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
fi fi
;; ;;
*) *)
@@ -123,26 +151,19 @@ install_with_yarn_or_npm() {
exit 1 exit 1
;; ;;
esac esac
return 0
} }
vscode_install() { vscode_install() {
echo 'Installing Code dependencies...' echo 'Installing Code dependencies...'
cd lib/vscode cd lib/vscode
if ! install_with_yarn_or_npm; then install_with_yarn_or_npm
return 1
fi
symlink_asar symlink_asar
symlink_bin_script remote-cli code code-server symlink_bin_script remote-cli code code-server
symlink_bin_script helpers browser browser .sh symlink_bin_script helpers browser browser .sh
cd extensions cd extensions
if ! install_with_yarn_or_npm; then install_with_yarn_or_npm
return 1
fi
return 0
} }
main "$@" main "$@"

View File

@@ -11,6 +11,22 @@ main() {
make -s out/index.js make -s out/index.js
popd popd
# Our code imports from `out` in order to work during development but if you
# have only built for production you will have not have this directory. In
# that case symlink `out` to a production build directory.
if [[ ! -e lib/vscode/out ]]; then
pushd lib
local out=(vscode-reh-web-*)
if [[ -d "${out[0]}" ]]; then
ln -s "../${out[0]}/out" ./vscode/out
else
echo "Could not find lib/vscode/out or lib/vscode-reh-web-*"
echo "Code must be built before running unit tests"
exit 1
fi
popd
fi
# We must keep jest in a sub-directory. See ../../test/package.json for more # We must keep jest in a sub-directory. See ../../test/package.json for more
# information. We must also run it from the root otherwise coverage will not # information. We must also run it from the root otherwise coverage will not
# include our source files. # include our source files.

View File

@@ -1,4 +1,4 @@
import { spawn, ChildProcess } from "child_process" import { spawn, fork, ChildProcess } from "child_process"
import * as path from "path" import * as path from "path"
import { onLine, OnLineCallback } from "../../src/node/util" import { onLine, OnLineCallback } from "../../src/node/util"
@@ -30,13 +30,12 @@ class Watcher {
// Pass CLI args, save for `node` and the initial script name. // Pass CLI args, save for `node` and the initial script name.
const args = process.argv.slice(2) const args = process.argv.slice(2)
this.webServer = spawn("node", [path.join(this.rootPath, "out/node/entry.js"), ...args]) this.webServer = fork(path.join(this.rootPath, "out/node/entry.js"), args)
onLine(this.webServer, (line) => console.log("[code-server]", line))
const { pid } = this.webServer const { pid } = this.webServer
this.webServer.on("exit", () => console.log("[code-server]", `Web process ${pid} exited`)) this.webServer.on("exit", () => console.log("[Code Server]", `Web process ${pid} exited`))
console.log("\n[code-server]", `Spawned web server process ${pid}`) console.log("\n[Code Server]", `Spawned web server process ${pid}`)
} }
//#endregion //#endregion
@@ -83,10 +82,10 @@ class Watcher {
private parseVSCodeLine: OnLineCallback = (strippedLine, originalLine) => { private parseVSCodeLine: OnLineCallback = (strippedLine, originalLine) => {
if (!strippedLine.length) return if (!strippedLine.length) return
console.log("[Code OSS]", originalLine) console.log("[VS Code]", originalLine)
if (strippedLine.includes("Finished compilation with")) { if (strippedLine.includes("Finished compilation with")) {
console.log("[Code OSS] ✨ Finished compiling! ✨", "(Refresh your web browser ♻️)") console.log("[VS Code] ✨ Finished compiling! ✨", "(Refresh your web browser ♻️)")
this.reloadWebServer() this.reloadWebServer()
} }
} }
@@ -94,10 +93,10 @@ class Watcher {
private parseCodeServerLine: OnLineCallback = (strippedLine, originalLine) => { private parseCodeServerLine: OnLineCallback = (strippedLine, originalLine) => {
if (!strippedLine.length) return if (!strippedLine.length) return
console.log("[Compiler][code-server]", originalLine) console.log("[Compiler][Code Server]", originalLine)
if (strippedLine.includes("Watching for file changes")) { if (strippedLine.includes("Watching for file changes")) {
console.log("[Compiler][code-server]", "Finished compiling!", "(Refresh your web browser ♻️)") console.log("[Compiler][Code Server]", "Finished compiling!", "(Refresh your web browser ♻️)")
this.reloadWebServer() this.reloadWebServer()
} }
} }

View File

@@ -15,9 +15,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # 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. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 3.13.1 version: 3.3.3
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using. # follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 4.17.1 appVersion: 4.8.3

View File

@@ -20,9 +20,6 @@ spec:
labels: labels:
app.kubernetes.io/name: {{ include "code-server.name" . }} app.kubernetes.io/name: {{ include "code-server.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Values.podAnnotations }}
annotations: {{- toYaml .Values.podAnnotations | nindent 8 }}
{{- end }}
spec: spec:
imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }} imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }}
{{- if .Values.hostnameOverride }} {{- if .Values.hostnameOverride }}
@@ -105,7 +102,6 @@ spec:
{{- range .Values.extraSecretMounts }} {{- range .Values.extraSecretMounts }}
- name: {{ .name }} - name: {{ .name }}
mountPath: {{ .mountPath }} mountPath: {{ .mountPath }}
subPath: {{ .subPath | default "" }}
readOnly: {{ .readOnly }} readOnly: {{ .readOnly }}
{{- end }} {{- end }}
{{- range .Values.extraVolumeMounts }} {{- range .Values.extraVolumeMounts }}
@@ -118,11 +114,6 @@ spec:
- name: http - name: http
containerPort: 8080 containerPort: 8080
protocol: TCP protocol: TCP
{{- range .Values.extraPorts }}
- name: {{ .name }}
containerPort: {{ .port }}
protocol: {{ .protocol }}
{{- end }}
livenessProbe: livenessProbe:
httpGet: httpGet:
path: / path: /

View File

@@ -1,4 +1,3 @@
{{- if not .Values.existingSecret }}
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
@@ -12,9 +11,8 @@ metadata:
app.kubernetes.io/managed-by: {{ .Release.Service }} app.kubernetes.io/managed-by: {{ .Release.Service }}
type: Opaque type: Opaque
data: data:
{{- if .Values.password }} {{ if .Values.password }}
password: "{{ .Values.password | b64enc }}" password: "{{ .Values.password | b64enc }}"
{{- else }} {{ else }}
password: "{{ randAlphaNum 24 | b64enc }}" password: "{{ randAlphaNum 24 | b64enc }}"
{{- end }} {{ end }}
{{- end }}

View File

@@ -14,12 +14,6 @@ spec:
targetPort: http targetPort: http
protocol: TCP protocol: TCP
name: http name: http
{{- range .Values.extraPorts }}
- port: {{ .port }}
targetPort: {{ .port }}
protocol: {{ .protocol }}
name: {{ .name }}
{{- end }}
selector: selector:
app.kubernetes.io/name: {{ include "code-server.name" . }} app.kubernetes.io/name: {{ include "code-server.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}

View File

@@ -6,7 +6,7 @@ replicaCount: 1
image: image:
repository: codercom/code-server repository: codercom/code-server
tag: '4.17.1' tag: '4.8.3'
pullPolicy: Always pullPolicy: Always
# Specifies one or more secrets to be used when pulling images from a # Specifies one or more secrets to be used when pulling images from a
@@ -179,7 +179,6 @@ extraInitContainers: |
extraSecretMounts: [] extraSecretMounts: []
# - name: secret-files # - name: secret-files
# mountPath: /etc/secrets # mountPath: /etc/secrets
# subPath: private.key # (optional)
# secretName: code-server-secret-files # secretName: code-server-secret-files
# readOnly: true # readOnly: true
@@ -197,8 +196,3 @@ extraConfigmapMounts: []
# subPath: certificates.crt # (optional) # subPath: certificates.crt # (optional)
# configMap: certs-configmap # configMap: certs-configmap
# readOnly: true # readOnly: true
extraPorts: []
# - name: minecraft
# port: 25565
# protocol: tcp

View File

@@ -11,10 +11,9 @@
- [Version updates to Code](#version-updates-to-code) - [Version updates to Code](#version-updates-to-code)
- [Patching Code](#patching-code) - [Patching Code](#patching-code)
- [Build](#build) - [Build](#build)
- [Creating a Standalone Release](#creating-a-standalone-release)
- [Troubleshooting](#troubleshooting) - [Troubleshooting](#troubleshooting)
- [I see "Forbidden access" when I load code-server in the browser](#i-see-forbidden-access-when-i-load-code-server-in-the-browser) - [I see "Forbidden access" when I load code-server in the browser](#i-see-forbidden-access-when-i-load-code-server-in-the-browser)
- ["Can only have one anonymous define call per script"](#can-only-have-one-anonymous-define-call-per-script) - ["Can only have one anonymous define call per script"](#can-only-have-one-anonymous-define-call-per-script)
- [Help](#help) - [Help](#help)
- [Test](#test) - [Test](#test)
- [Unit tests](#unit-tests) - [Unit tests](#unit-tests)
@@ -37,7 +36,7 @@ for [VS
Code](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites). Code](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites).
Here is what is needed: Here is what is needed:
- `node` v18.x - `node` v16.x
- `git` v2.x or greater - `git` v2.x or greater
- [`git-lfs`](https://git-lfs.github.com) - [`git-lfs`](https://git-lfs.github.com)
- [`yarn`](https://classic.yarnpkg.com/en/) - [`yarn`](https://classic.yarnpkg.com/en/)
@@ -63,7 +62,7 @@ Here is what is needed:
If you're developing code-server on Linux, make sure you have installed or install the following dependencies: If you're developing code-server on Linux, make sure you have installed or install the following dependencies:
```shell ```shell
sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev python-is-python3 sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-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. These are required by Code. See [their Wiki](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites) for more information.
@@ -171,22 +170,6 @@ yarn package
> If you need your builds to support older distros, run the build commands > If you need your builds to support older distros, run the build commands
> inside a Docker container with all the build requirements installed. > inside a Docker container with all the build requirements installed.
#### Creating a Standalone Release
Part of the build process involves creating standalone releases. At the time of
writing, we do this for the following platforms/architectures:
- Linux amd64 (.tar.gz, .deb, and .rpm)
- Linux arm64 (.tar.gz, .deb, and .rpm)
- Linux arm7l (.tar.gz)
- Linux armhf.deb
- Linux armhf.rpm
- macOS amd64 (Intel-based)
Currently, these are compiled in CI using the `yarn release-standalone` command
in the `release.yaml` workflow. We then upload them to the draft release and
distribute via GitHub Releases.
### Troubleshooting ### Troubleshooting
#### I see "Forbidden access" when I load code-server in the browser #### I see "Forbidden access" when I load code-server in the browser
@@ -195,7 +178,7 @@ This means your patches didn't apply correctly. We have a patch to remove the au
Try popping off the patches with `quilt pop -a` and reapplying with `quilt push -a`. Try popping off the patches with `quilt pop -a` and reapplying with `quilt push -a`.
#### "Can only have one anonymous define call per script" ### "Can only have one anonymous define call per script"
Code might be trying to use a dev or prod HTML in the wrong context. You can try re-running code-server and setting `VSCODE_DEV=1`. Code might be trying to use a dev or prod HTML in the wrong context. You can try re-running code-server and setting `VSCODE_DEV=1`.

View File

@@ -14,7 +14,6 @@
- [How do I install an extension manually?](#how-do-i-install-an-extension-manually) - [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) - [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 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 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 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) - [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)
@@ -27,7 +26,6 @@
- [Is multi-tenancy possible?](#is-multi-tenancy-possible) - [Is multi-tenancy possible?](#is-multi-tenancy-possible)
- [Can I use Docker in a code-server container?](#can-i-use-docker-in-a-code-server-container) - [Can I use Docker in a code-server container?](#can-i-use-docker-in-a-code-server-container)
- [How do I disable telemetry?](#how-do-i-disable-telemetry) - [How do I disable telemetry?](#how-do-i-disable-telemetry)
- [What's the difference between code-server and Coder?](#whats-the-difference-between-code-server-and-coder)
- [What's the difference between code-server and Theia?](#whats-the-difference-between-code-server-and-theia) - [What's the difference between code-server and Theia?](#whats-the-difference-between-code-server-and-theia)
- [What's the difference between code-server and OpenVSCode-Server?](#whats-the-difference-between-code-server-and-openvscode-server) - [What's the difference between code-server and OpenVSCode-Server?](#whats-the-difference-between-code-server-and-openvscode-server)
- [What's the difference between code-server and GitHub Codespaces?](#whats-the-difference-between-code-server-and-github-codespaces) - [What's the difference between code-server and GitHub Codespaces?](#whats-the-difference-between-code-server-and-github-codespaces)
@@ -35,8 +33,6 @@
- [Are there community projects involving code-server?](#are-there-community-projects-involving-code-server) - [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 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 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 --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
@@ -90,12 +86,6 @@ app (PWA):
1. Start the editor 1. Start the editor
2. Click the **plus** icon in the URL toolbar to install the PWA 2. Click the **plus** icon in the URL toolbar to install the PWA
If you use Firefox, you can use the appropriate extension to install PWA.
1. Go to the installation [website](https://addons.mozilla.org/en-US/firefox/addon/pwas-for-firefox/) of the add-on
2. Add the add-on to Firefox
3. Follow the os-specific instructions on how to install the runtime counterpart
For other browsers, you'll have to remap keybindings for shortcuts to work. For other browsers, you'll have to remap keybindings for shortcuts to work.
## Why can't code-server use Microsoft's extension marketplace? ## Why can't code-server use Microsoft's extension marketplace?
@@ -177,10 +167,10 @@ If you own a marketplace that implements the VS Code Extension Gallery API, you
can point code-server to it by setting `$EXTENSIONS_GALLERY`. can point code-server to it by setting `$EXTENSIONS_GALLERY`.
This corresponds directly with the `extensionsGallery` entry in in VS Code's `product.json`. This corresponds directly with the `extensionsGallery` entry in in VS Code's `product.json`.
For example: For example, to use the legacy Coder extensions marketplace:
```bash ```bash
export EXTENSIONS_GALLERY='{"serviceUrl": "https://my-extensions/api"}' export EXTENSIONS_GALLERY='{"serviceUrl": "https://extensions.coder.com/api"}'
``` ```
Though you can technically use Microsoft's marketplace in this manner, we Though you can technically use Microsoft's marketplace in this manner, we
@@ -193,20 +183,10 @@ docs](https://github.com/VSCodium/vscodium/blob/master/DOCS.md#extensions--marke
## Where are extensions stored? ## Where are extensions stored?
Extensions are stored in `~/.local/share/code-server/extensions` by default. Extensions are store, by default, to `~/.local/share/code-server/extensions`.
On Linux and macOS if you set the `XDG_DATA_HOME` environment variable, the If you set the `XDG_DATA_HOME` environment variable, the data directory will be
extensions directory will be `$XDG_DATA_HOME/code-server/extensions`. In `$XDG_DATA_HOME/code-server/extensions`. In general, we try to follow the XDG directory spec.
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? ## How can I reuse my VS Code configuration?
@@ -382,15 +362,6 @@ Use the `--disable-telemetry` flag to disable telemetry.
> We use the data collected only to improve code-server. > We use the data collected only to improve code-server.
## What's the difference between code-server and Coder?
code-server and Coder are both applications that can be installed on any
machine. The main difference is who they serve. Out of the box, code-server is
simply VS Code in the browser while Coder is a tool for provisioning remote
development environments via Terraform.
code-server was built for individuals while Coder was built for teams. In Coder, you create Workspaces which can have applications like code-server. If you're looking for a team solution, you should reach for [Coder](https://github.com/coder/coder).
## What's the difference between code-server and Theia? ## What's the difference between code-server and Theia?
At a high level, code-server is a patched fork of VS Code that runs in the At a high level, code-server is a patched fork of VS Code that runs in the
@@ -408,13 +379,19 @@ Theia doesn't allow you to reuse your existing VS Code config.
## What's the difference between code-server and OpenVSCode-Server? ## What's the difference between code-server and OpenVSCode-Server?
code-server and OpenVSCode-Server both allow you to access VS Code via a code-server and OpenVSCode-Server both allow you to access VS Code via a
browser. OpenVSCode-Server is a direct fork of VS Code with changes comitted browser. The two projects also use their own [forks of VS Code](https://github.com/coder/vscode) to
directly while code-server pulls VS Code in via a submodule and makes changes leverage modern VS Code APIs and stay up to date with the upsteam version.
via patch files.
However, OpenVSCode-Server is scoped at only making VS Code available as-is in However, OpenVSCode-Server is scoped at only making VS Code available in the web browser.
the web browser. code-server contains additional changes to make the self-hosted code-server includes some other features:
experience better (see the next section for details).
- password auth
- proxy web ports
- certificate support
- plugin API
- settings sync (coming soon)
For more details, see [this discussion post](https://github.com/coder/code-server/discussions/4267#discussioncomment-1411583).
## What's the difference between code-server and GitHub Codespaces? ## What's the difference between code-server and GitHub Codespaces?
@@ -422,24 +399,8 @@ Both code-server and GitHub Codespaces allow you to access VS Code via a
browser. GitHub Codespaces, however, is a closed-source, paid service offered by browser. GitHub Codespaces, however, is a closed-source, paid service offered by
GitHub and Microsoft. GitHub and Microsoft.
On the other hand, code-server is self-hosted, free, open-source, and can be run On the other hand, code-server is self-hosted, free, open-source, and
on any machine with few limitations. can be run on any machine with few limitations.
Specific changes include:
- Password authentication
- The ability to host at sub-paths
- Self-contained web views that do not call out to Microsoft's servers
- The ability to use your own marketplace and collect your own telemetry
- Built-in proxy for accessing ports on the remote machine integrated into
VS Code's ports panel
- Wrapper process that spawns VS Code on-demand and has a separate CLI
- Notification when updates are available
- [Some other things](https://github.com/coder/code-server/tree/main/patches)
Some of these changes appear very unlikely to ever be adopted by Microsoft.
Some may make their way upstream, further closing the gap, but at the moment it
looks like there will always be some subtle differences.
## Does code-server have any security login validation? ## Does code-server have any security login validation?
@@ -464,20 +425,3 @@ There are two ways to change the port on which code-server runs:
You can pass the flag `--disable-getting-started-override` to `code-server` or 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 you can set the environment variable `CS_DISABLE_GETTING_STARTED_OVERRIDE=1` or
`CS_DISABLE_GETTING_STARTED_OVERRIDE=true`. `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`

View File

@@ -142,28 +142,24 @@ changelog](https://github.com/emacs-mirror/emacs/blob/master/etc/NEWS).
### Publishing a release ### Publishing a release
1. Go to GitHub Actions > Draft release > Run workflow on the commit you want to 1. Go to GitHub Actions > Draft release > Run workflow off commit you want to
release. Make sure CI has finished the build workflow on that commit or this release. CI will automatically upload the artifacts to the release. Make sure CI
will fail. has finished on that commit.
2. CI will automatically grab the build artifact on that commit, inject the 1. CI will automatically grab the
version into the `package.json`, put together platform-specific packages, and artifacts, publish the NPM package from `npm-package`, and publish the Docker
upload those packages to a draft release. Hub image from `release-images`.
3. Summarize the major changes in the `CHANGELOG.md`. 1. Publish release.
4. Copy the relevant changelog section to the release then publish it. 1. After, create a new branch called `release/v0.0.0` (replace 0s with actual version aka v4.5.0)
5. CI will automatically publish the NPM package, Docker image, and update 1. Summarize the major changes in the `CHANGELOG.md`
Homebrew using the published release assets. 1. Bump chart version in `Chart.yaml`.
6. Bump the chart version in `Chart.yaml` and merge in the changelog updates.
#### Release Candidates #### Release Candidates
We prefer to do release candidates so the community can test things before a We prefer to do release candidates so the community can test things before a full-blown release. To do this follow the same steps as above but:
full-blown release. To do this follow the same steps as above but:
1. Add a `-rc.<number>` suffix to the version. 1. Only bump version in `package.json`
2. When you publish the release select "pre-release". CI will not automatically 1. use `0.0.0-rc.0`
publish pre-releases. 1. When you publish the release, select "pre-release"
3. Do not update the chart version or merge in the changelog until the final
release.
#### AUR #### AUR

View File

@@ -5,8 +5,7 @@
Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and
access it in the browser. access it in the browser.
![Screenshot](./assets/screenshot-1.png) ![Screenshot](./assets/screenshot.png)
![Screenshot](./assets/screenshot-2.png)
## Highlights ## Highlights
@@ -17,7 +16,7 @@ access it in the browser.
## Requirements ## Requirements
See [requirements](https://coder.com/docs/code-server/latest/requirements) for minimum specs, as well as instructions See [requirements](requirements.md) for minimum specs, as well as instructions
on how to set up a Google VM on which you can install code-server. on how to set up a Google VM on which you can install code-server.
**TL;DR:** Linux machine with WebSockets enabled, 1 GB RAM, and 2 vCPUs **TL;DR:** Linux machine with WebSockets enabled, 1 GB RAM, and 2 vCPUs

View File

@@ -11,21 +11,13 @@ 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 6. Exit the terminal using `exit` and then reopen the terminal
7. Install and use Node.js 18: 7. Install and use Node.js 16:
```shell ```shell
nvm install 18 nvm install 16
nvm use 18 nvm use 16
``` ```
8. Install code-server globally on device with: `npm install --global code-server --unsafe-perm` 8. Install code-server globally on device with: `npm install --global code-server --unsafe-perm`
9. Run code-server with `code-server` 9. Run code-server with `code-server`
10. Access on localhost:8080 in your browser 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

BIN
docs/assets/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

View File

@@ -1,37 +0,0 @@
# Coder
To install and run code-server in a Coder workspace, we suggest using the `install.sh`
script in your template like so:
```terraform
resource "coder_agent" "dev" {
arch = "amd64"
os = "linux"
startup_script = <<EOF
#!/bin/sh
set -x
# install and start code-server
curl -fsSL https://code-server.dev/install.sh | sh -s -- --version 4.8.3
code-server --auth none --port 13337 &
EOF
}
resource "coder_app" "code-server" {
agent_id = coder_agent.dev.id
slug = "code-server"
display_name = "code-server"
url = "http://localhost:13337/"
icon = "/icon/code.svg"
subdomain = false
share = "owner"
healthcheck {
url = "http://localhost:13337/healthz"
interval = 3
threshold = 10
}
}
```
If you run into issues, ask for help on the `coder/coder` [Discussions
here](https://github.com/coder/coder/discussions).

View File

@@ -14,12 +14,10 @@
- [Accessing web services](#accessing-web-services) - [Accessing web services](#accessing-web-services)
- [Using a subdomain](#using-a-subdomain) - [Using a subdomain](#using-a-subdomain)
- [Using a subpath](#using-a-subpath) - [Using a subpath](#using-a-subpath)
- [Using your own proxy](#using-your-own-proxy)
- [Stripping `/proxy/<port>` from the request path](#stripping-proxyport-from-the-request-path) - [Stripping `/proxy/<port>` from the request path](#stripping-proxyport-from-the-request-path)
- [Proxying to create a React app](#proxying-to-create-a-react-app) - [Proxying to create a React app](#proxying-to-create-a-react-app)
- [Proxying to a Vue app](#proxying-to-a-vue-app) - [Proxying to a Vue app](#proxying-to-a-vue-app)
- [Proxying to an Angular app](#proxying-to-an-angular-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) - [SSH into code-server on VS Code](#ssh-into-code-server-on-vs-code)
- [Option 1: cloudflared tunnel](#option-1-cloudflared-tunnel) - [Option 1: cloudflared tunnel](#option-1-cloudflared-tunnel)
- [Option 2: ngrok tunnel](#option-2-ngrok-tunnel) - [Option 2: ngrok tunnel](#option-2-ngrok-tunnel)
@@ -139,9 +137,9 @@ sudo apt install caddy
1. Replace `/etc/caddy/Caddyfile` using `sudo` so that the file looks like this: 1. Replace `/etc/caddy/Caddyfile` using `sudo` so that the file looks like this:
```text ```text
mydomain.com { mydomain.com
reverse_proxy 127.0.0.1:8080
} reverse_proxy 127.0.0.1:8080
``` ```
If you want to serve code-server from a sub-path, you can do so as follows: If you want to serve code-server from a sub-path, you can do so as follows:
@@ -191,7 +189,7 @@ At this point, you should be able to access code-server via
location / { location / {
proxy_pass http://localhost:8080/; proxy_pass http://localhost:8080/;
proxy_set_header Host $http_host; proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade; proxy_set_header Connection upgrade;
proxy_set_header Accept-Encoding gzip; proxy_set_header Accept-Encoding gzip;
@@ -318,32 +316,12 @@ To set your domain, start code-server with the `--proxy-domain` flag:
code-server --proxy-domain <domain> code-server --proxy-domain <domain>
``` ```
For instance, if you have code-server exposed on `domain.tld` and a Python Now you can browse to `<port>.<domain>`. Note that this uses the host header, so
server running on port 8080 of the same machine code-server is running on, you ensure your reverse proxy (if you're using one) forwards that information.
could run code-server with `--proxy-domain domain.tld` and access the Python
server via `8080.domain.tld`.
Note that this uses the host header, so ensure your reverse proxy (if you're
using one) forwards that information.
### Using a subpath ### Using a subpath
Simply browse to `/proxy/<port>/`. For instance, if you have code-server Simply browse to `/proxy/<port>/`.
exposed on `domain.tld` and a Python server running on port 8080 of the same
machine code-server is running on, you could access the Python server via
`domain.tld/proxy/8000`.
### Using your own proxy
You can make extensions and the ports panel use your own proxy by setting
`VSCODE_PROXY_URI`. For example if you set
`VSCODE_PROXY_URI=https://{{port}}.kyle.dev` when an application is detected
running on port 3000 of the same machine code-server is running on the ports
panel will create a link to https://3000.kyle.dev instead of pointing to the
built-in subpath-based proxy.
Note: relative paths are also supported i.e.
`VSCODE_PROXY_URI=./proxy/{{port}}`
### Stripping `/proxy/<port>` from the request path ### Stripping `/proxy/<port>` from the request path
@@ -415,27 +393,6 @@ 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). 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 ## SSH into code-server on VS Code
[![SSH](https://img.shields.io/badge/SSH-363636?style=for-the-badge&logo=GNU+Bash&logoColor=ffffff)](https://ohmyz.sh/) [![Terminal](https://img.shields.io/badge/Terminal-2E2E2E?style=for-the-badge&logo=Windows+Terminal&logoColor=ffffff)](https://img.shields.io/badge/Terminal-2E2E2E?style=for-the-badge&logo=Windows+Terminal&logoColor=ffffff) [![Visual Studio Code](https://img.shields.io/badge/Visual_Studio_Code-007ACC?style=for-the-badge&logo=Visual+Studio+Code&logoColor=ffffff)](vscode:extension/ms-vscode-remote.remote-ssh) [![SSH](https://img.shields.io/badge/SSH-363636?style=for-the-badge&logo=GNU+Bash&logoColor=ffffff)](https://ohmyz.sh/) [![Terminal](https://img.shields.io/badge/Terminal-2E2E2E?style=for-the-badge&logo=Windows+Terminal&logoColor=ffffff)](https://img.shields.io/badge/Terminal-2E2E2E?style=for-the-badge&logo=Windows+Terminal&logoColor=ffffff) [![Visual Studio Code](https://img.shields.io/badge/Visual_Studio_Code-007ACC?style=for-the-badge&logo=Visual+Studio+Code&logoColor=ffffff)](vscode:extension/ms-vscode-remote.remote-ssh)

View File

@@ -103,9 +103,10 @@ _exact_ same commands presented in the rest of this document.
We recommend installing with `npm` when: We recommend installing with `npm` when:
1. You aren't using a machine with `amd64` or `arm64`. 1. You aren't using a machine with `amd64` or `arm64`.
2. You are installing code-server on Windows. 1. You are installing code-server on Windows
3. You're on Linux with `glibc` < v2.28 or `glibcxx` < v3.4.21. 1. You're on Linux with `glibc` < v2.17, `glibcxx` < v3.4.18 on `amd64`, `glibc`
4. You're running Alpine Linux or are using a non-glibc libc. See < v2.23, or `glibcxx` < v3.4.21 on `arm64`.
1. You're running Alpine Linux or are using a non-glibc libc. See
[#1430](https://github.com/coder/code-server/issues/1430#issuecomment-629883198) [#1430](https://github.com/coder/code-server/issues/1430#issuecomment-629883198)
for more information. for more information.
@@ -122,8 +123,8 @@ node binary and node modules.
We create the standalone releases using the [npm package](#npm), and we We create the standalone releases using the [npm package](#npm), and we
then create the remaining releases using the standalone version. then create the remaining releases using the standalone version.
The only requirement to use the standalone release is `glibc` >= 2.28 and The only requirement to use the standalone release is `glibc` >= 2.17 and
`glibcxx` >= v3.4.21 on Linux (for macOS, there is no minimum system `glibcxx` >= v3.4.18 on Linux (for macOS, there is no minimum system
requirement). requirement).
To use a standalone release: To use a standalone release:
@@ -279,7 +280,6 @@ brew services start code-server
# outside the container. # outside the container.
mkdir -p ~/.config mkdir -p ~/.config
docker run -it --name code-server -p 127.0.0.1:8080:8080 \ 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 "$HOME/.config:/home/coder/.config" \
-v "$PWD:/home/coder/project" \ -v "$PWD:/home/coder/project" \
-u "$(id -u):$(id -g)" \ -u "$(id -u):$(id -g)" \
@@ -297,9 +297,9 @@ You can install code-server using the [Helm package manager](https://coder.com/d
## Windows ## Windows
We currently [do not publish Windows We currently [do not publish Windows releases](https://github.com/coder/code-server/issues/1397). We recommend installing code-server onto Windows with [`npm`](#npm).
releases](https://github.com/coder/code-server/issues/1397). We recommend
installing code-server onto Windows with [`npm`](#npm). > Note: You will also need to [build coder/cloud-agent manually](https://github.com/coder/cloud-agent/issues/17) if you would like to use `code-server --link` on Windows.
## Raspberry Pi ## Raspberry Pi

11
docs/link.md Normal file
View File

@@ -0,0 +1,11 @@
# code-server --link
> Note: This feature is no longer recommended due to instability. Stay tuned for a revised version.
Run code-server with the flag `--link` and you'll get TLS, authentication, and a dedicated URL
for accessing your IDE out of the box.
```console
$ code-server --link
Proxying code-server, you can access your IDE at https://example.coder.co
```

View File

@@ -38,9 +38,9 @@
"path": "./guide.md", "path": "./guide.md",
"children": [ "children": [
{ {
"title": "Coder", "title": "--link",
"description": "How to run code-server in Coder", "description": "How to run code-server --link",
"path": "./coder.md" "path": "./link.md"
}, },
{ {
"title": "iPad", "title": "iPad",

View File

@@ -30,7 +30,7 @@ includes installing instructions based on your operating system.
## Node.js version ## Node.js version
We use the same major version of Node.js shipped with Code's remote, which is We use the same major version of Node.js shipped with Code's remote, which is
currently `18.x`. VS Code also [lists Node.js currently `16.x`. VS Code also [lists Node.js
requirements](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites). requirements](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites).
Using other versions of Node.js [may lead to unexpected Using other versions of Node.js [may lead to unexpected
@@ -79,7 +79,7 @@ Proceed to [installing](#installing)
## FreeBSD ## FreeBSD
```sh ```sh
pkg install -y git python npm-node18 pkgconf pkg install -y git python npm-node16 pkgconf
pkg install -y libinotify pkg install -y libinotify
``` ```
@@ -97,7 +97,7 @@ code-server
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml # Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
``` ```
A `postinstall.sh` script will attempt to run. Select your terminal (e.g., Git bash) as the default shell for npm run-scripts. If an additional dialog does not appear, run the install command again. A `postinstall.sh` script will attempt to run. Select your terminal (e.g., Git bash) as the default application for `.sh` files. If an additional dialog does not appear, run the install command again.
If the `code-server` command is not found, you'll need to [add a directory to your PATH](https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/). To find the directory, use the following command: If the `code-server` command is not found, you'll need to [add a directory to your PATH](https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/). To find the directory, use the following command:

View File

@@ -8,11 +8,11 @@
- [Upgrade](#upgrade) - [Upgrade](#upgrade)
- [Known Issues](#known-issues) - [Known Issues](#known-issues)
- [Git won't work in `/sdcard`](#git-wont-work-in-sdcard) - [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) - [Extra](#extra)
- [Create a new user](#create-a-new-user) - [Create a new user](#create-a-new-user)
- [Install Go](#install-go) - [Install Go](#install-go)
- [Install Python](#install-python) - [Install Python](#install-python)
- [Working with PRoot](#working-with-proot)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
@@ -20,9 +20,53 @@
## Install ## Install
1. Get [Termux](https://f-droid.org/en/packages/com.termux/) from **F-Droid**. 1. Get [Termux](https://f-droid.org/en/packages/com.termux/) from **F-Droid**.
2. Run `pkg install tur-repo` 2. Install Debian by running the following:
3. Run `pkg install code-server` - Run `termux-setup-storage` to allow storage access, or else code-server won't be able to read from `/sdcard`.\
4. You can now start code server by simply running `code-server`. > The following command is from [proot-distro](https://github.com/termux/proot-distro), but you can also use [Andronix](https://andronix.app/).
> After Debian is installed the `~ $` will change to `root@localhost`.
```bash
pkg update -y && pkg install proot-distro -y && proot-distro install debian && proot-distro login debian
```
3. Run the following commands to setup Debian:
```bash
apt update && apt upgrade -y && apt-get install sudo vim git -y
```
4. Install [NVM](https://github.com/nvm-sh/nvm#install--update-script) by following the install guide in the README, just a curl/wget command.
5. Set up NVM for multi-user. After installing NVM it automatically adds the necessary commands for it to work, but it will only work if you are logged in as root:
- Copy the lines NVM asks you to run after running the install script.
- Run `nano /root/.bashrc` and comment out those lines by adding a `#` at the start.
- Run `nano /etc/profile` and paste those lines at the end of the file. Make sure to replace `$HOME` with `/root` on the first line.
- Now run `exit`
- Start Debian again `proot-distro login debian`
6. After following the instructions and setting up NVM you can now install the [required node version](https://coder.com/docs/code-server/latest/npm#nodejs-version) by running:
```bash
nvm install v<major_version_here>
```
7. To install `code-server` run the following:
> To check the install process (Will not actually install code-server)
> If it all looks good, you can install code-server by running the second command
```bash
curl -fsSL https://code-server.dev/install.sh | sh -s -- --dry-run
```
```bash
curl -fsSL https://code-server.dev/install.sh | sh
```
8. You can now start code server by simply running `code-server`.
> Consider using a new user instead of root, read [here](https://www.howtogeek.com/124950/htg-explains-why-you-shouldnt-log-into-your-linux-system-as-root/) why using root is not recommended.\
> Learn how to add a user [here](#create-a-new-user).
## NPM Installation ## NPM Installation
@@ -56,7 +100,7 @@ npm config set python python3
node -v node -v
``` ```
you will get Node version `v18` you will get node version `v16.15.0`
5. Now install code-server following our guide on [installing with npm](./npm.md) 5. Now install code-server following our guide on [installing with npm](./npm.md)
@@ -88,37 +132,6 @@ Potential Workaround :
1. Create a soft-link from the debian-fs to your folder in `/sdcard` 1. Create a soft-link from the debian-fs to your folder in `/sdcard`
2. Use git from termux (preferred) 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 ## Extra
### Create a new user ### Create a new user
@@ -189,3 +202,35 @@ eval "$(pyenv virtualenv-init -)"
7. Run `touch /root/.pyenv/version && echo "your_version_here" > /root/.pyenv/version` 7. Run `touch /root/.pyenv/version && echo "your_version_here" > /root/.pyenv/version`
8. (You may have to start Debian again) Run `python3 -V` to verify if PATH works or not. 8. (You may have to start Debian again) Run `python3 -V` to verify if PATH works or not.
> If `python3` doesn't work but pyenv says that the install was successful in step 6 then try running `$PYENV_ROOT/versions/your_version/bin/python3`. > If `python3` doesn't work but pyenv says that the install was successful in step 6 then try running `$PYENV_ROOT/versions/your_version/bin/python3`.
### Working with PRoot
Debian PRoot Distro Dev Environment
- Since Node and code-server are installed in the Debian PRoot distro, your `~/.ssh/` configuration, `~/.bashrc`, git, npm packages, etc. should be setup in PRoot as well.
- The terminal accessible in code-server will bring up the filesystem and `~/.bashrc` in the Debian PRoot distro.
Accessing files in the Debian PRoot Distro
- The `/data/data/com.termux/files/home` directory in PRoot accesses the termux home directory (`~`)
- The `/sdcard` directory in PRoot accesses the Android storage directory, though there are [known issues with git and files in the `/sdcard` path](#git-wont-work-in-sdcard)
Accessing the Debian PRoot distro/Starting code-server
- Run the following command to access the Debian PRoot distro, from the termux shell:
```bash
proot-distro login debian
```
- Run the following command to start code-server directly in the Debian PRoot distro, from the termux shell:
```bash
proot-distro login debian -- code-server
```
- If you [created a new user](#create-a-new-user), you'll need to insert the `--user <username>` option between `login` and `debian` in the commands above to run as the user instead of root in PRoot.
Additional information on PRoot and Termux
- Additional information on using your Debian PRoot Distro can be [found here](https://github.com/termux/proot-distro#functionality-overview).

30
flake.lock generated
View File

@@ -1,15 +1,12 @@
{ {
"nodes": { "nodes": {
"flake-utils": { "flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": { "locked": {
"lastModified": 1681202837, "lastModified": 1659877975,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401", "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -20,11 +17,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1683594133, "lastModified": 1660639432,
"narHash": "sha256-iUhLhEAgOCnexSGDsYT2ouydis09uDoNzM7UC685XGE=", "narHash": "sha256-2WDiboOCfB0LhvnDVMXOAr8ZLDfm3WdO54CkoDPwN1A=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "8d447c5626cfefb9b129d5b30103344377fe09bc", "rev": "6c6409e965a6c883677be7b9d87a95fab6c3472e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -37,21 +34,6 @@
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "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", "root": "root",

View File

@@ -7,16 +7,16 @@
flake-utils.lib.eachDefaultSystem flake-utils.lib.eachDefaultSystem
(system: (system:
let pkgs = nixpkgs.legacyPackages.${system}; let pkgs = nixpkgs.legacyPackages.${system};
nodejs = pkgs.nodejs-18_x; nodejs = pkgs.nodejs-16_x;
yarn' = pkgs.yarn.override { inherit nodejs; }; yarn' = pkgs.yarn.override { inherit nodejs; };
in { in {
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
nodejs yarn' python3 pkg-config git rsync jq moreutils quilt bats openssl nodejs yarn' python pkg-config git rsync jq moreutils quilt bats
]; ];
buildInputs = with pkgs; (lib.optionals (!stdenv.isDarwin) [ libsecret libkrb5 ] buildInputs = with pkgs; (lib.optionals (!stdenv.isDarwin) [ libsecret ]
++ (with xorg; [ libX11 libxkbfile ]) ++ (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 AppKit Cocoa CoreServices Security xcbuild
])); ]));
}; };

View File

@@ -46,7 +46,7 @@ Usage:
Sets the prefix used by standalone release archives. Defaults to ~/.local Sets the prefix used by standalone release archives. Defaults to ~/.local
The release is unarchived into ~/.local/lib/code-server-X.X.X The release is unarchived into ~/.local/lib/code-server-X.X.X
and the binary symlinked into ~/.local/bin/code-server and the binary symlinked into ~/.local/bin/code-server
To install system wide pass --prefix=/usr/local To install system wide pass ---prefix=/usr/local
--rsh <bin> --rsh <bin>
Specifies the remote shell for remote installation. Defaults to ssh. Specifies the remote shell for remote installation. Defaults to ssh.
@@ -441,7 +441,7 @@ install_npm() {
return return
fi fi
echoerr "Please install npm to install code-server!" echoerr "Please install npm to install code-server!"
echoerr "You will need at least node v18 and a few C dependencies." echoerr "You will need at least node v12 and a few C dependencies."
echoerr "See the docs https://coder.com/docs/code-server/latest/install#npm" echoerr "See the docs https://coder.com/docs/code-server/latest/install#npm"
exit 1 exit 1

View File

@@ -38,61 +38,74 @@
}, },
"main": "out/node/entry.js", "main": "out/node/entry.js",
"devDependencies": { "devDependencies": {
"@schemastore/package": "^0.0.10", "@schemastore/package": "^0.0.6",
"@types/compression": "^1.7.3", "@types/compression": "^1.7.0",
"@types/cookie-parser": "^1.4.4", "@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.17", "@types/express": "^4.17.8",
"@types/http-proxy": "1.17.7", "@types/http-proxy": "^1.17.4",
"@types/js-yaml": "^4.0.6", "@types/js-yaml": "^4.0.0",
"@types/node": "^18.0.0", "@types/node": "^16.0.0",
"@types/pem": "^1.14.1", "@types/pem": "^1.9.5",
"@types/proxy-from-env": "^1.0.1", "@types/proxy-from-env": "^1.0.1",
"@types/safe-compare": "^1.1.0", "@types/safe-compare": "^1.1.0",
"@types/semver": "^7.5.2", "@types/semver": "^7.1.0",
"@types/trusted-types": "^2.0.4", "@types/split2": "^3.2.0",
"@types/ws": "^8.5.5", "@types/trusted-types": "^2.0.2",
"@typescript-eslint/eslint-plugin": "^6.7.2", "@types/ws": "^8.5.3",
"@typescript-eslint/parser": "^6.7.2", "@typescript-eslint/eslint-plugin": "^5.41.0",
"audit-ci": "^6.6.1", "@typescript-eslint/parser": "^5.41.0",
"doctoc": "^2.2.1", "audit-ci": "^6.0.0",
"eslint": "^8.49.0", "doctoc": "2.2.1",
"eslint-config-prettier": "^9.0.0", "eslint": "^8.26.0",
"eslint-import-resolver-typescript": "^3.6.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.28.1", "eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-import": "^2.26.0",
"prettier": "^3.0.3", "eslint-plugin-prettier": "^4.2.1",
"prettier-plugin-sh": "^0.13.1", "prettier": "2.8.0",
"ts-node": "^10.9.1", "prettier-plugin-sh": "^0.12.8",
"typescript": "^5.2.2" "ts-node": "^10.0.0",
}, "typescript": "^4.6.2"
"dependencies": {
"@coder/logger": "^3.0.1",
"argon2": "^0.31.1",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"env-paths": "^2.2.1",
"express": "5.0.0-alpha.8",
"http-proxy": "^1.18.1",
"httpolyglot": "^0.1.2",
"i18next": "^23.5.1",
"js-yaml": "^4.1.0",
"limiter": "^2.1.0",
"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.5.4",
"ws": "^8.14.2",
"xdg-basedir": "^4.0.0"
}, },
"resolutions": { "resolutions": {
"@types/node": "^18.0.0", "ansi-regex": "^5.0.1",
"qs": "6.9.7" "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"
}, },
"overrides": { "dependencies": {
"qs": "6.9.7" "@coder/logger": "^3.0.0",
"argon2": "0.30.2",
"compression": "^1.7.4",
"cookie-parser": "^1.4.5",
"env-paths": "^2.2.0",
"express": "5.0.0-alpha.8",
"http-proxy": "^1.18.0",
"httpolyglot": "^0.1.2",
"js-yaml": "^4.0.0",
"limiter": "^1.1.5",
"pem": "^1.14.2",
"proxy-agent": "^5.0.0",
"qs": "6.11.0",
"rotating-file-stream": "^3.0.0",
"safe-buffer": "^5.1.1",
"safe-compare": "^1.1.4",
"semver": "^7.1.3",
"split2": "^4.0.0",
"ws": "^8.0.0",
"xdg-basedir": "^4.0.0"
}, },
"bin": { "bin": {
"code-server": "out/node/entry.js" "code-server": "out/node/entry.js"
@@ -107,7 +120,7 @@
"remote-development" "remote-development"
], ],
"engines": { "engines": {
"node": "18" "node": "16"
}, },
"jest": { "jest": {
"transform": { "transform": {

View File

@@ -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.orig/lib/vscode/src/vs/base/common/network.ts
+++ code-server/lib/vscode/src/vs/base/common/network.ts +++ code-server/lib/vscode/src/vs/base/common/network.ts
@@ -168,7 +168,9 @@ class RemoteAuthoritiesImpl { @@ -162,7 +162,9 @@ class RemoteAuthoritiesImpl {
return URI.from({ return URI.from({
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource, scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
authority: `${host}:${port}`, authority: `${host}:${port}`,
@@ -99,19 +99,26 @@ 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.orig/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts
+++ code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts +++ code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts
@@ -280,6 +280,7 @@ export class BrowserSocketFactory implem @@ -274,6 +274,7 @@ export class BrowserSocketFactory implem
connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
return new Promise<ISocket>((resolve, reject) => { connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws'); const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/") + 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 socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
const errorListener = socket.onError(reject); const errorListener = socket.onError((err) => callback(err, undefined));
socket.onOpen(() => { socket.onOpen(() => {
@@ -282,6 +283,3 @@ export class BrowserSocketFactory implem
});
}
}
-
-
-
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
=================================================================== ===================================================================
--- code-server.orig/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 +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -269,16 +269,15 @@ export class WebClientServer { @@ -267,12 +267,11 @@ export class WebClientServer {
return void res.end(); return void res.end();
} }
@@ -120,33 +127,33 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
- return Array.isArray(val) ? val[0] : val; - return Array.isArray(val) ? val[0] : val;
- }; - };
- -
const useTestResolver = (!this._environmentService.isBuilt && this._environmentService.args['use-test-resolver']); - const remoteAuthority = getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host;
+ // For now we are getting the remote authority from the client to avoid + // For now we are getting the remote authority from the client to avoid
+ // needing specific configuration for reverse proxies to work. Set this to + // needing specific configuration for reverse proxies to work. Set this to
+ // something invalid to make sure we catch code that is using this value + // something invalid to make sure we catch code that is using this value
+ // from the backend when it should not. + // from the backend when it should not.
const remoteAuthority = ( + const remoteAuthority = 'remote';
useTestResolver
? 'test+test'
- : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host)
+ : 'remote'
);
if (!remoteAuthority) { if (!remoteAuthority) {
return serveError(req, res, 400, `Bad request.`); return serveError(req, res, 400, `Bad request.`);
@@ -305,8 +304,12 @@ export class WebClientServer { }
@@ -298,6 +297,8 @@ export class WebClientServer {
scopes: [['user:email'], ['repo']] scopes: [['user:email'], ['repo']]
} : undefined; } : undefined;
+ const base = relativeRoot(getOriginalUrl(req)) + const base = relativeRoot(getOriginalUrl(req))
+ const vscodeBase = relativePath(getOriginalUrl(req)) + const vscodeBase = relativePath(getOriginalUrl(req))
+
const productConfiguration = <Partial<IProductConfiguration>>{ const workbenchWebConfiguration = {
codeServerVersion: this._productService.codeServerVersion, remoteAuthority,
+ rootEndpoint: base, @@ -309,6 +310,7 @@ export class WebClientServer {
embedderIdentifier: 'server-distro', workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']),
extensionsGallery: this._webExtensionResourceUrlTemplate ? { productConfiguration: <Partial<IProductConfiguration>>{
...this._productService.extensionsGallery, codeServerVersion: this._productService.codeServerVersion,
@@ -341,8 +344,10 @@ export class WebClientServer { + rootEndpoint: base,
embedderIdentifier: 'server-distro',
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
...this._productService.extensionsGallery,
@@ -326,8 +328,10 @@ export class WebClientServer {
const values: { [key: string]: string } = { const values: { [key: string]: string } = {
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '', WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '',
@@ -158,17 +165,17 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
+ VS_BASE: vscodeBase, + VS_BASE: vscodeBase,
}; };
if (useTestResolver) {
@@ -367,7 +372,7 @@ export class WebClientServer { @@ -344,7 +348,7 @@ export class WebClientServer {
'default-src \'self\';', 'default-src \'self\';',
'img-src \'self\' https: data: blob:;', 'img-src \'self\' https: data: blob:;',
'media-src \'self\';', 'media-src \'self\';',
- `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=' 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 + `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
'child-src \'self\';', 'child-src \'self\';',
`frame-src 'self' https://*.vscode-cdn.net data:;`, `frame-src 'self' https://*.vscode-cdn.net data:;`,
'worker-src \'self\' data: blob:;', 'worker-src \'self\' data:;',
@@ -440,3 +445,70 @@ export class WebClientServer { @@ -417,3 +421,70 @@ export class WebClientServer {
return void res.end(data); return void res.end(data);
} }
} }
@@ -243,7 +250,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts --- code-server.orig/lib/vscode/src/vs/base/common/product.ts
+++ code-server/lib/vscode/src/vs/base/common/product.ts +++ code-server/lib/vscode/src/vs/base/common/product.ts
@@ -56,6 +56,7 @@ export type ExtensionVirtualWorkspaceSup @@ -32,6 +32,7 @@ export type ExtensionVirtualWorkspaceSup
export interface IProductConfiguration { export interface IProductConfiguration {
readonly codeServerVersion?: string readonly codeServerVersion?: string
@@ -255,45 +262,23 @@ 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.orig/lib/vscode/src/vs/code/browser/workbench/workbench.ts
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts +++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
@@ -304,7 +304,8 @@ class LocalStorageURLCallbackProvider ex @@ -489,6 +489,7 @@ function doCreateUri(path: string, query
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('&') });
} }
private startListening(): void { + path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
@@ -550,17 +551,6 @@ class WorkspaceProvider implements IWork return URI.parse(window.location.href).with({ path, query });
}
} }
-function readCookie(name: string): string | undefined { @@ -500,7 +501,7 @@ function doCreateUri(path: string, query
- 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) { if (!configElement || !configElementAttribute) {
throw new Error('Missing web configuration element'); throw new Error('Missing web configuration element');
} }
- const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents; callbackRoute: string } = JSON.parse(configElementAttribute); - 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 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 Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts --- code-server.orig/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
@@ -304,12 +289,12 @@ Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/ext
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
-import { RemoteAuthorities } from 'vs/base/common/network'; -import { RemoteAuthorities } from 'vs/base/common/network';
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
import { TargetPlatform } from 'vs/platform/extensions/common/extensions';
@@ -102,7 +101,7 @@ export abstract class AbstractExtensionR export const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource';
: version, @@ -75,7 +74,7 @@ export abstract class AbstractExtensionR
path: 'extension' 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' }));
- return this._isWebExtensionResourceEndPoint(uri) ? uri.with({ scheme: RemoteAuthorities.getPreferredWebSchema() }) : uri; - return this._isWebExtensionResourceEndPoint(uri) ? uri.with({ scheme: RemoteAuthorities.getPreferredWebSchema() }) : uri;
+ return this._isWebExtensionResourceEndPoint(uri) ? URI.joinPath(URI.parse(window.location.href), uri.path) : uri; + return this._isWebExtensionResourceEndPoint(uri) ? URI.joinPath(URI.parse(window.location.href), uri.path) : uri;
} }

View File

@@ -8,7 +8,7 @@ To test:
2. Open code-server 2. Open code-server
3. Open terminal 3. Open terminal
4. Open another code-server window 4. Open another code-server window
5. Run node ./out/node/entry.js with a file or directory argument 5. Run code-server with a file or directory argument
The file or directory should only open from the instance attached to that The file or directory should only open from the instance attached to that
terminal. terminal.
@@ -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.orig/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts +++ code-server/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
@@ -104,10 +104,14 @@ class RemoteTerminalBackend extends Base @@ -100,10 +100,14 @@ class RemoteTerminalBackend extends Base
} }
const reqId = e.reqId; const reqId = e.reqId;
const commandId = e.commandId; const commandId = e.commandId;

View File

@@ -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.orig/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
@@ -244,6 +244,10 @@ export class Extension implements IExten @@ -237,6 +237,10 @@ export class Extension implements IExten
if (this.type === ExtensionType.System && this.productService.quality === 'stable') { if (this.type === ExtensionType.System && this.productService.quality === 'stable') {
return false; return false;
} }
@@ -18,3 +18,14 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
if (!this.local.preRelease && this.gallery.properties.isPreReleaseVersion) { if (!this.local.preRelease && this.gallery.properties.isPreReleaseVersion) {
return false; return false;
} }
@@ -1237,6 +1241,10 @@ export class ExtensionsWorkbenchService
// Skip if check updates only for builtin extensions and current extension is not builtin.
continue;
}
+ if (installed.type !== ExtensionType.User) {
+ // Never update builtin extensions.
+ continue;
+ }
if (installed.isBuiltin && (!installed.local?.identifier.uuid || (!isWeb && this.productService.quality === 'stable'))) {
// Skip checking updates for a builtin extension if it does not has Marketplace identifier or the current product is VS Code Desktop stable.
continue;

View File

@@ -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.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts +++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
@@ -281,6 +281,11 @@ export interface IWorkbenchConstructionO @@ -271,6 +271,11 @@ export interface IWorkbenchConstructionO
*/ */
readonly userDataPath?: string readonly userDataPath?: string
@@ -23,24 +23,24 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
+ +
//#endregion //#endregion
//#region Profile options
Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts --- 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 +++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -34,6 +34,11 @@ export interface IBrowserWorkbenchEnviro @@ -32,6 +32,11 @@ export interface IBrowserWorkbenchEnviro
* Options used to configure the workbench.
*/
readonly options?: IWorkbenchConstructionOptions; readonly options?: IWorkbenchConstructionOptions;
+
/** + /**
+ * Enable downloading files via menu actions. + * Enable downloading files via menu actions.
+ */ + */
+ readonly isEnabledFileDownloads?: boolean; + readonly isEnabledFileDownloads?: boolean;
+ }
+ /**
* Gets whether a resolver extension is expected for the environment. export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
*/ @@ -87,6 +92,13 @@ export class BrowserWorkbenchEnvironment
readonly expectsResolverExtension: boolean;
@@ -111,6 +116,13 @@ export class BrowserWorkbenchEnvironment
return this.options.userDataPath; 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.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/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 @@ -14,6 +14,7 @@ export const serverOptions: OptionDescri
/* ----- code-server ----- */ /* ----- code-server ----- */
'disable-update-check': { type: 'boolean' }, 'disable-update-check': { type: 'boolean' },
'auth': { type: 'string' }, 'auth': { type: 'string' },
@@ -66,7 +66,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
/* ----- server setup ----- */ /* ----- server setup ----- */
@@ -97,6 +98,7 @@ export interface ServerParsedArgs { @@ -94,6 +95,7 @@ export interface ServerParsedArgs {
/* ----- code-server ----- */ /* ----- code-server ----- */
'disable-update-check'?: boolean; 'disable-update-check'?: boolean;
'auth'?: string '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.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -332,6 +332,7 @@ export class WebClientServer { @@ -304,6 +304,7 @@ export class WebClientServer {
remoteAuthority, remoteAuthority,
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
userDataPath: this._environmentService.userDataPath, userDataPath: this._environmentService.userDataPath,
@@ -90,22 +90,29 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
=================================================================== ===================================================================
--- code-server.orig/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 +++ code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
@@ -7,12 +7,12 @@ import { Event } from 'vs/base/common/ev @@ -7,12 +7,11 @@ import { Event } from 'vs/base/common/ev
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys'; 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, 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, 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, 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, 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, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -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 { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { WorkbenchState, IWorkspaceContextService, isTemporaryWorkspace } from 'vs/platform/workspace/common/workspace'; import { WorkbenchState, IWorkspaceContextService, isTemporaryWorkspace } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
@@ -79,7 +79,7 @@ export class WorkbenchContextKeysHandler @@ -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
@IContextKeyService private readonly contextKeyService: IContextKeyService, @IContextKeyService private readonly contextKeyService: IContextKeyService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IConfigurationService private readonly configurationService: IConfigurationService, @IConfigurationService private readonly configurationService: IConfigurationService,
@@ -114,7 +121,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
@IProductService private readonly productService: IProductService, @IProductService private readonly productService: IProductService,
@IEditorService private readonly editorService: IEditorService, @IEditorService private readonly editorService: IEditorService,
@IEditorResolverService private readonly editorResolverService: IEditorResolverService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService,
@@ -209,6 +209,9 @@ export class WorkbenchContextKeysHandler @@ -202,6 +202,9 @@ export class WorkbenchContextKeysHandler
this.auxiliaryBarVisibleContext = AuxiliaryBarVisibleContext.bindTo(this.contextKeyService); this.auxiliaryBarVisibleContext = AuxiliaryBarVisibleContext.bindTo(this.contextKeyService);
this.auxiliaryBarVisibleContext.set(this.layoutService.isVisible(Parts.AUXILIARYBAR_PART)); this.auxiliaryBarVisibleContext.set(this.layoutService.isVisible(Parts.AUXILIARYBAR_PART));
@@ -136,8 +143,8 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions
+import { DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys'; +import { DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ThemeIcon } from 'vs/base/common/themables'; import { ThemeIcon } from 'vs/platform/theme/common/themeService';
@@ -489,13 +489,16 @@ MenuRegistry.appendMenuItem(MenuId.Explo @@ -483,13 +483,16 @@ MenuRegistry.appendMenuItem(MenuId.Explo
id: DOWNLOAD_COMMAND_ID, id: DOWNLOAD_COMMAND_ID,
title: DOWNLOAD_LABEL title: DOWNLOAD_LABEL
}, },
@@ -165,9 +172,9 @@ Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
=================================================================== ===================================================================
--- code-server.orig/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 +++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
@@ -38,6 +38,8 @@ export const HasWebFileSystemAccess = ne @@ -32,6 +32,8 @@ export const IsFullscreenContext = new R
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 HasWebFileSystemAccess = new RawContextKey<boolean>('hasWebFileSystemAccess', false, true); // Support for FileSystemAccess web APIs (https://wicg.github.io/file-system-access)
+export const IsEnabledFileDownloads = new RawContextKey<boolean>('isEnabledFileDownloads', true, true); +export const IsEnabledFileDownloads = new RawContextKey<boolean>('isEnabledFileDownloads', true, true);
+ +

View File

@@ -14,31 +14,20 @@ We can remove this once upstream supports all language packs.
one but is worse because it does not handle non-existent or empty files. one but is worse because it does not handle non-existent or empty files.
7. Replace some caching and Node requires because code-server does not restart 7. Replace some caching and Node requires because code-server does not restart
when changing the language unlike native Code. when changing the language unlike native Code.
8. Make language extensions installable like normal rather than using the
special set/clear language actions.
Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
=================================================================== ===================================================================
--- code-server.orig/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 +++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
@@ -11,7 +11,7 @@ import * as path from 'vs/base/common/pa @@ -216,6 +216,9 @@ export async function setupServerService
import { IURITransformer } from 'vs/base/common/uriIpc';
import { getMachineId } from 'vs/base/node/id';
import { Promises } from 'vs/base/node/pfs';
-import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
+import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import { ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
@@ -227,6 +227,9 @@ export async function setupServerService
const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)); const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority));
socketServer.registerChannel('extensions', channel); socketServer.registerChannel('extensions', channel);
+ const languagePackChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILanguagePackService), disposables); + const languagePackChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILanguagePackService));
+ socketServer.registerChannel('languagePacks', languagePackChannel); + socketServer.registerChannel('languagePacks', languagePackChannel);
+ +
// clean up extensions folder const encryptionChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(IEncryptionMainService));
remoteExtensionsScanner.whenExtensionsReady().then(() => extensionManagementService.cleanUp()); socketServer.registerChannel('encryption', encryptionChannel);
Index: code-server/lib/vscode/src/vs/base/common/platform.ts Index: code-server/lib/vscode/src/vs/base/common/platform.ts
=================================================================== ===================================================================
@@ -53,7 +42,7 @@ Index: code-server/lib/vscode/src/vs/base/common/platform.ts
export const LANGUAGE_DEFAULT = 'en'; export const LANGUAGE_DEFAULT = 'en';
let _isWindows = false; let _isWindows = false;
@@ -90,17 +88,21 @@ if (typeof navigator === 'object' && !is @@ -83,17 +81,19 @@ if (typeof navigator === 'object' && !is
_isMobile = _userAgent?.indexOf('Mobi') >= 0; _isMobile = _userAgent?.indexOf('Mobi') >= 0;
_isWeb = true; _isWeb = true;
@@ -67,18 +56,16 @@ Index: code-server/lib/vscode/src/vs/base/common/platform.ts
- -
- _locale = configuredLocale || LANGUAGE_DEFAULT; - _locale = configuredLocale || LANGUAGE_DEFAULT;
+ _locale = LANGUAGE_DEFAULT; + _locale = LANGUAGE_DEFAULT;
_language = _locale; _language = _locale;
_platformLocale = navigator.language;
+ const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration'); + const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
+ const rawNlsConfig = el && el.getAttribute('data-settings'); + const rawNlsConfig = el && el.getAttribute('data-settings');
+ if (rawNlsConfig) { + if (rawNlsConfig) {
+ try { + try {
+ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig); + const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
+ const resolved = nlsConfig.availableLanguages['*'];
+ _locale = nlsConfig.locale; + _locale = nlsConfig.locale;
+ _platformLocale = nlsConfig.osLocale;
+ _language = resolved ? resolved : LANGUAGE_DEFAULT;
+ _translationsConfigFile = nlsConfig._translationsConfigFile; + _translationsConfigFile = nlsConfig._translationsConfigFile;
+ _language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT;
+ } catch (error) { /* Oh well. */ } + } catch (error) { /* Oh well. */ }
+ } + }
} }
@@ -98,10 +85,10 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
<!-- Workbench Icon/Manifest/CSS --> <!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="{{BASE}}/_static/src/browser/media/favicon-dark-support.svg" /> <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" /> <link rel="alternate icon" href="{{BASE}}/_static/src/browser/media/favicon.ico" type="image/x-icon" />
@@ -48,15 +51,26 @@ @@ -46,15 +49,26 @@
// Normalize locale to lowercase because translationServiceUrl is case-sensitive. // Set up nls if the user is not using the default language (English)
// ref: https://github.com/microsoft/vscode/issues/187795 const nlsConfig = {};
const locale = window.localStorage.getItem('vscode.nls.locale') || navigator.language.toLowerCase(); const locale = window.localStorage.getItem('vscode.nls.locale') || navigator.language;
- if (!locale.startsWith('en')) { - if (!locale.startsWith('en')) {
- nlsConfig['vs/nls'] = { - nlsConfig['vs/nls'] = {
- availableLanguages: { - availableLanguages: {
@@ -138,7 +125,7 @@ Index: code-server/lib/vscode/src/vs/platform/environment/common/environmentServ
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/environment/common/environmentService.ts --- code-server.orig/lib/vscode/src/vs/platform/environment/common/environmentService.ts
+++ code-server/lib/vscode/src/vs/platform/environment/common/environmentService.ts +++ code-server/lib/vscode/src/vs/platform/environment/common/environmentService.ts
@@ -101,7 +101,7 @@ export abstract class AbstractNativeEnvi @@ -110,7 +110,7 @@ export abstract class AbstractNativeEnvi
return URI.file(join(vscodePortable, 'argv.json')); return URI.file(join(vscodePortable, 'argv.json'));
} }
@@ -151,7 +138,7 @@ Index: code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts --- code-server.orig/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
+++ code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts +++ code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
@@ -32,6 +32,12 @@ export function getNLSConfiguration(lang @@ -30,6 +30,12 @@ export function getNLSConfiguration(lang
if (InternalNLSConfiguration.is(value)) { if (InternalNLSConfiguration.is(value)) {
value._languagePackSupport = true; value._languagePackSupport = true;
} }
@@ -164,7 +151,7 @@ Index: code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
return value; return value;
}); });
_cache.set(key, result); _cache.set(key, result);
@@ -46,3 +52,43 @@ export namespace InternalNLSConfiguratio @@ -44,3 +50,43 @@ export namespace InternalNLSConfiguratio
return candidate && typeof candidate._languagePackId === 'string'; return candidate && typeof candidate._languagePackId === 'string';
} }
} }
@@ -212,36 +199,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.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -27,6 +27,7 @@ import { URI } from 'vs/base/common/uri' @@ -26,6 +26,7 @@ import { URI } from 'vs/base/common/uri'
import { streamToBuffer } from 'vs/base/common/buffer'; import { streamToBuffer } from 'vs/base/common/buffer';
import { IProductConfiguration } from 'vs/base/common/product'; import { IProductConfiguration } from 'vs/base/common/product';
import { isString } from 'vs/base/common/types'; import { isString } from 'vs/base/common/types';
+import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks'; +import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks';
import { CharCode } from 'vs/base/common/charCode'; import { CharCode } from 'vs/base/common/charCode';
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; 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 locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath);
+ const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath) + const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath)
const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl;
const values: { [key: string]: string } = { const workbenchWebConfiguration = {
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), remoteAuthority,
@@ -352,6 +355,7 @@ export class WebClientServer { @@ -336,6 +339,7 @@ export class WebClientServer {
WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''), WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''),
BASE: base, BASE: base,
VS_BASE: vscodeBase, VS_BASE: vscodeBase,
+ NLS_CONFIGURATION: asJSON(nlsConfiguration), + NLS_CONFIGURATION: asJSON(nlsConfiguration),
}; };
if (useTestResolver) {
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
=================================================================== ===================================================================
--- code-server.orig/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 +++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -17,6 +17,7 @@ export const serverOptions: OptionDescri @@ -15,6 +15,7 @@ export const serverOptions: OptionDescri
'disable-update-check': { type: 'boolean' }, 'disable-update-check': { type: 'boolean' },
'auth': { type: 'string' }, 'auth': { type: 'string' },
'disable-file-downloads': { type: 'boolean' }, 'disable-file-downloads': { type: 'boolean' },
@@ -249,7 +236,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
/* ----- server setup ----- */ /* ----- server setup ----- */
@@ -99,6 +100,7 @@ export interface ServerParsedArgs { @@ -96,6 +97,7 @@ export interface ServerParsedArgs {
'disable-update-check'?: boolean; 'disable-update-check'?: boolean;
'auth'?: string 'auth'?: string
'disable-file-downloads'?: boolean; 'disable-file-downloads'?: boolean;
@@ -261,18 +248,9 @@ Index: code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/workbench.web.main.ts --- code-server.orig/lib/vscode/src/vs/workbench/workbench.web.main.ts
+++ code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts +++ code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts
@@ -50,7 +50,7 @@ import 'vs/workbench/services/dialogs/br @@ -119,8 +119,9 @@ import 'vs/workbench/contrib/logs/browse
import 'vs/workbench/services/host/browser/browserHostService'; // Explorer
import 'vs/workbench/services/lifecycle/browser/lifecycleService'; import 'vs/workbench/contrib/files/browser/files.web.contribution';
import 'vs/workbench/services/clipboard/browser/clipboardService';
-import 'vs/workbench/services/localization/browser/localeService';
+import 'vs/workbench/services/localization/electron-sandbox/localeService';
import 'vs/workbench/services/path/browser/pathService';
import 'vs/workbench/services/themes/browser/browserHostColorSchemeService';
import 'vs/workbench/services/encryption/browser/encryptionService';
@@ -115,8 +115,9 @@ registerSingleton(ILanguagePackService,
// Logs
import 'vs/workbench/contrib/logs/browser/logs.contribution';
-// Localization -// Localization
-import 'vs/workbench/contrib/localization/browser/localization.contribution'; -import 'vs/workbench/contrib/localization/browser/localization.contribution';
@@ -286,9 +264,9 @@ Index: code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePack
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts --- code-server.orig/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts
+++ code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts +++ code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts
@@ -5,18 +5,24 @@ @@ -6,18 +6,24 @@
import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Language } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
+import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
@@ -311,8 +289,8 @@ Index: code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePack
+ this.languagePackService = ProxyChannel.toService<ILanguagePackService>(remoteAgentService.getConnection()!.getChannel('languagePacks')) + this.languagePackService = ProxyChannel.toService<ILanguagePackService>(remoteAgentService.getConnection()!.getChannel('languagePacks'))
} }
async getBuiltInExtensionTranslationsUri(id: string, language: string): Promise<URI | undefined> { async getBuiltInExtensionTranslationsUri(id: string): Promise<URI | undefined> {
@@ -72,6 +78,6 @@ export class WebLanguagePacksService ext @@ -73,6 +79,6 @@ export class WebLanguagePacksService ext
// Web doesn't have a concept of language packs, so we just return an empty array // Web doesn't have a concept of language packs, so we just return an empty array
getInstalledLanguages(): Promise<ILanguagePackItem[]> { getInstalledLanguages(): Promise<ILanguagePackItem[]> {
@@ -320,11 +298,11 @@ Index: code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePack
+ return this.languagePackService.getInstalledLanguages() + return this.languagePackService.getInstalledLanguages()
} }
} }
Index: code-server/lib/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts Index: code-server/lib/vscode/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts --- code-server.orig/lib/vscode/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts +++ code-server/lib/vscode/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
@@ -51,7 +51,8 @@ class NativeLocaleService implements ILo @@ -41,7 +41,8 @@ export class NativeLocaleService impleme
@IProductService private readonly productService: IProductService @IProductService private readonly productService: IProductService
) { } ) { }
@@ -334,7 +312,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/localization/electron-sa
try { try {
const content = await this.textFileService.read(this.environmentService.argvResource, { encoding: 'utf8' }); const content = await this.textFileService.read(this.environmentService.argvResource, { encoding: 'utf8' });
@@ -78,9 +79,6 @@ class NativeLocaleService implements ILo @@ -68,9 +69,6 @@ export class NativeLocaleService impleme
} }
private async writeLocaleValue(locale: string | undefined): Promise<boolean> { private async writeLocaleValue(locale: string | undefined): Promise<boolean> {
@@ -344,70 +322,3 @@ Index: code-server/lib/vscode/src/vs/workbench/services/localization/electron-sa
await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true); await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true);
return true; return true;
} }
Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
===================================================================
--- 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
@@ -321,9 +321,6 @@ export class InstallAction extends Exten
if (this.extension.isBuiltin) {
return;
}
- if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
- return;
- }
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();
@@ -591,7 +588,7 @@ export abstract class InstallInOtherServ
}
if (isLanguagePackExtension(this.extension.local.manifest)) {
- return true;
+ return false;
}
// Prefers to run on UI
@@ -1683,17 +1680,6 @@ export class SetLanguageAction extends E
update(): void {
this.enabled = false;
this.class = SetLanguageAction.DisabledClass;
- if (!this.extension) {
- return;
- }
- if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
- return;
- }
- if (this.extension.gallery && language === getLocale(this.extension.gallery)) {
- return;
- }
- this.enabled = true;
- this.class = SetLanguageAction.EnabledClass;
}
override async run(): Promise<any> {
@@ -1710,7 +1696,6 @@ export class ClearLanguageAction extends
private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`;
constructor(
- @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@ILocaleService private readonly localeService: ILocaleService,
) {
super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false);
@@ -1720,17 +1705,6 @@ export class ClearLanguageAction extends
update(): void {
this.enabled = false;
this.class = ClearLanguageAction.DisabledClass;
- if (!this.extension) {
- return;
- }
- if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
- return;
- }
- if (this.extension.gallery && language !== getLocale(this.extension.gallery)) {
- return;
- }
- this.enabled = true;
- this.class = ClearLanguageAction.EnabledClass;
}
override async run(): Promise<any> {

View File

@@ -10,25 +10,16 @@ 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.orig/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
@@ -10,7 +10,7 @@ import { IInstantiationService } from 'v @@ -62,7 +62,7 @@ import { GettingStartedIndexList } from
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 { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes'; import { KeyCode } from 'vs/base/common/keyCodes';
import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils'; import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils';
-import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; -import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys';
+import { IsEnabledCoderGettingStarted, WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; +import { IsEnabledCoderGettingStarted, WorkbenchStateContext } from 'vs/workbench/common/contextkeys';
import { OpenFolderAction, OpenFileFolderAction, OpenFolderViaWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; import { OpenFolderViaWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions';
import { OpenRecentAction } from 'vs/workbench/browser/actions/windowActions'; import { OpenRecentAction } from 'vs/workbench/browser/actions/windowActions';
import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
@@ -783,6 +783,72 @@ export class GettingStartedPage extends @@ -758,6 +758,72 @@ export class GettingStartedPage extends
$('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved")) $('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))
); );
@@ -81,7 +72,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
+ 'align-items: center', + 'align-items: center',
+ ].join(';'), + ].join(';'),
+ }, 'Get started ', $('span', { + }, 'Get started ', $('span', {
+ class: ThemeIcon.asClassName(Codicon.arrowRight), + class: Codicon.arrowRight.classNames,
+ style: [ + style: [
+ 'color: white', + 'color: white',
+ 'margin-left: 8px', + 'margin-left: 8px',
@@ -96,21 +87,38 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
+ ), + ),
+ ), + ),
+ ); + );
+ } + }
+ +
const leftColumn = $('.categories-column.categories-column-left', {},); const leftColumn = $('.categories-column.categories-column-left', {},);
const rightColumn = $('.categories-column.categories-column-right', {},); const rightColumn = $('.categories-column.categories-column-right', {},);
@@ -775,13 +841,23 @@ export class GettingStartedPage extends
@@ -832,6 +898,9 @@ export class GettingStartedPage extends const layoutLists = () => {
if (gettingStartedList.itemCount) {
this.container.classList.remove('noWalkthroughs');
- 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());
+ }
+
recentList.setLimit(5); recentList.setLimit(5);
reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
} }
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) { else {
+ prepend(rightColumn, gettingStartedCoder) this.container.classList.add('noWalkthroughs');
+ } - reset(leftColumn, startList.getDomElement());
}; + if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
+ reset(leftColumn, startList.getDomElement(), gettingStartedCoder);
featuredExtensionList.onDidChange(layoutFeaturedExtension); + } else {
+ reset(leftColumn, startList.getDomElement());
+ }
reset(rightColumn, recentList.getDomElement());
recentList.setLimit(10);
}
Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css 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 --- code-server.orig/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
@@ -135,7 +143,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.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts +++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
@@ -286,6 +286,11 @@ export interface IWorkbenchConstructionO @@ -276,6 +276,11 @@ export interface IWorkbenchConstructionO
*/ */
readonly isEnabledFileDownloads?: boolean readonly isEnabledFileDownloads?: boolean
@@ -146,24 +154,24 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
+ +
//#endregion //#endregion
//#region Profile options
Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts --- 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 +++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -39,6 +39,11 @@ export interface IBrowserWorkbenchEnviro @@ -37,6 +37,11 @@ export interface IBrowserWorkbenchEnviro
* Enable downloading files via menu actions.
*/
readonly isEnabledFileDownloads?: boolean; readonly isEnabledFileDownloads?: boolean;
+
/** + /**
+ * Enable Coder's custom getting started text. + * Enable Coder's custom getting started text.
+ */ + */
+ readonly isEnabledCoderGettingStarted?: boolean; + readonly isEnabledCoderGettingStarted?: boolean;
+ }
+ /**
* Gets whether a resolver extension is expected for the environment. export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
*/ @@ -99,6 +104,13 @@ export class BrowserWorkbenchEnvironment
readonly expectsResolverExtension: boolean;
@@ -123,6 +128,13 @@ export class BrowserWorkbenchEnvironment
return this.options.isEnabledFileDownloads; return this.options.isEnabledFileDownloads;
} }
@@ -181,7 +189,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.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts +++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -18,6 +18,7 @@ export const serverOptions: OptionDescri @@ -16,6 +16,7 @@ export const serverOptions: OptionDescri
'auth': { type: 'string' }, 'auth': { type: 'string' },
'disable-file-downloads': { type: 'boolean' }, 'disable-file-downloads': { type: 'boolean' },
'locale': { type: 'string' }, 'locale': { type: 'string' },
@@ -189,11 +197,11 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
/* ----- server setup ----- */ /* ----- server setup ----- */
@@ -101,6 +102,7 @@ export interface ServerParsedArgs { @@ -98,6 +99,7 @@ export interface ServerParsedArgs {
'auth'?: string 'auth'?: string
'disable-file-downloads'?: boolean; 'disable-file-downloads'?: boolean;
'locale'?: string 'locale'?: string
+ 'disable-getting-started-override'?: boolean, + 'disable-getting-started-override'?: boolean;
/* ----- server setup ----- */ /* ----- server setup ----- */
@@ -201,7 +209,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.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -335,6 +335,7 @@ export class WebClientServer { @@ -308,6 +308,7 @@ export class WebClientServer {
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
userDataPath: this._environmentService.userDataPath, userDataPath: this._environmentService.userDataPath,
isEnabledFileDownloads: !this._environmentService.args['disable-file-downloads'], isEnabledFileDownloads: !this._environmentService.args['disable-file-downloads'],
@@ -215,14 +223,14 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts +++ code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
@@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/ev @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/ev
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys'; 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, 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, 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, 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, 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, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
@@ -211,6 +211,7 @@ export class WorkbenchContextKeysHandler @@ -204,6 +204,7 @@ export class WorkbenchContextKeysHandler
// code-server // code-server
IsEnabledFileDownloads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileDownloads ?? true) IsEnabledFileDownloads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileDownloads ?? true)
@@ -234,8 +242,8 @@ Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
=================================================================== ===================================================================
--- code-server.orig/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 +++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
@@ -39,6 +39,7 @@ export const HasWebFileSystemAccess = ne @@ -33,6 +33,7 @@ export const IsFullscreenContext = new R
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 HasWebFileSystemAccess = new RawContextKey<boolean>('hasWebFileSystemAccess', false, true); // Support for FileSystemAccess web APIs (https://wicg.github.io/file-system-access)
export const IsEnabledFileDownloads = new RawContextKey<boolean>('isEnabledFileDownloads', true, true); export const IsEnabledFileDownloads = new RawContextKey<boolean>('isEnabledFileDownloads', true, true);
+export const IsEnabledCoderGettingStarted = new RawContextKey<boolean>('isEnabledCoderGettingStarted', true, true); +export const IsEnabledCoderGettingStarted = new RawContextKey<boolean>('isEnabledCoderGettingStarted', true, true);

106
patches/github-auth.diff Normal file
View File

@@ -0,0 +1,106 @@
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 || []),
+ ])
+ }));
+ }
}

View File

@@ -38,7 +38,7 @@ Index: code-server/lib/vscode/src/vs/server/node/server.main.ts
-const LOCAL_HISTORY_HOME = join(APP_SETTINGS_HOME, 'History'); -const LOCAL_HISTORY_HOME = join(APP_SETTINGS_HOME, 'History');
-const MACHINE_SETTINGS_HOME = join(USER_DATA_PATH, 'Machine'); -const MACHINE_SETTINGS_HOME = join(USER_DATA_PATH, 'Machine');
-args['user-data-dir'] = USER_DATA_PATH; -args['user-data-dir'] = USER_DATA_PATH;
-const APP_ROOT = dirname(FileAccess.asFileUri('').fsPath); -const APP_ROOT = dirname(FileAccess.asFileUri('', require).fsPath);
-const BUILTIN_EXTENSIONS_FOLDER_PATH = join(APP_ROOT, 'extensions'); -const BUILTIN_EXTENSIONS_FOLDER_PATH = join(APP_ROOT, 'extensions');
-args['builtin-extensions-dir'] = BUILTIN_EXTENSIONS_FOLDER_PATH; -args['builtin-extensions-dir'] = BUILTIN_EXTENSIONS_FOLDER_PATH;
-args['extensions-dir'] = args['extensions-dir'] || join(REMOTE_DATA_FOLDER, 'extensions'); -args['extensions-dir'] = args['extensions-dir'] || join(REMOTE_DATA_FOLDER, 'extensions');
@@ -58,7 +58,7 @@ Index: code-server/lib/vscode/src/vs/server/node/server.main.ts
+ const LOCAL_HISTORY_HOME = join(APP_SETTINGS_HOME, 'History'); + const LOCAL_HISTORY_HOME = join(APP_SETTINGS_HOME, 'History');
+ const MACHINE_SETTINGS_HOME = join(USER_DATA_PATH, 'Machine'); + const MACHINE_SETTINGS_HOME = join(USER_DATA_PATH, 'Machine');
+ args['user-data-dir'] = USER_DATA_PATH; + args['user-data-dir'] = USER_DATA_PATH;
+ const APP_ROOT = dirname(FileAccess.asFileUri('').fsPath); + const APP_ROOT = dirname(FileAccess.asFileUri('', require).fsPath);
+ const BUILTIN_EXTENSIONS_FOLDER_PATH = args['builtin-extensions-dir'] || join(APP_ROOT, 'extensions'); + const BUILTIN_EXTENSIONS_FOLDER_PATH = args['builtin-extensions-dir'] || join(APP_ROOT, 'extensions');
+ args['builtin-extensions-dir'] = BUILTIN_EXTENSIONS_FOLDER_PATH; + args['builtin-extensions-dir'] = BUILTIN_EXTENSIONS_FOLDER_PATH;
+ args['extensions-dir'] = args['extensions-dir'] || join(REMOTE_DATA_FOLDER, 'extensions'); + args['extensions-dir'] = args['extensions-dir'] || join(REMOTE_DATA_FOLDER, 'extensions');
@@ -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.orig/lib/vscode/src/vs/base/common/processes.ts
+++ code-server/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 @@ -111,6 +111,8 @@ export function sanitizeProcessEnvironme
/^VSCODE_(?!(PORTABLE|SHELL_LOGIN|ENV_REPLACE|ENV_APPEND|ENV_PREPEND)).+$/, /^VSCODE_(?!SHELL_LOGIN).+$/,
/^SNAP(|_.*)$/, /^SNAP(|_.*)$/,
/^GDK_PIXBUF_.+$/, /^GDK_PIXBUF_.+$/,
+ /^CODE_SERVER_.+$/, + /^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.orig/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ code-server/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts
@@ -77,8 +77,11 @@ export class BrowserDialogHandler extend @@ -143,8 +143,11 @@ export class BrowserDialogHandler implem
async about(): Promise<void> { async about(): Promise<void> {
const detailString = (useAgo: boolean): string => { const detailString = (useAgo: boolean): string => {
@@ -176,15 +176,15 @@ 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.orig/lib/vscode/src/vs/workbench/browser/web.main.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts +++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
@@ -65,6 +65,7 @@ import { mixin, safeStringify } from 'vs @@ -69,6 +69,7 @@ import { IndexedDB } from 'vs/base/brows
import { IndexedDB } from 'vs/base/browser/indexedDB'; import { BrowserCredentialsService } from 'vs/workbench/services/credentials/browser/credentialsService';
import { IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; import { IWorkspace } from 'vs/workbench/services/host/browser/browserHostService';
import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess';
+import { CodeServerClient } from 'vs/workbench/browser/client'; +import { CodeServerClient } from 'vs/workbench/browser/client';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IProgressService } from 'vs/platform/progress/common/progress'; import { IProgressService } from 'vs/platform/progress/common/progress';
import { DelayedLogChannel } from 'vs/workbench/services/output/common/delayedLogChannel'; import { DelayedLogChannel } from 'vs/workbench/services/output/common/delayedLogChannel';
@@ -130,6 +131,9 @@ export class BrowserMain extends Disposa @@ -117,6 +118,9 @@ export class BrowserMain extends Disposa
// Startup // Startup
const instantiationService = workbench.startup(); const instantiationService = workbench.startup();
@@ -198,7 +198,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts --- code-server.orig/lib/vscode/src/vs/base/common/product.ts
+++ code-server/lib/vscode/src/vs/base/common/product.ts +++ code-server/lib/vscode/src/vs/base/common/product.ts
@@ -55,6 +55,8 @@ export type ExtensionVirtualWorkspaceSup @@ -31,6 +31,8 @@ export type ExtensionVirtualWorkspaceSup
}; };
export interface IProductConfiguration { export interface IProductConfiguration {
@@ -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.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -306,6 +306,7 @@ export class WebClientServer { @@ -308,6 +308,7 @@ export class WebClientServer {
} : undefined; folderUri: resolveWorkspaceURI(this._environmentService.args['default-folder']),
workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']),
const productConfiguration = <Partial<IProductConfiguration>>{ productConfiguration: <Partial<IProductConfiguration>>{
+ codeServerVersion: this._productService.codeServerVersion, + codeServerVersion: this._productService.codeServerVersion,
embedderIdentifier: 'server-distro', embedderIdentifier: 'server-distro',
extensionsGallery: this._webExtensionResourceUrlTemplate ? { extensionsGallery: this._webExtensionResourceUrlTemplate ? {
...this._productService.extensionsGallery, ...this._productService.extensionsGallery,

View File

@@ -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.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -327,6 +327,7 @@ export class WebClientServer { @@ -303,6 +303,7 @@ export class WebClientServer {
const workbenchWebConfiguration = { const workbenchWebConfiguration = {
remoteAuthority, remoteAuthority,
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', 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.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts +++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
@@ -276,6 +276,11 @@ export interface IWorkbenchConstructionO @@ -266,6 +266,11 @@ export interface IWorkbenchConstructionO
*/ */
readonly configurationDefaults?: Record<string, any>; readonly configurationDefaults?: Record<string, any>;
@@ -43,12 +43,12 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
+ +
//#endregion //#endregion
//#region Profile options
Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts --- 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 +++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -102,7 +102,14 @@ export class BrowserWorkbenchEnvironment @@ -78,7 +78,14 @@ export class BrowserWorkbenchEnvironment
get logFile(): URI { return joinPath(this.windowLogsPath, 'window.log'); } get logFile(): URI { return joinPath(this.windowLogsPath, 'window.log'); }
@memoize @memoize

View File

@@ -8,7 +8,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts --- code-server.orig/lib/vscode/src/vs/base/common/product.ts
+++ code-server/lib/vscode/src/vs/base/common/product.ts +++ code-server/lib/vscode/src/vs/base/common/product.ts
@@ -58,6 +58,7 @@ export interface IProductConfiguration { @@ -34,6 +34,7 @@ export interface IProductConfiguration {
readonly codeServerVersion?: string readonly codeServerVersion?: string
readonly rootEndpoint?: string readonly rootEndpoint?: string
readonly updateEndpoint?: string readonly updateEndpoint?: string
@@ -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.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts +++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri' @@ -13,6 +13,7 @@ import { IEnvironmentService, INativeEnv
export const serverOptions: OptionDescriptions<Required<ServerParsedArgs>> = { export const serverOptions: OptionDescriptions<Required<ServerParsedArgs>> = {
/* ----- code-server ----- */ /* ----- code-server ----- */
'disable-update-check': { type: 'boolean' }, 'disable-update-check': { type: 'boolean' },
@@ -28,7 +28,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
/* ----- server setup ----- */ /* ----- server setup ----- */
@@ -95,6 +96,7 @@ export const serverOptions: OptionDescri @@ -92,6 +93,7 @@ export const serverOptions: OptionDescri
export interface ServerParsedArgs { export interface ServerParsedArgs {
/* ----- code-server ----- */ /* ----- code-server ----- */
'disable-update-check'?: boolean; '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.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -311,6 +311,7 @@ export class WebClientServer { @@ -313,6 +313,7 @@ export class WebClientServer {
codeServerVersion: this._productService.codeServerVersion, codeServerVersion: this._productService.codeServerVersion,
rootEndpoint: base, rootEndpoint: base,
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined, updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
+ logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined, + logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
embedderIdentifier: 'server-distro', embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery, extensionsGallery: this._productService.extensionsGallery,
}; },
Index: code-server/lib/vscode/src/vs/workbench/browser/client.ts Index: code-server/lib/vscode/src/vs/workbench/browser/client.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/client.ts --- code-server.orig/lib/vscode/src/vs/workbench/browser/client.ts

View File

@@ -12,14 +12,14 @@ in-between and has web extensions install directly from the marketplace.
This can be tested by setting EXTENSIONS_GALLERY set to: This can be tested by setting EXTENSIONS_GALLERY set to:
'{"serviceUrl": "https://my-extensions/api"}' '{"serviceUrl": "https://extensions.coder.com/api"}'
Index: code-server/lib/vscode/src/vs/platform/product/common/product.ts Index: code-server/lib/vscode/src/vs/platform/product/common/product.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/product/common/product.ts --- code-server.orig/lib/vscode/src/vs/platform/product/common/product.ts
+++ code-server/lib/vscode/src/vs/platform/product/common/product.ts +++ code-server/lib/vscode/src/vs/platform/product/common/product.ts
@@ -47,6 +47,16 @@ else if (globalThis._VSCODE_PRODUCT_JSON @@ -53,6 +53,16 @@ else if (typeof require?.__$__nodeRequir
version: pkg.version version: pkg.version
}); });
} }
@@ -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.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -113,7 +113,7 @@ export class WebClientServer { @@ -111,7 +111,7 @@ export class WebClientServer {
const serverRootPath = getRemoteServerRootPath(_productService); const serverRootPath = getRemoteServerRootPath(_productService);
this._staticRoute = `${serverRootPath}/static`; this._staticRoute = `${serverRootPath}/static`;
this._callbackRoute = `${serverRootPath}/callback`; this._callbackRoute = `${serverRootPath}/callback`;
@@ -49,22 +49,22 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
} }
/** /**
@@ -311,14 +311,7 @@ export class WebClientServer { @@ -312,14 +312,7 @@ export class WebClientServer {
codeServerVersion: this._productService.codeServerVersion, codeServerVersion: this._productService.codeServerVersion,
rootEndpoint: base, rootEndpoint: base,
embedderIdentifier: 'server-distro', embedderIdentifier: 'server-distro',
- extensionsGallery: this._webExtensionResourceUrlTemplate ? { - extensionsGallery: this._webExtensionResourceUrlTemplate ? {
- ...this._productService.extensionsGallery, - ...this._productService.extensionsGallery,
- 'resourceUrlTemplate': this._webExtensionResourceUrlTemplate.with({ - 'resourceUrlTemplate': this._webExtensionResourceUrlTemplate.with({
- scheme: 'http', - scheme: 'http',
- authority: remoteAuthority, - authority: remoteAuthority,
- path: `${this._webExtensionRoute}/${this._webExtensionResourceUrlTemplate.authority}${this._webExtensionResourceUrlTemplate.path}` - path: `${this._webExtensionRoute}/${this._webExtensionResourceUrlTemplate.authority}${this._webExtensionResourceUrlTemplate.path}`
- }).toString(true) - }).toString(true)
- } : undefined - } : undefined
+ extensionsGallery: this._productService.extensionsGallery, + extensionsGallery: this._productService.extensionsGallery,
},
callbackRoute: this._callbackRoute
}; };
if (!this._environmentService.isBuilt) {
Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
=================================================================== ===================================================================
--- code-server.orig/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 { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
-import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; -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'; export const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource';
@@ -77,7 +76,7 @@ export abstract class AbstractExtensionR
@@ -60,7 +59,7 @@ export abstract class AbstractExtensionR
private readonly _environmentService: IEnvironmentService, private readonly _environmentService: IEnvironmentService,
private readonly _configurationService: IConfigurationService, private readonly _configurationService: IConfigurationService,
) { ) {

View File

@@ -6,11 +6,24 @@ https://github.com/microsoft/vscode-extension-samples/tree/ddae6c0c9ff203b4ed6f6
We also override isProposedApiEnabled in case an extension does not declare the We also override isProposedApiEnabled in case an extension does not declare the
APIs it needs correctly (the Jupyter extension had this issue). APIs it needs correctly (the Jupyter extension had this issue).
Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
@@ -1482,7 +1482,7 @@ class ProposedApiController {
this._envEnabledExtensions = new Set((_environmentService.extensionEnabledProposedApi ?? []).map(id => ExtensionIdentifier.toKey(id)));
- this._envEnablesProposedApiForAll =
+ this._envEnablesProposedApiForAll = true ||
!_environmentService.isBuilt || // always allow proposed API when running out of sources
(_environmentService.isExtensionDevelopment && productService.quality !== 'stable') || // do not allow proposed API against stable builds when developing an extension
(this._envEnabledExtensions.size === 0 && Array.isArray(_environmentService.extensionEnabledProposedApi)); // always allow proposed API if --enable-proposed-api is provided without extension ID
Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts --- code-server.orig/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
+++ code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts +++ code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
@@ -282,10 +282,7 @@ function extensionDescriptionArrayToMap( @@ -364,10 +364,7 @@ function extensionDescriptionArrayToMap(
} }
export function isProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): boolean { export function isProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): boolean {
@@ -22,16 +35,3 @@ Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/extens
} }
export function checkProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): void { export function checkProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): void {
Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensionsProposedApi.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/extensions/common/extensionsProposedApi.ts
+++ code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensionsProposedApi.ts
@@ -24,7 +24,7 @@ export class ExtensionsProposedApi {
this._envEnabledExtensions = new Set((_environmentService.extensionEnabledProposedApi ?? []).map(id => ExtensionIdentifier.toKey(id)));
- this._envEnablesProposedApiForAll =
+ this._envEnablesProposedApiForAll = true ||
!_environmentService.isBuilt || // always allow proposed API when running out of sources
(_environmentService.isExtensionDevelopment && productService.quality !== 'stable') || // do not allow proposed API against stable builds when developing an extension
(this._envEnabledExtensions.size === 0 && Array.isArray(_environmentService.extensionEnabledProposedApi)); // always allow proposed API if --enable-proposed-api is provided without extension ID

View File

@@ -30,7 +30,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts --- code-server.orig/lib/vscode/src/vs/base/common/product.ts
+++ code-server/lib/vscode/src/vs/base/common/product.ts +++ code-server/lib/vscode/src/vs/base/common/product.ts
@@ -59,6 +59,7 @@ export interface IProductConfiguration { @@ -35,6 +35,7 @@ export interface IProductConfiguration {
readonly rootEndpoint?: string readonly rootEndpoint?: string
readonly updateEndpoint?: string readonly updateEndpoint?: string
readonly logoutEndpoint?: string readonly logoutEndpoint?: string
@@ -42,48 +42,70 @@ 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.orig/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts
+++ code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts
@@ -33,7 +33,7 @@ export class RemoteAuthorityResolverServ @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/comm
isWorkbenchOptionsBasedResolution: boolean, import { RemoteAuthorities } from 'vs/base/common/network';
connectionToken: Promise<string> | string | undefined, import { URI } from 'vs/base/common/uri';
resourceUriProvider: ((uri: URI) => URI) | undefined, import { IProductService } from 'vs/platform/product/common/productService';
- @IProductService productService: IProductService, -import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
+ @IProductService private readonly productService: IProductService, +import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolvedOptions, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
@ILogService private readonly _logService: ILogService, import { getRemoteServerRootPath, parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts';
) {
export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService {
@@ -23,7 +23,7 @@ export class RemoteAuthorityResolverServ
private readonly _connectionToken: Promise<string> | string | undefined;
private readonly _connectionTokens: Map<string, string>;
- constructor(@IProductService productService: IProductService, connectionToken: Promise<string> | string | undefined, resourceUriProvider: ((uri: URI) => URI) | undefined) {
+ constructor(@IProductService productService: IProductService, connectionToken: Promise<string> | string | undefined, resourceUriProvider: ((uri: URI) => URI) | undefined, private readonly proxyEndpointTemplate?: string) {
super(); super();
@@ -84,9 +84,14 @@ export class RemoteAuthorityResolverServ this._connectionToken = connectionToken;
this._connectionTokens = new Map<string, string>();
@@ -61,9 +61,14 @@ export class RemoteAuthorityResolverServ
private async _doResolveAuthority(authority: string): Promise<ResolverResult> {
const connectionToken = await Promise.resolve(this._connectionTokens.get(authority) || this._connectionToken); 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`);
+ let options: ResolvedOptions | undefined; + let options: ResolvedOptions | undefined;
+ if (this.productService.proxyEndpointTemplate) { + if (this.proxyEndpointTemplate) {
+ const proxyUrl = new URL(this.productService.proxyEndpointTemplate, window.location.href); + const proxyUrl = new URL(this.proxyEndpointTemplate, window.location.href);
+ options = { extensionHostEnv: { VSCODE_PROXY_URI: decodeURIComponent(proxyUrl.toString()) }} + options = { extensionHostEnv: { VSCODE_PROXY_URI: decodeURIComponent(proxyUrl.toString()) }}
+ } + }
const defaultPort = (/^https:/.test(window.location.href) ? 443 : 80); const defaultPort = (/^https:/.test(window.location.href) ? 443 : 80);
const { host, port } = parseAuthorityWithOptionalPort(authority, defaultPort); const { host, port } = parseAuthorityWithOptionalPort(authority, defaultPort);
- const result: ResolverResult = { authority: { authority, connectTo: new WebSocketRemoteConnection(host, port), connectionToken } }; - const result: ResolverResult = { authority: { authority, host: host, port: port, connectionToken } };
+ const result: ResolverResult = { authority: { authority, connectTo: new WebSocketRemoteConnection(host, port), connectionToken }, options }; + const result: ResolverResult = { authority: { authority, host: host, port: port, connectionToken }, options };
RemoteAuthorities.set(authority, host, port); RemoteAuthorities.set(authority, result.authority.host, result.authority.port);
this._cache.set(authority, result); this._cache.set(authority, result);
this._onDidChangeConnectionData.fire(); this._onDidChangeConnectionData.fire();
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
=================================================================== ===================================================================
--- code-server.orig/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 +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -312,6 +312,7 @@ export class WebClientServer { @@ -314,6 +314,7 @@ export class WebClientServer {
rootEndpoint: base, rootEndpoint: base,
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined, updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined, logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
+ proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/', + proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
embedderIdentifier: 'server-distro', embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery, extensionsGallery: this._productService.extensionsGallery,
}; },
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
@@ -248,7 +248,7 @@ export class BrowserMain extends Disposa
// Remote
const connectionToken = environmentService.options.connectionToken || getCookieValue(connectionTokenCookieName);
- const remoteAuthorityResolverService = new RemoteAuthorityResolverService(productService, connectionToken, this.configuration.resourceUriProvider);
+ const remoteAuthorityResolverService = new RemoteAuthorityResolverService(productService, connectionToken, this.configuration.resourceUriProvider, this.configuration.productConfiguration?.proxyEndpointTemplate);
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
// Signing
Index: code-server/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts 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.orig/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ code-server/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
@@ -271,7 +271,7 @@ export async function createTerminalEnvi @@ -381,7 +381,7 @@ export async function createTerminalEnvi
// Sanitize the environment, removing any undesirable VS Code and Electron environment // Sanitize the environment, removing any undesirable VS Code and Electron environment
// variables // variables
@@ -96,32 +118,33 @@ 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.orig/lib/vscode/src/vs/code/browser/workbench/workbench.ts
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts +++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
@@ -19,6 +19,7 @@ import { isFolderToOpen, isWorkspaceToOp @@ -21,6 +21,7 @@ import type { ICredentialsProvider } fro
import type { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService';
import type { IWorkbenchConstructionOptions } from 'vs/workbench/browser/web.api'; 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 type { IWorkspace, IWorkspaceProvider } from 'vs/workbench/services/host/browser/browserHostService';
+import { extractLocalHostUriMetaDataForPortMapping, TunnelOptions, TunnelCreationOptions } from 'vs/platform/tunnel/common/tunnel'; +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';
@@ -571,6 +572,39 @@ class WorkspaceProvider implements IWork interface ICredential {
settingsSyncOptions: config.settingsSyncOptions ? { enabled: config.settingsSyncOptions.enabled, } : undefined, service: string;
@@ -511,6 +512,38 @@ function doCreateUri(path: string, query
} : undefined,
workspaceProvider: WorkspaceProvider.create(config), workspaceProvider: WorkspaceProvider.create(config),
urlCallbackProvider: new LocalStorageURLCallbackProvider(config.callbackRoute), 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> => { + resolveExternalUri: (uri: URI): Promise<URI> => {
+ let resolvedUri = uri + let resolvedUri = uri
+ const localhostMatch = extractLocalHostUriMetaDataForPortMapping(resolvedUri) + const localhostMatch = extractLocalHostUriMetaDataForPortMapping(resolvedUri)
+
+ if (localhostMatch && resolvedUri.authority !== location.host) { + if (localhostMatch && resolvedUri.authority !== location.host) {
+ if (config.productConfiguration && config.productConfiguration.proxyEndpointTemplate) { + if (config.productConfiguration && config.productConfiguration.proxyEndpointTemplate) {
+ const renderedTemplate = config.productConfiguration.proxyEndpointTemplate + resolvedUri = URI.parse(new URL(config.productConfiguration.proxyEndpointTemplate.replace('{{port}}', localhostMatch.port.toString()), window.location.href).toString())
+ .replace('{{port}}', localhostMatch.port.toString())
+ .replace('{{host}}', window.location.host)
+ resolvedUri = URI.parse(new URL(renderedTemplate, window.location.href).toString())
+ } else { + } else {
+ throw new Error(`Failed to resolve external URI: ${uri.toString()}. Could not determine base url because productConfiguration missing.`) + 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) + return Promise.resolve(resolvedUri)
+ }, + },
+ tunnelProvider: { + tunnelProvider: {
@@ -140,20 +163,19 @@ 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 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.orig/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ code-server/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
@@ -76,7 +76,7 @@ export class ForwardedPortsView extends @@ -73,7 +73,7 @@ export class ForwardedPortsView extends
this.contextKeyListener = undefined; this.contextKeyListener = undefined;
} }
- const viewEnabled: boolean = !!forwardedPortsViewEnabled.getValue(this.contextKeyService); - const viewEnabled: boolean = !!forwardedPortsViewEnabled.getValue(this.contextKeyService);
+ const viewEnabled: boolean = true; + const viewEnabled: boolean = true;
if (viewEnabled) { if (this.environmentService.remoteAuthority && viewEnabled) {
const viewContainer = await this.getViewContainer(); const viewContainer = await this.getViewContainer();

View File

@@ -1,82 +0,0 @@
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,
Index: code-server/lib/vscode/src/vs/base/common/tfIdf.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/tfIdf.ts
+++ code-server/lib/vscode/src/vs/base/common/tfIdf.ts
@@ -88,8 +88,7 @@ export class TfIdfCalculator {
for (const [word] of input.matchAll(/\b\p{Letter}[\p{Letter}\d]{2,}\b/gu)) {
yield normalize(word);
- // eslint-disable-next-line local/code-no-look-behind-regex
- const camelParts = word.split(/(?<=[a-z])(?=[A-Z])/g);
+ const camelParts = word.split(/(?=[A-Z])/g);
if (camelParts.length > 1) {
for (const part of camelParts) {
// Require at least 3 letters in the parts of a camel case word

View File

@@ -9,6 +9,7 @@ update-check.diff
logout.diff logout.diff
store-socket.diff store-socket.diff
proxy-uri.diff proxy-uri.diff
github-auth.diff
unique-db.diff unique-db.diff
local-storage.diff local-storage.diff
service-worker.diff service-worker.diff
@@ -18,4 +19,3 @@ telemetry.diff
display-language.diff display-language.diff
cli-window-open.diff cli-window-open.diff
getting-started.diff getting-started.diff
safari.diff

View File

@@ -6,7 +6,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts --- code-server.orig/lib/vscode/src/vs/base/common/product.ts
+++ code-server/lib/vscode/src/vs/base/common/product.ts +++ code-server/lib/vscode/src/vs/base/common/product.ts
@@ -60,6 +60,10 @@ export interface IProductConfiguration { @@ -36,6 +36,10 @@ export interface IProductConfiguration {
readonly updateEndpoint?: string readonly updateEndpoint?: string
readonly logoutEndpoint?: string readonly logoutEndpoint?: string
readonly proxyEndpointTemplate?: string readonly proxyEndpointTemplate?: string
@@ -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.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -313,6 +313,10 @@ export class WebClientServer { @@ -316,6 +316,10 @@ export class WebClientServer {
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined, updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined, logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/', proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
+ serviceWorker: { + serviceWorker: {
+ scope: vscodeBase + '/', + scope: vscodeBase + '/',
+ path: base + '/_static/out/browser/serviceWorker.js', + path: base + '/_static/out/browser/serviceWorker.js',
+ }, + },
embedderIdentifier: 'server-distro', embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery, extensionsGallery: this._productService.extensionsGallery,
}; },

View File

@@ -10,7 +10,7 @@ Index: code-server/lib/vscode/build/gulpfile.reh.js
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/build/gulpfile.reh.js --- code-server.orig/lib/vscode/build/gulpfile.reh.js
+++ code-server/lib/vscode/build/gulpfile.reh.js +++ code-server/lib/vscode/build/gulpfile.reh.js
@@ -238,8 +238,7 @@ function packageTask(type, platform, arc @@ -192,8 +192,7 @@ function packageTask(type, platform, arc
const src = gulp.src(sourceFolderName + '/**', { base: '.' }) const src = gulp.src(sourceFolderName + '/**', { base: '.' })
.pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); })) .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 workspaceExtensionPoints = ['debuggers', 'jsonValidation'];
const isUIExtension = (manifest) => { const isUIExtension = (manifest) => {
@@ -278,9 +277,9 @@ function packageTask(type, platform, arc @@ -232,9 +231,9 @@ function packageTask(type, platform, arc
.map(name => `.build/extensions/${name}/**`); .map(name => `.build/extensions/${name}/**`);
const extensions = gulp.src(extensionPaths, { base: '.build', dot: true }); 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; let version = packageJson.version;
const quality = product.quality; const quality = product.quality;
@@ -427,7 +426,7 @@ function tweakProductForServerWeb(produc @@ -388,7 +387,7 @@ function tweakProductForServerWeb(produc
const minifyTask = task.define(`minify-vscode-${type}`, task.series( const minifyTask = task.define(`minify-vscode-${type}`, task.series(
optimizeTask, optimizeTask,
util.rimraf(`out-vscode-${type}-min`), util.rimraf(`out-vscode-${type}-min`),

View File

@@ -1,4 +1,4 @@
Store the IPC socket with workspace metadata. Store a static reference to the IPC socket
This lets us use it to open files inside code-server from outside of This lets us use it to open files inside code-server from outside of
code-server. code-server.
@@ -9,121 +9,29 @@ To test this:
It should open in your existing code-server instance. 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 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.orig/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
+++ code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts +++ code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
@@ -2,7 +2,7 @@ @@ -2,7 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
- -
+import * as _http from 'http'; +import { promises as fs } from 'fs';
+import * as os from 'os'
+import * as path from 'vs/base/common/path';
import * as performance from 'vs/base/common/performance'; import * as performance from 'vs/base/common/performance';
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl'; import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
@@ -17,6 +17,7 @@ import { ExtensionRuntime } from 'vs/wor @@ -72,6 +74,10 @@ export class ExtHostExtensionService ext
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; if (this._initData.remote.isRemote && this._initData.remote.authority) {
import { realpathSync } from 'vs/base/node/extpath'; const cliServer = this._instaService.createInstance(CLIServer);
import { ExtHostConsoleForwarder } from 'vs/workbench/api/node/extHostConsoleForwarder'; process.env['VSCODE_IPC_HOOK_CLI'] = cliServer.ipcHandlePath;
+import { IExtHostWorkspace } from '../common/extHostWorkspace';
import { ExtHostDiskFileSystemProvider } from 'vs/workbench/api/node/extHostDiskFileSystemProvider';
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. + fs.writeFile(path.join(os.tmpdir(), 'vscode-ipc'), cliServer.ipcHandlePath).catch((error) => {
const configProvider = await this._extHostConfiguration.getConfigProvider(); + this._logService.error(error);
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'; // Module loading tricks
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));

View File

@@ -1,7 +1,7 @@
Add support for telemetry endpoint Add support for telemetry endpoint
To test: To test:
1. Create a mock API using [RequestBin](https://requestbin.io/) or [Beeceptor](https://beeceptor.com/) 1. Create a RequestBin - https://requestbin.io/
2. Run code-server with `CS_TELEMETRY_URL` set: 2. Run code-server with `CS_TELEMETRY_URL` set:
i.e. `CS_TELEMETRY_URL="https://requestbin.io/1ebub9z1" ./code-server-<version>-macos-amd64/bin/code-server` i.e. `CS_TELEMETRY_URL="https://requestbin.io/1ebub9z1" ./code-server-<version>-macos-amd64/bin/code-server`
NOTE: it has to be a production build. NOTE: it has to be a production build.
@@ -12,26 +12,30 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
=================================================================== ===================================================================
--- code-server.orig/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 +++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
@@ -65,6 +65,7 @@ import { IExtensionsScannerService } fro @@ -71,6 +71,7 @@ import { IExtensionsScannerService } fro
import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService';
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { TelemetryClient } from 'vs/server/node/telemetryClient'; +import { TelemetryClient } from "vs/server/node/telemetryClient";
import { NullPolicyService } from 'vs/platform/policy/common/policy'; import { NullPolicyService } from 'vs/platform/policy/common/policy';
import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
import { LoggerService } from 'vs/platform/log/node/loggerService'; import { LoggerService } from 'vs/platform/log/node/loggerService';
@@ -148,7 +149,10 @@ export async function setupServerService @@ -139,10 +140,13 @@ export async function setupServerService
let oneDsAppender: ITelemetryAppender = NullAppender; const machineId = await getMachineId();
const isInternal = isInternalTelemetry(productService, configurationService); const isInternal = isInternalTelemetry(productService, configurationService);
if (supportsTelemetry(productService, environmentService)) { if (supportsTelemetry(productService, environmentService)) {
- if (!isLoggingOnly(productService, environmentService) && productService.aiConfig?.ariaKey) { - if (productService.aiConfig && productService.aiConfig.ariaKey) {
+ const telemetryEndpoint = process.env.CS_TELEMETRY_URL || "https://v1.telemetry.coder.com/track"; + const telemetryEndpoint = process.env.CS_TELEMETRY_URL || "https://v1.telemetry.coder.com/track";
+ if (telemetryEndpoint) { + if (telemetryEndpoint) {
+ oneDsAppender = new OneDataSystemAppender(requestService, false, eventPrefix, null, () => new TelemetryClient(telemetryEndpoint)); + oneDsAppender = new OneDataSystemAppender(false, eventPrefix, null, () => new TelemetryClient(telemetryEndpoint));
+ } else if (!isLoggingOnly(productService, environmentService) && productService.aiConfig?.ariaKey) { + } else if (productService.aiConfig && productService.aiConfig.ariaKey) {
oneDsAppender = new OneDataSystemAppender(requestService, isInternal, eventPrefix, null, 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 - 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 Index: code-server/lib/vscode/src/vs/server/node/telemetryClient.ts
=================================================================== ===================================================================
--- /dev/null --- /dev/null
@@ -90,11 +94,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.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -317,6 +317,7 @@ export class WebClientServer { @@ -321,6 +321,7 @@ export class WebClientServer {
scope: vscodeBase + '/', scope: vscodeBase + '/',
path: base + '/_static/out/browser/serviceWorker.js', path: base + '/_static/out/browser/serviceWorker.js',
},
+ enableTelemetry: this._productService.enableTelemetry,
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
}, },
+ enableTelemetry: this._productService.enableTelemetry,
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
};

View File

@@ -93,7 +93,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
=================================================================== ===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts --- code-server.orig/lib/vscode/src/vs/base/common/product.ts
+++ code-server/lib/vscode/src/vs/base/common/product.ts +++ code-server/lib/vscode/src/vs/base/common/product.ts
@@ -57,6 +57,7 @@ export type ExtensionVirtualWorkspaceSup @@ -33,6 +33,7 @@ export type ExtensionVirtualWorkspaceSup
export interface IProductConfiguration { export interface IProductConfiguration {
readonly codeServerVersion?: string readonly codeServerVersion?: string
readonly rootEndpoint?: string readonly rootEndpoint?: string
@@ -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.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -310,6 +310,7 @@ export class WebClientServer { @@ -312,6 +312,7 @@ export class WebClientServer {
const productConfiguration = <Partial<IProductConfiguration>>{ productConfiguration: <Partial<IProductConfiguration>>{
codeServerVersion: this._productService.codeServerVersion, codeServerVersion: this._productService.codeServerVersion,
rootEndpoint: base, rootEndpoint: base,
+ updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined, + updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
embedderIdentifier: 'server-distro', embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery, extensionsGallery: this._productService.extensionsGallery,
}; },
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
=================================================================== ===================================================================
--- code-server.orig/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 +++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -13,6 +13,8 @@ import { memoize } from 'vs/base/common/ @@ -11,6 +11,8 @@ import { refineServiceDecorator } from '
import { URI } from 'vs/base/common/uri'; import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
export const serverOptions: OptionDescriptions<Required<ServerParsedArgs>> = { export const serverOptions: OptionDescriptions<Required<ServerParsedArgs>> = {
+ /* ----- code-server ----- */ + /* ----- code-server ----- */
@@ -126,7 +126,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
/* ----- server setup ----- */ /* ----- server setup ----- */
@@ -91,6 +93,8 @@ export const serverOptions: OptionDescri @@ -88,6 +90,8 @@ export const serverOptions: OptionDescri
}; };
export interface ServerParsedArgs { export interface ServerParsedArgs {

View File

@@ -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.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts +++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -225,7 +225,7 @@ export class BrowserWorkbenchEnvironment @@ -210,7 +210,7 @@ export class BrowserWorkbenchEnvironment
@memoize @memoize
get webviewExternalEndpoint(): string { 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.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -323,6 +323,7 @@ export class WebClientServer { @@ -302,6 +302,7 @@ export class WebClientServer {
const workbenchWebConfiguration = { const workbenchWebConfiguration = {
remoteAuthority, remoteAuthority,
@@ -70,12 +70,12 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" <meta http-equiv="Content-Security-Policy"
- content="default-src 'none'; script-src 'sha256-QA1gXilHYAUFCvp7MpjgcmyBCFzSKV0SpiecMU8aUVc=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> - content="default-src 'none'; script-src 'sha256-lC8sxUeeYqUtmkCpPt/OX/HQdE0JbHG1Z3dzrilsRU0=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
+ content="default-src 'none'; script-src 'sha256-5X5RiKYn8NTJVx919WStPrAmsV80rIIBbePhKquPcAQ=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> + content="default-src 'none'; script-src 'sha256-/9/YQU12wvTeVXCsIGB4shLwdWrMceCpKojfkloNjPU=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
<!-- Disable pinch zooming --> <!-- Disable pinch zooming -->
<meta name="viewport" <meta name="viewport"
@@ -335,6 +335,12 @@ @@ -331,6 +331,12 @@
const hostname = location.hostname; 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.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 +++ code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
@@ -334,6 +334,12 @@ @@ -330,6 +330,12 @@
const hostname = location.hostname; const hostname = location.hostname;

View File

@@ -6,16 +6,10 @@
"matchUpdateTypes": ["minor", "patch", "digest"], "matchUpdateTypes": ["minor", "patch", "digest"],
"automerge": true, "automerge": true,
"groupName": "Minor dependency updates" "groupName": "Minor dependency updates"
},
{
"matchDepTypes": ["peerDependencies"],
"matchUpdateTypes": ["minor", "patch", "digest"],
"automerge": true,
"groupName": "Peer dependency updates"
} }
], ],
"vulnerabilityAlerts": { "vulnerabilityAlerts": {
"enabled": "true" "enabled": "true"
}, },
"ignoreDeps": ["express", "ansi-regex", "env-paths", "limiter", "node", "prettier"] "ignoreDeps": ["express"]
} }

View File

@@ -1,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />

View File

@@ -46,9 +46,7 @@ button {
.card-box { .card-box {
background-color: rgb(250, 253, 258); background-color: rgb(250, 253, 258);
border-radius: 5px; border-radius: 5px;
box-shadow: box-shadow: rgba(60, 66, 87, 0.117647) 0px 7px 14px 0px, rgba(0, 0, 0, 0.117647) 0px 3px 6px 0px;
rgba(60, 66, 87, 0.117647) 0px 7px 14px 0px,
rgba(0, 0, 0, 0.117647) 0px 3px 6px 0px;
max-width: 650px; max-width: 650px;
width: 100%; width: 100%;
} }

View File

@@ -1,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -10,7 +10,7 @@
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;" content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;"
/> />
<title>{{I18N_LOGIN_TITLE}}</title> <title>{{APP_NAME}} login</title>
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" /> <link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" /> <link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
<link rel="manifest" href="{{BASE}}/manifest.json" crossorigin="use-credentials" /> <link rel="manifest" href="{{BASE}}/manifest.json" crossorigin="use-credentials" />
@@ -25,7 +25,7 @@
<div class="card-box"> <div class="card-box">
<div class="header"> <div class="header">
<h1 class="main">{{WELCOME_TEXT}}</h1> <h1 class="main">{{WELCOME_TEXT}}</h1>
<div class="sub">{{I18N_LOGIN_BELOW}} {{PASSWORD_MSG}}</div> <div class="sub">Please log in below. {{PASSWORD_MSG}}</div>
</div> </div>
<div class="content"> <div class="content">
<form class="login-form" method="post"> <form class="login-form" method="post">
@@ -38,11 +38,11 @@
autofocus autofocus
class="password" class="password"
type="password" type="password"
placeholder="{{I18N_PASSWORD_PLACEHOLDER}}" placeholder="PASSWORD"
name="password" name="password"
autocomplete="current-password" autocomplete="current-password"
/> />
<input class="submit -button" value="{{I18N_SUBMIT}}" type="submit" /> <input class="submit -button" value="SUBMIT" type="submit" />
</div> </div>
{{ERROR}} {{ERROR}}
</form> </form>

View File

@@ -4,7 +4,6 @@ export enum HttpCode {
NotFound = 404, NotFound = 404,
BadRequest = 400, BadRequest = 400,
Unauthorized = 401, Unauthorized = 401,
Forbidden = 403,
LargePayload = 413, LargePayload = 413,
ServerError = 500, ServerError = 500,
} }
@@ -14,11 +13,7 @@ export enum HttpCode {
* used in the HTTP response. * used in the HTTP response.
*/ */
export class HttpError extends Error { export class HttpError extends Error {
public constructor( public constructor(message: string, public readonly statusCode: HttpCode, public readonly details?: object) {
message: string,
public readonly statusCode: HttpCode,
public readonly details?: object,
) {
super(message) super(message)
this.name = this.constructor.name this.name = this.constructor.name
} }

View File

@@ -9,11 +9,9 @@ import * as util from "../common/util"
import { DefaultedArgs } from "./cli" import { DefaultedArgs } from "./cli"
import { disposer } from "./http" import { disposer } from "./http"
import { isNodeJSErrnoException } from "./util" import { isNodeJSErrnoException } from "./util"
import { EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSocket"
import { handleUpgrade } from "./wsRouter" import { handleUpgrade } from "./wsRouter"
type SocketOptions = { socket: string; "socket-mode"?: string } type ListenOptions = Pick<DefaultedArgs, "socket-mode" | "socket" | "port" | "host">
type ListenOptions = DefaultedArgs | SocketOptions
export interface App extends Disposable { export interface App extends Disposable {
/** Handles regular HTTP requests. */ /** Handles regular HTTP requests. */
@@ -22,18 +20,12 @@ export interface App extends Disposable {
wsRouter: Express wsRouter: Express
/** The underlying HTTP server. */ /** The underlying HTTP server. */
server: http.Server server: http.Server
/** Handles requests to the editor session management API. */
editorSessionManagerServer: http.Server
} }
const isSocketOpts = (opts: ListenOptions): opts is SocketOptions => { export const listen = async (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
return !!(opts as SocketOptions).socket || !(opts as DefaultedArgs).host if (socket) {
}
export const listen = async (server: http.Server, opts: ListenOptions) => {
if (isSocketOpts(opts)) {
try { try {
await fs.unlink(opts.socket) await fs.unlink(socket)
} catch (error: any) { } catch (error: any) {
handleArgsSocketCatchError(error) handleArgsSocketCatchError(error)
} }
@@ -46,20 +38,18 @@ export const listen = async (server: http.Server, opts: ListenOptions) => {
server.on("error", (err) => util.logError(logger, "http server error", err)) server.on("error", (err) => util.logError(logger, "http server error", err))
resolve() resolve()
} }
if (isSocketOpts(opts)) { if (socket) {
server.listen(opts.socket, onListen) server.listen(socket, onListen)
} else { } else {
// [] is the correct format when using :: but Node errors with them. // [] is the correct format when using :: but Node errors with them.
server.listen(opts.port, opts.host.replace(/^\[|\]$/g, ""), onListen) server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
} }
}) })
// NOTE@jsjoeio: we need to chmod after the server is finished // NOTE@jsjoeio: we need to chmod after the server is finished
// listening. Otherwise, the socket may not have been created yet. // listening. Otherwise, the socket may not have been created yet.
if (isSocketOpts(opts)) { if (socket && mode) {
if (opts["socket-mode"]) { await fs.chmod(socket, mode)
await fs.chmod(opts.socket, opts["socket-mode"])
}
} }
} }
@@ -80,22 +70,14 @@ export const createApp = async (args: DefaultedArgs): Promise<App> => {
) )
: http.createServer(router) : http.createServer(router)
const disposeServer = disposer(server) const dispose = disposer(server)
await listen(server, args) await listen(server, args)
const wsRouter = express() const wsRouter = express()
handleUpgrade(wsRouter, server) handleUpgrade(wsRouter, server)
const editorSessionManager = new EditorSessionManager() return { router, wsRouter, server, dispose }
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 }
} }
/** /**

View File

@@ -1,9 +1,11 @@
import { field, Level, logger } from "@coder/logger" import { field, Level, logger } from "@coder/logger"
import { promises as fs } from "fs" import { promises as fs } from "fs"
import { load } from "js-yaml" import { load } from "js-yaml"
import * as os from "os"
import * as path from "path" import * as path from "path"
import { generateCertificate, generatePassword, paths, splitOnFirstEquals } from "./util" import { canConnect, generateCertificate, generatePassword, humanPath, paths, isNodeJSErrnoException } from "./util"
import { EditorSessionManagerClient } from "./vscodeSocket"
const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc")
export enum Feature { export enum Feature {
// No current experimental features! // No current experimental features!
@@ -50,8 +52,6 @@ export interface UserProvidedCodeArgs {
"disable-file-downloads"?: boolean "disable-file-downloads"?: boolean
"disable-workspace-trust"?: boolean "disable-workspace-trust"?: boolean
"disable-getting-started-override"?: boolean "disable-getting-started-override"?: boolean
"disable-proxy"?: boolean
"session-socket"?: string
} }
/** /**
@@ -79,12 +79,12 @@ export interface UserProvidedArgs extends UserProvidedCodeArgs {
"bind-addr"?: string "bind-addr"?: string
socket?: string socket?: string
"socket-mode"?: string "socket-mode"?: string
"trusted-origins"?: string[]
version?: boolean version?: boolean
"proxy-domain"?: string[] "proxy-domain"?: string[]
"reuse-window"?: boolean "reuse-window"?: boolean
"new-window"?: boolean "new-window"?: boolean
"ignore-last-opened"?: boolean "ignore-last-opened"?: boolean
link?: OptionalString
verbose?: boolean verbose?: boolean
"app-name"?: string "app-name"?: string
"welcome-text"?: string "welcome-text"?: string
@@ -162,9 +162,6 @@ 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" + "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.", "then notifies you once every week that a new release is available.",
}, },
"session-socket": {
type: "string",
},
"disable-file-downloads": { "disable-file-downloads": {
type: "boolean", type: "boolean",
description: description:
@@ -178,23 +175,12 @@ export const options: Options<Required<UserProvidedArgs>> = {
type: "boolean", type: "boolean",
description: "Disable the coder/coder override in the Help: Getting Started page.", 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 // --enable can be used to enable experimental features. These features
// provide no guarantees. // provide no guarantees.
enable: { type: "string[]" }, enable: { type: "string[]" },
help: { type: "boolean", short: "h", description: "Show this output." }, help: { type: "boolean", short: "h", description: "Show this output." },
json: { type: "boolean" }, json: { type: "boolean" },
locale: { locale: { type: "string" }, // The preferred way to set the locale is via the UI.
// The preferred way to set the locale is via the UI.
type: "string",
description: `
Set vscode display language and language to show on the login page, more info see
https://en.wikipedia.org/wiki/IETF_language_tag
`,
},
open: { type: "boolean", description: "Open in browser on startup. Does not work remotely." }, open: { type: "boolean", description: "Open in browser on startup. Does not work remotely." },
"bind-addr": { "bind-addr": {
@@ -213,11 +199,6 @@ export const options: Options<Required<UserProvidedArgs>> = {
socket: { type: "string", path: true, description: "Path to a socket (bind-addr will be ignored)." }, 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." }, "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." }, version: { type: "boolean", short: "v", description: "Display version information." },
_: { type: "string[]" }, _: { type: "string[]" },
@@ -274,6 +255,15 @@ export const options: Options<Required<UserProvidedArgs>> = {
short: "w", short: "w",
description: "Text to show on login page", description: "Text to show on login page",
}, },
link: {
type: OptionalString,
description: `
Securely bind code-server via our cloud service with the passed name. You'll get a URL like
https://hostname-username.coder.co at which you can easily access your code-server instance.
Authorization is done via GitHub.
`,
deprecated: true,
},
} }
export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedArgs>>> = options): string[] => { export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedArgs>>> = options): string[] => {
@@ -305,6 +295,19 @@ export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedAr
}) })
} }
export function splitOnFirstEquals(str: string): string[] {
// we use regex instead of "=" to ensure we split at the first
// "=" and return the following substring with it
// important for the hashed-password which looks like this
// $argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY
// 2 means return two items
// Source: https://stackoverflow.com/a/4607799/3015595
// We use the ? to say the the substr after the = is optional
const split = str.split(/=(.+)?/, 2)
return split
}
/** /**
* Parse arguments into UserProvidedArgs. This should not go beyond checking * Parse arguments into UserProvidedArgs. This should not go beyond checking
* that arguments are valid types and have values when required. * that arguments are valid types and have values when required.
@@ -438,23 +441,19 @@ export const parse = (
throw new Error("--cert-key is missing") throw new Error("--cert-key is missing")
} }
logger.debug(() => [`parsed ${opts?.configFile ? "config" : "command line"}`, field("args", redactArgs(args))]) 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,
}),
])
return 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 * User-provided arguments with defaults. The distinction between user-provided
* args and defaulted args exists so we can tell the difference between end * args and defaulted args exists so we can tell the difference between end
@@ -473,7 +472,6 @@ export interface DefaultedArgs extends ConfigArgs {
usingEnvHashedPassword: boolean usingEnvHashedPassword: boolean
"extensions-dir": string "extensions-dir": string
"user-data-dir": string "user-data-dir": string
"session-socket": string
/* Positional arguments. */ /* Positional arguments. */
_: string[] _: string[]
} }
@@ -494,11 +492,6 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args["extensions-dir"] = path.join(args["user-data-dir"], "extensions") 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 // --verbose takes priority over --log and --log takes priority over the
// environment variable. // environment variable.
if (args.verbose) { if (args.verbose) {
@@ -547,6 +540,17 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args.host = addr.host args.host = addr.host
args.port = addr.port args.port = addr.port
// If we're being exposed to the cloud, we listen on a random address and
// disable auth.
if (args.link) {
args.host = "localhost"
args.port = 0
args.socket = undefined
args["socket-mode"] = undefined
args.cert = undefined
args.auth = AuthType.None
}
if (args.cert && !args.cert.value) { if (args.cert && !args.cert.value) {
const { cert, certKey } = await generateCertificate(args["cert-host"] || "localhost") const { cert, certKey } = await generateCertificate(args["cert-host"] || "localhost")
args.cert = { args.cert = {
@@ -568,10 +572,6 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args["disable-getting-started-override"] = true 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 const usingEnvHashedPassword = !!process.env.HASHED_PASSWORD
if (process.env.HASHED_PASSWORD) { if (process.env.HASHED_PASSWORD) {
args["hashed-password"] = process.env.HASHED_PASSWORD args["hashed-password"] = process.env.HASHED_PASSWORD
@@ -589,25 +589,12 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
// Filter duplicate proxy domains and remove any leading `*.`. // Filter duplicate proxy domains and remove any leading `*.`.
const proxyDomains = new Set((args["proxy-domain"] || []).map((d) => d.replace(/^\*\./, ""))) const proxyDomains = new Set((args["proxy-domain"] || []).map((d) => d.replace(/^\*\./, "")))
const finalProxies = [] args["proxy-domain"] = Array.from(proxyDomains)
for (const proxyDomain of proxyDomains) { if (typeof args._ === "undefined") {
if (!proxyDomain.includes("{{port}}")) { args._ = []
finalProxies.push("{{port}}." + proxyDomain)
} else {
finalProxies.push(proxyDomain)
}
} }
// 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 { return {
...args, ...args,
usingEnvPassword, usingEnvPassword,
@@ -615,10 +602,6 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
} as DefaultedArgs // TODO: Technically no guarantee this is fulfilled. } 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. * Helper function to return the default config file.
* *
@@ -662,7 +645,7 @@ export async function readConfigFile(configPath?: string): Promise<ConfigArgs> {
await fs.writeFile(configPath, defaultConfigFile(generatedPassword), { await fs.writeFile(configPath, defaultConfigFile(generatedPassword), {
flag: "wx", // wx means to fail if the path exists. flag: "wx", // wx means to fail if the path exists.
}) })
logger.info(`Wrote default config file to ${configPath}`) logger.info(`Wrote default config file to ${humanPath(os.homedir(), configPath)}`)
} catch (error: any) { } catch (error: any) {
// EEXIST is fine; we don't want to overwrite existing configurations. // EEXIST is fine; we don't want to overwrite existing configurations.
if (error.code !== "EEXIST") { if (error.code !== "EEXIST") {
@@ -732,9 +715,6 @@ export function bindAddrFromArgs(addr: Addr, args: UserProvidedArgs): Addr {
if (args["bind-addr"]) { if (args["bind-addr"]) {
addr = parseBindAddr(args["bind-addr"]) addr = parseBindAddr(args["bind-addr"])
} }
if (process.env.CODE_SERVER_HOST) {
addr.host = process.env.CODE_SERVER_HOST
}
if (args.host) { if (args.host) {
addr.host = args.host addr.host = args.host
} }
@@ -761,38 +741,47 @@ function bindAddrFromAllSources(...argsConfig: UserProvidedArgs[]): Addr {
return 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 * 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 * existing instance. The arguments here should be the arguments the user
* explicitly passed on the command line, *NOT DEFAULTS* or the configuration. * explicitly passed on the command line, *NOT DEFAULTS* or the configuration.
*/ */
export const shouldOpenInExistingInstance = async ( export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Promise<string | undefined> => {
args: UserProvidedArgs,
sessionSocket: string,
): Promise<string | undefined> => {
// Always use the existing instance if we're running from VS Code's terminal. // Always use the existing instance if we're running from VS Code's terminal.
if (process.env.VSCODE_IPC_HOOK_CLI) { if (process.env.VSCODE_IPC_HOOK_CLI) {
logger.debug("Found VSCODE_IPC_HOOK_CLI") logger.debug("Found VSCODE_IPC_HOOK_CLI")
return process.env.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 // If these flags are set then assume the user is trying to open in an
// existing instance since these flags have no effect otherwise. That means // existing instance since these flags have no effect otherwise.
// 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) => { const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => {
return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev
}, 0) }, 0)
if (openInFlagCount > 0) { if (openInFlagCount > 0) {
logger.debug("Found --reuse-window or --new-window") logger.debug("Found --reuse-window or --new-window")
const socketPath = await client.getConnectedSocketPath(paths[0]) return readSocketPath(DEFAULT_SOCKET_PATH)
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. // It's possible the user is trying to spawn another instance of code-server.
@@ -800,13 +789,9 @@ export const shouldOpenInExistingInstance = async (
// code-server is invoked exactly like this: `code-server my-file`). // code-server is invoked exactly like this: `code-server my-file`).
// 2. That a file or directory was passed. // 2. That a file or directory was passed.
// 3. That the socket is active. // 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) { if (Object.keys(args).length === 1 && typeof args._ !== "undefined" && args._.length > 0) {
if (!(await client.canConnect())) { const socketPath = await readSocketPath(DEFAULT_SOCKET_PATH)
return undefined if (socketPath && (await canConnect(socketPath))) {
}
const socketPath = await client.getConnectedSocketPath(paths[0])
if (socketPath) {
logger.debug("Found existing code-server socket") logger.debug("Found existing code-server socket")
return socketPath return socketPath
} }

43
src/node/coder_cloud.ts Normal file
View File

@@ -0,0 +1,43 @@
import { logger } from "@coder/logger"
import { spawn } from "child_process"
import path from "path"
import split2 from "split2"
// https://github.com/coder/coder-cloud
const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent")
function runAgent(...args: string[]): Promise<void> {
logger.debug(`running agent with ${args}`)
const agent = spawn(coderCloudAgent, args, {
stdio: ["inherit", "inherit", "pipe"],
})
agent.stderr.pipe(split2()).on("data", (line) => {
line = line.replace(/^[0-9-]+ [0-9:]+ [^ ]+\t/, "")
logger.info(line)
})
return new Promise((res, rej) => {
agent.on("error", rej)
agent.on("close", (code) => {
if (code !== 0) {
rej({
message: `--link agent exited with ${code}`,
})
return
}
res()
})
})
}
export function coderCloudBind(address: URL | string, serverName = ""): Promise<void> {
if (typeof address === "string") {
throw new Error("Cannot link socket paths")
}
// Address needs to be in hostname:port format without the protocol.
return runAgent("bind", `--code-server-addr=${address.host}`, serverName)
}

View File

@@ -51,7 +51,7 @@ async function entry(): Promise<void> {
return runCodeCli(args) return runCodeCli(args)
} }
const socketPath = await shouldOpenInExistingInstance(cliArgs, args["session-socket"]) const socketPath = await shouldOpenInExistingInstance(cliArgs)
if (socketPath) { if (socketPath) {
logger.debug("Trying to open in existing instance") logger.debug("Trying to open in existing instance")
return openInExistingInstance(args, socketPath) return openInExistingInstance(args, socketPath)

View File

@@ -9,10 +9,7 @@ export class Heart {
private heartbeatInterval = 60000 private heartbeatInterval = 60000
public lastHeartbeat = 0 public lastHeartbeat = 0
public constructor( public constructor(private readonly heartbeatPath: string, private readonly isActive: () => Promise<boolean>) {
private readonly heartbeatPath: string,
private readonly isActive: () => Promise<boolean>,
) {
this.beat = this.beat.bind(this) this.beat = this.beat.bind(this)
this.alive = this.alive.bind(this) this.alive = this.alive.bind(this)
} }

View File

@@ -12,15 +12,7 @@ import { version as codeServerVersion } from "./constants"
import { Heart } from "./heart" import { Heart } from "./heart"
import { CoderSettings, SettingsProvider } from "./settings" import { CoderSettings, SettingsProvider } from "./settings"
import { UpdateProvider } from "./update" import { UpdateProvider } from "./update"
import { import { getPasswordMethod, IsCookieValidArgs, isCookieValid, sanitizeString, escapeHtml, escapeJSON } from "./util"
getPasswordMethod,
IsCookieValidArgs,
isCookieValid,
sanitizeString,
escapeHtml,
escapeJSON,
splitOnFirstEquals,
} from "./util"
/** /**
* Base options included on every page. * Base options included on every page.
@@ -75,25 +67,6 @@ export const replaceTemplates = <T extends object>(
.replace("{{OPTIONS}}", () => escapeJSON(serverOptions)) .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. * Throw an error if not authorized. Call `next` if provided.
*/ */
@@ -335,91 +308,3 @@ export const getCookieOptions = (req: express.Request): express.CookieOptions =>
export const self = (req: express.Request): string => { export const self = (req: express.Request): string => {
return normalize(`${req.baseUrl}${req.originalUrl.endsWith("/") ? "/" : ""}`, true) return normalize(`${req.baseUrl}${req.originalUrl.endsWith("/") ? "/" : ""}`, true)
} }
function getFirstHeader(req: http.IncomingMessage, headerName: string): string | undefined {
const val = req.headers[headerName]
return Array.isArray(val) ? val[0] : val
}
/**
* Throw a forbidden error if origin checks fail. Call `next` if provided.
*/
export function ensureOrigin(req: express.Request, _?: express.Response, next?: express.NextFunction): void {
try {
authenticateOrigin(req)
if (next) {
next()
}
} catch (error) {
logger.debug(`${error instanceof Error ? error.message : error}; blocking request to ${req.originalUrl}`)
throw new HttpError("Forbidden", HttpCode.Forbidden)
}
}
/**
* Authenticate the request origin against the host. Throw if invalid.
*/
export function authenticateOrigin(req: express.Request): void {
// A missing origin probably means the source is non-browser. Not sure we
// have a use case for this but let it through.
const originRaw = getFirstHeader(req, "origin")
if (!originRaw) {
return
}
let origin: string
try {
origin = new URL(originRaw).host.trim().toLowerCase()
} catch (error) {
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
// forward the host which means we cannot perform the check. Emit an error
// so an admin can fix the issue.
logger.error("No host headers found")
logger.error("Are you behind a reverse proxy that does not forward the host?")
throw new Error("no host headers found")
}
if (host !== origin) {
throw new Error(`host "${host}" does not match origin "${origin}"`)
}
}
/**
* Get the host from headers. It will be trimmed and lowercased.
*/
export function getHost(req: express.Request): string | undefined {
// Honor Forwarded if present.
const forwardedRaw = getFirstHeader(req, "forwarded")
if (forwardedRaw) {
const parts = forwardedRaw.split(/[;,]/)
for (let i = 0; i < parts.length; ++i) {
const [key, value] = splitOnFirstEquals(parts[i])
if (key.trim().toLowerCase() === "host" && value) {
return value.trim().toLowerCase()
}
}
}
// Honor X-Forwarded-Host if present. Some reverse proxies will set multiple
// comma-separated hosts.
const xHost = getFirstHeader(req, "x-forwarded-host")
if (xHost) {
const firstXHost = xHost.split(",")[0]
if (firstXHost) {
return firstXHost.trim().toLowerCase()
}
}
const host = getFirstHeader(req, "host")
return host ? host.trim().toLowerCase() : undefined
}

View File

@@ -1,33 +0,0 @@
import i18next, { init } from "i18next"
import * as en from "./locales/en.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.
returnNull: false,
lowerCaseLng: true,
debug: process.env.NODE_ENV === "development",
resources: {
en: {
translation: en,
},
"zh-cn": {
translation: zhCn,
},
th: {
translation: th,
},
ja: {
translation: ja,
},
ur: {
translation: ur,
},
},
})
export default i18next

View File

@@ -1,13 +0,0 @@
{
"LOGIN_TITLE": "{{app}} login",
"LOGIN_BELOW": "Please log in below.",
"WELCOME": "Welcome to {{app}}",
"LOGIN_PASSWORD": "Check the config file at {{configFile}} for the password.",
"LOGIN_USING_ENV_PASSWORD": "Password was set from $PASSWORD.",
"LOGIN_USING_HASHED_PASSWORD": "Password was set from $HASHED_PASSWORD.",
"SUBMIT": "SUBMIT",
"PASSWORD_PLACEHOLDER": "PASSWORD",
"LOGIN_RATE_LIMIT": "Login rate limited!",
"MISS_PASSWORD": "Missing password",
"INCORRECT_PASSWORD": "Incorrect password"
}

View File

@@ -1,13 +0,0 @@
{
"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": "パスワードが間違っています。"
}

View File

@@ -1,13 +0,0 @@
{
"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": "รหัสผ่านไม่ถูกต้อง"
}

View File

@@ -1,13 +0,0 @@
{
"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": "غلط پاس ورڈ"
}

View File

@@ -1,13 +0,0 @@
{
"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": "密码不正确"
}

View File

@@ -1,12 +1,15 @@
import { field, logger } from "@coder/logger" import { field, logger } from "@coder/logger"
import http from "http" import http from "http"
import * as os from "os"
import path from "path"
import { Disposable } from "../common/emitter" import { Disposable } from "../common/emitter"
import { plural } from "../common/util" import { plural } from "../common/util"
import { createApp, ensureAddress } from "./app" import { createApp, ensureAddress } from "./app"
import { AuthType, DefaultedArgs, Feature, SpawnCodeCli, toCodeArgs, UserProvidedArgs } from "./cli" import { AuthType, DefaultedArgs, Feature, SpawnCodeCli, toCodeArgs, UserProvidedArgs } from "./cli"
import { coderCloudBind } from "./coder_cloud"
import { commit, version } from "./constants" import { commit, version } from "./constants"
import { register } from "./routes" import { register } from "./routes"
import { isDirectory, loadAMDModule, open } from "./util" import { humanPath, isDirectory, loadAMDModule, open } from "./util"
/** /**
* Return true if the user passed an extension-related VS Code flag. * Return true if the user passed an extension-related VS Code flag.
@@ -68,8 +71,9 @@ export const openInExistingInstance = async (args: DefaultedArgs, socketPath: st
forceNewWindow: args["new-window"], forceNewWindow: args["new-window"],
gotoLineMode: true, gotoLineMode: true,
} }
for (let i = 0; i < args._.length; i++) { const paths = args._ || []
const fp = args._[i] for (let i = 0; i < paths.length; i++) {
const fp = path.resolve(paths[i])
if (await isDirectory(fp)) { if (await isDirectory(fp)) {
pipeArgs.folderURIs.push(fp) pipeArgs.folderURIs.push(fp)
} else { } else {
@@ -108,8 +112,8 @@ export const runCodeServer = async (
): Promise<{ dispose: Disposable["dispose"]; server: http.Server }> => { ): Promise<{ dispose: Disposable["dispose"]; server: http.Server }> => {
logger.info(`code-server ${version} ${commit}`) logger.info(`code-server ${version} ${commit}`)
logger.info(`Using user-data-dir ${args["user-data-dir"]}`) logger.info(`Using user-data-dir ${humanPath(os.homedir(), args["user-data-dir"])}`)
logger.trace(`Using extensions-dir ${args["extensions-dir"]}`) logger.trace(`Using extensions-dir ${humanPath(os.homedir(), args["extensions-dir"])}`)
if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) { if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) {
throw new Error( throw new Error(
@@ -122,8 +126,13 @@ export const runCodeServer = async (
const serverAddress = ensureAddress(app.server, protocol) const serverAddress = ensureAddress(app.server, protocol)
const disposeRoutes = await register(app, args) const disposeRoutes = await register(app, args)
logger.info(`Using config file ${args.config}`) logger.info(`Using config file ${humanPath(os.homedir(), args.config)}`)
logger.info(`${protocol.toUpperCase()} server listening on ${serverAddress.toString()}`) logger.info(
`${protocol.toUpperCase()} server listening on ${serverAddress.toString()} ${
args.link ? "(randomized by --link)" : ""
}`,
)
if (args.auth === AuthType.Password) { if (args.auth === AuthType.Password) {
logger.info(" - Authentication is enabled") logger.info(" - Authentication is enabled")
if (args.usingEnvPassword) { if (args.usingEnvPassword) {
@@ -131,31 +140,26 @@ export const runCodeServer = async (
} else if (args.usingEnvHashedPassword) { } else if (args.usingEnvHashedPassword) {
logger.info(" - Using password from $HASHED_PASSWORD") logger.info(" - Using password from $HASHED_PASSWORD")
} else { } else {
logger.info(` - Using password from ${args.config}`) logger.info(` - Using password from ${humanPath(os.homedir(), args.config)}`)
} }
} else { } else {
logger.info(" - Authentication is disabled") logger.info(` - Authentication is disabled ${args.link ? "(disabled by --link)" : ""}`)
} }
if (args.cert) { if (args.cert) {
logger.info(` - Using certificate for HTTPS: ${args.cert.value}`) logger.info(` - Using certificate for HTTPS: ${humanPath(os.homedir(), args.cert.value)}`)
} else { } else {
logger.info(" - Not serving HTTPS") logger.info(` - Not serving HTTPS ${args.link ? "(disabled by --link)" : ""}`)
} }
if (args["disable-proxy"]) { if (args["proxy-domain"].length > 0) {
logger.info(" - Proxy disabled")
} else if (args["proxy-domain"].length > 0) {
logger.info(` - ${plural(args["proxy-domain"].length, "Proxying the following domain")}:`) 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 (args.link) {
if (sessionServerAddress) { await coderCloudBind(serverAddress, args.link.value)
logger.info(`Session server listening on ${sessionServerAddress.toString()}`) logger.info(" - Connected to cloud agent")
} }
if (args.enable && args.enable.length > 0) { if (args.enable && args.enable.length > 0) {

View File

@@ -3,19 +3,9 @@ import { HttpCode } from "../common/http"
export const proxy = proxyServer.createProxyServer({}) export const proxy = proxyServer.createProxyServer({})
// The error handler catches when the proxy fails to connect (for example when
// there is nothing running on the target port).
proxy.on("error", (error, _, res) => { proxy.on("error", (error, _, res) => {
// This could be for either a web socket or a regular request. Despite what res.writeHead(HttpCode.ServerError)
// the types say, writeHead() will not exist on web socket requests (nor will res.end(error.message)
// status() from Express). But writing out the code manually does not work
// for regular requests thus the branching behavior.
if (typeof res.writeHead !== "undefined") {
res.writeHead(HttpCode.ServerError)
res.end(error.message)
} else {
res.end(`HTTP/1.1 ${HttpCode.ServerError} ${error.message}\r\n\r\n`)
}
}) })
// Intercept the response to rewrite absolute redirects against the base path. // Intercept the response to rewrite absolute redirects against the base path.

View File

@@ -1,56 +1,34 @@
import { Request, Router } from "express" import { Request, Router } from "express"
import { HttpCode, HttpError } from "../../common/http" import { HttpCode, HttpError } from "../../common/http"
import { getHost, ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http" import { authenticated, ensureAuthenticated, redirect, self } from "../http"
import { proxy } from "../proxy" import { proxy } from "../proxy"
import { Router as WsRouter } from "../wsRouter" import { Router as WsRouter } from "../wsRouter"
export const router = Router() 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. * 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
* The proxy-domain should be of format anyprefix-{{port}}-anysuffix.{{host}}, where {{host}} is optional * should return `undefined` and will be handled as normal.
* 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 => { const maybeProxy = (req: Request): string | undefined => {
const reqDomain = getHost(req) // Split into parts.
if (reqDomain === undefined) { 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)) {
return undefined return undefined
} }
const regexs = proxyDomainsToRegex(req.args["proxy-domain"]) return port
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) => { router.all("*", async (req, res, next) => {
@@ -59,8 +37,6 @@ router.all("*", async (req, res, next) => {
return next() return next()
} }
ensureProxyEnabled(req)
// Must be authenticated to use the proxy. // Must be authenticated to use the proxy.
const isAuthenticated = await authenticated(req) const isAuthenticated = await authenticated(req)
if (!isAuthenticated) { if (!isAuthenticated) {
@@ -103,9 +79,9 @@ wsRouter.ws("*", async (req, _, next) => {
return next() return next()
} }
ensureProxyEnabled(req) // Must be authenticated to use the proxy.
ensureOrigin(req)
await ensureAuthenticated(req) await ensureAuthenticated(req)
proxy.ws(req, req.ws, req.head, { proxy.ws(req, req.ws, req.head, {
ignorePath: true, ignorePath: true,
target: `http://0.0.0.0:${port}${req.originalUrl}`, target: `http://0.0.0.0:${port}${req.originalUrl}`,

View File

@@ -62,16 +62,6 @@ export const errorHandler: express.ErrorRequestHandler = async (err, req, res, n
} }
export const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => { export const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
let statusCode = 500 logger.error(`${err.message} ${err.stack}`)
if (errorHasStatusCode(err)) { ;(req as WebsocketRequest).ws.end()
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`)
} }

View File

@@ -33,15 +33,7 @@ import { CodeServerRouteWrapper } from "./vscode"
export const register = async (app: App, args: DefaultedArgs): Promise<Disposable["dispose"]> => { export const register = async (app: App, args: DefaultedArgs): Promise<Disposable["dispose"]> => {
const heart = new Heart(path.join(paths.data, "heartbeat"), async () => { const heart = new Heart(path.join(paths.data, "heartbeat"), async () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// getConnections appears to not call the callback when there are no more
// connections. Feels like it must be a bug? For now add a timer to make
// sure we eventually resolve.
const timer = setTimeout(() => {
logger.debug("Node failed to respond with connections; assuming zero")
resolve(false)
}, 5000)
app.server.getConnections((error, count) => { app.server.getConnections((error, count) => {
clearTimeout(timer)
if (error) { if (error) {
return reject(error) return reject(error)
} }

View File

@@ -1,18 +1,18 @@
import { Router, Request } from "express" import { Router, Request } from "express"
import { promises as fs } from "fs" import { promises as fs } from "fs"
import { RateLimiter as Limiter } from "limiter" import { RateLimiter as Limiter } from "limiter"
import * as os from "os"
import * as path from "path" import * as path from "path"
import { CookieKeys } from "../../common/http" import { CookieKeys } from "../../common/http"
import { rootPath } from "../constants" import { rootPath } from "../constants"
import { authenticated, getCookieOptions, redirect, replaceTemplates } from "../http" import { authenticated, getCookieOptions, redirect, replaceTemplates } from "../http"
import i18n from "../i18n" import { getPasswordMethod, handlePasswordValidation, humanPath, sanitizeString, escapeHtml } from "../util"
import { getPasswordMethod, handlePasswordValidation, sanitizeString, escapeHtml } from "../util"
// RateLimiter wraps around the limiter library for logins. // RateLimiter wraps around the limiter library for logins.
// It allows 2 logins every minute plus 12 logins every hour. // It allows 2 logins every minute plus 12 logins every hour.
export class RateLimiter { export class RateLimiter {
private readonly minuteLimiter = new Limiter({ tokensPerInterval: 2, interval: "minute" }) private readonly minuteLimiter = new Limiter(2, "minute")
private readonly hourLimiter = new Limiter({ tokensPerInterval: 12, interval: "hour" }) private readonly hourLimiter = new Limiter(12, "hour")
public canTry(): boolean { public canTry(): boolean {
// Note: we must check using >= 1 because technically when there are no tokens left // Note: we must check using >= 1 because technically when there are no tokens left
@@ -28,26 +28,21 @@ export class RateLimiter {
const getRoot = async (req: Request, error?: Error): Promise<string> => { const getRoot = async (req: Request, error?: Error): Promise<string> => {
const content = await fs.readFile(path.join(rootPath, "src/browser/pages/login.html"), "utf8") const content = await fs.readFile(path.join(rootPath, "src/browser/pages/login.html"), "utf8")
const locale = req.args["locale"] || "en"
i18n.changeLanguage(locale)
const appName = req.args["app-name"] || "code-server" const appName = req.args["app-name"] || "code-server"
const welcomeText = req.args["welcome-text"] || (i18n.t("WELCOME", { app: appName }) as string) const welcomeText = req.args["welcome-text"] || `Welcome to ${appName}`
let passwordMsg = i18n.t("LOGIN_PASSWORD", { configFile: req.args.config }) let passwordMsg = `Check the config file at ${humanPath(os.homedir(), req.args.config)} for the password.`
if (req.args.usingEnvPassword) { if (req.args.usingEnvPassword) {
passwordMsg = i18n.t("LOGIN_USING_ENV_PASSWORD") passwordMsg = "Password was set from $PASSWORD."
} else if (req.args.usingEnvHashedPassword) { } else if (req.args.usingEnvHashedPassword) {
passwordMsg = i18n.t("LOGIN_USING_HASHED_PASSWORD") passwordMsg = "Password was set from $HASHED_PASSWORD."
} }
return replaceTemplates( return replaceTemplates(
req, req,
content content
.replace(/{{I18N_LOGIN_TITLE}}/g, i18n.t("LOGIN_TITLE", { app: appName })) .replace(/{{APP_NAME}}/g, appName)
.replace(/{{WELCOME_TEXT}}/g, welcomeText) .replace(/{{WELCOME_TEXT}}/g, welcomeText)
.replace(/{{PASSWORD_MSG}}/g, passwordMsg) .replace(/{{PASSWORD_MSG}}/g, passwordMsg)
.replace(/{{I18N_LOGIN_BELOW}}/g, i18n.t("LOGIN_BELOW"))
.replace(/{{I18N_PASSWORD_PLACEHOLDER}}/g, i18n.t("PASSWORD_PLACEHOLDER"))
.replace(/{{I18N_SUBMIT}}/g, i18n.t("SUBMIT"))
.replace(/{{ERROR}}/, error ? `<div class="error">${escapeHtml(error.message)}</div>` : ""), .replace(/{{ERROR}}/, error ? `<div class="error">${escapeHtml(error.message)}</div>` : ""),
) )
} }
@@ -75,11 +70,11 @@ router.post<{}, string, { password: string; base?: string }, { to?: string }>("/
try { try {
// Check to see if they exceeded their login attempts // Check to see if they exceeded their login attempts
if (!limiter.canTry()) { if (!limiter.canTry()) {
throw new Error(i18n.t("LOGIN_RATE_LIMIT") as string) throw new Error("Login rate limited!")
} }
if (!password) { if (!password) {
throw new Error(i18n.t("MISS_PASSWORD") as string) throw new Error("Missing password")
} }
const passwordMethod = getPasswordMethod(hashedPasswordFromArgs) const passwordMethod = getPasswordMethod(hashedPasswordFromArgs)
@@ -113,7 +108,7 @@ router.post<{}, string, { password: string; base?: string }, { to?: string }>("/
}), }),
) )
throw new Error(i18n.t("INCORRECT_PASSWORD") as string) throw new Error("Incorrect password")
} catch (error: any) { } catch (error: any) {
const renderedHtml = await getRoot(req, error) const renderedHtml = await getRoot(req, error)
res.send(renderedHtml) res.send(renderedHtml)

View File

@@ -3,7 +3,7 @@ import * as path from "path"
import * as qs from "qs" import * as qs from "qs"
import * as pluginapi from "../../../typings/pluginapi" import * as pluginapi from "../../../typings/pluginapi"
import { HttpCode, HttpError } from "../../common/http" import { HttpCode, HttpError } from "../../common/http"
import { ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http" import { authenticated, ensureAuthenticated, redirect, self } from "../http"
import { proxy as _proxy } from "../proxy" import { proxy as _proxy } from "../proxy"
const getProxyTarget = (req: Request, passthroughPath?: boolean): string => { 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}` return `http://0.0.0.0:${req.params.port}/${req.originalUrl}`
} }
const query = qs.stringify(req.query) const query = qs.stringify(req.query)
return encodeURI(`http://0.0.0.0:${req.params.port}${req.params[0] || ""}${query ? `?${query}` : ""}`) return `http://0.0.0.0:${req.params.port}/${req.params[0] || ""}${query ? `?${query}` : ""}`
} }
export async function proxy( export async function proxy(
@@ -21,8 +21,6 @@ export async function proxy(
passthroughPath?: boolean passthroughPath?: boolean
}, },
): Promise<void> { ): Promise<void> {
ensureProxyEnabled(req)
if (!(await authenticated(req))) { if (!(await authenticated(req))) {
// If visiting the root (/:port only) redirect to the login page. // If visiting the root (/:port only) redirect to the login page.
if (!req.params[0] || req.params[0] === "/") { if (!req.params[0] || req.params[0] === "/") {
@@ -52,8 +50,6 @@ export async function wsProxy(
passthroughPath?: boolean passthroughPath?: boolean
}, },
): Promise<void> { ): Promise<void> {
ensureProxyEnabled(req)
ensureOrigin(req)
await ensureAuthenticated(req) await ensureAuthenticated(req)
_proxy.ws(req, req.ws, req.head, { _proxy.ws(req, req.ws, req.head, {
ignorePath: true, ignorePath: true,

View File

@@ -1,7 +1,5 @@
import { logger } from "@coder/logger" import { logger } from "@coder/logger"
import * as crypto from "crypto"
import * as express from "express" import * as express from "express"
import { promises as fs } from "fs"
import * as http from "http" import * as http from "http"
import * as net from "net" import * as net from "net"
import * as path from "path" import * as path from "path"
@@ -9,7 +7,7 @@ import { WebsocketRequest } from "../../../typings/pluginapi"
import { logError } from "../../common/util" import { logError } from "../../common/util"
import { CodeArgs, toCodeArgs } from "../cli" import { CodeArgs, toCodeArgs } from "../cli"
import { isDevMode } from "../constants" import { isDevMode } from "../constants"
import { authenticated, ensureAuthenticated, ensureOrigin, redirect, replaceTemplates, self } from "../http" import { authenticated, ensureAuthenticated, redirect, replaceTemplates, self } from "../http"
import { SocketProxyProvider } from "../socket" import { SocketProxyProvider } from "../socket"
import { isFile, loadAMDModule } from "../util" import { isFile, loadAMDModule } from "../util"
import { Router as WsRouter } from "../wsRouter" import { Router as WsRouter } from "../wsRouter"
@@ -34,7 +32,6 @@ export class CodeServerRouteWrapper {
private _wsRouterWrapper = WsRouter() private _wsRouterWrapper = WsRouter()
private _socketProxyProvider = new SocketProxyProvider() private _socketProxyProvider = new SocketProxyProvider()
public router = express.Router() public router = express.Router()
private mintKeyPromise: Promise<Buffer> | undefined
public get wsRouter() { public get wsRouter() {
return this._wsRouterWrapper.router return this._wsRouterWrapper.router
@@ -43,7 +40,6 @@ export class CodeServerRouteWrapper {
//#region Route Handlers //#region Route Handlers
private manifest: express.Handler = async (req, res, next) => { private manifest: express.Handler = async (req, res, next) => {
const appName = req.args["app-name"] || "code-server"
res.writeHead(200, { "Content-Type": "application/manifest+json" }) res.writeHead(200, { "Content-Type": "application/manifest+json" })
return res.end( return res.end(
@@ -51,11 +47,10 @@ export class CodeServerRouteWrapper {
req, req,
JSON.stringify( JSON.stringify(
{ {
name: appName, name: "code-server",
short_name: appName, short_name: "code-server",
start_url: ".", start_url: ".",
display: "fullscreen", display: "fullscreen",
display_override: ["window-controls-overlay"],
description: "Run Code on a remote server.", description: "Run Code on a remote server.",
icons: [192, 512].map((size) => ({ icons: [192, 512].map((size) => ({
src: `{{BASE}}/_static/src/browser/media/pwa-icon-${size}.png`, src: `{{BASE}}/_static/src/browser/media/pwa-icon-${size}.png`,
@@ -70,33 +65,6 @@ 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) => { private $root: express.Handler = async (req, res, next) => {
const isAuthenticated = await authenticated(req) const isAuthenticated = await authenticated(req)
const NO_FOLDER_OR_WORKSPACE_QUERY = !req.query.folder && !req.query.workspace const NO_FOLDER_OR_WORKSPACE_QUERY = !req.query.folder && !req.query.workspace
@@ -204,9 +172,8 @@ export class CodeServerRouteWrapper {
constructor() { constructor() {
this.router.get("/", this.ensureCodeServerLoaded, this.$root) this.router.get("/", this.ensureCodeServerLoaded, this.$root)
this.router.get("/manifest.json", this.manifest) this.router.get("/manifest.json", this.manifest)
this.router.post("/mint-key", this.mintKey)
this.router.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest) this.router.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest)
this._wsRouterWrapper.ws("*", ensureOrigin, ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket) this._wsRouterWrapper.ws("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket)
} }
dispose() { dispose() {

View File

@@ -1,7 +1,7 @@
import { field, logger } from "@coder/logger" import { field, logger } from "@coder/logger"
import * as http from "http" import * as http from "http"
import * as https from "https" import * as https from "https"
import { ProxyAgent } from "proxy-agent" import ProxyAgent from "proxy-agent"
import * as semver from "semver" import * as semver from "semver"
import * as url from "url" import * as url from "url"
import { httpProxyUri, version } from "./constants" import { httpProxyUri, version } from "./constants"
@@ -104,9 +104,7 @@ export class UpdateProvider {
const request = (uri: string): void => { const request = (uri: string): void => {
logger.debug("Making request", field("uri", uri)) logger.debug("Making request", field("uri", uri))
const isHttps = uri.startsWith("https") const isHttps = uri.startsWith("https")
const agent = new ProxyAgent({ const agent = httpProxyUri ? new ProxyAgent(httpProxyUri) : undefined
getProxyForUrl: () => httpProxyUri || "",
})
const httpx = isHttps ? https : http const httpx = isHttps ? https : http
const client = httpx.get(uri, { headers: { "User-Agent": "code-server" }, agent }, (response) => { const client = httpx.get(uri, { headers: { "User-Agent": "code-server" }, agent }, (response) => {
if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 400) { if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 400) {

View File

@@ -87,6 +87,20 @@ 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 }> => { export const generateCertificate = async (hostname: string): Promise<{ cert: string; certKey: string }> => {
const certPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.crt`) const certPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.crt`)
const certKeyPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.key`) const certKeyPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.key`)
@@ -527,13 +541,3 @@ export const loadAMDModule = async <T>(amdPath: string, exportName: string): Pro
return module[exportName] as T return module[exportName] as T
} }
/**
* Split a string on the first equals. The result will always be an array with
* two items regardless of how many equals there are. The second item will be
* undefined if empty or missing.
*/
export function splitOnFirstEquals(str: string): [string, string | undefined] {
const split = str.split(/=(.+)?/, 2)
return [split[0], split[1]]
}

View File

@@ -1,206 +0,0 @@
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()
})
}
}

View File

@@ -3,7 +3,7 @@ import * as cp from "child_process"
import * as path from "path" import * as path from "path"
import * as rfs from "rotating-file-stream" import * as rfs from "rotating-file-stream"
import { Emitter } from "../common/emitter" import { Emitter } from "../common/emitter"
import { DefaultedArgs, redactArgs } from "./cli" import { DefaultedArgs } from "./cli"
import { paths } from "./util" import { paths } from "./util"
const timeoutInterval = 10000 // 10s, matches VS Code's timeouts. const timeoutInterval = 10000 // 10s, matches VS Code's timeouts.
@@ -44,11 +44,10 @@ export function onMessage<M, T extends M>(
} }
const onMessage = (message: M) => { const onMessage = (message: M) => {
;(customLogger || logger).trace("got message", field("message", message))
if (fn(message)) { if (fn(message)) {
cleanup() cleanup()
resolve(message) resolve(message)
} else {
;(customLogger || logger).debug("got unhandled message", field("message", message))
} }
} }
@@ -78,10 +77,7 @@ type ChildMessage = RelaunchMessage | ChildHandshakeMessage
type ParentMessage = ParentHandshakeMessage type ParentMessage = ParentHandshakeMessage
class ProcessError extends Error { class ProcessError extends Error {
public constructor( public constructor(message: string, public readonly code: number | undefined) {
message: string,
public readonly code: number | undefined,
) {
super(message) super(message)
this.name = this.constructor.name this.name = this.constructor.name
Error.captureStackTrace(this, this.constructor) Error.captureStackTrace(this, this.constructor)
@@ -151,7 +147,7 @@ abstract class Process {
* Child process that will clean up after itself if the parent goes away and can * Child process that will clean up after itself if the parent goes away and can
* perform a handshake with the parent and ask it to relaunch. * perform a handshake with the parent and ask it to relaunch.
*/ */
export class ChildProcess extends Process { class ChildProcess extends Process {
public logger = logger.named(`child:${process.pid}`) public logger = logger.named(`child:${process.pid}`)
public constructor(private readonly parentPid: number) { public constructor(private readonly parentPid: number) {
@@ -176,7 +172,6 @@ export class ChildProcess extends Process {
* Initiate the handshake and wait for a response from the parent. * Initiate the handshake and wait for a response from the parent.
*/ */
public async handshake(): Promise<DefaultedArgs> { public async handshake(): Promise<DefaultedArgs> {
this.logger.debug("initiating handshake")
this.send({ type: "handshake" }) this.send({ type: "handshake" })
const message = await onMessage<ParentMessage, ParentHandshakeMessage>( const message = await onMessage<ParentMessage, ParentHandshakeMessage>(
process, process,
@@ -185,13 +180,6 @@ export class ChildProcess extends Process {
}, },
this.logger, this.logger,
) )
this.logger.debug(
"got message",
field("message", {
type: message.type,
args: redactArgs(message.args),
}),
)
return message.args return message.args
} }
@@ -292,10 +280,6 @@ export class ParentProcess extends Process {
} }
public start(args: DefaultedArgs): Promise<void> { 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. // Store for relaunches.
this.args = args this.args = args
if (!this.started) { if (!this.started) {
@@ -322,7 +306,7 @@ export class ParentProcess extends Process {
}) })
} }
this.logger.debug(`spawned child process ${child.pid}`) this.logger.debug(`spawned inner process ${child.pid}`)
await this.handshake(child) await this.handshake(child)
@@ -350,14 +334,13 @@ export class ParentProcess extends Process {
if (!this.args) { if (!this.args) {
throw new Error("started without args") throw new Error("started without args")
} }
const message = await onMessage<ChildMessage, ChildHandshakeMessage>( await onMessage<ChildMessage, ChildHandshakeMessage>(
child, child,
(message): message is ChildHandshakeMessage => { (message): message is ChildHandshakeMessage => {
return message.type === "handshake" return message.type === "handshake"
}, },
this.logger, this.logger,
) )
this.logger.debug("got message", field("message", message))
this.send(child, { type: "handshake", args: this.args }) this.send(child, { type: "handshake", args: this.args })
} }

View File

@@ -32,9 +32,6 @@ export class WebsocketRouter {
/** /**
* Handle a websocket at this route. Note that websockets are immediately * Handle a websocket at this route. Note that websockets are immediately
* paused when they come in. * paused when they come in.
*
* If the origin header exists it must match the host or the connection will
* be prevented.
*/ */
public ws(route: expressCore.PathParams, ...handlers: pluginapi.WebSocketHandler[]): void { public ws(route: expressCore.PathParams, ...handlers: pluginapi.WebSocketHandler[]): void {
this.router.get( this.router.get(

View File

@@ -117,26 +117,40 @@ export class CodeServer {
* directories. * directories.
*/ */
private async spawn(): Promise<CodeServerProcess> { 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 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) => { return new Promise((resolve, reject) => {
const args = [
this.entry,
"--extensions-dir",
path.join(dir, "extensions"),
"--auth",
"none",
...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,
// The last argument is the workspace to open.
dir,
]
this.logger.debug("spawning `node " + args.join(" ") + "`") this.logger.debug("spawning `node " + args.join(" ") + "`")
const proc = cp.spawn("node", args, { const proc = cp.spawn("node", args, {
cwd: path.join(__dirname, "../../.."), cwd: path.join(__dirname, "../../.."),
env: { env: {
...process.env, ...process.env,
...this.env, ...this.env,
// Prevent code-server from using the existing instance when running // Set to empty string to prevent code-server from
// the e2e tests from an integrated terminal. // using the existing instance when running the e2e tests
// from an integrated terminal.
VSCODE_IPC_HOOK_CLI: "", VSCODE_IPC_HOOK_CLI: "",
PASSWORD, PASSWORD,
}, },
@@ -159,15 +173,11 @@ export class CodeServer {
reject(error) reject(error)
}) })
// Tracks when the HTTP and session servers are ready.
let httpAddress: string | undefined
let sessionAddress: string | undefined
let resolved = false let resolved = false
proc.stdout.setEncoding("utf8") proc.stdout.setEncoding("utf8")
onLine(proc, (line) => { onLine(proc, (line) => {
// As long as we are actively getting input reset the timer. If we stop // As long as we are actively getting input reset the timer. If we stop
// getting input and still have not found the addresses the timer will // getting input and still have not found the address the timer will
// reject. // reject.
timer.reset() timer.reset()
@@ -176,69 +186,20 @@ export class CodeServer {
if (resolved) { if (resolved) {
return 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) { if (match) {
// Cookies don't seem to work on IP addresses so swap to localhost. // Cookies don't seem to work on IP address so swap to localhost.
// TODO: Investigate whether this is a bug with code-server. // TODO: Investigate whether this is a bug with code-server.
httpAddress = match[1].replace("127.0.0.1", "localhost") const address = match[1].replace("127.0.0.1", "localhost")
} this.logger.debug(`spawned on ${address}`)
match = line.trim().match(/Session server listening on (.+)$/)
if (match) {
sessionAddress = match[1]
}
if (typeof httpAddress !== "undefined" && typeof sessionAddress !== "undefined") {
resolved = true resolved = true
timer.dispose() timer.dispose()
this.logger.debug(`code-server is ready: ${httpAddress} ${sessionAddress}`) resolve({ process: proc, address })
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. * Close the code-server process.
*/ */
@@ -269,10 +230,7 @@ export class CodeServer {
export class CodeServerPage { export class CodeServerPage {
private readonly editorSelector = "div.monaco-workbench" private readonly editorSelector = "div.monaco-workbench"
constructor( constructor(private readonly codeServer: CodeServer, public readonly page: Page) {
private readonly codeServer: CodeServer,
public readonly page: Page,
) {
this.page.on("console", (message) => { this.page.on("console", (message) => {
this.codeServer.logger.debug(message.text()) this.codeServer.logger.debug(message.text())
}) })
@@ -406,13 +364,6 @@ export class CodeServerPage {
await this.waitForTab(file) 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. * Wait for a tab to open for the specified file.
*/ */

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