Skip to content

Adds info about deprecated extensions to VSCode client API - #1328

Closed
davidgomes wants to merge 1 commit into
eclipse-openvsx:mainfrom
davidgomes:deprecated-extensions-vscode-api
Closed

Adds info about deprecated extensions to VSCode client API#1328
davidgomes wants to merge 1 commit into
eclipse-openvsx:mainfrom
davidgomes:deprecated-extensions-vscode-api

Conversation

@davidgomes

Copy link
Copy Markdown
Contributor

No description provided.

@netomi

netomi commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Looked into whether this matches what the Marketplace actually does. The short answer: the control manifest is the right mechanism and the deprecated shape here is exactly right, but the flags change is inert, and the endpoint as written would break the client that consumes it.

flags is not how the Marketplace signals deprecation

Asked the Marketplace about three extensions it is known to have deprecated:

ms-vscode.vscode-typescript-tslint-plugin   flags=validated, public
eg2.tslint                                  flags=validated, public   displayName="TSLint (deprecated)"
HookyQR.beautify                            flags=validated, public

No deprecated anywhere. eg2.tslint puts the word in its displayName, which is a publisher writing it by hand rather than an API signal.

The client side agrees. In extensionGalleryService.ts the extension-level flags string has exactly one consumer:

function getIsPreview(flags: string): boolean {
	return flags.indexOf('preview') !== -1;
}

used once, as preview: getIsPreview(galleryExtension.flags). Nothing reads any other value out of it. So emitting "deprecated" there is harmless — indexOf('preview') is unaffected — but no VS Code client acts on it. I would drop that part rather than ship a field nothing reads, unless the intent is a signal for other consumers of the API, in which case that is worth saying in the description.

The control manifest is the mechanism, and the shape is right

VS Code fetches it from productService.extensionsGallery?.controlUrl and parses:

interface IRawExtensionsControlManifest {
	malicious: string[];
	learnMoreLinks?: IStringDictionary<string>;
	migrateToPreRelease?: IStringDictionary<{ id, displayName, migrateStorage?, engine? }>;
	deprecated?: IStringDictionary<boolean | {
		disallowInstall?: boolean;
		extension?: { id: string; displayName: string };
		settings?: string[];
		additionalInfo?: string;
	}>;
	...
}

boolean | { disallowInstall, extension: { id, displayName } } is precisely what buildExtensionsControlManifestDeprecated produces. settings and additionalInfo are optional and unused here, which is fine. Keys are lowercased by the client (deprecatedExtensionId.toLowerCase()), so the casing of namespace.name does not matter.

But the response would throw in the client

malicious is not optional, and the parse loop is unguarded:

if (result) {
	for (const id of result.malicious) {

This endpoint returns Map.of("deprecated", ...) and no malicious, so for...of undefined raises a TypeError and getExtensionsControlManifest fails outright — taking the malicious list and the search preferences down with the deprecations, not just failing to report them. Adding "malicious": [] to the response fixes it, and is worth a comment saying why an empty array is not the same as omitting the key.

The endpoint does nothing until something points at it

this.extensionsControlUrl = productService.extensionsGallery?.controlUrl, so a client only fetches this if its product.json names it. Adding the route changes nothing on its own — VSCodium and friends would need extensionsGallery.controlUrl set to it. Worth stating in the description so it is not mistaken for a change that takes effect on merge.

One scalability point

buildExtensionsControlManifestDeprecated walks findAllActiveExtensions() — that is 17,572 rows on open-vsx.org today — to find the handful that are deprecated, and issues a findLatestVersion query for each one that has a replacement. Clients poll this manifest, so it wants a query that selects deprecated extensions directly rather than filtering in Java, and ideally a cached response. RepositoryService has no such finder yet; findDeprecatedExtensions(Extension replacement) is the closest and is scoped to one replacement.

Also

It is currently CONFLICTING against main, and there are no tests. Happy to help with any of the above if it would be useful.

@netomi

netomi commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Close this PR for now as such a mechanism should be discussed first. The general extension control logic needs an overhaul anyways.

@netomi netomi closed this Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants