[WIP] Complete package manager command class refactoring#1621
Draft
edvilme wants to merge 28 commits into
Draft
Conversation
- Create base command infrastructure in src/managers/base/commands/
- PackageManagerCommand base class with minimal interface
- CommandSettings interface for execution configuration
- Six template classes (Install, Uninstall, List, Version, AvailableVersions, ListDirectNames)
- Each template loads command-specific settings from VS Code config
- Implement pip/uv commands in src/managers/builtin/commands/
- PipInstallCommand, PipUninstallCommand, PipListCommand, etc.
- UvInstallCommand, UvUninstallCommand, UvListCommand, etc.
- UV uses 'pip' subcommand syntax vs Pip uses '-m pip'
- Implement conda commands in src/managers/conda/commands/
- CondaInstallCommand, CondaUninstallCommand, CondaListCommand
- CondaVersionCommand, CondaAvailableVersionsCommand, CondaListDirectNamesCommand
- Implement poetry commands in src/managers/poetry/commands/
- PoetryInstallCommand, PoetryUninstallCommand, PoetryListCommand
- PoetryVersionCommand, PoetryAvailableVersionsCommand, PoetryListDirectNamesCommand
- Remove redundant old command types
- Delete PackageManagerCommand.ts (namespace interface)
- Delete PackageManagerCommandArguments.ts
- Delete BuiltinCommandExecutor (executor pattern not needed)
- Restore package managers to original state (no integration yet)
- Package managers remain unchanged from main branch
- New command classes available for future integration
Architecture: Three-level hierarchy
- Level 1: Base class (PackageManagerCommand) - minimal shared interface
- Level 2: Templates (InstallCommand, etc.) - load settings, define execute signature
- Level 3: Concrete (Pip/Uv/Conda/Poetry commands) - implement buildCommand() and execute()
Design principles:
- Persisting arguments stored in constructor (pythonExecutable, indexUrl, settings)
- Ephemeral arguments passed to execute() method (packages, upgrade flags)
- Settings automatically loaded from python-envs.packageManager.{commandType}CommandArgs
- Direct runPython calls with timeout support, no wrapper layer
- Update CommandConstructorOptions to accept pythonExecutable
- Modify PipInstallCommand/PipUninstallCommand to use pythonExecutable for pip
- Modify UvInstallCommand/UvUninstallCommand to add --python flag with pythonExecutable
- Create parsePackageSpecs utility to convert string[] to {packageName, version}[] objects
- Integrate pip install/uninstall in pipPackageManager.manage():
- Detect UV via shouldUseUv()
- Choose PipInstallCommand or UvInstallCommand based on UV detection
- Execute commands directly with parsed package specs
- Replace managePackages() utils call with direct command execution
- Remove unused managePackages import from pipPackageManager
- Fix syntax error in UvAvailableVersionsCommand (missing closing brace)
- Defer Conda and Poetry command implementation for future phases
Architecture simplification:
- pythonExecutable now serves dual purpose:
- Pip: environment.execInfo.run.executable (Python interpreter path)
- UV: environment.execInfo.run.executable (passed with --python flag to uv pip)
- No redundant environmentExecutable parameter needed
- Commands execute with timeout from settings.executionTimeout
Status: Pip/UV install/uninstall fully integrated and compiling
Pending: List, Version, AvailableVersions, ListDirectNames commands for Pip/UV
Pending: Full implementation for Conda and Poetry managers
- Delete builtinCommandExecutor.ts (executor pattern was rejected in favor of direct command calls) - Remove managePackages() function from builtin/utils.ts (now handled by direct command execution) - Still needed in condaUtils.ts for conda integration Retained utilities: - refreshPipPackages() - used by getPackages() - refreshPipDirectPackageNames() - used by getDirectPackageNames() - normalizePackageName() - used by getDirectPackageNames() - parsePackageSpecs() - used by manage() for install/uninstall commands - processEditableInstallArgs() - used by PipInstallCommand All command execution for pip/uv now goes through direct command class instantiation with no intermediary dispatcher layer.
…utility - Create src/managers/conda/commands/ with install.ts and uninstall.ts - Implement CondaInstallCommand and CondaUninstallCommand classes - CondaInstallCommand: ['install', '-y', '-c', 'conda-forge', ...packages] - CondaUninstallCommand: ['remove', '-y', ...packages] - Create conda/helpers.ts with runConda wrapper function - Integrate conda commands into condaPackageManager.manage() - Direct instantiation of CondaInstallCommand/CondaUninstallCommand - Use parsePackageSpecs() from builtin/utils for package conversion - No intermediary dispatcher or managePackages utility - Remove managePackages() function from: - src/managers/builtin/utils.ts (no longer used by pip/uv) - src/managers/conda/condaUtils.ts (replaced by direct command execution) Result: - All package managers (pip, uv, conda) now use direct command class execution - No indirection layer or dispatcher patterns - managePackages utility completely eliminated - Both managers tested and compiling successfully Remove thin runConda wrapper - use runCondaExecutable directly - Delete src/managers/conda/helpers.ts (unnecessary indirection) - Update CondaInstallCommand to import and call runCondaExecutable directly - Update CondaUninstallCommand to import and call runCondaExecutable directly Simplifies conda command execution by removing wrapper layer per user preference
- Replace getPackages() to use PipListCommand/UvListCommand - Detects UV vs Pip and instantiates appropriate command - Calls execute() which returns PackageInfo[] - Replace getVersion() to use PipVersionCommand/UvVersionCommand - Direct command execution instead of inline pip/uv parsing - Replace getPackageAvailableVersions() to use PipAvailableVersionsCommand/UvAvailableVersionsCommand - Commands handle JSON parsing and version extraction - Still respects pip version check (< 21.2.0 returns undefined) - Replace getDirectPackageNames() to use PipListDirectNamesCommand/UvListDirectNamesCommand - Commands return package names, manager normalizes them Result: - All pipPackageManager operations now use the three-level command hierarchy - Removed imports of refreshPipPackages, refreshPipDirectPackageNames - No more inline runPython/runUV calls in getVersion/getPackageAvailableVersions - Consistent pattern: detect UV, choose command class, instantiate, execute() Remove parsePipIndexVersionsJson function and its tests - Delete src/test/managers/builtin/pipVersions.unit.test.ts - This file only tested parsePipIndexVersionsJson which is no longer needed - Remove parsePipIndexVersionsJson from pipPackageManager.ts - JSON parsing is now handled inside PipAvailableVersionsCommand - and UvAvailableVersionsCommand.execute() methods - Remove unused rcompare import from @renovatebot/pep440 The command classes now handle all parsing internally, eliminating need for this utility function in the package manager.
…commands New Conda Commands: - CondaListCommand: runs 'conda list -p <path> --json' - Takes environmentPath as ephemeral argument to execute() - Returns PackageInfo[] with name, displayName, version, description - CondaVersionCommand: runs 'conda --version' - Returns version string in format 'conda X.Y.Z' - CondaAvailableVersionsCommand: runs 'conda search <package> --json' - Takes packageName, returns unique versions as string[] - Note: conda search returns all builds, we deduplicate versions Integration into condaPackageManager: - getPackages() uses CondaListCommand - getVersion() uses CondaVersionCommand - getPackageAvailableVersions() uses CondaAvailableVersionsCommand Removed: - Direct runCondaExecutable() calls in condaPackageManager - Inline JSON parsing and version filtering logic - Unused rcompare import (no longer needed for sorting) - traceError import (error handling moved to command level) Result: - All conda operations (install, uninstall, list, version, availableVersions) now use the three-level command hierarchy - Note: conda has no ListDirectNames equivalent (user confirmed --from-history is inaccurate)
Implement all Poetry operations (Add, Remove, Show, Version, ShowTopLevel) as concrete command classes extending the base template classes: - PoetryAddCommand: extends InstallCommand, uses 'poetry add' - PoetryRemoveCommand: extends UninstallCommand, uses 'poetry remove' - PoetryShowCommand: extends ListCommand, uses 'poetry show --no-ansi' - PoetryVersionCommand: extends VersionCommand, gets poetry version - PoetryShowTopLevelCommand: extends ListDirectNamesCommand, uses 'poetry show --top-level' Refactor poetryPackageManager methods to use command classes directly: - manage(): instantiates Add/Remove commands - fetchPackagesFromTool(): uses PoetryShowCommand - getVersion(): uses PoetryVersionCommand - getDirectPackageNames(): uses PoetryShowTopLevelCommand - getPackageAvailableVersions(): returns undefined (Poetry doesn't support this) Remove direct poetry command calls and unused imports. All operations now follow the three-level command class pattern.
…ands Add comprehensive JSDoc comments to all Poetry command classes that include: - Parsed Command: The exact CLI command structure with arguments - Official Documentation: Links to Poetry CLI documentation - Description: What each command does and notable flags Commands documented: - PoetryAddCommand: poetry add [--allow-prereleases] <package> - PoetryRemoveCommand: poetry remove <package> - PoetryShowCommand: poetry show --no-ansi - PoetryVersionCommand: poetry --version - PoetryShowTopLevelCommand: poetry show --no-ansi --top-level This enables identification of false/invalid commands and provides reference documentation for maintainers.
Move the Poetry command documentation (parsed command, official documentation, and description) from the ephemeral argument interfaces to the command classes where they are more visible and semantically appropriate.
…managers Add comprehensive JSDoc comments to all 16 concrete command classes with: - Parsed Command: The exact CLI command structure with arguments - Official Documentation: Links to official tool documentation - Description: What each command does and notable flags/behavior Pip commands (6): - PipInstallCommand: python -m pip install - PipUninstallCommand: python -m pip uninstall -y - PipListCommand: python -m pip list --format=json - PipVersionCommand: python -m pip --version - PipAvailableVersionsCommand: python -m pip index versions --json - PipListDirectNamesCommand: python -m pip list --not-required UV commands (6): - UvInstallCommand: uv pip install --python - UvUninstallCommand: uv pip uninstall -y --python - UvListCommand: uv pip list --format=json - UvVersionCommand: uv --version - UvAvailableVersionsCommand: uv pip index versions --json - UvListDirectNamesCommand: uv pip list --not-required Conda commands (5): - CondaInstallCommand: conda install -y -c conda-forge - CondaUninstallCommand: conda remove -y - CondaListCommand: conda list -p --json - CondaVersionCommand: conda --version - CondaAvailableVersionsCommand: conda search --json Poetry commands already have documentation from previous commit. This enables identification of false/invalid commands and provides reference documentation across all package managers.
…tions Replace direct helper calls (runPython, runUV) with command class usage: - refreshPipPackages() now uses PipListCommand/UvListCommand - refreshPipDirectPackageNames() now uses PipListDirectNamesCommand/UvListDirectNamesCommand Remove: - execPipList() function - no longer needed - PIP_LIST_TIMEOUT_MS constant - no longer used - parsePipListJson import - parsing now handled by command classes This eliminates intermediate abstraction layers and uses the consistent command class pattern throughout the codebase.
- Export PackageManagerCommand and supporting types (CommandSettings, CommandType, CommandResult, CommandConstructorOptions) - Export 6 abstract base command classes: InstallCommand, UninstallCommand, ListCommand, VersionCommand, AvailableVersionsCommand, ListDirectNamesCommand - Add comprehensive JSDoc documentation for each command class explaining purpose, usage patterns, and examples - Extensions can now implement custom package managers by extending these base classes - All 1269 unit tests passing, webpack compilation successful
- Remove CommandType type from commandSettings.ts - not used by command implementations - Remove CommandSettings interface from commandSettings.ts - will be added later with proper design - Remove CommandSettings property from all 6 base command classes - Remove CommandSettings initialization logic from base command class constructors - Remove all this.settings.executionTimeout references in concrete implementations - Replace with default 300000ms timeout value (hardcoded for now until CommandSettings reintroduced) - Remove CommandSettings and CommandType from public API exports in src/api.ts - All 1269 unit tests passing, webpack compilation successful
- Add protected timeout: number = 300000 property to all 6 base command classes - Add protected config property to each base class using getConfiguration() for command-specific settings - InstallCommand: 'python-envs.packageManager.installCommandArgs' - UninstallCommand: 'python-envs.packageManager.uninstallCommandArgs' - ListCommand: 'python-envs.packageManager.listCommandArgs' - VersionCommand: 'python-envs.packageManager.versionCommandArgs' - AvailableVersionsCommand: 'python-envs.packageManager.availableVersionsCommandArgs' - ListDirectNamesCommand: 'python-envs.packageManager.listDirectNamesCommandArgs' - Update all concrete command implementations to use this.timeout instead of hardcoded 300000 - Import getConfiguration in all base command classes - All 1269 unit tests passing, webpack compilation successful
- Define InstallEphemeralArgs in InstallCommand base class with packages and upgrade fields - Define UninstallEphemeralArgs in UninstallCommand base class with packages field - Define AvailableVersionsEphemeralArgs in AvailableVersionsCommand base class with packageName, pythonVersion, and includePrerelease fields - Add abstract buildCommand method signature to each base class that uses the appropriate ephemeral args type - Export ephemeral argument types from src/managers/base/commands/index.ts - Remove duplicate type definitions from concrete implementations (PipInstallCommand, UvInstallCommand, PipUninstallCommand, UvUninstallCommand, PipAvailableVersionsCommand, UvAvailableVersionsCommand) - Update concrete implementations to import ephemeral argument types from base commands - Improves polymorphic design - eliminates duplication and establishes clear command type interface - All 1269 unit tests passing, webpack compilation successful
- Update execute method signatures in base classes to accept ephemeral args objects - InstallCommand.execute(ephemeralArgs: InstallEphemeralArgs) - UninstallCommand.execute(ephemeralArgs: UninstallEphemeralArgs) - AvailableVersionsCommand.execute(ephemeralArgs: AvailableVersionsEphemeralArgs) - Update all concrete implementations (Pip, UV, Conda, Poetry) to use ephemeral args - Remove local duplicate ephemeral arg type definitions in Conda commands - Update Poetry commands (Add, Remove) to use base class types - Update all callers to pass ephemeral args objects instead of individual parameters - pipPackageManager.ts: install, uninstall, availableVersions calls - condaPackageManager.ts: install, uninstall, availableVersions calls - poetryPackageManager.ts: add, remove calls - Improves consistency - both buildCommand and execute now use the same ephemeral args interface - All 1269 unit tests passing, webpack compilation successful
- Rename InstallEphemeralArgs -> InstallExecuteArgs - Rename UninstallEphemeralArgs -> UninstallExecuteArgs - Rename AvailableVersionsEphemeralArgs -> AvailableVersionsExecuteArgs - Rename all ephemeralArgs parameter names to executeArgs throughout - Update all base classes, concrete implementations, and callers - Update documentation comments from 'ephemeral' to execution-focused language - All 1269 unit tests passing, webpack compilation successful - Improves naming clarity: 'executeArgs' better describes parameters passed to execute methods
…ed CommandResult - Rename src/managers/base/commands/commandSettings.ts to packageManagerCommand.ts - File now named after its primary export (PackageManagerCommand) - Remove unused CommandResult interface - Not used anywhere in the codebase, only exported in API - Update all imports in base command classes to use new filename - availableVersions.ts, install.ts, list.ts, listDirectNames.ts, uninstall.ts, version.ts - Update index.ts to import from packageManagerCommand instead of commandSettings - Remove CommandResult from public API export - Delete unused src/managers/builtin/commands/commandSettings.ts file - All 1269 unit tests passing, webpack compilation successful
- Add protected timeout property with 300000ms default to PackageManagerCommand - Remove duplicate timeout declarations from all 6 command-specific classes - InstallCommand, UninstallCommand, AvailableVersionsCommand - ListCommand, VersionCommand, ListDirectNamesCommand - Subclasses can still override if needed for custom timeout behavior - Centralizes timeout configuration at the base level - All 1269 unit tests passing, webpack compilation successful
- Add configSection optional parameter to CommandConstructorOptions - Move getConfiguration() call to PackageManagerCommand constructor - Remove duplicate config assignments from all 6 base command classes - InstallCommand, UninstallCommand, AvailableVersionsCommand - ListCommand, VersionCommand, ListDirectNamesCommand - Each derived class now passes its config section via super() call - Reduces boilerplate by 6 duplicate lines (1 per command class) - Makes adding new command types easier (no config setup needed) - Centralized pattern: python-envs.packageManager.\ - All 1269 unit tests passing, webpack compilation successful
…stall - Add manageCommandOptions variable in manage() and runPoetryManage() methods - Consolidate pythonExecutable, log, and cancellationToken in single object - Reuse manageCommandOptions for both uninstall and install command creation - Eliminates repeated object literal duplication in install/uninstall blocks - Applied to: PipPackageManager, CondaPackageManager, PoetryPackageManager - Import CommandConstructorOptions type in all three managers - Clear variable name (manageCommandOptions) indicates scope and purpose - All 1269 unit tests passing, webpack compilation successful
Contributor
There was a problem hiding this comment.
Pull request overview
This PR refactors package-manager operations (pip/uv, conda, poetry) to use a shared command-class hierarchy (base → templates → concrete implementations) and exposes new command-argument settings/configuration to support reuse and future extensibility.
Changes:
- Introduces a new
PackageManagerCommandbase and template command types (install/uninstall/list/version/availableVersions/listDirectNames) with per-command config sections. - Updates pip/uv, conda, and poetry package managers to call the new command classes instead of utility wrappers.
- Adds new settings surfaces and documentation for the command-argument architecture.
Reviewed changes
Copilot reviewed 39 out of 40 changed files in this pull request and generated 31 comments.
Show a summary per file
| File | Description |
|---|---|
| src/test/managers/builtin/pipVersions.unit.test.ts | Removes unit tests covering pip “index versions” JSON parsing. |
| src/managers/poetry/poetryPackageManager.ts | Switches Poetry operations to command classes; modifies error/cancellation behavior. |
| src/managers/poetry/commands/add.ts | New Poetry add command implementation. |
| src/managers/poetry/commands/remove.ts | New Poetry remove command implementation. |
| src/managers/poetry/commands/show.ts | New Poetry show command implementation. |
| src/managers/poetry/commands/showTopLevel.ts | New Poetry show-top-level command implementation. |
| src/managers/poetry/commands/version.ts | New Poetry version command implementation. |
| src/managers/poetry/commands/index.ts | Barrel export for Poetry commands. |
| src/managers/conda/condaUtils.ts | Removes managePackages() utility. |
| src/managers/conda/condaPackageManager.ts | Refactors conda operations to command classes and updates version/available-versions handling. |
| src/managers/conda/commands/install.ts | New conda install command implementation. |
| src/managers/conda/commands/uninstall.ts | New conda uninstall command implementation. |
| src/managers/conda/commands/list.ts | New conda list command implementation. |
| src/managers/conda/commands/version.ts | New conda version command implementation. |
| src/managers/conda/commands/availableVersions.ts | New conda available-versions command implementation. |
| src/managers/conda/commands/index.ts | Barrel export for conda commands. |
| src/managers/builtin/utils.ts | Removes pip management utilities; adds parsePackageSpecs(). |
| src/managers/builtin/pipUtils.ts | Updates workspace-package selection to use list command classes. |
| src/managers/builtin/pipPackageManager.ts | Refactors pip/uv management, listing, version, and available-versions flows to command classes. |
| src/managers/builtin/helpers.ts | Adjusts stderr handling in runPython(); retains runUV() helper. |
| src/managers/builtin/commands/install.ts | Adds pip/uv install command classes. |
| src/managers/builtin/commands/uninstall.ts | Adds pip/uv uninstall command classes. |
| src/managers/builtin/commands/list.ts | Adds pip/uv list command classes. |
| src/managers/builtin/commands/listDirectNames.ts | Adds pip/uv list-direct-names command classes. |
| src/managers/builtin/commands/version.ts | Adds pip/uv version command classes. |
| src/managers/builtin/commands/availableVersions.ts | Adds pip/uv available-versions command classes. |
| src/managers/builtin/commands/index.ts | Barrel export for builtin pip/uv commands. |
| src/managers/base/commands/packageManagerCommand.ts | Introduces shared command base + progress wrapper + config wiring. |
| src/managers/base/commands/install.ts | Adds install template class and execute-args interface. |
| src/managers/base/commands/uninstall.ts | Adds uninstall template class and execute-args interface. |
| src/managers/base/commands/list.ts | Adds list template class. |
| src/managers/base/commands/listDirectNames.ts | Adds list-direct-names template class. |
| src/managers/base/commands/version.ts | Adds version template class. |
| src/managers/base/commands/availableVersions.ts | Adds available-versions template class and execute-args interface. |
| src/managers/base/commands/index.ts | Barrel export for base command templates. |
| src/api.ts | Documents and re-exports the new command architecture via the extension API. |
| SETTINGS_ARCHITECTURE.md | Adds new documentation describing the command/settings architecture. |
| package.nls.json | Adds setting-description localization keys for new command sections. |
| package.json | Adds new configuration schema objects for command argument settings. |
| package-lock.json | Updates lockfile metadata. |
Comment on lines
+132
to
+136
| const listCmd = new CondaListCommand({ | ||
| pythonExecutable: 'conda', | ||
| log: this.log, | ||
| }); | ||
| const data = await listCmd.execute(); |
Comment on lines
+20
to
+26
| protected buildCommand(executeArgs: InstallExecuteArgs): string[] { | ||
| let args = ['install', '-y', '-c', 'conda-forge']; | ||
|
|
||
| if (executeArgs.upgrade) { | ||
| args.push('--upgrade'); | ||
| } | ||
|
|
Comment on lines
+19
to
+26
| protected buildCommand(executeArgs: UninstallExecuteArgs): string[] { | ||
| return ['remove', '-y', ...executeArgs.packages.map((pkg) => pkg.packageName)]; | ||
| } | ||
|
|
||
| async execute(executeArgs: UninstallExecuteArgs): Promise<void> { | ||
| const args = this.buildCommand(executeArgs); | ||
|
|
||
| await runCondaExecutable(args, this.log, executeArgs.cancellationToken); |
Comment on lines
+222
to
+226
| const versionStrings = await availableVersionsCmd.execute({ | ||
| packageName, | ||
| pythonVersion: environment.version, | ||
| }); | ||
| return versionStrings.map((v) => parse(v)).filter((parsed) => parsed !== undefined) as Pep440Version[]; |
Comment on lines
+169
to
173
| log: this.log, | ||
| }); | ||
| const versionStrings = await availableVersionsCmd.execute({ packageName, pythonVersion: '' }); | ||
| return versionStrings.map((v) => parse(v)).filter((parsed) => parsed !== undefined) as Pep440Version[]; | ||
| } catch { |
Comment on lines
+153
to
+156
| "executionTimeout": { | ||
| "type": "integer", | ||
| "description": "Timeout in milliseconds (0 = no timeout). Default: 5 minutes.", | ||
| "default": 300000, |
Comment on lines
+30
to
+34
| const parser = (output: string): void => { | ||
| const match = output.match(/{[\s\S]*}/); | ||
| if (!match) { | ||
| availableVersions = []; | ||
| return; |
Comment on lines
+75
to
+79
| const output = await runPython( | ||
| this.pythonExecutable, | ||
| args, | ||
| undefined, | ||
| this.log, |
Comment on lines
+107
to
+111
| const output = await runPython( | ||
| this.pythonExecutable, | ||
| args, | ||
| undefined, | ||
| this.log, |
Comment on lines
+17
to
+21
| **File**: `src/managers/builtin/commands/commandSettings.ts` | ||
|
|
||
| ```typescript | ||
| interface CommandConstructorOptions { | ||
| pythonExecutable: string; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Doing some streamlining to the commands so they can be reused, extended and cached with no utils functions. So far this is an experiment
Summary
This PR completes the comprehensive refactoring of package manager command handling to use a clean three-level command class architecture.
Changes
✅ Completed
Architecture
Three-Level Command Class Hierarchy:
All package managers now use consistent pattern:
Testing
Files Modified
Key files: