diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 442799b..2b00a0d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -40,7 +40,6 @@ updates: labels: - 'dependencies' - 'debt' - - 'no-changelog' directory: '/' schedule: interval: 'weekly' diff --git a/.github/workflows/pr-labels.yml b/.github/workflows/pr-labels.yml index b3e13d6..5b1f48b 100644 --- a/.github/workflows/pr-labels.yml +++ b/.github/workflows/pr-labels.yml @@ -21,4 +21,4 @@ jobs: with: mode: minimum count: 1 - labels: 'bug, debt, dependencies, feature-request, no-changelog' + labels: 'bug, debt, dependencies, feature-request' diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 93561e0..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,108 +0,0 @@ -# Changelog - -All notable changes to the `vscode-common-python-lsp` package will be -documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [0.8.3] - 2026-10-16 - -### Added - -- Support for per-project Python environments. - -### Changed - -- Consume the shared libraries as a git submodule rather than published packages. -- Update dependencies and group compatible Dependabot updates with a 14-day cooldown. - -## [0.8.0] - 2026-06-25 - -### Added - -- **TypeScript** `ToolConfig.refreshExtensionOnPackagesChange` — opt-in key that, - when set to `true`, makes the shared activation logic restart the language - server whenever the active environment's package managers report a package - change (install/uninstall). The provider subscribes to the underlying - `IPythonApi.onDidChangePackages` event once during initialization. The key - defaults to `false`, so existing extensions are unaffected until they opt in. - The legacy `ms-python.python` extension does not expose package events, so this - has no effect unless the Python Environments extension is available. - -## [0.7.0] - 2026-06-17 - -### Added - -- **TypeScript** `ToolConfig.isFormatter` — optional boolean flag for tool - extensions that provide LSP-based document formatting - (`textDocument/formatting`, `rangeFormatting`, `rangesFormatting`). - -- **TypeScript** `NullFormatter` class — a lifecycle-aware placeholder - `DocumentFormattingEditProvider`. It registers a no-op formatter at - activation time (so VS Code lists the extension in the formatter picker - *before* the LSP has finished starting) and exposes `register()`, - `unregister()`, `isRegistered()`, and `dispose()` for manual control. - Exported from the package root. - -- **TypeScript** `createToolContext` — when `ToolConfig.isFormatter` is - `true`, the shared activation pattern now automatically: - - registers the `NullFormatter` placeholder immediately at activation, - - disposes it when the language client transitions to `State.Running` - (including the "already Running" case where the initial transition would - otherwise be missed), - - re-registers it if the client later transitions to `State.Stopped` or - `State.Starting` (e.g. during a restart), and disposes it again on the - next `Running`, - - disposes it on `ctx.dispose()` / `deactivateServer()`. - - This fixes the "duplicate formatter entry" regression reported in - [microsoft/vscode-black-formatter#752](https://github.com/microsoft/vscode-black-formatter/issues/752) - and unblocks the same fix for other formatter extensions - (`vscode-autopep8`, `vscode-isort`, …). - - Linter-only extensions (pylint, flake8, mypy, …) are unaffected — the - placeholder is **not** registered when `isFormatter` is `false` or unset. - -## [0.6.1] - 2026-06-17 - -### Fixed - -- **TypeScript** `getServerCwd`: Broadened the per-document variable guard - from a hardcoded `${file…}` / `${relativeFile…}` allowlist to a general - `${…}` check. Any token still unresolved at spawn-time (including - tool-specific tokens like mypy's `${nearestConfig}`) now correctly falls - back to the workspace path instead of being passed verbatim to - `child_process.spawn`, which caused an `ENOENT` error. - (Fixes [microsoft/vscode-mypy#556](https://github.com/microsoft/vscode-mypy/issues/556)) - -## [0.4.0] - 2026-05-01 - -### Added - -- **Python**: `update_environ_path()` — adds the virtual environment's scripts directory to PATH, ensuring tool executables are discoverable. - -## [0.1.0] - 2025-04-20 - -### Added - -- **TypeScript**: Logging utilities (`createOutputChannel`, `traceLog`, `traceError`, `traceWarn`, `traceInfo`, `traceVerbose`) -- **TypeScript**: Language status item registration and update helpers -- **TypeScript**: Server setup with `loadServerDefaults` for bundled Python server discovery -- **TypeScript**: Configuration file watcher factory (`createConfigFileWatchers`) -- **TypeScript**: VS Code API wrappers (`getConfiguration`, `getWorkspaceFolders`, etc.) -- **TypeScript**: Utility functions (resource URI helpers, `getProjectRoot`, interpreter/document checks) -- **TypeScript**: `.env` file parser (`getEnvVariables`) -- **TypeScript**: Full settings resolution pipeline (`IBaseSettings`, `getExtensionSettings`, `getGlobalSettings`, `getWorkspaceSettings`) -- **TypeScript**: Python environment provider (`PythonEnvironmentsProvider`) with interpreter change events -- **TypeScript**: Language server lifecycle management (`createServer`, `restartServer`) -- **TypeScript**: Extension activation pattern (`activateExtension`) with common subscriptions -- **TypeScript**: LSP handler registration helpers for document sync, formatting, code actions -- **Python**: LSP utilities (`shutdown_json_rpc`, `choose_working_directory`, file classification) -- **Python**: JSON-RPC runner infrastructure (`JsonRpc`, `run_over_json_rpc`, `get_or_start_json_rpc`) -- **Python**: Notebook cell-aware diagnostic mapping (`notebook_document_change`, `notebook_did_open`) -- **Python**: Subprocess message loop runner (`run_message_loop`, `RunResult`) -- **Python**: Code action / quick-fix edit builder (`build_quick_fix_edit`, `QuickFixRegistrationError`) -- **Python**: Diagnostic linting version tracker (`LintVersionTracker`) -- **Python**: Tool version detection and validation (`extract_version`, `check_min_version`) -- **Python**: Server infrastructure (`ToolServer`, `ToolServerConfig`, `ServerActivationConfig`) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30c78a3..e2dff02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,11 +38,19 @@ Extensions consume this repository at a pinned Git commit and build both the TypeScript and Python runtimes from that revision. The pinned source revision, not the version strings in the package manifests, is the compatibility identity. -The private npm manifest and Python package metadata retain fixed version values -needed for local builds and installation. Ordinary source changes do not require -changing or synchronizing those values. The default Python server reports its -installed package version for diagnostics, but that value is not a source -provenance guarantee. The npm and Python packages are not published. +The private npm manifest and Python distribution metadata use the internal +build identity `vscode-python-tool-internal` and retain fixed version +values needed for local builds and installation. This identity is not a consumer +import API. Ordinary source changes do not require changing or synchronizing +those values. The default Python server reports that internal distribution's +installed version for diagnostics (or `v0.0.0-dev` when its metadata is absent), +but that value is not a source provenance guarantee. Neither runtime is +published to a package registry. + +Record consumer-visible changes and upgrade instructions in pull request +descriptions. For breaking changes, add focused migration documentation. +There is no separate changelog to maintain; historical entries remain in Git +history. Maintainers may optionally tag a reviewed commit and create a [GitHub Release](https://github.com/microsoft/vscode-common-python-lsp/releases) diff --git a/README.md b/README.md index 02e7c8b..3dced4f 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,15 @@ Shared Python and TypeScript libraries for VS Code Python tool extensions ``` vscode-common-python-lsp/ -├── python/ # Python package (bundled server-side) -│ ├── vscode_common_python_lsp/ # Package source +├── python/ # Python source (bundled server-side) +│ ├── vscode_common_python_lsp/ # Import package source │ │ └── __init__.py │ ├── tests/ # Python tests (pytest) -│ ├── pyproject.toml # Package metadata & build config +│ ├── pyproject.toml # Local installation metadata & build config │ └── requirements-dev.txt # Dev/test dependencies │ -├── typescript/ # TypeScript package (VS Code client-side) -│ ├── src/ # Package source +├── typescript/ # TypeScript source (VS Code client-side) +│ ├── src/ # Public entry point and implementation │ │ └── index.ts │ ├── tests/ # TypeScript tests (mocha) │ ├── package.json @@ -31,7 +31,7 @@ vscode-common-python-lsp/ └── README.md ``` -## Python Package +## Python Source The `vscode_common_python_lsp` Python package provides server-side utilities shared across all five extensions: path resolution, context managers, tool execution runners, @@ -45,9 +45,9 @@ pip install -e ".[dev]" pytest tests/ ``` -## TypeScript Package +## TypeScript Source -The `vscode-common-python-lsp` TypeScript package provides VS Code client-side +The TypeScript source provides VS Code client-side utilities: extension activation, server lifecycle, settings management, Python interpreter resolution, and logging. @@ -55,7 +55,7 @@ interpreter resolution, and logging. ```bash cd typescript -npm install +npm ci npm run lint npm run build npm test @@ -80,18 +80,61 @@ git submodule update --init --recursive Clone a consuming extension with `--recurse-submodules`, or run the update command above after cloning. -Reference the TypeScript package from the submodule in `package.json`: +#### TypeScript: build, type-check, then bundle -```json -{ - "dependencies": { - "@vscode/common-python-lsp": "file:external/vscode-common-python-lsp/typescript" - } -} +The extension does not declare a shared-runtime npm dependency or alias. From +the extension root, install its own locked development tools and the checkout's +locked dependencies, then build the shared JavaScript and declarations **before** +type-checking or bundling the extension: + +```powershell +npm ci +npm --prefix .\external\vscode-common-python-lsp\typescript ci +npm --prefix .\external\vscode-common-python-lsp\typescript run build +``` + +In the extension's `src/extension.ts`, import exported symbols from the built +public entry point (not individual implementation files or a bare package name): + +```typescript +import { createToolContext } from '../external/vscode-common-python-lsp/typescript/dist'; +``` + +Use the extension's existing TypeScript configuration and development type +dependencies, including `@types/vscode` and `@types/node`, to type-check its +`src/` files. The shared declarations resolve their dependencies from the +checkout's `typescript/node_modules`; the extension's `rootDir` can remain +`src`. For example, with TypeScript already installed by the extension: + +```powershell +npx --no-install tsc --noEmit ``` -Install the Python package directly from the same submodule commit when -building the extension bundle. For example, in `noxfile.py`: +Bundle the source-relative entry and its JavaScript dependency closure into the +extension. For example, an extension already using esbuild can run this from +the extension root after the steps above: + +```powershell +npx --no-install esbuild .\src\extension.ts --bundle --platform=node --format=cjs --external:vscode --outfile=.\dist\extension.js +``` + +VS Code supplies `vscode`, and esbuild leaves Node built-ins external. Do not +externalize the shared entry or its npm dependencies: they must resolve from +the checkout and be included in the bundle. Merely running `tsc` does not bundle +them; shipping only the extension's emitted JavaScript would leave a relative +runtime dependency on the checkout. Other bundlers must preserve the same +resolution and packaging behavior. Rebuild both the shared entry and extension +bundle whenever the pinned checkout changes. + +When migrating an existing consumer, remove its former shared-runtime +dependency/alias, update its imports and regenerate its lockfile without that +dependency. Review the consumer's own build and packaging rules; changing this +repository's metadata alone does not migrate an extension. + +#### Python: install from the same checkout + +Install the Python source directly from the same submodule commit when building +the extension bundle. For example, in `noxfile.py`: ```python import pathlib @@ -118,8 +161,19 @@ def install_bundled_libs(session: nox.Session) -> None: ) ``` -The package manifests support building from the submodule. This repository no -longer publishes packages to npm or PyPI. +The `--no-deps` example preserves the consumer's runtime dependency policy: the +extension must also bundle the dependencies declared in `python/pyproject.toml` +using its own reviewed requirements/locks. Keep the installed distribution +metadata alongside `vscode_common_python_lsp`; the default server uses its +version for diagnostics, or `v0.0.0-dev` if that metadata is absent. Python +imports continue to use `vscode_common_python_lsp`. + +Both manifests use `vscode-python-tool-internal` solely as internal +local build/install metadata. npm remains private, and Python metadata is marked +`Private :: Do Not Upload`. Local-path pip installation and local wheel builds +remain supported. Neither identity is a registry-facing consumption API: do not +add a bare dependency or registry install for it. This repository publishes +neither npm nor PyPI packages. ### Dependabot updates diff --git a/python/README.md b/python/README.md index 9bf78fa..ef9cb3f 100644 --- a/python/README.md +++ b/python/README.md @@ -1,5 +1,11 @@ -# vscode-common-python-lsp (Python) +# Shared Python source Shared Python utilities for VS Code Python tool extensions. -See the [main README](../README.md) for full documentation. +Install this directory from a pinned repository checkout. Its internal +distribution name, `vscode-python-tool-internal`, supports local +installation and wheel builds only; it is not a published product. The Python +import namespace remains `vscode_common_python_lsp`. + +See the [main README](../README.md#consuming-in-extensions) for local-path +installation and bundling instructions. diff --git a/python/pyproject.toml b/python/pyproject.toml index 33d2645..d2353d9 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -3,12 +3,13 @@ requires = ["setuptools>=68.0", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "vscode-common-python-lsp" +name = "vscode-python-tool-internal" version = "0.8.3" -description = "Shared Python utilities for VS Code Python tool extensions" +description = "Internal source-only Python utilities for VS Code Python tool extensions" readme = "README.md" license = "MIT" requires-python = ">=3.10" +classifiers = ["Private :: Do Not Upload"] authors = [ { name = "Microsoft Corporation" }, ] diff --git a/python/tests/test_server.py b/python/tests/test_server.py index 4964ee1..3d99e82 100644 --- a/python/tests/test_server.py +++ b/python/tests/test_server.py @@ -1247,10 +1247,11 @@ def test_creates_default_server(self): patch( "vscode_common_python_lsp.server.importlib.metadata.version", return_value="1.2.3", - ), + ) as mock_version, ): mock_cls.return_value = MagicMock() ts = ToolServer(BASIC_CONFIG) + mock_version.assert_called_once_with("vscode-python-tool-internal") mock_cls.assert_called_once_with( name="testtool-server", version="v1.2.3", @@ -1264,22 +1265,52 @@ def test_fallback_version_when_package_not_installed(self): patch( "vscode_common_python_lsp.server.importlib.metadata.version", side_effect=importlib.metadata.PackageNotFoundError( - "vscode-common-python-lsp" + "vscode-python-tool-internal" ), - ), + ) as mock_version, ): mock_cls.return_value = MagicMock() ToolServer(BASIC_CONFIG) + mock_version.assert_called_once_with("vscode-python-tool-internal") mock_cls.assert_called_once_with( name="testtool-server", version="v0.0.0-dev", max_workers=5, ) - def test_accepts_custom_server(self): - custom = MagicMock() - ts = ToolServer(BASIC_CONFIG, server=custom) - assert ts.server is custom + def test_missing_internal_metadata_does_not_use_other_distributions(self): + def lookup_version(distribution_name): + if distribution_name == "vscode-python-tool-internal": + raise importlib.metadata.PackageNotFoundError(distribution_name) + return "9.9.9" + + with ( + patch("vscode_common_python_lsp.server.LanguageServer") as mock_cls, + patch( + "vscode_common_python_lsp.server.importlib.metadata.version", + side_effect=lookup_version, + ) as mock_version, + ): + ToolServer(BASIC_CONFIG) + mock_version.assert_called_once_with("vscode-python-tool-internal") + mock_cls.assert_called_once_with( + name="testtool-server", + version="v0.0.0-dev", + max_workers=5, + ) + + def test_accepts_custom_server_without_metadata_lookup(self): + with ( + patch("vscode_common_python_lsp.server.LanguageServer") as mock_cls, + patch( + "vscode_common_python_lsp.server.importlib.metadata.version" + ) as mock_version, + ): + custom = MagicMock() + ts = ToolServer(BASIC_CONFIG, server=custom) + assert ts.server is custom + mock_version.assert_not_called() + mock_cls.assert_not_called() def test_empty_initial_state(self): ts = _make_server() diff --git a/python/vscode_common_python_lsp/server.py b/python/vscode_common_python_lsp/server.py index 1b87762..e8f63be 100644 --- a/python/vscode_common_python_lsp/server.py +++ b/python/vscode_common_python_lsp/server.py @@ -102,7 +102,7 @@ def __init__( self.global_settings: dict[str, Any] = {} if server is None: try: - _pkg_version = importlib.metadata.version("vscode-common-python-lsp") + _pkg_version = importlib.metadata.version("vscode-python-tool-internal") except importlib.metadata.PackageNotFoundError: _pkg_version = "0.0.0-dev" server = LanguageServer( diff --git a/typescript/README.md b/typescript/README.md index 41c937e..8969abb 100644 --- a/typescript/README.md +++ b/typescript/README.md @@ -1,5 +1,11 @@ -# @vscode/common-python-lsp (TypeScript) +# Shared TypeScript source Shared TypeScript utilities for VS Code Python tool extensions. -See the [main README](../README.md) for full documentation. +Build this directory from a pinned repository checkout and import its `dist` +public entry point by a source-relative path. The private manifest name +`vscode-python-tool-internal` is only local build metadata, not a +published package or a supported bare-module import. + +See the [main README](../README.md#consuming-in-extensions) for build order, +consumer type-checking and bundling instructions. diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 6491f46..9a482bf 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@vscode/common-python-lsp", + "name": "vscode-python-tool-internal", "version": "0.8.3", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@vscode/common-python-lsp", + "name": "vscode-python-tool-internal", "version": "0.8.3", "license": "MIT", "dependencies": { diff --git a/typescript/package.json b/typescript/package.json index 6f18a2e..7892037 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -1,8 +1,8 @@ { - "name": "@vscode/common-python-lsp", + "name": "vscode-python-tool-internal", "version": "0.8.3", "private": true, - "description": "Shared TypeScript utilities for VS Code Python tool extensions", + "description": "Internal source-only TypeScript utilities for VS Code Python tool extensions", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [