fix: invoking code-server in integrated terminal (#5360)

* Include bin scripts for all platforms

These will get symlinked as part of the postinstall.  These scripts
provide everything ours does inside the integrated terminal plus more.

* Improve OS detection

Specifically for Windows although we do not yet support Windows.

Also standardize the duplicate arch functions since they had drifted
from each other bit.

* Remove duplicate asar symlink

Since standalone releases run the postinstall they will get the asar
symlink there.  That means the symlink will not exist for the npm
package and we will not need to ignore it.

The symlink portion is split out so it can be re-used for other
symlinks (for example linking bin scripts).

* Add symlinks to bin scripts

* Add test for opening a file from the terminal

* Add global Playwright timeout

Otherwise it will exceed the Actions timeout and get rudely killed
without any output.

* Make sed work on macOS

* Fix Node path in bin scripts

* Disable shellcheck expansion error

* Make scripts executable

* Remove .bak files created by sed

* Include Code build script in cache hash

Otherwise if we change the script it will not rebuild Code.

* Make sure the terminal opens

The selector was timing out even though it matched more than one element
but matching on the focused one appears to work.

In addition add a loop so it can keep trying to open the terminal
if something goes wrong with the focus.
This commit is contained in:
Asher 2022-08-04 11:03:28 -05:00 committed by GitHub
parent 0022473744
commit 9087e0c091
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 167 additions and 93 deletions

View File

@ -156,7 +156,7 @@ jobs:
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: lib/vscode-reh-web-* path: lib/vscode-reh-web-*
key: vscode-reh-package-${{ secrets.VSCODE_CACHE_VERSION }}-${{ steps.vscode-rev.outputs.rev }}-${{ steps.version.outputs.version }}-${{ hashFiles('patches/*.diff') }} key: vscode-reh-package-${{ secrets.VSCODE_CACHE_VERSION }}-${{ steps.vscode-rev.outputs.rev }}-${{ steps.version.outputs.version }}-${{ hashFiles('patches/*.diff', 'ci/build/build-vscode.sh') }}
- name: Build vscode - name: Build vscode
if: steps.cache-vscode.outputs.cache-hit != 'true' if: steps.cache-vscode.outputs.cache-hit != 'true'
@ -499,7 +499,7 @@ jobs:
./test/node_modules/.bin/playwright install ./test/node_modules/.bin/playwright install
- name: Run end-to-end tests - name: Run end-to-end tests
run: yarn test:e2e run: yarn test:e2e --global-timeout 840000
- name: Upload test artifacts - name: Upload test artifacts
if: always() if: always()

View File

@ -110,10 +110,6 @@ bundle_vscode() {
rsync "$VSCODE_SRC_PATH/extensions/package.json" "$VSCODE_OUT_PATH/extensions/package.json" rsync "$VSCODE_SRC_PATH/extensions/package.json" "$VSCODE_OUT_PATH/extensions/package.json"
rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions/yarn.lock" rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions/yarn.lock"
rsync "$VSCODE_SRC_PATH/extensions/postinstall.mjs" "$VSCODE_OUT_PATH/extensions/postinstall.mjs" rsync "$VSCODE_SRC_PATH/extensions/postinstall.mjs" "$VSCODE_OUT_PATH/extensions/postinstall.mjs"
pushd "$VSCODE_OUT_PATH"
symlink_asar
popd
} }
main "$@" main "$@"

View File

@ -27,8 +27,9 @@ main() {
ln -s "./bin/code-server" "$RELEASE_PATH/code-server" ln -s "./bin/code-server" "$RELEASE_PATH/code-server"
ln -s "./lib/node" "$RELEASE_PATH/node" ln -s "./lib/node" "$RELEASE_PATH/node"
cd "$RELEASE_PATH" pushd "$RELEASE_PATH"
yarn --production --frozen-lockfile yarn --production --frozen-lockfile
popd
} }
main "$@" main "$@"

View File

