Skip to content

[WIP] Complete package manager command class refactoring#1621

Draft
edvilme wants to merge 25 commits into
mainfrom
package-manager-command-refactor
Draft

[WIP] Complete package manager command class refactoring#1621
edvilme wants to merge 25 commits into
mainfrom
package-manager-command-refactor

Conversation

@edvilme

@edvilme edvilme commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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

  • All Pip/UV command classes (12 total) - created, documented, integrated
  • All Conda command classes (5 total) - created, documented, integrated
  • All Poetry command classes (5 total) - created, documented, integrated
  • All package managers refactored to use command classes directly (no utilities)
  • Comprehensive JSDoc documentation on all 22 command classes with parsed commands and official documentation links
  • Dead code verification - confirmed no orphaned code exists
  • Removed all intermediate utility wrappers (execPipList, refreshPipPackages, etc.)

Architecture

Three-Level Command Class Hierarchy:

  • Level 1 (Base): \PackageManagerCommand\ abstract class with minimal interface (pythonExecutable, log, cancellationToken)
  • Level 2 (Templates): 6 abstract template classes defining per-command-type behavior (Install, Uninstall, List, Version, AvailableVersions, ListDirectNames)
  • Level 3 (Concrete): 22 package-manager-specific implementations

All package managers now use consistent pattern:

  • Pip/UV: 6 command types × 2 implementations = 12 classes
  • Conda: 5 command types (no ListDirectNames) = 5 classes
  • Poetry: 5 command types (no AvailableVersions) = 5 classes

Testing

  • ✅ 1,269 unit tests passing
  • ✅ 0 failures
  • ✅ Clean compilation with no errors

Files Modified

  • 118 files changed
  • 10,974 insertions
  • 1,331 deletions

Key files:

  • src/managers/builtin/pipPackageManager.ts - Pip/UV operations
  • src/managers/conda/condaPackageManager.ts - Conda operations
  • src/managers/poetry/poetryPackageManager.ts - Poetry operations
  • src/managers//commands/.ts - All command classes
  • src/managers/builtin/pipUtils.ts - Direct command class usage in workspace packages
  • src/managers/builtin/utils.ts - Removed utility wrappers

edvilme added 17 commits July 1, 2026 13:15
- 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
@edvilme edvilme added the debt Code quality issues label Jul 5, 2026
edvilme added 2 commits July 5, 2026 05:53
- 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
@edvilme edvilme changed the title Complete package manager command class refactoring [WIP] Complete package manager command class refactoring Jul 5, 2026
edvilme added 6 commits July 5, 2026 06:03
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

debt Code quality issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant