diff --git a/ci/build/add-vscode-extension-dependencies.sh b/ci/build/add-vscode-extension-dependencies.sh new file mode 100755 index 000000000000..775687e2be67 --- /dev/null +++ b/ci/build/add-vscode-extension-dependencies.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +main() { + if [[ $# -ne 2 ]]; then + echo "Usage: $0 " >&2 + exit 1 + fi + + local package_json="$1" + local vscode_extensions_src_path="$2" + + # VS Code's optimizer keeps a runtime require("@vscode/fs-copyfile") in the + # built git extension but strips dependencies from the built extension + # package.json. code-server's npm postinstall installs from lib/vscode/extensions, + # so hoist this runtime dependency into the shared extension manifest. + add_dependency_from_extension_package \ + "$package_json" \ + "$vscode_extensions_src_path/git/package.json" \ + "@vscode/fs-copyfile" +} + +add_dependency_from_extension_package() { + local package_json="$1" + local source_package_json="$2" + local dependency="$3" + + if [[ ! -f $package_json ]]; then + echo "Missing package manifest: $package_json" >&2 + exit 1 + fi + + if [[ ! -f $source_package_json ]]; then + echo "Missing source extension manifest: $source_package_json" >&2 + exit 1 + fi + + local version + version="$(jq -r --arg dependency "$dependency" '.dependencies[$dependency] // empty' "$source_package_json")" + if [[ -z $version ]]; then + echo "Expected $dependency in $source_package_json dependencies" >&2 + exit 1 + fi + + local package_json_tmp + package_json_tmp="$(mktemp)" + cp -p "$package_json" "$package_json_tmp" + jq \ + --arg dependency "$dependency" \ + --arg version "$version" \ + '.dependencies = (.dependencies // {}) | .dependencies[$dependency] = $version' \ + "$package_json" > "$package_json_tmp" + mv "$package_json_tmp" "$package_json" +} + +main "$@" diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh index 9ded35f98ccb..68c8692a8a08 100755 --- a/ci/build/build-release.sh +++ b/ci/build/build-release.sh @@ -136,6 +136,7 @@ bundle_vscode() { # Include global extension dependencies as well. rsync "$VSCODE_SRC_PATH/extensions/package.json" "$VSCODE_OUT_PATH/extensions/package.json" + ./ci/build/add-vscode-extension-dependencies.sh "$VSCODE_OUT_PATH/extensions/package.json" "$VSCODE_SRC_PATH/extensions" cp "$VSCODE_SRC_PATH/extensions/npm-shrinkwrap.json" "$VSCODE_OUT_PATH/extensions/npm-shrinkwrap.json" rsync "$VSCODE_SRC_PATH/extensions/postinstall.mjs" "$VSCODE_OUT_PATH/extensions/postinstall.mjs" } @@ -165,11 +166,22 @@ create_shrinkwraps() { mv package-lock.json.temp package-lock.json popd - pushd "$VSCODE_SRC_PATH/extensions/" - cp package-lock.json package-lock.json.temp + # Generate the extension shrinkwrap from a temporary manifest so we can add + # code-server-specific hoisted runtime dependencies without mutating the VS Code + # submodule package.json or package-lock.json. + local extensions_shrinkwrap_tmp + extensions_shrinkwrap_tmp="$(mktemp -d)" + cp "$VSCODE_SRC_PATH/extensions/package.json" "$extensions_shrinkwrap_tmp/package.json" + cp "$VSCODE_SRC_PATH/extensions/package-lock.json" "$extensions_shrinkwrap_tmp/package-lock.json" + ./ci/build/add-vscode-extension-dependencies.sh "$extensions_shrinkwrap_tmp/package.json" "$VSCODE_SRC_PATH/extensions" + + pushd "$extensions_shrinkwrap_tmp" + npm install --package-lock-only --ignore-scripts npm shrinkwrap - mv package-lock.json.temp package-lock.json popd + + cp "$extensions_shrinkwrap_tmp/npm-shrinkwrap.json" "$VSCODE_SRC_PATH/extensions/npm-shrinkwrap.json" + rm -rf "$extensions_shrinkwrap_tmp" } main "$@" diff --git a/test/scripts/build-release.bats b/test/scripts/build-release.bats new file mode 100644 index 000000000000..f13faa82b3b0 --- /dev/null +++ b/test/scripts/build-release.bats @@ -0,0 +1,61 @@ +#!/usr/bin/env bats + +SCRIPT="$BATS_TEST_DIRNAME/../../ci/build/add-vscode-extension-dependencies.sh" + +@test "add-vscode-extension-dependencies.sh: hoists git fs-copyfile runtime dependency" { + fixture="$BATS_TEST_TMPDIR/extensions" + mkdir -p "$fixture/git" + + cat > "$fixture/package.json" <<'JSON' +{ + "name": "vscode-extensions", + "version": "0.0.1", + "dependencies": { + "typescript": "^6.0.3" + } +} +JSON + + cat > "$fixture/git/package.json" <<'JSON' +{ + "name": "git", + "version": "10.0.0", + "dependencies": { + "@vscode/fs-copyfile": "2.0.0", + "byline": "^5.0.0" + } +} +JSON + + run "$SCRIPT" "$fixture/package.json" "$fixture" + + [ "$status" -eq 0 ] + [ "$(jq -r '.dependencies["@vscode/fs-copyfile"]' "$fixture/package.json")" = "2.0.0" ] + [ "$(jq -r '.dependencies.typescript' "$fixture/package.json")" = "^6.0.3" ] + [ "$(jq -r '.dependencies.byline // empty' "$fixture/package.json")" = "" ] +} + +@test "add-vscode-extension-dependencies.sh: fails when VS Code git manifest drops fs-copyfile" { + fixture="$BATS_TEST_TMPDIR/extensions" + mkdir -p "$fixture/git" + + cat > "$fixture/package.json" <<'JSON' +{ + "name": "vscode-extensions", + "version": "0.0.1" +} +JSON + + cat > "$fixture/git/package.json" <<'JSON' +{ + "name": "git", + "version": "10.0.0", + "dependencies": {} +} +JSON + + run "$SCRIPT" "$fixture/package.json" "$fixture" + + [ "$status" -eq 1 ] + [[ "$output" = *"Expected @vscode/fs-copyfile"* ]] +}