Compare commits

..

5 Commits

Author SHA1 Message Date
Ben Potter
0381f1400c replace remaining cdr github links 2022-01-30 07:32:43 -06:00
LG
97d864d09f docs: Update links in whatever files that have cdr 2022-01-29 09:29:34 +05:30
LG
37435deadb docs: Update links in npm.md 2022-01-29 09:08:58 +05:30
LG
7b4248e8aa docs: Update links in triage.md 2022-01-29 08:59:30 +05:30
LG
dfeca1c2bd Update links in package.json
I will try checking the docs too
2022-01-29 08:54:17 +05:30
117 changed files with 2848 additions and 5024 deletions

View File

@@ -61,6 +61,3 @@ types:
# implementations. For example, if a commit adds a fix + test, it's a fix # implementations. For example, if a commit adds a fix + test, it's a fix
# commit. If a commit is simply bumping coverage, it's a test commit. # commit. If a commit is simply bumping coverage, it's a test commit.
- test - test
# A new release.
- release

View File

@@ -8,13 +8,6 @@ on:
branches: branches:
- main - main
# Cancel in-progress runs for pull requests when developers push
# 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:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
# Note: if: success() is used in several jobs - # Note: if: success() is used in several jobs -
# this ensures that it only executes if all previous jobs succeeded. # this ensures that it only executes if all previous jobs succeeded.
@@ -28,30 +21,31 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
- name: Install Node.js v14 - name: Install Node.js v14
uses: actions/setup-node@v3 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "14"
- name: Install helm - name: Install helm
uses: azure/setup-helm@v1.1 uses: azure/setup-helm@v1.1
- name: Fetch dependencies from cache # NOTE@jsjoeio
id: cache-yarn # disabling this until we can audit the build process
uses: actions/cache@v3 # and the usefulness of this step
with: # See: https://github.com/coder/code-server/issues/4287
path: "**/node_modules" # - name: Fetch dependencies from cache
key: yarn-build-${{ hashFiles('**/yarn.lock') }} # id: cache-yarn
restore-keys: | # uses: actions/cache@v2
yarn-build- # with:
# path: "**/node_modules"
# key: yarn-build-${{ hashFiles('**/yarn.lock') }}
# restore-keys: |
# yarn-build-
- name: Install dependencies - name: Install dependencies
if: steps.cache-yarn.outputs.cache-hit != 'true' # if: steps.cache-yarn.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile run: yarn --frozen-lockfile
- name: Run yarn fmt - name: Run yarn fmt
@@ -69,19 +63,16 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
- name: Install Node.js v14 - name: Install Node.js v14
uses: actions/setup-node@v3 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "14"
- name: Fetch dependencies from cache - name: Fetch dependencies from cache
id: cache-yarn id: cache-yarn
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: "**/node_modules" path: "**/node_modules"
key: yarn-build-${{ hashFiles('**/yarn.lock') }} key: yarn-build-${{ hashFiles('**/yarn.lock') }}
@@ -104,53 +95,56 @@ jobs:
env: env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
steps: steps:
- name: Checkout repo - uses: actions/checkout@v2
uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true
- name: Install quilt
run: sudo apt update && sudo apt install quilt
- name: Patch Code
run: quilt push -a
- name: Install Node.js v14 - name: Install Node.js v14
uses: actions/setup-node@v3 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "14"
- name: Fetch dependencies from cache # TODO@Teffen investigate why this omits code-oss-dev/node_modules
id: cache-yarn # - name: Fetch dependencies from cache
uses: actions/cache@v3 # id: cache-yarn
with: # uses: actions/cache@v2
path: "**/node_modules" # with:
key: yarn-build-${{ hashFiles('**/yarn.lock') }} # path: |
restore-keys: | # "**/node_modules"
yarn-build- # "**/vendor/modules"
# "**/vendor/modules/code-oss-dev/node_modules"
# key: yarn-build-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**/vendor/yarn.lock') }}
# restore-keys: |
# yarn-build-
- name: Install dependencies - name: Install dependencies
if: steps.cache-yarn.outputs.cache-hit != 'true' # if: steps.cache-yarn.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile run: yarn --frozen-lockfile
- name: Build code-server - name: Build code-server
run: yarn build run: yarn build
# Get Code's git hash. When this changes it means the content is # Parse the hash of the latest commit inside vendor/modules/code-oss-dev
# different and we need to rebuild. # use this to avoid rebuilding it if nothing changed
- name: Get latest lib/vscode rev # How it works: the `git log` command fetches the hash of the last commit
# that changed a file inside `vendor/modules/code-oss-dev`. If a commit changes any file in there,
# the hash returned will change, and we rebuild vscode. If the hash did not change,
# (for example, a change to `src/` or `docs/`), we reuse the same build as last time.
# This saves a lot of time in CI, as compiling VSCode can take anywhere from 5-10 minutes.
- name: Get latest vendor/modules/code-oss-dev rev
id: vscode-rev id: vscode-rev
run: echo "::set-output name=rev::$(git rev-parse HEAD:./lib/vscode)" run: echo "::set-output name=rev::$(jq -r '.devDependencies["code-oss-dev"]' vendor/package.json | sed -r 's|.*#(.*)$|\1|')"
# We need to rebuild when we have a new version of Code or when any of - name: Attempt to fetch vscode build from cache
# the patches changed. Use VSCODE_CACHE_VERSION to force a rebuild.
- name: Fetch prebuilt Code package from cache
id: cache-vscode id: cache-vscode
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: lib/vscode-reh-web-* path: |
key: vscode-reh-package-${{ secrets.VSCODE_CACHE_VERSION }}-${{ steps.vscode-rev.outputs.rev }}-${{ hashFiles('patches/*.diff') }} vendor/modules/code-oss-dev/.build
vendor/modules/code-oss-dev/out-build
vendor/modules/code-oss-dev/out-vscode-reh-web
vendor/modules/code-oss-dev/out-vscode-reh-web-min
key: vscode-reh-build-${{ steps.vscode-rev.outputs.rev }}
- name: Build vscode - name: Build vscode
if: steps.cache-vscode.outputs.cache-hit != 'true' if: steps.cache-vscode.outputs.cache-hit != 'true'
@@ -178,65 +172,11 @@ jobs:
run: tar -czf package.tar.gz release run: tar -czf package.tar.gz release
- name: Upload npm package artifact - name: Upload npm package artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: npm-package name: npm-package
path: ./package.tar.gz path: ./package.tar.gz
npm:
# 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
# Reason: forks cannot access secrets and this will always fail
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- 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:
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 }}
```
# TODO: cache building yarn --production # TODO: cache building yarn --production
# possibly 2m30s of savings(?) # possibly 2m30s of savings(?)
# this requires refactoring our release scripts # this requires refactoring our release scripts
@@ -248,13 +188,10 @@ jobs:
container: "centos:7" container: "centos:7"
steps: steps:
- name: Checkout repo - uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Node.js v14 - name: Install Node.js v14
uses: actions/setup-node@v3 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "14"
@@ -275,7 +212,7 @@ jobs:
run: npm install -g yarn run: npm install -g yarn
- name: Download npm package - name: Download npm package
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: npm-package name: npm-package
@@ -294,7 +231,7 @@ jobs:
run: yarn package run: yarn package
- name: Upload release artifacts - name: Upload release artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: release-packages name: release-packages
path: ./release-packages path: ./release-packages
@@ -340,13 +277,10 @@ jobs:
NODE_VERSION: v14.17.4 NODE_VERSION: v14.17.4
steps: steps:
- name: Checkout repo - uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Node.js v14 - name: Install Node.js v14
uses: actions/setup-node@v3 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "14"
@@ -361,7 +295,7 @@ jobs:
PACKAGE: ${{ format('g++-{0}', matrix.prefix) }} PACKAGE: ${{ format('g++-{0}', matrix.prefix) }}
- name: Download npm package - name: Download npm package
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: npm-package name: npm-package
@@ -381,7 +315,7 @@ jobs:
run: yarn package ${NPM_CONFIG_ARCH} run: yarn package ${NPM_CONFIG_ARCH}
- name: Upload release artifacts - name: Upload release artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: release-packages name: release-packages
path: ./release-packages path: ./release-packages
@@ -392,13 +326,10 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout repo - uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Node.js v14 - name: Install Node.js v14
uses: actions/setup-node@v3 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "14"
@@ -408,7 +339,7 @@ jobs:
echo "$HOME/.local/bin" >> $GITHUB_PATH echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Download npm package - name: Download npm package
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: npm-package name: npm-package
@@ -425,7 +356,7 @@ jobs:
run: yarn package run: yarn package
- name: Upload release artifacts - name: Upload release artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: release-packages name: release-packages
path: ./release-packages path: ./release-packages
@@ -440,20 +371,16 @@ jobs:
# since VS Code will load faster due to the bundling. # since VS Code will load faster due to the bundling.
CODE_SERVER_TEST_ENTRY: "./release-packages/code-server-linux-amd64" CODE_SERVER_TEST_ENTRY: "./release-packages/code-server-linux-amd64"
steps: steps:
- name: Checkout repo - uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: true
- name: Install Node.js v14 - name: Install Node.js v14
uses: actions/setup-node@v3 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "14"
- name: Fetch dependencies from cache - name: Fetch dependencies from cache
id: cache-yarn id: cache-yarn
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: "**/node_modules" path: "**/node_modules"
key: yarn-build-${{ hashFiles('**/yarn.lock') }} key: yarn-build-${{ hashFiles('**/yarn.lock') }}
@@ -461,7 +388,7 @@ jobs:
yarn-build- yarn-build-
- name: Download release packages - name: Download release packages
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: release-packages name: release-packages
path: ./release-packages path: ./release-packages
@@ -486,7 +413,7 @@ jobs:
- name: Upload test artifacts - name: Upload test artifacts
if: always() if: always()
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: failed-test-videos name: failed-test-videos
path: ./test/test-results path: ./test/test-results
@@ -495,18 +422,13 @@ jobs:
run: rm -rf ./release-packages ./test/test-results run: rm -rf ./release-packages ./test/test-results
trivy-scan-repo: trivy-scan-repo:
permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repo - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Run Trivy vulnerability scanner in repo mode - name: Run Trivy vulnerability scanner in repo mode
uses: aquasecurity/trivy-action@40c4ca9e7421287d0c5576712fdff370978f9c3c #Commit SHA for v0.0.17
uses: aquasecurity/trivy-action@9c21d3ca2c14eb35419e2a8b66d1195946d579b8
with: with:
scan-type: "fs" scan-type: "fs"
scan-ref: "." scan-ref: "."
@@ -515,7 +437,6 @@ jobs:
template: "@/contrib/sarif.tpl" template: "@/contrib/sarif.tpl"
output: "trivy-repo-results.sarif" output: "trivy-repo-results.sarif"
severity: "HIGH,CRITICAL" severity: "HIGH,CRITICAL"
- name: Upload Trivy scan results to GitHub Security tab - name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v1 uses: github/codeql-action/upload-sarif@v1
with: with:

View File

@@ -10,28 +10,14 @@ 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 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:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs: jobs:
analyze: analyze:
permissions:
actions: read # for github/codeql-action/init to get workflow details
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/autobuild to send a status report
name: Analyze name: Analyze
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL

View File

@@ -6,22 +6,13 @@ on:
workflow_dispatch: workflow_dispatch:
release: release:
types: types: [released]
- released
# Cancel in-progress runs for pull requests when developers push
# 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:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs: jobs:
docker-images: docker-images:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Checkout - uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
@@ -29,27 +20,9 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Get version
id: version
run: echo "::set-output name=version::$(jq -r .version package.json)"
- name: Download artifact
uses: dawidd6/action-download-artifact@v2
id: download
with:
branch: v${{ steps.version.outputs.version }}
workflow: ci.yaml
workflow_conclusion: completed
name: "release-packages"
path: release-packages
- name: Run ./ci/steps/docker-buildx-push.sh - name: Run ./ci/steps/docker-buildx-push.sh
run: ./ci/steps/docker-buildx-push.sh run: ./ci/steps/docker-buildx-push.sh
env: env:
GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -21,24 +21,75 @@ jobs:
preview: preview:
name: Docs preview name: Docs preview
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
environment: CI
steps: steps:
- uses: actions/checkout@v3 - name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.1
- name: Set outputs - name: Checkout m
id: vars uses: actions/checkout@v2
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" with:
repository: coder/m
ref: refs/heads/master
ssh-key: ${{ secrets.READONLY_M_DEPLOY_KEY }}
submodules: true
fetch-depth: 0
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: 14
- name: Cache Node Modules
uses: actions/cache@v2
with:
path: "/node_modules"
key: node-${{ hashFiles('yarn.lock') }}
- name: Create Deployment
id: deployment
run: ./ci/scripts/github_deployment.sh create
env:
GITHUB_TOKEN: ${{ github.token }}
DEPLOY_ENVIRONMENT: codercom-preview-docs
- name: Deploy Preview to Vercel
id: preview
run: ./ci/scripts/deploy_vercel.sh
env:
VERCEL_ORG_ID: team_tGkWfhEGGelkkqUUm9nXq17r
VERCEL_PROJECT_ID: QmZRucMRh3GFk1817ZgXjRVuw5fhTspHPHKct3JNQDEPGd
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
CODE_SERVER_DOCS_MAIN_BRANCH: ${{ github.event.pull_request.head.sha }}
- name: Install node_modules
run: yarn install
- name: Check docs
run: yarn ts-node ./product/coder.com/site/scripts/checkDocs.ts
env:
BASE_URL: ${{ steps.preview.outputs.url }}
- name: Update Deployment
# If we don't specify always, it won't run this check if failed.
# This means the deployment would be stuck pending.
if: always()
run: ./ci/scripts/github_deployment.sh update
env:
GITHUB_DEPLOYMENT: ${{ steps.deployment.outputs.id }}
GITHUB_TOKEN: ${{ github.token }}
DEPLOY_STATUS: ${{ steps.preview.outcome }}
DEPLOY_URL: ${{ steps.preview.outputs.url }}
- name: Comment Credentials - name: Comment Credentials
uses: marocchino/sticky-pull-request-comment@v2 uses: marocchino/sticky-pull-request-comment@v2
# Only run if PR comes from base repo if: always()
# Reason: forks cannot access secrets and this will always fail
if: github.event.pull_request.head.repo.full_name == github.repository
with: with:
GITHUB_TOKEN: ${{ github.token }}
header: codercom-preview-docs header: codercom-preview-docs
message: | message: |
code-server docs for PR #${{ github.event.number }} is ready! It will be updated on every commit. Coder.com for PR #${{ github.event.number }} deployed! It will be updated on every commit.
* _Host_: https://coder.com/docs/code-server/${{ steps.vars.outputs.sha_short }}
* _Last deploy status_: success * _Host_: ${{ steps.preview.outputs.url }}/docs/code-server
* _Last deploy status_: ${{ steps.preview.outcome }}
* _Commit_: ${{ github.event.pull_request.head.sha }} * _Commit_: ${{ github.event.pull_request.head.sha }}
* _Workflow status_: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} * _Workflow status_: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}

View File

@@ -12,23 +12,13 @@ on:
paths: paths:
- "install.sh" - "install.sh"
# Cancel in-progress runs for pull requests when developers push
# 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:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs: jobs:
ubuntu: ubuntu:
name: Test installer on Ubuntu name: Test installer on Ubuntu
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Install code-server - name: Install code-server
run: ./install.sh run: ./install.sh
@@ -42,7 +32,7 @@ jobs:
container: "alpine:3.14" container: "alpine:3.14"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Install curl - name: Install curl
run: apk add curl run: apk add curl
@@ -60,7 +50,7 @@ jobs:
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Install code-server - name: Install code-server
run: ./install.sh run: ./install.sh

29
.github/workflows/npm-beta.yaml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Publish on npm and tag with "beta"
on:
# Shows the manual trigger in GitHub UI
# helpful as a back-up in case the GitHub Actions Workflow fails
workflow_dispatch:
push:
branches:
- main
jobs:
# NOTE: this job requires curl, jq and yarn
# All of them are included in ubuntu-latest.
npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Publish npm package and tag "beta"
run: yarn publish:npm
env:
ENVIRONMENT: "staging"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TAG: "beta"
# Since this only runs on a merge into main, we can't use github.event.number
# so we instead use the word "beta" and the PR merge commit SHA
PR_NUMBER_AND_COMMIT_SHA: beta-${{ github.sha }}

View File

@@ -8,45 +8,27 @@ on:
release: release:
types: [released] types: [released]
# Cancel in-progress runs for pull requests when developers push
# 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:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs: jobs:
# NOTE: this job requires curl, jq and yarn # NOTE: this job requires curl, jq and yarn
# All of them are included in ubuntu-latest. # All of them are included in ubuntu-latest.
npm: npm:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Get version
id: version
run: echo "::set-output name=version::$(jq -r .version package.json)"
- name: Download artifact
uses: dawidd6/action-download-artifact@v2
id: download
with:
branch: v${{ steps.version.outputs.version }}
workflow: ci.yaml
workflow_conclusion: completed
name: "npm-package"
path: release-npm-package
- name: Publish npm package and tag with "latest" - name: Publish npm package and tag with "latest"
run: yarn publish:npm run: yarn publish:npm
env: env:
ENVIRONMENT: "production"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_ENVIRONMENT: "production" NPM_TAG: "latest"
homebrew: homebrew:
# The newest version of code-server needs to be available on npm when this runs
# otherwise, it will 404 and won't open a PR to bump version on homebrew/homebrew-core
needs: npm needs: npm
runs-on: ubuntu-latest runs-on: macos-latest
steps: steps:
# Ensure things are up to date # Ensure things are up to date
# Suggested by homebrew maintainers # Suggested by homebrew maintainers
@@ -55,14 +37,11 @@ jobs:
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@master
- name: Checkout code-server - uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Configure git - name: Configure git
run: | run: |
git config user.name cdrci git config user.name github-actions
git config user.email opensource@coder.com git config user.email github-actions@github.com
- name: Bump code-server homebrew version - name: Bump code-server homebrew version
env: env:
HOMEBREW_GITHUB_API_TOKEN: ${{secrets.HOMEBREW_GITHUB_API_TOKEN}} HOMEBREW_GITHUB_API_TOKEN: ${{secrets.HOMEBREW_GITHUB_API_TOKEN}}

30
.github/workflows/npm-dev.yaml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Publish on npm and tag with PR number
on:
# Shows the manual trigger in GitHub UI
# helpful as a back-up in case the GitHub Actions Workflow fails
workflow_dispatch:
pull_request:
branches:
- main
jobs:
# NOTE: this job requires curl, jq and yarn
# All of them are included in ubuntu-latest.
npm:
# This environment "npm" requires someone from
# coder/code-server-reviewers to approve the PR before this job runs.
environment: npm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run ./ci/steps/publish-npm.sh
run: yarn publish:npm
env:
ENVIRONMENT: "development"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TAG: ${{ github.event.number }}
PR_NUMBER_AND_COMMIT_SHA: ${{ github.event.number }}-${{ github.event.pull_request.head.sha }}

View File

@@ -14,25 +14,6 @@ on:
- "**.sh" - "**.sh"
- "**.bats" - "**.bats"
permissions:
actions: none
checks: none
contents: read
deployments: none
issues: none
packages: none
pull-requests: none
repository-projects: none
security-events: none
statuses: none
# Cancel in-progress runs for pull requests when developers push
# 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:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs: jobs:
test: test:
name: Run script unit tests name: Run script unit tests
@@ -41,7 +22,7 @@ jobs:
container: "alpine:3.14" container: "alpine:3.14"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Install test utilities - name: Install test utilities
run: apk add bats checkbashisms run: apk add bats checkbashisms

View File

@@ -1,65 +0,0 @@
name: Trivy Nightly Docker Scan
on:
# Run scans if the workflow is modified, in order to test the
# workflow itself. This results in some spurious notifications,
# but seems okay for testing.
pull_request:
branches:
- main
paths:
- .github/workflows/trivy-docker.yaml
# Run scans against master whenever changes are merged.
push:
branches:
- main
paths:
- .github/workflows/trivy-docker.yaml
schedule:
# Run at 10:15 am UTC (3:15am PT/5:15am CT)
# Run at 0 minutes 0 hours of every day.
- cron: "15 10 * * *"
workflow_dispatch:
permissions:
actions: none
checks: none
contents: read
deployments: none
issues: none
packages: none
pull-requests: none
repository-projects: none
security-events: write
statuses: none
# Cancel in-progress runs for pull requests when developers push
# 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:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
trivy-scan-image:
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run Trivy vulnerability scanner in image mode
uses: aquasecurity/trivy-action@40c4ca9e7421287d0c5576712fdff370978f9c3c
with:
image-ref: "docker.io/codercom/code-server:latest"
ignore-unfixed: true
format: "sarif"
output: "trivy-image-results.sarif"
severity: "HIGH,CRITICAL"
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: "trivy-image-results.sarif"

10
.gitignore vendored
View File

@@ -8,18 +8,12 @@ release-packages/
release-gcp/ release-gcp/
release-images/ release-images/
node_modules node_modules
vendor/modules
node-*
/plugins /plugins
/lib/coder-cloud-agent /lib/coder-cloud-agent
.home .home
coverage coverage
**/.DS_Store **/.DS_Store
# Code packages itself here.
/lib/vscode-reh-web-*
# Failed e2e test videos are saved here # Failed e2e test videos are saved here
test/test-results test/test-results
# Quilt's internal data.
/.pc
/patches/*.diff~

3
.gitmodules vendored
View File

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

View File

@@ -7,7 +7,7 @@ useTabs: false
overrides: overrides:
# Attempt to keep VScode's existing code style intact. # Attempt to keep VScode's existing code style intact.
- files: "lib/vscode/**/*.ts" - files: "vendor/modules/code-oss-dev/**/*.ts"
options: options:
# No limit defined upstream. # No limit defined upstream.
printWidth: 10000 printWidth: 10000

View File

@@ -143,7 +143,7 @@
"description": "Static images and the manifest live here in `src/browser/media` (see the explorer)." "description": "Static images and the manifest live here in `src/browser/media` (see the explorer)."
}, },
{ {
"directory": "lib/vscode", "directory": "vendor/modules/code-oss-dev",
"line": 1, "line": 1,
"description": "code-server makes use of VS Code's frontend web/remote support. Most of the modifications implement the remote server since that portion of the code is closed source and not released with VS Code.\n\nWe also have a few bug fixes and have added some features (like client-side extensions). See [https://github.com/coder/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code](https://github.com/coder/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code) for a list.\n\nWe make an effort to keep the modifications as few as possible." "description": "code-server makes use of VS Code's frontend web/remote support. Most of the modifications implement the remote server since that portion of the code is closed source and not released with VS Code.\n\nWe also have a few bug fixes and have added some features (like client-side extensions). See [https://github.com/coder/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code](https://github.com/coder/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code) for a list.\n\nWe make an effort to keep the modifications as few as possible."
} }

View File

@@ -9,10 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [9.99.999] - 9090-09-09 ## [9.99.999] - 9090-09-09
Code v99.99.999 VS Code v99.99.999
### Added
### Changed ### Changed
### Added
### Deprecated ### Deprecated
### Removed ### Removed
### Fixed ### Fixed
@@ -20,103 +20,17 @@ Code v99.99.999
--> -->
## [4.3.0](https://github.com/coder/code-server/releases/tag/v4.3.0) - 2022-04-14 ## [Unreleased](https://github.com/coder/code-server/releases)
Code v1.65.2 VS Code v0.00.0
### Changed ### Changed
- Excluded .deb files from release Docker image which drops the compressed and - Add here
uncompressed size by 58% and 34%.
- Upgraded to Code 1.65.2.
### Added
- Added a new CLI flag called `--disable-file-downloads` which allows you to
disable the "Download..." option that shows in the UI when right-clicking on a
file. This can also set by running `CS_DISABLE_FILE_DOWNLOADS=1`.
- Aligned the dependencies for binary and npm release artifacts.
### Fixed
- Fixed the code-server version from not displaying in the Help > About dialog.
- Fixed issues with the TypeScript and JavaScript Language Features Extension
failing to activate.
- Fixed missing files in ipynb extension.
- Fixed the homebrew release workflow.
- Fixed the Docker release workflow from not always publishing version tags.
## [4.2.0](https://github.com/coder/code-server/releases/tag/v4.2.0) - 2022-03-22
Code v1.64.2
### Added
- Added tests for `handleArgsSocketCatchError`, `setDefaults` and
`optionDescriptions`.
### Changed
- We switched from using the fork `coder/vscode` to a submodule of
`microsoft/vscode` + patches managed by `quilt` for how Code sits inside the
code-server codebase.
- Upgraded to Code 1.64.2.
### Fixed
- Update popup notification through `--disable-update-check` is now fixed.
- Fixed PWA icons not loading on iPad
- Fixed the homebrew release process. Our `cdrci` bot should now automatically
update the version as part of the release pipeline.
- Fixed titleBar color setting being ignored in PWA.
### Security
- Updated to `minimist-list`.
- Updated `cloud-agent` to `v0.2.4` which uses `nhooyr.io/webscoket` `v1.8.7`.
## [4.1.0](https://github.com/coder/code-server/releases/tag/v4.1.0) - 2022-03-03
Code v1.63.0
### Added
- Support for injecting GitHub token into Code so extensions can make use of it.
This can be done with the `GITHUB_TOKEN` environment variable or `github-auth`
in the config file.
- New flag `--socket-mode` allows setting the mode (file permissions) of the
socket created when using `--socket`.
- The version of Code bundled with code-server now appears when using the
`--version` flag. For example: `4.0.2 5cdfe74686aa73e023f8354a9a6014eb30caa7dd with Code 1.63.0`.
If you have been parsing this flag for the version you might want to use
`--version --json` instead as doing that will be more stable.
### Changed
- The workspace or folder passed on the CLI will now use the same redirect
method that the last opened workspace or folder uses. This means if you use
something like `code-server /path/to/dir` you will now get a query parameter
added (like so: `my-domain.tld?folder=/path/to/dir`), making it easier to edit
by hand and making it consistent with the last opened and menu open behaviors.
- The folder/workspace query parameter no longer has encoded slashes, making
them more readable and editable by hand. This was only affecting the last
opened behavior, not opens from the menu.
### Fixed
- Fix web sockets not connecting when using `--cert`.
- Prevent workspace state collisions when opening a workspace that shares the
same file path with another workspace on a different machine that shares the
same domain. This was causing files opened in one workspace to be "re-"opened
in the other workspace when the other workspace is opened.
- Pin the Express version which should make installing from npm work again.
- Propagate signals to code-server in the Docker image which means it should
stop more quickly and gracefully.
- Fix missing argon binaries in the standalone releases on arm machines.
## [4.0.2](https://github.com/coder/code-server/releases/tag/v4.0.2) - 2022-01-27 ## [4.0.2](https://github.com/coder/code-server/releases/tag/v4.0.2) - 2022-01-27
Code v1.63.0 VS Code v1.63.0
### Fixed ### Fixed
@@ -127,7 +41,7 @@ Code v1.63.0
## [4.0.1](https://github.com/coder/code-server/releases/tag/v4.0.1) - 2022-01-04 ## [4.0.1](https://github.com/coder/code-server/releases/tag/v4.0.1) - 2022-01-04
Code v1.63.0 VS Code v1.63.0
code-server has been rebased on upstream's newly open-sourced server code-server has been rebased on upstream's newly open-sourced server
implementation (#4414). implementation (#4414).
@@ -143,7 +57,7 @@ implementation (#4414).
settings file (we rely on the already-existing query object instead). settings file (we rely on the already-existing query object instead).
- The marketplace override environment variables `SERVICE_URL` and `ITEM_URL` - The marketplace override environment variables `SERVICE_URL` and `ITEM_URL`
have been replaced with a single `EXTENSIONS_GALLERY` variable that have been replaced with a single `EXTENSIONS_GALLERY` variable that
corresponds to `extensionsGallery` in Code's `product.json`. corresponds to `extensionsGallery` in VS Code's `product.json`.
### Added ### Added
@@ -165,11 +79,11 @@ implementation (#4414).
## [3.12.0](https://github.com/coder/code-server/releases/tag/v3.12.0) - 2021-09-15 ## [3.12.0](https://github.com/coder/code-server/releases/tag/v3.12.0) - 2021-09-15
Code v1.60.0 VS Code v1.60.0
### Changed ### Changed
- Upgrade Code to 1.60.0. - Upgrade VS Code to 1.60.0.
### Fixed ### Fixed
@@ -185,7 +99,7 @@ Undocumented (see releases page).
## [3.10.2](https://github.com/coder/code-server/releases/tag/v3.10.2) - 2021-05-21 ## [3.10.2](https://github.com/coder/code-server/releases/tag/v3.10.2) - 2021-05-21
Code v1.56.1 VS Code v1.56.1
### Added ### Added
@@ -201,7 +115,7 @@ Code v1.56.1
## [3.10.1](https://github.com/coder/code-server/releases/tag/v3.10.1) - 2021-05-17 ## [3.10.1](https://github.com/coder/code-server/releases/tag/v3.10.1) - 2021-05-17
Code v1.56.1 VS Code v1.56.1
### Fixed ### Fixed
@@ -215,13 +129,13 @@ Code v1.56.1
## [3.10.0](https://github.com/coder/code-server/releases/tag/v3.10.0) - 2021-05-10 ## [3.10.0](https://github.com/coder/code-server/releases/tag/v3.10.0) - 2021-05-10
Code v1.56.0 VS Code v1.56.0
### Changed ### Changed
- Update to Code 1.56.0 (#3269). - Update to VS Code 1.56.0 (#3269).
- Minor connections refactor (#3178). Improves connection stability. - Minor connections refactor (#3178). Improves connection stability.
- Use ptyHostService (#3308). This brings us closer to upstream Code. - Use ptyHostService (#3308). This brings us closer to upstream VS Code.
### Added ### Added

View File

@@ -50,11 +50,6 @@ release_nfpm() {
export NFPM_ARCH export NFPM_ARCH
# Code deletes some files from the extension node_modules directory which
# leaves broken symlinks in the corresponding .bin directory. nfpm will fail
# on these broken symlinks so clean them up.
rm -fr "./release-standalone/lib/vscode/extensions/node_modules/.bin"
PKG_FORMAT="deb" PKG_FORMAT="deb"
NFPM_ARCH="$(get_nfpm_arch $PKG_FORMAT "$ARCH")" NFPM_ARCH="$(get_nfpm_arch $PKG_FORMAT "$ARCH")"
nfpm_config="$(envsubst < ./ci/build/nfpm.yaml)" nfpm_config="$(envsubst < ./ci/build/nfpm.yaml)"

View File

@@ -15,8 +15,8 @@ main() {
source ./ci/lib.sh source ./ci/lib.sh
VSCODE_SRC_PATH="lib/vscode" VSCODE_SRC_PATH="vendor/modules/code-oss-dev"
VSCODE_OUT_PATH="$RELEASE_PATH/lib/vscode" VSCODE_OUT_PATH="$RELEASE_PATH/vendor/modules/code-oss-dev"
mkdir -p "$RELEASE_PATH" mkdir -p "$RELEASE_PATH"
@@ -24,8 +24,8 @@ main() {
bundle_vscode bundle_vscode
rsync ./docs/README.md "$RELEASE_PATH" rsync ./docs/README.md "$RELEASE_PATH"
rsync LICENSE "$RELEASE_PATH" rsync LICENSE.txt "$RELEASE_PATH"
rsync ./lib/vscode/ThirdPartyNotices.txt "$RELEASE_PATH" rsync ./vendor/modules/code-oss-dev/ThirdPartyNotices.txt "$RELEASE_PATH"
} }
bundle_code_server() { bundle_code_server() {
@@ -55,17 +55,6 @@ bundle_code_server() {
EOF EOF
) > "$RELEASE_PATH/package.json" ) > "$RELEASE_PATH/package.json"
rsync yarn.lock "$RELEASE_PATH" rsync yarn.lock "$RELEASE_PATH"
# To ensure deterministic dependency versions (even when code-server is installed with NPM), we seed
# an npm-shrinkwrap file from our yarn lockfile and the current node-modules installed.
synp --source-file yarn.lock
npm shrinkwrap
# HACK@edvincent: The shrinkwrap file will contain the devDependencies, which by default
# are installed if present in a lockfile. To avoid every user having to specify --production
# to skip them, we carefully remove them from the shrinkwrap file.
json -f npm-shrinkwrap.json -I -e "Object.keys(this.dependencies).forEach(dependency => { if (this.dependencies[dependency].dev) { delete this.dependencies[dependency] } } )"
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
@@ -77,29 +66,31 @@ EOF
bundle_vscode() { bundle_vscode() {
mkdir -p "$VSCODE_OUT_PATH" mkdir -p "$VSCODE_OUT_PATH"
rsync "$VSCODE_SRC_PATH/yarn.lock" "$VSCODE_OUT_PATH"
rsync "$VSCODE_SRC_PATH/out-vscode-reh-web${MINIFY:+-min}/" "$VSCODE_OUT_PATH/out"
local rsync_opts=() rsync "$VSCODE_SRC_PATH/.build/extensions/" "$VSCODE_OUT_PATH/extensions"
if [[ ${DEBUG-} = 1 ]]; then if [ "$KEEP_MODULES" = 0 ]; then
rsync_opts+=(-vh) rm -Rf "$VSCODE_OUT_PATH/extensions/node_modules"
else
rsync "$VSCODE_SRC_PATH/node_modules/" "$VSCODE_OUT_PATH/node_modules"
fi fi
rsync "$VSCODE_SRC_PATH/extensions/package.json" "$VSCODE_OUT_PATH/extensions"
rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions"
rsync "$VSCODE_SRC_PATH/extensions/postinstall.js" "$VSCODE_OUT_PATH/extensions"
# Some extensions have a .gitignore which excludes their built source from the mkdir -p "$VSCODE_OUT_PATH/resources/"
# npm package so exclude any .gitignore files. rsync "$VSCODE_SRC_PATH/resources/" "$VSCODE_OUT_PATH/resources/"
rsync_opts+=(--exclude .gitignore)
# Exclude Node as we will add it ourselves for the standalone and will not # TODO: We should look into using VS Code's packaging task (see
# need it for the npm package. # gulpfile.reh.js). For now copy this directory into the right spot (for some
rsync_opts+=(--exclude /node) # reason VS Code uses a different path in production).
mkdir -p "$VSCODE_OUT_PATH/bin/helpers"
rsync "$VSCODE_SRC_PATH/resources/server/bin/helpers/" "$VSCODE_OUT_PATH/bin/helpers"
chmod +x "$VSCODE_OUT_PATH/bin/helpers/browser.sh"
# Exclude Node modules. # Add the commit and date and enable telemetry. This just makes telemetry
if [[ $KEEP_MODULES = 0 ]]; then # available; telemetry can still be disabled by flag or setting.
rsync_opts+=(--exclude node_modules)
fi
rsync "${rsync_opts[@]}" ./lib/vscode-reh-web-*/ "$VSCODE_OUT_PATH"
# Add the commit, date, our name, links, and enable telemetry. This just makes
# telemetry available; telemetry can still be disabled by flag or setting.
jq --slurp '.[0] * .[1]' "$VSCODE_SRC_PATH/product.json" <( jq --slurp '.[0] * .[1]' "$VSCODE_SRC_PATH/product.json" <(
cat << EOF cat << EOF
{ {
@@ -107,46 +98,15 @@ bundle_vscode() {
"commit": "$(cd "$VSCODE_SRC_PATH" && git rev-parse HEAD)", "commit": "$(cd "$VSCODE_SRC_PATH" && git rev-parse HEAD)",
"quality": "stable", "quality": "stable",
"date": $(jq -n 'now | todate'), "date": $(jq -n 'now | todate'),
"codeServerVersion": "$VERSION", "codeServerVersion": "$VERSION"
"nameShort": "code-server",
"nameLong": "code-server",
"applicationName": "code-server",
"dataFolderName": ".code-server",
"win32MutexName": "codeserver",
"licenseUrl": "https://github.com/coder/code-server/blob/main/LICENSE",
"win32DirName": "code-server",
"win32NameVersion": "code-server",
"win32AppUserModelId": "coder.code-server",
"win32ShellNameShort": "c&ode-server",
"darwinBundleIdentifier": "com.coder.code.server",
"linuxIconName": "com.coder.code.server",
"reportIssueUrl": "https://github.com/coder/code-server/issues/new",
"documentationUrl": "https://go.microsoft.com/fwlink/?LinkID=533484#vscode",
"keyboardShortcutsUrlMac": "https://go.microsoft.com/fwlink/?linkid=832143",
"keyboardShortcutsUrlLinux": "https://go.microsoft.com/fwlink/?linkid=832144",
"keyboardShortcutsUrlWin": "https://go.microsoft.com/fwlink/?linkid=832145",
"introductoryVideosUrl": "https://go.microsoft.com/fwlink/?linkid=832146",
"tipsAndTricksUrl": "https://go.microsoft.com/fwlink/?linkid=852118",
"newsletterSignupUrl": "https://www.research.net/r/vsc-newsletter",
"linkProtectionTrustedDomains": [
"https://open-vsx.org"
]
} }
EOF EOF
) > "$VSCODE_OUT_PATH/product.json" ) > "$VSCODE_OUT_PATH/product.json"
# Use the package.json for the web/remote server. It does not have the right # We remove the scripts field so that later on we can run
# version though so pull that from the main package.json. # yarn to fetch node_modules if necessary without build scripts running.
jq --slurp '.[0] * {version: .[1].version}' \ # We cannot use --no-scripts because we still want dependent package scripts to run.
"$VSCODE_SRC_PATH/remote/package.json" \ jq 'del(.scripts)' < "$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"
# Include global extension dependencies as well.
rsync "$VSCODE_SRC_PATH/extensions/package.json" "$VSCODE_OUT_PATH/extensions/package.json"
rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions/yarn.lock"
rsync "$VSCODE_SRC_PATH/extensions/postinstall.js" "$VSCODE_OUT_PATH/extensions/postinstall.js"
pushd "$VSCODE_OUT_PATH" pushd "$VSCODE_OUT_PATH"
symlink_asar symlink_asar

View File

@@ -1,10 +1,6 @@
#!/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}")/../.."
@@ -29,6 +25,12 @@ main() {
cd "$RELEASE_PATH" cd "$RELEASE_PATH"
yarn --production --frozen-lockfile yarn --production --frozen-lockfile
# HACK: the version of Typescript vscode 1.57 uses in extensions/
# leaves a few stray symlinks. Clean them up so nfpm does not fail.
# Remove this line when its no longer needed.
rm -fr "$RELEASE_PATH/vendor/modules/code-oss-dev/extensions/node_modules/.bin"
} }
main "$@" main "$@"

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# Builds vscode into lib/vscode/out-vscode. # Builds vscode into vendor/modules/code-oss-dev/out-vscode.
# MINIFY controls whether a minified version of vscode is built. # MINIFY controls whether a minified version of vscode is built.
MINIFY=${MINIFY-true} MINIFY=${MINIFY-true}
@@ -9,7 +9,7 @@ MINIFY=${MINIFY-true}
main() { main() {
cd "$(dirname "${0}")/../.." cd "$(dirname "${0}")/../.."
cd lib/vscode cd vendor/modules/code-oss-dev
# Any platform works since we have our own packaging step (for now). # Any platform works since we have our own packaging step (for now).
yarn gulp "vscode-reh-web-linux-x64${MINIFY:+-min}" yarn gulp "vscode-reh-web-linux-x64${MINIFY:+-min}"

View File

@@ -18,9 +18,6 @@ detect_arch() {
} }
ARCH="${NPM_CONFIG_ARCH:-$(detect_arch)}" ARCH="${NPM_CONFIG_ARCH:-$(detect_arch)}"
# 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
@@ -90,14 +87,22 @@ symlink_asar() {
} }
vscode_yarn() { vscode_yarn() {
echo 'Installing Code dependencies...' echo 'Installing vendor dependencies...'
cd lib/vscode cd vendor/modules/code-oss-dev
yarn --production --frozen-lockfile yarn --production --frozen-lockfile
symlink_asar symlink_asar
cd extensions cd extensions
yarn --production --frozen-lockfile yarn --production --frozen-lockfile
for ext in */; do
ext="${ext%/}"
echo "extensions/$ext: installing dependencies"
cd "$ext"
yarn --production --frozen-lockfile
cd "$OLDPWD"
done
} }
main "$@" main "$@"

View File

@@ -9,15 +9,6 @@ set -euo pipefail
main() { main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
source ./ci/lib.sh source ./ci/lib.sh
source ./ci/steps/steps-lib.sh
# NOTE@jsjoeio - only needed if we use the download_artifact
# because we talk to the GitHub API.
# Needed to use GitHub API
if ! is_env_var_set "GITHUB_TOKEN"; then
echo "GITHUB_TOKEN is not set. Cannot download npm release-packages without GitHub credentials."
exit 1
fi
download_artifact release-packages ./release-packages download_artifact release-packages ./release-packages
local assets=(./release-packages/code-server*"$VERSION"*{.tar.gz,.deb,.rpm}) local assets=(./release-packages/code-server*"$VERSION"*{.tar.gz,.deb,.rpm})

View File

@@ -81,7 +81,7 @@ main() {
read -r -p "What version of code-server do you want to update to?"$'\n' CODE_SERVER_VERSION_TO_UPDATE read -r -p "What version of code-server do you want to update to?"$'\n' CODE_SERVER_VERSION_TO_UPDATE
echo -e "Great! We'll prep a PR for updating to $CODE_SERVER_VERSION_TO_UPDATE\n" echo -e "Great! We'll prep a PR for updating to $CODE_SERVER_VERSION_TO_UPDATE\n"
$CMD rg -g '!yarn.lock' -g '!*.svg' -g '!CHANGELOG.md' -g '!lib/vscode/**' --files-with-matches --fixed-strings "${CODE_SERVER_CURRENT_VERSION}" | $CMD xargs sd "$CODE_SERVER_CURRENT_VERSION" "$CODE_SERVER_VERSION_TO_UPDATE" $CMD rg -g '!yarn.lock' -g '!*.svg' -g '!CHANGELOG.md' --files-with-matches --fixed-strings "${CODE_SERVER_CURRENT_VERSION}" | $CMD xargs sd "$CODE_SERVER_CURRENT_VERSION" "$CODE_SERVER_VERSION_TO_UPDATE"
$CMD git commit --no-verify -am "chore(release): bump version to $CODE_SERVER_VERSION_TO_UPDATE" $CMD git commit --no-verify -am "chore(release): bump version to $CODE_SERVER_VERSION_TO_UPDATE"

View File

@@ -19,7 +19,7 @@ main() {
"*.sh" "*.sh"
) )
prettier --write --loglevel=warn $( prettier --write --loglevel=warn $(
git ls-files "${prettierExts[@]}" | grep -v "lib/vscode" | grep -v 'helm-chart' git ls-files "${prettierExts[@]}" | grep -v "lib/vscode" | grep -v "vendor/modules/code-oss-dev" | grep -v 'helm-chart'
) )
doctoc --title '# FAQ' docs/FAQ.md > /dev/null doctoc --title '# FAQ' docs/FAQ.md > /dev/null

View File

@@ -4,10 +4,10 @@ set -euo pipefail
main() { main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js" | grep -v "lib/vscode") eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js" | grep -v "vendor/modules/code-oss-dev" | grep -v "lib/vscode")
stylelint $(git ls-files "*.css" | grep -v "lib/vscode") stylelint $(git ls-files "*.css" | grep -v "vendor/modules/code-oss-dev" | grep -v "lib/vscode")
tsc --noEmit --skipLibCheck tsc --noEmit --skipLibCheck
shellcheck -e SC2046,SC2164,SC2154,SC1091,SC1090,SC2002 $(git ls-files "*.sh" | grep -v "lib/vscode") shellcheck -e SC2046,SC2164,SC2154,SC1091,SC1090,SC2002 $(git ls-files "*.sh" | grep -v "vendor/modules/code-oss-dev" | grep -v "lib/vscode")
if command -v helm && helm kubeval --help > /dev/null; then if command -v helm && helm kubeval --help > /dev/null; then
helm kubeval ci/helm-chart helm kubeval ci/helm-chart
fi fi

View File

@@ -1,32 +1,50 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# Install dependencies in $1.
install-deps() {
local args=(install)
if [[ ${CI-} ]]; then
args+=(--frozen-lockfile)
fi
# If there is no package.json then yarn will look upward and end up installing
# from the root resulting in an infinite loop (this can happen if you have not
# checked out the submodule yet for example).
if [[ ! -f "$1/package.json" ]]; then
echo "$1/package.json is missing; did you run git submodule update --init?"
exit 1
fi
pushd "$1"
echo "Installing dependencies for $PWD"
yarn "${args[@]}"
popd
}
main() { main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
source ./ci/lib.sh source ./ci/lib.sh
install-deps test pushd test
install-deps test/e2e/extensions/test-extension echo "Installing dependencies for $PWD"
install-deps lib/vscode yarn install
popd
local args=(install)
if [[ ${CI-} ]]; then
args+=(--frozen-lockfile)
fi
pushd test
echo "Installing dependencies for $PWD"
yarn "${args[@]}"
popd
pushd test/e2e/extensions/test-extension
echo "Installing dependencies for $PWD"
yarn "${args[@]}"
popd
pushd vendor
echo "Installing dependencies for $PWD"
# We install in 'modules' instead of 'node_modules' because VS Code's
# extensions use a webpack config which cannot differentiate between its own
# node_modules and itself being in a directory with the same name.
args+=(--modules-folder modules)
# We ignore scripts because NPM/Yarn's default behavior is to assume that
# devDependencies are not needed, and that even git repo based packages are
# assumed to be compiled. Because the default behavior for VS Code's
# `postinstall` assumes we're also compiled, this needs to be ignored.
args+=(--ignore-scripts)
yarn "${args[@]}"
# Finally, run the vendor `postinstall`
yarn run postinstall
popd
} }
main "$@" main "$@"

View File

@@ -37,7 +37,7 @@ main() {
exit 1 exit 1
fi fi
if [[ ! -d $dir/lib/vscode/out ]]; then if [[ ! -d $dir/vendor/modules/code-oss-dev/out ]]; then
echo >&2 "No VS Code build detected" echo >&2 "No VS Code build detected"
help help
exit 1 exit 1

View File

@@ -14,17 +14,11 @@ main() {
# Our code imports from `out` in order to work during development but if you # 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 # have only built for production you will have not have this directory. In
# that case symlink `out` to a production build directory. # that case symlink `out` to a production build directory.
if [[ ! -e lib/vscode/out ]]; then local vscode="vendor/modules/code-oss-dev"
pushd lib local link="$vscode/out"
local out=(vscode-reh-web-*) local target="out-build"
if [[ -d "${out[0]}" ]]; then if [[ ! -e $link ]] && [[ -d $vscode/$target ]]; then
ln -s "../${out[0]}/out" ./vscode/out ln -s "$target" "$link"
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 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

View File

@@ -14,7 +14,7 @@ class Watcher {
private rootPath = path.resolve(process.cwd()) private rootPath = path.resolve(process.cwd())
private readonly paths = { private readonly paths = {
/** Path to uncompiled VS Code source. */ /** Path to uncompiled VS Code source. */
vscodeDir: path.join(this.rootPath, "lib/vscode"), vscodeDir: path.join(this.rootPath, "vendor", "modules", "code-oss-dev"),
pluginDir: process.env.PLUGIN_DIR, pluginDir: process.env.PLUGIN_DIR,
} }

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: 2.4.0 version: 2.0.1
# 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.3.0 appVersion: 4.0.2

View File

@@ -21,7 +21,6 @@ spec:
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 }}
spec: spec:
imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }}
{{- if .Values.hostnameOverride }} {{- if .Values.hostnameOverride }}
hostname: {{ .Values.hostnameOverride }} hostname: {{ .Values.hostnameOverride }}
{{- end }} {{- end }}

View File

@@ -18,9 +18,6 @@ metadata:
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}
{{- end }} {{- end }}
spec: spec:
{{- if .Values.ingress.ingressClassName }}
ingressClassName: {{ .Values.ingress.ingressClassName }}
{{- end }}
{{- if .Values.ingress.tls }} {{- if .Values.ingress.tls }}
tls: tls:
{{- range .Values.ingress.tls }} {{- range .Values.ingress.tls }}

View File

@@ -6,15 +6,10 @@ replicaCount: 1
image: image:
repository: codercom/code-server repository: codercom/code-server
tag: '4.3.0' tag: '4.0.2'
pullPolicy: Always pullPolicy: Always
# Specifies one or more secrets to be used when pulling images from a
# private container repository
# https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry
imagePullSecrets: [] imagePullSecrets: []
# - name: registry-creds
nameOverride: "" nameOverride: ""
fullnameOverride: "" fullnameOverride: ""
hostnameOverride: "" hostnameOverride: ""
@@ -40,12 +35,13 @@ service:
ingress: ingress:
enabled: false enabled: false
#annotations: #annotations:
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true" # kubernetes.io/tls-acme: "true"
#hosts: #hosts:
# - host: code-server.example.loc # - host: code-server.example.loc
# paths: # paths:
# - / # - /
ingressClassName: ""
#tls: #tls:
# - secretName: code-server # - secretName: code-server
# hosts: # hosts:
@@ -70,8 +66,6 @@ extraArgs: []
extraVars: [] extraVars: []
# - name: DISABLE_TELEMETRY # - name: DISABLE_TELEMETRY
# value: true # value: true
# - name: DOCKER_HOST
# value: "tcp://localhost:2375"
## ##
## Init containers parameters: ## Init containers parameters:
@@ -128,7 +122,6 @@ persistence:
## Enable an Specify container in extraContainers. ## Enable an Specify container in extraContainers.
## This is meant to allow adding code-server dependencies, like docker-dind. ## This is meant to allow adding code-server dependencies, like docker-dind.
extraContainers: | extraContainers: |
# If docker-dind is used, DOCKER_HOST env is mandatory to set in "extraVars"
#- name: docker-dind #- name: docker-dind
# image: docker:19.03-dind # image: docker:19.03-dind
# imagePullPolicy: IfNotPresent # imagePullPolicy: IfNotPresent

View File

@@ -14,7 +14,7 @@ pkg_json_version() {
} }
vscode_version() { vscode_version() {
jq -r .version lib/vscode/package.json jq -r .version vendor/modules/code-oss-dev/package.json
} }
os() { os() {

View File

@@ -1,8 +1,3 @@
# syntax=docker/dockerfile:experimental
FROM scratch AS packages
COPY release-packages/code-server*.deb /tmp/
FROM debian:11 FROM debian:11
RUN apt-get update \ RUN apt-get update \
@@ -39,8 +34,9 @@ RUN ARCH="$(dpkg --print-architecture)" && \
mkdir -p /etc/fixuid && \ mkdir -p /etc/fixuid && \
printf "user: coder\ngroup: coder\n" > /etc/fixuid/config.yml printf "user: coder\ngroup: coder\n" > /etc/fixuid/config.yml
COPY release-packages/code-server*.deb /tmp/
COPY ci/release-image/entrypoint.sh /usr/bin/entrypoint.sh COPY ci/release-image/entrypoint.sh /usr/bin/entrypoint.sh
RUN --mount=from=packages,src=/tmp,dst=/tmp/packages dpkg -i /tmp/packages/code-server*$(dpkg --print-architecture).deb RUN dpkg -i /tmp/code-server*$(dpkg --print-architecture).deb && rm /tmp/code-server*.deb
EXPOSE 8080 EXPOSE 8080
# This way, if someone sets $DOCKER_USER, docker-exec will still work as # This way, if someone sets $DOCKER_USER, docker-exec will still work as

View File

@@ -18,4 +18,4 @@ if [ "${DOCKER_USER-}" ]; then
fi fi
fi fi
exec dumb-init /usr/bin/code-server "$@" dumb-init /usr/bin/code-server "$@"

View File

@@ -2,6 +2,7 @@
set -euo pipefail set -euo pipefail
main() { main() {
cd "$(dirname "$0")/../.."
# Only sourcing this so we get access to $VERSION # Only sourcing this so we get access to $VERSION
source ./ci/lib.sh source ./ci/lib.sh
source ./ci/steps/steps-lib.sh source ./ci/steps/steps-lib.sh
@@ -20,18 +21,94 @@ main() {
exit 1 exit 1
fi fi
# NOTE: we need to make sure cdrci/homebrew-core
# is up-to-date
# otherwise, brew bump-formula-pr will use an
# outdated base
echo "Cloning cdrci/homebrew-core"
git clone https://github.com/cdrci/homebrew-core.git
# Make sure the git clone step is successful
if directory_exists "homebrew-core"; then
echo "git clone failed. Cannot find homebrew-core directory."
ls -la
exit 1
fi
echo "Changing into homebrew-core directory"
pushd homebrew-core && pwd
echo "Adding Homebrew/homebrew-core"
git remote add upstream https://github.com/Homebrew/homebrew-core.git
# Make sure the git remote step is successful
if ! git config remote.upstream.url > /dev/null; then
echo "git remote add upstream failed."
echo "Could not find upstream in list of remotes."
git remote -v
exit 1
fi
# TODO@jsjoeio - can I somehow check that this succeeded?
echo "Fetching upstream Homebrew/hombrew-core commits"
git fetch upstream
# TODO@jsjoeio - can I somehow check that this succeeded?
echo "Merging in latest Homebrew/homebrew-core changes"
git merge upstream/master
echo "Pushing changes to cdrci/homebrew-core fork on GitHub"
# GIT_ASKPASS lets us use the password when pushing without revealing it in the process list
# See: https://serverfault.com/a/912788
PATH_TO_GIT_ASKPASS="$HOME/git-askpass.sh"
# Source: https://serverfault.com/a/912788
# shellcheck disable=SC2016,SC2028
echo 'echo $HOMEBREW_GITHUB_API_TOKEN' > "$PATH_TO_ASKPASS"
# Make sure the git-askpass.sh file creation is successful
if file_exists "$PATH_TO_GIT_ASKPASS"; then
echo "git-askpass.sh not found in $HOME."
ls -la "$HOME"
exit 1
fi
# Ensure it's executable since we just created it
chmod +x "$PATH_TO_GIT_ASKPASS"
# Make sure the git-askpass.sh file is executable
if is_executable "$PATH_TO_GIT_ASKPASS"; then
echo "$PATH_TO_GIT_ASKPASS is not executable."
ls -la "$PATH_TO_GIT_ASKPASS"
exit 1
fi
# Export the variables so git sees them
export HOMEBREW_GITHUB_API_TOKEN="$HOMEBREW_GITHUB_API_TOKEN"
export GIT_ASKPASS="$PATH_TO_ASKPASS"
git push https://cdr-oss@github.com/cdr-oss/homebrew-core.git --all
# Find the docs for bump-formula-pr here # Find the docs for bump-formula-pr here
# https://github.com/Homebrew/brew/blob/master/Library/Homebrew/dev-cmd/bump-formula-pr.rb#L18 # https://github.com/Homebrew/brew/blob/master/Library/Homebrew/dev-cmd/bump-formula-pr.rb#L18
local output local output
if ! output=$(brew bump-formula-pr --version="${VERSION}" code-server --no-browse --no-audit 2>&1); then if ! output=$(brew bump-formula-pr --version="${VERSION}" code-server --no-browse --no-audit 2>&1); then
if [[ $output == *"Duplicate PRs should not be opened"* ]]; then if [[ $output == *"Duplicate PRs should not be opened"* ]]; then
echo "$VERSION is already submitted" echo "$VERSION is already submitted"
exit 0
else else
echo "$output" echo "$output"
exit 1 exit 1
fi fi
fi fi
# Clean up and remove homebrew-core
popd
rm -rf homebrew-core
# Make sure homebrew-core is removed
if directory_exists "homebrew-core"; then
echo "rm -rf homebrew-core failed."
ls -la
fi
} }
main "$@" main "$@"

View File

@@ -1,15 +1,36 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# See if this version already exists on Docker Hub.
function version_exists() {
local output
output=$(curl --silent "https://index.docker.io/v1/repositories/codercom/code-server/tags/$VERSION")
if [[ $output == "Tag not found" ]]; then
return 1
else
return 0
fi
}
main() { main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
# ci/lib.sh sets VERSION so it's available to ci/release-image/docker-bake.hcl
# to push the VERSION tag. # ci/lib.sh sets VERSION and provides download_artifact here
source ./ci/lib.sh source ./ci/lib.sh
# NOTE@jsjoeio - this script assumes that you've downloaded if version_exists; then
# the release-packages artifact to ./release-packages before echo "$VERSION is already pushed"
# running this docker buildx step return
fi
# Download the release-packages artifact
download_artifact release-packages ./release-packages
# Login to Docker
if [[ ${CI-} ]]; then
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
fi
docker buildx bake -f ci/release-image/docker-bake.hcl --push docker buildx bake -f ci/release-image/docker-bake.hcl --push
} }

View File

@@ -13,6 +13,28 @@ main() {
exit 1 exit 1
fi fi
# NOTE@jsjoeio - only needed if we use the download_artifact
# because we talk to the GitHub API.
# Needed to use GitHub API
if ! is_env_var_set "GITHUB_TOKEN"; then
echo "GITHUB_TOKEN is not set. Cannot download npm release artifact without GitHub credentials."
exit 1
fi
## Environment
# This string is used to determine how we should tag the npm release.
# Environment can be one of three choices:
# "development" - this means we tag with the PR number, allowing
# a developer to install this version with `yarn add code-server@<pr-number>`
# "staging" - this means we tag with `beta`, allowing
# a developer to install this version with `yarn add code-server@beta`
# "production" - this means we tag with `latest` (default), allowing
# a developer to install this version with `yarn add code-server@latest`
if ! is_env_var_set "ENVIRONMENT"; then
echo "ENVIRONMENT is not set. Cannot determine npm tag without ENVIRONMENT."
exit 1
fi
## Publishing Information ## Publishing Information
# All the variables below are used to determine how we should publish # All the variables below are used to determine how we should publish
# the npm package. We also use this information for bumping the version. # the npm package. We also use this information for bumping the version.
@@ -25,59 +47,22 @@ main() {
exit 1 exit 1
fi fi
# We use this to grab the PR_NUMBER # We need TAG to know what to publish under on npm
if ! is_env_var_set "GITHUB_REF"; then # Options are "latest", "beta", or "<pr number >"
echo "GITHUB_REF is not set. Are you running this locally? We rely on values provided by GitHub." # See Environment comments above to know when each is used.
if ! is_env_var_set "NPM_TAG"; then
echo "NPM_TAG is not set. This is needed for tagging the npm release."
exit 1 exit 1
fi fi
# We use this when setting NPM_VERSION echo "using tag: $NPM_TAG"
if ! is_env_var_set "GITHUB_SHA"; then
echo "GITHUB_SHA is not set. Are you running this locally? We rely on values provided by GitHub."
exit 1
fi
# We use this to determine the NPM_ENVIRONMENT
if ! is_env_var_set "GITHUB_EVENT_NAME"; then
echo "GITHUB_EVENT_NAME is not set. Are you running this locally? We rely on values provided by GitHub."
exit 1
fi
# Check that we're using at least v7 of npm CLI
if ! command -v jq &> /dev/null; then
echo "Couldn't find jq"
echo "We need this in order to modify the package.json for dev builds."
exit 1
fi
# This allows us to publish to npm in CI workflows # This allows us to publish to npm in CI workflows
if [[ ${CI-} ]]; then if [[ ${CI-} ]]; then
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
fi fi
## Environment download_artifact npm-package ./release-npm-package
# This string is used to determine how we should tag the npm release.
# Environment can be one of three choices:
# "development" - this means we tag with the PR number, allowing
# a developer to install this version with `yarn add code-server@<pr-number>`
# "staging" - this means we tag with `beta`, allowing
# a developer to install this version with `yarn add code-server@beta`
# "production" - this means we tag with `latest` (default), allowing
# a developer to install this version with `yarn add code-server@latest`
if ! is_env_var_set "NPM_ENVIRONMENT"; then
echo "NPM_ENVIRONMENT is not set. Determining in script based on GITHUB environment variables."
if [[ "$GITHUB_EVENT_NAME" == 'push' && "$GITHUB_REF" == 'refs/heads/main' ]]; then
NPM_ENVIRONMENT="staging"
else
NPM_ENVIRONMENT="development"
fi
echo "Using npm environment: $NPM_ENVIRONMENT"
fi
# NOTE@jsjoeio - this script assumes we have the artifact downloaded on disk
# That happens in CI as a step before we run this.
# https://github.com/actions/upload-artifact/issues/38 # https://github.com/actions/upload-artifact/issues/38
tar -xzf release-npm-package/package.tar.gz tar -xzf release-npm-package/package.tar.gz
@@ -85,62 +70,30 @@ main() {
# See: https://github.com/coder/code-server/pull/3935 # See: https://github.com/coder/code-server/pull/3935
echo "node_modules.asar" > release/.npmignore echo "node_modules.asar" > release/.npmignore
# We use this to set the name of the package in the
# package.json
PACKAGE_NAME="code-server"
# NOTES:@jsjoeio # NOTES:@jsjoeio
# We only need to run npm version for "development" and "staging". # We only need to run npm version for "development" and "staging".
# This is because our release:prep script automatically bumps the version # This is because our release:prep script automatically bumps the version
# in the package.json and we commit it as part of the release PR. # in the package.json and we commit it as part of the release PR.
if [[ "$NPM_ENVIRONMENT" == "production" ]]; then if [[ "$ENVIRONMENT" == "production" ]]; then
NPM_VERSION="$VERSION" NPM_VERSION="$VERSION"
# This means the npm version will be published as "stable"
# and installed when a user runs `yarn install code-server`
NPM_TAG="latest"
else else
COMMIT_SHA="$GITHUB_SHA"
echo "Not a production environment" echo "Not a production environment"
echo "Found environment: $NPM_ENVIRONMENT" echo "Found environment: $ENVIRONMENT"
echo "Manually bumping npm version..." echo "Manually bumping npm version..."
if [[ "$NPM_ENVIRONMENT" == "staging" ]]; then if ! is_env_var_set "PR_NUMBER_AND_COMMIT_SHA"; then
NPM_VERSION="$VERSION-beta-$COMMIT_SHA" echo "PR_NUMBER_AND_COMMIT_SHA is not set. This is needed for setting the npm version in non-production environments."
# This means the npm version will be tagged with "beta" exit 1
# and installed when a user runs `yarn install code-server@beta`
NPM_TAG="beta"
fi fi
if [[ "$NPM_ENVIRONMENT" == "development" ]]; then
# Source: https://github.com/actions/checkout/issues/58#issuecomment-614041550
PR_NUMBER=$(echo "$GITHUB_REF" | awk 'BEGIN { FS = "/" } ; { print $3 }')
NPM_VERSION="$VERSION-$PR_NUMBER-$COMMIT_SHA"
PACKAGE_NAME="@coder/code-server-pr"
# This means the npm version will be tagged with "<pr number>"
# and installed when a user runs `yarn install code-server@<pr number>`
NPM_TAG="$PR_NUMBER"
fi
echo "using tag: $NPM_TAG"
echo "using package name: $PACKAGE_NAME"
# We modify the version in the package.json # We modify the version in the package.json
# to be the current version + the PR number + commit SHA # to be the current version + the PR number + commit SHA
# or we use current version + beta + commit SHA
# Example: "version": "4.0.1-4769-ad7b23cfe6ffd72914e34781ef7721b129a23040" # Example: "version": "4.0.1-4769-ad7b23cfe6ffd72914e34781ef7721b129a23040"
# Example: "version": "4.0.1-beta-ad7b23cfe6ffd72914e34781ef7721b129a23040" NPM_VERSION="$VERSION-$PR_NUMBER_AND_COMMIT_SHA"
pushd release pushd release
# NOTE@jsjoeio # NOTE:@jsjoeio
# I originally tried to use `yarn version` but ran into issues and abandoned it. # I originally tried to use `yarn version` but ran into issues and abandoned it.
npm version "$NPM_VERSION" npm version "$NPM_VERSION"
# NOTE@jsjoeio
# Use the development package name
# This is so we don't clutter the code-server versions on npm
# with development versions.
# jq can't edit in place so we must store in memory and echo
local contents
contents="$(jq ".name |= \"$PACKAGE_NAME\"" package.json)"
echo "${contents}" > package.json
popd popd
fi fi
@@ -154,10 +107,7 @@ main() {
return return
fi fi
# NOTE@jsjoeio yarn publish --non-interactive release --tag "$NPM_TAG"
# Since the dev builds are scoped to @coder
# We pass --access public to ensure npm knows it's not private.
yarn publish --non-interactive release --tag "$NPM_TAG" --access public
} }
main "$@" main "$@"

View File

@@ -7,8 +7,7 @@
- [Creating pull requests](#creating-pull-requests) - [Creating pull requests](#creating-pull-requests)
- [Commits and commit history](#commits-and-commit-history) - [Commits and commit history](#commits-and-commit-history)
- [Development workflow](#development-workflow) - [Development workflow](#development-workflow)
- [Version updates to Code](#version-updates-to-code) - [Updates to VS Code](#updates-to-vs-code)
- [Patching Code](#patching-code)
- [Build](#build) - [Build](#build)
- [Help](#help) - [Help](#help)
- [Test](#test) - [Test](#test)
@@ -17,7 +16,7 @@
- [Integration tests](#integration-tests) - [Integration tests](#integration-tests)
- [End-to-end tests](#end-to-end-tests) - [End-to-end tests](#end-to-end-tests)
- [Structure](#structure) - [Structure](#structure)
- [Modifications to Code](#modifications-to-code) - [Modifications to VS Code](#modifications-to-vs-code)
- [Currently Known Issues](#currently-known-issues) - [Currently Known Issues](#currently-known-issues)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -45,8 +44,6 @@ Here is what is needed:
signature signature
verification](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification) verification](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification)
or follow [this tutorial](https://joeprevite.com/verify-commits-on-github) or follow [this tutorial](https://joeprevite.com/verify-commits-on-github)
- `quilt`
- Used to manage patches to Code
- `rsync` and `unzip` - `rsync` and `unzip`
- Used for code-server releases - Used for code-server releases
- `bats` - `bats`
@@ -60,7 +57,7 @@ If you're developing code-server on Linux, make sure you have installed or insta
sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev python-is-python3 sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev 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 VS Code. See [their Wiki](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites) for more information.
## Creating pull requests ## Creating pull requests
@@ -81,44 +78,41 @@ we'll guide you.
## Development workflow ## Development workflow
The current development workflow is a bit tricky because we have this repo and we use our `coder/vscode` fork inside it with [`yarn link`](https://classic.yarnpkg.com/lang/en/docs/cli/link/).
Here are these steps you should follow to get your dev environment setup:
1. `git clone https://github.com/coder/code-server.git` - Clone `code-server` 1. `git clone https://github.com/coder/code-server.git` - Clone `code-server`
2. `git submodule update --init` - Clone `vscode` submodule 2. `git clone https://github.com/coder/vscode.git` - Clone `vscode`
3. `quilt push -a` - Apply patches to the `vscode` submodule. 3. `cd vscode && yarn install` - install the dependencies in the `vscode` repo
4. `yarn` - Install dependencies 4. `cd code-server && yarn install` - install the dependencies in the `code-server` repo
5. `yarn watch` - Launch code-server localhost:8080. code-server will be live 5. `cd vscode && yarn link` - use `yarn` to create a symlink to the `vscode` repo (`code-oss-dev` package)
reloaded when changes are made; the browser needs to be refreshed manually. 6. `cd code-server && yarn link code-oss-dev --modules-folder vendor/modules` - links your local `vscode` repo (`code-oss-dev` package) inside your local version of code-server
7. `cd code-server && yarn watch` - this will spin up code-server on localhost:8080 which you can start developing. It will live reload changes to the source.
When pulling down changes that include modifications to the patches you will ### Updates to VS Code
need to apply them with `quilt`. If you pull down changes that update the
`vscode` submodule you will need to run `git submodule update --init` and
re-apply the patches.
### Version updates to Code If changes are made and merged into `main` in the [`coder/vscode`](https://github.com/coder/vscode) repo, then you'll need to update the version in the `code-server` repo by following these steps:
1. Update the `lib/vscode` submodule to the desired upstream version branch. 1. Update the package tag listed in `vendor/package.json`:
2. Apply the patches (`quilt push -a`) or restore your stashed changes. At this
stage you may need to resolve conflicts. For example use `quilt push -f`, ```json
manually apply the rejected portions, then `quilt refresh`. {
3. From the code-server **project root**, run `yarn install`. "devDependencies": {
4. Test code-server locally to make sure everything works. "vscode": "coder/vscode#<latest-commit-sha>"
5. Check the Node.js version that's used by Electron (which is shipped with VS }
}
```
2. From the code-server **project root**, run `yarn install`.
Then, test code-server locally to make sure everything works.
3. Check the Node.js version that's used by Electron (which is shipped with VS
Code. If necessary, update your version of Node.js to match. Code. If necessary, update your version of Node.js to match.
6. Commit the updated submodule and patches to `code-server`. 4. Open a PR
7. Open a PR.
### Patching Code > Watch for updates to
> `vendor/modules/code-oss-dev/src/vs/code/browser/workbench/workbench.html`. You may need to
0. You can go through the patch stack with `quilt push` and `quilt pop`. > make changes to `src/browser/pages/vscode.html`.
1. Create a new patch (`quilt new {name}.diff`) or use an existing patch.
2. Add the file(s) you are patching (`quilt add [-P patch] {file}`). A file
**must** be added before you make changes to it.
3. Make your changes. Patches do not need to be independent of each other but
each patch must result in a working code-server without any broken in-between
states otherwise they are difficult to test and modify.
4. Add your changes to the patch (`quilt refresh`)
5. Add a comment in the patch about the reason for the patch and how to
reproduce the behavior it fixes or adds. Every patch should have an e2e test
as well.
### Build ### Build
@@ -214,46 +208,99 @@ code-server running locally. In CI, this is taken care of for you.
## Structure ## Structure
The `code-server` script serves as an HTTP API for login and starting a remote The `code-server` script serves as an HTTP API for login and starting a remote VS
Code process. Code process.
The CLI code is in [src/node](../src/node) and the HTTP routes are implemented The CLI code is in [src/node](../src/node) and the HTTP routes are implemented
in [src/node/routes](../src/node/routes). in [src/node/routes](../src/node/routes).
Most of the meaty parts are in the Code portion of the codebase under Most of the meaty parts are in the VS Code portion of the codebase under
[lib/vscode](../lib/vscode), which we describe next. [vendor/modules/code-oss-dev](../vendor/modules/code-oss-dev), which we describe next.
### Modifications to Code ### Modifications to VS Code
Our modifications to Code can be found in the [patches](../patches) directory. In v1 of code-server, we had a patch of VS Code that split the codebase into a
We pull in Code as a submodule pointing to an upstream release branch. front-end and a server. The front-end consisted of the UI code, while the server
ran the extensions and exposed an API to the front-end for file access and all
UI needs.
In v1 of code-server, we had Code as a submodule and used a single massive patch Over time, Microsoft added support to VS Code to run it on the web. They have
that split the codebase into a front-end and a server. The front-end consisted made the front-end open source, but not the server. As such, code-server v2 (and
of the UI code, while the server ran the extensions and exposed an API to the later) uses the VS Code front-end and implements the server. We do this by using
front-end for file access and all UI needs. a Git subtree to fork and modify VS Code. This code lives under
[vendor/modules/code-oss-dev](../vendor/modules/code-oss-dev).
Over time, Microsoft added support to Code to run it on the web. They had made Some noteworthy changes in our version of VS Code include:
the front-end open source, but not the server. As such, code-server v2 (and
later) uses the Code front-end and implements the server. We did this by using a
Git subtree to fork and modify Code.
Microsoft eventually made the server open source and we were able to reduce our - Adding our build file, [`vendor/modules/code-oss-dev/coder.js`](../vendor/modules/code-oss-dev/coder.js), which includes build steps specific to code-server
changes significantly. Some time later we moved back to a submodule and patches - Node.js version detection changes in [`build/lib/node.ts`](../vendor/modules/code-oss-dev/build/lib/node.ts) and [`build/lib/util.ts`](../vendor/modules/code-oss-dev/build/lib/util.ts)
(managed by `quilt` this time instead of the mega-patch). - Allowing extra extension directories
- Added extra arguments to [`src/vs/platform/environment/common/argv.ts`](../vendor/modules/code-oss-dev/src/vs/platform/environment/common/argv.ts) and to [`src/vs/platform/environment/node/argv.ts`](../vendor/modules/code-oss-dev/src/vs/platform/environment/node/argv.ts)
- Added extra environment state to [`src/vs/platform/environment/common/environment.ts`](../vendor/modules/code-oss-dev/src/vs/platform/environment/common/environment.ts);
- Added extra getters to [`src/vs/platform/environment/common/environmentService.ts`](../vendor/modules/code-oss-dev/src/vs/platform/environment/common/environmentService.ts)
- Added extra scanning paths to [`src/vs/platform/extensionManagement/node/extensionsScanner.ts`](../vendor/modules/code-oss-dev/src/vs/platform/extensionManagement/node/extensionsScanner.ts)
- Additions/removals from [`package.json`](../vendor/modules/code-oss-dev/package.json):
- Removing `electron`, `keytar` and `native-keymap` to avoid pulling in desktop dependencies during build on Linux
- Removing `gulp-azure-storage` and `gulp-tar` (unsued in our build process, may pull in outdated dependencies)
- Adding `proxy-agent`, `proxy-from-env` (for proxying) and `rimraf` (used during build/install steps)
- Adding our branding/custom URLs/version:
- [`product.json`](../vendor/modules/code-oss-dev/product.json)
- [`src/vs/base/common/product.ts`](../vendor/modules/code-oss-dev/src/vs/base/common/product.ts)
- [`src/vs/workbench/browser/parts/dialogs/dialogHandler.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts)
- [`src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts)
- [`src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts)
- Removing azure/macOS signing related dependencies from [`build/package.json`](../vendor/modules/code-oss-dev/build/package.json)
- Modifying `.gitignore` to allow us to add files to `src/vs/server` and modifying `.eslintignore` to ignore lint on the shared files below (we use different formatter settings than VS Code).
- Sharing some files with our codebase via symlinks:
- [`src/vs/base/common/ipc.d.ts`](../vendor/modules/code-oss-dev/src/vs/base/common/ipc.d.ts) points to [`typings/ipc.d.ts`](../typings/ipc.d.ts)
- [`src/vs/base/common/util.ts`](../vendor/modules/code-oss-dev/src/vs/base/common/util.ts) points to [`src/common/util.ts`](../src/common/util.ts)
- [`src/vs/base/node/proxy_agent.ts`](../vendor/modules/code-oss-dev/src/vs/base/node/proxy_agent.ts) points to [`src/node/proxy_agent.ts`](../src/node/proxy_agent.ts)
- Allowing socket changes by adding `setSocket` in [`src/vs/base/parts/ipc/common/ipc.net.ts`](../vendor/modules/code-oss-dev/src/vs/base/parts/ipc/common/ipc.net.ts)
- We use this for connection persistence in our server-side code.
- Added our server-side Node.JS code to `src/vs/server`.
- This code includes the logic to spawn the various services (extension host, terminal, etc.) and some glue
- Added [`src/vs/workbench/browser/client.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/browser/client.ts) to hold some server customizations.
- Includes the functionality for the Log Out command and menu item
- Also, imported and called `initialize` from the main web file, [`src/vs/workbench/browser/web.main.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/browser/web.main.ts)
- Added a (hopefully temporary) hotfix to [`src/vs/workbench/common/resources.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/common/resources.ts) to get context menu actions working for the Git integration.
- Added connection type to WebSocket query parameters in [`src/vs/platform/remote/common/remoteAgentConnection.ts`](../vendor/modules/code-oss-dev/src/vs/platform/remote/common/remoteAgentConnection.ts)
- Added `CODE_SERVER*` variables to the sanitization list in [`src/vs/base/common/processes.ts`](../vendor/modules/code-oss-dev/src/vs/base/common/processes.ts)
- Fix localization support:
- Added file [`src/vs/workbench/services/localizations/browser/localizationsService.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/services/localizations/browser/localizationsService.ts).
- Modified file [`src/vs/base/common/platform.ts`](../vendor/modules/code-oss-dev/src/vs/base/common/platform.ts)
- Modified file [`src/vs/base/node/languagePacks.js`](../vendor/modules/code-oss-dev/src/vs/base/node/languagePacks.js)
- Added code to allow server to inject settings to [`src/vs/platform/product/common/product.ts`](../vendor/modules/code-oss-dev/src/vs/platform/product/common/product.ts)
- Extension fixes:
- Avoid disabling extensions by extensionKind in [`src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts) (Needed for vscode-icons)
- Remove broken symlinks in [`extensions/postinstall.js`](../vendor/modules/code-oss-dev/extensions/postinstall.js)
- Add tip about extension gallery in [`src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts)
- Use our own server for GitHub authentication in [`extensions/github-authentication/src/githubServer.ts`](../vendor/modules/code-oss-dev/extensions/github-authentication/src/githubServer.ts)
- Settings persistence on the server in [`src/vs/workbench/services/environment/browser/environmentService.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/services/environment/browser/environmentService.ts)
- Add extension install fallback in [`src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts)
- Add proxy-agent monkeypatch and keep extension host indefinitely running in [`src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts)
- Patch build system to avoid removing extension dependencies for `yarn global add` users in [`build/lib/extensions.ts`](../vendor/modules/code-oss-dev/build/lib/extensions.ts)
- Allow all extensions to use proposed APIs in [`src/vs/workbench/services/environment/browser/environmentService.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/services/environment/browser/environmentService.ts)
- Make storage writes async to allow extensions to wait for them to complete in [`src/vs/platform/storage/common/storage.ts`](../vendor/modules/code-oss-dev/src/vs/platform/storage/common/storage.ts)
- Specify webview path in [`src/vs/code/browser/workbench/workbench.ts`](../vendor/modules/code-oss-dev/src/vs/code/browser/workbench/workbench.ts)
- URL readability improvements for folder/workspace in [`src/vs/code/browser/workbench/workbench.ts`](../vendor/modules/code-oss-dev/src/vs/code/browser/workbench/workbench.ts)
- Socket/Authority-related fixes (for remote proxying etc.):
- [`src/vs/code/browser/workbench/workbench.ts`](../vendor/modules/code-oss-dev/src/vs/code/browser/workbench/workbench.ts)
- [`src/vs/platform/remote/browser/browserSocketFactory.ts`](../vendor/modules/code-oss-dev/src/vs/platform/remote/browser/browserSocketFactory.ts)
- [`src/vs/base/common/network.ts`](../vendor/modules/code-oss-dev/src/vs/base/common/network.ts)
- Added code to write out IPC path in [`src/vs/workbench/api/node/extHostCLIServer.ts`](../vendor/modules/code-oss-dev/src/vs/workbench/api/node/extHostCLIServer.ts)
As the web portion of Code continues to mature, we'll be able to shrink and As the web portion of VS Code matures, we'll be able to shrink and possibly
possibly eliminate our patches. In the meantime, upgrading the Code version eliminate our modifications. In the meantime, upgrading the VS Code version requires
requires us to ensure that our changes are still applied correctly and work as us to ensure that our changes are still applied and work as intended. In the future,
intended. In the future, we'd like to run Code unit tests against our builds to we'd like to run VS Code unit tests against our builds to ensure that features
ensure that features work as expected. work as expected.
> We have [extension docs](../ci/README.md) on the CI and build system. > We have [extension docs](../ci/README.md) on the CI and build system.
If the functionality you're working on does NOT depend on code from Code, please If the functionality you're working on does NOT depend on code from VS Code, please
move it out and into code-server. move it out and into code-server.
### Currently Known Issues ### Currently Known Issues
- Creating custom Code extensions and debugging them doesn't work - Creating custom VS Code extensions and debugging them doesn't work
- Extension profiling and tips are currently disabled - Extension profiling and tips are currently disabled

View File

@@ -19,7 +19,7 @@
- [Docker](#docker) - [Docker](#docker)
- [Homebrew](#homebrew) - [Homebrew](#homebrew)
- [npm](#npm) - [npm](#npm)
- [Syncing with upstream Code](#syncing-with-upstream-code) - [Syncing with Upstream VS Code](#syncing-with-upstream-vs-code)
- [Testing](#testing) - [Testing](#testing)
- [Documentation](#documentation) - [Documentation](#documentation)
- [Troubleshooting](#troubleshooting) - [Troubleshooting](#troubleshooting)
@@ -34,6 +34,7 @@ as well as share our workflow for maintaining the project.
Current maintainers: Current maintainers:
- @code-asher - @code-asher
- @TeffenEllis
- @jsjoeio - @jsjoeio
Occasionally, other Coder employees may step in time to time to assist with code-server. Occasionally, other Coder employees may step in time to time to assist with code-server.
@@ -164,7 +165,7 @@ If you're the current release manager, follow these steps:
### Publishing a release ### Publishing a release
1. Create a new branch called `v0.0.0` (replace 0s with actual version aka v4.3.0) 1. Create a release branch called `v0.0.0` but replace with new version
1. Run `yarn release:prep` and type in the new version (e.g., `3.8.1`) 1. Run `yarn release:prep` and type in the new version (e.g., `3.8.1`)
1. GitHub Actions will generate the `npm-package`, `release-packages` and 1. GitHub Actions will generate the `npm-package`, `release-packages` and
`release-images` artifacts. You do not have to wait for this step to complete `release-images` artifacts. You do not have to wait for this step to complete
@@ -214,9 +215,17 @@ We publish code-server as a npm package [here](https://www.npmjs.com/package/cod
This is currently automated with the release process. This is currently automated with the release process.
## Syncing with upstream Code ## Syncing with Upstream VS Code
Refer to the [contributing docs](https://coder.com/docs/code-server/latest/CONTRIBUTING#version-updates-to-code) for information on how to update Code within code-server. The VS Code portion of code-server lives under [`coder/vscode`](https://github.com/coder/vscode). To update VS Code for code-server, follow these steps:
1. `git checkout -b vscode-update` - Create a new branch locally based off `main`
2. `git fetch upstream` - Fetch upstream (VS Code)'s latest `main` branch
3. `git merge upstream/main` - Merge it locally
1. If there are merge conflicts, fix them locally
4. Open a PR merging your branch (`vscode-update`) into `main` and add the code-server review team
Ideally, our fork stays as close to upstream as possible. See the differences between our fork and upstream [here](https://github.com/microsoft/vscode/compare/main...cdr:main).
## Testing ## Testing

View File

@@ -1,6 +1,6 @@
# code-server # code-server
[!["GitHub Discussions"](https://img.shields.io/badge/%20GitHub-%20Discussions-gray.svg?longCache=true&logo=github&colorB=purple)](https://github.com/coder/code-server/discussions) [!["Join us on Slack"](https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen)](https://coder.com/community) [![Twitter Follow](https://img.shields.io/twitter/follow/CoderHQ?label=%40CoderHQ&style=social)](https://twitter.com/coderhq) [![codecov](https://codecov.io/gh/coder/code-server/branch/main/graph/badge.svg?token=5iM9farjnC)](https://codecov.io/gh/coder/code-server) [![See latest](https://img.shields.io/static/v1?label=Docs&message=see%20latest&color=blue)](https://coder.com/docs/code-server/latest) [!["GitHub Discussions"](https://img.shields.io/badge/%20GitHub-%20Discussions-gray.svg?longCache=true&logo=github&colorB=purple)](https://github.com/coder/code-server/discussions) [!["Join us on Slack"](https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen)](https://cdr.co/join-community) [![Twitter Follow](https://img.shields.io/twitter/follow/CoderHQ?label=%40CoderHQ&style=social)](https://twitter.com/coderhq) [![codecov](https://codecov.io/gh/coder/code-server/branch/main/graph/badge.svg?token=5iM9farjnC)](https://codecov.io/gh/coder/code-server) [![See v4.0.2 docs](https://img.shields.io/static/v1?label=Docs&message=see%20v4.0.2%20&color=blue)](https://github.com/coder/code-server/tree/v4.0.2/docs)
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.

View File

@@ -26,8 +26,8 @@ We use the following tools to help us stay on top of vulnerability mitigation.
Coder sponsors the development and maintenance of the code-server project. We will fix security issues within 90 days of receiving a report and publish the fix in a subsequent release. The code-server project does not provide backports or patch releases for security issues at this time. Coder sponsors the development and maintenance of the code-server project. We will fix security issues within 90 days of receiving a report and publish the fix in a subsequent release. The code-server project does not provide backports or patch releases for security issues at this time.
| Version | Supported | | Version | Supported |
| ------------------------------------------------------- | ------------------ | | ----------------------------------------------------- | ------------------ |
| [Latest](https://github.com/coder/code-server/releases) | :white_check_mark: | | [Latest](https://github.com/coder/code-server/releases) | :white_check_mark: |
## Reporting a Vulnerability ## Reporting a Vulnerability

View File

@@ -60,6 +60,6 @@ As `code-server` is based on VS Code, you can follow the steps described on Duck
code-server --enable-proposed-api genuitecllc.codetogether code-server --enable-proposed-api genuitecllc.codetogether
``` ```
Another option would be to add a value in code-server's [config file](https://coder.com/docs/code-server/v4.3.0/FAQ#how-does-the-config-file-work). Another option would be to add a value in code-server's [config file](https://coder.com/docs/code-server/v4.0.2/FAQ#how-does-the-config-file-work).
3. Refresh code-server and navigate to the CodeTogether icon in the sidebar to host or join a coding session. 3. Refresh code-server and navigate to the CodeTogether icon in the sidebar to host or join a coding session.

View File

@@ -52,7 +52,7 @@ There are several approaches to operating and exposing code-server securely:
We highly recommend using [port forwarding via We highly recommend using [port forwarding via
SSH](https://help.ubuntu.com/community/SSH/OpenSSH/PortForwarding) to access SSH](https://help.ubuntu.com/community/SSH/OpenSSH/PortForwarding) to access
code-server. If you have an SSH server on your remote machine, this approach code-server. If you have an SSH server on your remote machine, this approach
doesn't require any additional setup at all. doesn't required additional setup.
The downside to SSH forwarding, however, is that you can't access code-server The downside to SSH forwarding, however, is that you can't access code-server
when using machines without SSH clients (such as iPads). If this applies to you, when using machines without SSH clients (such as iPads). If this applies to you,

View File

@@ -1,6 +1,6 @@
# code-server Helm Chart # code-server Helm Chart
[![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square)](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) [![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)](https://img.shields.io/badge/Type-application-informational?style=flat-square) [![AppVersion: 4.3.0](https://img.shields.io/badge/AppVersion-4.3.0-informational?style=flat-square)](https://img.shields.io/badge/AppVersion-4.3.0-informational?style=flat-square) [![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square)](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) [![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)](https://img.shields.io/badge/Type-application-informational?style=flat-square) [![AppVersion: 4.0.2](https://img.shields.io/badge/AppVersion-4.0.2-informational?style=flat-square)](https://img.shields.io/badge/AppVersion-4.0.2-informational?style=flat-square)
[code-server](https://github.com/coder/code-server) code-server is VS Code running [code-server](https://github.com/coder/code-server) code-server is VS Code running
on a remote server, accessible through the browser. on a remote server, accessible through the browser.
@@ -73,7 +73,7 @@ and their default values.
| hostnameOverride | string | `""` | | hostnameOverride | string | `""` |
| image.pullPolicy | string | `"Always"` | | image.pullPolicy | string | `"Always"` |
| image.repository | string | `"codercom/code-server"` | | image.repository | string | `"codercom/code-server"` |
| image.tag | string | `"4.3.0"` | | image.tag | string | `"4.0.2"` |
| imagePullSecrets | list | `[]` | | imagePullSecrets | list | `[]` |
| ingress.enabled | bool | `false` | | ingress.enabled | bool | `false` |
| nameOverride | string | `""` | | nameOverride | string | `""` |

View File

@@ -1,9 +1,7 @@
# Using code-server on iOS with iSH # Using code-server on iOS with iSH
1. Install iSH from the [App Store](https://apps.apple.com/us/app/ish-shell/id1436902243) 1. Install iSH from the [App Store](https://apps.apple.com/us/app/ish-shell/id1436902243)
2. Install `curl` and `nano` with `apk add curl nano` 2. Install `curl` with `apk add curl`
3. Configure iSH to use an earlier version of NodeJS with `nano /etc/apk/repositories` and edit `v3.14` to `v3.12` on both repository links. 3. Install code-server with `curl -fsSL https://code-server.dev/install.sh | sh`
4. Install `nodejs` and `npm` with `apk add nodejs npm` 4. Run code-server with `code-server`
5. Install code-server with `curl -fsSL https://code-server.dev/install.sh | sh` 5. Access on localhost:8080 in your browser
6. Run code-server with `code-server`
7. Access on localhost:8080 in your browser

View File

@@ -7,5 +7,5 @@ for accessing your IDE out of the box.
```console ```console
$ code-server --link $ code-server --link
Proxying code-server, you can access your IDE at https://example.coder.co Proxying code-server, you can access your IDE at https://example.cdr.co
``` ```

View File

@@ -1,5 +1,5 @@
{ {
"versions": ["v4.3.0"], "versions": ["v4.0.2"],
"routes": [ "routes": [
{ {
"title": "Home", "title": "Home",

View File

@@ -33,7 +33,7 @@ new Compute Engine VM instance:
4. Choose the **region** that's closest to you based on [GCP 4. Choose the **region** that's closest to you based on [GCP
ping](https://gcping.com/). ping](https://gcping.com/).
5. Choose a **zone** (any option is fine). 5. Choose a **zone** (any option is fine).
6. We recommend choosing an **E2 series instance** from the [general-purpose 6. We recommend choose an **E2 series instance** from the [general-purpose
family](https://cloud.google.com/compute/docs/machine-types#general_purpose). family](https://cloud.google.com/compute/docs/machine-types#general_purpose).
7. Change the instance type to **custom** and set at least **2 cores** and **2 7. Change the instance type to **custom** and set at least **2 cores** and **2
GB of RAM**. You can add more resources if desired, though you can also edit GB of RAM**. You can add more resources if desired, though you can also edit

View File

@@ -10,45 +10,40 @@
- [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 -->
## 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. Install Debian by running the following: 2. Install Debian by running the following.
- Run `termux-setup-storage` to allow storage access, or else code-server won't be able to read from `/sdcard`.\ - Run `termux-setup-storage` to allow storage access, or else code-server won't be able to read from `/sdcard`.\
> The following command is from [proot-distro](https://github.com/termux/proot-distro), but you can also use [Andronix](https://andronix.app/). If you used the Andronix command then you may have to edit the `start-debian.sh` script to mount `/sdcard` just as simple as uncommenting the `command+=" -b /sdcard"` line.
> The following command was extracted from [Andronix](https://andronix.app/) you can also use [proot-distro](https://github.com/termux/proot-distro).
> After Debian is installed the `~ $` will change to `root@localhost`. > After Debian is installed the `~ $` will change to `root@localhost`.
```bash ```bash
pkg update -y && pkg install proot-distro -y && proot-distro install debian && proot-distro login debian pkg update -y && pkg install wget curl proot tar -y && wget https://raw.githubusercontent.com/AndronixApp/AndronixOrigin/master/Installer/Debian/debian.sh -O debian.sh && chmod +x debian.sh && bash debian.sh
``` ```
3. Run the following commands to setup Debian: 3. Run the following commands to setup Debian.
```bash ```bash
apt update && apt upgrade -y && apt-get install sudo vim git -y apt update
apt upgrade -y
apt-get install nano vim sudo curl wget 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. 4. Install [NVM](https://github.com/nvm-sh/nvm) 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;
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. - 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 /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. - Run `nano /etc/profile` and paste those lines at the end and make sure to replace `$HOME` with `/root`
- Now run `exit` - Now run `exit` and start Debain again.
- 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: 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) using `nvm install version_here`.
7. To install `code-server` run the following.
```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) > 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 > If it all looks good, you can install code-server by running the second command
@@ -87,11 +82,11 @@ Potential Workaround :
To create a new user follow these simple steps - To create a new user follow these simple steps -
1. Create a new user by running `useradd <username> -m`. 1. Create a new user by running `useradd username -m`.
2. Change the password by running `passwd <username>`. 2. Change the password by running `passwd username`.
3. Give your new user sudo access by running `visudo`, scroll down to `User privilege specification` and add the following line after root `username ALL=(ALL:ALL) ALL`. 3. Give your new user sudo access by runnning `visudo`, scroll down to `User privilege specification` and add the following line after root `username ALL=(ALL:ALL) ALL`.
4. Now edit the `/etc/passwd` file with your command line editor of choice and at the end of the line that specifies your user change `/bin/sh` to `/bin/bash`. 4. Now edit the `/etc/passwd` file with your commadline editor of choice and at the end of the line that specifies your user change `/bin/sh` to `/bin/bash`.
5. Now switch users by running `su - <username>` 5. Now switch users, by running `su - username`
- Remember the `-` betweeen `su` and username is required to execute `/etc/profile`,\ - Remember the `-` betweeen `su` and username is required to execute `/etc/profile`,\
since `/etc/profile` may have some necessary things to be executed you should always add a `-`. since `/etc/profile` may have some necessary things to be executed you should always add a `-`.
@@ -100,7 +95,7 @@ To create a new user follow these simple steps -
> From https://golang.org/doc/install > From https://golang.org/doc/install
1. Go to https://golang.org/dl/ and copy the download link for `linux arm` and run the following: 1. Go to https://golang.org/dl/ and copy the download link for `linux arm` and run the following.
```bash ```bash
wget download_link wget download_link
@@ -120,7 +115,7 @@ rm -rf /usr/local/go && tar -C /usr/local -xzf archive_name
> Run these commands as root > Run these commands as root
1. Run the following commands to install required packages to build python: 1. Run the following command to install required packages to build python.
```bash ```bash
sudo apt-get update sudo apt-get update
@@ -129,13 +124,13 @@ sudo apt-get install make build-essential libssl-dev zlib1g-dev \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
``` ```
2. Install [pyenv](https://github.com/pyenv/pyenv/) from [pyenv-installer](https://github.com/pyenv/pyenv-installer) by running: 2. Install [pyenv](https://github.com/pyenv/pyenv/) from [pyenv-installer](https://github.com/pyenv/pyenv-installer) by running.
```bash ```bash
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
``` ```
3. Run `nano /etc/profile` and add the following: 3. Run `nano /etc/profile` and add the following
```bash ```bash
export PYENV_ROOT="/root/.pyenv" export PYENV_ROOT="/root/.pyenv"
@@ -144,42 +139,10 @@ eval "$(pyenv init --path)"
eval "$(pyenv virtualenv-init -)" eval "$(pyenv virtualenv-init -)"
``` ```
4. Exit and start Debian again. 4. Exit start Debian again.
5. Run `pyenv versions` to list all installable versions. 5. Run `pyenv versions` to list all installable versions.
6. Run `pyenv install version` to install the desired python version. 6. Run `pyenv install version` to install the desired python version.
> The build process may take some time (an hour or 2 depending on your device). > The build process may take some time (an hour or 2 depending on your device).
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).

View File

@@ -55,7 +55,7 @@ The detection method works as follows:
- Debian, Ubuntu, Raspbian: install the deb package from GitHub. - Debian, Ubuntu, Raspbian: install the deb package from GitHub.
- Fedora, CentOS, RHEL, openSUSE: install the rpm package from GitHub. - Fedora, CentOS, RHEL, openSUSE: install the rpm package from GitHub.
- Arch Linux: install from the AUR (which pulls releases from GitHub). - Arch Linux: install from the AUR (which pulls releases from GitHub).
- FreeBSD, Alpine: install from npm. - FreeBSD, Alpine: install from yarn/npm.
- macOS: install using Homebrew if installed otherwise install from GitHub. - macOS: install using Homebrew if installed otherwise install from GitHub.
- All others: install the release from GitHub. - All others: install the release from GitHub.
@@ -419,9 +419,19 @@ install_npm() {
echoh "Installing latest from npm." echoh "Installing latest from npm."
echoh echoh
YARN_PATH="${YARN_PATH-yarn}"
NPM_PATH="${YARN_PATH-npm}" NPM_PATH="${YARN_PATH-npm}"
if command_exists "$YARN_PATH"; then
if command_exists "$NPM_PATH"; then sh_c="sh_c"
if [ ! "${DRY_RUN-}" ] && [ ! -w "$($YARN_PATH global bin)" ]; then
sh_c="sudo_sh_c"
fi
echoh "Installing with yarn."
echoh
"$sh_c" "$YARN_PATH" global add code-server --unsafe-perm
NPM_BIN_DIR="\$($YARN_PATH global bin)" echo_npm_postinstall
return
elif command_exists "$NPM_PATH"; then
sh_c="sh_c" sh_c="sh_c"
if [ ! "${DRY_RUN-}" ] && [ ! -w "$(NPM_PATH config get prefix)" ]; then if [ ! "${DRY_RUN-}" ] && [ ! -w "$(NPM_PATH config get prefix)" ]; then
sh_c="sudo_sh_c" sh_c="sudo_sh_c"
@@ -432,9 +442,9 @@ install_npm() {
NPM_BIN_DIR="\$($NPM_PATH bin -g)" echo_npm_postinstall NPM_BIN_DIR="\$($NPM_PATH bin -g)" echo_npm_postinstall
return return
fi fi
echoerr "Please install npm to install code-server!" echoerr "Please install npm or yarn to install code-server!"
echoerr "You will need at least node v12 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#yarn-npm"
exit 1 exit 1
} }

Submodule lib/vscode deleted from c722ca6c7e

View File

@@ -1,7 +1,7 @@
{ {
"name": "code-server", "name": "code-server",
"license": "MIT", "license": "MIT",
"version": "4.3.0", "version": "4.0.2",
"description": "Run VS Code on a remote server.", "description": "Run VS Code on a remote server.",
"homepage": "https://github.com/coder/code-server", "homepage": "https://github.com/coder/code-server",
"bugs": { "bugs": {
@@ -17,7 +17,7 @@
"release:github-draft": "./ci/build/release-github-draft.sh", "release:github-draft": "./ci/build/release-github-draft.sh",
"release:github-assets": "./ci/build/release-github-assets.sh", "release:github-assets": "./ci/build/release-github-assets.sh",
"release:prep": "./ci/build/release-prep.sh", "release:prep": "./ci/build/release-prep.sh",
"test:e2e": "VSCODE_IPC_HOOK_CLI= ./ci/dev/test-e2e.sh", "test:e2e": "./ci/dev/test-e2e.sh",
"test:standalone-release": "./ci/build/test-standalone-release.sh", "test:standalone-release": "./ci/build/test-standalone-release.sh",
"test:unit": "./ci/dev/test-unit.sh --forceExit --detectOpenHandles", "test:unit": "./ci/dev/test-unit.sh --forceExit --detectOpenHandles",
"test:scripts": "./ci/dev/test-scripts.sh", "test:scripts": "./ci/dev/test-scripts.sh",
@@ -51,7 +51,7 @@
"@types/ws": "^8.0.0", "@types/ws": "^8.0.0",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^5.0.0",
"audit-ci": "^6.0.0", "audit-ci": "^5.0.0",
"codecov": "^3.8.3", "codecov": "^3.8.3",
"doctoc": "^2.0.0", "doctoc": "^2.0.0",
"eslint": "^7.7.0", "eslint": "^7.7.0",
@@ -59,19 +59,17 @@
"eslint-import-resolver-typescript": "^2.5.0", "eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.18.2", "eslint-plugin-import": "^2.18.2",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"json": "^11.0.0",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"prettier-plugin-sh": "^0.10.0", "prettier-plugin-sh": "^0.8.0",
"shellcheck": "^1.0.0", "shellcheck": "^1.0.0",
"stylelint": "^13.0.0", "stylelint": "^13.0.0",
"stylelint-config-recommended": "^5.0.0", "stylelint-config-recommended": "^5.0.0",
"synp": "^1.9.10",
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "^4.4.0-dev.20210528" "typescript": "^4.4.0-dev.20210528"
}, },
"resolutions": { "resolutions": {
"ansi-regex": "^5.0.1", "ansi-regex": "^5.0.1",
"normalize-package-data": "^4.0.0", "normalize-package-data": "^3.0.0",
"doctoc/underscore": "^1.13.1", "doctoc/underscore": "^1.13.1",
"doctoc/**/trim": "^1.0.0", "doctoc/**/trim": "^1.0.0",
"postcss": "^8.2.1", "postcss": "^8.2.1",
@@ -80,19 +78,18 @@
"vfile-message": "^2.0.2", "vfile-message": "^2.0.2",
"tar": "^6.1.9", "tar": "^6.1.9",
"path-parse": "^1.0.7", "path-parse": "^1.0.7",
"vm2": "^3.9.6", "vm2": "^3.9.4",
"follow-redirects": "^1.14.8", "follow-redirects": "^1.14.7",
"node-fetch": "^2.6.7", "node-fetch": "^2.6.7",
"nanoid": "^3.1.31", "nanoid": "^3.1.31"
"minimist": "npm:minimist-lite@2.2.1"
}, },
"dependencies": { "dependencies": {
"@coder/logger": "1.1.16", "@coder/logger": "1.1.16",
"argon2": "^0.28.0", "@node-rs/argon2": "^1.0.5",
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-parser": "^1.4.5", "cookie-parser": "^1.4.5",
"env-paths": "^2.2.0", "env-paths": "^2.2.0",
"express": "5.0.0-alpha.8", "express": "^5.0.0-alpha.8",
"http-proxy": "^1.18.0", "http-proxy": "^1.18.0",
"httpolyglot": "^0.1.2", "httpolyglot": "^0.1.2",
"js-yaml": "^4.0.0", "js-yaml": "^4.0.0",
@@ -130,6 +127,7 @@
"testEnvironment": "node", "testEnvironment": "node",
"testPathIgnorePatterns": [ "testPathIgnorePatterns": [
"/node_modules/", "/node_modules/",
"/vendor/",
"/lib/", "/lib/",
"/out/", "/out/",
"test/e2e" "test/e2e"
@@ -160,7 +158,7 @@
"<rootDir>/release-npm-package", "<rootDir>/release-npm-package",
"<rootDir>/release-gcp", "<rootDir>/release-gcp",
"<rootDir>/release-images", "<rootDir>/release-images",
"<rootDir>/lib" "<rootDir>/vendor"
], ],
"moduleNameMapper": { "moduleNameMapper": {
"^.+\\.(css|less)$": "<rootDir>/test/utils/cssStub.ts" "^.+\\.(css|less)$": "<rootDir>/test/utils/cssStub.ts"

View File

@@ -1,326 +0,0 @@
Add base path support
Some users will host code-server behind a path-rewriting reverse proxy, for
example domain.tld/my/base/path. This patch adds support for that since Code
assumes everything is on / by default.
To test this serve code-server behind a reverse proxy with a path like /code.
Index: code-server/lib/vscode/src/vs/base/common/network.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/network.ts
+++ code-server/lib/vscode/src/vs/base/common/network.ts
@@ -151,8 +151,10 @@ class RemoteAuthoritiesImpl {
}
return URI.from({
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
- authority: `${host}:${port}`,
- path: `/vscode-remote-resource`,
+ authority: platform.isWeb ? window.location.host : `${host}:${port}`,
+ path: platform.isWeb
+ ? URI.joinPath(URI.parse(window.location.href), `/vscode-remote-resource`).path
+ : `/vscode-remote-resource`,
query
});
}
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html
@@ -11,8 +11,8 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Code">
- <link rel="apple-touch-icon" sizes="192x192" href="/_static/src/browser/media/pwa-icon-192.png" />
- <link rel="apple-touch-icon" sizes="512x512" href="/_static/src/browser/media/pwa-icon-512.png" />
+ <link rel="apple-touch-icon" sizes="192x192" href="{{BASE}}/_static/src/browser/media/pwa-icon-192.png" />
+ <link rel="apple-touch-icon" sizes="512x512" href="{{BASE}}/_static/src/browser/media/pwa-icon-512.png" />
<!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
@@ -27,23 +27,26 @@
<meta id="vscode-workbench-builtin-extensions" data-settings="{{WORKBENCH_BUILTIN_EXTENSIONS}}">
<!-- Workbench Icon/Manifest/CSS -->
- <link rel="icon" href="/_static/src/browser/media/favicon-dark-support.svg" />
- <link rel="alternate icon" href="/_static/src/browser/media/favicon.ico" />
- <link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
+ <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" />
+ <link rel="manifest" href="{{VS_BASE}}/manifest.json" crossorigin="use-credentials" />
</head>
<body aria-label="">
</body>
<!-- Startup (do not modify order of script tags!) -->
- <script src="./static/out/vs/loader.js"></script>
- <script src="./static/out/vs/webPackagePaths.js"></script>
+ <script src="{{VS_BASE}}/static/out/vs/loader.js"></script>
+ <script src="{{VS_BASE}}/static/out/vs/webPackagePaths.js"></script>
<script>
Object.keys(self.webPackagePaths).map(function (key, index) {
- self.webPackagePaths[key] = `${window.location.origin}/static/remote/web/node_modules/${key}/${self.webPackagePaths[key]}`;
+ self.webPackagePaths[key] = new URL(
+ `{{VS_BASE}}/static/remote/web/node_modules/${key}/${self.webPackagePaths[key]}`,
+ window.location,
+ ).toString();
});
require.config({
- baseUrl: `${window.location.origin}/static/out`,
+ baseUrl: new URL(`{{VS_BASE}}/static/out`, window.location).toString(),
recordStats: true,
trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', {
createScriptURL(value) {
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.html
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
@@ -11,8 +11,8 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Code">
- <link rel="apple-touch-icon" sizes="192x192" href="/_static/src/browser/media/pwa-icon-192.png" />
- <link rel="apple-touch-icon" sizes="512x512" href="/_static/src/browser/media/pwa-icon-512.png" />
+ <link rel="apple-touch-icon" sizes="192x192" href="{{BASE}}/_static/src/browser/media/pwa-icon-192.png" />
+ <link rel="apple-touch-icon" sizes="512x512" href="{{BASE}}/_static/src/browser/media/pwa-icon-512.png" />
<!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
@@ -24,10 +24,10 @@
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">
<!-- Workbench Icon/Manifest/CSS -->
- <link rel="icon" href="/_static/src/browser/media/favicon-dark-support.svg" />
- <link rel="alternate icon" href="/_static/src/browser/media/favicon.ico" />
- <link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
- <link data-name="vs/workbench/workbench.web.main" rel="stylesheet" href="./static/out/vs/workbench/workbench.web.main.css">
+ <link rel="icon" href="{{BASE}}/_static/src/browser/media/favicon-dark-support.svg" />
+ <link rel="alternate icon" href="{{BASE}}/_static/src/browser/media/favicon.ico" />
+ <link rel="manifest" href="{{VS_BASE}}/manifest.json" crossorigin="use-credentials" />
+ <link data-name="vs/workbench/workbench.web.main" rel="stylesheet" href="{{VS_BASE}}/static/out/vs/workbench/workbench.web.main.css">
</head>
@@ -35,14 +35,17 @@
</body>
<!-- Startup (do not modify order of script tags!) -->
- <script src="./static/out/vs/loader.js"></script>
- <script src="./static/out/vs/webPackagePaths.js"></script>
+ <script src="{{VS_BASE}}/static/out/vs/loader.js"></script>
+ <script src="{{VS_BASE}}/static/out/vs/webPackagePaths.js"></script>
<script>
Object.keys(self.webPackagePaths).map(function (key, index) {
- self.webPackagePaths[key] = `${window.location.origin}/static/node_modules/${key}/${self.webPackagePaths[key]}`;
+ self.webPackagePaths[key] = new URL(
+ `{{VS_BASE}}/static/node_modules/${key}/${self.webPackagePaths[key]}`,
+ window.location,
+ ).toString();
});
require.config({
- baseUrl: `${window.location.origin}/static/out`,
+ baseUrl: new URL(`{{VS_BASE}}/static/out`, window.location).toString(),
recordStats: true,
trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', {
createScriptURL(value) {
@@ -55,7 +58,7 @@
<script>
performance.mark('code/willLoadWorkbenchMain');
</script>
- <script src="./static/out/vs/workbench/workbench.web.main.nls.js"></script>
- <script src="./static/out/vs/workbench/workbench.web.main.js"></script>
- <script src="./static/out/vs/code/browser/workbench/workbench.js"></script>
+ <script src="{{VS_BASE}}/static/out/vs/workbench/workbench.web.main.nls.js"></script>
+ <script src="{{VS_BASE}}/static/out/vs/workbench/workbench.web.main.js"></script>
+ <script src="{{VS_BASE}}/static/out/vs/code/browser/workbench/workbench.js"></script>
</html>
Index: code-server/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
@@ -274,7 +274,7 @@ export class BrowserSocketFactory implem
connect(host: string, port: number, query: string, debugLabel: string, callback: IConnectCallback): void {
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
- const socket = this._webSocketFactory.create(`${webSocketSchema}://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=false`, debugLabel);
+ const socket = this._webSocketFactory.create(`${webSocketSchema}://${window.location.host}${window.location.pathname}?${query}&skipWebSocketFrames=false`, debugLabel);
const errorListener = socket.onError((err) => callback(err, undefined));
socket.onOpen(() => {
errorListener.dispose();
@@ -282,6 +282,3 @@ export class BrowserSocketFactory implem
});
}
}
-
-
-
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -252,7 +252,10 @@ export class WebClientServer {
return res.end();
}
- const remoteAuthority = req.headers.host;
+ // It is not possible to reliably detect the remote authority on the server
+ // in all cases. Set this to something invalid to make sure we catch code
+ // that is using this when it should not.
+ const remoteAuthority = 'remote';
function escapeAttribute(value: string): string {
return value.replace(/"/g, '&quot;');
@@ -272,6 +275,8 @@ export class WebClientServer {
accessToken: this._environmentService.args['github-auth'],
scopes: [['user:email'], ['repo']]
} : undefined;
+ const base = relativeRoot(getOriginalUrl(req))
+ const vscodeBase = relativePath(getOriginalUrl(req))
const data = (await util.promisify(fs.readFile)(filePath)).toString()
.replace('{{WORKBENCH_WEB_CONFIGURATION}}', escapeAttribute(JSON.stringify({
remoteAuthority,
@@ -279,6 +284,7 @@ export class WebClientServer {
developmentOptions: { enableSmokeTestDriver: this._environmentService.driverHandle === 'web' ? true : undefined },
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
productConfiguration: <Partial<IProductConfiguration>>{
+ rootEndpoint: base,
codeServerVersion: this._productService.codeServerVersion,
embedderIdentifier: 'server-distro',
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
@@ -291,7 +297,9 @@ export class WebClientServer {
} : undefined
}
})))
- .replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : '');
+ .replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : '')
+ .replace(/{{BASE}}/g, base)
+ .replace(/{{VS_BASE}}/g, vscodeBase);
const cspDirectives = [
'default-src \'self\';',
@@ -370,3 +378,70 @@ export class WebClientServer {
return res.end(data);
}
}
+
+/**
+ * Remove extra slashes in a URL.
+ *
+ * This is meant to fill the job of `path.join` so you can concatenate paths and
+ * then normalize out any extra slashes.
+ *
+ * If you are using `path.join` you do not need this but note that `path` is for
+ * file system paths, not URLs.
+ */
+export const normalizeUrlPath = (url: string, keepTrailing = false): string => {
+ return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "")
+}
+
+/**
+ * Get the relative path that will get us to the root of the page. For each
+ * slash we need to go up a directory. Will not have a trailing slash.
+ *
+ * For example:
+ *
+ * / => .
+ * /foo => .
+ * /foo/ => ./..
+ * /foo/bar => ./..
+ * /foo/bar/ => ./../..
+ *
+ * All paths must be relative in order to work behind a reverse proxy since we
+ * we do not know the base path. Anything that needs to be absolute (for
+ * example cookies) must get the base path from the frontend.
+ *
+ * All relative paths must be prefixed with the relative root to ensure they
+ * work no matter the depth at which they happen to appear.
+ *
+ * For Express `req.originalUrl` should be used as they remove the base from the
+ * standard `url` property making it impossible to get the true depth.
+ */
+export const relativeRoot = (originalUrl: string): string => {
+ const depth = (originalUrl.split("?", 1)[0].match(/\//g) || []).length
+ return normalizeUrlPath("./" + (depth > 1 ? "../".repeat(depth - 1) : ""))
+}
+
+/**
+ * Get the relative path to the current resource.
+ *
+ * For example:
+ *
+ * / => .
+ * /foo => ./foo
+ * /foo/ => .
+ * /foo/bar => ./bar
+ * /foo/bar/ => .
+ */
+export const relativePath = (originalUrl: string): string => {
+ const parts = originalUrl.split("?", 1)[0].split("/")
+ return normalizeUrlPath("./" + parts[parts.length - 1])
+}
+
+/**
+ * code-server serves Code using Express. Express removes the base from the url
+ * and puts the original in `originalUrl` so we must use this to get the correct
+ * depth. Code is not aware it is behind Express so the types do not match. We
+ * may want to continue moving code into Code and eventually remove the Express
+ * wrapper or move the web server back into code-server.
+ */
+export const getOriginalUrl = (req: http.IncomingMessage): string => {
+ return (req as any).originalUrl || req.url
+}
Index: code-server/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
@@ -32,6 +32,7 @@ export type ExtensionVirtualWorkspaceSup
export interface IProductConfiguration {
readonly codeServerVersion?: string
+ readonly rootEndpoint?: string
readonly version: string;
readonly date?: string;
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.ts
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
@@ -482,6 +482,7 @@ function doCreateUri(path: string, query
});
}
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
return URI.parse(window.location.href).with({ path, query });
}
@@ -493,7 +494,7 @@ function doCreateUri(path: string, query
if (!configElement || !configElementAttribute) {
throw new Error('Missing web configuration element');
}
- const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents } = JSON.parse(configElementAttribute);
+ const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = { ...JSON.parse(configElementAttribute), remoteAuthority: location.host }
// Create workbench
create(document.body, {
Index: code-server/lib/vscode/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts
+++ code-server/lib/vscode/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts
@@ -16,7 +16,6 @@ import { getServiceMachineId } from 'vs/
import { IStorageService } from 'vs/platform/storage/common/storage';
import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
-import { RemoteAuthorities } from 'vs/base/common/network';
export const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource';
@@ -72,7 +71,7 @@ export abstract class AbstractExtensionR
public getExtensionGalleryResourceURL(galleryExtension: { publisher: string; name: string; version: string }, path?: string): URI | undefined {
if (this._extensionGalleryResourceUrlTemplate) {
const uri = URI.parse(format2(this._extensionGalleryResourceUrlTemplate, { publisher: galleryExtension.publisher, name: galleryExtension.name, version: galleryExtension.version, path: 'extension' }));
- 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 undefined;
}

View File

@@ -1,19 +0,0 @@
Add connection type to web sockets
This allows the backend to distinguish them. In our case we use them to count a
single "open" of Code so we need to be able to distinguish between web sockets
from two instances and two web sockets used in a single instance.
Index: code-server/lib/vscode/src/vs/platform/remote/common/remoteAgentConnection.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/remote/common/remoteAgentConnection.ts
+++ code-server/lib/vscode/src/vs/platform/remote/common/remoteAgentConnection.ts
@@ -231,7 +231,7 @@ async function connectToRemoteExtensionH
let socket: ISocket;
try {
- socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken);
+ socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `type=${connectionTypeToString(connectionType)}&reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken);
} catch (error) {
options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`);
options.logService.error(error);

View File

@@ -1,183 +0,0 @@
Add option to disable file downloads via CLI
This patch adds support for a new CLI flag called `--disable-file-downloads`
which allows a user to remove the "Download..." option that shows up when you
right-click files in Code. The default value for this is `false`.
To test this, start code-server with `--disable-file-downloads`, open editor,
right-click on a file (not a folder) and you should **not** see the
"Download..." option.
Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
@@ -210,6 +210,11 @@ export interface IWorkbenchConstructionO
*/
readonly userDataPath?: string
+ /**
+ * Whether the "Download..." option is enabled for files.
+ */
+ readonly isEnabledFileDownloads?: boolean
+
//#endregion
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/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -30,6 +30,11 @@ export interface IBrowserWorkbenchEnviro
* Options used to configure the workbench.
*/
readonly options?: IWorkbenchConstructionOptions;
+
+ /**
+ * Enable downloading files via menu actions.
+ */
+ readonly isEnabledFileDownloads?: boolean;
}
export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
@@ -61,6 +66,13 @@ export class BrowserWorkbenchEnvironment
return this.options.userDataPath;
}
+ get isEnabledFileDownloads(): boolean {
+ if (typeof this.options.isEnabledFileDownloads === "undefined") {
+ throw new Error('isEnabledFileDownloads was not provided to the browser');
+ }
+ return this.options.isEnabledFileDownloads;
+ }
+
@memoize
get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); }
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -15,6 +15,7 @@ export const serverOptions: OptionDescri
'disable-update-check': { type: 'boolean' },
'auth': { type: 'string' },
'locale': { type: 'string' },
+ 'disable-file-downloads': { type: 'boolean' },
/* ----- server setup ----- */
@@ -92,6 +93,7 @@ export interface ServerParsedArgs {
'disable-update-check'?: boolean;
'auth'?: string
'locale'?: string
+ 'disable-file-downloads'?: boolean;
/* ----- server setup ----- */
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -290,6 +290,7 @@ export class WebClientServer {
logLevel: this._logService.getLevel(),
},
userDataPath: this._environmentService.userDataPath,
+ isEnabledFileDownloads: !this._environmentService.args['disable-file-downloads'],
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
productConfiguration: <Partial<IProductConfiguration>>{
rootEndpoint: base,
Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/contextkeys.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
@@ -7,12 +7,11 @@ import { Event } from 'vs/base/common/ev
import { Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext } from 'vs/platform/contextkey/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 } 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, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys';
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
@@ -24,6 +23,7 @@ import { IEditorResolverService } from '
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { Schemas } from 'vs/base/common/network';
import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess';
+import { IBrowserWorkbenchEnvironmentService } from '../services/environment/browser/environmentService';
export class WorkbenchContextKeysHandler extends Disposable {
private inputFocusedContext: IContextKey<boolean>;
@@ -75,7 +75,7 @@ export class WorkbenchContextKeysHandler
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IConfigurationService private readonly configurationService: IConfigurationService,
- @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
+ @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService,
@IEditorService private readonly editorService: IEditorService,
@IEditorResolverService private readonly editorResolverService: IEditorResolverService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@@ -194,6 +194,9 @@ export class WorkbenchContextKeysHandler
this.auxiliaryBarVisibleContext = AuxiliaryBarVisibleContext.bindTo(this.contextKeyService);
this.auxiliaryBarVisibleContext.set(this.layoutService.isVisible(Parts.AUXILIARYBAR_PART));
+ // code-server
+ IsEnabledFileDownloads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileDownloads ?? true)
+
this.registerListeners();
}
Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts
@@ -21,7 +21,7 @@ import { CLOSE_SAVED_EDITORS_COMMAND_ID,
import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService';
import { Schemas } from 'vs/base/common/network';
-import { DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey } from 'vs/workbench/common/contextkeys';
+import { DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
@@ -475,13 +475,16 @@ MenuRegistry.appendMenuItem(MenuId.Explo
id: DOWNLOAD_COMMAND_ID,
title: DOWNLOAD_LABEL
},
- when: ContextKeyExpr.or(
- // native: for any remote resource
- ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.notEqualsTo(Schemas.file)),
- // web: for any files
- ContextKeyExpr.and(IsWebContext, ExplorerFolderContext.toNegated(), ExplorerRootContext.toNegated()),
- // web: for any folders if file system API support is provided
- ContextKeyExpr.and(IsWebContext, HasWebFileSystemAccess)
+ when: ContextKeyExpr.and(
+ IsEnabledFileDownloads,
+ ContextKeyExpr.or(
+ // native: for any remote resource
+ ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.notEqualsTo(Schemas.file)),
+ // web: for any files
+ ContextKeyExpr.and(IsWebContext, ExplorerFolderContext.toNegated(), ExplorerRootContext.toNegated()),
+ // web: for any folders if file system API support is provided
+ ContextKeyExpr.and(IsWebContext, HasWebFileSystemAccess)
+ )
)
}));
Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/common/contextkeys.ts
+++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
@@ -30,6 +30,8 @@ export const IsFullscreenContext = new R
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);
+
//#endregion

View File

@@ -1,265 +0,0 @@
Add display language support
This likely needs tweaking if we want to upstream.
Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
@@ -188,6 +188,9 @@ export async function setupServerService
const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority));
socketServer.registerChannel('extensions', channel);
+ const localizationsChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILocalizationsService));
+ socketServer.registerChannel('localizations', localizationsChannel);
+
const encryptionChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(IEncryptionMainService));
socketServer.registerChannel('encryption', encryptionChannel);
Index: code-server/lib/vscode/src/vs/base/common/platform.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/platform.ts
+++ code-server/lib/vscode/src/vs/base/common/platform.ts
@@ -84,6 +84,17 @@ if (typeof navigator === 'object' && !is
_isWeb = true;
_locale = navigator.language;
_language = _locale;
+
+ const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
+ const rawNlsConfig = el && el.getAttribute('data-settings');
+ if (rawNlsConfig) {
+ try {
+ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
+ _locale = nlsConfig.locale;
+ _translationsConfigFile = nlsConfig._translationsConfigFile;
+ _language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT;
+ } catch (error) { /* Oh well. */ }
+ }
}
// Native environment
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.html
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
@@ -23,6 +23,9 @@
<!-- Workbench Auth Session -->
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">
+ <!-- NLS Configuration -->
+ <meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
+
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="{{BASE}}/_static/src/browser/media/favicon-dark-support.svg" />
<link rel="alternate icon" href="{{BASE}}/_static/src/browser/media/favicon.ico" />
@@ -38,6 +41,27 @@
<script src="{{VS_BASE}}/static/out/vs/loader.js"></script>
<script src="{{VS_BASE}}/static/out/vs/webPackagePaths.js"></script>
<script>
+ let nlsConfig
+ try {
+ nlsConfig = JSON.parse(document.getElementById("vscode-remote-nls-configuration").getAttribute("data-settings"))
+ if (nlsConfig._resolvedLanguagePackCoreLocation) {
+ const bundles = Object.create(null)
+ nlsConfig.loadBundle = (bundle, _language, cb) => {
+ const result = bundles[bundle]
+ if (result) {
+ return cb(undefined, result)
+ }
+ const path = nlsConfig._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json"
+ fetch(`{{VS_BASE}}/vscode-remote-resource?path=${encodeURIComponent(path)}`)
+ .then((response) => response.json())
+ .then((json) => {
+ bundles[bundle] = json
+ cb(undefined, json)
+ })
+ .catch(cb)
+ }
+ }
+ } catch (error) { /* Probably fine. */ }
Object.keys(self.webPackagePaths).map(function (key, index) {
self.webPackagePaths[key] = new URL(
`{{VS_BASE}}/static/node_modules/${key}/${self.webPackagePaths[key]}`,
@@ -52,7 +76,8 @@
return value;
}
}),
- paths: self.webPackagePaths
+ paths: self.webPackagePaths,
+ 'vs/nls': nlsConfig,
});
</script>
<script>
Index: code-server/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
@@ -105,7 +105,7 @@ export abstract class AbstractNativeEnvi
return URI.file(join(vscodePortable, 'argv.json'));
}
- return joinPath(this.userHome, this.productService.dataFolderName, 'argv.json');
+ return joinPath(this.appSettingsHome, 'argv.json');
}
@memoize
Index: code-server/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
@@ -30,6 +30,12 @@ export function getNLSConfiguration(lang
if (InternalNLSConfiguration.is(value)) {
value._languagePackSupport = true;
}
+ // If the configuration has no results keep trying since code-server
+ // doesn't restart when a language is installed so this result would
+ // persist (the plugin might not be installed yet for example).
+ if (value.locale !== 'en' && value.locale !== 'en-us' && Object.keys(value.availableLanguages).length === 0) {
+ _cache.delete(key);
+ }
return value;
});
_cache.set(key, result);
@@ -44,3 +50,43 @@ export namespace InternalNLSConfiguratio
return candidate && typeof candidate._languagePackId === 'string';
}
}
+
+/**
+ * The code below is copied from from src/main.js.
+ */
+
+export const getLocaleFromConfig = async (argvResource: string): Promise<string> => {
+ try {
+ const content = stripComments(await fs.promises.readFile(argvResource, 'utf8'));
+ return JSON.parse(content).locale;
+ } catch (error) {
+ if (error.code !== "ENOENT") {
+ console.warn(error)
+ }
+ return 'en';
+ }
+};
+
+const stripComments = (content: string): string => {
+ const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
+
+ return content.replace(regexp, (match, _m1, _m2, m3, m4) => {
+ // Only one of m1, m2, m3, m4 matches
+ if (m3) {
+ // A block comment. Replace with nothing
+ return '';
+ } else if (m4) {
+ // A line comment. If it ends in \r?\n then keep it.
+ const length_1 = m4.length;
+ if (length_1 > 2 && m4[length_1 - 1] === '\n') {
+ return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
+ }
+ else {
+ return '';
+ }
+ } else {
+ // We match a string
+ return match;
+ }
+ });
+};
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -26,6 +26,7 @@ import { URI } from 'vs/base/common/uri'
import { streamToBuffer } from 'vs/base/common/buffer';
import { IProductConfiguration } from 'vs/base/common/product';
import { isString } from 'vs/base/common/types';
+import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks';
const textMimeType = {
'.html': 'text/html',
@@ -277,6 +278,8 @@ export class WebClientServer {
} : undefined;
const base = relativeRoot(getOriginalUrl(req))
const vscodeBase = relativePath(getOriginalUrl(req))
+ const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath);
+ const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath)
const data = (await util.promisify(fs.readFile)(filePath)).toString()
.replace('{{WORKBENCH_WEB_CONFIGURATION}}', escapeAttribute(JSON.stringify({
remoteAuthority,
@@ -303,7 +306,8 @@ export class WebClientServer {
})))
.replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : '')
.replace(/{{BASE}}/g, base)
- .replace(/{{VS_BASE}}/g, vscodeBase);
+ .replace(/{{VS_BASE}}/g, vscodeBase)
+ .replace(/{{NLS_CONFIGURATION}}/g, () => escapeAttribute(JSON.stringify(nlsConfiguration)));
const cspDirectives = [
'default-src \'self\';',
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -14,6 +14,7 @@ export const serverOptions: OptionDescri
/* ----- code-server ----- */
'disable-update-check': { type: 'boolean' },
'auth': { type: 'string' },
+ 'locale': { type: 'string' },
/* ----- server setup ----- */
@@ -90,6 +91,7 @@ export interface ServerParsedArgs {
/* ----- code-server ----- */
'disable-update-check'?: boolean;
'auth'?: string
+ 'locale'?: string
/* ----- server setup ----- */
Index: code-server/lib/vscode/src/vs/workbench/services/localizations/browser/localizationsService.ts
===================================================================
--- /dev/null
+++ code-server/lib/vscode/src/vs/workbench/services/localizations/browser/localizationsService.ts
@@ -0,0 +1,28 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Coder Technologies. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
+import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
+import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
+import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
+
+/**
+ * Add localizations service for the browser.
+ * @author coder
+ */
+
+// @ts-ignore: interface is implemented via proxy
+export class LocalizationsService implements ILocalizationsService {
+
+ declare readonly _serviceBrand: undefined;
+
+ constructor(
+ @IRemoteAgentService remoteAgentService: IRemoteAgentService,
+ ) {
+ return ProxyChannel.toService<ILocalizationsService>(remoteAgentService.getConnection()!.getChannel('localizations'));
+ }
+}
+
+registerSingleton(ILocalizationsService, LocalizationsService, true);
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/lib/vscode/src/vs/workbench/workbench.web.main.ts
@@ -111,6 +111,10 @@ registerSingleton(IDiagnosticsService, N
//#region --- workbench contributions
+// Localizations
+import 'vs/workbench/contrib/localizations/browser/localizations.contribution';
+import 'vs/workbench/services/localizations/browser/localizationsService';
+
// Output
import 'vs/workbench/contrib/output/common/outputChannelModelService';

View File

@@ -1,118 +0,0 @@
Use our own GitHub auth relay server
Microsoft's does not work with self-hosted instances so we run our own.
Also add an extra set of scopes so that tokens provided via --github-auth will
work for the PR extension.
Index: code-server/lib/vscode/extensions/github-authentication/src/githubServer.ts
===================================================================
--- code-server.orig/lib/vscode/extensions/github-authentication/src/githubServer.ts
+++ code-server/lib/vscode/extensions/github-authentication/src/githubServer.ts
@@ -17,7 +17,7 @@ const localize = nls.loadMessageBundle()
const CLIENT_ID = '01ab8ac9400c4e429b23';
const NETWORK_ERROR = 'network error';
-const AUTH_RELAY_SERVER = 'vscode-auth.github.com';
+const AUTH_RELAY_SERVER = 'auth.code-server.dev';
// const AUTH_RELAY_STAGING_SERVER = 'client-auth-staging-14a768b.herokuapp.com';
class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.UriHandler {
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -274,7 +274,7 @@ export class WebClientServer {
id: generateUuid(),
providerId: 'github',
accessToken: this._environmentService.args['github-auth'],
- scopes: [['user:email'], ['repo']]
+ scopes: [['read:user', 'user:email', 'repo'], ['user:email'], ['repo']]
} : undefined;
const base = relativeRoot(getOriginalUrl(req))
const vscodeBase = relativePath(getOriginalUrl(req))
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.ts
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
@@ -17,6 +17,7 @@ import { isFolderToOpen, isWorkspaceToOp
import { create, ICredentialsProvider, IURLCallbackProvider, IWorkbenchConstructionOptions, IWorkspace, IWorkspaceProvider } from 'vs/workbench/workbench.web.main';
import { posix } from 'vs/base/common/path';
import { ltrim } from 'vs/base/common/strings';
+import { equals as arrayEquals } from 'vs/base/common/arrays';
interface ICredential {
service: string;
@@ -24,6 +25,13 @@ interface ICredential {
password: string;
}
+interface IToken {
+ accessToken: string
+ account?: { label: string }
+ id: string
+ scopes: string[]
+}
+
class LocalStorageCredentialsProvider implements ICredentialsProvider {
private static readonly CREDENTIALS_STORAGE_KEY = 'credentials.provider';
@@ -51,6 +59,58 @@ class LocalStorageCredentialsProvider im
scopes,
accessToken: authSessionInfo!.accessToken
}))));
+
+ // Add tokens for extensions to use. This works for extensions like the
+ // pull requests one or GitLens.
+ const extensionId = `vscode.${authSessionInfo.providerId}-authentication`;
+ const service = `${product.urlProtocol}${extensionId}`;
+ const account = `${authSessionInfo.providerId}.auth`;
+ // 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[] = authSessionInfo.scopes.map((scopes) => ({
+ id: authSessionInfo!.id,
+ scopes: scopes.sort(), // Sort for comparing later.
+ accessToken: authSessionInfo!.accessToken,
+ }));
+ this.getPassword(service, account).then((raw) => {
+ let existing: {
+ content: IToken[]
+ } | undefined;
+
+ if (raw) {
+ try {
+ const json = JSON.parse(raw);
+ json.content = JSON.parse(json.content);
+ existing = json;
+ } catch (error) {
+ console.log(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

@@ -1,57 +0,0 @@
Add a notification when accessing code-server in an insecure context
This is done because otherwise when things like the clipboard do not work users
may think code-server is broken. Ideally there would be a notification at the
point where these things are used instead of this though.
To test access over something like an HTTP domain or an IP address (not
localhost).
Index: code-server/lib/vscode/src/vs/workbench/browser/client.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/client.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/client.ts
@@ -1,7 +1,10 @@
import { Disposable } from 'vs/base/common/lifecycle';
+import { localize } from 'vs/nls';
+import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
export class CodeServerClient extends Disposable {
constructor (
+ @INotificationService private notificationService: INotificationService,
) {
super();
}
@@ -42,5 +45,32 @@ export class CodeServerClient extends Di
}
});
}
+
+ if (!window.isSecureContext) {
+ this.notificationService.notify({
+ severity: Severity.Warning,
+ message: localize(
+ 'insecureContext',
+ "{0} is being accessed in an insecure context. Web views, the clipboard, and other functionality may not work as expected.",
+ 'code-server',
+ ),
+ actions: {
+ primary: [
+ {
+ id: 'understand',
+ label: localize('confirmInsecure', "I understand"),
+ tooltip: '',
+ class: undefined,
+ enabled: true,
+ checked: true,
+ dispose: () => undefined,
+ run: () => {
+ return Promise.resolve();
+ },
+ },
+ ],
+ },
+ });
+ }
}
}

