From 7267da65b7ae383cedbfd819ae12d5bbcd1f96a6 Mon Sep 17 00:00:00 2001 From: emaryn Date: Sat, 22 Mar 2025 17:53:01 +0800 Subject: [PATCH 1/4] vscode-extensions-update: init --- .../vs/vscode-extensions-update/package.nix | 5 + .../vscode_extensions_update.py | 323 ++++++++++++++++++ 2 files changed, 328 insertions(+) create mode 100644 pkgs/by-name/vs/vscode-extensions-update/package.nix create mode 100755 pkgs/by-name/vs/vscode-extensions-update/vscode_extensions_update.py diff --git a/pkgs/by-name/vs/vscode-extensions-update/package.nix b/pkgs/by-name/vs/vscode-extensions-update/package.nix new file mode 100644 index 000000000000..80c333ddf23d --- /dev/null +++ b/pkgs/by-name/vs/vscode-extensions-update/package.nix @@ -0,0 +1,5 @@ +{ + writeScriptBin, +}: + +writeScriptBin "vscode-extensions-update" (builtins.readFile ./vscode_extensions_update.py) diff --git a/pkgs/by-name/vs/vscode-extensions-update/vscode_extensions_update.py b/pkgs/by-name/vs/vscode-extensions-update/vscode_extensions_update.py new file mode 100755 index 000000000000..3819a9df6d67 --- /dev/null +++ b/pkgs/by-name/vs/vscode-extensions-update/vscode_extensions_update.py @@ -0,0 +1,323 @@ +#! /usr/bin/env nix-shell +#! nix-shell -i python3 -p nix python3 nodePackages.semver vsce nix-update gitMinimal + +import argparse +import json +import os +import subprocess +import sys + + +class VSCodeExtensionUpdater: + """ + A class to update VSCode extension version in nixpkgs. + """ + + def __init__(self): + self.parser = argparse.ArgumentParser( + description="Update VSCode extension version in nixpkgs." + ) + self.parser.add_argument( + "attribute", + nargs="?", + default=os.getenv("UPDATE_NIX_ATTR_PATH"), + help="nix attribute path of the extension", + ) + self.parser.add_argument( + "--override-filename", help="override-filename for nix-update" + ) + self.parser.add_argument( + "--pre-release", + action="store_true", + help="allow check pre-release versions", + ) + self.parser.add_argument( + "--platforms", + action="store_true", + help="set system according to meta.platforms for nix-update", + ) + self.parser.add_argument( + "--commit", action="store_true", help="commit the updated package" + ) + self.args = self.parser.parse_args() + self.attribute_path = self.args.attribute + if not self.attribute_path: + print("Error: Attribute path is required.", file=sys.stderr) + sys.exit(1) + self.target_vscode_version = self._get_nix_vscode_version() + self.current_version = self._get_nix_vscode_extension_version() + self.override_filename = self.args.override_filename + self.allow_pre_release = self.args.pre_release + self.commit = self.args.commit + self.extension_publisher = self._get_nix_vscode_extension_publisher() + self.extension_name = self._get_nix_vscode_extension_name() + self.extension_marketplace_id = ( + f"{self.extension_publisher}.{self.extension_name}" + ) + self.nix_system = self.get_nix_system() + self.nix_systems = self._get_nix_vscode_extension_platforms() or [ + self.nix_system + ] + if not self.args.platforms and self.nix_system in self.nix_systems: + self.nix_systems = [self.nix_system] + else: + self.nix_systems = self.nix_systems[:1] + print(f"VSCode version: {self.target_vscode_version}") + print(f"Extension Marketplace ID: {self.extension_marketplace_id}") + print(f"Extension Current Version: {self.current_version}") + + def execute_command( + self, commandline: list[str], env: dict[str, str] = None + ) -> str: + """ + Executes a shell command and returns its output. + """ + return subprocess.run( + commandline, + check=True, + capture_output=True, + text=True, + env=env, + ).stdout.strip() + + def _get_nix_attribute(self, attribute_path: str) -> str: + """ + Retrieves a raw Nix attribute value. + """ + return self.execute_command(["nix", "eval", "--raw", "-f", ".", attribute_path]) + + def get_nix_system(self) -> str: + """ + Retrieves system from Nix. + """ + return self._get_nix_attribute("system") + + def get_target_platform(self, nix_system: str) -> str: + """ + Retrieves the VS Code targetPlatform variable based on the Nix system. + """ + platform_mapping = { + "x86_64-linux": "linux-x64", + "aarch64-linux": "linux-arm64", + "armv7l-linux": "linux-armhf", + "x86_64-darwin": "darwin-x64", + "aarch64-darwin": "darwin-arm64", + "x86_64-windows": "win32-x64", + "aarch64-windows": "win32-arm64", + } + try: + return platform_mapping[nix_system] + except KeyError: + print( + f"Error: Unknown Nix system '{nix_system}'. Cannot determine targetPlatform.", + file=sys.stderr, + ) + sys.exit(1) + + def _get_nix_vscode_version(self) -> str: + """ + Retrieves the current VSCode version from Nix. + """ + return self._get_nix_attribute("vscode.version") + + def _get_nix_vscode_extension_version(self) -> str: + """ + Retrieves the extension current version from Nix. + """ + return os.getenv("UPDATE_NIX_OLD_VERSION") or self._get_nix_attribute( + f"{self.attribute_path}.version" + ) + + def _get_nix_vscode_extension_platforms(self) -> list[str]: + """ + Retrieves the extension meta.platforms from Nix. + """ + try: + return json.loads( + self.execute_command( + [ + "nix", + "eval", + "--json", + "-f", + ".", + f"{self.attribute_path}.meta.platforms", + ] + ) + ) + except subprocess.CalledProcessError: + return [] + + def _get_nix_vscode_extension_publisher(self) -> str: + """ + Retrieves the extension publisher from Nix. + """ + return self._get_nix_attribute(f"{self.attribute_path}.vscodeExtPublisher") + + def _get_nix_vscode_extension_name(self) -> str: + """ + Retrieves the extension name from Nix. + """ + return self._get_nix_attribute(f"{self.attribute_path}.vscodeExtName") + + def get_marketplace_extension_data(self, extension_id: str) -> dict: + """ + Retrieves extension data from the VSCode Marketplace using vsce. + """ + command = ["vsce", "show", extension_id, "--json"] + try: + with subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) as proc: + data = json.load(proc.stdout) + proc.wait() + if proc.returncode != 0: + err = proc.stderr.read() + raise subprocess.CalledProcessError( + proc.returncode, command, output=err + ) + return data + except json.JSONDecodeError: + print( + "Error: Couldn’t decode JSON response from VSCode marketplace; maybe the extension is not available?", + file=sys.stderr, + ) + sys.exit(1) + + def find_compatible_extension_version( + self, extension_versions: list, target_platform: str + ) -> str: + """ + Finds the first compatible extension version based on Target Platform and VSCode compatibility. + """ + for version_info in extension_versions: + candidate_platform = version_info.get("targetPlatform", None) + if candidate_platform is not None and candidate_platform != target_platform: + continue + candidate_version = version_info.get("version") + candidate_pre_release = next( + ( + prop.get("value") + for prop in version_info.get("properties", []) + if prop.get("key") == "Microsoft.VisualStudio.Code.PreRelease" + ), + None, + ) + if candidate_pre_release and not self.allow_pre_release: + print(f"Skipping PreRelease version {candidate_version}") + continue + engine_version_constraint = next( + ( + prop.get("value") + for prop in version_info.get("properties", []) + if prop.get("key") == "Microsoft.VisualStudio.Code.Engine" + ), + None, + ) + if engine_version_constraint: + print( + f"Testing extension version: {candidate_version} with VSCode {self.target_vscode_version} (constraint: {engine_version_constraint})" + ) + try: + self.execute_command( + [ + "semver", + self.target_vscode_version, + "-r", + engine_version_constraint, + ] + ) + + print(f"Compatible version found: {candidate_version}") + return candidate_version + except (ValueError, subprocess.CalledProcessError): + print( + f"Version {candidate_version} is not compatible with VSCode {self.target_vscode_version} (constraint: {engine_version_constraint})." + ) + continue + return candidate_version + else: + print( + "Error: not found compatible version.", + file=sys.stderr, + ) + sys.exit(1) + + def run_nix_update(self, new_version: str, system: str) -> None: + """ + Builds and executes the nix-update command. + """ + if not self.override_filename: + self.override_filename = self.execute_command( + [ + "nix", + "edit", + "--extra-experimental-features", + "nix-command", + "-f", + ".", + self.attribute_path, + ], + env={**os.environ, "EDITOR": "echo"}, + ) + if ( + "pkgs/applications/editors/vscode/extensions/vscode-utils.nix" + in self.override_filename + ) and os.path.exists( + "pkgs/applications/editors/vscode/extensions/default.nix" + ): + self.override_filename = ( + "pkgs/applications/editors/vscode/extensions/default.nix" + ) + update_command = [ + "nix-update", + self.attribute_path, + "--version", + new_version, + "--override-filename", + self.override_filename, + "--system", + system, + ] + self.execute_command(update_command) + + def run(self): + marketplace_data = self.get_marketplace_extension_data( + self.extension_marketplace_id + ) + available_versions = marketplace_data.get("versions", []) + print( + f"Total versions found for {self.extension_marketplace_id}: {len(available_versions)}" + ) + self.new_version = self.find_compatible_extension_version( + available_versions, self.get_target_platform(self.nix_systems[0]) + ) + try: + self.execute_command( + ["semver", self.current_version, "-r", f"<{self.new_version}"] + ) + except subprocess.CalledProcessError: + print("Already up to date or new version is older!") + sys.exit(0) + print(f"Selected extension version from Marketplace: {self.new_version}") + for i, system in enumerate(self.nix_systems): + version = self.new_version if i == 0 else "skip" + self.run_nix_update(version, system) + if self.commit: + self.execute_command(["git", "add", self.override_filename]) + self.execute_command( + [ + "git", + "commit", + "-m", + f"{self.attribute_path}: {self.current_version} -> {self.new_version}", + ] + ) + + +if __name__ == "__main__": + updater = VSCodeExtensionUpdater() + updater.run() From 4f1a07bad811fa2c4a3c17eb57252ad22683acf2 Mon Sep 17 00:00:00 2001 From: emaryn Date: Sat, 22 Mar 2025 17:53:39 +0800 Subject: [PATCH 2/4] vscode-extensions-update-script: init --- .../vscode-extensions-update-script.nix | 13 +++++++++++++ pkgs/top-level/all-packages.nix | 3 +++ 2 files changed, 16 insertions(+) create mode 100644 pkgs/by-name/vs/vscode-extensions-update/vscode-extensions-update-script.nix diff --git a/pkgs/by-name/vs/vscode-extensions-update/vscode-extensions-update-script.nix b/pkgs/by-name/vs/vscode-extensions-update/vscode-extensions-update-script.nix new file mode 100644 index 000000000000..af432feb58e4 --- /dev/null +++ b/pkgs/by-name/vs/vscode-extensions-update/vscode-extensions-update-script.nix @@ -0,0 +1,13 @@ +{ + lib, + vscode-extensions-update, +}: + +{ + attrPath ? null, + extraArgs ? [ ], +}: + +[ "${lib.getExe vscode-extensions-update}" ] +++ lib.optionals (attrPath != null) [ attrPath ] +++ extraArgs diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 174a802f54fc..77ab7b0cbe17 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -15144,6 +15144,9 @@ with pkgs; vscode-extensions = recurseIntoAttrs (callPackage ../applications/editors/vscode/extensions { }); + + vscode-extensions-update-script = callPackage ../by-name/vs/vscode-extensions-update/vscode-extensions-update-script.nix { }; + vscode-js-debug = callPackage ../by-name/vs/vscode-js-debug/package.nix { inherit (darwin.apple_sdk.frameworks) AppKit Security; }; From 762fcb12a961d8bdbc0e8417a4cf9fb3ae52bc02 Mon Sep 17 00:00:00 2001 From: emaryn Date: Tue, 25 Mar 2025 18:51:11 +0000 Subject: [PATCH 3/4] vscode-extensions.ms-python.python: use vscode-extensions-update-script --- .../extensions/ms-python.python/default.nix | 38 +------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/pkgs/applications/editors/vscode/extensions/ms-python.python/default.nix b/pkgs/applications/editors/vscode/extensions/ms-python.python/default.nix index df96b37116c9..4904c2494e70 100644 --- a/pkgs/applications/editors/vscode/extensions/ms-python.python/default.nix +++ b/pkgs/applications/editors/vscode/extensions/ms-python.python/default.nix @@ -8,13 +8,7 @@ # Defaults to `false` as we expect it to be project specific most of the time. pythonUseFixed ? false, # For updateScript - writeScript, - bash, - curl, - coreutils, - gnused, - jq, - nix, + vscode-extensions-update-script, }: vscode-utils.buildVscodeMarketplaceExtension rec { @@ -51,35 +45,7 @@ vscode-utils.buildVscodeMarketplaceExtension rec { --replace-fail "\"default\":\"python\"" "\"default\":\"${python3.interpreter}\"" ''; - passthru.updateScript = writeScript "update" '' - #! ${bash}/bin/bash - - set -eu -o pipefail - - export PATH=${ - lib.makeBinPath [ - curl - coreutils - gnused - jq - nix - ] - } - - api=$(curl -s 'https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery' \ - -H 'accept: application/json;api-version=3.0-preview.1' \ - -H 'content-type: application/json' \ - --data-raw '{"filters":[{"criteria":[{"filterType":7,"value":"${mktplcRef.publisher}.${mktplcRef.name}"}]}],"flags":16}') - # Find the latest version compatible with stable vscode version - version=$(echo $api | jq -r '.results[0].extensions[0].versions | map(select(has("properties"))) | map(select(.properties | map(select(.key == "Microsoft.VisualStudio.Code.Engine")) | .[0].value | test("\\^[0-9.]+$"))) | .[0].version') - - if [[ $version != ${mktplcRef.version} ]]; then - tmp=$(mktemp) - curl -sLo $tmp $(echo ${(import ../mktplcExtRefToFetchArgs.nix mktplcRef).url} | sed "s|${mktplcRef.version}|$version|") - hash=$(nix hash file --type sha256 --base32 --sri $tmp) - sed -i -e "s|${mktplcRef.hash}|$hash|" -e "s|${mktplcRef.version}|$version|" pkgs/applications/editors/vscode/extensions/ms-python.python/default.nix - fi - ''; + passthru.updateScript = vscode-extensions-update-script { }; meta = { description = "Visual Studio Code extension with rich support for the Python language"; From 4f9510e91dd23a75c1932421aa38a2f55fc4be69 Mon Sep 17 00:00:00 2001 From: emaryn Date: Tue, 25 Mar 2025 20:11:31 +0000 Subject: [PATCH 4/4] vscode-extensions.rooveterinaryinc.roo-cline: use vscode-extensions-update-script --- .../vscode/extensions/rooveterinaryinc.roo-cline/default.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgs/applications/editors/vscode/extensions/rooveterinaryinc.roo-cline/default.nix b/pkgs/applications/editors/vscode/extensions/rooveterinaryinc.roo-cline/default.nix index 17fbe6c2a248..47f844424189 100644 --- a/pkgs/applications/editors/vscode/extensions/rooveterinaryinc.roo-cline/default.nix +++ b/pkgs/applications/editors/vscode/extensions/rooveterinaryinc.roo-cline/default.nix @@ -1,6 +1,7 @@ { lib, vscode-utils, + vscode-extensions-update-script, }: vscode-utils.buildVscodeMarketplaceExtension { @@ -11,6 +12,8 @@ vscode-utils.buildVscodeMarketplaceExtension { hash = "sha256-7A8BQHUu7CYA28fHv68Zvf6zhlJwwXZaNVLb+/cBAIg="; }; + passthru.updateScript = vscode-extensions-update-script { }; + meta = { description = "AI-powered autonomous coding agent that lives in your editor"; downloadPage = "https://marketplace.visualstudio.com/items?itemName=RooVeterinaryInc.roo-cline";