@ -6,12 +6,38 @@ set -euo pipefail
# 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}
delete-bin-script() {
rm -f "lib/vscode-reh-web-linux-x64/bin/$1"
}
copy-bin-script() {
local script="$1"
local dest="lib/vscode-reh-web-linux-x64/bin/$script"
cp "lib/vscode/resources/server/bin/$script" "$dest"
sed -i.bak "s/@@VERSION@@/$(vscode_version)/g" "$dest"
sed -i.bak "s/@@COMMIT@@/$VSCODE_DISTRO_COMMIT/g" "$dest"
sed -i.bak "s/@@APPNAME@@/code-server/g" "$dest"
# Fix Node path on Darwin and Linux.
# We do not want expansion here; this text should make it to the file as-is.
# shellcheck disable=SC2016
sed -i.bak 's/^ROOT=\(.*\)$/VSROOT=\1\nROOT="$(dirname "$(dirname "$VSROOT")")"/g' "$dest"
sed -i.bak 's/ROOT\/out/VSROOT\/out/g' "$dest"
# Fix Node path on Windows.
sed -i.bak 's/^set ROOT_DIR=\(.*\)$/set ROOT_DIR=%~dp0..\\..\\..\\..\r\nset VSROOT_DIR=\1/g' "$dest"
sed -i.bak 's/%ROOT_DIR%\\out/%VSROOT_DIR%\\out/g' "$dest"
chmod +x "$dest"
rm "$dest.bak"
}
main() { main() {
cd "$(dirname "${0}")/../.." cd "$(dirname "${0}")/../.."
source ./ci/lib.sh source ./ci/lib.sh
cd lib/vscode pushd lib/vscode
# Set the commit Code will embed into the product.json. We need to do this # Set the commit Code will embed into the product.json. We need to do this
# since Code tries to get the commit from the `.git` directory which will fail # since Code tries to get the commit from the `.git` directory which will fail
@ -58,13 +84,31 @@ main() {
EOF EOF
) > product.json ) > product.json
# Any platform works since we have our own packaging step (for now). # Any platform here works since we will do our own packaging. We have to do
# this because we have an NPM package that could be installed on any platform.
# The correct platform dependencies and scripts will be installed as part of
# the post-install during `npm install` or when building a standalone release.
yarn gulp "vscode-reh-web-linux-x64${MINIFY:+-min}" yarn gulp "vscode-reh-web-linux-x64${MINIFY:+-min}"
# Reset so if you develop after building you will not be stuck with the wrong # Reset so if you develop after building you will not be stuck with the wrong
# commit (the dev client will use `oss-dev` but the dev server will still use # commit (the dev client will use `oss-dev` but the dev server will still use
# product.json which will have `stable-$commit`). # product.json which will have `stable-$commit`).
git checkout product.json git checkout product.json
popd
# These provide a `code-server` command in the integrated terminal to open
# files in the current instance.
delete-bin-script remote-cli/code-server
copy-bin-script remote-cli/code-darwin.sh
copy-bin-script remote-cli/code-linux.sh
copy-bin-script remote-cli/code.cmd
# These provide a way for terminal applications to open browser windows.
delete-bin-script helpers/browser.sh
copy-bin-script helpers/browser-darwin.sh
copy-bin-script helpers/browser-linux.sh
copy-bin-script helpers/browser.cmd
} }
main "$@" main "$@"

View File

