Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0fa9ad6
test: cover registered package manager lifecycles (Fixes #1701)
edvilme Aug 7, 2026
596583f
fix: address review feedback (PR #1704)
edvilme Aug 7, 2026
537f5fc
test: add package manager integration lifecycle
edvilme Aug 8, 2026
98396c9
Revert "fix: address review feedback (PR #1704)"
edvilme Aug 8, 2026
4d32f1d
Revert "test: cover registered package manager lifecycles (Fixes #1701)"
edvilme Aug 8, 2026
bf42d8a
feat: expose registered package manager
edvilme Aug 8, 2026
ae5e1d3
fix: stabilize package manager integration tests
edvilme Aug 11, 2026
fa2f858
Add headless option for PackageManager
edvilme Aug 11, 2026
cb454c9
Update api version
edvilme Aug 12, 2026
31d186d
fix: support headless environment removal
edvilme Aug 12, 2026
0078986
fix: skip unavailable package version lookup
edvilme Aug 12, 2026
a8f4e67
Merge remote-tracking branch 'origin/main' into package-manager-integ…
edvilme Aug 13, 2026
d6f0a7a
Merge branch 'main' into package-manager-integration-coverage
edvilme Aug 14, 2026
3545cb5
fix: suppress headless pip refresh prompts
edvilme Aug 17, 2026
90995e6
refactor: use package facade in integration test
edvilme Aug 17, 2026
ebfe2a3
test: cover pip and uv package paths
edvilme Aug 17, 2026
f599840
test: verify environment cleanup
edvilme Aug 17, 2026
d5f5979
test: isolate package manager profiles
edvilme Aug 17, 2026
3b5af0e
Merge branch 'main' into package-manager-integration-coverage
edvilme Aug 17, 2026
57f3026
fix: preserve pip package refresh failures
edvilme Aug 17, 2026
6a80878
fix: propagate conda package failures
edvilme Aug 17, 2026
7039795
fix: sync sample package options
edvilme Aug 17, 2026
4f464b3
test: enforce package manager coverage
edvilme Aug 17, 2026
3a4f358
test: harden package manager lifecycle
edvilme Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/instructions/testing-workflow.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -606,3 +606,4 @@ envConfig.inspect
- **Never skip tests to hide infrastructure problems**: If tests require native binaries (like `pet`), the CI workflow must build/download them. Skipping tests when infrastructure is missing gives false confidence. Build from source (like vscode-python does) rather than skipping. Tests should fail clearly when something is wrong (2)
- **No retries for masking flakiness**: Mocha `retries` should not be used to mask test flakiness. If a test is flaky, fix the root cause. Retries hide real issues and slow down CI (1)
- **pet binary is required for environment manager registration**: The smoke/E2E/integration tests require the `pet` binary from `microsoft/python-environment-tools` to be built and placed in `python-env-tools/bin/`. Without it, `waitForApiReady()` will timeout because managers never register. CI must build pet from source using `cargo build --release --package pet` (2)
- **Check exact project registration with `getPythonProjects()`**: `getPythonProject(uri)` can return a containing parent project, so it cannot prove that a nested project was registered or unregistered (1)
8 changes: 8 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,14 @@ jobs:
if: runner.os != 'Linux'
run: npm run integration-test

- name: Run Package Manager Network Integration Tests
if: runner.os == 'Linux' && matrix.python-version == '3.12'
uses: GabrielBB/xvfb-action@86d97bde4a65fe9b290c0b3fb92c2c4ed0e5302d # v1.6
env:
VSC_PYTHON_PACKAGE_NETWORK_TEST: '1'
with:
run: npm run integration-test -- --grep "Package Manager"

integration-tests-multiroot:
name: Integration Tests (Multi-Root)
runs-on: ${{ matrix.os }}
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/push-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,11 @@ jobs:
- name: Run Integration Tests (non-Linux)
if: runner.os != 'Linux'
run: npm run integration-test

- name: Run Package Manager Network Integration Tests
if: runner.os == 'Linux' && matrix.python-version == '3.12'
uses: GabrielBB/xvfb-action@86d97bde4a65fe9b290c0b3fb92c2c4ed0e5302d # v1.6
env:
VSC_PYTHON_PACKAGE_NETWORK_TEST: '1'
with:
run: npm run integration-test -- --grep "Package Manager"
7 changes: 7 additions & 0 deletions api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to the `@vscode/python-environments` API package are documen
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).

## [1.2.0]

### Added

- Added `PackageManagementInteractionOptions` with an optional `runHeadless?: boolean` property, mixed into `PackageManagementOptions`. When `true`, package management operations run without any user prompts or interaction — steps that would normally require input, such as selecting packages to install when none are specified, are skipped instead of prompting — for automated or headless scenarios such as integration tests.
- Added `RemoveEnvironmentOptions` with an optional `runHeadless?: boolean` property to remove environments without a confirmation prompt in automated or headless scenarios.

## [1.1.0]

### Added
Expand Down
4 changes: 2 additions & 2 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@vscode/python-environments",
"description": "An API facade for the Python Environments extension in VS Code",
"version": "1.1.0",
"version": "1.2.0",
"author": {
"name": "Microsoft Corporation"
},
Expand Down
111 changes: 68 additions & 43 deletions examples/sample1/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,17 @@ export interface QuickCreateConfig {
readonly detail?: string;
}

/**
* Options controlling environment removal.
*/
export interface RemoveEnvironmentOptions {
/**
* When `true`, removes the environment without prompting for confirmation.
* Intended for automated or headless scenarios. Defaults to `false`.
*/
runHeadless?: boolean;
}

/**
* Interface representing an environment manager.
*/
Expand Down Expand Up @@ -392,7 +403,7 @@ export interface EnvironmentManager {
* @param environment - The Python environment to remove.
* @returns A promise that resolves when the environment is removed.
*/
remove?(environment: PythonEnvironment): Promise<void>;
remove?(environment: PythonEnvironment, options?: RemoveEnvironmentOptions): Promise<void>;

/**
* Refreshes the list of Python environments within the specified scope.
Expand Down Expand Up @@ -739,49 +750,62 @@ export interface GetPackagesOptions {
}

/**
* Options for package management.
* Options controlling user interaction during package management operations.
*/
export type PackageManagementOptions =
| {
/**
* Upgrade the packages if it is already installed.
*/
upgrade?: boolean;

/**
* Show option to skip package installation
*/
showSkipOption?: boolean;
/**
* The list of packages to install.
*/
install: string[];

/**
* The list of packages to uninstall.
*/
uninstall?: string[];
}
| {
/**
* Upgrade the packages if it is already installed.
*/
upgrade?: boolean;

/**
* Show option to skip package installation
*/
showSkipOption?: boolean;
/**
* The list of packages to install.
*/
install?: string[];
export interface PackageManagementInteractionOptions {
/**
* When `true`, the package management operation runs without any user prompts or
* interaction and relies solely on the packages provided in the options. Any step
* that would normally require user input — such as selecting packages to install
* when none are specified — is skipped instead of prompting the user. Intended for
* automated or headless scenarios such as integration tests. Defaults to `false`.
*/
runHeadless?: boolean;
}

/**
* The list of packages to uninstall.
*/
uninstall: string[];
};
export type PackageManagementOptions = PackageManagementInteractionOptions &
(
| {
/**
* Upgrade the packages if it is already installed.
*/
upgrade?: boolean;

/**
* Show option to skip package installation or uninstallation.
*/
showSkipOption?: boolean;
/**
* The list of packages to install.
*/
install: string[];

/**
* The list of packages to uninstall.
*/
uninstall?: string[];
}
| {
/**
* Upgrade the packages if it is already installed.
*/
upgrade?: boolean;

/**
* Show option to skip package installation or uninstallation.
*/
showSkipOption?: boolean;
/**
* The list of packages to install.
*/
install?: string[];

/**
* The list of packages to uninstall.
*/
uninstall: string[];
}
);

/**
* Options for creating a Python environment.
Expand Down Expand Up @@ -881,9 +905,10 @@ export interface PythonEnvironmentManagementApi {
* Remove a Python environment.
*
* @param environment The Python environment to remove.
* @param options Optional parameters controlling environment removal.
* @returns A promise that resolves when the environment has been removed.
*/
removeEnvironment(environment: PythonEnvironment): Promise<void>;
removeEnvironment(environment: PythonEnvironment, options?: RemoveEnvironmentOptions): Promise<void>;
}
Comment thread
edvilme marked this conversation as resolved.

export interface PythonEnvironmentsApi {
Expand Down
112 changes: 70 additions & 42 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,17 @@ export interface QuickCreateConfig {
readonly detail?: string;
}

/**
* Options controlling environment removal.
*/
export interface RemoveEnvironmentOptions {
/**
* When `true`, removes the environment without prompting for confirmation.
* Intended for automated or headless scenarios. Defaults to `false`.
*/
runHeadless?: boolean;
}

/**
* Interface representing an environment manager.
*
Expand Down Expand Up @@ -425,7 +436,7 @@ export interface EnvironmentManager {
* Invoked to delete the given environment. Typical triggers include an explicit user
* action (such as a "Delete Environment" command) and programmatic removal via the API.
*/
remove?(environment: PythonEnvironment): Promise<void>;
remove?(environment: PythonEnvironment, options?: RemoveEnvironmentOptions): Promise<void>;

/**
* Refreshes the list of Python environments within the specified scope.
Expand Down Expand Up @@ -872,47 +883,63 @@ export interface GetPackagesOptions {
skipCache?: boolean;
}

export type PackageManagementOptions =
| {
/**
* Upgrade the packages if they are already installed.
*/
upgrade?: boolean;

/**
* Show option to skip package installation or uninstallation.
*/
showSkipOption?: boolean;
/**
* The list of packages to install.
*/
install: string[];

/**
* The list of packages to uninstall.
*/
uninstall?: string[];
}
| {
/**
* Upgrade the packages if they are already installed.
*/
upgrade?: boolean;

/**
* Show option to skip package installation or uninstallation.
*/
showSkipOption?: boolean;
/**
* The list of packages to install.
*/
install?: string[];
/**
* Options controlling user interaction during package management operations.
*/
export interface PackageManagementInteractionOptions {
/**
* When `true`, the package management operation runs without any user prompts or
* interaction and relies solely on the packages provided in the options. Any step
* that would normally require user input — such as selecting packages to install
* when none are specified — is skipped instead of prompting the user. Intended for
Comment thread
edvilme marked this conversation as resolved.
* automated or headless scenarios such as integration tests. Defaults to `false`.
*/
runHeadless?: boolean;
}

/**
* The list of packages to uninstall.
*/
uninstall: string[];
};
export type PackageManagementOptions = PackageManagementInteractionOptions &
(
| {
/**
* Upgrade the packages if they are already installed.
*/
upgrade?: boolean;

/**
* Show option to skip package installation or uninstallation.
*/
showSkipOption?: boolean;
/**
* The list of packages to install.
*/
install: string[];

/**
* The list of packages to uninstall.
*/
uninstall?: string[];
}
| {
/**
* Upgrade the packages if they are already installed.
*/
upgrade?: boolean;

/**
* Show option to skip package installation or uninstallation.
*/
showSkipOption?: boolean;
/**
* The list of packages to install.
*/
install?: string[];

/**
* The list of packages to uninstall.
*/
uninstall: string[];
}
);

/**
* Options for creating a Python environment.
Expand Down Expand Up @@ -1011,9 +1038,10 @@ export interface PythonEnvironmentManagementApi {
* Remove a Python environment.
*
* @param environment The Python environment to remove.
* @param options Optional parameters controlling environment removal.
* @returns A promise that resolves when the environment has been removed.
*/
removeEnvironment(environment: PythonEnvironment): Promise<void>;
removeEnvironment(environment: PythonEnvironment, options?: RemoveEnvironmentOptions): Promise<void>;
}

export interface PythonEnvironmentsApi {
Expand Down
15 changes: 15 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,21 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
},
);
}),
...(process.env.VSC_PYTHON_INTEGRATION_TEST === '1'
? [
commands.registerCommand('python-envs.test.getPackageManagerIds', () =>
envManagers.packageManagers.map((manager) => manager.id),
),
commands.registerCommand(
'python-envs.test.getDirectPackageNames',
async (environment: PythonEnvironment) => {
const manager = envManagers.getPackageManager(environment);
const names = await manager?.getDirectPackageNames?.(environment);
return names ? Array.from(names) : undefined;
},
),
]
: []),
commands.registerCommand('python-envs.searchSettings', async () => {
await openSearchSettings();
}),
Expand Down
Loading
Loading