Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 8 additions & 2 deletions .github/workflows/pr-file-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,21 @@ jobs:
- name: 'Public API changes require a version bump'
uses: brettcannon/check-for-changed-files@d85c64d17b3c1d0ac57c9cc46ea39399b0d6fa71 # v1.2.2
with:
prereq-pattern: 'src/api.ts'
prereq-pattern: |
src/api.ts
src/types.ts
src/publicErrors.ts
file-pattern: 'api/package.json'
skip-label: 'skip api version'
failure-message: 'The public API (${prereq-pattern}) was changed without bumping the package version in ${file-pattern} (the ${skip-label} label can be used to pass this check)'

- name: 'Public API changes require a changelog entry'
uses: brettcannon/check-for-changed-files@d85c64d17b3c1d0ac57c9cc46ea39399b0d6fa71 # v1.2.2
with:
prereq-pattern: 'src/api.ts'
prereq-pattern: |
src/api.ts
src/types.ts
src/publicErrors.ts
file-pattern: 'api/CHANGELOG.md'
skip-label: 'skip api changelog'
failure-message: 'The public API (${prereq-pattern}) was changed without a changelog entry in ${file-pattern} (the ${skip-label} label can be used to pass this check)'
10 changes: 5 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope

## Public API package (`@vscode/python-environments`)

The npm package under [`api/`](./api) is the public API facade other extensions consume. Its entry point, `api/src/main.ts`, is a **copy** of [`src/api.ts`](./src/api.ts) — the single source of truth — and is **not committed** (see [`api/.gitignore`](./api/.gitignore)).
The npm package under [`api/`](./api) is the public API facade other extensions consume. Its sources — `api/src/main.ts`, `api/src/types.ts`, and `api/src/publicErrors.ts` — are **copies** of [`src/api.ts`](./src/api.ts), [`src/types.ts`](./src/types.ts), and [`src/publicErrors.ts`](./src/publicErrors.ts) respectively — the single sources of truth — and are **not committed** (see [`api/.gitignore`](./api/.gitignore)).

- Edit the API only in `src/api.ts`. This file contains the full public surface, including the runtime `PythonEnvironments.api()` helper and `EXTENSION_ID`. `api/src/main.ts` is a build artifact — never edit or commit it.
- `api/src/main.ts` is produced by the publish pipeline ([`build/azure-pipeline.npm.yml`](./build/azure-pipeline.npm.yml)), which copies `src/api.ts` to `api/src/main.ts` before compiling. The api package is therefore built in CI only; to build it locally, copy the file first (e.g. `cp src/api.ts api/src/main.ts`).
- `src/api.ts` itself is validated on every PR by the extension's own lint and TypeScript compile.
- **Versioning:** the published package version in [`api/package.json`](./api/package.json) must always match the extension version in [`package.json`](./package.json). CI enforces this via [`scripts/compare_package_versions.py`](./scripts/compare_package_versions.py). Additionally, any PR that edits `src/api.ts` must bump `api/package.json` (use the `skip api version` label to bypass) and add an entry to [`api/CHANGELOG.md`](./api/CHANGELOG.md) (use the `skip api changelog` label to bypass). When bumping, update both `package.json` files so they stay in sync.
- Edit the public API only in `src/api.ts` (the runtime facade: `PythonEnvironments.api()` helper and `EXTENSION_ID`), `src/types.ts` (public contracts: interfaces, types, enums), and `src/publicErrors.ts` (concrete public error classes and type guards). `api/src/*.ts` files are build artifacts — never edit or commit them.
- `api/src/main.ts`, `api/src/types.ts`, and `api/src/publicErrors.ts` are produced by the publish pipeline ([`build/azure-pipeline.npm.yml`](./build/azure-pipeline.npm.yml)), which copies `src/api.ts` to `api/src/main.ts`, `src/types.ts` to `api/src/types.ts`, and `src/publicErrors.ts` to `api/src/publicErrors.ts` before compiling. The api package is therefore built in CI only; to build it locally, copy the files first (e.g. `cp src/api.ts api/src/main.ts && cp src/types.ts api/src/types.ts && cp src/publicErrors.ts api/src/publicErrors.ts`).
Comment thread
edvilme marked this conversation as resolved.
- `src/api.ts`, `src/types.ts`, and `src/publicErrors.ts` are validated on every PR by the extension's own lint and TypeScript compile.
- **Versioning and compatibility:** the published package version in [`api/package.json`](./api/package.json) is maintained independently of the extension version in [`package.json`](./package.json) — the two do not need to match. Compatibility is based on the API shape exported by the installed Python Environments extension at runtime. Package updates must preserve backwards-compatible contracts unless the API package version intentionally communicates a breaking change; consumers should treat newly added members as optional when they may run against older installed extension versions. Any PR that edits `src/api.ts`, `src/types.ts`, or `src/publicErrors.ts` must bump `api/package.json` (use the `skip api version` label to bypass) and add an entry to [`api/CHANGELOG.md`](./api/CHANGELOG.md) (use the `skip api changelog` label to bypass).