@ -1,23 +1,69 @@
#!/usr/bin/env sh #!/usr/bin/env sh
set -eu set -eu
# Copied from arch() in ci/lib.sh. # Copied from ../lib.sh.
detect_arch() { arch() {
case "$(uname -m)" in cpu="$(uname -m)"
aarch64) case "$cpu" in
echo arm64 aarch64) cpu=arm64 ;;
;; x86_64) cpu=amd64 ;;
x86_64 | amd64) esac
echo amd64 echo "$cpu"
;; }
*)
# This will cause the download to fail, but is intentional # Copied from ../lib.sh except we do not rename Darwin since the cloud agent
uname -m # uses "darwin" in the release names and we do not need to detect Alpine.
;; os() {
osname=$(uname | tr '[:upper:]' '[:lower:]')
case $osname in
cygwin* | mingw*) osname="windows" ;;
esac
echo "$osname"
}
# Create a symlink at $2 pointing to $1 on any platform. Anything that
# currently exists at $2 will be deleted.
symlink() {
source="$1"
dest="$2"
rm -rf "$dest"
case $OS in
windows) mklink /J "$dest" "$source" ;;
*) ln -s "$source" "$dest" ;;
esac esac
} }
ARCH="${NPM_CONFIG_ARCH:-$(detect_arch)}" # VS Code bundles some modules into an asar which is an archive format that
# works like tar. It then seems to get unpacked into node_modules.asar.
#
# I don't know why they do this but all the dependencies they bundle already
# exist in node_modules so just symlink it. We have to do this since not only
# Code itself but also extensions will look specifically in this directory for
# files (like the ripgrep binary or the oniguruma wasm).
symlink_asar() {
symlink node_modules node_modules.asar
}
# Make a symlink at bin/$1/$3 pointing to the platform-specific version of the
# script in $2. The extension of the link will be .cmd for Windows otherwise it
# will be whatever is in $4 (or no extension if $4 is not set).
symlink_bin_script() {
oldpwd="$(pwd)"
cd "bin/$1"
source="$2"
dest="$3"
ext="${4-}"
case $OS in
windows) symlink "$source.cmd" "$dest.cmd" ;;
darwin | macos) symlink "$source-darwin.sh" "$dest$ext" ;;
*) symlink "$source-linux.sh" "$dest$ext" ;;
esac
cd "$oldpwd"
}
ARCH="${NPM_CONFIG_ARCH:-$(arch)}"
OS="$(os)"
# This is due to an upstream issue with RHEL7/CentOS 7 comptability with node-argon2 # 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 # See: https://github.com/cdr/code-server/pull/3422#pullrequestreview-677765057
export npm_config_build_from_source=true export npm_config_build_from_source=true
@ -56,8 +102,6 @@ main() {
;; ;;
esac esac
OS="$(uname | tr '[:upper:]' '[:lower:]')"
mkdir -p ./lib mkdir -p ./lib
if curl -fsSL "https://github.com/coder/cloud-agent/releases/latest/download/cloud-agent-$OS-$ARCH" -o ./lib/coder-cloud-agent; then if curl -fsSL "https://github.com/coder/cloud-agent/releases/latest/download/cloud-agent-$OS-$ARCH" -o ./lib/coder-cloud-agent; then
@ -79,22 +123,14 @@ main() {
fi fi
} }
# This is a copy of symlink_asar in ../lib.sh. Look there for details.
symlink_asar() {
rm -rf node_modules.asar
if [ "${WINDIR-}" ]; then
mklink /J node_modules.asar node_modules
else
ln -s node_modules node_modules.asar
fi
}
vscode_yarn() { vscode_yarn() {
echo 'Installing Code dependencies...' echo 'Installing Code dependencies...'
cd lib/vscode cd lib/vscode
yarn --production --frozen-lockfile --no-default-rc yarn --production --frozen-lockfile --no-default-rc
symlink_asar symlink_asar
symlink_bin_script remote-cli code code-server
symlink_bin_script helpers browser browser .sh
cd extensions cd extensions
yarn --production --frozen-lockfile yarn --production --frozen-lockfile

View File

@ -18,35 +18,30 @@ vscode_version() {
} }
os() { os() {
local os osname=$(uname | tr '[:upper:]' '[:lower:]')
os=$(uname | tr '[:upper:]' '[:lower:]') case $osname in
if [[ $os == "linux" ]]; then linux)
# Alpine's ldd doesn't have a version flag but if you use an invalid flag # Alpine's ldd doesn't have a version flag but if you use an invalid flag
# (like --version) it outputs the version to stderr and exits with 1. # (like --version) it outputs the version to stderr and exits with 1.
local ldd_output # TODO: Better to check /etc/os-release; see ../install.sh.
ldd_output=$(ldd --version 2>&1 || true) ldd_output=$(ldd --version 2>&1 || true)
if echo "$ldd_output" | grep -iq musl; then if echo "$ldd_output" | grep -iq musl; then
os="alpine" osname="alpine"
fi fi
elif [[ $os == "darwin" ]]; then ;;
os="macos" darwin) osname="macos" ;;
fi cygwin* | mingw*) osname="windows" ;;
echo "$os" esac
echo "$osname"
} }
arch() { arch() {
cpu="$(uname -m)" cpu="$(uname -m)"
case "$cpu" in case "$cpu" in
aarch64) aarch64) cpu=arm64 ;;
echo arm64 x86_64) cpu=amd64 ;;
;;
x86_64 | amd64)
echo amd64
;;
*)
echo "$cpu"
;;
esac esac
echo "$cpu"
} }
# Grabs the most recent ci.yaml github workflow run that was triggered from the # Grabs the most recent ci.yaml github workflow run that was triggered from the
@ -104,21 +99,3 @@ export OS
# RELEASE_PATH is the destination directory for the release from the root. # RELEASE_PATH is the destination directory for the release from the root.
# Defaults to release # Defaults to release
RELEASE_PATH="${RELEASE_PATH-release}" RELEASE_PATH="${RELEASE_PATH-release}"
# VS Code bundles some modules into an asar which is an archive format that
# works like tar. It then seems to get unpacked into node_modules.asar.
#
# I don't know why they do this but all the dependencies they bundle already
# exist in node_modules so just symlink it. We have to do this since not only VS
# Code itself but also extensions will look specifically in this directory for
# files (like the ripgrep binary or the oniguruma wasm).
symlink_asar() {
rm -rf node_modules.asar
if [ "${WINDIR-}" ]; then
# mklink takes the link name first.
mklink /J node_modules.asar node_modules
else
# ln takes the link name second.
ln -s node_modules node_modules.asar
fi
}