View File

@@ -1,271 +0,0 @@
Prepare Code for integration with code-server
1. We already have the arguments so allow passing them in. There is also a
slight change in a few directories to preserve the directory structure we
have been using and to not override passed-in arguments.
2. Modify the terminal environment to filter out code-server environment variables.
3. Add the code-server version to the help dialog.
4. Add ready events for use in an iframe.
5. Add our icons.
Index: code-server/lib/vscode/src/vs/server/node/server.main.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/server.main.ts
+++ code-server/lib/vscode/src/vs/server/node/server.main.ts
@@ -12,7 +12,7 @@ import { createServer as doCreateServer,
import { parseArgs, ErrorReporter } from 'vs/platform/environment/node/argv';
import { join, dirname } from 'vs/base/common/path';
import { performance } from 'perf_hooks';
-import { serverOptions } from 'vs/server/node/serverEnvironmentService';
+import { serverOptions, ServerParsedArgs } from 'vs/server/node/serverEnvironmentService';
import product from 'vs/platform/product/common/product';
import * as perf from 'vs/base/common/performance';
@@ -33,37 +33,42 @@ const errorReporter: ErrorReporter = {
}
};
-const args = parseArgs(process.argv.slice(2), serverOptions, errorReporter);
+function parse(): ServerParsedArgs {
+ return parseArgs(process.argv.slice(2), serverOptions, errorReporter);
+}
-const REMOTE_DATA_FOLDER = args['server-data-dir'] || process.env['VSCODE_AGENT_FOLDER'] || join(os.homedir(), product.serverDataFolderName || '.vscode-remote');
-const USER_DATA_PATH = join(REMOTE_DATA_FOLDER, 'data');
-const APP_SETTINGS_HOME = join(USER_DATA_PATH, 'User');
-const GLOBAL_STORAGE_HOME = join(APP_SETTINGS_HOME, 'globalStorage');
-const MACHINE_SETTINGS_HOME = join(USER_DATA_PATH, 'Machine');
-args['user-data-dir'] = USER_DATA_PATH;
-const APP_ROOT = dirname(FileAccess.asFileUri('', require).fsPath);
-const BUILTIN_EXTENSIONS_FOLDER_PATH = join(APP_ROOT, 'extensions');
-args['builtin-extensions-dir'] = BUILTIN_EXTENSIONS_FOLDER_PATH;
-args['extensions-dir'] = args['extensions-dir'] || join(REMOTE_DATA_FOLDER, 'extensions');
-
-[REMOTE_DATA_FOLDER, args['extensions-dir'], USER_DATA_PATH, APP_SETTINGS_HOME, MACHINE_SETTINGS_HOME, GLOBAL_STORAGE_HOME].forEach(f => {
- try {
- if (!fs.existsSync(f)) {
- fs.mkdirSync(f, { mode: 0o700 });
- }
- } catch (err) { console.error(err); }
-});
+function createDirs(args: ServerParsedArgs): string {
+ const REMOTE_DATA_FOLDER = args['server-data-dir'] || args['user-data-dir'] || process.env['VSCODE_AGENT_FOLDER'] || join(os.homedir(), product.serverDataFolderName || '.vscode-remote');
+ const USER_DATA_PATH = args['user-data-dir'] || join(REMOTE_DATA_FOLDER, 'data');
+ const APP_SETTINGS_HOME = join(USER_DATA_PATH, 'User');
+ const GLOBAL_STORAGE_HOME = join(APP_SETTINGS_HOME, 'globalStorage');
+ const MACHINE_SETTINGS_HOME = join(USER_DATA_PATH, 'Machine');
+ args['user-data-dir'] = USER_DATA_PATH;
+ const APP_ROOT = dirname(FileAccess.asFileUri('', require).fsPath);
+ const BUILTIN_EXTENSIONS_FOLDER_PATH = args['builtin-extensions-dir'] || join(APP_ROOT, 'extensions');
+ args['builtin-extensions-dir'] = BUILTIN_EXTENSIONS_FOLDER_PATH;
+ args['extensions-dir'] = args['extensions-dir'] || join(REMOTE_DATA_FOLDER, 'extensions');
+
+ [REMOTE_DATA_FOLDER, args['extensions-dir'], USER_DATA_PATH, APP_SETTINGS_HOME, MACHINE_SETTINGS_HOME, GLOBAL_STORAGE_HOME].forEach(f => {
+ try {
+ if (!fs.existsSync(f)) {
+ fs.mkdirSync(f, { mode: 0o700 });
+ }
+ } catch (err) { console.error(err); }
+ });
+ return REMOTE_DATA_FOLDER
+}
/**
* invoked by server-main.js
*/
-export function spawnCli() {
- runCli(args, REMOTE_DATA_FOLDER, serverOptions);
+export function spawnCli(args = parse()): Promise<void> {
+ return runCli(args, createDirs(args), serverOptions);
}
/**
* invoked by server-main.js
*/
-export function createServer(address: string | net.AddressInfo | null): Promise<IServerAPI> {
- return doCreateServer(address, args, REMOTE_DATA_FOLDER);
+export function createServer(address: string | net.AddressInfo | null, args = parse()): Promise<IServerAPI> {
+ return doCreateServer(address, args, createDirs(args));
}
Index: code-server/lib/vscode/src/vs/base/common/processes.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/processes.ts
+++ code-server/lib/vscode/src/vs/base/common/processes.ts
@@ -111,6 +111,8 @@ export function sanitizeProcessEnvironme
/^VSCODE_(?!SHELL_LOGIN).+$/,
/^SNAP(|_.*)$/,
/^GDK_PIXBUF_.+$/,
+ /^CODE_SERVER_.+$/,
+ /^CS_.+$/,
];
const envKeys = Object.keys(env);
envKeys
Index: code-server/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
@@ -143,12 +143,15 @@ export class BrowserDialogHandler implem
async about(): Promise<void> {
const detailString = (useAgo: boolean): string => {
- return localize('aboutDetail',
- "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
+ return localize('aboutCodeServerDetail',
+ "code-server: {0}",
+ this.productService.codeServerVersion ? `v${this.productService.codeServerVersion}` : 'Unknown'
+ ) + '\n' + localize('aboutDetail',
+ "Code: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
this.productService.version || 'Unknown',
this.productService.commit || 'Unknown',
this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown',
- navigator.userAgent
+ navigator.userAgent,
);
};
Index: code-server/lib/vscode/src/vs/workbench/browser/client.ts
===================================================================
--- /dev/null
+++ code-server/lib/vscode/src/vs/workbench/browser/client.ts
@@ -0,0 +1,46 @@
+import { Disposable } from 'vs/base/common/lifecycle';
+
+export class CodeServerClient extends Disposable {
+ constructor (
+ ) {
+ super();
+ }
+
+ async startup(): Promise<void> {
+ // Emit ready events
+ const event = new CustomEvent('ide-ready');
+ window.dispatchEvent(event);
+
+ if (parent) {
+ // Tell the parent loading has completed.
+ parent.postMessage({ event: 'loaded' }, '*');
+
+ // Proxy or stop proxing events as requested by the parent.
+ const listeners = new Map<string, (event: Event) => void>();
+
+ window.addEventListener('message', parentEvent => {
+ const eventName = parentEvent.data.bind || parentEvent.data.unbind;
+ if (eventName) {
+ const oldListener = listeners.get(eventName);
+ if (oldListener) {
+ document.removeEventListener(eventName, oldListener);
+ }
+ }
+
+ if (parentEvent.data.bind && parentEvent.data.prop) {
+ const listener = (event: Event) => {
+ parent?.postMessage(
+ {
+ event: parentEvent.data.event,
+ [parentEvent.data.prop]: event[parentEvent.data.prop as keyof Event],
+ },
+ window.location.origin,
+ );
+ };
+ listeners.set(parentEvent.data.bind, listener);
+ document.addEventListener(parentEvent.data.bind, listener);
+ }
+ });
+ }
+ }
+}
Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.main.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
@@ -69,6 +69,7 @@ import { IndexedDB } from 'vs/base/brows
import { BrowserCredentialsService } from 'vs/workbench/services/credentials/browser/credentialsService';
import { IWorkspace } from 'vs/workbench/services/host/browser/browserHostService';
import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess';
+import { CodeServerClient } from 'vs/workbench/browser/client';
export class BrowserMain extends Disposable {
@@ -103,6 +104,9 @@ export class BrowserMain extends Disposa
// Startup
const instantiationService = workbench.startup();
+ const codeServerClient = this._register(instantiationService.createInstance(CodeServerClient));
+ await codeServerClient.startup();
+
// Window
this._register(instantiationService.createInstance(BrowserWindow));
Index: code-server/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
@@ -31,6 +31,8 @@ export type ExtensionVirtualWorkspaceSup
};
export interface IProductConfiguration {
+ readonly codeServerVersion?: string
+
readonly version: string;
readonly date?: string;
readonly quality?: string;
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html
@@ -11,7 +11,8 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Code">
- <link rel="apple-touch-icon" href="/code-192.png" />
+ <link rel="apple-touch-icon" sizes="192x192" href="/_static/src/browser/media/pwa-icon-192.png" />
+ <link rel="apple-touch-icon" sizes="512x512" href="/_static/src/browser/media/pwa-icon-512.png" />
<!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
@@ -26,7 +27,8 @@
<meta id="vscode-workbench-builtin-extensions" data-settings="{{WORKBENCH_BUILTIN_EXTENSIONS}}">
<!-- Workbench Icon/Manifest/CSS -->
- <link rel="icon" href="/favicon.ico" type="image/x-icon" />
+ <link rel="icon" href="/_static/src/browser/media/favicon-dark-support.svg" />
+ <link rel="alternate icon" href="/_static/src/browser/media/favicon.ico" />
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
</head>
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.html
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
@@ -11,7 +11,8 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Code">
- <link rel="apple-touch-icon" href="/code-192.png" />
+ <link rel="apple-touch-icon" sizes="192x192" href="/_static/src/browser/media/pwa-icon-192.png" />
+ <link rel="apple-touch-icon" sizes="512x512" href="/_static/src/browser/media/pwa-icon-512.png" />
<!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
@@ -23,7 +24,8 @@
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">
<!-- Workbench Icon/Manifest/CSS -->
- <link rel="icon" href="/favicon.ico" type="image/x-icon" />
+ <link rel="icon" href="/_static/src/browser/media/favicon-dark-support.svg" />
+ <link rel="alternate icon" href="/_static/src/browser/media/favicon.ico" />
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link data-name="vs/workbench/workbench.web.main" rel="stylesheet" href="./static/out/vs/workbench/workbench.web.main.css">
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -279,6 +279,7 @@ export class WebClientServer {
developmentOptions: { enableSmokeTestDriver: this._environmentService.driverHandle === 'web' ? true : undefined },
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
productConfiguration: <Partial<IProductConfiguration>>{
+ codeServerVersion: this._productService.codeServerVersion,
embedderIdentifier: 'server-distro',
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
...this._productService.extensionsGallery,

View File

@@ -1,8 +0,0 @@
Remove last opened functionality
This conflicts with our own handling of the last opened workspace. If we wanted
to switch to this we would need to pass through the disable-last-opened flag and
respect it here then remove our own redirction code that handles this.
Our version might be better anyway since it puts the workspace in the URL.

View File

@@ -1,66 +0,0 @@
Make storage local to the remote server
This solves two problems:
1. Extensions running in the browser (like Vim) might use these paths
directly instead of using the file service and most likely can't write
to `/User` on disk.
2. Settings will be stored in the file system instead of in browser
storage. Using browser storage makes sharing or seeding settings
between browsers difficult. We may want to revisit this once/if we get
settings sync.
Unfortunately this does not affect state which uses a separate method with
IndexedDB and does not appear nearly as easy to redirect to disk.
To test install the Vim extension and make sure something that uses file storage
works (history recall for example) and change settings from the UI and on disk
while making sure they appear on the other side.
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -289,6 +289,7 @@ export class WebClientServer {
enableSmokeTestDriver: this._environmentService.driverHandle === 'web' ? true : undefined,
logLevel: this._logService.getLevel(),
},
+ userDataPath: this._environmentService.userDataPath,
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
productConfiguration: <Partial<IProductConfiguration>>{
rootEndpoint: base,
Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
@@ -205,6 +205,11 @@ export interface IWorkbenchConstructionO
*/
readonly configurationDefaults?: Record<string, any>;
+ /**
+ * Path to the user data directory.
+ */
+ readonly userDataPath?: string
+
//#endregion
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/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -52,7 +52,14 @@ export class BrowserWorkbenchEnvironment
get logFile(): URI { return joinPath(this.logsHome, 'window.log'); }
@memoize
- get userRoamingDataHome(): URI { return URI.file('/User').with({ scheme: Schemas.userData }); }
+ get userRoamingDataHome(): URI { return joinPath(URI.file(this.userDataPath).with({ scheme: Schemas.vscodeRemote }), 'User'); }
+
+ get userDataPath(): string {
+ if (!this.options.userDataPath) {
+ throw new Error('userDataPath was not provided to the browser');
+ }
+ return this.options.userDataPath;
+ }
@memoize
get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); }

View File

@@ -1,21 +0,0 @@
Propagate the log level to the client
This can be tested by using `--log trace`. You should see plenty of debug and
trace logs in the console.
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -285,7 +285,10 @@ export class WebClientServer {
remoteAuthority,
webviewEndpoint: vscodeBase + '/static/out/vs/workbench/contrib/webview/browser/pre',
_wrapWebWorkerExtHostInIframe,
- developmentOptions: { enableSmokeTestDriver: this._environmentService.driverHandle === 'web' ? true : undefined },
+ developmentOptions: {
+ enableSmokeTestDriver: this._environmentService.driverHandle === 'web' ? true : undefined,
+ logLevel: this._logService.getLevel(),
+ },
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
productConfiguration: <Partial<IProductConfiguration>>{
rootEndpoint: base,

View File

@@ -1,107 +0,0 @@
Add a logout command and menu item
This will only show if you have authentication enabled.
This has e2e tests but are currently disabled and need to be fixed.
Index: code-server/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
@@ -34,6 +34,7 @@ export interface IProductConfiguration {
readonly codeServerVersion?: string
readonly rootEndpoint?: string
readonly updateEndpoint?: string
+ readonly logoutEndpoint?: string
readonly version: string;
readonly date?: string;
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -13,6 +13,7 @@ import { IEnvironmentService, INativeEnv
export const serverOptions: OptionDescriptions<ServerParsedArgs> = {
/* ----- code-server ----- */
'disable-update-check': { type: 'boolean' },
+ 'auth': { type: 'string' },
/* ----- server setup ----- */
@@ -88,6 +89,7 @@ export const serverOptions: OptionDescri
export interface ServerParsedArgs {
/* ----- code-server ----- */
'disable-update-check'?: boolean;
+ 'auth'?: string
/* ----- server setup ----- */
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -287,6 +287,7 @@ export class WebClientServer {
productConfiguration: <Partial<IProductConfiguration>>{
rootEndpoint: base,
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
+ logoutEndpoint: this._environmentService.args['auth'] ? base + '/logout' : undefined,
codeServerVersion: this._productService.codeServerVersion,
embedderIdentifier: 'server-distro',
extensionsGallery: {
Index: code-server/lib/vscode/src/vs/workbench/browser/client.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/client.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/client.ts
@@ -1,11 +1,15 @@
import { Disposable } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
+import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
+import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IProductService } from 'vs/platform/product/common/productService';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
export class CodeServerClient extends Disposable {
+ static LOGOUT_COMMAND_ID = 'code-server.logout';
+
constructor (
@ILogService private logService: ILogService,
@INotificationService private notificationService: INotificationService,
@@ -82,6 +86,10 @@ export class CodeServerClient extends Di
if (this.productService.updateEndpoint) {
this.checkUpdates(this.productService.updateEndpoint)
}
+
+ if (this.productService.logoutEndpoint) {
+ this.addLogoutCommand(this.productService.logoutEndpoint);
+ }
}
private checkUpdates(updateEndpoint: string) {
@@ -133,4 +141,25 @@ export class CodeServerClient extends Di
updateLoop();
}
+
+ private addLogoutCommand(logoutEndpoint: string) {
+ CommandsRegistry.registerCommand(CodeServerClient.LOGOUT_COMMAND_ID, () => {
+ const logoutUrl = new URL(logoutEndpoint, window.location.href);
+ // Cookies must be set with absolute paths and must use the same path to
+ // be unset (we set it on the root) so send the relative root and the
+ // current href so the backend can derive the absolute path to the root.
+ logoutUrl.searchParams.set('base', this.productService.rootEndpoint || ".");
+ logoutUrl.searchParams.set('href', window.location.href);
+ window.location.assign(logoutUrl);
+ });
+
+ for (const menuId of [MenuId.CommandPalette, MenuId.MenubarHomeMenu]) {
+ MenuRegistry.appendMenuItem(menuId, {
+ command: {
+ id: CodeServerClient.LOGOUT_COMMAND_ID,
+ title: localize('logout', "Sign out of {0}", 'code-server'),
+ },
+ });
+ }
+ }
}

View File

@@ -1,53 +0,0 @@
Add Open VSX default and an env var for marketplace, fix old marketplace
Our old marketplace only supports `serviceUrl` but this causes the marketplace
to be disabled entirely so this moves the template var check to fix that.
This can be tested by setting EXTENSIONS_GALLERY set to:
'{"serviceUrl": "https://extensions.coder.com/api"}'
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/lib/vscode/src/vs/platform/product/common/product.ts
@@ -45,7 +45,14 @@ else if (typeof require?.__$__nodeRequir
}
Object.assign(product, {
- version: pkg.version
+ version: pkg.version,
+ extensionsGallery: env.EXTENSIONS_GALLERY ? JSON.parse(env.EXTENSIONS_GALLERY) : (product.extensionsGallery || {
+ serviceUrl: "https://open-vsx.org/vscode/gallery",
+ itemUrl: "https://open-vsx.org/vscode/item",
+ resourceUrlTemplate: "https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}",
+ controlUrl: "",
+ recommendationsUrl: "",
+ }),
});
}
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -287,14 +287,14 @@ export class WebClientServer {
rootEndpoint: base,
codeServerVersion: this._productService.codeServerVersion,
embedderIdentifier: 'server-distro',
- extensionsGallery: this._webExtensionResourceUrlTemplate ? {
+ extensionsGallery: {
...this._productService.extensionsGallery,
- 'resourceUrlTemplate': this._webExtensionResourceUrlTemplate.with({
+ 'resourceUrlTemplate': this._webExtensionResourceUrlTemplate ? this._webExtensionResourceUrlTemplate.with({
scheme: 'http',
authority: remoteAuthority,
path: `web-extension-resource/${this._webExtensionResourceUrlTemplate.authority}${this._webExtensionResourceUrlTemplate.path}`
- }).toString(true)
- } : undefined
+ }).toString(true) : undefined
+ },
}
})))
.replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : '')

View File

@@ -1,107 +0,0 @@
Patch the Node version to use the current version of Node
Previously it would use the yarnrc which results in builds that cannot run with
the version of Node they were built with because the native modules are
targeting the wrong version.
One way test this is to build in a fresh Docker container, run the build, then
try opening the built-in terminal.
Index: code-server/lib/vscode/build/gulpfile.reh.js
===================================================================
--- code-server.orig/lib/vscode/build/gulpfile.reh.js
+++ code-server/lib/vscode/build/gulpfile.reh.js
@@ -124,9 +124,7 @@ const serverWithWebEntryPoints = [
];
function getNodeVersion() {
- const yarnrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.yarnrc'), 'utf8');
- const target = /^target "(.*)"$/m.exec(yarnrc)[1];
- return target;
+ return process.versions.node;
}
const nodeVersion = getNodeVersion();
Index: code-server/lib/vscode/build/lib/node.js
===================================================================
--- code-server.orig/lib/vscode/build/lib/node.js
+++ code-server/lib/vscode/build/lib/node.js
@@ -7,9 +7,7 @@ Object.defineProperty(exports, "__esModu
const path = require("path");
const fs = require("fs");
const root = path.dirname(path.dirname(__dirname));
-const yarnrcPath = path.join(root, 'remote', '.yarnrc');
-const yarnrc = fs.readFileSync(yarnrcPath, 'utf8');
-const version = /^target\s+"([^"]+)"$/m.exec(yarnrc)[1];
+const version = process.versions.node;
const platform = process.platform;
const arch = platform === 'darwin' ? 'x64' : process.arch;
const node = platform === 'win32' ? 'node.exe' : 'node';
Index: code-server/lib/vscode/build/lib/node.ts
===================================================================
--- code-server.orig/lib/vscode/build/lib/node.ts
+++ code-server/lib/vscode/build/lib/node.ts
@@ -7,9 +7,7 @@ import * as path from 'path';
import * as fs from 'fs';
const root = path.dirname(path.dirname(__dirname));
-const yarnrcPath = path.join(root, 'remote', '.yarnrc');
-const yarnrc = fs.readFileSync(yarnrcPath, 'utf8');
-const version = /^target\s+"([^"]+)"$/m.exec(yarnrc)![1];
+const version = process.versions.node;
const platform = process.platform;
const arch = platform === 'darwin' ? 'x64' : process.arch;
Index: code-server/lib/vscode/build/lib/util.js
===================================================================
--- code-server.orig/lib/vscode/build/lib/util.js
+++ code-server/lib/vscode/build/lib/util.js
@@ -298,9 +298,7 @@ function streamToPromise(stream) {
}
exports.streamToPromise = streamToPromise;
function getElectronVersion() {
- const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8');
- const target = /^target "(.*)"$/m.exec(yarnrc)[1];
- return target;
+ return process.versions.node;
}
exports.getElectronVersion = getElectronVersion;
function acquireWebNodePaths() {
Index: code-server/lib/vscode/build/lib/util.ts
===================================================================
--- code-server.orig/lib/vscode/build/lib/util.ts
+++ code-server/lib/vscode/build/lib/util.ts
@@ -371,9 +371,7 @@ export function streamToPromise(stream:
}
export function getElectronVersion(): string {
- const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8');
- const target = /^target "(.*)"$/m.exec(yarnrc)![1];
- return target;
+ return process.versions.node;
}
export function acquireWebNodePaths() {
@@ -455,4 +453,3 @@ export function buildWebNodePaths(outDir
result.taskName = 'build-web-node-paths';
return result;
}
-
Index: code-server/lib/vscode/remote/.yarnrc
===================================================================
--- code-server.orig/lib/vscode/remote/.yarnrc
+++ /dev/null
@@ -1,4 +0,0 @@
-disturl "http://nodejs.org/dist"
-target "14.16.0"
-runtime "node"
-build_from_source "true"
Index: code-server/lib/vscode/.yarnrc
===================================================================
--- code-server.orig/lib/vscode/.yarnrc
+++ /dev/null
@@ -1,4 +0,0 @@
-disturl "https://electronjs.org/headers"
-target "13.5.2"
-runtime "electron"
-build_from_source "true"

View File

@@ -1,26 +0,0 @@
Replace rimraf with fs.rmSync in postinstall
The postinstall gets ran when you install with npm but rimraf is a development
dependency so it will not exist.
Index: code-server/lib/vscode/extensions/postinstall.js
===================================================================
--- code-server.orig/lib/vscode/extensions/postinstall.js
+++ code-server/lib/vscode/extensions/postinstall.js
@@ -8,7 +8,6 @@
const fs = require('fs');
const path = require('path');
-const rimraf = require('rimraf');
const root = path.join(__dirname, 'node_modules', 'typescript');
@@ -21,7 +20,7 @@ function processRoot() {
if (!toKeep.has(name)) {
const filePath = path.join(root, name);
console.log(`Removed ${filePath}`);
- rimraf.sync(filePath);
+ fs.rmSync(filePath, { recursive: true });
}
}
}

View File

@@ -1,36 +0,0 @@
Unconditionally enable the proposed API
To test run an extension that uses the proposed API.
We also override isProposedApiEnabled in case an extension does not declare the
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
@@ -1163,7 +1163,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
===================================================================
--- 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
@@ -134,10 +134,7 @@ export interface IExtensionHost {
}
export function isProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): boolean {
- if (!extension.enabledApiProposals) {
- return false;
- }
- return extension.enabledApiProposals.includes(proposal);
+ return true
}
export function checkProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): void {

View File

@@ -1,104 +0,0 @@
Add VSCODE_PROXY_URI environment variable
This can be used by extensions to open a port and access it through the proxy.
It is available in the terminal as well.
This can be tested using printenv in the terminal and by using the
codeServerTest.proxyUri command through the test extension (copy it into your
extensions, use --extensions-dir, or symlink it).
This has e2e tests.
Index: code-server/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
@@ -35,6 +35,7 @@ export interface IProductConfiguration {
readonly rootEndpoint?: string
readonly updateEndpoint?: string
readonly logoutEndpoint?: string
+ readonly proxyEndpointTemplate?: string
readonly version: string;
readonly date?: string;
Index: code-server/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
@@ -7,7 +7,7 @@ import { Emitter } from 'vs/base/common/
import { Disposable } from 'vs/base/common/lifecycle';
import { RemoteAuthorities } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
-import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
+import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolvedOptions, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService {
@@ -20,7 +20,7 @@ export class RemoteAuthorityResolverServ
private readonly _connectionToken: string | undefined;
private readonly _connectionTokens: Map<string, string>;
- constructor(connectionToken: string | undefined, resourceUriProvider: ((uri: URI) => URI) | undefined) {
+ constructor(connectionToken: string | undefined, resourceUriProvider: ((uri: URI) => URI) | undefined, private readonly proxyEndpointTemplate?: string) {
super();
this._cache = new Map<string, ResolverResult>();
this._connectionToken = connectionToken;
@@ -59,12 +59,17 @@ export class RemoteAuthorityResolverServ
private _doResolveAuthority(authority: string): ResolverResult {
const connectionToken = this._connectionTokens.get(authority) || this._connectionToken;
+ let options: ResolvedOptions | undefined
+ if (this.proxyEndpointTemplate) {
+ const proxyUrl = new URL(this.proxyEndpointTemplate, window.location.href);
+ options = { extensionHostEnv: { VSCODE_PROXY_URI: decodeURIComponent(proxyUrl.toString()) }}
+ }
if (authority.indexOf(':') >= 0) {
const pieces = authority.split(':');
- return { authority: { authority, host: pieces[0], port: parseInt(pieces[1], 10), connectionToken } };
+ return { authority: { authority, host: pieces[0], port: parseInt(pieces[1], 10), connectionToken }, options };
}
const port = (/^https:/.test(window.location.href) ? 443 : 80);
- return { authority: { authority, host: authority, port: port, connectionToken } };
+ return { authority: { authority, host: authority, port: port, connectionToken }, options };
}
_clearResolvedAuthority(authority: string): void {
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -288,6 +288,7 @@ export class WebClientServer {
rootEndpoint: base,
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
logoutEndpoint: this._environmentService.args['auth'] ? base + '/logout' : undefined,
+ proxyEndpointTemplate: base + '/proxy/{{port}}',
codeServerVersion: this._productService.codeServerVersion,
embedderIdentifier: 'server-distro',
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
@@ -179,7 +179,7 @@ export class BrowserMain extends Disposa
// Remote
const connectionToken = environmentService.options.connectionToken || getCookieValue(connectionTokenCookieName);
- const remoteAuthorityResolverService = new RemoteAuthorityResolverService(connectionToken, this.configuration.resourceUriProvider);
+ const remoteAuthorityResolverService = new RemoteAuthorityResolverService(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
===================================================================
--- 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
@@ -390,7 +390,7 @@ export function createTerminalEnvironmen
// Sanitize the environment, removing any undesirable VS Code and Electron environment
// variables
- sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI');
+ sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI', 'VSCODE_PROXY_URI');
// Merge config (settings) and ShellLaunchConfig environments
mergeEnvironments(env, allowedEnvFromConfig);

View File

@@ -1,21 +0,0 @@
integration.diff
node-version.diff
base-path.diff
proposed-api.diff
marketplace.diff
webview.diff
insecure-notification.diff
update-check.diff
logout.diff
store-socket.diff
proxy-uri.diff
display-language.diff
github-auth.diff
unique-db.diff
post-install.diff
log-level.diff
local-storage.diff
service-worker.diff
connection-type.diff
sourcemaps.diff
disable-downloads.diff

View File

@@ -1,67 +0,0 @@
Add a service worker
To test try installing code-server as a PWA.
Index: code-server/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
@@ -36,6 +36,10 @@ export interface IProductConfiguration {
readonly updateEndpoint?: string
readonly logoutEndpoint?: string
readonly proxyEndpointTemplate?: string
+ readonly serviceWorker?: {
+ readonly path: string;
+ readonly scope: string;
+ }
readonly version: string;
readonly date?: string;
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -298,6 +298,10 @@ export class WebClientServer {
proxyEndpointTemplate: base + '/proxy/{{port}}',
codeServerVersion: this._productService.codeServerVersion,
embedderIdentifier: 'server-distro',
+ serviceWorker: {
+ scope: vscodeBase + '/',
+ path: base + '/_static/out/browser/serviceWorker.js',
+ },
extensionsGallery: {
...this._productService.extensionsGallery,
'resourceUrlTemplate': this._webExtensionResourceUrlTemplate ? this._webExtensionResourceUrlTemplate.with({
Index: code-server/lib/vscode/src/vs/workbench/browser/client.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/client.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/client.ts
@@ -90,6 +90,10 @@ export class CodeServerClient extends Di
if (this.productService.logoutEndpoint) {
this.addLogoutCommand(this.productService.logoutEndpoint);
}
+
+ if (this.productService.serviceWorker) {
+ await this.registerServiceWorker(this.productService.serviceWorker);
+ }
}
private checkUpdates(updateEndpoint: string) {
@@ -162,4 +166,17 @@ export class CodeServerClient extends Di
});
}
}
+
+ private async registerServiceWorker(serviceWorker: { path: string; scope: string }) {
+ if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
+ try {
+ await navigator.serviceWorker.register(serviceWorker.path, {
+ scope: serviceWorker.scope,
+ });
+ this.logService.info('[Service Worker] registered');
+ } catch (error: any) {
+ this.logService.error('[Service Worker] registration', error as Error);
+ }
+ }
+ }
}

View File

@@ -1,43 +0,0 @@
Make sourcemaps self-hosted
Normally source maps get removed as part of the build process so prevent that
from happening. Also avoid using the windows.net host since obviously we can
not host our source maps there and want them to be self-hosted even if we could.
To test try debugging/browsing the source of a build in a browser.
Index: code-server/lib/vscode/build/gulpfile.reh.js
===================================================================
--- code-server.orig/lib/vscode/build/gulpfile.reh.js
+++ code-server/lib/vscode/build/gulpfile.reh.js
@@ -197,8 +197,7 @@ function packageTask(type, platform, arc
const src = gulp.src(sourceFolderName + '/**', { base: '.' })
.pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); }))
- .pipe(util.setExecutableBit(['**/*.sh']))
- .pipe(filter(['**', '!**/*.js.map']));
+ .pipe(util.setExecutableBit(['**/*.sh']));
const workspaceExtensionPoints = ['debuggers', 'jsonValidation'];
const isUIExtension = (manifest) => {
@@ -237,9 +236,9 @@ function packageTask(type, platform, arc
.map(name => `.build/extensions/${name}/**`);
const extensions = gulp.src(extensionPaths, { base: '.build', dot: true });
- const extensionsCommonDependencies = gulp.src('.build/extensions/node_modules/**', { base: '.build', dot: true });
- const sources = es.merge(src, extensions, extensionsCommonDependencies)
+ const extensionsCommonDependencies = gulp.src('.build/extensions/node_modules/**', { base: '.build', dot: true })
.pipe(filter(['**', '!**/*.js.map'], { dot: true }));
+ const sources = es.merge(src, extensions, extensionsCommonDependencies);
let version = packageJson.version;
const quality = product.quality;
@@ -374,7 +373,7 @@ function tweakProductForServerWeb(produc
const minifyTask = task.define(`minify-vscode-${type}`, task.series(
optimizeTask,
util.rimraf(`out-vscode-${type}-min`),
- common.minifyTask(`out-vscode-${type}`, `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`)
+ common.minifyTask(`out-vscode-${type}`, '')
));
gulp.task(minifyTask);

View File

@@ -1,31 +0,0 @@
Store a static reference to the IPC socket
This lets us use it to open files inside code-server from outside of
code-server.
Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
+++ code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
@@ -2,7 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-
+import { promises as fs } from 'fs';
+import * as os from 'os'
+import * as path from 'vs/base/common/path';
import * as performance from 'vs/base/common/performance';
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
@@ -69,6 +71,10 @@ export class ExtHostExtensionService ext
if (this._initData.remote.isRemote && this._initData.remote.authority) {
const cliServer = this._instaService.createInstance(CLIServer);
process.env['VSCODE_IPC_HOOK_CLI'] = cliServer.ipcHandlePath;
+
+ fs.writeFile(path.join(os.tmpdir(), 'vscode-ipc'), cliServer.ipcHandlePath).catch((error) => {
+ this._logService.error(error);
+ });
}
// Module loading tricks

View File

@@ -1,63 +0,0 @@
Prevent state collisions
Previously if you opened different workspaces that had the same filesystem path
(for example if you have /home/coder on two different machines that are both
accessed through the same host) they would conflict with each other. This
ensures that different browser paths will be unique (for example /workspace1 and
/workspace2).
The easiest way to test is to open files in the same workspace using both / and
/vscode and make sure they are not interacting with each other.
It should also migrate old databases which can be tested by opening in an old
code-server.
This has e2e tests.
Index: code-server/lib/vscode/src/vs/platform/storage/browser/storageService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/storage/browser/storageService.ts
+++ code-server/lib/vscode/src/vs/platform/storage/browser/storageService.ts
@@ -13,6 +13,7 @@ import { InMemoryStorageDatabase, isStor
import { ILogService } from 'vs/platform/log/common/log';
import { AbstractStorageService, IS_NEW_KEY, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
+import { hash } from 'vs/base/common/hash';
export class BrowserStorageService extends AbstractStorageService {
@@ -36,7 +37,11 @@ export class BrowserStorageService exten
}
private getId(scope: StorageScope): string {
- return scope === StorageScope.GLOBAL ? 'global' : this.payload.id;
+ // Add a unique ID based on the current path for per-workspace databases.
+ // This prevents workspaces on different machines that share the same domain
+ // and file path from colliding (since it does not appear IndexedDB can be
+ // scoped to a path) as long as they are hosted on different paths.
+ return scope === StorageScope.GLOBAL ? 'global' : (this.payload.id + '-' + hash(location.pathname.toString().replace(/\/$/, "")).toString(16));
}
protected async doInitialize(): Promise<void> {
@@ -75,6 +80,21 @@ export class BrowserStorageService exten
const firstWorkspaceOpen = this.workspaceStorage.getBoolean(IS_NEW_KEY);
if (firstWorkspaceOpen === undefined) {
this.workspaceStorage.set(IS_NEW_KEY, true);
+ // Migrate the old database.
+ let db: IIndexedDBStorageDatabase | undefined
+ try {
+ db = await IndexedDBStorageDatabase.create({ id: this.payload.id }, this.logService)
+ const items = await db.getItems()
+ for (const [key, value] of items) {
+ this.workspaceStorage.set(key, value);
+ }
+ } catch (error) {
+ this.logService.error(`[IndexedDB Storage ${this.payload.id}] migrate error: ${toErrorMessage(error)}`);
+ } finally {
+ if (db) {
+ db.close()
+ }
+ }
} else if (firstWorkspaceOpen) {
this.workspaceStorage.set(IS_NEW_KEY, false);
}

View File

@@ -1,132 +0,0 @@
Add a notification that lets you know when an update is out
The easiest way to test this is probably to change the version in your
package.json and delete the last notification storage item.
Index: code-server/lib/vscode/src/vs/workbench/browser/client.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/client.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/client.ts
@@ -1,10 +1,16 @@
import { Disposable } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
+import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
+import { IProductService } from 'vs/platform/product/common/productService';
+import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
export class CodeServerClient extends Disposable {
constructor (
+ @ILogService private logService: ILogService,
@INotificationService private notificationService: INotificationService,
+ @IProductService private productService: IProductService,
+ @IStorageService private storageService: IStorageService,
) {
super();
}
@@ -72,5 +78,59 @@ export class CodeServerClient extends Di
},
});
}
+
+ if (this.productService.updateEndpoint) {
+ this.checkUpdates(this.productService.updateEndpoint)
+ }
+ }
+
+ private checkUpdates(updateEndpoint: string) {
+ const getUpdate = async (updateCheckEndpoint: string): Promise<void> => {
+ this.logService.debug('Checking for update...');
+
+ const response = await fetch(updateCheckEndpoint, {
+ headers: { Accept: 'application/json' },
+ });
+ if (!response.ok) {
+ throw new Error(response.statusText);
+ }
+ const json = await response.json();
+ if (json.error) {
+ throw new Error(json.error);
+ }
+ if (json.isLatest) {
+ return;
+ }
+
+ const lastNoti = this.storageService.getNumber('csLastUpdateNotification', StorageScope.GLOBAL);
+ if (lastNoti) {
+ // Only remind them again after 1 week.
+ const timeout = 1000 * 60 * 60 * 24 * 7;
+ const threshold = lastNoti + timeout;
+ if (Date.now() < threshold) {
+ return;
+ }
+ }
+
+ this.storageService.store('csLastUpdateNotification', Date.now(), StorageScope.GLOBAL, StorageTarget.MACHINE);
+
+ this.notificationService.notify({
+ severity: Severity.Info,
+ message: `[code-server v${json.latest}](https://github.com/cdr/code-server/releases/tag/v${json.latest}) has been released!`,
+ });
+ };
+
+ const updateLoop = (): void => {
+ getUpdate(updateEndpoint)
+ .catch(error => {
+ this.logService.debug(`failed to check for update: ${error}`);
+ })
+ .finally(() => {
+ // Check again every 6 hours.
+ setTimeout(updateLoop, 1000 * 60 * 60 * 6);
+ });
+ };
+
+ updateLoop();
}
}
Index: code-server/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
@@ -33,6 +33,7 @@ export type ExtensionVirtualWorkspaceSup
export interface IProductConfiguration {
readonly codeServerVersion?: string
readonly rootEndpoint?: string
+ readonly updateEndpoint?: string
readonly version: string;
readonly date?: string;
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -286,6 +286,7 @@ export class WebClientServer {
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
productConfiguration: <Partial<IProductConfiguration>>{
rootEndpoint: base,
+ updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
codeServerVersion: this._productService.codeServerVersion,
embedderIdentifier: 'server-distro',
extensionsGallery: {
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -11,6 +11,8 @@ import { refineServiceDecorator } from '
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
export const serverOptions: OptionDescriptions<ServerParsedArgs> = {
+ /* ----- code-server ----- */
+ 'disable-update-check': { type: 'boolean' },
/* ----- server setup ----- */
@@ -84,6 +86,8 @@ export const serverOptions: OptionDescri
};
export interface ServerParsedArgs {
+ /* ----- code-server ----- */
+ 'disable-update-check'?: boolean;
/* ----- server setup ----- */

View File

@@ -1,76 +0,0 @@
Serve webviews from the same origin
Normally webviews are served from vscode-webview.net but we would rather them be
self-hosted.
When doing this CSP will block resources (for example when viewing images) so
add 'self' to the CSP to fix that.
Additionally the service worker defaults to handling *all* requests made to the
current host but when self-hosting the webview this will end up including the
webview HTML itself which means these requests will fail since the communication
channel between the webview and the main thread has not been set up yet as the
webview itself is not ready yet (it has no HTML and therefore no script either).
Since this code exists only for the authentication case we can just skip it when
it is served from the current host as authentication is not a problem if the
request is not cross-origin.
To test, open a few types of webviews (images, markdown, extension details, etc).
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/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -176,7 +176,7 @@ export class BrowserWorkbenchEnvironment
@memoize
get webviewExternalEndpoint(): string {
- const endpoint = this.options.webviewEndpoint
+ const endpoint = (this.options.webviewEndpoint && new URL(this.options.webviewEndpoint, window.location.toString()).toString())
|| this.productService.webviewContentExternalBaseUrlTemplate
|| 'https://{{uuid}}.vscode-webview.net/{{quality}}/{{commit}}/out/vs/workbench/contrib/webview/browser/pre/';
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -280,6 +280,7 @@ export class WebClientServer {
const data = (await util.promisify(fs.readFile)(filePath)).toString()
.replace('{{WORKBENCH_WEB_CONFIGURATION}}', escapeAttribute(JSON.stringify({
remoteAuthority,
+ webviewEndpoint: vscodeBase + '/static/out/vs/workbench/contrib/webview/browser/pre',
_wrapWebWorkerExtHostInIframe,
developmentOptions: { enableSmokeTestDriver: this._environmentService.driverHandle === 'web' ? true : undefined },
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
Index: code-server/lib/vscode/src/vs/workbench/common/webview.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/common/webview.ts
+++ code-server/lib/vscode/src/vs/workbench/common/webview.ts
@@ -24,7 +24,7 @@ export const webviewResourceBaseHost = '
export const webviewRootResourceAuthority = `vscode-resource.${webviewResourceBaseHost}`;
-export const webviewGenericCspSource = `https://*.${webviewResourceBaseHost}`;
+export const webviewGenericCspSource = `'self' https://*.${webviewResourceBaseHost}`;
/**
* Construct a uri that can load resources inside a webview
Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/service-worker.js
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/service-worker.js
+++ code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/service-worker.js
@@ -188,9 +188,11 @@ sw.addEventListener('fetch', (event) =>
}
}
- // If we're making a request against the remote authority, we want to go
- // back through VS Code itself so that we are authenticated properly
- if (requestUrl.host === remoteAuthority) {
+ // If we're making a request against the remote authority, we want to go back
+ // through VS Code itself so that we are authenticated properly. If the
+ // service worker is hosted on the same origin we will have cookies and
+ // authentication will not be an issue.
+ if (requestUrl.origin !== sw.origin && requestUrl.host === remoteAuthority) {
switch (event.request.method) {
case 'GET':
case 'HEAD':

View File

@@ -10,6 +10,5 @@
], ],
"vulnerabilityAlerts": { "vulnerabilityAlerts": {
"enabled": "true" "enabled": "true"
}, }
"ignoreDeps": ["express"]
} }

View File

@@ -1,14 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
self.addEventListener("install", () => {
console.debug("[Service Worker] installed")
})
self.addEventListener("activate", (event: any) => {
event.waitUntil((self as any).clients.claim())
console.debug("[Service Worker] activated")
})
self.addEventListener("fetch", () => {
// Without this event handler we won't be recognized as a PWA.
})

View File

@@ -11,7 +11,7 @@ import { disposer } from "./http"
import { isNodeJSErrnoException } from "./util" import { isNodeJSErrnoException } from "./util"
import { handleUpgrade } from "./wsRouter" import { handleUpgrade } from "./wsRouter"
type ListenOptions = Pick<DefaultedArgs, "socket-mode" | "socket" | "port" | "host"> type ListenOptions = Pick<DefaultedArgs, "socket" | "port" | "host">
export interface App extends Disposable { export interface App extends Disposable {
/** Handles regular HTTP requests. */ /** Handles regular HTTP requests. */
@@ -22,35 +22,31 @@ export interface App extends Disposable {
server: http.Server server: http.Server
} }
export const listen = async (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => { const listen = (server: http.Server, { host, port, socket }: ListenOptions) => {
if (socket) { return new Promise<void>(async (resolve, reject) => {
try {
await fs.unlink(socket)
} catch (error: any) {
handleArgsSocketCatchError(error)
}
}
await new Promise<void>(async (resolve, reject) => {
server.on("error", reject) server.on("error", reject)
const onListen = () => { const onListen = () => {
// Promise resolved earlier so this is an unrelated error. // Promise resolved earlier so this is an unrelated error.
server.off("error", reject) server.off("error", reject)
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 (socket) { if (socket) {
try {
await fs.unlink(socket)
} catch (error: any) {
handleArgsSocketCatchError(error)
}
server.listen(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(port, host.replace(/^\[|\]$/g, ""), onListen) server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
} }
}) })
// NOTE@jsjoeio: we need to chmod after the server is finished
// listening. Otherwise, the socket may not have been created yet.
if (socket && mode) {
await fs.chmod(socket, mode)
}
} }
/** /**
@@ -133,6 +129,6 @@ export const handleServerError = (resolved: boolean, err: Error, reject: (err: E
*/ */
export const handleArgsSocketCatchError = (error: any) => { export const handleArgsSocketCatchError = (error: any) => {
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") { if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
throw Error(error.message ? error.message : error) logger.error(error.message ? error.message : error)
} }
} }

View File

@@ -3,7 +3,15 @@ import { promises as fs } from "fs"
import yaml from "js-yaml" import yaml from "js-yaml"
import * as os from "os" import * as os from "os"
import * as path from "path" import * as path from "path"
import { canConnect, generateCertificate, generatePassword, humanPath, paths, isNodeJSErrnoException } from "./util" import {
canConnect,
generateCertificate,
generatePassword,
humanPath,
paths,
isNodeJSErrnoException,
isFile,
} from "./util"
const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc") const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc")
@@ -32,9 +40,41 @@ export enum LogLevel {
export class OptionalString extends Optional<string> {} export class OptionalString extends Optional<string> {}
/** /**
* Code flags provided by the user. * Arguments that the user explicitly provided on the command line. All
* arguments must be optional.
*
* For arguments with defaults see DefaultedArgs.
*/ */
export interface UserProvidedCodeArgs { export interface UserProvidedArgs {
config?: string
auth?: AuthType
password?: string
"hashed-password"?: string
cert?: OptionalString
"cert-host"?: string
"cert-key"?: string
"disable-update-check"?: boolean
enable?: string[]
help?: boolean
host?: string
locale?: string
port?: number
json?: boolean
log?: LogLevel
open?: boolean
"bind-addr"?: string
socket?: string
version?: boolean
"proxy-domain"?: string[]
"reuse-window"?: boolean
"new-window"?: boolean
"ignore-last-opened"?: boolean
link?: OptionalString
verbose?: boolean
/* Positional arguments. */
_?: string[]
// VS Code flags.
"disable-telemetry"?: boolean "disable-telemetry"?: boolean
force?: boolean force?: boolean
"user-data-dir"?: string "user-data-dir"?: string
@@ -47,45 +87,6 @@ export interface UserProvidedCodeArgs {
"locate-extension"?: string[] "locate-extension"?: string[]
"show-versions"?: boolean "show-versions"?: boolean
category?: string category?: string
"github-auth"?: string
"disable-update-check"?: boolean
"disable-file-downloads"?: boolean
}
/**
* Arguments that the user explicitly provided on the command line. All
* arguments must be optional.
*
* For arguments with defaults see DefaultedArgs.
*/
export interface UserProvidedArgs extends UserProvidedCodeArgs {
config?: string
auth?: AuthType
password?: string
"hashed-password"?: string
cert?: OptionalString
"cert-host"?: string
"cert-key"?: string
enable?: string[]
help?: boolean
host?: string
locale?: string
port?: number
json?: boolean
log?: LogLevel
open?: boolean
"bind-addr"?: string
socket?: string
"socket-mode"?: string
version?: boolean
"proxy-domain"?: string[]
"reuse-window"?: boolean
"new-window"?: boolean
"ignore-last-opened"?: boolean
link?: OptionalString
verbose?: boolean
/* Positional arguments. */
_?: string[]
} }
interface Option<T> { interface Option<T> {
@@ -125,11 +126,11 @@ type OptionType<T> = T extends boolean
? "string[]" ? "string[]"
: "unknown" : "unknown"
export type Options<T> = { type Options<T> = {
[P in keyof T]: Option<OptionType<T[P]>> [P in keyof T]: Option<OptionType<T[P]>>
} }
export const options: Options<Required<UserProvidedArgs>> = { const options: Options<Required<UserProvidedArgs>> = {
auth: { type: AuthType, description: "The type of authentication to use." }, auth: { type: AuthType, description: "The type of authentication to use." },
password: { password: {
type: "string", type: "string",
@@ -158,10 +159,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.",
}, },
"disable-file-downloads": {
type: "boolean",
description: "Disable file downloads from Code.",
},
// --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[]" },
@@ -185,7 +182,6 @@ export const options: Options<Required<UserProvidedArgs>> = {
port: { type: "number", description: "" }, port: { type: "number", description: "" },
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." },
version: { type: "boolean", short: "v", description: "Display version information." }, version: { type: "boolean", short: "v", description: "Display version information." },
_: { type: "string[]" }, _: { type: "string[]" },
@@ -209,10 +205,6 @@ export const options: Options<Required<UserProvidedArgs>> = {
}, },
"uninstall-extension": { type: "string[]", description: "Uninstall a VS Code extension by id." }, "uninstall-extension": { type: "string[]", description: "Uninstall a VS Code extension by id." },
"show-versions": { type: "boolean", description: "Show VS Code extension versions." }, "show-versions": { type: "boolean", description: "Show VS Code extension versions." },
"github-auth": {
type: "string",
description: "GitHub authentication token (can only be passed in via $GITHUB_TOKEN or the config file).",
},
"proxy-domain": { type: "string[]", description: "Domain used for proxying ports." }, "proxy-domain": { type: "string[]", description: "Domain used for proxying ports." },
"ignore-last-opened": { "ignore-last-opened": {
type: "boolean", type: "boolean",
@@ -237,15 +229,15 @@ export const options: Options<Required<UserProvidedArgs>> = {
type: OptionalString, type: OptionalString,
description: ` description: `
Securely bind code-server via our cloud service with the passed name. You'll get a URL like 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. https://hostname-username.cdr.co at which you can easily access your code-server instance.
Authorization is done via GitHub. Authorization is done via GitHub.
`, `,
deprecated: true, deprecated: true,
}, },
} }
export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedArgs>>> = options): string[] => { export const optionDescriptions = (): string[] => {
const entries = Object.entries(opts).filter(([, v]) => !!v.description) const entries = Object.entries(options).filter(([, v]) => !!v.description)
const widths = entries.reduce( const widths = entries.reduce(
(prev, [k, v]) => ({ (prev, [k, v]) => ({
long: k.length > prev.long ? k.length : prev.long, long: k.length > prev.long ? k.length : prev.long,
@@ -344,10 +336,6 @@ export const parse = (
throw new Error("--hashed-password can only be set in the config file or passed in via $HASHED_PASSWORD") throw new Error("--hashed-password can only be set in the config file or passed in via $HASHED_PASSWORD")
} }
if (key === "github-auth" && !opts?.configFile) {
throw new Error("--github-auth can only be set in the config file or passed in via $GITHUB_TOKEN")
}
const option = options[key] const option = options[key]
if (option.type === "boolean") { if (option.type === "boolean") {
;(args[key] as boolean) = true ;(args[key] as boolean) = true
@@ -421,12 +409,7 @@ export const parse = (
logger.debug(() => [ logger.debug(() => [
`parsed ${opts?.configFile ? "config" : "command line"}`, `parsed ${opts?.configFile ? "config" : "command line"}`,
field("args", { field("args", { ...args, password: undefined }),
...args,
password: args.password ? "<redacted>" : undefined,
"hashed-password": args["hashed-password"] ? "<redacted>" : undefined,
"github-auth": args["github-auth"] ? "<redacted>" : undefined,
}),
]) ])
return args return args
@@ -451,7 +434,7 @@ export interface DefaultedArgs extends ConfigArgs {
"extensions-dir": string "extensions-dir": string
"user-data-dir": string "user-data-dir": string
/* Positional arguments. */ /* Positional arguments. */
_: string[] _: []
} }
/** /**
@@ -524,7 +507,6 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args.host = "localhost" args.host = "localhost"
args.port = 0 args.port = 0
args.socket = undefined args.socket = undefined
args["socket-mode"] = undefined
args.cert = undefined args.cert = undefined
args.auth = AuthType.None args.auth = AuthType.None
} }
@@ -542,24 +524,15 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args.password = process.env.PASSWORD args.password = process.env.PASSWORD
} }
if (process.env.CS_DISABLE_FILE_DOWNLOADS === "1") {
args["disable-file-downloads"] = 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
usingEnvPassword = false usingEnvPassword = false
} }
if (process.env.GITHUB_TOKEN) {
args["github-auth"] = process.env.GITHUB_TOKEN
}
// Ensure they're not readable by child processes. // Ensure they're not readable by child processes.
delete process.env.PASSWORD delete process.env.PASSWORD
delete process.env.HASHED_PASSWORD delete process.env.HASHED_PASSWORD
delete process.env.GITHUB_TOKEN
// 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(/^\*\./, "")))
@@ -774,37 +747,30 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
return undefined return undefined
} }
/**
* Arguments for running Code's server.
*
* A subset of ../../lib/vscode/src/vs/server/node/serverEnvironmentService.ts:90
*/
export interface CodeArgs extends UserProvidedCodeArgs {
"accept-server-license-terms"?: boolean
"connection-token"?: string
help: boolean
port?: string
version: boolean
"without-connection-token"?: boolean
"without-browser-env-var"?: boolean
compatibility: string
}
/**
* Types for ../../lib/vscode/src/vs/server/node/server.main.ts:65.
*/
export type SpawnCodeCli = (args: CodeArgs) => Promise<void>
/** /**
* Convert our arguments to VS Code server arguments. * Convert our arguments to VS Code server arguments.
*/ */
export const toCodeArgs = async (args: DefaultedArgs): Promise<CodeArgs> => { export const toVsCodeArgs = async (args: DefaultedArgs): Promise<CodeServerLib.ServerParsedArgs> => {
let workspace = ""
let folder = ""
if (args._.length) {
const lastEntry = path.resolve(args._[args._.length - 1])
const entryIsFile = await isFile(lastEntry)
if (entryIsFile && path.extname(lastEntry) === ".code-workspace") {
workspace = lastEntry
} else if (!entryIsFile) {
folder = lastEntry
}
// Otherwise it is a regular file. Spawning VS Code with a file is not yet
// supported but it can be done separately after code-server spawns.
}
return { return {
"connection-token": "0000",
...args, ...args,
workspace,
folder,
"accept-server-license-terms": true, "accept-server-license-terms": true,
// This seems to be used to make the connection token flags optional (when
// set to 1.63) but we have always included them.
compatibility: "1.64",
/** Type casting. */ /** Type casting. */
help: !!args.help, help: !!args.help,
version: !!args.version, version: !!args.version,

View File

@@ -16,36 +16,14 @@ export function getPackageJson(relativePath: string): JSONSchemaForNPMPackageJso
return pkg return pkg
} }
export const rootPath = path.resolve(__dirname, "../..") const pkg = getPackageJson("../../package.json")
export const vsRootPath = path.join(rootPath, "lib/vscode")
const PACKAGE_JSON = "package.json"
const pkg = getPackageJson(`${rootPath}/${PACKAGE_JSON}`)
const codePkg = getPackageJson(`${vsRootPath}/${PACKAGE_JSON}`) || { version: "0.0.0" }
export const pkgName = pkg.name || "code-server" export const pkgName = pkg.name || "code-server"
export const version = pkg.version || "development" export const version = pkg.version || "development"
export const commit = pkg.commit || "development" export const commit = pkg.commit || "development"
export const codeVersion = codePkg.version || "development" export const rootPath = path.resolve(__dirname, "../..")
export const vsRootPath = path.join(rootPath, "vendor/modules/code-oss-dev")
export const tmpdir = path.join(os.tmpdir(), "code-server") export const tmpdir = path.join(os.tmpdir(), "code-server")
export const isDevMode = commit === "development" export const isDevMode = commit === "development"
export const httpProxyUri = export const httpProxyUri =
process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy
/**
* getVersionString returns a human-readable version string suitable
* for outputting to the console.
*/
export function getVersionString(): string {
return [version, commit, "with Code", codeVersion].join(" ")
}
/**
* getVersionJsonString returns a machine-readable version string
* suitable for outputting to the console.
*/
export function getVersionJsonString(): string {
return JSON.stringify({
codeServer: version,
commit,
vscode: codeVersion,
})
}

View File

@@ -1,7 +1,7 @@
import { logger } from "@coder/logger" import { logger } from "@coder/logger"
import { optionDescriptions, parse, readConfigFile, setDefaults, shouldOpenInExistingInstance } from "./cli" import { optionDescriptions, parse, readConfigFile, setDefaults, shouldOpenInExistingInstance } from "./cli"
import { getVersionString, getVersionJsonString } from "./constants" import { commit, version } from "./constants"
import { openInExistingInstance, runCodeServer, runCodeCli, shouldSpawnCliProcess } from "./main" import { openInExistingInstance, runCodeServer, runVsCodeCli, shouldSpawnCliProcess } from "./main"
import { isChild, wrapper } from "./wrapper" import { isChild, wrapper } from "./wrapper"
async function entry(): Promise<void> { async function entry(): Promise<void> {
@@ -24,7 +24,7 @@ async function entry(): Promise<void> {
const args = await setDefaults(cliArgs, configArgs) const args = await setDefaults(cliArgs, configArgs)
if (args.help) { if (args.help) {
console.log("code-server", getVersionString()) console.log("code-server", version, commit)
console.log("") console.log("")
console.log(`Usage: code-server [options] [path]`) console.log(`Usage: code-server [options] [path]`)
console.log(` - Opening a directory: code-server ./path/to/your/project`) console.log(` - Opening a directory: code-server ./path/to/your/project`)
@@ -39,16 +39,22 @@ async function entry(): Promise<void> {
if (args.version) { if (args.version) {
if (args.json) { if (args.json) {
console.log(getVersionJsonString()) console.log(
JSON.stringify({
codeServer: version,
commit,
vscode: require("../../vendor/modules/code-oss-dev/package.json").version,
}),
)
} else { } else {
console.log(getVersionString()) console.log(version, commit)
} }
return return
} }
if (shouldSpawnCliProcess(args)) { if (shouldSpawnCliProcess(args)) {
logger.debug("Found VS Code arguments; spawning VS Code CLI") logger.debug("Found VS Code arguments; spawning VS Code CLI")
return runCodeCli(args) return runVsCodeCli(args)
} }
const socketPath = await shouldOpenInExistingInstance(cliArgs) const socketPath = await shouldOpenInExistingInstance(cliArgs)

View File

@@ -138,21 +138,6 @@ export const relativeRoot = (originalUrl: string): string => {
return normalize("./" + (depth > 1 ? "../".repeat(depth - 1) : "")) return normalize("./" + (depth > 1 ? "../".repeat(depth - 1) : ""))
} }
/**
* A helper function to construct a redirect path based on
* an Express Request, query and a path to redirect to.
*
* Redirect path is relative to `/${to}`.
*/
export const constructRedirectPath = (req: express.Request, query: qs.ParsedQs, to: string): string => {
const relativePath = normalize(`${relativeRoot(req.originalUrl)}/${to}`, true)
// %2f or %2F are both equalivent to an encoded slash /
const queryString = qs.stringify(query).replace(/%2[fF]/g, "/")
const redirectPath = `${relativePath}${queryString ? `?${queryString}` : ""}`
return redirectPath
}
/** /**
* Redirect relatively to `/${to}`. Query variables on the current URI will be * Redirect relatively to `/${to}`. Query variables on the current URI will be
* preserved. `to` should be a simple path without any query parameters * preserved. `to` should be a simple path without any query parameters
@@ -171,7 +156,9 @@ export const redirect = (
} }
}) })
const redirectPath = constructRedirectPath(req, query, to) const relativePath = normalize(`${relativeRoot(req.originalUrl)}/${to}`, true)
const queryString = qs.stringify(query)
const redirectPath = `${relativePath}${queryString ? `?${queryString}` : ""}`
logger.debug(`redirecting from ${req.originalUrl} to ${redirectPath}`) logger.debug(`redirecting from ${req.originalUrl} to ${redirectPath}`)
res.redirect(redirectPath) res.redirect(redirectPath)
} }
@@ -209,7 +196,7 @@ export const getCookieDomain = (host: string, proxyDomains: string[]): string |
// default NGINX does this). // default NGINX does this).
!host.includes(".") !host.includes(".")
) { ) {
logger.debug("no valid cookie domain", field("host", host)) logger.debug("no valid cookie doman", field("host", host))
return undefined return undefined
} }
@@ -219,7 +206,7 @@ export const getCookieDomain = (host: string, proxyDomains: string[]): string |
} }
}) })
logger.debug("got cookie domain", field("host", host)) logger.debug("got cookie doman", field("host", host))
return host || undefined return host || undefined
} }

View File

@@ -5,7 +5,7 @@ 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, toVsCodeArgs, UserProvidedArgs } from "./cli"
import { coderCloudBind } from "./coder_cloud" import { coderCloudBind } from "./coder_cloud"
import { commit, version } from "./constants" import { commit, version } from "./constants"
import { register } from "./routes" import { register } from "./routes"
@@ -24,46 +24,27 @@ export const shouldSpawnCliProcess = (args: UserProvidedArgs): boolean => {
} }
/** /**
* This is copy of OpenCommandPipeArgs from * This is useful when an CLI arg should be passed to VS Code directly,
* ../../lib/vscode/src/vs/workbench/api/node/extHostCLIServer.ts:15 * such as when managing extensions.
* * @deprecated This should be removed when code-server merges with lib/vscode.
* Arguments supported by Code's socket. It can be used to perform actions from
* the CLI in a running instance of Code (for example to open a file).
*
* TODO: Can we import this (and other types) directly?
*/ */
export interface OpenCommandPipeArgs { export const runVsCodeCli = async (args: DefaultedArgs): Promise<void> => {
type: "open" logger.debug("Running VS Code CLI")
fileURIs?: string[]
folderURIs: string[]
forceNewWindow?: boolean
diffMode?: boolean
addMode?: boolean
gotoLineMode?: boolean
forceReuseWindow?: boolean
waitMarkerFilePath?: string
}
/** // See ../../vendor/modules/code-oss-dev/src/vs/server/main.js.
* Run Code's CLI for things like managing extensions. const spawnCli = await loadAMDModule<CodeServerLib.SpawnCli>("vs/server/remoteExtensionHostAgent", "spawnCli")
*/
export const runCodeCli = async (args: DefaultedArgs): Promise<void> => {
logger.debug("Running Code CLI")
// See ../../lib/vscode/src/vs/server/node/server.main.ts:65.
const spawnCli = await loadAMDModule<SpawnCodeCli>("vs/server/node/server.main", "spawnCli")
try { try {
await spawnCli(await toCodeArgs(args)) await spawnCli(await toVsCodeArgs(args))
} catch (error: any) { } catch (error: any) {
logger.error("Got error from Code", error) logger.error("Got error from VS Code", error)
} }
process.exit(0) process.exit(0)
} }
export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => { export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => {
const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = { const pipeArgs: CodeServerLib.OpenCommandPipeArgs & { fileURIs: string[] } = {
type: "open", type: "open",
folderURIs: [], folderURIs: [],
fileURIs: [], fileURIs: [],
@@ -95,12 +76,12 @@ export const openInExistingInstance = async (args: DefaultedArgs, socketPath: st
}, },
(response) => { (response) => {
response.on("data", (message) => { response.on("data", (message) => {
logger.debug("got message from Code", field("message", message.toString())) logger.debug("got message from VS Code", field("message", message.toString()))
}) })
}, },
) )
vscode.on("error", (error: unknown) => { vscode.on("error", (error: unknown) => {
logger.error("got error from Code", field("error", error)) logger.error("got error from VS Code", field("error", error))
}) })
vscode.write(JSON.stringify(pipeArgs)) vscode.write(JSON.stringify(pipeArgs))
vscode.end() vscode.end()

View File

@@ -129,14 +129,6 @@ export const register = async (app: App, args: DefaultedArgs): Promise<Disposabl
express.static(rootPath, { express.static(rootPath, {
cacheControl: commit !== "development", cacheControl: commit !== "development",
fallthrough: false, fallthrough: false,
setHeaders: (res, path, stat) => {
// The service worker is served from a sub-path on the static route so
// this is required to allow it to register a higher scope (by default
// the browser only allows it to register from its own path or lower).
if (path.endsWith("/serviceWorker.js")) {
res.setHeader("Service-Worker-Allowed", "/")
}
},
}), }),
) )

View File

@@ -1,36 +1,18 @@
import { logger } from "@coder/logger" import { logger } from "@coder/logger"
import * as express from "express" import * as express from "express"
import * as http from "http"
import * as net from "net"
import * as path from "path"
import { WebsocketRequest } from "../../../typings/pluginapi" import { WebsocketRequest } from "../../../typings/pluginapi"
import { logError } from "../../common/util" import { logError } from "../../common/util"
import { CodeArgs, toCodeArgs } from "../cli" import { toVsCodeArgs } from "../cli"
import { isDevMode } from "../constants" import { isDevMode } from "../constants"
import { authenticated, ensureAuthenticated, redirect, replaceTemplates, self } from "../http" import { authenticated, ensureAuthenticated, redirect, self } from "../http"
import { SocketProxyProvider } from "../socket" import { loadAMDModule } from "../util"
import { isFile, loadAMDModule } from "../util"
import { Router as WsRouter } from "../wsRouter" import { Router as WsRouter } from "../wsRouter"
import { errorHandler } from "./errors"
/**
* This is the API of Code's web client server. code-server delegates requests
* to Code here.
*/
export interface IServerAPI {
handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void>
handleUpgrade(req: http.IncomingMessage, socket: net.Socket): void
handleServerError(err: Error): void
dispose(): void
}
// Types for ../../../lib/vscode/src/vs/server/node/server.main.ts:72.
export type CreateServer = (address: string | net.AddressInfo | null, args: CodeArgs) => Promise<IServerAPI>
export class CodeServerRouteWrapper { export class CodeServerRouteWrapper {
/** Assigned in `ensureCodeServerLoaded` */ /** Assigned in `ensureCodeServerLoaded` */
private _codeServerMain!: IServerAPI private _codeServerMain!: CodeServerLib.IServerAPI
private _wsRouterWrapper = WsRouter() private _wsRouterWrapper = WsRouter()
private _socketProxyProvider = new SocketProxyProvider()
public router = express.Router() public router = express.Router()
public get wsRouter() { public get wsRouter() {
@@ -39,37 +21,8 @@ export class CodeServerRouteWrapper {
//#region Route Handlers //#region Route Handlers
private manifest: express.Handler = async (req, res, next) => {
res.writeHead(200, { "Content-Type": "application/manifest+json" })
return res.end(
replaceTemplates(
req,
JSON.stringify(
{
name: "code-server",
short_name: "code-server",
start_url: ".",
display: "fullscreen",
description: "Run Code on a remote server.",
icons: [192, 512].map((size) => ({
src: `{{BASE}}/_static/src/browser/media/pwa-icon-${size}.png`,
type: "image/png",
sizes: `${size}x${size}`,
})),
},
null,
2,
),
),
)
}
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
// Ew means the workspace was closed so clear the last folder/workspace.
const FOLDER_OR_WORKSPACE_WAS_CLOSED = req.query.ew
if (!isAuthenticated) { if (!isAuthenticated) {
const to = self(req) const to = self(req)
@@ -78,38 +31,25 @@ export class CodeServerRouteWrapper {
}) })
} }
if (NO_FOLDER_OR_WORKSPACE_QUERY && !FOLDER_OR_WORKSPACE_WAS_CLOSED) { const { query } = await req.settings.read()
const settings = await req.settings.read() if (query) {
const lastOpened = settings.query || {} // Ew means the workspace was closed so clear the last folder/workspace.
// This flag disables the last opened behavior if (req.query.ew) {
const IGNORE_LAST_OPENED = req.args["ignore-last-opened"] delete query.folder
const HAS_LAST_OPENED_FOLDER_OR_WORKSPACE = lastOpened.folder || lastOpened.workspace delete query.workspace
const HAS_FOLDER_OR_WORKSPACE_FROM_CLI = req.args._.length > 0
const to = self(req)
let folder = undefined
let workspace = undefined
// Redirect to the last folder/workspace if nothing else is opened.
if (HAS_LAST_OPENED_FOLDER_OR_WORKSPACE && !IGNORE_LAST_OPENED) {
folder = lastOpened.folder
workspace = lastOpened.workspace
} else if (HAS_FOLDER_OR_WORKSPACE_FROM_CLI) {
const lastEntry = path.resolve(req.args._[req.args._.length - 1])
const entryIsFile = await isFile(lastEntry)
const IS_WORKSPACE_FILE = entryIsFile && path.extname(lastEntry) === ".code-workspace"
if (IS_WORKSPACE_FILE) {
workspace = lastEntry
} else if (!entryIsFile) {
folder = lastEntry
}
} }
if (folder || workspace) { // Redirect to the last folder/workspace if nothing else is opened.
if (
!req.query.folder &&
!req.query.workspace &&
(query.folder || query.workspace) &&
!req.args["ignore-last-opened"] // This flag disables this behavior.
) {
const to = self(req)
return redirect(req, res, to, { return redirect(req, res, to, {
folder, folder: query.folder,
workspace, workspace: query.workspace,
}) })
} }
} }
@@ -122,14 +62,24 @@ export class CodeServerRouteWrapper {
} }
private $proxyRequest: express.Handler = async (req, res, next) => { private $proxyRequest: express.Handler = async (req, res, next) => {
// We allow certain errors to propagate so that other routers may handle requests
// outside VS Code
const requestErrorHandler = (error: any) => {
if (error instanceof Error && ["EntryNotFound", "FileNotFound", "HttpError"].includes(error.message)) {
next()
}
errorHandler(error, req, res, next)
}
req.once("error", requestErrorHandler)
this._codeServerMain.handleRequest(req, res) this._codeServerMain.handleRequest(req, res)
} }
private $proxyWebsocket = async (req: WebsocketRequest) => { private $proxyWebsocket = async (req: WebsocketRequest) => {
const wrappedSocket = await this._socketProxyProvider.createProxy(req.ws) this._codeServerMain.handleUpgrade(req, req.socket)
this._codeServerMain.handleUpgrade(req, wrappedSocket)
req.ws.resume() req.socket.resume()
} }
//#endregion //#endregion
@@ -147,14 +97,18 @@ export class CodeServerRouteWrapper {
const { args } = req const { args } = req
// See ../../../lib/vscode/src/vs/server/node/server.main.ts:72. /**
const createVSServer = await loadAMDModule<CreateServer>("vs/server/node/server.main", "createServer") * @file ../../../vendor/modules/code-oss-dev/src/vs/server/main.js
*/
const createVSServer = await loadAMDModule<CodeServerLib.CreateServer>(
"vs/server/remoteExtensionHostAgent",
"createServer",
)
try { try {
this._codeServerMain = await createVSServer(null, { this._codeServerMain = await createVSServer(null, {
...(await toCodeArgs(args)), ...(await toVsCodeArgs(args)),
// TODO: Make the browser helper script work. // TODO: Make the browser helper script work.
"without-connection-token": true,
"without-browser-env-var": true, "without-browser-env-var": true,
}) })
} catch (error) { } catch (error) {
@@ -170,13 +124,11 @@ 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.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest) this.router.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest)
this._wsRouterWrapper.ws("/", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket) this._wsRouterWrapper.ws("/", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket)
} }
dispose() { dispose() {
this._codeServerMain?.dispose() this._codeServerMain?.dispose()
this._socketProxyProvider.stop()
} }
} }

View File

@@ -1,4 +1,5 @@
import * as argon2 from "argon2" import { logger } from "@coder/logger"
import * as argon2 from "@node-rs/argon2"
import * as cp from "child_process" import * as cp from "child_process"
import * as crypto from "crypto" import * as crypto from "crypto"
import envPaths from "env-paths" import envPaths from "env-paths"
@@ -156,7 +157,12 @@ export const generatePassword = async (length = 24): Promise<string> => {
* Used to hash the password. * Used to hash the password.
*/ */
export const hash = async (password: string): Promise<string> => { export const hash = async (password: string): Promise<string> => {
return await argon2.hash(password) try {
return await argon2.hash(password)
} catch (error: any) {
logger.error(error)
return ""
}
} }
/** /**
@@ -166,7 +172,12 @@ export const isHashMatch = async (password: string, hash: string) => {
if (password === "" || hash === "" || !hash.startsWith("$")) { if (password === "" || hash === "" || !hash.startsWith("$")) {
return false return false
} }
return await argon2.verify(hash, password) try {
return await argon2.verify(hash, password)
} catch (error: any) {
logger.error(error)
return false
}
} }
/** /**

View File

@@ -9,16 +9,10 @@ import { CodeServer, CodeServerPage } from "./models/CodeServer"
* *
* If `includeCredentials` is `true` page requests will be authenticated. * If `includeCredentials` is `true` page requests will be authenticated.
*/ */
export const describe = ( export const describe = (name: string, includeCredentials: boolean, fn: (codeServer: CodeServer) => void) => {
name: string,
includeCredentials: boolean,
codeServerArgs: string[],
codeServerEnv: NodeJS.ProcessEnv,
fn: (codeServer: CodeServer) => void,
) => {
test.describe(name, () => { test.describe(name, () => {
// This will spawn on demand so nothing is necessary on before. // This will spawn on demand so nothing is necessary on before.
const codeServer = new CodeServer(name, codeServerArgs, codeServerEnv) const codeServer = new CodeServer(name)
// Kill code-server after the suite has ended. This may happen even without // Kill code-server after the suite has ended. This may happen even without
// doing it explicitly but it seems prudent to be sure. // doing it explicitly but it seems prudent to be sure.
@@ -42,9 +36,6 @@ export const describe = (
authenticated: includeCredentials, authenticated: includeCredentials,
// This provides a cookie that authenticates with code-server. // This provides a cookie that authenticates with code-server.
storageState: includeCredentials ? storageState : {}, storageState: includeCredentials ? storageState : {},
// NOTE@jsjoeio some tests use --cert which uses a self-signed certificate
// without this option, those tests will fail.
ignoreHTTPSErrors: true,
}) })
fn(codeServer) fn(codeServer)
@@ -70,8 +61,8 @@ export const test = base.extend<TestFixtures>({
// made too). In these cases just accept. // made too). In these cases just accept.
page.on("dialog", (d) => d.accept()) page.on("dialog", (d) => d.accept())
const codeServerPage = new CodeServerPage(codeServer, page, authenticated) const codeServerPage = new CodeServerPage(codeServer, page)
await codeServerPage.navigate() await codeServerPage.setup(authenticated)
await use(codeServerPage) await use(codeServerPage)
}, },
}) })

View File

@@ -1,45 +1,6 @@
import * as cp from "child_process"
import { promises as fs } from "fs"
import * as os from "os"
import * as path from "path"
import * as util from "util"
import { describe, test, expect } from "./baseFixture" import { describe, test, expect } from "./baseFixture"
import { CodeServer } from "./models/CodeServer"
describe("code-server", true, [], {}, () => {
// TODO@asher: Generalize this? Could be nice if we were to ever need
// multiple migration tests in other suites.
const instances = new Map<string, CodeServer>()
test.afterAll(async () => {
const procs = Array.from(instances.values())
instances.clear()
await Promise.all(procs.map((cs) => cs.close()))
})
/**
* Spawn a specific version of code-server using the install script.
*/
const spawn = async (version: string, dir?: string): Promise<CodeServer> => {
let instance = instances.get(version)
if (!instance) {
await util.promisify(cp.exec)(`./install.sh --method standalone --version ${version}`, {
cwd: path.join(__dirname, "../.."),
})
instance = new CodeServer(
"code-server@" + version,
["--auth=none"],
{ VSCODE_DEV: "" },
dir,
`${os.homedir()}/.local/lib/code-server-${version}`,
)
instances.set(version, instance)
}
return instance
}
describe("CodeServer", true, () => {
test("should navigate to home page", async ({ codeServerPage }) => { test("should navigate to home page", async ({ codeServerPage }) => {
// We navigate codeServer before each test // We navigate codeServer before each test
// and we start the test with a storage state // and we start the test with a storage state
@@ -55,62 +16,12 @@ describe("code-server", true, [], {}, () => {
expect(await codeServerPage.isEditorVisible()).toBe(true) expect(await codeServerPage.isEditorVisible()).toBe(true)
}) })
test("should always have a connection", async ({ codeServerPage }) => {
expect(await codeServerPage.isConnected()).toBe(true)
})
test("should show the Integrated Terminal", async ({ codeServerPage }) => { test("should show the Integrated Terminal", async ({ codeServerPage }) => {
await codeServerPage.focusTerminal() await codeServerPage.focusTerminal()
expect(await codeServerPage.page.isVisible("#terminal")).toBe(true) expect(await codeServerPage.page.isVisible("#terminal")).toBe(true)
}) })
test("should open a file", async ({ codeServerPage }) => {
const dir = await codeServerPage.workspaceDir
const file = path.join(dir, "foo")
await fs.writeFile(file, "bar")
await codeServerPage.openFile(file)
})
test("should migrate state to avoid collisions", async ({ codeServerPage }) => {
// This can take a very long time in development because of how long pages
// take to load and we are doing a lot of that here.
test.slow()
const dir = await codeServerPage.workspaceDir
const files = [path.join(dir, "foo"), path.join(dir, "bar")]
await Promise.all(
files.map((file) => {
return fs.writeFile(file, path.basename(file))
}),
)
// Open a file in the latest instance.
await codeServerPage.openFile(files[0])
await codeServerPage.stateFlush()
// Open a file in an older version of code-server. It should not see the
// file opened in the new instance since the database has a different
// name. This must be accessed through the proxy so it shares the same
// domain and can write to the same database.
const cs = await spawn("4.0.2", dir)
const address = new URL(await cs.address())
await codeServerPage.navigate("/proxy/" + address.port + "/")
await codeServerPage.openFile(files[1])
expect(await codeServerPage.tabIsVisible(files[0])).toBe(false)
await codeServerPage.stateFlush()
// Move back to latest code-server. We should see the file we previously
// opened with it but not the old code-server file because the new instance
// already created its own database on this path and will avoid migrating.
await codeServerPage.navigate()
await codeServerPage.waitForTab(files[0])
expect(await codeServerPage.tabIsVisible(files[1])).toBe(false)
// Open a new path in latest code-server. This one should migrate the
// database from old code-server but see nothing from the new database
// created on the root.
await codeServerPage.navigate("/vscode")
await codeServerPage.waitForTab(files[1])
expect(await codeServerPage.tabIsVisible(files[0])).toBe(false)
// Should still be open after a reload.
await codeServerPage.navigate("/vscode")
await codeServerPage.waitForTab(files[1])
expect(await codeServerPage.tabIsVisible(files[0])).toBe(false)
})
}) })

View File

@@ -1,48 +0,0 @@
import * as path from "path"
import { promises as fs } from "fs"
import { clean } from "../utils/helpers"
import { describe, test, expect } from "./baseFixture"
describe("Downloads (enabled)", true, [], {}, async () => {
const testName = "downloads-enabled"
test.beforeAll(async () => {
await clean(testName)
})
test("should see the 'Download...' option", async ({ codeServerPage }) => {
// Setup
const workspaceDir = await codeServerPage.workspaceDir
const tmpFilePath = path.join(workspaceDir, "unique-file.txt")
await fs.writeFile(tmpFilePath, "hello world")
// Action
const fileInExplorer = await codeServerPage.page.waitForSelector("text=unique-file.txt")
await fileInExplorer.click({
button: "right",
})
expect(await codeServerPage.page.isVisible("text=Download...")).toBe(true)
})
})
describe("Downloads (disabled)", true, ["--disable-file-downloads"], {}, async () => {
const testName = "downloads-disabled"
test.beforeAll(async () => {
await clean(testName)
})
test("should not see the 'Download...' option", async ({ codeServerPage }) => {
// Setup
const workspaceDir = await codeServerPage.workspaceDir
const tmpFilePath = path.join(workspaceDir, "unique-file.txt")
await fs.writeFile(tmpFilePath, "hello world")
// Action
const fileInExplorer = await codeServerPage.page.waitForSelector("text=unique-file.txt")
await fileInExplorer.click({
button: "right",
})
expect(await codeServerPage.page.isVisible("text=Download...")).toBe(false)
})
})

View File

@@ -1,23 +1,12 @@
import * as path from "path"
import { describe, test } from "./baseFixture" import { describe, test } from "./baseFixture"
function runTestExtensionTests() { describe("Extensions", true, () => {
// This will only work if the test extension is loaded into code-server. // This will only work if the test extension is loaded into code-server.
test("should have access to VSCODE_PROXY_URI", async ({ codeServerPage }) => { test("should have access to VSCODE_PROXY_URI", async ({ codeServerPage }) => {
const address = await codeServerPage.address() const address = await codeServerPage.address()
await codeServerPage.executeCommandViaMenus("code-server: Get proxy URI") await codeServerPage.executeCommandViaMenus("code-server: Get proxy URI")
await codeServerPage.page.waitForSelector(`text=${address}/proxy/{{port}}`) await codeServerPage.page.waitForSelector(`text=${address}/proxy/{port}`)
}) })
}
const flags = ["--extensions-dir", path.join(__dirname, "./extensions")]
describe("Extensions", true, flags, {}, () => {
runTestExtensionTests()
})
describe("Extensions with --cert", true, [...flags, "--cert"], {}, () => {
runTestExtensionTests()
}) })

View File

@@ -2,7 +2,7 @@
"name": "code-server-extension", "name": "code-server-extension",
"description": "code-server test extension", "description": "code-server test extension",
"version": "0.0.1", "version": "0.0.1",
"publisher": "coder", "publisher": "cdr",
"activationEvents": [ "activationEvents": [
"onCommand:codeServerTest.proxyUri" "onCommand:codeServerTest.proxyUri"
], ],

View File

@@ -1,38 +0,0 @@
import { test as base } from "@playwright/test"
import { describe, expect, test } from "./baseFixture"
if (process.env.GITHUB_TOKEN) {
describe("GitHub token", true, [], {}, () => {
test("should be logged in to pull requests extension", async ({ codeServerPage }) => {
await codeServerPage.exec("git init")
await codeServerPage.exec("git remote add origin https://github.com/coder/code-server")
await codeServerPage.installExtension("GitHub.vscode-pull-request-github")
await codeServerPage.executeCommandViaMenus("View: Show Github")
await codeServerPage.page.click("text=Sign in")
await codeServerPage.page.click("text=Allow")
// It should ask to select an account, one of which will be the one we
// pre-injected.
expect(await codeServerPage.page.isVisible("text=Select an account")).toBe(false)
})
})
describe("No GitHub token", true, [], { GITHUB_TOKEN: "" }, () => {
test("should not be logged in to pull requests extension", async ({ codeServerPage }) => {
await codeServerPage.exec("git init")
await codeServerPage.exec("git remote add origin https://github.com/coder/code-server")
await codeServerPage.installExtension("GitHub.vscode-pull-request-github")
await codeServerPage.executeCommandViaMenus("View: Show Github")
await codeServerPage.page.click("text=Sign in")
await codeServerPage.page.click("text=Allow")
// Since there is no account it will ask directly for the token (because
// we are on localhost; otherwise it would initiate the oauth flow).
expect(await codeServerPage.page.isVisible("text=GitHub Personal Access Token")).toBe(false)
})
})
} else {
base.describe("GitHub token", () => {
base.skip("skipped because GITHUB_TOKEN is not set", () => {
// Playwright will not show this without a function.
})
})
}

View File

@@ -2,7 +2,7 @@ import { describe, test, expect } from "./baseFixture"
// This test is to make sure the globalSetup works as expected // This test is to make sure the globalSetup works as expected
// meaning globalSetup ran and stored the storageState // meaning globalSetup ran and stored the storageState
describe("globalSetup", true, [], {}, () => { describe("globalSetup", true, () => {
test("should keep us logged in using the storageState", async ({ codeServerPage }) => { test("should keep us logged in using the storageState", async ({ codeServerPage }) => {
// Make sure the editor actually loaded // Make sure the editor actually loaded
expect(await codeServerPage.isEditorVisible()).toBe(true) expect(await codeServerPage.isEditorVisible()).toBe(true)

View File

@@ -1,7 +1,7 @@
import { PASSWORD } from "../utils/constants" import { PASSWORD } from "../utils/constants"
import { describe, test, expect } from "./baseFixture" import { describe, test, expect } from "./baseFixture"
describe("login", false, [], {}, () => { describe("login", false, () => {
test("should see the login page", async ({ codeServerPage }) => { test("should see the login page", async ({ codeServerPage }) => {
// It should send us to the login page // It should send us to the login page
expect(await codeServerPage.page.title()).toBe("code-server login") expect(await codeServerPage.page.title()).toBe("code-server login")

View File

@@ -1,7 +1,7 @@
// NOTE@jsjoeio commenting out until we can figure out what's wrong // NOTE@jsjoeio commenting out until we can figure out what's wrong
// import { describe, test, expect } from "./baseFixture" // import { describe, test, expect } from "./baseFixture"
// describe("logout", true, [], {}, () => { // describe("logout", true, () => {
// test("should be able logout", async ({ codeServerPage }) => { // test("should be able logout", async ({ codeServerPage }) => {
// // Recommended by Playwright for async navigation // // Recommended by Playwright for async navigation
// // https://github.com/microsoft/playwright/issues/1987#issuecomment-620182151 // // https://github.com/microsoft/playwright/issues/1987#issuecomment-620182151

View File

@@ -3,8 +3,7 @@ import * as cp from "child_process"
import { promises as fs } from "fs" import { promises as fs } from "fs"
import * as path from "path" import * as path from "path"
import { Page } from "playwright" import { Page } from "playwright"
import * as util from "util" import { logError } from "../../../src/common/util"
import { logError, plural } from "../../../src/common/util"
import { onLine } from "../../../src/node/util" import { onLine } from "../../../src/node/util"
import { PASSWORD, workspaceDir } from "../../utils/constants" import { PASSWORD, workspaceDir } from "../../utils/constants"
import { idleTimer, tmpdir } from "../../utils/helpers" import { idleTimer, tmpdir } from "../../utils/helpers"
@@ -14,21 +13,14 @@ interface CodeServerProcess {
address: string address: string
} }
class Context { class CancelToken {
private _canceled = false private _canceled = false
private _done = false
public canceled(): boolean { public canceled(): boolean {
return this._canceled return this._canceled
} }
public finished(): boolean {
return this._done
}
public cancel(): void { public cancel(): void {
this._canceled = true this._canceled = true
} }
public finish(): void {
this._done = true
}
} }
/** /**
@@ -39,13 +31,7 @@ export class CodeServer {
public readonly logger: Logger public readonly logger: Logger
private closed = false private closed = false
constructor( constructor(name: string) {
name: string,
private readonly args: string[],
private readonly env: NodeJS.ProcessEnv,
private _workspaceDir: Promise<string> | string | undefined,
private readonly entry = process.env.CODE_SERVER_TEST_ENTRY || ".",
) {
this.logger = logger.named(name) this.logger = logger.named(name)
} }
@@ -61,22 +47,12 @@ export class CodeServer {
return address return address
} }
/**
* The workspace directory code-server opens with.
*/
get workspaceDir(): Promise<string> | string {
if (!this._workspaceDir) {
this._workspaceDir = tmpdir(workspaceDir)
}
return this._workspaceDir
}
/** /**
* Create a random workspace and seed it with settings. * Create a random workspace and seed it with settings.
*/ */
private async createWorkspace(): Promise<string> { private async createWorkspace(): Promise<string> {
const dir = await this.workspaceDir const dir = await tmpdir(workspaceDir)
await fs.mkdir(path.join(dir, "User"), { recursive: true }) await fs.mkdir(path.join(dir, "User"))
await fs.writeFile( await fs.writeFile(
path.join(dir, "User/settings.json"), path.join(dir, "User/settings.json"),
JSON.stringify({ JSON.stringify({
@@ -97,33 +73,34 @@ export class CodeServer {
const dir = await this.createWorkspace() const dir = await this.createWorkspace()
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const args = [ this.logger.debug("spawning")
this.entry, const proc = cp.spawn(
"--extensions-dir", "node",
path.join(dir, "extensions"), [
...this.args, process.env.CODE_SERVER_TEST_ENTRY || ".",
// Using port zero will spawn on a random port. // Using port zero will spawn on a random port.
"--bind-addr", "--bind-addr",
"127.0.0.1:0", "127.0.0.1:0",
// Setting the XDG variables would be easier and more thorough but the // Setting the XDG variables would be easier and more thorough but the
// modules we import ignores those variables for non-Linux operating // modules we import ignores those variables for non-Linux operating
// systems so use these flags instead. // systems so use these flags instead.
"--config", "--config",
path.join(dir, "config.yaml"), path.join(dir, "config.yaml"),
"--user-data-dir", "--user-data-dir",
dir, dir,
// The last argument is the workspace to open. "--extensions-dir",
dir, path.join(__dirname, "../extensions"),
] // The last argument is the workspace to open.
this.logger.debug("spawning `node " + args.join(" ") + "`") dir,
const proc = cp.spawn("node", args, { ],
cwd: path.join(__dirname, "../../.."), {
env: { cwd: path.join(__dirname, "../../.."),
...process.env, env: {
...this.env, ...process.env,
PASSWORD, PASSWORD,
},
}, },
}) )
const timer = idleTimer("Failed to extract address; did the format change?", reject) const timer = idleTimer("Failed to extract address; did the format change?", reject)
@@ -134,7 +111,7 @@ export class CodeServer {
}) })
proc.on("close", (code) => { proc.on("close", (code) => {
const error = new Error("code-server closed unexpectedly. Try running with LOG_LEVEL=debug to see more info.") const error = new Error("closed unexpectedly")
if (!this.closed) { if (!this.closed) {
this.logger.error(error.message, field("code", code)) this.logger.error(error.message, field("code", code))
} }
@@ -151,7 +128,7 @@ export class CodeServer {
timer.reset() timer.reset()
// Log the line without the timestamp. // Log the line without the timestamp.
this.logger.debug(line.replace(/\[.+\]/, "")) this.logger.trace(line.replace(/\[.+\]/, ""))
if (resolved) { if (resolved) {
return return
} }
@@ -192,13 +169,9 @@ 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,
private readonly authenticated: boolean,
) {
this.page.on("console", (message) => { this.page.on("console", (message) => {
this.codeServer.logger.debug(message.text()) this.codeServer.logger.debug(message)
}) })
this.page.on("pageerror", (error) => { this.page.on("pageerror", (error) => {
logError(this.codeServer.logger, "page", error) logError(this.codeServer.logger, "page", error)
@@ -210,25 +183,11 @@ export class CodeServerPage {
} }
/** /**
* The workspace directory code-server opens with. * Navigate to code-server.
*/ */
get workspaceDir() { async navigate() {
return this.codeServer.workspaceDir const address = await this.codeServer.address()
} await this.page.goto(address, { waitUntil: "networkidle" })
/**
* Navigate to a code-server endpoint (root by default). Then wait for the
* editor to become available.
*/
async navigate(endpoint = "/") {
const to = new URL(endpoint, await this.codeServer.address())
await this.page.goto(to.toString(), { waitUntil: "networkidle" })
// Only reload editor if authenticated. Otherwise we'll get stuck
// reloading the login page.
if (this.authenticated) {
await this.reloadUntilEditorIsReady()
}
} }
/** /**
@@ -241,13 +200,14 @@ export class CodeServerPage {
this.codeServer.logger.debug("Waiting for editor to be ready...") this.codeServer.logger.debug("Waiting for editor to be ready...")
const editorIsVisible = await this.isEditorVisible() const editorIsVisible = await this.isEditorVisible()
const editorIsConnected = await this.isConnected()
let reloadCount = 0 let reloadCount = 0
// Occassionally code-server timeouts in Firefox // Occassionally code-server timeouts in Firefox
// we're not sure why // we're not sure why
// but usually a reload or two fixes it // but usually a reload or two fixes it
// TODO@jsjoeio @oxy look into Firefox reconnection/timeout issues // TODO@jsjoeio @oxy look into Firefox reconnection/timeout issues
while (!editorIsVisible) { while (!editorIsVisible && !editorIsConnected) {
// When a reload happens, we want to wait for all resources to be // When a reload happens, we want to wait for all resources to be
// loaded completely. Hence why we use that instead of DOMContentLoaded // loaded completely. Hence why we use that instead of DOMContentLoaded
// Read more: https://thisthat.dev/dom-content-loaded-vs-load/ // Read more: https://thisthat.dev/dom-content-loaded-vs-load/
@@ -255,7 +215,7 @@ export class CodeServerPage {
// Give it an extra second just in case it's feeling extra slow // Give it an extra second just in case it's feeling extra slow
await this.page.waitForTimeout(1000) await this.page.waitForTimeout(1000)
reloadCount += 1 reloadCount += 1
if (await this.isEditorVisible()) { if ((await this.isEditorVisible()) && (await this.isConnected())) {
this.codeServer.logger.debug(`editor became ready after ${reloadCount} reloads`) this.codeServer.logger.debug(`editor became ready after ${reloadCount} reloads`)
break break
} }
@@ -279,6 +239,23 @@ export class CodeServerPage {
return visible return visible
} }
/**
* Checks if the editor is visible
*/
async isConnected() {
this.codeServer.logger.debug("Waiting for network idle...")
await this.page.waitForLoadState("networkidle")
const host = new URL(await this.codeServer.address()).host
// NOTE: This seems to be pretty brittle between version changes.
const hostSelector = `[aria-label="remote ${host}"]`
this.codeServer.logger.debug(`Waiting selector: ${hostSelector}`)
await this.page.waitForSelector(hostSelector)
return await this.page.isVisible(hostSelector)
}
/** /**
* Focuses Integrated Terminal * Focuses Integrated Terminal
* by using "Terminal: Focus Terminal" * by using "Terminal: Focus Terminal"
@@ -295,29 +272,6 @@ export class CodeServerPage {
await this.page.waitForSelector("textarea.xterm-helper-textarea") await this.page.waitForSelector("textarea.xterm-helper-textarea")
} }
/**
* Open a file by using menus.
*/
async openFile(file: string) {
await this.navigateMenus(["File", "Open File"])
await this.navigateQuickInput([path.basename(file)])
await this.waitForTab(file)
}
/**
* Wait for a tab to open for the specified file.
*/
async waitForTab(file: string): Promise<void> {
await this.page.waitForSelector(`.tab :text("${path.basename(file)}")`)
}
/**
* See if the specified tab is open.
*/
async tabIsVisible(file: string): Promise<boolean> {
return this.page.isVisible(`.tab :text("${path.basename(file)}")`)
}
/** /**
* Navigate to the command palette via menus then execute a command by typing * Navigate to the command palette via menus then execute a command by typing
* it then clicking the match from the results. * it then clicking the match from the results.
@@ -332,45 +286,13 @@ export class CodeServerPage {
} }
/** /**
* Navigate through the items in the selector. `open` is a function that will * Navigate through the specified set of menus. If it fails it will keep
* open the menu/popup containing the items through which to navigation. * trying.
*/ */
async navigateItems(items: string[], selector: string, open?: (selector: string) => void): Promise<void> { async navigateMenus(menus: string[]) {
const logger = this.codeServer.logger.named(selector) const navigate = async (cancelToken: CancelToken) => {
const steps: Array<() => Promise<unknown>> = [() => this.page.waitForSelector(`${menuSelector}:focus-within`)]
/** for (const menu of menus) {
* If the selector loses focus or gets removed this will resolve with false,
* signaling we need to try again.
*/
const openThenWaitClose = async (ctx: Context) => {
if (open) {
await open(selector)
}
this.codeServer.logger.debug(`watching ${selector}`)
try {
await this.page.waitForSelector(`${selector}:not(:focus-within)`)
} catch (error) {
if (!ctx.finished()) {
this.codeServer.logger.debug(`${selector} navigation: ${(error as any).message || error}`)
}
}
return false
}
/**
* This will step through each item, aborting and returning false if
* canceled or if any navigation step has an error which signals we need to
* try again.
*/
const navigate = async (ctx: Context) => {
const steps: Array<{ fn: () => Promise<unknown>; name: string }> = [
{
fn: () => this.page.waitForSelector(`${selector}:focus-within`),
name: "focus",
},
]
for (const item of items) {
// Normally these will wait for the item to be visible and then execute // Normally these will wait for the item to be visible and then execute
// the action. The problem is that if the menu closes these will still // the action. The problem is that if the menu closes these will still
// be waiting and continue to execute once the menu is visible again, // be waiting and continue to execute once the menu is visible again,
@@ -378,102 +300,56 @@ export class CodeServerPage {
// if the old promise clicks logout before the new one can). By // if the old promise clicks logout before the new one can). By
// splitting them into two steps each we can cancel before running the // splitting them into two steps each we can cancel before running the
// action. // action.
steps.push({ steps.push(() => this.page.hover(`text=${menu}`, { trial: true }))
fn: () => this.page.hover(`${selector} :text("${item}")`, { trial: true }), steps.push(() => this.page.hover(`text=${menu}`, { force: true }))
name: `${item}:hover:trial`, steps.push(() => this.page.click(`text=${menu}`, { trial: true }))
}) steps.push(() => this.page.click(`text=${menu}`, { force: true }))
steps.push({
fn: () => this.page.hover(`${selector} :text("${item}")`, { force: true }),
name: `${item}:hover:force`,
})
steps.push({
fn: () => this.page.click(`${selector} :text("${item}")`, { trial: true }),
name: `${item}:click:trial`,
})
steps.push({
fn: () => this.page.click(`${selector} :text("${item}")`, { force: true }),
name: `${item}:click:force`,
})
} }
for (const step of steps) { for (const step of steps) {
try { await step()
logger.debug(`navigation step: ${step.name}`) if (cancelToken.canceled()) {
await step.fn() this.codeServer.logger.debug("menu navigation canceled")
if (ctx.canceled()) {
logger.debug("navigation canceled")
return false
}
} catch (error) {
logger.debug(`navigation: ${(error as any).message || error}`)
return false return false
} }
} }
return true return true
} }
// We are seeing the menu closing after opening if we open it too soon and const menuSelector = '[aria-label="Application Menu"]'
// the picker getting recreated in the middle of trying to select an item. const open = async () => {
// To counter this we will keep trying to navigate through the items every await this.page.click(menuSelector)
// time we lose focus or there is an error. await this.page.waitForSelector(`${menuSelector}:not(:focus-within)`)
let attempts = 1 return false
let context = new Context()
while (!(await Promise.race([openThenWaitClose(context), navigate(context)]))) {
++attempts
logger.debug("closed, retrying (${attempt}/∞)")
context.cancel()
context = new Context()
} }
context.finish() // TODO: Starting in 1.57 something closes the menu after opening it if we
logger.debug(`navigation took ${attempts} ${plural(attempts, "attempt")}`) // open it too soon. To counter that we'll watch for when the menu loses
// focus and when/if it does we'll try again.
// I tried using the classic menu but it doesn't show up at all for some
// reason. I also tried toggle but the menu disappears after toggling.
let retryCount = 0
let cancelToken = new CancelToken()
while (!(await Promise.race([open(), navigate(cancelToken)]))) {
this.codeServer.logger.debug("menu was closed, retrying")
++retryCount
cancelToken.cancel()
cancelToken = new CancelToken()
}
this.codeServer.logger.debug(`menu navigation retries: ${retryCount}`)
} }
/** /**
* Navigate through a currently opened "quick input" widget, retrying on * Navigates to code-server then reloads until the editor is ready.
* failure. *
* It is recommended to run setup before using this model in any tests.
*/ */
async navigateQuickInput(items: string[]): Promise<void> { async setup(authenticated: boolean) {
await this.navigateItems(items, ".quick-input-widget") await this.navigate()
} // If we aren't authenticated we'll see a login page so we can't wait until
// the editor is ready.
/** if (authenticated) {
* Navigate through the menu, retrying on failure. await this.reloadUntilEditorIsReady()
*/ }
async navigateMenus(menus: string[]): Promise<void> {
await this.navigateItems(menus, '[aria-label="Application Menu"]', async (selector) => {
await this.page.click(selector)
})
}
/**
* Execute a command in the root of the instance's workspace directory.
*/
async exec(command: string): Promise<void> {
await util.promisify(cp.exec)(command, {
cwd: await this.workspaceDir,
})
}
/**
* Install an extension by ID to the instance's temporary extension
* directory.
*/
async installExtension(id: string): Promise<void> {
const dir = path.join(await this.workspaceDir, "extensions")
await util.promisify(cp.exec)(`node . --install-extension ${id} --extensions-dir ${dir}`, {
cwd: path.join(__dirname, "../../.."),
})
}
/**
* Wait for state to be flushed to the database.
*/
async stateFlush(): Promise<void> {
// If we reload too quickly VS Code will be unable to save the state changes
// so wait until those have been written to the database. It flushes every
// five seconds so we need to wait at least that long.
// TODO@asher: There must be a better way.
await this.page.waitForTimeout(5500)
} }
} }

View File

@@ -1,17 +1,12 @@
import { version } from "../../src/node/constants"
import { describe, test, expect } from "./baseFixture" import { describe, test, expect } from "./baseFixture"
describe("Open Help > About", true, [], {}, () => { describe("Open Help > About", true, () => {
test("should see code-server version in about dialog", async ({ codeServerPage }) => { test("should see code-server version in about dialog", async ({ codeServerPage }) => {
// Open using the menu. // Open using the menu.
await codeServerPage.navigateMenus(["Help", "About"]) await codeServerPage.navigateMenus(["Help", "About"])
const isDevMode = process.env.VSCODE_DEV === "1"
// Look for code-server info div. // Look for code-server info div.
const element = await codeServerPage.page.waitForSelector( const element = await codeServerPage.page.waitForSelector('div[role="dialog"] >> text=code-server')
`div[role="dialog"] >> text=code-server: ${isDevMode ? "Unknown" : "v" + version}`,
)
expect(element).not.toBeNull() expect(element).not.toBeNull()
}) })
}) })

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