## Questions or Issues?

Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,7 @@ The Python Environments extension was built to provide a cohesive and user frien

### API Reference (proposed)

See [api.ts](https://github.com/microsoft/vscode-python-environments/blob/main/src/api.ts) for the full list of Extension APIs.

To consume these APIs you can look at the example here: [API Consumption Examples](https://github.com/microsoft/vscode-python-environments/blob/main/examples/README.md)
See [api.ts](https://github.com/microsoft/vscode-python-environments/blob/main/src/api.ts) for the runtime API facade and [types.ts](https://github.com/microsoft/vscode-python-environments/blob/main/src/types.ts) for the public API contracts. Extension authors can consume these contracts from the `@vscode/python-environments` npm package.

### Callable Commands

Expand Down
9 changes: 6 additions & 3 deletions api/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Copied from ../src/api.ts by the publish pipeline (build/azure-pipeline.npm.yml).
# This is the published package entry point; it is produced at publish time and
# intentionally NOT committed. src/api.ts is the single source of truth.
# Copied from ../src/api.ts, ../src/types.ts, ../src/publicErrors.ts by the publish pipeline
# (build/azure-pipeline.npm.yml). These are the published package sources; they are produced
# at publish time and intentionally NOT committed. src/api.ts, src/types.ts, and
# src/publicErrors.ts are the single sources of truth.
src/main.ts
src/types.ts
src/publicErrors.ts
Comment thread
edvilme marked this conversation as resolved.
6 changes: 6 additions & 0 deletions api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ 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.4.0]

### Changed

- Reorganized the package source into separate API facade, public contract, and public error modules without changing the root package exports.

## [1.3.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.

7 changes: 4 additions & 3 deletions 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.3.0",
"version": "1.4.0",
Comment thread
edvilme marked this conversation as resolved.
"author": {
"name": "Microsoft Corporation"
},
Expand Down Expand Up @@ -39,9 +39,10 @@
"scripts": {
"prepublishOnly": "echo \"⛔ Can only publish from a secure pipeline ⛔\" && node -e \"process.exitCode = 1\"",
"prepack": "npm run all:publish",
"all:publish": "git clean -xfd . && npm install && npm run compile",
"all:publish": "git clean -xfd . && npm install && npm run copy:sources && npm run compile",
"copy:sources": "node ./scripts/copy-sources.cjs",
"compile": "npm run compile:esm && npm run compile:cjs",
"compile:esm": "tsc -b ./tsconfig.esm.json && mve out/esm/main.js out/esm/main.mjs",
"compile:esm": "tsc -b ./tsconfig.esm.json && mve out/esm/main.js out/esm/main.mjs && node -e \"require('fs').writeFileSync('out/esm/package.json', '{\\\"type\\\":\\\"module\\\"}')\"",
"compile:cjs": "tsc -b ./tsconfig.cjs.json && mve out/cjs/main.js out/cjs/main.cjs",
"clean": "node -e \"const fs = require('fs'); fs.rmSync('./out', { recursive: true, force: true });\"",
"test:package": "node ./scripts/test-package.cjs"
Expand Down
25 changes: 25 additions & 0 deletions api/scripts/copy-sources.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// Repopulates the package's generated `src` sources from the single sources of
// truth in `../src`. These files are gitignored and are removed by the
// `git clean -xfd .` step in `all:publish`, so they must be copied back before
// TypeScript can compile. Mirrors the copy step in build/azure-pipeline.npm.yml.

const fs = require('node:fs');
const path = require('node:path');

const packageRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(packageRoot, '..');
const srcDir = path.join(packageRoot, 'src');

const sources = [
{ from: path.join(repoRoot, 'src', 'api.ts'), to: path.join(srcDir, 'main.ts') },
{ from: path.join(repoRoot, 'src', 'types.ts'), to: path.join(srcDir, 'types.ts') },
{ from: path.join(repoRoot, 'src', 'publicErrors.ts'), to: path.join(srcDir, 'publicErrors.ts') },
];

fs.mkdirSync(srcDir, { recursive: true });
for (const { from, to } of sources) {
fs.copyFileSync(from, to);
}
53 changes: 49 additions & 4 deletions api/scripts/test-package.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,23 @@ try {
}

const installedPackageRoot = path.join(testRoot, 'node_modules', '@vscode', 'python-environments');
const vscodeStubRoot = path.join(testRoot, 'node_modules', 'vscode');
fs.mkdirSync(vscodeStubRoot, { recursive: true });
fs.writeFileSync(path.join(vscodeStubRoot, 'package.json'), JSON.stringify({ main: 'index.js' }));
fs.writeFileSync(
path.join(vscodeStubRoot, 'index.js'),
[
"const runtimeApi = { getEnvironments: async () => [] };",
"const extension = {",
" isActive: false,",
" exports: undefined,",
" packageJSON: { version: '1.37.0' },",
" activate: async () => { extension.isActive = true; extension.exports = runtimeApi; return runtimeApi; },",
"};",
'exports.__runtimeApi = runtimeApi;',
'exports.extensions = { getExtension: () => extension };',
].join('\n'),
);
const installedPackageJson = JSON.parse(fs.readFileSync(path.join(installedPackageRoot, 'package.json'), 'utf8'));
assert.strictEqual(installedPackageJson.main, './out/cjs/main.cjs');
assert.strictEqual(installedPackageJson.types, './out/cjs/main.d.ts');
Expand All @@ -104,24 +121,52 @@ try {
}

const requireFromConsumer = createRequire(path.join(testRoot, 'legacy', 'consumer.cjs'));
const commonJsModule = requireFromConsumer('@vscode/python-environments');
assert.strictEqual(
typeof commonJsModule.PythonEnvironments.api,
'function',
'CommonJS consumers should load the package runtime facade',
);
execFileSync(
process.execPath,
[
'--eval',
[
"const packageModule = require('@vscode/python-environments');",
"const vscode = require('vscode');",
"(async () => {",
' const api = await packageModule.PythonEnvironments.api();',
' if (api !== vscode.__runtimeApi) process.exit(1);',
'})().catch(() => process.exit(1));',
].join('\n'),
],
{
cwd: path.join(testRoot, 'legacy'),
encoding: 'utf8',
},
);
assert.strictEqual(
canonicalPath(requireFromConsumer.resolve('@vscode/python-environments')),
canonicalPath(path.join(installedPackageRoot, installedPackageJson.exports.require.default)),
'CommonJS consumers should resolve the packaged CommonJS entry point',
);

const esmEntryPoint = execFileSync(
const esmModuleCheck = execFileSync(
process.execPath,
['--input-type=module', '--eval', "console.log(import.meta.resolve('@vscode/python-environments'))"],
[
'--input-type=module',
'--eval',
"const packageModule = await import('@vscode/python-environments'); const vscode = await import('vscode'); if (typeof packageModule.PythonEnvironments.api !== 'function') process.exit(1); const api = await packageModule.PythonEnvironments.api(); if (api !== vscode.default.__runtimeApi) process.exit(1); console.log(import.meta.resolve('@vscode/python-environments'));",
],
{
cwd: path.join(testRoot, 'modern'),
encoding: 'utf8',
},
).trim();
assert.strictEqual(
canonicalPath(fileURLToPath(esmEntryPoint)),
canonicalPath(fileURLToPath(esmModuleCheck)),
canonicalPath(path.join(installedPackageRoot, installedPackageJson.exports.import.default)),
'ES module consumers should resolve the packaged ES module entry point',
'ES module consumers should load the packaged runtime facade',
);
} finally {
fs.rmSync(testRoot, { recursive: true, force: true });
Expand Down
4 changes: 4 additions & 0 deletions api/test/consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import type {
PackageManager,
Pep440Version,
PythonEnvironment,
PythonEnvironmentApi,
PythonPackageGetterApi,
} from '@vscode/python-environments';
import {
isPackageVersionLookupNotSupportedError,
PackageVersionLookupNotSupportedError,
PythonEnvironments,
} from '@vscode/python-environments';

type Equal<Left, Right> =
Expand All @@ -32,6 +34,7 @@ const explicitLegacyAvailableVersions: Promise<Pep440Version[] | undefined> = ap
const throwingAvailableVersions: Promise<Pep440Version[]> = api.getPackageAvailableVersions(environment, 'example', {
errorMode: 'throw',
});
const runtimeApi: Promise<PythonEnvironmentApi> = PythonEnvironments.api();

// The unsupported-capability error is part of the public contract: it is constructible, extends
// Error, and exposes a stable string-literal `code` discriminator.
Expand All @@ -50,6 +53,7 @@ void refreshReturnIsExact;
void legacyAvailableVersions;
void explicitLegacyAvailableVersions;
void throwingAvailableVersions;
void runtimeApi;
void lookupErrorIsError;
void lookupErrorCodeIsExact;
void guardNarrows;
4 changes: 2 additions & 2 deletions build/azure-pipeline.npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ extends:
workingDirectory: $(Build.SourcesDirectory)/api
displayName: Install package dependencies

- script: mkdir -p src && cp ../src/api.ts src/main.ts
- script: npm run copy:sources
workingDirectory: $(Build.SourcesDirectory)/api
displayName: Copy src/api.ts to API package entry point
displayName: Copy src/api.ts, src/types.ts, src/publicErrors.ts to API package sources

- script: npm run compile
workingDirectory: $(Build.SourcesDirectory)/api
Expand Down
4 changes: 2 additions & 2 deletions docs/projects-api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
This is how the projects API is designed with the different parts of the project flow. Here `getPythonProjects` is used as an example function but behavior will mirror other getter and setter functions exposed in the API.

1. **API Call:** Extensions can calls `getPythonProjects` on [`PythonEnvironmentApi`](../src/api.ts).
2. **API Implementation:** [`PythonEnvironmentApiImpl`](../src/features/pythonApi.ts) delegates to its internal project manager.
3. **Internal API:** The project manager is typed as [`PythonProjectManager`](../src/internal.api.ts).
2. **API Implementation:** [`PythonEnvironmentApiImpl`](../src/extensionApi.ts) delegates to its internal project manager.
3. **Internal API:** The project manager is typed as [`PythonProjectManager`](../src/features/projectManager.ts).
4. **Concrete Implementation:** [`PythonProjectManagerImpl`](../src/features/projectManager.ts) implements the actual logic.
5. **Data Model:** Returns an array of [`PythonProject`](../src/api.ts) objects.

Expand Down
87 changes: 0 additions & 87 deletions examples/README.md

This file was deleted.

11 changes: 0 additions & 11 deletions examples/sample1/.vscode/extensions.json

This file was deleted.

Loading
Loading