View File

@ -81,10 +81,6 @@ main() {
# 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
# Ignore symlink when publishing npm package
# See: https://github.com/coder/code-server/pull/3935
echo "node_modules.asar" > release/.npmignore
# We use this to set the name of the package in the # We use this to set the name of the package in the
# package.json # package.json
PACKAGE_NAME="code-server" PACKAGE_NAME="code-server"

View File

@ -283,19 +283,31 @@ export class CodeServerPage {
} }
/** /**
* Focuses Integrated Terminal * Focuses the integrated terminal by navigating through the command palette.
* by using "Terminal: Focus Terminal"
* from the Command Palette
* *
* This should focus the terminal no matter * This should focus the terminal no matter if it already has focus and/or is
* if it already has focus and/or is or isn't * or isn't visible already. It will always create a new terminal to avoid
* visible already. * clobbering parallel tests.
*/ */
async focusTerminal() { async focusTerminal() {
await this.executeCommandViaMenus("Terminal: Focus Terminal") const doFocus = async (): Promise<boolean> => {
await this.executeCommandViaMenus("Terminal: Create New Terminal")
try {
await this.page.waitForLoadState("load")
await this.page.waitForSelector("textarea.xterm-helper-textarea:focus-within", { timeout: 5000 })
return true
} catch (error) {
return false
}
}
// Wait for terminal textarea to show up let attempts = 1
await this.page.waitForSelector("textarea.xterm-helper-textarea") while (!(await doFocus())) {
++attempts
this.codeServer.logger.debug(`no focused terminal textarea, retrying (${attempts}/∞)`)
}
this.codeServer.logger.debug(`opening terminal took ${attempts} ${plural(attempts, "attempt")}`)
} }
/** /**
@ -423,7 +435,7 @@ export class CodeServerPage {
let context = new Context() let context = new Context()
while (!(await Promise.race([openThenWaitClose(context), navigate(context)]))) { while (!(await Promise.race([openThenWaitClose(context), navigate(context)]))) {
++attempts ++attempts
logger.debug("closed, retrying (${attempt}/∞)") logger.debug(`closed, retrying (${attempts}/∞)`)
context.cancel() context.cancel()
context = new Context() context = new Context()
} }

View File

@ -1,4 +1,5 @@
import * as cp from "child_process" import * as cp from "child_process"
import { promises as fs } from "fs"
import * as path from "path" import * as path from "path"
import util from "util" import util from "util"
import { clean, tmpdir } from "../utils/helpers" import { clean, tmpdir } from "../utils/helpers"
@ -21,13 +22,24 @@ describe("Integrated Terminal", true, [], {}, () => {
// Open terminal and type in value // Open terminal and type in value
await codeServerPage.focusTerminal() await codeServerPage.focusTerminal()
await codeServerPage.page.waitForLoadState("load")
await codeServerPage.page.keyboard.type(`printenv VSCODE_PROXY_URI > ${tmpFile}`) await codeServerPage.page.keyboard.type(`printenv VSCODE_PROXY_URI > ${tmpFile}`)
await codeServerPage.page.keyboard.press("Enter") await codeServerPage.page.keyboard.press("Enter")
// It may take a second to process
await codeServerPage.page.waitForTimeout(1000)
const { stdout } = await output const { stdout } = await output
expect(stdout).toMatch(await codeServerPage.address()) expect(stdout).toMatch(await codeServerPage.address())
}) })
test("should be able to invoke `code-server` to open a file", async ({ codeServerPage }) => {
const tmpFolderPath = await tmpdir(testName)
const tmpFile = path.join(tmpFolderPath, "test-file")
await fs.writeFile(tmpFile, "test")
const fileName = path.basename(tmpFile)
await codeServerPage.focusTerminal()
await codeServerPage.page.keyboard.type(`code-server ${tmpFile}`)
await codeServerPage.page.keyboard.press("Enter")
await codeServerPage.waitForTab(fileName)
})
}) })