From cfa476be6fd993b3cfe6977e03355bc83ae5c29f Mon Sep 17 00:00:00 2001 From: FluoriteCafe-work <202210574+FluoriteCafe-work@users.noreply.github.com> Date: Fri, 26 Sep 2025 02:12:09 +0000 Subject: [PATCH 1/4] feat: do JIT extension check on command execution --- package.json | 2 +- src/upgrade/upgradeManager.ts | 14 ++++++------- src/upgrade/utility.ts | 37 ++++++++++++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 31fd592f..5e325e53 100644 --- a/package.json +++ b/package.json @@ -593,7 +593,7 @@ }, { "command": "_java.view.modernizeJavaProject", - "when": "explorerResourceIsFolder && java:serverMode && isModernizationExtensionInstalled", + "when": "explorerResourceIsFolder && java:serverMode", "group": "1_javaactions@40" }, { diff --git a/src/upgrade/upgradeManager.ts b/src/upgrade/upgradeManager.ts index 60e270e4..0f0d95f0 100644 --- a/src/upgrade/upgradeManager.ts +++ b/src/upgrade/upgradeManager.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { commands, type ExtensionContext, extensions, workspace, type WorkspaceFolder } from "vscode"; +import { commands, type ExtensionContext, workspace, type WorkspaceFolder } from "vscode"; import { Jdtls } from "../java/jdtls"; import { languageServerApiManager } from "../languageServerApi/languageServerApiManager"; @@ -11,13 +11,13 @@ import { Commands } from "../commands"; import notificationManager from "./display/notificationManager"; import { Settings } from "../settings"; import assessmentManager from "./assessmentManager"; +import { checkOrInstallExtension } from "./utility"; const DEFAULT_UPGRADE_PROMPT = "Upgrade Java project dependency to latest version."; function shouldRunCheckup() { - return Settings.getEnableDependencyCheckup() - && !!extensions.getExtension(ExtensionName.APP_MODERNIZATION_UPGRADE_FOR_JAVA); + return Settings.getEnableDependencyCheckup(); } class UpgradeManager { @@ -26,13 +26,13 @@ class UpgradeManager { // Commands to be used context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.JAVA_UPGRADE_WITH_COPILOT, async (promptText?: string) => { + await checkOrInstallExtension(ExtensionName.APP_MODERNIZATION_UPGRADE_FOR_JAVA, ExtensionName.APP_MODERNIZATION_FOR_JAVA); const promptToUse = promptText ?? DEFAULT_UPGRADE_PROMPT; await commands.executeCommand(Commands.GOTO_AGENT_MODE, { prompt: promptToUse }); })); - commands.executeCommand('setContext', 'isModernizationExtensionInstalled', - !!extensions.getExtension(ExtensionName.APP_MODERNIZATION_FOR_JAVA)); - context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.VIEW_MODERNIZE_JAVA_PROJECT, () => { - commands.executeCommand("workbench.view.extension.azureJavaMigrationExplorer"); + context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.VIEW_MODERNIZE_JAVA_PROJECT, async () => { + await checkOrInstallExtension(ExtensionName.APP_MODERNIZATION_FOR_JAVA); + await commands.executeCommand("workbench.view.extension.azureJavaMigrationExplorer"); })); UpgradeManager.scan(); diff --git a/src/upgrade/utility.ts b/src/upgrade/utility.ts index 1735f599..b821ef99 100644 --- a/src/upgrade/utility.ts +++ b/src/upgrade/utility.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { Uri } from "vscode"; +import { commands, extensions, Uri, window } from "vscode"; import * as semver from "semver"; import { UpgradeReason, type UpgradeIssue } from "./type"; import { Upgrade } from "../constants"; @@ -73,3 +73,38 @@ export function normalizePath(path: string): string { return Uri.parse(path).toString(); } +export async function checkOrInstallExtension(extensionIdToCheck: string, extensionIdToInstall?: string): Promise { + if (extensions.getExtension(extensionIdToCheck)) { + return; + } + + const actualExtensionIdToInstall = extensionIdToInstall ?? extensionIdToCheck; + + { + const BTN_TEXT = "Install extension"; + const choice = await window.showInformationMessage( + "An extension is needed for the feature to work. Please install it and try again.", + BTN_TEXT + ); + if (choice === BTN_TEXT) { + await commands.executeCommand("workbench.extensions.installExtension", actualExtensionIdToInstall); + } + } + + if (extensions.getExtension(actualExtensionIdToInstall)) { + return; + } + + // In this case the extension is disabled. + await commands.executeCommand("workbench.extensions.search", actualExtensionIdToInstall); + { + const BTN_TEXT = "Show extension in sidebar"; + const choice = await window.showInformationMessage( + "An extension is needed for the feature to work but it seems disabled. Please enable it manually and try again.", + BTN_TEXT + ); + if (choice === BTN_TEXT) { + await commands.executeCommand("workbench.extensions.search", actualExtensionIdToInstall); + } + } +} \ No newline at end of file From cab8f494f8a10147dc3e8f43f39e40c15b05a957 Mon Sep 17 00:00:00 2001 From: FluoriteCafe-work <202210574+FluoriteCafe-work@users.noreply.github.com> Date: Fri, 26 Sep 2025 13:50:58 +0800 Subject: [PATCH 2/4] chore: revise extension install UX --- src/upgrade/upgradeManager.ts | 16 +++++++++---- src/upgrade/utility.ts | 43 ++++++++++++++++------------------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/upgrade/upgradeManager.ts b/src/upgrade/upgradeManager.ts index 0f0d95f0..e1478eb1 100644 --- a/src/upgrade/upgradeManager.ts +++ b/src/upgrade/upgradeManager.ts @@ -11,7 +11,7 @@ import { Commands } from "../commands"; import notificationManager from "./display/notificationManager"; import { Settings } from "../settings"; import assessmentManager from "./assessmentManager"; -import { checkOrInstallExtension } from "./utility"; +import { checkOrPromptToInstallAppModExtension } from "./utility"; const DEFAULT_UPGRADE_PROMPT = "Upgrade Java project dependency to latest version."; @@ -24,14 +24,22 @@ class UpgradeManager { public static initialize(context: ExtensionContext) { notificationManager.initialize(context); - // Commands to be used + // Upgrade project context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.JAVA_UPGRADE_WITH_COPILOT, async (promptText?: string) => { - await checkOrInstallExtension(ExtensionName.APP_MODERNIZATION_UPGRADE_FOR_JAVA, ExtensionName.APP_MODERNIZATION_FOR_JAVA); + await checkOrPromptToInstallAppModExtension( + ExtensionName.APP_MODERNIZATION_UPGRADE_FOR_JAVA, + "To upgrade the Java project, we need to use the App Modernization extension.", + "Install extension and upgrade"); const promptToUse = promptText ?? DEFAULT_UPGRADE_PROMPT; await commands.executeCommand(Commands.GOTO_AGENT_MODE, { prompt: promptToUse }); })); + + // Show modernization view context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.VIEW_MODERNIZE_JAVA_PROJECT, async () => { - await checkOrInstallExtension(ExtensionName.APP_MODERNIZATION_FOR_JAVA); + await checkOrPromptToInstallAppModExtension( + ExtensionName.APP_MODERNIZATION_FOR_JAVA, + "To modernize the Java project, we need to use the App Modernization extension.", + "Install extension and modernize"); await commands.executeCommand("workbench.view.extension.azureJavaMigrationExplorer"); })); diff --git a/src/upgrade/utility.ts b/src/upgrade/utility.ts index b821ef99..c0e75bcf 100644 --- a/src/upgrade/utility.ts +++ b/src/upgrade/utility.ts @@ -4,7 +4,7 @@ import { commands, extensions, Uri, window } from "vscode"; import * as semver from "semver"; import { UpgradeReason, type UpgradeIssue } from "./type"; -import { Upgrade } from "../constants"; +import { ExtensionName, Upgrade } from "../constants"; function findEolDate(currentVersion: string, eolDate: Record): string | null { @@ -73,38 +73,33 @@ export function normalizePath(path: string): string { return Uri.parse(path).toString(); } -export async function checkOrInstallExtension(extensionIdToCheck: string, extensionIdToInstall?: string): Promise { +export async function checkOrPromptToInstallAppModExtension( + extensionIdToCheck: string, + notificationText: string, + buttonText: string): Promise { if (extensions.getExtension(extensionIdToCheck)) { return; } - const actualExtensionIdToInstall = extensionIdToInstall ?? extensionIdToCheck; - - { - const BTN_TEXT = "Install extension"; - const choice = await window.showInformationMessage( - "An extension is needed for the feature to work. Please install it and try again.", - BTN_TEXT - ); - if (choice === BTN_TEXT) { - await commands.executeCommand("workbench.extensions.installExtension", actualExtensionIdToInstall); - } + const choice = await window.showInformationMessage(notificationText, buttonText); + if (choice === buttonText) { + await commands.executeCommand("workbench.extensions.installExtension", ExtensionName.APP_MODERNIZATION_FOR_JAVA); + } else { + return; } - if (extensions.getExtension(actualExtensionIdToInstall)) { + if (extensions.getExtension(ExtensionName.APP_MODERNIZATION_FOR_JAVA)) { return; } // In this case the extension is disabled. - await commands.executeCommand("workbench.extensions.search", actualExtensionIdToInstall); - { - const BTN_TEXT = "Show extension in sidebar"; - const choice = await window.showInformationMessage( - "An extension is needed for the feature to work but it seems disabled. Please enable it manually and try again.", - BTN_TEXT - ); - if (choice === BTN_TEXT) { - await commands.executeCommand("workbench.extensions.search", actualExtensionIdToInstall); - } + await commands.executeCommand("workbench.extensions.search", ExtensionName.APP_MODERNIZATION_FOR_JAVA); + const BTN_TEXT = "Show extension in sidebar"; + const choice2 = await window.showInformationMessage( + "App Modernization extension is needed for the feature to work but it seems disabled. Please enable it manually and try again.", + BTN_TEXT + ); + if (choice2 === BTN_TEXT) { + await commands.executeCommand("workbench.extensions.search", ExtensionName.APP_MODERNIZATION_FOR_JAVA); } } \ No newline at end of file From 105182c97e72f6f80b6ad68cb66f6317835545af Mon Sep 17 00:00:00 2001 From: FluoriteCafe-work <202210574+FluoriteCafe-work@users.noreply.github.com> Date: Fri, 26 Sep 2025 14:50:33 +0800 Subject: [PATCH 3/4] chore: UX update --- src/constants.ts | 1 + src/upgrade/dependency.metadata.ts | 2 +- src/upgrade/display/notificationManager.ts | 12 ++++-- src/upgrade/upgradeManager.ts | 11 ++--- src/upgrade/utility.ts | 47 ++++++++++++++-------- 5 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 659e67ca..8bb176a5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -35,6 +35,7 @@ export namespace ExtensionName { export const JAVA_LANGUAGE_SUPPORT: string = "redhat.java"; export const APP_MODERNIZATION_FOR_JAVA = "vscjava.migrate-java-to-azure"; export const APP_MODERNIZATION_UPGRADE_FOR_JAVA = "vscjava.vscode-java-upgrade"; + export const APP_MODERNIZATION_EXTENSION_NAME = "GitHub Copilot app modernization"; } export namespace Upgrade { diff --git a/src/upgrade/dependency.metadata.ts b/src/upgrade/dependency.metadata.ts index 8b31b04f..71a793c8 100644 --- a/src/upgrade/dependency.metadata.ts +++ b/src/upgrade/dependency.metadata.ts @@ -11,7 +11,7 @@ export const DEPENDENCY_JAVA_RUNTIME = { "reason": UpgradeReason.JRE_TOO_OLD, "supportedVersion": `>=${LATEST_JAVA_LTS_VESRION}`, "suggestedVersion": { - "name": String(LATEST_JAVA_LTS_VESRION), + "name": `Java ${LATEST_JAVA_LTS_VESRION}`, "description": "latest LTS version", }, } as const; diff --git a/src/upgrade/display/notificationManager.ts b/src/upgrade/display/notificationManager.ts index 1b6c98d0..c597e746 100644 --- a/src/upgrade/display/notificationManager.ts +++ b/src/upgrade/display/notificationManager.ts @@ -1,17 +1,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { commands, ExtensionContext, window } from "vscode"; +import { commands, ExtensionContext, extensions, window } from "vscode"; import type { IUpgradeIssuesRenderer, UpgradeIssue } from "../type"; import { buildFixPrompt, buildNotificationMessage } from "../utility"; import { Commands } from "../../commands"; import { Settings } from "../../settings"; import { instrumentOperation, sendInfo } from "vscode-extension-telemetry-wrapper"; +import { ExtensionName } from "../../constants"; const KEY_PREFIX = 'javaupgrade.notificationManager'; const NEXT_SHOW_TS_KEY = `${KEY_PREFIX}.nextShowTs`; const BUTTON_TEXT_UPGRADE = "Upgrade Now"; +const BUTTON_TEXT_INSTALL_AND_UPGRADE = "Install Extension and Upgrade"; const BUTTON_TEXT_NOT_NOW = "Not Now"; const SECONDS_IN_A_DAY = 24 * 60 * 60; @@ -47,11 +49,13 @@ class NotificationManager implements IUpgradeIssuesRenderer { } this.hasShown = true; + const hasExtension = !!extensions.getExtension(ExtensionName.APP_MODERNIZATION_UPGRADE_FOR_JAVA); const prompt = buildFixPrompt(issue); - const notificationMessage = buildNotificationMessage(issue); + const notificationMessage = buildNotificationMessage(issue, hasExtension); + const upgradeButtonText = hasExtension ? BUTTON_TEXT_UPGRADE : BUTTON_TEXT_INSTALL_AND_UPGRADE; const selection = await window.showInformationMessage( notificationMessage, - BUTTON_TEXT_UPGRADE, + upgradeButtonText, BUTTON_TEXT_NOT_NOW); sendInfo(operationId, { operationName: "java.dependency.upgradeNotification.runUpgrade", @@ -59,7 +63,7 @@ class NotificationManager implements IUpgradeIssuesRenderer { }); switch (selection) { - case BUTTON_TEXT_UPGRADE: { + case upgradeButtonText: { commands.executeCommand(Commands.JAVA_UPGRADE_WITH_COPILOT, prompt); break; } diff --git a/src/upgrade/upgradeManager.ts b/src/upgrade/upgradeManager.ts index e1478eb1..9808a3ac 100644 --- a/src/upgrade/upgradeManager.ts +++ b/src/upgrade/upgradeManager.ts @@ -11,7 +11,7 @@ import { Commands } from "../commands"; import notificationManager from "./display/notificationManager"; import { Settings } from "../settings"; import assessmentManager from "./assessmentManager"; -import { checkOrPromptToInstallAppModExtension } from "./utility"; +import { checkOrInstallAppModExtension, checkOrPromptToInstallAppModExtension } from "./utility"; const DEFAULT_UPGRADE_PROMPT = "Upgrade Java project dependency to latest version."; @@ -26,10 +26,7 @@ class UpgradeManager { // Upgrade project context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.JAVA_UPGRADE_WITH_COPILOT, async (promptText?: string) => { - await checkOrPromptToInstallAppModExtension( - ExtensionName.APP_MODERNIZATION_UPGRADE_FOR_JAVA, - "To upgrade the Java project, we need to use the App Modernization extension.", - "Install extension and upgrade"); + await checkOrInstallAppModExtension(ExtensionName.APP_MODERNIZATION_UPGRADE_FOR_JAVA); const promptToUse = promptText ?? DEFAULT_UPGRADE_PROMPT; await commands.executeCommand(Commands.GOTO_AGENT_MODE, { prompt: promptToUse }); })); @@ -38,8 +35,8 @@ class UpgradeManager { context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.VIEW_MODERNIZE_JAVA_PROJECT, async () => { await checkOrPromptToInstallAppModExtension( ExtensionName.APP_MODERNIZATION_FOR_JAVA, - "To modernize the Java project, we need to use the App Modernization extension.", - "Install extension and modernize"); + "Install GitHub Copilot app modernization to modernize the Java project.", + "Install Extension and Modernize"); await commands.executeCommand("workbench.view.extension.azureJavaMigrationExplorer"); })); diff --git a/src/upgrade/utility.ts b/src/upgrade/utility.ts index c0e75bcf..b14bb81b 100644 --- a/src/upgrade/utility.ts +++ b/src/upgrade/utility.ts @@ -20,7 +20,7 @@ function findEolDate(currentVersion: string, eolDate: Record): s return null; } -export function buildNotificationMessage(issue: UpgradeIssue): string { +export function buildNotificationMessage(issue: UpgradeIssue, hasExtension: boolean): string { const { packageId, currentVersion, @@ -29,21 +29,22 @@ export function buildNotificationMessage(issue: UpgradeIssue): string { packageDisplayName } = issue; + const upgradeWord = hasExtension ? "upgrade" : `install ${ExtensionName.APP_MODERNIZATION_EXTENSION_NAME} extension and upgrade`; if (packageId === Upgrade.PACKAGE_ID_FOR_JAVA_RUNTIME) { - return `The current project is using an older Java runtime (${currentVersion}). Do you want to upgrade to the latest LTS version ${suggestedVersionName}?`; + return `This project is using an older Java runtime (${currentVersion}). Would you like to ${upgradeWord} it to ${suggestedVersionName} (latest LTS)?`; } switch (reason) { case UpgradeReason.END_OF_LIFE: { const { eolDate } = issue; const versionEolDate = findEolDate(currentVersion, eolDate); - return `The current project is using ${packageDisplayName} ${currentVersion}, which has reached end of life${versionEolDate ? ` in ${versionEolDate}` : "" - }. Do you want to upgrade to ${suggestedVersionName} (${suggestedVersionDescription})?`; + return `This project is using ${packageDisplayName} ${currentVersion}, which has reached end of life${versionEolDate ? ` in ${versionEolDate}` : "" + }. Would you like to ${upgradeWord} it to ${suggestedVersionName} (${suggestedVersionDescription})?`; } case UpgradeReason.DEPRECATED: default: { - return `The current project is using ${packageDisplayName} ${currentVersion}, which has been deprecated. Do you want to upgrade to ${suggestedVersionName} (${suggestedVersionDescription})?`; + return `This project is using ${packageDisplayName} ${currentVersion}, which has been deprecated. Would you like to ${upgradeWord} it to ${suggestedVersionName} (${suggestedVersionDescription})?`; } } } @@ -73,6 +74,23 @@ export function normalizePath(path: string): string { return Uri.parse(path).toString(); } +async function checkOrPromptToEnableAppModExtension() { + if (extensions.getExtension(ExtensionName.APP_MODERNIZATION_FOR_JAVA)) { + return; + } + + // The extension is disabled if we cannot detect the extension after installing it. + await commands.executeCommand("workbench.extensions.search", ExtensionName.APP_MODERNIZATION_FOR_JAVA); + const BTN_TEXT = "Show extension in sidebar"; + const choice2 = await window.showInformationMessage( + `${ExtensionName.APP_MODERNIZATION_EXTENSION_NAME} extension is needed for the feature to work but it seems disabled. Please enable it manually and try again.`, + BTN_TEXT + ); + if (choice2 === BTN_TEXT) { + await commands.executeCommand("workbench.extensions.search", ExtensionName.APP_MODERNIZATION_FOR_JAVA); + } +} + export async function checkOrPromptToInstallAppModExtension( extensionIdToCheck: string, notificationText: string, @@ -88,18 +106,15 @@ export async function checkOrPromptToInstallAppModExtension( return; } - if (extensions.getExtension(ExtensionName.APP_MODERNIZATION_FOR_JAVA)) { + await checkOrPromptToEnableAppModExtension(); +} + +export async function checkOrInstallAppModExtension( + extensionIdToCheck: string): Promise { + if (extensions.getExtension(extensionIdToCheck)) { return; } - // In this case the extension is disabled. - await commands.executeCommand("workbench.extensions.search", ExtensionName.APP_MODERNIZATION_FOR_JAVA); - const BTN_TEXT = "Show extension in sidebar"; - const choice2 = await window.showInformationMessage( - "App Modernization extension is needed for the feature to work but it seems disabled. Please enable it manually and try again.", - BTN_TEXT - ); - if (choice2 === BTN_TEXT) { - await commands.executeCommand("workbench.extensions.search", ExtensionName.APP_MODERNIZATION_FOR_JAVA); - } + await commands.executeCommand("workbench.extensions.installExtension", ExtensionName.APP_MODERNIZATION_FOR_JAVA); + await checkOrPromptToEnableAppModExtension(); } \ No newline at end of file From 39bc5bd070babc3bf8fd69b9e9d27f14c4838c7e Mon Sep 17 00:00:00 2001 From: FluoriteCafe-work <202210574+FluoriteCafe-work@users.noreply.github.com> Date: Fri, 26 Sep 2025 15:03:26 +0800 Subject: [PATCH 4/4] chore: bump to 0.25.2 --- CHANGELOG.md | 4 ++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f46a7af..02058291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to the "vscode-java-dependency" extension will be documented The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 0.25.2 + +- ux - Check extension existence on-the-fly when needed by @FluoriteCafe-work in https://github.com/microsoft/vscode-java-dependency/pull/911 + ## 0.25.0 - feat - Remind users to upgrade old (<21) Java and EOL Spring Boot/Framework versions by @FluoriteCafe-work in https://github.com/microsoft/vscode-java-dependency/pull/901 - feat - Improve ProjectCommand.getMainClasses by @snjeza in https://github.com/microsoft/vscode-java-dependency/pull/883 diff --git a/package-lock.json b/package-lock.json index 86f0b6a8..02feb80c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-java-dependency", - "version": "0.25.0", + "version": "0.25.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-java-dependency", - "version": "0.25.0", + "version": "0.25.2", "license": "MIT", "dependencies": { "await-lock": "^2.2.2", diff --git a/package.json b/package.json index 5e325e53..118693fb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-java-dependency", "displayName": "Project Manager for Java", "description": "%description%", - "version": "0.25.0", + "version": "0.25.2", "publisher": "vscjava", "preview": false, "aiKey": "5c642b22-e845-4400-badb-3f8509a70777",