|
1 | 1 | import type { |
| 2 | + Capabilities, |
| 3 | + CapabilityContext, |
| 4 | + EnvironmentManager, |
| 5 | + EnvironmentManagerCapability, |
2 | 6 | PackageManager, |
| 7 | + PackageManagerCapability, |
3 | 8 | Pep440Version, |
4 | 9 | PythonEnvironment, |
5 | 10 | PythonEnvironmentApi, |
6 | 11 | PythonPackageGetterApi, |
| 12 | + Support, |
7 | 13 | } from '@vscode/python-environments'; |
8 | 14 | import { |
| 15 | + EnvironmentCapability, |
| 16 | + getLegacyEnvironmentManagerCapability, |
| 17 | + getLegacyPackageManagerCapability, |
9 | 18 | isPackageVersionLookupNotSupportedError, |
| 19 | + PackageCapability, |
10 | 20 | PackageVersionLookupNotSupportedError, |
11 | 21 | PythonEnvironments, |
| 22 | + resolveEnvironmentManagerCapability, |
| 23 | + resolvePackageManagerCapability, |
| 24 | + supportedCapability, |
12 | 25 | } from '@vscode/python-environments'; |
13 | 26 |
|
14 | 27 | type Equal<Left, Right> = |
15 | 28 | (<Value>() => Value extends Left ? 1 : 2) extends <Value>() => Value extends Right ? 1 : 2 ? true : false; |
16 | 29 |
|
| 30 | +// Compile-only fixture shared by modern and legacy consumers; not executed. |
| 31 | +declare const api: PythonEnvironmentApi; |
| 32 | +declare const environment: PythonEnvironment; |
| 33 | +PythonEnvironments.api() satisfies Promise<PythonEnvironmentApi>; |
| 34 | + |
| 35 | +// Package version lookup contracts. |
17 | 36 | type AvailableVersionsReturn = ReturnType<PythonPackageGetterApi['getPackageAvailableVersions']>; |
18 | 37 | type RefreshReturn = ReturnType<PackageManager['refresh']>; |
19 | 38 |
|
20 | | -const availableVersionsReturnIsExact: Equal<AvailableVersionsReturn, Promise<Pep440Version[] | undefined>> = true; |
21 | | -const refreshReturnIsExact: Equal<RefreshReturn, Promise<void>> = true; |
| 39 | +true satisfies Equal<AvailableVersionsReturn, Promise<Pep440Version[] | undefined>>; |
| 40 | +true satisfies Equal<RefreshReturn, Promise<void>>; |
22 | 41 |
|
23 | | -declare const api: PythonPackageGetterApi; |
24 | | -declare const environment: PythonEnvironment; |
25 | | -const legacyAvailableVersions: Promise<Pep440Version[] | undefined> = api.getPackageAvailableVersions( |
26 | | - environment, |
27 | | - 'example', |
28 | | -); |
29 | | -const explicitLegacyAvailableVersions: Promise<Pep440Version[] | undefined> = api.getPackageAvailableVersions( |
30 | | - environment, |
31 | | - 'example', |
32 | | - { errorMode: 'legacy' }, |
33 | | -); |
34 | | -const throwingAvailableVersions: Promise<Pep440Version[]> = api.getPackageAvailableVersions(environment, 'example', { |
35 | | - errorMode: 'throw', |
36 | | -}); |
37 | | -const runtimeApi: Promise<PythonEnvironmentApi> = PythonEnvironments.api(); |
38 | | - |
39 | | -// The unsupported-capability error is part of the public contract: it is constructible, extends |
40 | | -// Error, and exposes a stable string-literal `code` discriminator. |
| 42 | +api.getPackageAvailableVersions(environment, 'example') satisfies Promise<Pep440Version[] | undefined>; |
| 43 | +api.getPackageAvailableVersions(environment, 'example', { |
| 44 | + errorMode: 'legacy', |
| 45 | +}) satisfies Promise<Pep440Version[] | undefined>; |
| 46 | +api.getPackageAvailableVersions(environment, 'example', { errorMode: 'throw' }) satisfies Promise<Pep440Version[]>; |
| 47 | + |
| 48 | +// Public error construction, discriminator, and narrowing. |
41 | 49 | const lookupError = new PackageVersionLookupNotSupportedError('unsupported'); |
42 | | -const lookupErrorIsError: Error = lookupError; |
43 | | -const lookupErrorCodeIsExact: Equal<typeof lookupError.code, 'PackageVersionLookupNotSupported'> = true; |
| 50 | +lookupError satisfies Error; |
| 51 | +true satisfies Equal<typeof lookupError.code, 'PackageVersionLookupNotSupported'>; |
44 | 52 |
|
45 | | -// The type guard narrows unknown values via the stable discriminator (bundle-boundary safe). |
46 | 53 | declare const maybeError: unknown; |
47 | | -const guardNarrows: boolean = isPackageVersionLookupNotSupportedError(maybeError) |
48 | | - ? maybeError.code === 'PackageVersionLookupNotSupported' |
49 | | - : false; |
50 | | - |
51 | | -void availableVersionsReturnIsExact; |
52 | | -void refreshReturnIsExact; |
53 | | -void legacyAvailableVersions; |
54 | | -void explicitLegacyAvailableVersions; |
55 | | -void throwingAvailableVersions; |
56 | | -void runtimeApi; |
57 | | -void lookupErrorIsError; |
58 | | -void lookupErrorCodeIsExact; |
59 | | -void guardNarrows; |
| 54 | +if (isPackageVersionLookupNotSupportedError(maybeError)) { |
| 55 | + maybeError.code satisfies 'PackageVersionLookupNotSupported'; |
| 56 | +} |
| 57 | + |
| 58 | +// Legacy providers may omit advertisements; advertised maps must include every key without defaults. |
| 59 | +true satisfies Equal<typeof PackageCapability.ListDirect, 'packages.list.direct'>; |
| 60 | +true satisfies Equal<typeof PackageCapability.FetchAvailableVersions, 'packages.fetchversions'>; |
| 61 | +const fullPackageCapabilities: Capabilities<PackageManagerCapability> = { |
| 62 | + [PackageCapability.List]: supportedCapability, |
| 63 | + [PackageCapability.Install]: supportedCapability, |
| 64 | + [PackageCapability.Uninstall]: supportedCapability, |
| 65 | + [PackageCapability.Upgrade]: supportedCapability, |
| 66 | + [PackageCapability.ListDirect]: supportedCapability, |
| 67 | + [PackageCapability.FetchAvailableVersions]: supportedCapability, |
| 68 | +}; |
| 69 | +const fullEnvironmentCapabilities: Capabilities<EnvironmentManagerCapability> = { |
| 70 | + [EnvironmentCapability.List]: supportedCapability, |
| 71 | + [EnvironmentCapability.Resolve]: supportedCapability, |
| 72 | + [EnvironmentCapability.GetSelected]: supportedCapability, |
| 73 | + [EnvironmentCapability.SetSelected]: supportedCapability, |
| 74 | + [EnvironmentCapability.Create]: supportedCapability, |
| 75 | + [EnvironmentCapability.Remove]: supportedCapability, |
| 76 | +}; |
| 77 | + |
| 78 | +const legacyPackageManager: PackageManager = { |
| 79 | + name: 'legacy', |
| 80 | + manage: async () => {}, |
| 81 | + refresh: async () => {}, |
| 82 | + getPackages: async () => [], |
| 83 | +}; |
| 84 | +const legacyEnvironmentManager: EnvironmentManager = { |
| 85 | + name: 'legacy', |
| 86 | + preferredPackageManagerId: 'example:legacy', |
| 87 | + refresh: async () => {}, |
| 88 | + getEnvironments: async () => [], |
| 89 | + get: async () => undefined, |
| 90 | + set: async () => {}, |
| 91 | + resolve: async () => undefined, |
| 92 | +}; |
| 93 | + |
| 94 | +getLegacyPackageManagerCapability(legacyPackageManager, PackageCapability.Install) satisfies Promise<Support>; |
| 95 | +getLegacyEnvironmentManagerCapability(legacyEnvironmentManager, EnvironmentCapability.Create) satisfies Promise<Support>; |
| 96 | + |
| 97 | +// Capability authoring and raw-provider resolution. |
| 98 | +const advertised: Capabilities<PackageManagerCapability> = { |
| 99 | + ...fullPackageCapabilities, |
| 100 | + [PackageCapability.Upgrade]: (context: CapabilityContext) => |
| 101 | + resolvePackageManagerCapability(legacyPackageManager, PackageCapability.Install, context), |
| 102 | + [PackageCapability.ListDirect]: async (_context) => ({ supported: false, reason: 'Unavailable' }), |
| 103 | +}; |
| 104 | +const managerWithCapabilities: PackageManager = { ...legacyPackageManager, capabilities: advertised }; |
| 105 | +resolvePackageManagerCapability(managerWithCapabilities, PackageCapability.ListDirect, { environment }) satisfies Promise<Support>; |
| 106 | +resolveEnvironmentManagerCapability(legacyEnvironmentManager, EnvironmentCapability.Create, { |
| 107 | + scope: 'global', |
| 108 | +}) satisfies Promise<Support>; |
| 109 | + |
| 110 | +// Public queries and support-result narrowing. |
| 111 | +api.getPackageManagerCapability(environment, PackageCapability.List) satisfies Promise<Support>; |
| 112 | +api.getEnvironmentManagerCapability('example:legacy', EnvironmentCapability.List, { scope: 'all' }) satisfies Promise<Support>; |
| 113 | +declare const support: Support; |
| 114 | +support.isLegacy satisfies boolean | undefined; |
| 115 | +if (!support.supported) { |
| 116 | + support.reason satisfies string; |
| 117 | +} |
| 118 | + |
| 119 | +// Invalid capability keys, advertisements, and context must remain compile errors. |
| 120 | +// @ts-expect-error Legacy environment inference requires an environment capability. |
| 121 | +getLegacyEnvironmentManagerCapability(legacyEnvironmentManager, PackageCapability.List); |
| 122 | +// @ts-expect-error Legacy package inference requires a package capability. |
| 123 | +getLegacyPackageManagerCapability(legacyPackageManager, EnvironmentCapability.List); |
| 124 | +// @ts-expect-error Package keys do not belong to the environment capability domain. |
| 125 | +PackageCapability.List satisfies EnvironmentManagerCapability; |
| 126 | +// @ts-expect-error Environment keys do not belong to the package capability domain. |
| 127 | +EnvironmentCapability.List satisfies PackageManagerCapability; |
| 128 | +// @ts-expect-error Advertisements must be asynchronous functions, not static Support values. |
| 129 | +({ [PackageCapability.List]: { supported: true } } satisfies Capabilities<PackageManagerCapability>); |
| 130 | +// @ts-expect-error Advertisements cannot introduce arbitrary keys. |
| 131 | +({ 'packages.unknown': async () => ({ supported: true }) } satisfies Capabilities<PackageManagerCapability>); |
| 132 | +// @ts-expect-error Operation options are not capability query context. |
| 133 | +({ createOptions: { quickCreate: true } } satisfies CapabilityContext); |
| 134 | +declare const arbitraryKey: string; |
| 135 | +// @ts-expect-error A general string is not a declared environment capability. |
| 136 | +resolveEnvironmentManagerCapability(legacyEnvironmentManager, arbitraryKey); |
| 137 | +// @ts-expect-error A general string is not a declared package capability. |
| 138 | +resolvePackageManagerCapability(legacyPackageManager, arbitraryKey); |
| 139 | +// @ts-expect-error Public environment queries require a catalog key. |
| 140 | +api.getEnvironmentManagerCapability('example:legacy', arbitraryKey); |
| 141 | +// @ts-expect-error Public package queries require a catalog key. |
| 142 | +api.getPackageManagerCapability(environment, arbitraryKey); |
| 143 | +// @ts-expect-error Capability dictionaries have no arbitrary string index signature. |
| 144 | +fullPackageCapabilities[arbitraryKey]; |
| 145 | +// @ts-expect-error Capability dictionaries have no arbitrary string index signature. |
| 146 | +fullEnvironmentCapabilities[arbitraryKey]; |
| 147 | +// @ts-expect-error Capability dictionaries must advertise every catalog key; partial maps are rejected. |
| 148 | +({ [PackageCapability.List]: async () => ({ supported: true }) } satisfies Capabilities<PackageManagerCapability>); |
0 commit comments