Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pyright for openSUSE:Factory checked in at 2026-08-29 17:40:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyright (Old) and /work/SRC/openSUSE:Factory/.python-pyright.new.1265 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyright" Sat Aug 29 17:40:40 2026 rev:4 rq:1374470 version:1.1.411 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyright/python-pyright.changes 2026-08-13 13:17:00.596515374 +0200 +++ /work/SRC/openSUSE:Factory/.python-pyright.new.1265/python-pyright.changes 2026-08-29 17:41:55.777131950 +0200 @@ -1,0 +2,10 @@ +Fri Aug 28 16:11:31 UTC 2026 - Matej Cepl <[email protected]> + +- Vendor the pyright npm payload with obs-service-node_modules: + * build and run without network access + * use the system Node.js interpreter and remove nodeenv downloads + * omit the unneeded macOS-only fsevents binary +- Use libalternatives unconditionally. +- Fix env-script-interpreter rpmlint errors on js files in pyright/dist. + +------------------------------------------------------------------- New: ---- _service generate-package-json.py node_modules.obscpio node_modules.spec.inc package-lock.json package.json ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyright.spec ++++++ --- /var/tmp/diff_new_pack.1kmEEJ/_old 2026-08-29 17:41:56.917172184 +0200 +++ /var/tmp/diff_new_pack.1kmEEJ/_new 2026-08-29 17:41:56.919172255 +0200 @@ -16,11 +16,8 @@ # -%if 0%{?suse_version} > 1500 -%bcond_without libalternatives -%else -%bcond_with libalternatives -%endif +%global __nodejs_provides %{nil} +%global __nodejs_requires %{nil} %{?sle15_python_module_pythons} Name: python-pyright Version: 1.1.411 @@ -28,32 +25,30 @@ Summary: Command line wrapper for pyright License: MIT URL: https://github.com/RobertCraigie/pyright-python -Source: https://github.com/RobertCraigie/pyright-python/archive/refs/tags/v%{version}.tar.gz#/pyright-%{version}.tar.gz -# PATCH-FIX-UPSTREAM system-node.patch bugno [email protected] -# make package use system Nodejs +Source0: https://github.com/RobertCraigie/pyright-python/archive/refs/tags/v%{version}.tar.gz#/pyright-%{version}.tar.gz +Source1: generate-package-json.py +Source10: package.json +Source11: package-lock.json +Source12: node_modules.spec.inc +# PATCH-FIX-OPENSUSE system-node.patch -- Use system Node.js and the vendored npm payload Patch0: system-node.patch BuildRequires: %{python_module pip} BuildRequires: %{python_module pytest-subprocess} BuildRequires: %{python_module pytest} BuildRequires: %{python_module setuptools} BuildRequires: %{python_module wheel} +BuildRequires: alts BuildRequires: fdupes +BuildRequires: local-npm-registry BuildRequires: python-rpm-macros -# Requires: python-nodeenv >= 1.6.0 -Requires: nodejs +Requires: alts +Requires: nodejs >= 14.0.0 Requires: nodejs-common BuildArch: noarch +%include %{_sourcedir}/node_modules.spec.inc # SECTION test requirements -BuildRequires: %{python_module nodeenv >= 1.6.0} -BuildRequires: %{python_module typing_extensions} +BuildRequires: %{python_module typing_extensions >= 4.1} # /SECTION -%if %{with libalternatives} -BuildRequires: alts -Requires: alts -%else -Requires(post): update-alternatives -Requires(postun): update-alternatives -%endif %python_subpackages %description @@ -61,6 +56,17 @@ %prep %autosetup -p1 -n pyright-python-%{version} +cp -p %{SOURCE10} package.json +cp -p %{SOURCE11} package-lock.json +local-npm-registry %{_sourcedir} install --omit=dev --omit=optional --ignore-scripts --no-audit --no-fund --update-notifier=false +test "$(node -p "require('./node_modules/pyright/package.json').version")" = "%{version}" +! find node_modules/pyright -type f \( -name "*.node" -o -name "*.dll" -o -name "*.exe" \) -print -quit | grep -q . +rm -rf src/pyright/dist +mv node_modules/pyright src/pyright/dist +# Fix env-script-interpreter rpmlint error +sed -i 's|#!/usr/bin/env node|#!/usr/bin/node|' src/pyright/dist/*.js +rm -rf node_modules package.json package-lock.json +find . -name \*.pyi -empty -print -delete %build %pyproject_wheel @@ -71,6 +77,7 @@ # file from the main server at # https://raw.githubusercontent.com/microsoft/pylance-release/main/releases/{pylance_version}.json %pytest --ignore tests/test_main.py --ignore tests/test_langserver.py --ignore tests/test_node.py +%python_expand PYTHONPATH=src $python -m pyright --version | grep -Fx "pyright %{version}" %install %pyproject_install @@ -85,12 +92,6 @@ %pre %python_libalternatives_reset_alternative pyright -%post -%python_install_alternative pyright pyright-python pyright-langserver pyright-python-langserver - -%postun -%python_uninstall_alternative pyright - %files %{python_files} %doc README.md %license LICENSE ++++++ _service ++++++ <services> <service name="node_modules" mode="manual"> <param name="cpio">node_modules.obscpio</param> <param name="output">node_modules.spec.inc</param> <param name="source-offset">10000</param> </service> </services> ++++++ generate-package-json.py ++++++ #!/usr/bin/python3 """Generate the npm manifest and lockfile used to vendor Pyright.""" from __future__ import annotations import argparse import json import os import re import subprocess import tempfile from pathlib import Path VERSION_RE = re.compile(r"^Version:\s*([^\s#]+)", re.MULTILINE) def read_spec_version(spec_path: Path) -> str: match = VERSION_RE.search(spec_path.read_text(encoding="utf-8")) if match is None: raise SystemExit(f"could not find Version in {spec_path}") version = match.group(1) if "%" in version: raise SystemExit(f"Version in {spec_path} contains an RPM macro: {version}") return version def make_manifest(version: str) -> dict[str, object]: return { "name": "python-pyright-vendor", "version": version, "private": True, "description": "NPM dependencies vendored for the openSUSE python-pyright package", "license": "MIT", "dependencies": {"pyright": version}, } def write_json(path: Path, data: object) -> None: path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") def main() -> None: parser = argparse.ArgumentParser( description="Generate package.json and package-lock.json for obs-service-node_modules" ) parser.add_argument("--spec", type=Path, default=Path("python-pyright.spec")) parser.add_argument("--npm", type=Path, default=Path("/usr/bin/npm")) args = parser.parse_args() version = read_spec_version(args.spec) manifest = make_manifest(version) if not args.npm.is_file() or not os.access(args.npm, os.X_OK): raise SystemExit(f"system npm is not executable: {args.npm}") output_dir = args.spec.resolve().parent with tempfile.TemporaryDirectory(prefix="python-pyright-npm-") as temp_name: temp_dir = Path(temp_name) write_json(temp_dir / "package.json", manifest) subprocess.run( [ str(args.npm), "install", "--package-lock-only", "--legacy-peer-deps", "--ignore-scripts", "--no-audit", "--no-fund", "--update-notifier=false", ], cwd=temp_dir, check=True, ) lock_path = temp_dir / "package-lock.json" if not lock_path.is_file(): raise SystemExit("npm did not create package-lock.json") lock = json.loads(lock_path.read_text(encoding="utf-8")) packages = lock.get("packages", {}) fsevents = packages.get("node_modules/fsevents") if fsevents is not None: if not fsevents.get("optional") or fsevents.get("os") != ["darwin"]: raise SystemExit("refusing to prune an unexpected fsevents lock entry") # Pyright uses fsevents only on macOS. The OBS build passes # --omit=optional, so do not vendor its prebuilt Darwin binary. del packages["node_modules/fsevents"] write_json(output_dir / "package.json", manifest) write_json(output_dir / "package-lock.json", lock) print(f"generated package.json and package-lock.json for pyright {version}") if __name__ == "__main__": main() ++++++ node_modules.spec.inc ++++++ Source10000: https://registry.npmjs.org/pyright/-/pyright-1.1.411.tgz#/pyright-1.1.411.tgz ++++++ package-lock.json ++++++ { "name": "python-pyright-vendor", "version": "1.1.411", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "python-pyright-vendor", "version": "1.1.411", "license": "MIT", "dependencies": { "pyright": "1.1.411" } }, "node_modules/pyright": { "version": "1.1.411", "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.411.tgz", "integrity": "sha512-03S/vmS5lF1S/tVbKc2WNXCMq8JWCwta/qIYjj1jvqbQhoy+N3NgBzHTSmUlbYD6DJwqQ5XHf108QujoqeURvw==", "license": "MIT", "bin": { "pyright": "index.js", "pyright-langserver": "langserver.index.js" }, "engines": { "node": ">=14.0.0" }, "optionalDependencies": { "fsevents": "~2.3.3" } } } } ++++++ package.json ++++++ { "name": "python-pyright-vendor", "version": "1.1.411", "private": true, "description": "NPM dependencies vendored for the openSUSE python-pyright package", "license": "MIT", "dependencies": { "pyright": "1.1.411" } } ++++++ system-node.patch ++++++ --- /var/tmp/diff_new_pack.1kmEEJ/_old 2026-08-29 17:41:57.085178113 +0200 +++ /var/tmp/diff_new_pack.1kmEEJ/_new 2026-08-29 17:41:57.088178219 +0200 @@ -1,14 +1,11 @@ ---- - src/pyright/_utils.py | 35 ++-------------- - src/pyright/node.py | 108 +++++--------------------------------------------- - tests/conftest.py | 8 +++ - 3 files changed, 23 insertions(+), 128 deletions(-) - -Index: pyright-python-1.1.411/src/pyright/_utils.py -=================================================================== ---- pyright-python-1.1.411.orig/src/pyright/_utils.py 2026-06-25 04:13:32.000000000 +0200 -+++ pyright-python-1.1.411/src/pyright/_utils.py 2026-08-12 08:31:38.120365735 +0200 -@@ -1,10 +1,7 @@ +--- a/requirements.txt ++++ b/requirements.txt +@@ -1,2 +1 @@ +-nodeenv>=1.6.0 + typing-extensions>=4.1 +--- a/src/pyright/_utils.py ++++ b/src/pyright/_utils.py +@@ -1,112 +1,32 @@ from __future__ import annotations import os @@ -16,20 +13,54 @@ -import json import logging -import subprocess - from typing import Any +-from typing import Any from pathlib import Path -@@ -49,34 +46,12 @@ - log.debug('using bundled pyright at %s', bundled_path) - return bundled_path - +-from . import node, _mureq as mureq +-from .utils import env_to_bool, get_cache_dir, get_latest_version ++from .utils import env_to_bool, get_latest_version + from ._version import __version__, __pyright_version__ +- +-ROOT_CACHE_DIR = get_cache_dir() / 'pyright-python' +-DEFAULT_PACKAGE_JSON: dict[str, Any] = { +- 'name': 'pyright-binaries', +- 'version': '1.0.0', +- 'private': True, +- 'description': 'Cache directory created by Pyright Python to store downloads of the NPM package', +- 'main': 'node_modules/pyright/index.js', +- 'author': 'RobertCraigie', +- 'license': 'Apache-2.0', +-} + log: logging.Logger = logging.getLogger(__name__) + + + def install_pyright(args: tuple[object, ...], *, quiet: bool | None) -> Path: +- """Internal helper function to install the Pyright npm package to a cache. +- +- This returns the path to the installed package. +- +- This accepts a single argument which corresponds to the arguments given to the CLI / langserver +- which are used to determine whether or not certain warnings / logs will be printed. +- """ +- version = _get_configured_pyright_version() +- if version == 'latest': +- version = node.latest('pyright') +- else: +- if _should_warn_version(args=args, quiet=quiet): +- print( +- f'WARNING: there is a new pyright version available (v{version} -> v{get_latest_version()}).\n' +- + 'Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`\n' +- ) +- +- if version == __pyright_version__ and env_to_bool('PYRIGHT_PYTHON_USE_BUNDLED_PYRIGHT', default=True): +- bundled_path = Path(__file__).parent.joinpath('dist') +- if bundled_path.exists(): +- log.debug('using bundled pyright at %s', bundled_path) +- return bundled_path +- - cache_dir = ROOT_CACHE_DIR / version - cache_dir.mkdir(exist_ok=True, parents=True) -+ for root in (Path('/usr/share/node_modules'), Path('/usr/lib/node_modules')): -+ pkg_dir = root / 'pyright' -+ if node.get_pkg_version(pkg_dir / 'package.json') == version: -+ return pkg_dir - +- - pkg_dir = cache_dir / 'node_modules' / 'pyright' - package_json = cache_dir / 'package.json' - current_version = node.get_pkg_version(pkg_dir / 'package.json') @@ -55,37 +86,89 @@ - ) - - return pkg_dir -+ raise RuntimeError(f'System pyright npm package version {version} is not installed; install it from the distribution package') +- +- +-def _get_configured_pyright_version() -> str: ++ """Return the distribution-provided Pyright npm package.""" + force_version = os.environ.get('PYRIGHT_PYTHON_FORCE_VERSION') +- if force_version: +- return force_version +- +- pylance_version = os.environ.get('PYRIGHT_PYTHON_PYLANCE_VERSION') +- if pylance_version: +- return _get_pylance_pyright_version(pylance_version) +- +- return __pyright_version__ +- +- +-def _get_pylance_pyright_version(pylance_version: str) -> str: +- url = f'https://raw.githubusercontent.com/microsoft/pylance-release/main/releases/{pylance_version}.json' ++ if force_version and force_version != __pyright_version__: ++ raise RuntimeError( ++ f'This distribution package provides pyright {__pyright_version__}; ' ++ f'the requested version {force_version} cannot be downloaded' ++ ) +- try: +- response = mureq.get(url, timeout=1) +- response.raise_for_status() ++ if os.environ.get('PYRIGHT_PYTHON_PYLANCE_VERSION'): ++ raise RuntimeError('Pylance version lookup is unavailable in this distribution package') + +- data = response.json() +- log.debug(f'Pylance release data: {data}') +- version = data['pyrightVersion'] ++ bundled_path = Path(__file__).parent / 'dist' ++ if not bundled_path.exists(): ++ raise RuntimeError('The distribution-provided Pyright npm package is missing') + +- log.debug(f'Pylance {pylance_version} uses pyright version {version}') +- return version +- except Exception as exc: +- log.debug(f'Failed to download release metadata for Pylance {pylance_version} from {url}: {type(exc)} - {exc}') +- raise ++ log.debug('using distribution-provided pyright at %s', bundled_path) ++ return bundled_path + + + def _should_warn_version( +--- a/src/pyright/node.py ++++ b/src/pyright/node.py +@@ -1,30 +1,21 @@ + from __future__ import annotations - def _get_configured_pyright_version() -> str: -Index: pyright-python-1.1.411/src/pyright/node.py -=================================================================== ---- pyright-python-1.1.411.orig/src/pyright/node.py 2026-06-25 04:13:32.000000000 +0200 -+++ pyright-python-1.1.411/src/pyright/node.py 2026-08-12 08:31:38.120734741 +0200 -@@ -8,11 +8,10 @@ +-import os + import re + import sys +-import json + import shutil import logging import platform import subprocess -import importlib.util -from typing import Any, Dict, Tuple, Union, Mapping, Optional, NamedTuple, cast -+from typing import Any, Dict, Tuple, Union, Optional, NamedTuple, cast ++from typing import Any, Tuple, Union, Optional, NamedTuple, cast from pathlib import Path - from functools import lru_cache +-from functools import lru_cache -from typing_extensions import Literal, assert_never +from typing_extensions import Literal from . import errors from .types import Target, check_target -@@ -23,7 +22,6 @@ - ENV_DIR: Path = get_env_dir() - BINARIES_DIR: Path = get_bin_dir(env_dir=ENV_DIR) - USE_GLOBAL_NODE = env_to_bool('PYRIGHT_PYTHON_GLOBAL_NODE', default=True) +-from .utils import env_to_bool, get_bin_dir, get_env_dir, maybe_decode ++from .utils import maybe_decode + + log: logging.Logger = logging.getLogger(__name__) + +-ENV_DIR: Path = get_env_dir() +-BINARIES_DIR: Path = get_bin_dir(env_dir=ENV_DIR) +-USE_GLOBAL_NODE = env_to_bool('PYRIGHT_PYTHON_GLOBAL_NODE', default=True) -USE_NODEJS_WHEEL = env_to_bool('PYRIGHT_PYTHON_NODEJS_WHEEL', default=True) - NODE_VERSION = os.environ.get('PYRIGHT_PYTHON_NODE_VERSION', default=None) +-NODE_VERSION = os.environ.get('PYRIGHT_PYTHON_NODE_VERSION', default=None) VERSION_RE = re.compile(r'\d+\.\d+\.\d+') -@@ -41,27 +39,6 @@ + +@@ -41,27 +32,6 @@ return '.cmd' @@ -113,7 +196,7 @@ def _get_global_binary(target: Target) -> Optional[Path]: log.debug('Checking for global target binary: %s', target) -@@ -80,54 +57,20 @@ +@@ -80,54 +50,20 @@ return None @@ -174,10 +257,17 @@ def run( -@@ -143,37 +86,7 @@ - 'subprocess.CompletedProcess[str] | subprocess.CompletedProcess[bytes]', - subprocess.run(node_args, **kwargs), - ) +@@ -136,44 +72,12 @@ + check_target(target) + + strategy = _resolve_strategy(target) +- if strategy.type == 'global': +- node_args = [str(strategy.path), *args] +- log.debug('Running global node command with args: %s', node_args) +- return cast( +- 'subprocess.CompletedProcess[str] | subprocess.CompletedProcess[bytes]', +- subprocess.run(node_args, **kwargs), +- ) - elif strategy.type == 'nodejs_wheel': - import nodejs_wheel - @@ -209,48 +299,133 @@ - ) - else: - assert_never(strategy) -+ raise AssertionError(f'Unsupported node strategy: {strategy.type}') ++ node_args = [str(strategy.path), *args] ++ log.debug('Running system command with args: %s', node_args) ++ return cast( ++ 'subprocess.CompletedProcess[str] | subprocess.CompletedProcess[bytes]', ++ subprocess.run(node_args, **kwargs), ++ ) def version(target: Target) -> Tuple[int, ...]: -@@ -245,7 +158,7 @@ - return data.get('version') - - +@@ -187,91 +91,3 @@ + info = tuple(int(value) for value in match.group(0).split('.')) + log.debug('Version check for %s returning %s', target, info) + return info +- +- +-@lru_cache(maxsize=None) +-def latest(package: str) -> str: +- """Return the latest version for the given package""" +- proc = run( +- 'npm', +- 'info', +- package, +- 'version', +- stdout=subprocess.PIPE, +- stderr=subprocess.STDOUT, +- ) +- stdout = maybe_decode(proc.stdout) +- +- if proc.returncode != 0: +- print(stdout, file=sys.stderr) +- raise errors.VersionCheckFailed(f'Version check for {package} failed, see output above.') +- +- match = VERSION_RE.search(stdout) +- if not match: +- print(stdout, file=sys.stderr) +- raise errors.VersionCheckFailed(f'Could not find version for {package}, see output above') +- +- value = match.group(0) +- log.debug('Version check for %s returning %s', package, value) +- return value +- +- +-def get_env_variables() -> Dict[str, Any]: +- """Return the environmental variables that should be passed to a binary""" +- # NOTE: I do not actually know if these result in the intended behaviour +- # I simply copied them from bin/shim in nodeenv +- return { +- 'NODE_PATH': str(ENV_DIR / 'lib' / 'node_modules'), +- 'NPM_CONFIG_PREFIX': str(ENV_DIR), +- 'npm_config_prefix': str(ENV_DIR), +- } +- +- +-def get_pkg_version(pkg: Path) -> str | None: +- """Given a path to a `package.json` file, parse it and returns the `version` property +- +- Returns `None` if the version could not be resolved for any reason. +- """ +- if not pkg.exists(): +- return None +- +- try: +- data = json.loads(pkg.read_text()) +- except Exception: +- # TODO: test this +- log.debug('Ignoring error while reading/parsing the %s file', pkg, exc_info=True) +- return None +- +- return data.get('version') +- +- -def _update_path_env( -+'''def _update_path_env( - *, - env: Mapping[str, str] | None, - target_bin: Path, -@@ -275,3 +188,4 @@ - - log.debug('Using PATH environment variable: %s', path) - return path -+''' -Index: pyright-python-1.1.411/tests/conftest.py -=================================================================== ---- pyright-python-1.1.411.orig/tests/conftest.py 2026-06-25 04:13:32.000000000 +0200 -+++ pyright-python-1.1.411/tests/conftest.py 2026-08-12 08:17:47.441389908 +0200 -@@ -3,7 +3,10 @@ +- *, +- env: Mapping[str, str] | None, +- target_bin: Path, +- sep: str = os.pathsep, +-) -> str: +- """Returns a modified version of the `PATH` environment variable that has been updated +- to include the location of the downloaded Node binaries. +- """ +- if env is None: +- env = dict(os.environ) +- +- log.debug('Attempting to prepend %s to the PATH', target_bin) +- assert target_bin.exists(), f'Target directory {target_bin} does not exist' +- +- path = env.get('PATH', '') or os.environ.get('PATH', '') +- if path: +- log.debug('Found PATH contents: %s', path) +- +- # handle the case where the PATH already starts with the separator (this probably shouldn't happen) +- if path.startswith(sep): +- path = f'{target_bin.absolute()}{path}' +- else: +- path = f'{target_bin.absolute()}{sep}{path}' +- else: +- # handle the case where there is no PATH set (unlikely / impossible to actually happen?) +- path = str(target_bin.absolute()) +- +- log.debug('Using PATH environment variable: %s', path) +- return path +--- a/tests/conftest.py ++++ b/tests/conftest.py +@@ -1,9 +1,9 @@ + import os ++import shutil + from typing import Iterator from pathlib import Path import pytest -import nodejs_wheel -+try: -+ import nodejs_wheel -+except ImportError: -+ nodejs_wheel = None @pytest.fixture(name='tmp_path') -@@ -19,6 +22,9 @@ +@@ -19,10 +19,10 @@ @pytest.fixture(name='node', scope='session') def node_fixture() -> str: -+ if nodejs_wheel is None: -+ pytest.skip('nodejs-wheel-binaries is not installed') -+ - if os.name == 'nt': - return str(Path(nodejs_wheel.__file__).parent / 'node.exe') +- if os.name == 'nt': +- return str(Path(nodejs_wheel.__file__).parent / 'node.exe') +- +- return str(Path(nodejs_wheel.__file__).parent / 'bin' / 'node') ++ node = shutil.which('node') ++ if node is None: ++ pytest.skip('system node is not installed') ++ return node + + @pytest.fixture(autouse=True)
