diff --git a/server/src/main/java/org/eclipse/openvsx/adapter/ExtensionQueryResult.java b/server/src/main/java/org/eclipse/openvsx/adapter/ExtensionQueryResult.java index c4224d990..f00c91660 100644 --- a/server/src/main/java/org/eclipse/openvsx/adapter/ExtensionQueryResult.java +++ b/server/src/main/java/org/eclipse/openvsx/adapter/ExtensionQueryResult.java @@ -41,7 +41,7 @@ public record Extension( @Schema(description = "Date and time when this extension was last updated (ISO-8601)") String lastUpdated, List categories, - @Schema(description = "Flag extension as preview") + @Schema(description = "Flag extension as preview or deprecated (e.g., 'preview,deprecated', 'preview', 'deprecated')") String flags ) { public static final String FLAG_PREVIEW = "preview"; diff --git a/server/src/main/java/org/eclipse/openvsx/adapter/LocalVSCodeService.java b/server/src/main/java/org/eclipse/openvsx/adapter/LocalVSCodeService.java index 0ca11e508..e654bb89f 100644 --- a/server/src/main/java/org/eclipse/openvsx/adapter/LocalVSCodeService.java +++ b/server/src/main/java/org/eclipse/openvsx/adapter/LocalVSCodeService.java @@ -548,4 +548,46 @@ private boolean isWebExtension(ExtensionVersion extVer) { private boolean test(int flags, int flag) { return (flags & flag) != 0; } + + /** + * Build a VS Code Extensions Control Manifest (partial) containing only the `deprecated` dictionary. + * The dictionary maps extension id (publisher.name) to either `true` or an object with optional fields + * such as `disallowInstall` and `extension` (replacement recommendation), per IRawExtensionsControlManifest. + */ + public Map buildExtensionsControlManifestDeprecated() { + var deprecatedMap = new LinkedHashMap(); + + for (var ext : repositories.findAllActiveExtensions()) { + if (!ext.isDeprecated()) { + continue; + } + + var extensionId = ext.getNamespace().getName() + "." + ext.getName(); + + var details = new LinkedHashMap(); + + if (ext.getDownloadable() != null && !ext.getDownloadable()) { + details.put("disallowInstall", true); + } + + var replacement = ext.getReplacement(); + if (replacement != null) { + var latestReplacement = repositories.findLatestVersion(replacement, null, false, true); + var replacementDisplayName = latestReplacement != null ? latestReplacement.getDisplayName() : replacement.getName(); + var replacementId = replacement.getNamespace().getName() + "." + replacement.getName(); + details.put("extension", Map.of( + "id", replacementId, + "displayName", replacementDisplayName + )); + } + + if (details.isEmpty()) { + deprecatedMap.put(extensionId, Boolean.TRUE); + } else { + deprecatedMap.put(extensionId, details); + } + } + + return deprecatedMap; + } } diff --git a/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAPI.java b/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAPI.java index 393fb1391..debd0ec42 100644 --- a/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAPI.java +++ b/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAPI.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import static org.eclipse.openvsx.adapter.ExtensionQueryParam.*; import static org.eclipse.openvsx.adapter.ExtensionQueryResult.ExtensionFile.*; @@ -88,6 +89,21 @@ public ExtensionQueryResult extensionQuery(@RequestBody @Parameter(description = return extensionQueryRequestHandler.getResult(param, size, DEFAULT_PAGE_SIZE); } + @GetMapping( + path = "/vscode/gallery/extensioncontrol", + produces = MediaType.APPLICATION_JSON_VALUE + ) + @CrossOrigin + @Operation(summary = "Provides VS Code Extensions Control manifest (partial) containing 'deprecated' mapping") + @ApiResponse( + responseCode = "200", + description = "Returns the control manifest" + ) + public Map getExtensionsControlManifest() { + // Only provide the deprecated dictionary for now + return Map.of("deprecated", local.buildExtensionsControlManifestDeprecated()); + } + @Observed @GetMapping("/vscode/asset/{namespaceName}/{extensionName}/{version}/{assetType}/**") @CrossOrigin