From 77c22e9457152686b70ac77cd3dfcd98c3f5ac24 Mon Sep 17 00:00:00 2001 From: Azeem Muzammil Date: Wed, 13 Aug 2025 11:23:08 +0530 Subject: [PATCH 01/41] Implement devant flow --- .../ballerina-core/src/state-machine-types.ts | 24 ++++++++-- .../src/features/ai/service/connection.ts | 46 ++++++++++++++----- .../src/features/ai/utils.ts | 13 ++++-- .../src/features/natural-programming/utils.ts | 6 ++- .../src/rpc-managers/ai-panel/inline-utils.ts | 2 +- .../src/rpc-managers/ai-panel/rpc-manager.ts | 10 ++-- .../rpc-managers/bi-diagram/rpc-manager.ts | 10 ++-- .../ballerina-extension/src/utils/ai/auth.ts | 32 +++++++++++-- .../src/views/ai-panel/aiMachine.ts | 6 +-- .../src/views/ai-panel/utils.ts | 33 +++++++++---- .../src/views/AIPanel/SettingsPanel/index.tsx | 33 ++++++++----- 11 files changed, 161 insertions(+), 54 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/state-machine-types.ts b/workspaces/ballerina/ballerina-core/src/state-machine-types.ts index da1dfa6b99d..58d8f94156d 100644 --- a/workspaces/ballerina/ballerina-core/src/state-machine-types.ts +++ b/workspaces/ballerina/ballerina-core/src/state-machine-types.ts @@ -281,18 +281,24 @@ export type AIMachineSendableEvent = export enum LoginMethod { BI_INTEL = 'biIntel', - ANTHROPIC_KEY = 'anthropic_key' + ANTHROPIC_KEY = 'anthropic_key', + DEVANT_ENV = 'devant_env' } -interface BIIntelSecrets { +export interface BIIntelSecrets { accessToken: string; refreshToken: string; } -interface AnthropicKeySecrets { +export interface AnthropicKeySecrets { apiKey: string; } +export interface DevantEnvSecrets { + apiKey: string; + stsToken: string; +} + export type AuthCredentials = | { loginMethod: LoginMethod.BI_INTEL; @@ -301,10 +307,20 @@ export type AuthCredentials = | { loginMethod: LoginMethod.ANTHROPIC_KEY; secrets: AnthropicKeySecrets; + } + | { + loginMethod: LoginMethod.DEVANT_ENV; + secrets: DevantEnvSecrets; }; export interface AIUserToken { - token: string; // For BI Intel, this is the access token and for Anthropic, this is the API key + credentials: AuthCredentials; + usageToken?: string; + metadata?: { + lastRefresh?: string; + expiresAt?: string; + [key: string]: any; + }; } export interface AIMachineContext { diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/connection.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/connection.ts index 01553b83af6..caae253bc66 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/connection.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/connection.ts @@ -18,7 +18,7 @@ import { createAnthropic } from "@ai-sdk/anthropic"; import { getAccessToken, getLoginMethod, getRefreshedAccessToken } from "../../../utils/ai/auth"; import { AIStateMachine } from "../../../views/ai-panel/aiMachine"; import { BACKEND_URL } from "../utils"; -import { AIMachineEventType, LoginMethod } from "@wso2/ballerina-core"; +import { AIMachineEventType, AnthropicKeySecrets, LoginMethod, BIIntelSecrets, DevantEnvSecrets } from "@wso2/ballerina-core"; export const ANTHROPIC_HAIKU = "claude-3-5-haiku-20241022"; export const ANTHROPIC_SONNET_4 = "claude-sonnet-4-20250514"; @@ -40,21 +40,44 @@ let cachedAuthMethod: LoginMethod | null = null; */ export async function fetchWithAuth(input: string | URL | Request, options: RequestInit = {}): Promise { try { - const accessToken = await getAccessToken(); + const credentials = await getAccessToken(); + const loginMethod = credentials.loginMethod; - // Ensure headers object exists - options.headers = { - ...options.headers, - 'Authorization': `Bearer ${accessToken}`, + const headers: Record = { + "Content-Type": "application/json", 'User-Agent': 'Ballerina-VSCode-Plugin', 'Connection': 'keep-alive', }; + if (credentials && loginMethod === LoginMethod.DEVANT_ENV) { + // For DEVANT_ENV, use api-key and x-Authorization headers + const secrets = credentials.secrets as DevantEnvSecrets; + const apiKey = secrets.apiKey; + const stsToken = secrets.stsToken; + + if (apiKey && stsToken && apiKey.trim() !== "" && stsToken.trim() !== "") { + headers["api-key"] = apiKey; + headers["x-Authorization"] = stsToken; + } else { + console.warn("DevantEnv secrets missing, this may cause authentication issues"); + } + } else if (credentials && loginMethod === LoginMethod.BI_INTEL) { + // For BI_INTEL, use Bearer token + const secrets = credentials.secrets as BIIntelSecrets; + headers["Authorization"] = `Bearer ${secrets.accessToken}`; + } + + // Ensure headers object exists and merge with existing headers + options.headers = { + ...options.headers, + ...headers, + }; + let response = await fetch(input, options); console.log("Response status: ", response.status); - // Handle token expiration - if (response.status === 401) { + // Handle token expiration (only for BI_INTEL method) + if (response.status === 401 && loginMethod === LoginMethod.BI_INTEL) { console.log("Token expired. Refreshing token..."); const newToken = await getRefreshedAccessToken(); if (newToken) { @@ -87,17 +110,18 @@ export const getAnthropicClient = async (model: AnthropicModel) => { // Recreate client if login method has changed or no cached instance if (!cachedAnthropic || cachedAuthMethod !== loginMethod) { - if (loginMethod === LoginMethod.BI_INTEL) { + if (loginMethod === LoginMethod.BI_INTEL || loginMethod === LoginMethod.DEVANT_ENV) { cachedAnthropic = createAnthropic({ baseURL: BACKEND_URL + "/intelligence-api/v1.0/claude", apiKey: "xx", // dummy value; real auth is via fetchWithAuth fetch: fetchWithAuth, }); } else if (loginMethod === LoginMethod.ANTHROPIC_KEY) { - const apiKey = await getAccessToken(); + const credentials = await getAccessToken(); + const secrets = credentials.secrets as AnthropicKeySecrets; cachedAnthropic = createAnthropic({ baseURL: "https://api.anthropic.com/v1", - apiKey: apiKey, + apiKey: secrets.apiKey, }); } else { throw new Error(`Unsupported login method: ${loginMethod}`); diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts index b608b4ed2ff..6dc3fd67aa1 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts @@ -30,13 +30,16 @@ import { BallerinaProject } from '@wso2/ballerina-core'; import { BallerinaExtension } from 'src/core'; const config = workspace.getConfiguration('ballerina'); -export const BACKEND_URL : string = config.get('rootUrl') || process.env.BALLERINA_ROOT_URL; -export const AUTH_ORG : string = config.get('authOrg') || process.env.BALLERINA_AUTH_ORG; -export const AUTH_CLIENT_ID : string = config.get('authClientID') || process.env.BALLERINA_AUTH_CLIENT_ID; -export const AUTH_REDIRECT_URL : string = config.get('authRedirectURL') || process.env.BALLERINA_AUTH_REDIRECT_URL; +export const BACKEND_URL: string = config.get('rootUrl') || process.env.BALLERINA_ROOT_URL; +export const AUTH_ORG: string = config.get('authOrg') || process.env.BALLERINA_AUTH_ORG; +export const AUTH_CLIENT_ID: string = config.get('authClientID') || process.env.BALLERINA_AUTH_CLIENT_ID; +export const AUTH_REDIRECT_URL: string = config.get('authRedirectURL') || process.env.BALLERINA_AUTH_REDIRECT_URL; + +export const DEVANT_API_KEY: string = config.get('devantApiKey') || process.env.DEVANT_API_KEY; +export const DEVANT_STS_TOKEN: string = config.get('cloudStsToken') || process.env.CLOUD_STS_TOKEN; // This refers to old backend before FE Migration. We need to eventually remove this. -export const OLD_BACKEND_URL : string = BACKEND_URL + "/v2.0"; +export const OLD_BACKEND_URL: string = BACKEND_URL + "/v2.0"; export async function closeAllBallerinaFiles(dirPath: string): Promise { // Check if the directory exists diff --git a/workspaces/ballerina/ballerina-extension/src/features/natural-programming/utils.ts b/workspaces/ballerina/ballerina-extension/src/features/natural-programming/utils.ts index 6739245acfb..71c8ff9e862 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/natural-programming/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/natural-programming/utils.ts @@ -40,7 +40,7 @@ import { import { isError, isNumber } from 'lodash'; import { HttpStatusCode } from 'axios'; import { OLD_BACKEND_URL } from '../ai/utils'; -import { AIMachineEventType, BallerinaProject, LoginMethod } from '@wso2/ballerina-core'; +import { AIMachineEventType, BallerinaProject, BIIntelSecrets, LoginMethod } from '@wso2/ballerina-core'; import { getCurrentBallerinaProjectFromContext } from '../config-generator/configGenerator'; import { BallerinaExtension } from 'src/core'; import { getAccessToken as getAccesstokenFromUtils, getLoginMethod, getRefreshedAccessToken, REFRESH_TOKEN_NOT_AVAILABLE_ERROR_MESSAGE, TOKEN_REFRESH_ONLY_SUPPORTED_FOR_BI_INTEL } from '../../../src/utils/ai/auth'; @@ -512,7 +512,9 @@ export async function getAccessToken(): Promise { let token: string; const loginMethod = await getLoginMethod(); if (loginMethod === LoginMethod.BI_INTEL) { - token = await getAccesstokenFromUtils(); + const credentials = await getAccesstokenFromUtils(); + const secrets = credentials.secrets as BIIntelSecrets; + token = secrets.accessToken; } resolve(token as string); }); diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/inline-utils.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/inline-utils.ts index f9e9e88d7d1..fecee766e5c 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/inline-utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/inline-utils.ts @@ -393,7 +393,7 @@ async function getInlineDatamapperCode(inlineDataMapperResponse: InlineDataMappe return finalCode; } catch (error) { console.error(error); - throw error; + throw error; } } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts index 365772755ef..09e0e9d0b49 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts @@ -23,6 +23,7 @@ import { AIPanelAPI, AIPanelPrompt, AddToProjectRequest, + BIIntelSecrets, BIModuleNodesRequest, BISourceCodeResponse, CodeSegment, @@ -159,11 +160,14 @@ export class AiPanelRpcManager implements AIPanelAPI { async getAccessToken(): Promise { return new Promise(async (resolve, reject) => { try { - const accessToken = await getAccessToken(); - if (!accessToken) { + const credentials = await getAccessToken(); + + if (!credentials) { reject(new Error("Access Token is undefined")); return; } + const secrets = credentials.secrets as BIIntelSecrets; + const accessToken = secrets.accessToken; resolve(accessToken); } catch (error) { reject(error); @@ -798,7 +802,7 @@ export class AiPanelRpcManager implements AIPanelAPI { async abortAIGeneration(): Promise { AIPanelAbortController.getInstance().abort(); } - + async openInlineMappingChatWindow(): Promise { try { let filePath = StateMachine.context().documentUri; diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts index 1185c9ac8ec..b26df4d7a5c 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts @@ -589,7 +589,9 @@ export class BiDiagramRpcManager implements BIDiagramAPI { let token: string; const loginMethod = await getLoginMethod(); if (loginMethod === LoginMethod.BI_INTEL) { - token = await getAccessToken(); + const credentials = await getAccessToken(); + const secrets = credentials.secrets as BIIntelSecrets; + token = secrets.accessToken; } if (!token) { @@ -627,7 +629,9 @@ export class BiDiagramRpcManager implements BIDiagramAPI { let token: string; const loginMethod = await getLoginMethod(); if (loginMethod === LoginMethod.BI_INTEL) { - token = await getAccessToken(); + const credentials = await getAccessToken(); + const secrets = credentials.secrets as BIIntelSecrets; + token = secrets.accessToken; } if (!token) { //TODO: Do we need to prompt to login here? If so what? Copilot or Ballerina AI? @@ -1127,7 +1131,7 @@ export class BiDiagramRpcManager implements BIDiagramAPI { async deleteByComponentInfo(params: BIDeleteByComponentInfoRequest): Promise { console.log(">>> requesting bi delete node from ls by componentInfo", params); const projectDiags: Diagnostics[] = await checkProjectDiagnostics(StateMachine.langClient(), StateMachine.context().projectUri); - + // Helper function to perform the actual delete operation const performDelete = async (): Promise => { return new Promise((resolve, reject) => { diff --git a/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts b/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts index 7dcc552440f..17bff629960 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts @@ -22,6 +22,7 @@ import { AUTH_CLIENT_ID, AUTH_ORG } from '../../features/ai/utils'; import axios from 'axios'; import { jwtDecode, JwtPayload } from 'jwt-decode'; import { AuthCredentials, LoginMethod } from '@wso2/ballerina-core'; +import { checkDevantEnvironment } from '../../views/ai-panel/utils'; export const REFRESH_TOKEN_NOT_AVAILABLE_ERROR_MESSAGE = "Refresh token is not available."; export const TOKEN_REFRESH_ONLY_SUPPORTED_FOR_BI_INTEL = "Token refresh is only supported for BI Intelligence authentication"; @@ -148,6 +149,13 @@ export const clearAuthCredentials = async (): Promise => { // BI Copilot Auth Utils // ================================== export const getLoginMethod = async (): Promise => { + // Priority 1: Check devant environment first + const devantCredentials = await checkDevantEnvironment(); + if (devantCredentials) { + return devantCredentials.loginMethod; + } + + // Priority 2: Check stored credentials const credentials = await getAuthCredentials(); if (credentials) { return credentials.loginMethod; @@ -155,9 +163,17 @@ export const getLoginMethod = async (): Promise => { return undefined; }; -export const getAccessToken = async (): Promise => { +export const getAccessToken = async (): Promise => { return new Promise(async (resolve, reject) => { try { + // Priority 1: Check devant environment (highest priority) + const devantCredentials = await checkDevantEnvironment(); + if (devantCredentials) { + resolve(devantCredentials); + return; + } + + // Priority 2: Check stored credentials const credentials = await getAuthCredentials(); if (credentials) { @@ -173,7 +189,13 @@ export const getAccessToken = async (): Promise => { if (decoded.exp && decoded.exp < now) { finalToken = await getRefreshedAccessToken(); } - resolve(finalToken); + resolve({ + loginMethod: LoginMethod.BI_INTEL, + secrets: { + accessToken: finalToken, + refreshToken: credentials.secrets.refreshToken + } + }); return; } catch (err) { if (axios.isAxiosError(err)) { @@ -188,7 +210,11 @@ export const getAccessToken = async (): Promise => { } case LoginMethod.ANTHROPIC_KEY: - resolve(credentials.secrets.apiKey); + resolve(credentials); + return; + + case LoginMethod.DEVANT_ENV: + resolve(credentials); return; default: diff --git a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/aiMachine.ts b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/aiMachine.ts index cd22228ef0c..ec9922c4d4c 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/aiMachine.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/aiMachine.ts @@ -72,7 +72,7 @@ const aiMachine = createMachine({ target: 'Authenticated', actions: assign({ loginMethod: (_ctx, event) => event.data.loginMethod, - userToken: (_ctx, event) => ({ token: event.data.token }), + userToken: (_ctx, event) => ({ credentials: event.data }), errorMessage: (_ctx) => undefined, }) }, @@ -214,7 +214,7 @@ const aiMachine = createMachine({ src: 'getTokenAfterAuth', onDone: { actions: assign({ - userToken: (_ctx, event) => ({ token: event.data.token }), + userToken: (_ctx, event) => ({ credentials: event.data.credentials }), loginMethod: (_ctx, event) => event.data.loginMethod, errorMessage: (_ctx) => undefined, }) @@ -295,7 +295,7 @@ const getTokenAfterAuth = async () => { if (!result) { throw new Error('No authentication credentials found'); } - return { token: result, loginMethod: LoginMethod.BI_INTEL }; + return { credentials: result.secrets, loginMethod: result.loginMethod }; }; const aiStateService = interpret(aiMachine.withConfig({ diff --git a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts index a0920213486..bfe3a171871 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts @@ -23,23 +23,23 @@ import { generateText } from 'ai'; import { getAuthUrl, getLogoutUrl } from './auth'; import { extension } from '../../BalExtensionContext'; import { getAccessToken, clearAuthCredentials, storeAuthCredentials, getLoginMethod } from '../../utils/ai/auth'; +import { DEVANT_API_KEY, DEVANT_STS_TOKEN } from '../../features/ai/utils'; const LEGACY_ACCESS_TOKEN_SECRET_KEY = 'BallerinaAIUser'; const LEGACY_REFRESH_TOKEN_SECRET_KEY = 'BallerinaAIRefreshToken'; -export const checkToken = async (): Promise<{ token: string; loginMethod: LoginMethod } | undefined> => { +export const checkToken = async (): Promise => { return new Promise(async (resolve, reject) => { try { // Clean up any legacy tokens on initialization await cleanupLegacyTokens(); - const token = await getAccessToken(); - const loginMethod = await getLoginMethod(); - if (!token || !loginMethod) { + const credentials = await getAccessToken(); + if (!credentials) { resolve(undefined); return; } - resolve({ token, loginMethod }); + resolve(credentials); } catch (error) { reject(error); } @@ -63,8 +63,8 @@ const cleanupLegacyTokens = async (): Promise => { export const logout = async (isUserLogout: boolean = true) => { // For user-initiated logout, check if we need to redirect to SSO logout if (isUserLogout) { - const { token, loginMethod } = await checkToken(); - if (token && loginMethod === LoginMethod.BI_INTEL) { + const credentials = await checkToken(); + if (credentials.loginMethod === LoginMethod.BI_INTEL) { const logoutURL = getLogoutUrl(); vscode.env.openExternal(vscode.Uri.parse(logoutURL)); } @@ -112,7 +112,7 @@ export const validateApiKey = async (apiKey: string, loginMethod: LoginMethod): }; await storeAuthCredentials(credentials); - return { token: apiKey }; + return { credentials: credentials }; } catch (error) { console.error('API key validation failed:', error); @@ -129,3 +129,20 @@ export const validateApiKey = async (apiKey: string, loginMethod: LoginMethod): throw new Error('Validation failed. Please try again.'); } }; + +export const checkDevantEnvironment = async (): Promise => { + // Check if both required devant environment variables are present and non-empty + if (!DEVANT_API_KEY || !DEVANT_STS_TOKEN || + DEVANT_API_KEY.trim() === '' || DEVANT_STS_TOKEN.trim() === '') { + return undefined; + } + + // Return devant credentials without storing (always read from env) + return { + loginMethod: LoginMethod.DEVANT_ENV, + secrets: { + apiKey: DEVANT_API_KEY, + stsToken: DEVANT_STS_TOKEN + } + }; +}; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/SettingsPanel/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/SettingsPanel/index.tsx index 9961fdfce02..b1622c11055 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/SettingsPanel/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/SettingsPanel/index.tsx @@ -21,7 +21,7 @@ import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { Button, Codicon, Typography } from "@wso2/ui-toolkit"; import { AIChatView } from "../styles"; -import { AIMachineEventType } from "@wso2/ballerina-core"; +import { AIMachineEventType, LoginMethod } from "@wso2/ballerina-core"; const Container = styled.div` display: flex; @@ -62,6 +62,7 @@ export const SettingsPanel = (props: { onClose: () => void }) => { const { rpcClient } = useRpcContext(); const [copilotAuthorized, setCopilotAuthorized] = React.useState(false); + const [shouldShowLogoutButton, setShouldShowLogoutButton] = React.useState(true); const messagesEndRef = createRef(); @@ -69,6 +70,14 @@ export const SettingsPanel = (props: { onClose: () => void }) => { isCopilotAuthorized().then((authorized) => { setCopilotAuthorized(authorized); }); + + rpcClient + .getAiPanelRpcClient() + .getLoginMethod() + .then((loginMethod) => { + console.log("Login Method: ", loginMethod); + setShouldShowLogoutButton(loginMethod !== LoginMethod.DEVANT_ENV); + }); }, []); const handleCopilotLogout = () => { @@ -100,16 +109,18 @@ export const SettingsPanel = (props: { onClose: () => void }) => { Connect to AI Platforms for Enhanced Features - - - Logout from BI Copilot - - Logging out will end your session and disconnect access to AI-powered tools like code - generation, completions, test generation, and data mappings. - - - - + {shouldShowLogoutButton && ( + + + Logout from BI Copilot + + Logging out will end your session and disconnect access to AI-powered tools like code + generation, completions, test generation, and data mappings. + + + + + )} Enable GitHub Copilot Integration From 07a9a3b1e729ef4dd58b227e121dc6de8da9c836 Mon Sep 17 00:00:00 2001 From: Azeem Muzammil Date: Wed, 3 Sep 2025 12:43:12 +0530 Subject: [PATCH 02/41] Fix ask api key --- .../src/features/ai/service/ask/ask.ts | 44 +++++++++---------- .../src/features/ai/service/connection.ts | 8 ++-- .../src/features/ai/utils.ts | 1 + 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/ask/ask.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/ask/ask.ts index e9009e71d00..a9c2c719edb 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/ask/ask.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/ask/ask.ts @@ -103,14 +103,14 @@ async function fetchDocumentationFromVectorStore(query: string): Promise { library_link: `https://central.ballerina.io/${lib.name.replace(/'/g, '')}/` } as LibraryWithUrl; }); - + return apiDocs; } @@ -138,10 +138,10 @@ export async function getAskResponse(question: string): Promise try { // First, try to get tool calls from Claude const toolCallsResponse: ToolCall[] = await getToolCallsFromClaude(question); - + let centralContext: ApiDocResult[] = []; let documentationContext: Document[] = []; - + // Execute the tools if we got tool calls if (toolCallsResponse && toolCallsResponse.length > 0) { for (const toolCall of toolCallsResponse) { @@ -158,7 +158,7 @@ export async function getAskResponse(question: string): Promise const docs = await extractLearnPages(question); documentationContext.push(...docs); } - + // Build document chunks const docChunks: { [key: string]: DocChunk } = {}; if (documentationContext.length > 0) { @@ -170,13 +170,13 @@ export async function getAskResponse(question: string): Promise }; }); } - + // Build system message const systemMessage = buildLlmMessage(docChunks, documentationContext, centralContext); - + // Get final response from Claude const finalResponse = await getFinalResponseFromClaude(systemMessage, question); - + // Extract library links const libraryLinks: string[] = []; if (centralContext.length > 0) { @@ -184,7 +184,7 @@ export async function getAskResponse(question: string): Promise libraryLinks.push(lib.library_link); }); } - + // Extract doc IDs and add corresponding links const docIdPattern = /(.*?)<\/doc_id>/g; const docIds: string[] = []; @@ -192,25 +192,25 @@ export async function getAskResponse(question: string): Promise while ((match = docIdPattern.exec(finalResponse)) !== null) { docIds.push(match[1]); } - + // Add documentation links for referenced chunks docIds.forEach(id => { if (docChunks[id] && docChunks[id].doc_link.length > 0) { libraryLinks.push(docChunks[id].doc_link); } }); - + // Clean response const filteredResponse = finalResponse.replace(/.*?<\/doc_id>/g, '').trim(); - + // Format links const formattedLinks = libraryLinks.map(link => `<${link}>`); - + return { content: filteredResponse, references: formattedLinks }; - + } catch (error) { console.error('Error in assistantToolCall:', error); throw new Error(`Failed to process question: ${error instanceof Error ? error.message : 'Unknown error'}`); @@ -231,14 +231,14 @@ async function getToolCallsFromClaude(question: string): Promise { maxSteps: 1, // Limit to one step to get tool calls only abortSignal: AIPanelAbortController.getInstance().signal }); - + if (toolCalls && toolCalls.length > 0) { return toolCalls.map(toolCall => ({ name: toolCall.toolName, input: toolCall.args })); } - + return []; } @@ -255,7 +255,7 @@ async function getFinalResponseFromClaude(systemMessage: string, question: strin ], abortSignal: AIPanelAbortController.getInstance().signal }); - + return text; } @@ -264,14 +264,14 @@ function buildLlmMessage( documentationContext: Document[], centralContext: ApiDocResult[] ): string { - const documentationSection = documentationContext.length > 0 + const documentationSection = documentationContext.length > 0 ? `Information from Ballerina Learn Pages: This section includes content sourced from the Ballerina Learn pages, consisting of document chunks that cover various topics. These chunks also include sample code examples that are necessary for explaining Ballerina concepts effectively. Out of the given document chunks, you must include the chunk number(eg:- chunk1,chunk2...) of all the document chunks that you used to formulate the answer within tags and include it at the end of your response. Only include one chunk number per tag. Document chunks ${JSON.stringify(docChunks)}` : ""; - - const centralSection = centralContext.length > 0 + + const centralSection = centralContext.length > 0 ? `Information from the Ballerina API Documentation: This section provides detailed information about type definitions, clients, functions, function parameters, return types, and other library-specific details essential for answering questions related to the Ballerina programming language. ${JSON.stringify(centralContext)}` : ""; - + return `You are an AI assistant specialized in answering questions about the Ballerina programming language. Your task is to provide precise, accurate, and helpful answers based solely on the information provided below. The information provided below comes from reliable and authoritative sources on the Ballerina programming language. For every response, include your reasoning or derivation inside tags. The content within these tags should explain how you arrived at the answer. INFORMATION SOURCES: diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/connection.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/connection.ts index caae253bc66..0bce76cfe34 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/connection.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/connection.ts @@ -17,7 +17,7 @@ import { createAnthropic } from "@ai-sdk/anthropic"; import { getAccessToken, getLoginMethod, getRefreshedAccessToken } from "../../../utils/ai/auth"; import { AIStateMachine } from "../../../views/ai-panel/aiMachine"; -import { BACKEND_URL } from "../utils"; +import { BACKEND_URL, DEVANT_API_KEY_FOR_ASK } from "../utils"; import { AIMachineEventType, AnthropicKeySecrets, LoginMethod, BIIntelSecrets, DevantEnvSecrets } from "@wso2/ballerina-core"; export const ANTHROPIC_HAIKU = "claude-3-5-haiku-20241022"; @@ -36,9 +36,10 @@ let cachedAuthMethod: LoginMethod | null = null; * Reusable fetch function that handles authentication with token refresh * @param input - The URL, Request object, or string to fetch * @param options - Fetch options + * @param isAskRequest - TEMPORARY HACK: If true, uses DEVANT_API_KEY_FOR_ASK env variable as API key * @returns Promise */ -export async function fetchWithAuth(input: string | URL | Request, options: RequestInit = {}): Promise { +export async function fetchWithAuth(input: string | URL | Request, options: RequestInit = {}, isAskRequest: boolean = false): Promise { try { const credentials = await getAccessToken(); const loginMethod = credentials.loginMethod; @@ -52,7 +53,8 @@ export async function fetchWithAuth(input: string | URL | Request, options: Requ if (credentials && loginMethod === LoginMethod.DEVANT_ENV) { // For DEVANT_ENV, use api-key and x-Authorization headers const secrets = credentials.secrets as DevantEnvSecrets; - const apiKey = secrets.apiKey; + // TEMPORARY HACK: Use DEVANT_API_KEY_FOR_ASK env variable for ask requests + const apiKey = isAskRequest ? DEVANT_API_KEY_FOR_ASK : secrets.apiKey; const stsToken = secrets.stsToken; if (apiKey && stsToken && apiKey.trim() !== "" && stsToken.trim() !== "") { diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts index 6dc3fd67aa1..e545c1d5599 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts @@ -36,6 +36,7 @@ export const AUTH_CLIENT_ID: string = config.get('authClientID') || process.env. export const AUTH_REDIRECT_URL: string = config.get('authRedirectURL') || process.env.BALLERINA_AUTH_REDIRECT_URL; export const DEVANT_API_KEY: string = config.get('devantApiKey') || process.env.DEVANT_API_KEY; +export const DEVANT_API_KEY_FOR_ASK: string = config.get('devantApiKeyForAsk') || process.env.DEVANT_API_KEY_FOR_ASK; export const DEVANT_STS_TOKEN: string = config.get('cloudStsToken') || process.env.CLOUD_STS_TOKEN; // This refers to old backend before FE Migration. We need to eventually remove this. From 9e71391397a3bf97f96aef263afac9deca0ad6eb Mon Sep 17 00:00:00 2001 From: Azeem Muzammil Date: Wed, 3 Sep 2025 14:33:14 +0530 Subject: [PATCH 03/41] Fix merge conflicts --- .../src/rpc-managers/bi-diagram/rpc-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts index b26df4d7a5c..93bfdc8744b 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts @@ -138,7 +138,7 @@ import { VerifyTypeDeleteResponse, WorkspaceFolder, WorkspacesResponse, - deleteType + BIIntelSecrets } from "@wso2/ballerina-core"; import * as fs from "fs"; import * as path from 'path'; From b247ba071927ea489c596d9fe1c9e4e3a98efa6a Mon Sep 17 00:00:00 2001 From: Anjana Supun Date: Fri, 19 Sep 2025 13:01:11 +0530 Subject: [PATCH 04/41] Fix ts issues --- workspaces/ballerina/ballerina-core/src/state-machine-types.ts | 2 +- .../src/rpc-managers/bi-diagram/rpc-manager.ts | 2 +- workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts | 2 +- .../ballerina/ballerina-extension/src/views/ai-panel/utils.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/state-machine-types.ts b/workspaces/ballerina/ballerina-core/src/state-machine-types.ts index ad9cf2642e7..1644a83d986 100644 --- a/workspaces/ballerina/ballerina-core/src/state-machine-types.ts +++ b/workspaces/ballerina/ballerina-core/src/state-machine-types.ts @@ -294,7 +294,7 @@ export type AIMachineSendableEvent = export enum LoginMethod { BI_INTEL = 'biIntel', ANTHROPIC_KEY = 'anthropic_key', - DEVANT_ENV = 'devant_env' + DEVANT_ENV = 'devant_env', AWS_BEDROCK = 'aws_bedrock' } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts index bd23d704dd0..65c606fdca7 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts @@ -138,7 +138,7 @@ import { VerifyTypeDeleteResponse, WorkspaceFolder, WorkspacesResponse, - BIIntelSecrets + BIIntelSecrets, ConfigVariableRequest, AvailableNode, Item, diff --git a/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts b/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts index 3190e46f67b..f12222a2e8d 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts @@ -218,7 +218,7 @@ export const getAccessToken = async (): Promise => return; case LoginMethod.AWS_BEDROCK: - resolve(credentials.secrets.accessKeyId); + resolve(credentials); return; default: diff --git a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts index 3869771cf08..57369367c40 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts @@ -213,7 +213,7 @@ export const validateAwsCredentials = async (credentials: { }; await storeAuthCredentials(authCredentials); - return { token: accessKeyId }; + return { credentials: authCredentials }; } catch (error) { console.error('AWS Bedrock validation failed:', error); From 4e58436e0e0f1d7c2c25bc2b517d2c57c231c825 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Mon, 22 Sep 2025 14:10:03 +0530 Subject: [PATCH 05/41] Copied platform-ext changes from feature-cloud-mode --- .../wso2-platform-core/src/constants.ts | 1 + .../src/types/cli-rpc.types.ts | 115 ++++++ .../src/types/cmd-params.ts | 4 + .../src/types/common.types.ts | 45 +++ .../src/types/messenger-rpc.types.ts | 24 ++ .../src/types/store.types.ts | 3 +- .../src/types/webview-prop.types.ts | 2 + .../wso2-platform-core/src/utils.ts | 17 + .../wso2-platform-extension/.env.example | 33 -- .../wso2-platform-extension/package.json | 35 +- .../src/PlatformExtensionApi.ts | 1 + .../src/choreo-rpc/client.ts | 124 +++++- .../src/choreo-rpc/connection.ts | 14 +- .../src/choreo-rpc/rpc-resolver.ts | 19 + .../src/cmds/commit-and-push-to-git-cmd.ts | 187 +++++++++ .../src/cmds/create-component-cmd.ts | 116 ++++-- .../wso2-platform-extension/src/cmds/index.ts | 2 + .../src/cmds/open-in-console-cmd.ts | 15 +- .../src/cmds/sign-in-cmd.ts | 16 +- .../src/cmds/sign-in-with-code-cmd.ts | 5 +- .../wso2-platform-extension/src/config.ts | 84 +--- .../src/devant-utils.ts | 77 ++++ .../src/error-utils.ts | 28 +- .../wso2-platform-extension/src/extension.ts | 41 +- .../src/extensionVariables.ts | 7 + .../wso2-platform-extension/src/git/git.ts | 9 +- .../wso2-platform-extension/src/mcp.ts | 51 +++ .../src/stores/auth-store.ts | 11 +- .../src/stores/context-store.ts | 66 +++- .../src/stores/data-cache-store.ts | 59 +-- .../src/stores/location-store.ts | 2 +- .../src/stores/store-utils.ts | 6 +- .../src/uri-handlers.ts | 18 +- .../wso2-platform-extension/src/utils.ts | 37 +- .../src/webviews/ComponentDetailsView.ts | 24 +- .../src/webviews/ComponentFormView.ts | 2 +- .../src/webviews/WebviewRPC.ts | 162 +++++++- .../wso2-platform-extension/webpack.config.js | 15 +- .../wso2-platform-webviews/package.json | 3 +- .../FormElements/Dropdown/Dropdown.tsx | 20 +- .../src/components/SwaggerUI/SwaggerUI.tsx | 2 +- .../VerticalStepper/VerticalStepper.tsx | 33 +- .../src/hooks/use-queries.tsx | 50 ++- .../src/providers/react-query-provider.tsx | 2 +- .../src/utilities/vscode-webview-rpc.ts | 14 + .../ComponentDetailsView.tsx | 28 +- .../sections/BuildConfigsSection.tsx | 3 + .../ComponentFormView/ComponentFormView.tsx | 263 ++++++++----- .../ComponentFormView/componentFormSchema.ts | 32 +- .../ComponentFormGenDetailsSection.tsx | 8 +- .../sections/ComponentFormRepoInitSection.tsx | 368 ++++++++++++++++++ .../wso2-platform-webviews/webpack.config.js | 4 +- 52 files changed, 1856 insertions(+), 451 deletions(-) create mode 100644 workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts create mode 100644 workspaces/wso2-platform/wso2-platform-extension/src/devant-utils.ts create mode 100644 workspaces/wso2-platform/wso2-platform-extension/src/mcp.ts create mode 100644 workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx diff --git a/workspaces/wso2-platform/wso2-platform-core/src/constants.ts b/workspaces/wso2-platform/wso2-platform-core/src/constants.ts index 678ea7b0980..06f83669ce6 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/constants.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/constants.ts @@ -35,6 +35,7 @@ export const CommandIds = { CreateComponentDependency: "wso2.wso2-platform.component.create.dependency", ViewDependency: "wso2.wso2-platform.component.view.dependency", OpenCompSrcDir: "wso2.wso2-platform.open.component.src", + CommitAndPushToGit: "wso2.wso2-platform.push-to-git", // TODO: add command & code lens to delete dependency }; diff --git a/workspaces/wso2-platform/wso2-platform-core/src/types/cli-rpc.types.ts b/workspaces/wso2-platform/wso2-platform-core/src/types/cli-rpc.types.ts index 60981cb8b68..653ee327623 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/types/cli-rpc.types.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/types/cli-rpc.types.ts @@ -31,11 +31,14 @@ import type { DeploymentLogsData, DeploymentTrack, Environment, + GitRepoMetadata, + GithubOrganization, MarketplaceItem, Pagination, Project, ProjectBuildLogsData, ProxyDeploymentInfo, + SubscriptionItem, } from "./common.types"; import type { InboundConfig } from "./config-file.types"; @@ -53,6 +56,11 @@ export interface GetCredentialsReq { orgId: string; orgUuid: string; } +export interface GetCredentialDetailsReq { + orgId: string; + orgUuid: string; + credentialId: string; +} export interface IsRepoAuthorizedReq { orgId: string; repoUrl: string; @@ -385,6 +393,10 @@ export interface GetBuildLogsReq { displayType: string; projectId: string; buildId: number; + orgUuid: string; + buildRef: string; + deploymentTrackId: string; + clusterId: string; } export interface GetBuildLogsForTypeReq { @@ -394,6 +406,76 @@ export interface GetBuildLogsForTypeReq { buildId: number; } +export interface GetSubscriptionsReq { + orgId: string; + cloudType?: string; +} + +export interface UpdateCodeServerReq { + orgId: string; + orgUuid: string; + orgHandle: string; + projectId: string; + componentId: string; + sourceCommitHash: string; +} + +export interface GetGitTokenForRepositoryReq { + orgId: string; + gitOrg: string; + gitRepo: string; + secretRef: string; +} + +export interface GetGitTokenForRepositoryResp { + token: string; + gitOrganization: string; + gitRepository: string; + vendor: string; + username: string; + serverUrl: string; +} + +export interface GetGitMetadataReq { + orgId: string; + gitOrgName: string; + gitRepoName: string; + branch: string; + relativePath: string; + secretRef: string; +} + +export interface GetGitMetadataResp { + metadata: GitRepoMetadata; +} + +export interface SubscriptionsResp { + count: number; + list: SubscriptionItem[]; + cloudType: string; + emailType: string; +} + +export interface GetAuthorizedGitOrgsReq { + orgId: string; + credRef: string; +} + +export interface GetAuthorizedGitOrgsResp { + gitOrgs: GithubOrganization[]; +} + +export interface GetCliRpcResp { + billingConsoleUrl: string; + choreoConsoleUrl: string; + devantConsoleUrl: string; + ghApp: { + installUrl: string; + authUrl: string; + clientId: string; + }; +} + export interface IChoreoRPCClient { getComponentItem(params: GetComponentItemReq): Promise; getDeploymentTracks(params: GetDeploymentTracksReq): Promise; @@ -405,7 +487,9 @@ export interface IChoreoRPCClient { getBuildPacks(params: BuildPackReq): Promise; getRepoBranches(params: GetBranchesReq): Promise; isRepoAuthorized(params: IsRepoAuthorizedReq): Promise; + getAuthorizedGitOrgs(params: GetAuthorizedGitOrgsReq): Promise; getCredentials(params: GetCredentialsReq): Promise; + getCredentialDetails(params: GetCredentialDetailsReq): Promise; deleteComponent(params: DeleteCompReq): Promise; getBuilds(params: GetBuildsReq): Promise; createBuild(params: CreateBuildReq): Promise; @@ -433,6 +517,9 @@ export interface IChoreoRPCClient { cancelApprovalRequest(params: CancelApprovalReq): Promise; requestPromoteApproval(params: RequestPromoteApprovalReq): Promise; promoteProxyDeployment(params: PromoteProxyDeploymentReq): Promise; + getSubscriptions(params: GetSubscriptionsReq): Promise; + getGitTokenForRepository(params: GetGitTokenForRepositoryReq): Promise; + getGitRepoMetadata(params: GetGitMetadataReq): Promise; } export class ChoreoRpcWebview implements IChoreoRPCClient { @@ -462,9 +549,15 @@ export class ChoreoRpcWebview implements IChoreoRPCClient { isRepoAuthorized(params: IsRepoAuthorizedReq): Promise { return this._messenger.sendRequest(ChoreoRpcIsRepoAuthorizedRequest, HOST_EXTENSION, params); } + getAuthorizedGitOrgs(params: GetAuthorizedGitOrgsReq): Promise { + return this._messenger.sendRequest(ChoreoRpcGetAuthorizedGitOrgsRequest, HOST_EXTENSION, params); + } getCredentials(params: GetCredentialsReq): Promise { return this._messenger.sendRequest(ChoreoRpcGetCredentialsRequest, HOST_EXTENSION, params); } + getCredentialDetails(params: GetCredentialDetailsReq): Promise { + return this._messenger.sendRequest(ChoreoRpcGetCredentialDetailsRequest, HOST_EXTENSION, params); + } deleteComponent(params: DeleteCompReq): Promise { return this._messenger.sendRequest(ChoreoRpcDeleteComponentRequest, HOST_EXTENSION, params); } @@ -549,6 +642,15 @@ export class ChoreoRpcWebview implements IChoreoRPCClient { promoteProxyDeployment(params: PromoteProxyDeploymentReq): Promise { return this._messenger.sendRequest(ChoreoRpcPromoteProxyDeployment, HOST_EXTENSION, params); } + getSubscriptions(params: GetSubscriptionsReq): Promise { + return this._messenger.sendRequest(ChoreoRpcGetSubscriptions, HOST_EXTENSION, params); + } + getGitTokenForRepository(params: GetGitTokenForRepositoryReq): Promise { + return this._messenger.sendRequest(ChoreoRpcGetGitTokenForRepository, HOST_EXTENSION, params); + } + getGitRepoMetadata(params: GetGitMetadataReq): Promise { + return this._messenger.sendRequest(ChoreoRpcGetGitRepoMetadata, HOST_EXTENSION, params); + } } export const ChoreoRpcGetProjectsRequest: RequestType = { method: "rpc/project/getProjects" }; @@ -559,7 +661,11 @@ export const ChoreoRpcCreateComponentRequest: RequestType = { method: "rpc/component/getBuildPacks" }; export const ChoreoRpcGetBranchesRequest: RequestType = { method: "rpc/repo/getBranches" }; export const ChoreoRpcIsRepoAuthorizedRequest: RequestType = { method: "rpc/repo/isRepoAuthorized" }; +export const ChoreoRpcGetAuthorizedGitOrgsRequest: RequestType = { + method: "rpc/repo/getAuthorizedGitOrgs", +}; export const ChoreoRpcGetCredentialsRequest: RequestType = { method: "rpc/repo/getCredentials" }; +export const ChoreoRpcGetCredentialDetailsRequest: RequestType = { method: "rpc/repo/getCredentialDetails" }; export const ChoreoRpcDeleteComponentRequest: RequestType = { method: "rpc/component/delete" }; export const ChoreoRpcCreateBuildRequest: RequestType = { method: "rpc/build/create" }; export const ChoreoRpcGetDeploymentTracksRequest: RequestType = { @@ -612,3 +718,12 @@ export const ChoreoRpcRequestPromoteApproval: RequestType = { method: "rpc/deployment/promoteProxy", }; +export const ChoreoRpcGetSubscriptions: RequestType = { + method: "rpc/auth/getSubscriptions", +}; +export const ChoreoRpcGetGitTokenForRepository: RequestType = { + method: "rpc/repo/gitTokenForRepository", +}; +export const ChoreoRpcGetGitRepoMetadata: RequestType = { + method: "rpc/repo/getRepoMetadata", +}; diff --git a/workspaces/wso2-platform/wso2-platform-core/src/types/cmd-params.ts b/workspaces/wso2-platform/wso2-platform-core/src/types/cmd-params.ts index 4f62c0fa3a2..a7b50c84f86 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/types/cmd-params.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/types/cmd-params.ts @@ -15,6 +15,10 @@ export interface ICloneProjectCmdParams extends ICmdParamsBase { integrationDisplayType: string; } +export interface ICommitAndPuhCmdParams extends ICmdParamsBase { + componentPath: string; +} + export interface ICreateDependencyParams extends ICmdParamsBase { componentFsPath?: string; isCodeLens?: boolean; diff --git a/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts b/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts index 98630317e03..52e4cf42a5c 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts @@ -28,6 +28,7 @@ export interface IWso2PlatformExtensionAPI { getWebviewStateStore(): WebviewState; getContextStateStore(): ContextStoreState; openClonedDir(params: openClonedDirReq): Promise; + getStsToken(): Promise; } export interface openClonedDirReq { @@ -67,6 +68,7 @@ export interface ComponentKindSource { bitbucket?: ComponentKindGitProviderSource; github?: ComponentKindGitProviderSource; gitlab?: ComponentKindGitProviderSource; + secretRef?: string; } export interface ComponentKindBuildDocker { @@ -172,6 +174,8 @@ export interface BuildKind { completedAt: string; images: { id: string; createdAt: string; updatedAt: string }[]; gitCommit: { message: string; author: string; date: string; email: string }; + clusterId: string; + buildRef: string; }; } @@ -548,4 +552,45 @@ export interface CredentialItem { organizationUuid: string; type: string; referenceToken: string; + serverUrl: string; +} + +export interface SubscriptionItem { + subscriptionId: string; + tierId: string; + supportPlanId: string; + cloudType: string; + subscriptionType: string; + subscriptionBillingProvider: string; + subscriptionBillingProviderStatus: string; +} + +export interface GithubRepository { + name: string; +} + +export interface GithubOrganization { + orgName: string; + orgHandler: string; + repositories: GithubRepository[]; +} + +export interface GitRepoMetadata { + isBareRepo: boolean; + isSubPathEmpty: boolean; + isSubPathValid: boolean; + isValidRepo: boolean; + hasBallerinaTomlInPath: boolean; + hasBallerinaTomlInRoot: boolean; + isDockerfilePathValid: boolean; + hasDockerfileInPath: boolean; + isDockerContextPathValid: boolean; + isOpenApiFilePathValid: boolean; + hasOpenApiFileInPath: boolean; + hasPomXmlInPath: boolean; + hasPomXmlInRoot: boolean; + isBuildpackPathValid: boolean; + isTestRunnerPathValid: boolean; + isProcfileExists: boolean; + isEndpointYamlExists: boolean; } diff --git a/workspaces/wso2-platform/wso2-platform-core/src/types/messenger-rpc.types.ts b/workspaces/wso2-platform/wso2-platform-core/src/types/messenger-rpc.types.ts index b0785c461ec..32c61097855 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/types/messenger-rpc.types.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/types/messenger-rpc.types.ts @@ -76,6 +76,8 @@ export const CreateLocalEndpointsConfig: RequestType = { method: "createLocalProxyConfig" }; export const CreateLocalConnectionsConfig: RequestType = { method: "createLocalConnectionsConfig" }; export const DeleteLocalConnectionsConfig: RequestType = { method: "deleteLocalConnectionsConfig" }; +export const CloneRepositoryIntoCompDir: RequestType = { method: "cloneRepositoryIntoCompDir" }; +export const PushEverythingToRemoteRepo: RequestType = { method: "pushEverythingToRemoteRepo" }; const NotificationMethods = { onAuthStateChanged: "onAuthStateChanged", @@ -103,6 +105,28 @@ export interface OpenTestViewReq { endpoints: ComponentEP[]; } +export interface PushEverythingToRemoteRepoReq { + dirPath: string; + componentName: string; +} + +export interface CloneRepositoryIntoCompDirReq { + cwd: string; + subpath: string; + org: Organization; + componentName: string; + repo: { + provider: string; + orgName: string; + orgHandler: string; + repo: string; + serverUrl?: string; + branch: string; + secretRef: string; + isBareRepo: boolean; + }; +} + export interface SubmitComponentCreateReq { org: Organization; project: Project; diff --git a/workspaces/wso2-platform/wso2-platform-core/src/types/store.types.ts b/workspaces/wso2-platform/wso2-platform-core/src/types/store.types.ts index 612f7cb9e6f..3311ecb3bd9 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/types/store.types.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/types/store.types.ts @@ -20,7 +20,7 @@ import type { CommitHistory, ComponentKind, Environment, ExtensionName, Organiza export interface DataCacheState { orgs?: { - [orgHandle: string]: { + [orgRegionHandle: string]: { projects?: { [projectHandle: string]: { data?: Project; @@ -41,6 +41,7 @@ export interface DataCacheState { export interface AuthState { userInfo: UserInfo | null; + region: "US" | "EU"; } export interface WebviewState { diff --git a/workspaces/wso2-platform/wso2-platform-core/src/types/webview-prop.types.ts b/workspaces/wso2-platform/wso2-platform-core/src/types/webview-prop.types.ts index 045cc9b28c7..b9bf3bf4ec3 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/types/webview-prop.types.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/types/webview-prop.types.ts @@ -30,6 +30,7 @@ export interface NewComponentWebviewProps { existingComponents: ComponentKind[]; initialValues?: { type?: string; subType?: string; buildPackLang?: string; name?: string }; extensionName?: string; + isNewCodeServerComp?: boolean; } export interface ComponentsDetailsWebviewProps { @@ -39,6 +40,7 @@ export interface ComponentsDetailsWebviewProps { component: ComponentKind; directoryFsPath?: string; initialEnvs: Environment[]; + isNewComponent?: boolean; } export interface ComponentsListActivityViewProps { diff --git a/workspaces/wso2-platform/wso2-platform-core/src/utils.ts b/workspaces/wso2-platform/wso2-platform-core/src/utils.ts index 9f8d10fa74f..4ac9bcfa06f 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/utils.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/utils.ts @@ -285,6 +285,23 @@ export const parseGitURL = (url?: string): null | [string, string, string] => { return [org, repoName, provider]; }; +export const buildGitURL = (org: string, repoName: string, provider: string, withDotGitSuffix?: boolean, serverUrl?: string): string | null => { + switch (provider) { + case GitProvider.GITHUB: + return `https://github.com/${org}/${repoName}${withDotGitSuffix ? ".git" : ""}`; + case GitProvider.BITBUCKET: + return serverUrl + ? `${serverUrl}/${org}/${repoName}${withDotGitSuffix ? ".git" : ""}` + : `https://bitbucket.org/${org}/${repoName}${withDotGitSuffix ? ".git" : ""}`; + case GitProvider.GITLAB_SERVER: + return serverUrl + ? `${serverUrl}/${org}/${repoName}${withDotGitSuffix ? ".git" : ""}` + : `https://gitlab.com/${org}/${repoName}${withDotGitSuffix ? ".git" : ""}`; + default: + return null; + } +}; + export const getComponentKindRepoSource = (source: ComponentKindSource) => { return { repo: source?.github?.repository || source?.bitbucket?.repository || source?.gitlab?.repository || "", diff --git a/workspaces/wso2-platform/wso2-platform-extension/.env.example b/workspaces/wso2-platform/wso2-platform-extension/.env.example index 89dd6a06942..eff573cc0f2 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/.env.example +++ b/workspaces/wso2-platform/wso2-platform-extension/.env.example @@ -1,35 +1,2 @@ -#DEFAULT -PLATFORM_DEFAULT_GHAPP_INSTALL_URL=https://github.com/apps/wso2-cloud-app/installations/new -PLATFORM_DEFAULT_GHAPP_AUTH_URL=https://github.com/login/oauth/authorize -PLATFORM_DEFAULT_GHAPP_CLIENT_ID= -PLATFORM_DEFAULT_GHAPP_REDIRECT_URL=https://console.choreowww.dev/ghapp -PLATFORM_DEFAULT_GHAPP_DEVANT_REDIRECT_URL=https://console.devant.dev/ghapp -PLATFORM_DEFAULT_CHOREO_CONSOLE_BASE_URL=https://console.choreo.dev -PLATFORM_DEFAULT_BILLING_CONSOLE_BASE_URL=https://subscriptions.wso2.com -PLATFORM_DEFAULT_DEVANT_CONSOLE_BASE_URL=https://console.devant.dev -PLATFORM_DEFAULT_DEVANT_ASGARDEO_CLIENT_ID= - -# STAGE -PLATFORM_STAGE_GHAPP_INSTALL_URL=https://github.com/apps/wso2-cloud-app-stage/installations/new -PLATFORM_STAGE_GHAPP_AUTH_URL=https://github.com/login/oauth/authorize -PLATFORM_STAGE_GHAPP_CLIENT_ID= -PLATFORM_STAGE_GHAPP_REDIRECT_URL=https://console.st.choreo.dev/ghapp -PLATFORM_STAGE_GHAPP_DEVANT_REDIRECT_URL=https://preview-st.devant.dev/ghapp -PLATFORM_STAGE_CHOREO_CONSOLE_BASE_URL=https://console.st.choreo.dev -PLATFORM_STAGE_BILLING_CONSOLE_BASE_URL=https://subscriptions.st.wso2.com -PLATFORM_STAGE_DEVANT_CONSOLE_BASE_URL=https://preview-st.devant.dev -PLATFORM_STAGE_DEVANT_ASGARDEO_CLIENT_ID= - -# DEV -PLATFORM_DEV_GHAPP_INSTALL_URL=https://github.com/apps/wso2-cloud-app-dev/installations/new -PLATFORM_DEV_GHAPP_AUTH_URL=https://github.com/login/oauth/authorize -PLATFORM_DEV_GHAPP_CLIENT_ID= -PLATFORM_DEV_GHAPP_REDIRECT_URL=https://consolev2.preview-dv.choreo.dev/ghapp -PLATFORM_DEV_GHAPP_DEVANT_REDIRECT_URL=https://preview-dv.devant.dev/ghapp -PLATFORM_DEV_CHOREO_CONSOLE_BASE_URL=https://consolev2.preview-dv.choreo.dev -PLATFORM_DEV_BILLING_CONSOLE_BASE_URL=https://subscriptions.dv.wso2.com -PLATFORM_DEV_DEVANT_CONSOLE_BASE_URL=https://preview-dv.devant.dev -PLATFORM_DEV_DEVANT_ASGARDEO_CLIENT_ID= - # Common PLATFORM_CHOREO_CLI_RELEASES_BASE_URL=https://github.com/wso2/choreo-cli/releases/download/ \ No newline at end of file diff --git a/workspaces/wso2-platform/wso2-platform-extension/package.json b/workspaces/wso2-platform/wso2-platform-extension/package.json index 3a479682516..621a7b59232 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/package.json +++ b/workspaces/wso2-platform/wso2-platform-extension/package.json @@ -3,8 +3,8 @@ "displayName": "WSO2 Platform", "description": "Manage WSO2 Choreo and Devant projects in VS Code.", "license": "Apache-2.0", - "version": "1.0.13-sts-12", - "cliVersion": "v1.2.182507031200", + "version": "1.0.14-code-server-16", + "cliVersion": "v1.2.212509091800", "publisher": "wso2", "bugs": { "url": "https://github.com/wso2/choreo-vscode/issues" @@ -18,7 +18,8 @@ "Other" ], "activationEvents": [ - "onStartupFinished" + "onStartupFinished", + "onLanguageModel:agent" ], "extensionDependencies": [ "redhat.vscode-yaml" @@ -123,7 +124,14 @@ "shortTitle": "Open component source", "category": "WSO2", "icon": "$(repo-clone)" + }, + { + "command": "wso2.wso2-platform.push-to-git", + "title": "Commit & push component to remote repo", + "category": "WSO2", + "icon": "$(repo-push)" } + ], "configuration": { "type": "object", @@ -160,20 +168,14 @@ "dev" ], "default": "prod", - "description": "The WSO2 Platform Enviornment to use", - "scope": "window" + "description": "The WSO2 Platform Environment to use", + "scope": "machine" }, "WSO2.WSO2-Platform.Advanced.RpcPath": { "type": "string", "default": "", "description": "The path to Choreo RPC server", "scope": "window" - }, - "WSO2.WSO2-Platform.Advanced.StsToken": { - "type": "string", - "default": "", - "description": "User STS token", - "scope": "window" } } }, @@ -185,7 +187,13 @@ "fontCharacter": "\\f147" } } - } + }, + "mcpServerDefinitionProviders": [ + { + "id": "choreo", + "label": "Choreo MCP Server" + } + ] }, "scripts": { "clean": "del-cli ./dist ./out ./resources/jslibs ./platform-*.vsix ./coverage ./.nyc_output", @@ -221,7 +229,7 @@ "@types/which": "^3.0.4", "@vscode/vsce": "^3.4.2", "@wso2/playwright-vscode-tester": "workspace:*", - "axios": "^1.12.0", + "axios": "^1.9.0", "copyfiles": "^2.4.1", "del-cli": "^6.0.0", "mocha": "^11.5.0", @@ -249,6 +257,7 @@ "file-type": "^18.2.1", "js-yaml": "^4.1.0", "yaml": "^2.8.0", + "@iarna/toml": "^2.2.5", "jschardet": "^3.1.4", "vscode-messenger": "^0.5.1", "vscode-messenger-common": "^0.5.1", diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts index 82ecd87c2a7..6e1946724b8 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts @@ -36,4 +36,5 @@ export class PlatformExtensionApi implements IWso2PlatformExtensionAPI { public getWebviewStateStore = () => webviewStateStore.getState().state; public getContextStateStore = () => contextStore.getState().state; public openClonedDir = (params: openClonedDirReq) => openClonedDir(params); + public getStsToken = () => ext.clients.rpcClient.getStsToken(); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/client.ts b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/client.ts index aaa642d32b1..2391ba75ab2 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/client.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/client.ts @@ -41,12 +41,14 @@ import type { DeploymentLogsData, DeploymentTrack, Environment, + GetAuthorizedGitOrgsReq, GetAutoBuildStatusReq, GetAutoBuildStatusResp, GetBranchesReq, GetBuildLogsForTypeReq, GetBuildLogsReq, GetBuildsReq, + GetCliRpcResp, GetCommitsReq, GetComponentEndpointsReq, GetComponentItemReq, @@ -55,16 +57,23 @@ import type { GetConnectionGuideResp, GetConnectionItemReq, GetConnectionsReq, + GetCredentialDetailsReq, GetCredentialsReq, GetDeploymentStatusReq, GetDeploymentTracksReq, + GetGitMetadataReq, + GetGitMetadataResp, + GetGitTokenForRepositoryReq, + GetGitTokenForRepositoryResp, GetMarketplaceIdlReq, GetMarketplaceListReq, GetProjectEnvsReq, GetProxyDeploymentInfoReq, + GetSubscriptionsReq, GetSwaggerSpecReq, GetTestKeyReq, GetTestKeyResp, + GithubOrganization, IChoreoRPCClient, IsRepoAuthorizedReq, IsRepoAuthorizedResp, @@ -75,8 +84,10 @@ import type { PromoteProxyDeploymentReq, ProxyDeploymentInfo, RequestPromoteApprovalReq, + SubscriptionsResp, ToggleAutoBuildReq, ToggleAutoBuildResp, + UpdateCodeServerReq, UserInfo, } from "@wso2/wso2-platform-core"; import { workspace } from "vscode"; @@ -107,7 +118,7 @@ export class RPCClient { const resp = await this._conn.sendRequest<{}>("initialize", { clientName: "vscode", clientVersion: "1.0.0", - cloudStsToken: workspace.getConfiguration().get("WSO2.WSO2-Platform.Advanced.StsToken") || process.env.CLOUD_STS_TOKEN || "", + cloudStsToken: process.env.CLOUD_STS_TOKEN || "", }); console.log("Initialized RPC server", resp); } catch (e) { @@ -137,7 +148,6 @@ export class RPCClient { await this.init(); return this.sendRequest(method, params, timeout, true); } - getLogger().error("Error sending request", e); handlerError(e); throw e; } @@ -246,6 +256,14 @@ export class ChoreoRPCClient implements IChoreoRPCClient { return response; } + async getAuthorizedGitOrgs(params: GetAuthorizedGitOrgsReq) { + if (!this.client) { + throw new Error("RPC client is not initialized"); + } + const response = await this.client.sendRequest<{ gitOrgs: GithubOrganization[] }>("repo/getAuthorizedGitOrgs", params); + return { gitOrgs: response.gitOrgs }; + } + async getCredentials(params: GetCredentialsReq) { if (!this.client) { throw new Error("RPC client is not initialized"); @@ -254,6 +272,14 @@ export class ChoreoRPCClient implements IChoreoRPCClient { return response?.credentials; } + async getCredentialDetails(params: GetCredentialDetailsReq) { + if (!this.client) { + throw new Error("RPC client is not initialized"); + } + const response: CredentialItem = await this.client.sendRequest("repo/getCredentialDetails", params); + return response; + } + async getUserInfo(): Promise { if (!this.client) { throw new Error("RPC client is not initialized"); @@ -262,26 +288,23 @@ export class ChoreoRPCClient implements IChoreoRPCClient { return response.userInfo; } - async getSignInUrl({ - baseUrl, - callbackUrl, - clientId, - isSignUp, - }: { callbackUrl: string; baseUrl?: string; clientId?: string; isSignUp?: boolean }): Promise { + async getSignInUrl({ callbackUrl }: { callbackUrl: string }): Promise { + if (!this.client) { + throw new Error("RPC client is not initialized"); + } + const response = await this.client.sendRequest<{ loginUrl: string }>("auth/getSignInUrl", { callbackUrl }, 2000); + return response.loginUrl; + } + + async getDevantSignInUrl({ callbackUrl }: { callbackUrl: string }): Promise { if (!this.client) { throw new Error("RPC client is not initialized"); } - const response = await this.client.sendRequest<{ loginUrl: string }>("auth/getSignInUrl", { callbackUrl, baseUrl, clientId, isSignUp }, 2000); + const response = await this.client.sendRequest<{ loginUrl: string }>("auth/getDevantSignInUrl", { callbackUrl }, 2000); return response.loginUrl; } - async signInWithAuthCode( - authCode: string, - region?: string, - orgId?: string, - redirectUrl?: string, - clientId?: string, - ): Promise { + async signInWithAuthCode(authCode: string, region?: string, orgId?: string): Promise { if (!this.client) { throw new Error("RPC client is not initialized"); } @@ -289,8 +312,18 @@ export class ChoreoRPCClient implements IChoreoRPCClient { authCode, region, orgId, - redirectUrl, - clientId, + }); + return response.userInfo; + } + + async signInDevantWithAuthCode(authCode: string, region?: string, orgId?: string): Promise { + if (!this.client) { + throw new Error("RPC client is not initialized"); + } + const response = await this.client.sendRequest<{ userInfo: UserInfo }>("auth/signInDevantWithAuthCode", { + authCode, + region, + orgId, }); return response.userInfo; } @@ -302,6 +335,14 @@ export class ChoreoRPCClient implements IChoreoRPCClient { await this.client.sendRequest("auth/signOut", undefined, 2000); } + async getCurrentRegion(): Promise<"US" | "EU"> { + if (!this.client) { + throw new Error("RPC client is not initialized"); + } + const resp: { region: "US" | "EU" } = await this.client.sendRequest("auth/getCurrentRegion"); + return resp.region; + } + async changeOrgContext(orgId: string): Promise { if (!this.client) { throw new Error("RPC client is not initialized"); @@ -532,6 +573,53 @@ export class ChoreoRPCClient implements IChoreoRPCClient { } await this.client.sendRequest("deployment/promoteProxy", params); } + + async getSubscriptions(params: GetSubscriptionsReq): Promise { + if (!this.client) { + throw new Error("RPC client is not initialized"); + } + const response: SubscriptionsResp = await this.client.sendRequest("auth/getSubscriptions", params); + return response; + } + + async getStsToken(): Promise { + if (!this.client) { + throw new Error("RPC client is not initialized"); + } + const response: { token: string } = await this.client.sendRequest("auth/getStsToken", {}); + return response?.token; + } + + async getGitTokenForRepository(params: GetGitTokenForRepositoryReq): Promise { + if (!this.client) { + throw new Error("RPC client is not initialized"); + } + const response: GetGitTokenForRepositoryResp = await this.client.sendRequest("repo/gitTokenForRepository", params); + return response; + } + + async getGitRepoMetadata(params: GetGitMetadataReq): Promise { + if (!this.client) { + throw new Error("RPC client is not initialized"); + } + const response: GetGitMetadataResp = await this.client.sendRequest("repo/getRepoMetadata", params); + return response; + } + + async updateCodeServer(params: UpdateCodeServerReq): Promise { + if (!this.client) { + throw new Error("RPC client is not initialized"); + } + await this.client.sendRequest("component/updateCodeServer", params); + } + + async getConfigFromCli(): Promise { + if (!this.client) { + throw new Error("RPC client is not initialized"); + } + const response: GetCliRpcResp = await this.client.sendRequest("auth/getConfigs", {}); + return response; + } } export class ChoreoTracer implements Tracer { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/connection.ts b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/connection.ts index bde9ee67ae0..02d6ba9e93e 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/connection.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/connection.ts @@ -17,10 +17,11 @@ */ import { type ChildProcessWithoutNullStreams, spawn } from "child_process"; -import { workspace } from "vscode"; import { type MessageConnection, StreamMessageReader, StreamMessageWriter, createMessageConnection } from "vscode-jsonrpc/node"; +import { ext } from "../extensionVariables"; import { getLogger } from "../logger/logger"; -import { getChoreoEnv, getChoreoExecPath } from "./cli-install"; +import { parseJwt } from "../utils"; +import { getChoreoExecPath } from "./cli-install"; export class StdioConnection { private _connection: MessageConnection; @@ -29,11 +30,16 @@ export class StdioConnection { const executablePath = getChoreoExecPath(); console.log("Starting RPC server, path:", executablePath); getLogger().debug(`Starting RPC server${executablePath}`); + let region = process.env.CLOUD_REGION; + if (!region && process.env.CLOUD_STS_TOKEN && parseJwt(process.env.CLOUD_STS_TOKEN)?.iss?.includes(".eu.")) { + region = "EU"; + } this._serverProcess = spawn(executablePath, ["start-rpc-server"], { env: { ...process.env, - SKIP_KEYRING: workspace.getConfiguration().get("WSO2.WSO2-Platform.Advanced.StsToken") || process.env.CLOUD_STS_TOKEN ? "true" : "", - CHOREO_ENV: getChoreoEnv(), + SKIP_KEYRING: process.env.CLOUD_STS_TOKEN ? "true" : "", + CHOREO_ENV: ext.choreoEnv, + CHOREO_REGION: region, }, }); this._connection = createMessageConnection( diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/rpc-resolver.ts b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/rpc-resolver.ts index f44e7424f6b..6577050bdcb 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/rpc-resolver.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/rpc-resolver.ts @@ -31,6 +31,7 @@ import { ChoreoRpcDeleteConnection, ChoreoRpcDisableAutoBuild, ChoreoRpcEnableAutoBuild, + ChoreoRpcGetAuthorizedGitOrgsRequest, ChoreoRpcGetAutoBuildStatus, ChoreoRpcGetBranchesRequest, ChoreoRpcGetBuildLogs, @@ -43,20 +44,25 @@ import { ChoreoRpcGetConnectionGuide, ChoreoRpcGetConnectionItem, ChoreoRpcGetConnections, + ChoreoRpcGetCredentialDetailsRequest, ChoreoRpcGetCredentialsRequest, ChoreoRpcGetDeploymentStatusRequest, ChoreoRpcGetDeploymentTracksRequest, ChoreoRpcGetEndpointsRequest, ChoreoRpcGetEnvsRequest, + ChoreoRpcGetGitRepoMetadata, + ChoreoRpcGetGitTokenForRepository, ChoreoRpcGetMarketplaceItemIdl, ChoreoRpcGetMarketplaceItems, ChoreoRpcGetProjectsRequest, ChoreoRpcGetProxyDeploymentInfo, + ChoreoRpcGetSubscriptions, ChoreoRpcGetSwaggerRequest, ChoreoRpcGetTestKeyRequest, ChoreoRpcIsRepoAuthorizedRequest, ChoreoRpcPromoteProxyDeployment, ChoreoRpcRequestPromoteApproval, + type GetAuthorizedGitOrgsReq, type GetAutoBuildStatusReq, type GetBranchesReq, type GetBuildLogsForTypeReq, @@ -69,13 +75,17 @@ import { type GetConnectionGuideReq, type GetConnectionItemReq, type GetConnectionsReq, + type GetCredentialDetailsReq, type GetCredentialsReq, type GetDeploymentStatusReq, type GetDeploymentTracksReq, + type GetGitMetadataReq, + type GetGitTokenForRepositoryReq, type GetMarketplaceIdlReq, type GetMarketplaceListReq, type GetProjectEnvsReq, type GetProxyDeploymentInfoReq, + type GetSubscriptionsReq, type GetSwaggerSpecReq, type GetTestKeyReq, type IChoreoRPCClient, @@ -105,7 +115,9 @@ export function registerChoreoRpcResolver(messenger: Messenger, rpcClient: IChor messenger.onRequest(ChoreoRpcGetBuildPacksRequest, (params: BuildPackReq) => rpcClient.getBuildPacks(params)); messenger.onRequest(ChoreoRpcGetBranchesRequest, (params: GetBranchesReq) => rpcClient.getRepoBranches(params)); messenger.onRequest(ChoreoRpcIsRepoAuthorizedRequest, (params: IsRepoAuthorizedReq) => rpcClient.isRepoAuthorized(params)); + messenger.onRequest(ChoreoRpcGetAuthorizedGitOrgsRequest, (params: GetAuthorizedGitOrgsReq) => rpcClient.getAuthorizedGitOrgs(params)); messenger.onRequest(ChoreoRpcGetCredentialsRequest, (params: GetCredentialsReq) => rpcClient.getCredentials(params)); + messenger.onRequest(ChoreoRpcGetCredentialDetailsRequest, (params: GetCredentialDetailsReq) => rpcClient.getCredentialDetails(params)); messenger.onRequest(ChoreoRpcDeleteComponentRequest, async (params: Parameters[0]) => { const extName = webviewStateStore.getState().state.extensionName; return window.withProgress( @@ -174,4 +186,11 @@ export function registerChoreoRpcResolver(messenger: Messenger, rpcClient: IChor rpcClient.promoteProxyDeployment(params), ); }); + messenger.onRequest(ChoreoRpcGetSubscriptions, (params: GetSubscriptionsReq) => rpcClient.getSubscriptions(params)); + messenger.onRequest(ChoreoRpcGetGitTokenForRepository, (params: GetGitTokenForRepositoryReq) => rpcClient.getGitTokenForRepository(params)); + messenger.onRequest(ChoreoRpcGetGitRepoMetadata, async (params: GetGitMetadataReq) => { + return window.withProgress({ title: "Fetching repo metadata...", location: ProgressLocation.Notification }, () => + rpcClient.getGitRepoMetadata(params), + ); + }); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts new file mode 100644 index 00000000000..be2f87da3eb --- /dev/null +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts @@ -0,0 +1,187 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CommandIds, ComponentKind, type ContextStoreComponentState, GitProvider, type ICommitAndPuhCmdParams, parseGitURL } from "@wso2/wso2-platform-core"; +import { type ExtensionContext, ProgressLocation, type QuickPickItem, Uri, commands, env, window, workspace } from "vscode"; +import { ext } from "../extensionVariables"; +import { initGit } from "../git/main"; +import { hasDirtyRepo } from "../git/util"; +import { getLogger } from "../logger/logger"; +import { contextStore } from "../stores/context-store"; +import { webviewStateStore } from "../stores/webview-state-store"; +import { delay, isSamePath } from "../utils"; +import { getUserInfoForCmd, isRpcActive, setExtensionName } from "./cmd-utils"; + +export function commitAndPushToGitCommand(context: ExtensionContext) { + context.subscriptions.push( + commands.registerCommand(CommandIds.CommitAndPushToGit, async (params: ICommitAndPuhCmdParams) => { + setExtensionName(params?.extName); + const extensionName = webviewStateStore.getState().state.extensionName; + try { + isRpcActive(ext); + const userInfo = await getUserInfoForCmd("commit and push changes to Git"); + if (userInfo) { + const selected = contextStore.getState().state.selected; + if (!selected) { + throw new Error("project is not associated with a component directory"); + } + + let selectedComp: ContextStoreComponentState | undefined; + const getSelectedComponent = async (items: ContextStoreComponentState[]) => { + const componentItems: (QuickPickItem & { item?: ContextStoreComponentState })[] = items.map((item) => ({ + label: item?.component?.metadata?.displayName!, + item: item, + })); + const selectedComp = await window.showQuickPick(componentItems, { + title: `Multiple ${extensionName === "Devant" ? "integrations" : "components"} detected. Please select ${extensionName === "Devant" ? "an integration" : "a component"} to push`, + }); + return selectedComp?.item; + }; + + if (contextStore.getState().state?.components?.length === 0) { + throw new Error("No components in this workspace"); + } + + if (params?.componentPath) { + const matchingComponent = contextStore + .getState() + .state?.components?.filter((item) => isSamePath(item.componentFsPath, params?.componentPath)); + if (matchingComponent?.length === 0) { + selectedComp = await getSelectedComponent(contextStore.getState().state?.components!); + } else if (matchingComponent?.length === 1) { + selectedComp = matchingComponent[0]; + } else if (matchingComponent && matchingComponent?.length > 1) { + selectedComp = await getSelectedComponent(matchingComponent); + } + } else { + selectedComp = await getSelectedComponent(contextStore.getState().state?.components!); + } + + if (!selectedComp) { + throw new Error("Failed to select component fo be pushed to remote"); + } + + const haveChanges = await hasDirtyRepo(selectedComp.componentFsPath, ext.context, ["context.yaml"]); + if (!haveChanges) { + window.showErrorMessage("There are no new changes to push to cloud"); + return; + } + + const newGit = await initGit(ext.context); + if (!newGit) { + throw new Error("failed to initGit"); + } + const dotGit = await newGit?.getRepositoryDotGit(selectedComp.componentFsPath); + const repoRoot = await newGit?.getRepositoryRoot(selectedComp.componentFsPath); + const repo = newGit.open(repoRoot, dotGit); + + const remotes = await window.withProgress({ title: "Fetching remotes of the repo...", location: ProgressLocation.Notification }, () => + repo.getRemotes(), + ); + + if (remotes.length === 0) { + window.showErrorMessage("No remotes found within the directory"); + return; + } + + let matchingRemote = remotes.find((item) => { + if (item.pushUrl) { + const urlObj = new URL(item.pushUrl); + if (urlObj.password) { + return true; + } + } + }); + + if (!matchingRemote && process.env.CLOUD_STS_TOKEN && remotes[0].fetchUrl) { + try { + const repoUrl = remotes[0].fetchUrl; + const parsed = parseGitURL(repoUrl); + if (parsed && parsed[2] === GitProvider.GITHUB) { + const [repoOrg, repoName] = parsed; + const urlObj = new URL(repoUrl); + getLogger().debug(`Fetching PAT for org ${repoOrg} and repo ${repoName}`); + const gitPat = await window.withProgress( + { title: `Accessing the repository ${repoUrl}...`, location: ProgressLocation.Notification }, + () => + ext.clients.rpcClient.getGitTokenForRepository({ + orgId: selected.org?.id?.toString()!, + gitOrg: repoOrg, + gitRepo: repoName, + secretRef: selectedComp.component?.spec?.source?.secretRef || "", + }), + ); + urlObj.username = gitPat.username || "x-access-token"; + urlObj.password = gitPat.token; + await window.withProgress({ title: "Setting new remote...", location: ProgressLocation.Notification }, async () => { + await repo.addRemote("cloud-editor-remote", urlObj.href); + const remotes = await repo.getRemotes(); + matchingRemote = remotes.find((item) => item.name === "cloud-editor-remote"); + }); + } + } catch { + getLogger().debug(`Failed to get token for ${remotes[0].fetchUrl}`); + } + } + + await window.withProgress({ title: "Adding changes to be committed...", location: ProgressLocation.Notification }, async () => { + await repo.add(["."]); + }); + + const commitMessage = await window.showInputBox({ + placeHolder: "Message to describe the changes done to your integration", + title: "Enter commit message", + validateInput: (val) => { + if (!val) { + return "Commit message is required"; + } + return null; + }, + }); + + if (!commitMessage) { + window.showErrorMessage("Commit message is required in order to proceed"); + return; + } + + const headRef = await window.withProgress( + { title: "Fetching remote repo metadata...", location: ProgressLocation.Notification }, + async () => { + await repo.fetch({ silent: true, remote: matchingRemote?.name }); + await repo.commit(commitMessage); + await delay(500); + return repo.getHEADRef(); + }, + ); + + if (headRef?.ahead && (headRef?.behind === 0 || headRef?.behind === undefined)) { + await window.withProgress({ title: "Pushing changes to remote repository...", location: ProgressLocation.Notification }, () => + repo.push(matchingRemote?.name), + ); + window.showInformationMessage("Your changes have been successfully pushed to cloud"); + } else { + await commands.executeCommand("git.sync"); + } + } + } catch (err: any) { + console.error("Failed to push to remote", err); + window.showErrorMessage(err?.message || "Failed to push to remote"); + } + }), + ); +} diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts index 698eafe0c37..b83f59dcefe 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts @@ -23,9 +23,12 @@ import { ChoreoBuildPackNames, ChoreoComponentType, CommandIds, + type ComponentKind, DevantScopes, type ExtensionName, type ICreateComponentCmdParams, + type Organization, + type Project, type SubmitComponentCreateReq, type WorkspaceConfig, getComponentKindRepoSource, @@ -34,15 +37,16 @@ import { getTypeOfIntegrationType, parseGitURL, } from "@wso2/wso2-platform-core"; -import { type ExtensionContext, ProgressLocation, type QuickPickItem, Uri, commands, window, workspace } from "vscode"; -import { choreoEnvConfig } from "../config"; +import { type ExtensionContext, ProgressLocation, type QuickPickItem, Uri, commands, env, window, workspace } from "vscode"; import { ext } from "../extensionVariables"; +import { initGit } from "../git/main"; import { getGitRemotes, getGitRoot } from "../git/util"; +import { getLogger } from "../logger/logger"; import { authStore } from "../stores/auth-store"; -import { contextStore } from "../stores/context-store"; +import { contextStore, waitForContextStoreToLoad } from "../stores/context-store"; import { dataCacheStore } from "../stores/data-cache-store"; import { webviewStateStore } from "../stores/webview-state-store"; -import { convertFsPathToUriPath, isSamePath, isSubpath, openDirectory } from "../utils"; +import { convertFsPathToUriPath, delay, isSamePath, isSubpath, openDirectory } from "../utils"; import { showComponentDetailsView } from "../webviews/ComponentDetailsView"; import { ComponentFormView, type IComponentCreateFormParams } from "../webviews/ComponentFormView"; import { getUserInfoForCmd, isRpcActive, selectOrg, selectProjectWithCreateNew, setExtensionName } from "./cmd-utils"; @@ -59,6 +63,7 @@ export function createNewComponentCommand(context: ExtensionContext) { isRpcActive(ext); const userInfo = await getUserInfoForCmd(`create ${extName === "Devant" ? "an integration" : "a component"}`); if (userInfo) { + await waitForContextStoreToLoad(); const selected = contextStore.getState().state.selected; let selectedProject = selected?.project; let selectedOrg = selected?.org; @@ -172,8 +177,10 @@ export function createNewComponentCommand(context: ExtensionContext) { dataCacheStore.getState().setComponents(selectedOrg.handle, selectedProject.handler, components); let gitRoot: string | undefined; + let isGitInitialized = false; try { gitRoot = await getGitRoot(context, selectedUri.fsPath); + isGitInitialized = true; } catch (err) { // ignore error } @@ -222,6 +229,15 @@ export function createNewComponentCommand(context: ExtensionContext) { const isWithinWorkspace = workspace.workspaceFolders?.some((item) => isSubpath(item.uri?.fsPath, selectedUri?.fsPath)); + let compInitialName = params?.name || dirName || selectedType; + const existingNames = components.map((c) => c.metadata?.name?.toLowerCase?.()); + const baseName = compInitialName; + let counter = 1; + while (existingNames.includes(compInitialName.toLowerCase())) { + compInitialName = `${baseName}-${counter}`; + counter++; + } + const createCompParams: IComponentCreateFormParams = { directoryUriPath: selectedUri.path, directoryFsPath: selectedUri.fsPath, @@ -229,11 +245,12 @@ export function createNewComponentCommand(context: ExtensionContext) { organization: selectedOrg!, project: selectedProject!, extensionName: webviewStateStore.getState().state.extensionName, + isNewCodeServerComp: isGitInitialized === false && !!process.env.CLOUD_STS_TOKEN, initialValues: { type: selectedType, subType: selectedSubType, buildPackLang: params?.buildPackLang, - name: params?.name || dirName || "", + name: compInitialName, }, }; @@ -304,39 +321,67 @@ export const submitCreateComponentHandler = async ({ createParams, org, project } */ - if (extensionName !== "Devant") { - showComponentDetailsView(org, project, createdComponent, createParams?.componentDir); - } - - window - .showInformationMessage( - `${extensionName === "Devant" ? "Integration" : "Component"} '${createdComponent.metadata.name}' was successfully created`, - `Open in ${extensionName}`, - ) - .then(async (resp) => { - if (resp === `Open in ${extensionName}`) { - commands.executeCommand( - "vscode.open", - `${extensionName === "Devant" ? choreoEnvConfig.getDevantUrl() : choreoEnvConfig.getConsoleUrl()}/organizations/${org.handle}/projects/${project.id}/components/${createdComponent.metadata.handler}/overview`, - ); - } - }); - const compCache = dataCacheStore.getState().getComponents(org.handle, project.handler); dataCacheStore.getState().setComponents(org.handle, project.handler, [createdComponent, ...compCache]); // update the context file if needed try { - const gitRoot = await getGitRoot(ext.context, createParams.componentDir); + const newGit = await initGit(ext.context); + const gitRoot = await newGit?.getRepositoryRoot(createParams.componentDir); + const dotGit = await newGit?.getRepositoryDotGit(createParams.componentDir); const projectCache = dataCacheStore.getState().getProjects(org.handle); - if (gitRoot) { - updateContextFile(gitRoot, authStore.getState().state.userInfo!, project, org, projectCache); - contextStore.getState().refreshState(); + if (newGit && gitRoot && dotGit) { + if (process.env.CLOUD_STS_TOKEN) { + // update the code server, to attach itself to the created component + const repo = newGit.open(gitRoot, dotGit); + const head = await repo.getHEAD(); + if (head.name) { + const commit = await repo.getCommit(head.name); + try { + await window.withProgress( + { title: "Updating cloud editor with newly created component...", location: ProgressLocation.Notification }, + () => + ext.clients.rpcClient.updateCodeServer({ + componentId: createdComponent.metadata.id, + orgHandle: org.handle, + orgId: org.id.toString(), + orgUuid: org.uuid, + projectId: project.id, + sourceCommitHash: commit.hash, + }), + ); + } catch (err) { + getLogger().error("Failed to updated code server after creating the component", err); + } + + // Clear code server local storage data data + try { + await commands.executeCommand("devantEditor.clearLocalStorage"); + } catch (err) { + getLogger().error(`Failed to execute devantEditor.clearLocalStorage command: ${err}`); + } + } + } else { + updateContextFile(gitRoot, authStore.getState().state.userInfo!, project, org, projectCache); + contextStore.getState().refreshState(); + } } } catch (err) { console.error("Failed to get git details of ", createParams.componentDir); } + if (extensionName !== "Devant") { + showComponentDetailsView(org, project, createdComponent, createParams?.componentDir, undefined, true); + } + + const successMessage = `${extensionName === "Devant" ? "Integration" : "Component"} '${createdComponent.metadata.name}' was successfully created.`; + + const isWithinWorkspace = workspace.workspaceFolders?.some((item) => isSubpath(item.uri?.fsPath, createParams.componentDir)); + + if (process.env.CLOUD_STS_TOKEN) { + await ext.context.globalState.update("code-server-component-id", createdComponent.metadata?.id); + } + if (workspace.workspaceFile) { const workspaceContent: WorkspaceConfig = JSON.parse(readFileSync(workspace.workspaceFile.fsPath, "utf8")); workspaceContent.folders = [ @@ -346,9 +391,24 @@ export const submitCreateComponentHandler = async ({ createParams, org, project path: path.normalize(path.relative(path.dirname(workspace.workspaceFile.fsPath), createParams.componentDir)), }, ]; + } else if (isWithinWorkspace) { + window.showInformationMessage(successMessage, `Open in ${extensionName}`).then(async (resp) => { + if (resp === `Open in ${extensionName}`) { + commands.executeCommand( + "vscode.open", + `${extensionName === "Devant" ? ext.config?.devantConsoleUrl : ext.config?.choreoConsoleUrl}/organizations/${org.handle}/projects/${extensionName === "Devant" ? project.id : project.handler}/components/${createdComponent.metadata.handler}/overview`, + ); + } + }); } else { - contextStore.getState().refreshState(); + window.showInformationMessage(`${successMessage} Reload workspace to continue`, { modal: true }, "Continue").then(async (resp) => { + if (resp === "Continue") { + commands.executeCommand("vscode.openFolder", Uri.file(createParams.componentDir), { forceNewWindow: false }); + } + }); } + + contextStore.getState().refreshState(); } return createdComponent; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/index.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/index.ts index c07d8d22ee9..d38ba829a65 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/index.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/index.ts @@ -18,6 +18,7 @@ import type { ExtensionContext } from "vscode"; import { cloneRepoCommand } from "./clone-project-cmd"; +import { commitAndPushToGitCommand } from "./commit-and-push-to-git-cmd"; import { createComponentDependencyCommand } from "./create-comp-dependency-cmd"; import { createNewComponentCommand } from "./create-component-cmd"; import { createDirectoryContextCommand } from "./create-directory-context-cmd"; @@ -49,4 +50,5 @@ export function activateCmds(context: ExtensionContext) { createComponentDependencyCommand(context); viewComponentDependencyCommand(context); openCompSrcCommand(context); + commitAndPushToGitCommand(context); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts index 71dea4d7872..7161b376e19 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts @@ -16,16 +16,8 @@ * under the License. */ -import { - CommandIds, - type ComponentKind, - type ICreateComponentCmdParams, - type IOpenInConsoleCmdParams, - type Organization, - type Project, -} from "@wso2/wso2-platform-core"; +import { CommandIds, type ComponentKind, type ICreateComponentCmdParams, type IOpenInConsoleCmdParams } from "@wso2/wso2-platform-core"; import { type ExtensionContext, ProgressLocation, type QuickPickItem, QuickPickItemKind, Uri, commands, env, window } from "vscode"; -import { choreoEnvConfig } from "../config"; import { ext } from "../extensionVariables"; import { contextStore } from "../stores/context-store"; import { dataCacheStore } from "../stores/data-cache-store"; @@ -66,10 +58,7 @@ export function openInConsoleCommand(context: ExtensionContext) { } } - let projectBaseUrl = `${choreoEnvConfig.getConsoleUrl()}/organizations/${selectedOrg?.handle}/projects/${selectedProject.handler}`; - if (extensionName === "Devant") { - projectBaseUrl = `${choreoEnvConfig.getDevantUrl()}/organizations/${selectedOrg?.handle}/projects/${selectedProject.id}`; - } + let projectBaseUrl = `${ext.config?.choreoConsoleUrl}/organizations/${selectedOrg?.handle}/projects/${selectedProject.handler}`; if (params?.component) { env.openExternal(Uri.parse(`${projectBaseUrl}/components/${params?.component.metadata.handler}/overview`)); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts index 74953c79337..589900bb5b1 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts @@ -19,7 +19,6 @@ import { CommandIds, type ICmdParamsBase } from "@wso2/wso2-platform-core"; import { type ExtensionContext, ProgressLocation, commands, window } from "vscode"; import * as vscode from "vscode"; -import { choreoEnvConfig } from "../config"; import { ext } from "../extensionVariables"; import { getLogger } from "../logger/logger"; import { webviewStateStore } from "../stores/webview-state-store"; @@ -34,17 +33,12 @@ export function signInCommand(context: ExtensionContext) { getLogger().debug("Signing in to WSO2 Platform"); const callbackUrl = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://wso2.wso2-platform/signin`)); - let baseUrl: string | undefined; - if (webviewStateStore.getState().state?.extensionName === "Devant") { - baseUrl = `${choreoEnvConfig.getDevantUrl()}/login`; - } - let clientId: string | undefined; - if (webviewStateStore.getState().state?.extensionName === "Devant") { - clientId = choreoEnvConfig.getDevantAsgardeoClientId(); - } console.log("Generating WSO2 Platform login URL for ", callbackUrl.toString()); const loginUrl = await window.withProgress({ title: "Generating Login URL...", location: ProgressLocation.Notification }, async () => { - return ext.clients.rpcClient.getSignInUrl({ callbackUrl: callbackUrl.toString(), baseUrl, clientId }); + if (webviewStateStore.getState().state?.extensionName === "Devant") { + return ext.clients.rpcClient.getDevantSignInUrl({ callbackUrl: callbackUrl.toString() }); + } + return ext.clients.rpcClient.getSignInUrl({ callbackUrl: callbackUrl.toString() }); }); if (loginUrl) { @@ -54,7 +48,7 @@ export function signInCommand(context: ExtensionContext) { window.showErrorMessage("Unable to open external link for authentication."); } } catch (error: any) { - getLogger().error(`Error while signing in to WSO2 Platofmr. ${error?.message}${error?.cause ? `\nCause: ${error.cause.message}` : ""}`); + getLogger().error(`Error while signing in to WSO2 Platform. ${error?.message}${error?.cause ? `\nCause: ${error.cause.message}` : ""}`); if (error instanceof Error) { window.showErrorMessage(error.message); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts index b59704676f9..1825dba26d6 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts @@ -42,9 +42,10 @@ export function signInWithAuthCodeCommand(context: ExtensionContext) { }); if (authCode) { - ext.clients.rpcClient.signInWithAuthCode(authCode).then((userInfo) => { + ext.clients.rpcClient.signInWithAuthCode(authCode).then(async (userInfo) => { if (userInfo) { - authStore.getState().loginSuccess(userInfo); + const region = await ext.clients.rpcClient.getCurrentRegion(); + authStore.getState().loginSuccess(userInfo, region); } }); } else { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/config.ts b/workspaces/wso2-platform/wso2-platform-extension/src/config.ts index f407dd1eca0..72c597d3e40 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/config.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/config.ts @@ -18,23 +18,9 @@ import { window } from "vscode"; import { z } from "zod"; -import { getChoreoEnv } from "./choreo-rpc/cli-install"; +import { ext } from "./extensionVariables"; -const ghAppSchema = z.object({ - installUrl: z.string().min(1), - authUrl: z.string().min(1), - clientId: z.string().min(1), - redirectUrl: z.string().min(1), - devantRedirectUrl: z.string().min(1), -}); - -const envSchemaItem = z.object({ - ghApp: ghAppSchema, - choreoConsoleBaseUrl: z.string().min(1), - billingConsoleBaseUrl: z.string().min(1), - devantConsoleBaseUrl: z.string().min(1), - devantAsgardeoClientId: z.string().min(1), -}); +const envSchemaItem = z.object({}); const envSchema = z.object({ CLI_RELEASES_BASE_URL: z.string().min(1), @@ -45,45 +31,9 @@ const envSchema = z.object({ const _env = envSchema.safeParse({ CLI_RELEASES_BASE_URL: process.env.PLATFORM_CHOREO_CLI_RELEASES_BASE_URL, - defaultEnvs: { - ghApp: { - installUrl: process.env.PLATFORM_DEFAULT_GHAPP_INSTALL_URL ?? "", - authUrl: process.env.PLATFORM_DEFAULT_GHAPP_AUTH_URL ?? "", - clientId: process.env.PLATFORM_DEFAULT_GHAPP_CLIENT_ID ?? "", - redirectUrl: process.env.PLATFORM_DEFAULT_GHAPP_REDIRECT_URL ?? "", - devantRedirectUrl: process.env.PLATFORM_DEFAULT_GHAPP_DEVANT_REDIRECT_URL ?? "", - }, - choreoConsoleBaseUrl: process.env.PLATFORM_DEFAULT_CHOREO_CONSOLE_BASE_URL ?? "", - billingConsoleBaseUrl: process.env.PLATFORM_DEFAULT_BILLING_CONSOLE_BASE_URL ?? "", - devantConsoleBaseUrl: process.env.PLATFORM_DEFAULT_DEVANT_CONSOLE_BASE_URL ?? "", - devantAsgardeoClientId: process.env.PLATFORM_DEFAULT_DEVANT_ASGARDEO_CLIENT_ID ?? "", - }, - stageEnvs: { - ghApp: { - installUrl: process.env.PLATFORM_STAGE_GHAPP_INSTALL_URL ?? "", - authUrl: process.env.PLATFORM_STAGE_GHAPP_AUTH_URL ?? "", - clientId: process.env.PLATFORM_STAGE_GHAPP_CLIENT_ID ?? "", - redirectUrl: process.env.PLATFORM_STAGE_GHAPP_REDIRECT_URL ?? "", - devantRedirectUrl: process.env.PLATFORM_STAGE_GHAPP_DEVANT_REDIRECT_URL ?? "", - }, - choreoConsoleBaseUrl: process.env.PLATFORM_STAGE_CHOREO_CONSOLE_BASE_URL ?? "", - billingConsoleBaseUrl: process.env.PLATFORM_STAGE_BILLING_CONSOLE_BASE_URL ?? "", - devantConsoleBaseUrl: process.env.PLATFORM_STAGE_DEVANT_CONSOLE_BASE_URL ?? "", - devantAsgardeoClientId: process.env.PLATFORM_STAGE_DEVANT_ASGARDEO_CLIENT_ID ?? "", - }, - devEnvs: { - ghApp: { - installUrl: process.env.PLATFORM_DEV_GHAPP_INSTALL_URL ?? "", - authUrl: process.env.PLATFORM_DEV_GHAPP_AUTH_URL ?? "", - clientId: process.env.PLATFORM_DEV_GHAPP_CLIENT_ID ?? "", - redirectUrl: process.env.PLATFORM_DEV_GHAPP_REDIRECT_URL ?? "", - devantRedirectUrl: process.env.PLATFORM_DEV_GHAPP_DEVANT_REDIRECT_URL ?? "", - }, - choreoConsoleBaseUrl: process.env.PLATFORM_DEV_CHOREO_CONSOLE_BASE_URL ?? "", - billingConsoleBaseUrl: process.env.PLATFORM_DEV_BILLING_CONSOLE_BASE_URL ?? "", - devantConsoleBaseUrl: process.env.PLATFORM_DEV_DEVANT_CONSOLE_BASE_URL ?? "", - devantAsgardeoClientId: process.env.PLATFORM_DEV_DEVANT_ASGARDEO_CLIENT_ID ?? "", - }, + defaultEnvs: {}, + stageEnvs: {}, + devEnvs: {}, } as z.infer); if (!_env.success) { @@ -97,33 +47,11 @@ class ChoreoEnvConfig { public getCliInstallUrl() { return _env.data?.CLI_RELEASES_BASE_URL; } - - public getGHAppConfig() { - return this._config.ghApp; - } - - public getConsoleUrl(): string { - return this._config.choreoConsoleBaseUrl; - } - - public getBillingUrl(): string { - return this._config.billingConsoleBaseUrl; - } - - public getDevantUrl(): string { - return this._config.devantConsoleBaseUrl; - } - - public getDevantAsgardeoClientId(): string { - return this._config.devantAsgardeoClientId; - } } -const choreoEnv = getChoreoEnv(); - let pickedEnvConfig: z.infer; -switch (choreoEnv) { +switch (ext.choreoEnv) { case "prod": pickedEnvConfig = _env.data!.defaultEnvs; break; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/devant-utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/devant-utils.ts new file mode 100644 index 00000000000..196c3677926 --- /dev/null +++ b/workspaces/wso2-platform/wso2-platform-extension/src/devant-utils.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Uri, commands, window, workspace } from "vscode"; +import { ext } from "./extensionVariables"; +import { initGit } from "./git/main"; +import { getLogger } from "./logger/logger"; + +export const activateDevantFeatures = () => { + if (process.env.CLOUD_STS_TOKEN) { + autoRefetchDevantStsToken(); + showRepoSyncNotification(); + } +}; + +const autoRefetchDevantStsToken = () => { + const intervalTime = 20 * 60 * 1000; // 20 minutes + const intervalId = setInterval(async () => { + try { + await ext.clients.rpcClient.getStsToken(); + } catch { + getLogger().error("Failed to refresh STS token"); + if (intervalId) { + clearInterval(intervalId); + } + } + }, intervalTime); + + ext.context.subscriptions.push({ + dispose: () => { + if (intervalId) { + clearTimeout(intervalId); + } + }, + }); +}; + +const showRepoSyncNotification = async () => { + if (workspace.workspaceFolders && workspace.workspaceFolders?.length > 0) { + try { + const componentPath = Uri.from(workspace.workspaceFolders[0].uri).fsPath; + const newGit = await initGit(ext.context); + if (!newGit) { + throw new Error("failed to initGit"); + } + const dotGit = await newGit?.getRepositoryDotGit(componentPath); + const repoRoot = await newGit?.getRepositoryRoot(componentPath); + const repo = newGit.open(repoRoot, dotGit); + await repo.fetch(); + const head = await repo.getHEADRef(); + if (head?.behind) { + window.showInformationMessage(`Your remote Git repository has ${head.behind} new changes`, "Sync Repository").then((res) => { + if (res === "Sync Repository") { + commands.executeCommand("git.sync"); + } + }); + } + } catch (err) { + getLogger().error(`Failed to check if the Git head is behind: ${(err as Error)?.message}`); + } + } +}; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts index cad5720e8ba..cde75f4fc3b 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts @@ -16,11 +16,10 @@ * under the License. */ -import { CommandIds } from "@wso2/wso2-platform-core"; import { commands, window as w } from "vscode"; import { ResponseError } from "vscode-jsonrpc"; import { ErrorCode } from "./choreo-rpc/constants"; -import { choreoEnvConfig } from "./config"; +import { ext } from "./extensionVariables"; import { getLogger } from "./logger/logger"; import { authStore } from "./stores/auth-store"; import { webviewStateStore } from "./stores/webview-state-store"; @@ -84,7 +83,10 @@ export function handlerError(err: any) { w.showErrorMessage("Failed to create project due to reaching maximum number of projects allowed within the free tier.", "Upgrade").then( (res) => { if (res === "Upgrade") { - commands.executeCommand("vscode.open", `${choreoEnvConfig.getBillingUrl()}/cloud/choreo/upgrade`); + commands.executeCommand( + "vscode.open", + `${ext.config?.billingConsoleUrl}/cloud/${extensionName === "Devant" ? "devant" : "choreo"}/upgrade`, + ); } }, ); @@ -95,7 +97,10 @@ export function handlerError(err: any) { "Upgrade", ).then((res) => { if (res === "Upgrade") { - commands.executeCommand("vscode.open", `${choreoEnvConfig.getBillingUrl()}/cloud/choreo/upgrade`); + commands.executeCommand( + "vscode.open", + `${ext.config?.billingConsoleUrl}/cloud/${extensionName === "Devant" ? "devant" : "choreo"}/upgrade`, + ); } }); break; @@ -117,11 +122,10 @@ export function handlerError(err: any) { case ErrorCode.NoAccountAvailable: w.showErrorMessage(`It looks like you don't have an account yet. Please sign up before logging in.`, "Sign Up").then((res) => { if (res === "Sign Up") { - if (extensionName === "Devant") { - commands.executeCommand("vscode.open", `${choreoEnvConfig.getDevantUrl()}/signup`); - } else { - commands.executeCommand("vscode.open", `${choreoEnvConfig.getConsoleUrl()}/signup`); - } + commands.executeCommand( + "vscode.open", + ` ${extensionName === "Devant" ? ext.config?.devantConsoleUrl : ext.config?.choreoConsoleUrl}/signup`, + ); } }); break; @@ -131,11 +135,7 @@ export function handlerError(err: any) { `Open ${extensionName} Console`, ).then((res) => { if (res === `Open ${extensionName} Console`) { - if (extensionName === "Devant") { - commands.executeCommand("vscode.open", choreoEnvConfig.getDevantUrl()); - } else { - commands.executeCommand("vscode.open", choreoEnvConfig.getConsoleUrl()); - } + commands.executeCommand("vscode.open", extensionName === "Devant" ? ext.config?.devantConsoleUrl : ext.config?.choreoConsoleUrl); } }); break; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts index 69bdbcfa660..cf172b01c65 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts @@ -24,8 +24,10 @@ import { initRPCServer } from "./choreo-rpc/activate"; import { activateCmds } from "./cmds"; import { continueCreateComponent } from "./cmds/create-component-cmd"; import { activateCodeLenses } from "./code-lens"; +import { activateDevantFeatures } from "./devant-utils"; import { ext } from "./extensionVariables"; import { getLogger, initLogger } from "./logger/logger"; +import { activateMcp } from "./mcp"; import { activateStatusbar } from "./status-bar"; import { authStore } from "./stores/auth-store"; import { contextStore } from "./stores/context-store"; @@ -35,14 +37,20 @@ import { ChoreoConfigurationProvider, addTerminalHandlers } from "./tarminal-han import { activateTelemetry } from "./telemetry/telemetry"; import { activateURIHandlers } from "./uri-handlers"; import { registerYamlLanguageServer } from "./yaml-ls"; +import { getCliVersion } from "./choreo-rpc/cli-install"; +import { getExtVersion } from "./utils"; export async function activate(context: vscode.ExtensionContext) { activateTelemetry(context); await initLogger(context); - getLogger().debug("Activating WSO2 Platform Extension"); + ext.context = context; ext.api = new PlatformExtensionApi(); - setInitialEnv(); + ext.choreoEnv = getChoreoEnv(); + + getLogger().info("Activating WSO2 Platform Extension"); + getLogger().info(`Extension version: ${getExtVersion(context)}`); + getLogger().info(`CLI version: ${getCliVersion()}`); // Initialize stores await authStore.persist.rehydrate(); @@ -73,12 +81,14 @@ export async function activate(context: vscode.ExtensionContext) { continueCreateComponent(); addTerminalHandlers(); context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("*", new ChoreoConfigurationProvider())); + activateMcp(context); + activateDevantFeatures(); getLogger().debug("WSO2 Platform Extension activated"); + ext.config = await ext.clients.rpcClient.getConfigFromCli(); }) .catch((e) => { getLogger().error("Failed to initialize rpc client", e); }); - activateCmds(context); activateURIHandlers(); activateCodeLenses(context); @@ -88,24 +98,19 @@ export async function activate(context: vscode.ExtensionContext) { return ext.api; } -function setInitialEnv() { - const choreoEnv = process.env.CHOREO_ENV || process.env.CLOUD_ENV; - if ( - choreoEnv && - ["dev", "stage", "prod"].includes(choreoEnv) && - workspace.getConfiguration().get("WSO2.WSO2-Platform.Advanced.ChoreoEnvironment") !== choreoEnv - ) { - workspace.getConfiguration().update("WSO2.WSO2-Platform.Advanced.ChoreoEnvironment", choreoEnv); - } -} +const getChoreoEnv = (): string => { + return ( + process.env.CHOREO_ENV || + process.env.CLOUD_ENV || + workspace.getConfiguration().get("WSO2.WSO2-Platform.Advanced.ChoreoEnvironment") || + "prod" + ); +}; function registerPreInitHandlers(): any { workspace.onDidChangeConfiguration(async ({ affectsConfiguration }: ConfigurationChangeEvent) => { - if ( - affectsConfiguration("WSO2.WSO2-Platform.Advanced.ChoreoEnvironment") || - affectsConfiguration("WSO2.WSO2-Platform.Advanced.RpcPath") || - affectsConfiguration("WSO2.WSO2-Platform.Advanced.StsToken") - ) { + if (affectsConfiguration("WSO2.WSO2-Platform.Advanced.ChoreoEnvironment") || affectsConfiguration("WSO2.WSO2-Platform.Advanced.RpcPath")) { + // skip showing this if cloud sts env is available const selection = await window.showInformationMessage( "WSO2 Platform extension configuration changed. Please restart vscode for changes to take effect.", "Restart Now", diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts b/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts index 31d6b0d9ef5..6bdddd2c01c 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts @@ -16,6 +16,7 @@ * under the License. */ +import type { GetCliRpcResp } from "@wso2/wso2-platform-core"; import type { ExtensionContext, StatusBarItem } from "vscode"; import type { PlatformExtensionApi } from "./PlatformExtensionApi"; import type { ChoreoRPCClient } from "./choreo-rpc"; @@ -25,6 +26,12 @@ export class ExtensionVariables { public context!: ExtensionContext; public api!: PlatformExtensionApi; public statusBarItem!: StatusBarItem; + public config?: GetCliRpcResp; + public choreoEnv: string; + + public constructor() { + this.choreoEnv = "prod"; + } public clients!: { rpcClient: ChoreoRPCClient; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/git/git.ts b/workspaces/wso2-platform/wso2-platform-extension/src/git/git.ts index 40ce0fdf45a..b869afd6a77 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/git/git.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/git/git.ts @@ -414,6 +414,7 @@ const COMMIT_FORMAT = "%H%n%aN%n%aE%n%at%n%ct%n%P%n%D%n%B"; export interface ICloneOptions { readonly parentPath: string; + readonly skipCreateSubPath?: boolean; readonly progress: Progress<{ increment: number }>; readonly recursive?: boolean; readonly ref?: string; @@ -518,7 +519,13 @@ export class Git { }; try { - const command = ["clone", url.includes(" ") ? encodeURI(url) : url, folderPath, "--progress"]; + const command = ["clone", url.includes(" ") ? encodeURI(url) : url]; + if(options.skipCreateSubPath){ + command.push(".") + }else{ + command.push(folderPath) + } + command.push("--progress") if (options.recursive) { command.push("--recursive"); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/mcp.ts b/workspaces/wso2-platform/wso2-platform-extension/src/mcp.ts new file mode 100644 index 00000000000..3aaa7f02c6a --- /dev/null +++ b/workspaces/wso2-platform/wso2-platform-extension/src/mcp.ts @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as vscode from "vscode"; +import { getChoreoExecPath } from "./choreo-rpc/cli-install"; +import { getUserInfoForCmd } from "./cmds/cmd-utils"; +import { ext } from "./extensionVariables"; + +export function activateMcp(context: vscode.ExtensionContext) { + const didChangeEmitter = new vscode.EventEmitter(); + context.subscriptions.push( + vscode.lm.registerMcpServerDefinitionProvider("choreo", { + onDidChangeMcpServerDefinitions: didChangeEmitter.event, + provideMcpServerDefinitions: async () => { + const servers: vscode.McpServerDefinition[] = []; + servers.push( + new vscode.McpStdioServerDefinition( + "Choreo MCP Server", + getChoreoExecPath(), + ["start-mcp-server"], + { CHOREO_ENV: ext.choreoEnv, CHOREO_REGION: process.env.CLOUD_REGION || "" }, + "1.0.0", + ), + ); + return servers; + }, + resolveMcpServerDefinition: async (def, _token) => { + const userInfo = await getUserInfoForCmd("connect with Choreo MCP server"); + if (userInfo) { + return def; + } + return undefined; + }, + }), + ); +} diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts index 8801204ea5f..923cbdda230 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts @@ -27,21 +27,21 @@ import { getGlobalStateStore } from "./store-utils"; interface AuthStore { state: AuthState; resetState: () => void; - loginSuccess: (userInfo: UserInfo) => void; + loginSuccess: (userInfo: UserInfo, region: "US" | "EU") => void; logout: () => Promise; initAuth: () => Promise; } -const initialState: AuthState = { userInfo: null }; +const initialState: AuthState = { userInfo: null, region: "US" }; export const authStore = createStore( persist( (set, get) => ({ state: initialState, resetState: () => set(() => ({ state: initialState })), - loginSuccess: (userInfo) => { + loginSuccess: (userInfo, region) => { dataCacheStore.getState().setOrgs(userInfo.organizations); - set(({ state }) => ({ state: { ...state, userInfo } })); + set(({ state }) => ({ state: { ...state, userInfo, region } })); contextStore.getState().refreshState(); }, logout: async () => { @@ -54,7 +54,8 @@ export const authStore = createStore( try { const userInfo = await ext.clients.rpcClient.getUserInfo(); if (userInfo) { - get().loginSuccess(userInfo); + const region = await ext.clients.rpcClient.getCurrentRegion(); + get().loginSuccess(userInfo, region); const contextStoreState = contextStore.getState().state; if (contextStoreState.selected?.org) { ext?.clients?.rpcClient?.changeOrgContext(contextStoreState.selected?.org?.id?.toString()); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts index 527fb108d0e..8a01fb4ea16 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts @@ -36,7 +36,7 @@ import { createStore } from "zustand"; import { persist } from "zustand/middleware"; import { ext } from "../extensionVariables"; import { getGitRemotes, getGitRoot } from "../git/util"; -import { isSubpath } from "../utils"; +import { isSamePath, isSubpath } from "../utils"; import { authStore } from "./auth-store"; import { dataCacheStore } from "./data-cache-store"; import { locationStore } from "./location-store"; @@ -63,11 +63,12 @@ export const contextStore = createStore( if (authStore.getState().state?.userInfo) { set(({ state }) => ({ state: { ...state, loading: true } })); let items = await getAllContexts(get().state?.items); - let selected = getSelected(items, get().state?.selected); + let selected = await getSelected(items, get().state?.selected); + set(({ state }) => ({ state: { ...state, items, selected } })); let components = await getComponentsInfoCache(selected); set(({ state }) => ({ state: { ...state, items, selected, components } })); items = await getEnrichedContexts(get().state?.items); - selected = getSelected(items, selected); + selected = await getSelected(items, selected); components = await getComponentsInfoCache(selected); set(({ state }) => ({ state: { ...state, items, selected, components } })); components = await getComponentsInfo(selected); @@ -195,7 +196,44 @@ const getAllContexts = async (previousItems: { [key: string]: ContextItemEnriche return contextItems; }; -const getSelected = (items: { [key: string]: ContextItemEnriched }, prevSelected?: ContextItemEnriched) => { +const getSelected = async (items: { [key: string]: ContextItemEnriched }, prevSelected?: ContextItemEnriched) => { + if (process.env.CLOUD_STS_TOKEN && process.env.CLOUD_INITIAL_ORG_ID && process.env.CLOUD_INITIAL_PROJECT_ID) { + // Give priority to project provided as env variable, when running in the cloud editor + const userOrgs = authStore.getState().state.userInfo?.organizations; + const matchingOrg = userOrgs?.find( + (item) => item.uuid === process.env.CLOUD_INITIAL_ORG_ID || item.id?.toString() === process.env.CLOUD_INITIAL_ORG_ID, + ); + if (matchingOrg) { + let projectsCache = dataCacheStore.getState().getProjects(matchingOrg.handle); + if (projectsCache.length === 0) { + const projects = await ext.clients.rpcClient.getProjects(matchingOrg.id.toString()); + dataCacheStore.getState().setProjects(matchingOrg.handle, projects); + projectsCache = projects; + } + const matchingProject = projectsCache.find((item) => item.id === process.env.CLOUD_INITIAL_PROJECT_ID); + if (matchingProject) { + return { + orgHandle: matchingOrg.handle, + projectHandle: matchingProject.handler, + org: matchingOrg, + project: matchingProject, + contextDirs: + workspace.workspaceFolders?.map((item) => ({ + workspaceName: item.name, + projectRootFsPath: item.uri.fsPath, + dirFsPath: item.uri.fsPath, + })) ?? [], + } as ContextItemEnriched; + } + } + + const globalCompId: string | null | undefined = ext.context.globalState.get("code-server-component-id"); + if (globalCompId) { + await ext.context.globalState.update("code-server-component-id", null); + await ext.context.workspaceState.update("code-server-component-id", globalCompId); + } + } + let selected: ContextItemEnriched | undefined = undefined; const matchingItem = Object.values(items).find( (item) => @@ -300,9 +338,20 @@ const getComponentsInfo = async (selected?: ContextItemEnriched): Promise { + const workspaceCompId: string | null | undefined = ext.context.workspaceState.get("code-server-component-id") || process.env.SOURCE_COMPONENT_ID; // + if (process.env.CLOUD_STS_TOKEN && process.env.CLOUD_INITIAL_ORG_ID && process.env.CLOUD_INITIAL_PROJECT_ID && workspaceCompId) { + const filteredComps = components.filter((item) => item.metadata?.id === workspaceCompId); + if (filteredComps.length === 1) { + return filteredComps; + } + } + return components; +}; + const mapComponentList = async (components: ComponentKind[], selected?: ContextItemEnriched): Promise => { const comps: ContextStoreComponentState[] = []; - for (const componentItem of components) { + for (const componentItem of getFilteredComponents(components)) { if (selected?.contextDirs) { // biome-ignore lint/correctness/noUnsafeOptionalChaining: for (const item of selected?.contextDirs) { @@ -324,7 +373,12 @@ const mapComponentList = async (components: ComponentKind[], selected?: ContextI if (hasMatchingRemote) { const subPathDir = path.join(gitRoot, getComponentKindRepoSource(componentItem.spec.source)?.path); const isSubPath = isSubpath(item.dirFsPath, subPathDir); - if (isSubPath && existsSync(subPathDir) && !comps.some((item) => item.component?.metadata?.id === componentItem.metadata?.id)) { + const isPathSame = isSamePath(item.dirFsPath, subPathDir); + if ( + (isPathSame || isSubPath) && + existsSync(subPathDir) && + !comps.some((item) => item.component?.metadata?.id === componentItem.metadata?.id) + ) { comps.push({ component: componentItem, workspaceName: item.workspaceName, diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts index 3751dfecb86..d57723e2db3 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts @@ -19,6 +19,8 @@ import type { CommitHistory, ComponentKind, DataCacheState, Environment, Organization, Project } from "@wso2/wso2-platform-core"; import { createStore } from "zustand"; import { persist } from "zustand/middleware"; +import { ext } from "../extensionVariables"; +import { authStore } from "./auth-store"; import { getGlobalStateStore } from "./store-utils"; interface DataCacheStore { @@ -54,14 +56,14 @@ export const dataCacheStore = createStore( } = {}; projects.forEach((item) => { updatedProjects[item.handler] = { - components: get().state?.orgs?.[orgHandle]?.projects?.[item.handler]?.components || {}, + components: get().state?.orgs?.[getRootKey(orgHandle)]?.projects?.[item.handler]?.components || {}, data: item, }; }); const updatedOrgs = { ...(get().state?.orgs ?? {}), - [orgHandle]: { ...(get().state?.orgs?.[orgHandle] ?? {}), projects: updatedProjects }, + [getRootKey(orgHandle)]: { ...(get().state?.orgs?.[getRootKey(orgHandle)] ?? {}), projects: updatedProjects }, }; set(({ state }) => ({ state: { ...state, orgs: updatedOrgs } })); @@ -72,11 +74,11 @@ export const dataCacheStore = createStore( ...state, orgs: { ...(get().state?.orgs ?? {}), - [orgHandle]: { - ...(get().state?.orgs?.[orgHandle] ?? {}), + [getRootKey(orgHandle)]: { + ...(get().state?.orgs?.[getRootKey(orgHandle)] ?? {}), projects: { - ...(get().state?.orgs?.[orgHandle]?.projects ?? {}), - [projectHandle]: { ...(get().state?.orgs?.[orgHandle]?.projects?.[projectHandle] ?? {}), envs }, + ...(get().state?.orgs?.[getRootKey(orgHandle)]?.projects ?? {}), + [projectHandle]: { ...(get().state?.orgs?.[getRootKey(orgHandle)]?.projects?.[projectHandle] ?? {}), envs }, }, }, }, @@ -84,17 +86,17 @@ export const dataCacheStore = createStore( })); }, getEnvs: (orgHandle, projectHandle) => { - return get().state.orgs?.[orgHandle]?.projects?.[projectHandle]?.envs || []; + return get().state.orgs?.[getRootKey(orgHandle)]?.projects?.[projectHandle]?.envs || []; }, getProjects: (orgHandle) => { - const projectList = Object.values(get().state.orgs?.[orgHandle]?.projects ?? {}) + const projectList = Object.values(get().state.orgs?.[getRootKey(orgHandle)]?.projects ?? {}) .filter((item) => item.data) .map((item) => item.data); return projectList as Project[]; }, setComponents: (orgHandle, projectHandle, components) => { const newComponents: { [componentHandle: string]: { data?: ComponentKind; commits?: { [branch: string]: CommitHistory[] } } } = {}; - const prevComponents = get().state.orgs?.[orgHandle]?.projects?.[projectHandle]?.components ?? {}; + const prevComponents = get().state.orgs?.[getRootKey(orgHandle)]?.projects?.[projectHandle]?.components ?? {}; components.forEach((item) => { const matchingItem = prevComponents[item.metadata.name]; newComponents[item.metadata.name] = { ...matchingItem, data: item }; @@ -102,12 +104,12 @@ export const dataCacheStore = createStore( const updatedOrgs = { ...(get().state?.orgs ?? {}), - [orgHandle]: { - ...(get().state?.orgs?.[orgHandle] ?? {}), + [getRootKey(orgHandle)]: { + ...(get().state?.orgs?.[getRootKey(orgHandle)] ?? {}), projects: { - ...(get().state?.orgs?.[orgHandle]?.projects ?? {}), + ...(get().state?.orgs?.[getRootKey(orgHandle)]?.projects ?? {}), [projectHandle]: { - ...(get().state?.orgs?.[orgHandle]?.projects?.[projectHandle] ?? {}), + ...(get().state?.orgs?.[getRootKey(orgHandle)]?.projects?.[projectHandle] ?? {}), components: newComponents, }, }, @@ -117,7 +119,7 @@ export const dataCacheStore = createStore( set(({ state }) => ({ state: { ...state, orgs: updatedOrgs } })); }, getComponents: (orgHandle, projectHandle) => { - const componentList = Object.values(get().state.orgs?.[orgHandle]?.projects?.[projectHandle]?.components ?? {}) + const componentList = Object.values(get().state.orgs?.[getRootKey(orgHandle)]?.projects?.[projectHandle]?.components ?? {}) .filter((item) => item.data) .map((item) => item.data); return componentList as ComponentKind[]; @@ -125,18 +127,18 @@ export const dataCacheStore = createStore( setCommits: (orgHandle, projectHandle, componentHandle, branch, commits) => { const updatedOrgs = { ...(get().state?.orgs ?? {}), - [orgHandle]: { - ...(get().state?.orgs?.[orgHandle] ?? {}), + [getRootKey(orgHandle)]: { + ...(get().state?.orgs?.[getRootKey(orgHandle)] ?? {}), projects: { - ...(get().state?.orgs?.[orgHandle]?.projects ?? {}), + ...(get().state?.orgs?.[getRootKey(orgHandle)]?.projects ?? {}), [projectHandle]: { - ...(get().state?.orgs?.[orgHandle]?.projects?.[projectHandle] ?? {}), + ...(get().state?.orgs?.[getRootKey(orgHandle)]?.projects?.[projectHandle] ?? {}), components: { - ...(get().state?.orgs?.[orgHandle]?.projects?.[projectHandle]?.components ?? {}), + ...(get().state?.orgs?.[getRootKey(orgHandle)]?.projects?.[projectHandle]?.components ?? {}), [componentHandle]: { - ...(get().state?.orgs?.[orgHandle]?.projects?.[projectHandle]?.components?.[componentHandle] ?? {}), + ...(get().state?.orgs?.[getRootKey(orgHandle)]?.projects?.[projectHandle]?.components?.[componentHandle] ?? {}), commits: { - ...(get().state?.orgs?.[orgHandle]?.projects?.[projectHandle]?.components?.[componentHandle]?.commits ?? {}), + ...(get().state?.orgs?.[getRootKey(orgHandle)]?.projects?.[projectHandle]?.components?.[componentHandle]?.commits ?? {}), [branch]: commits, }, }, @@ -149,10 +151,21 @@ export const dataCacheStore = createStore( set(({ state }) => ({ state: { ...state, orgs: updatedOrgs } })); }, getCommits: (orgHandle, projectHandle, componentHandle, branch) => { - const commitList = get().state.orgs?.[orgHandle]?.projects?.[projectHandle]?.components?.[componentHandle]?.commits?.[branch] ?? []; + const commitList = + get().state.orgs?.[getRootKey(orgHandle)]?.projects?.[projectHandle]?.components?.[componentHandle]?.commits?.[branch] ?? []; return commitList; }, }), - getGlobalStateStore("data-cache-zustand-storage-v1"), + getGlobalStateStore("data-cache-zustand-storage"), ), ); + +const getRootKey = (orgHandle: string) => { + const region = authStore.getState().state.region; + const env = ext.choreoEnv; + let orgRegionHandle = `${region}-${orgHandle}`; + if (env !== "prod") { + orgRegionHandle = `${env}-${orgRegionHandle}`; + } + return orgRegionHandle; +}; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/location-store.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/location-store.ts index 831fa63881b..d68fc6776da 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/location-store.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/stores/location-store.ts @@ -72,6 +72,6 @@ export const locationStore = createStore( .filter((item) => existsSync(item.fsPath)); }, }), - getGlobalStateStore("location-zustand-storage-v2"), + getGlobalStateStore("location-zustand-storage"), ), ); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/store-utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/store-utils.ts index 5c2da0bae69..8d3a7b97607 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/store-utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/stores/store-utils.ts @@ -19,9 +19,11 @@ import { type PersistOptions, createJSONStorage } from "zustand/middleware"; import { ext } from "../extensionVariables"; +const version = "v4"; + export const getGlobalStateStore = (storeName: string): PersistOptions => { return { - name: storeName, + name: `${storeName}-${version}`, storage: createJSONStorage(() => ({ getItem: async (name) => { const value = await ext.context.globalState.get(name); @@ -36,7 +38,7 @@ export const getGlobalStateStore = (storeName: string): PersistOptions export const getWorkspaceStateStore = (storeName: string): PersistOptions => { return { - name: storeName, + name: `${storeName}-${version}`, storage: createJSONStorage(() => ({ getItem: async (name) => { const value = await ext.context.workspaceState.get(name); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts b/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts index 01b4c5564a0..b413bfe0b4c 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts @@ -23,6 +23,7 @@ import { type ICloneProjectCmdParams, type Organization, type Project, + type UserInfo, getComponentKindRepoSource, type openClonedDirReq, parseGitURL, @@ -32,7 +33,6 @@ import { ResponseError } from "vscode-jsonrpc"; import { ErrorCode } from "./choreo-rpc/constants"; import { getUserInfoForCmd, isRpcActive } from "./cmds/cmd-utils"; import { updateContextFile } from "./cmds/create-directory-context-cmd"; -import { choreoEnvConfig } from "./config"; import { ext } from "./extensionVariables"; import { getGitRemotes, getGitRoot } from "./git/util"; import { getLogger } from "./logger/logger"; @@ -41,7 +41,7 @@ import { contextStore, getContextKey, waitForContextStoreToLoad } from "./stores import { dataCacheStore } from "./stores/data-cache-store"; import { locationStore } from "./stores/location-store"; import { webviewStateStore } from "./stores/webview-state-store"; -import { delay, isSamePath, openDirectory } from "./utils"; +import { isSamePath, openDirectory } from "./utils"; export function activateURIHandlers() { window.registerUriHandler({ @@ -68,9 +68,12 @@ export function activateURIHandlers() { async () => { try { const orgId = contextStore?.getState().state?.selected?.org?.id?.toString(); - const callbackUrl = extName === "Devant" ? `${choreoEnvConfig.getDevantUrl()}/vscode-auth` : undefined; - const clientId = extName === "Devant" ? choreoEnvConfig.getDevantAsgardeoClientId() : undefined; - const userInfo = await ext.clients.rpcClient.signInWithAuthCode(authCode, region, orgId, callbackUrl, clientId); + let userInfo: UserInfo | undefined; + if (extName === "Devant") { + userInfo = await ext.clients.rpcClient.signInDevantWithAuthCode(authCode, region, orgId); + } else { + userInfo = await ext.clients.rpcClient.signInWithAuthCode(authCode, region, orgId); + } if (userInfo) { if (contextStore?.getState().state?.selected) { const includesOrg = userInfo.organizations?.some((item) => item.handle === contextStore?.getState().state?.selected?.orgHandle); @@ -78,7 +81,8 @@ export function activateURIHandlers() { contextStore.getState().resetState(); } } - authStore.getState().loginSuccess(userInfo); + const region = await ext.clients.rpcClient.getCurrentRegion(); + authStore.getState().loginSuccess(userInfo, region); window.showInformationMessage(`Successfully signed into ${extName}`); } } catch (error: any) { @@ -100,7 +104,7 @@ export function activateURIHandlers() { } else if (uri.path === "/ghapp") { try { isRpcActive(ext); - getLogger().info("WSO2 Platform Githup auth Callback hit"); + getLogger().info("WSO2 Platform Github auth Callback hit"); const urlParams = new URLSearchParams(uri.query); const authCode = urlParams.get("code"); // const installationId = urlParams.get("installationId"); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/utils.ts index 962d0988175..115f2467cd1 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/utils.ts @@ -196,8 +196,16 @@ export const saveFile = async ( }; export const isSamePath = (parent: string, sub: string): boolean => { - const normalizedParent = getNormalizedPath(parent).toLowerCase(); - const normalizedSub = getNormalizedPath(sub).toLowerCase(); + let normalizedParent = getNormalizedPath(parent).toLowerCase(); + if (normalizedParent.endsWith("/")) { + normalizedParent = normalizedParent.slice(0, -1); + } + + let normalizedSub = getNormalizedPath(sub).toLowerCase(); + if (normalizedSub.endsWith("/")) { + normalizedSub = normalizedSub.slice(0, -1); + } + if (normalizedParent === normalizedSub) { return true; } @@ -205,8 +213,16 @@ export const isSamePath = (parent: string, sub: string): boolean => { }; export const isSubpath = (parent: string, sub: string): boolean => { - const normalizedParent = getNormalizedPath(parent).toLowerCase(); - const normalizedSub = getNormalizedPath(sub).toLowerCase(); + let normalizedParent = getNormalizedPath(parent).toLowerCase(); + if (normalizedParent.endsWith("/")) { + normalizedParent = normalizedParent.slice(0, -1); + } + + let normalizedSub = getNormalizedPath(sub).toLowerCase(); + if (normalizedSub.endsWith("/")) { + normalizedSub = normalizedSub.slice(0, -1); + } + if (normalizedParent === normalizedSub) { return true; } @@ -413,3 +429,16 @@ export const getConfigFileDrifts = async ( return []; } }; + +export const parseJwt = (token: string): { iss: string } | null => { + try { + return JSON.parse(atob(token.split(".")[1])); + } catch (e) { + return null; + } +}; + +export const getExtVersion = (context: ExtensionContext): string => { + const packageJson = JSON.parse(readFileSync(path.join(context?.extensionPath, "package.json"), "utf8")); + return packageJson?.version; +}; \ No newline at end of file diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/ComponentDetailsView.ts b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/ComponentDetailsView.ts index 4fd5fd80195..d93bbe57507 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/ComponentDetailsView.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/ComponentDetailsView.ts @@ -32,10 +32,25 @@ class ComponentDetailsView { private _disposables: vscode.Disposable[] = []; private _rpcHandler: WebViewPanelRpc; - constructor(extensionUri: vscode.Uri, organization: Organization, project: Project, component: ComponentKind, directoryFsPath?: string) { + constructor( + extensionUri: vscode.Uri, + organization: Organization, + project: Project, + component: ComponentKind, + directoryFsPath?: string, + isNewComponent?: boolean, + ) { this._panel = ComponentDetailsView.createWebview(component); this._panel.onDidDispose(() => this.dispose(), null, this._disposables); - this._panel.webview.html = this._getWebviewContent(this._panel.webview, extensionUri, organization, project, component, directoryFsPath); + this._panel.webview.html = this._getWebviewContent( + this._panel.webview, + extensionUri, + organization, + project, + component, + directoryFsPath, + isNewComponent, + ); this._rpcHandler = new WebViewPanelRpc(this._panel); } @@ -67,6 +82,7 @@ class ComponentDetailsView { project: Project, component: ComponentKind, directoryFsPath?: string, + isNewComponent?: boolean, ) { // The JS file from the React build output const scriptUri = getUri(webview, extensionUri, ["resources", "jslibs", "main.js"]); @@ -99,6 +115,7 @@ class ComponentDetailsView { project, component, initialEnvs: dataCacheStore.getState().getEnvs(organization.handle, project.handler), + isNewComponent, } as WebviewProps)} ); } @@ -142,6 +159,7 @@ export const showComponentDetailsView = ( component: ComponentKind, directoryFsPath: string, viewColumn?: vscode.ViewColumn, + isNewComponent?: boolean, ) => { const webView = getComponentDetailsView(org.handle, project.handler, component.metadata.name); const componentKey = getComponentKey(org, project, component); @@ -150,7 +168,7 @@ export const showComponentDetailsView = ( webView?.reveal(viewColumn); } else { webviewStateStore.getState().onCloseComponentDrawer(getComponentKey(org, project, component)); - const componentDetailsView = new ComponentDetailsView(ext.context.extensionUri, org, project, component, directoryFsPath); + const componentDetailsView = new ComponentDetailsView(ext.context.extensionUri, org, project, component, directoryFsPath, isNewComponent); componentDetailsView.getWebview()?.reveal(viewColumn); componentViewMap.set(componentKey, componentDetailsView); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/ComponentFormView.ts b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/ComponentFormView.ts index 080786b8e50..782ca77d959 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/ComponentFormView.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/ComponentFormView.ts @@ -43,7 +43,7 @@ export class ComponentFormView { const extName = webviewStateStore.getState().state?.extensionName; const panel = vscode.window.createWebviewPanel( "create-new-component", - extName === "Devant" ? "Create Integration" : "Create Component", + extName === "Devant" ? "Deploy Integration" : "Create Component", vscode.ViewColumn.One, { enableScripts: true, diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts index ef5b696cb5e..188185c3e14 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts @@ -16,11 +16,29 @@ * under the License. */ -import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs"; +import { + copyFileSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + renameSync, + rmSync, + rmdirSync, + statSync, + unlinkSync, + writeFileSync, +} from "fs"; +import * as fs from "fs"; +import * as os from "os"; import { join } from "path"; +import * as toml from "@iarna/toml"; import { AuthStoreChangedNotification, ClearWebviewCache, + CloneRepositoryIntoCompDir, + type CloneRepositoryIntoCompDirReq, CloseComponentViewDrawer, CloseWebViewNotification, type CommitHistory, @@ -57,6 +75,8 @@ import { OpenExternalChoreo, OpenSubDialogRequest, type ProxyConfig, + PushEverythingToRemoteRepo, + type PushEverythingToRemoteRepoReq, ReadFile, ReadLocalEndpointsConfig, ReadLocalProxyConfig, @@ -84,21 +104,22 @@ import { WebviewNotificationsMethodList, type WebviewQuickPickItem, WebviewStateChangedNotification, + buildGitURL, deepEqual, getShortenedHash, makeURLSafe, } from "@wso2/wso2-platform-core"; import * as yaml from "js-yaml"; -import { ProgressLocation, QuickPickItemKind, Uri, type WebviewPanel, type WebviewView, commands, env, window } from "vscode"; +import { ProgressLocation, QuickPickItemKind, Uri, type WebviewPanel, type WebviewView, commands, env, window, workspace } from "vscode"; import * as vscode from "vscode"; import { Messenger } from "vscode-messenger"; import { BROADCAST } from "vscode-messenger-common"; import { registerChoreoRpcResolver } from "../choreo-rpc"; -import { getChoreoEnv, getChoreoExecPath } from "../choreo-rpc/cli-install"; +import { getChoreoExecPath } from "../choreo-rpc/cli-install"; import { quickPickWithLoader } from "../cmds/cmd-utils"; import { submitCreateComponentHandler } from "../cmds/create-component-cmd"; -import { choreoEnvConfig } from "../config"; import { ext } from "../extensionVariables"; +import { initGit } from "../git/main"; import { getGitHead, getGitRemotes, getGitRoot, hasDirtyRepo, removeCredentialsFromGitURL } from "../git/util"; import { getLogger } from "../logger/logger"; import { authStore } from "../stores/auth-store"; @@ -168,11 +189,14 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W vscode.env.openExternal(vscode.Uri.parse(url)); }); messenger.onRequest(OpenExternalChoreo, (choreoPath: string) => { - if (webviewStateStore.getState().state.extensionName === "Devant") { - vscode.env.openExternal(vscode.Uri.joinPath(vscode.Uri.parse(choreoEnvConfig.getDevantUrl()), choreoPath)); - } else { - vscode.env.openExternal(vscode.Uri.joinPath(vscode.Uri.parse(choreoEnvConfig.getConsoleUrl()), choreoPath)); - } + vscode.env.openExternal( + vscode.Uri.joinPath( + vscode.Uri.parse( + (webviewStateStore.getState().state.extensionName === "Devant" ? ext.config?.devantConsoleUrl : ext.config?.choreoConsoleUrl) || "", + ), + choreoPath, + ), + ); }); messenger.onRequest(SetWebviewCache, async (params: { cacheKey: string; data: any }) => { await ext.context.workspaceState.update(params.cacheKey, params.data); @@ -250,7 +274,7 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W async (params: { orgName: string; projectName: string; componentName: string; deploymentTrackName: string; envName: string; type: string }) => { const { orgName, projectName, componentName, deploymentTrackName, envName, type } = params; // todo: export the env from here - if (getChoreoEnv() !== "prod") { + if (ext.choreoEnv !== "prod") { window.showErrorMessage( "Choreo extension currently displays runtime logs is only if 'WSO2.Platform.Advanced.ChoreoEnvironment' is set to 'prod'", ); @@ -271,16 +295,16 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W return Buffer.from(JSON.stringify(state), "binary").toString("base64"); }; messenger.onRequest(TriggerGithubAuthFlow, async (orgId: string) => { - const { authUrl, clientId, redirectUrl, devantRedirectUrl } = choreoEnvConfig.getGHAppConfig(); const extName = webviewStateStore.getState().state.extensionName; + const baseUrl = extName === "Devant" ? ext.config?.devantConsoleUrl : ext.config?.choreoConsoleUrl; + const redirectUrl = `${baseUrl}/ghapp`; const state = await _getGithubUrlState(orgId); - const ghURL = Uri.parse(`${authUrl}?redirect_uri=${extName === "Devant" ? devantRedirectUrl : redirectUrl}&client_id=${clientId}&state=${state}`); + const ghURL = Uri.parse(`${ext.config?.ghApp.authUrl}?redirect_uri=${redirectUrl}&client_id=${ext.config?.ghApp.clientId}&state=${state}`); await env.openExternal(ghURL); }); messenger.onRequest(TriggerGithubInstallFlow, async (orgId: string) => { - const { installUrl } = choreoEnvConfig.getGHAppConfig(); const state = await _getGithubUrlState(orgId); - const ghURL = Uri.parse(`${installUrl}?state=${state}`); + const ghURL = Uri.parse(`${ext.config?.ghApp.installUrl}?state=${state}`); await env.openExternal(ghURL); }); messenger.onRequest(SubmitComponentCreate, submitCreateComponentHandler); @@ -552,6 +576,116 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W messenger.onRequest(GetConfigFileDrifts, async (params: GetConfigFileDriftsReq) => { return getConfigFileDrifts(params.type, params.repoUrl, params.branch, params.repoDir, ext.context); }); + messenger.onRequest(CloneRepositoryIntoCompDir, async (params: CloneRepositoryIntoCompDirReq) => { + const extName = webviewStateStore.getState().state.extensionName; + const newGit = await initGit(ext.context); + if (!newGit) { + throw new Error("failed to retrieve Git details"); + } + const _repoUrl = buildGitURL(params.repo.orgHandler, params.repo.repo, params.repo.provider, true, params.repo.serverUrl); + if (!_repoUrl || !_repoUrl.startsWith("https://")) { + throw new Error("failed to parse git details"); + } + const urlObj = new URL(_repoUrl); + + if (process.env.CLOUD_STS_TOKEN) { + try { + const gitPat = await window.withProgress({ title: `Accessing the repository ${_repoUrl}...`, location: ProgressLocation.Notification }, () => + ext.clients.rpcClient.getGitTokenForRepository({ + orgId: params.org.id?.toString(), + gitOrg: params.repo.orgName, + gitRepo: params.repo.repo, + secretRef: params.repo.secretRef || "", + }), + ); + + urlObj.username = gitPat.username || "x-access-token"; + urlObj.password = gitPat.token; + } catch { + getLogger().debug(`Failed to get token for ${params}`); + } + } + + const repoUrl = urlObj.href; + + // if ballerina toml exists, need to update the org and name + const balTomlPath = join(params.cwd, "Ballerina.toml"); + if (existsSync(balTomlPath)) { + const fileContent = await fs.promises.readFile(balTomlPath, "utf-8"); + const parsedToml: any = toml.parse(fileContent); + if (parsedToml?.package) { + parsedToml.package.org = params.org.handle; + parsedToml.package.name = params.componentName?.replaceAll("-", "_"); + } + const updatedTomlContent = toml.stringify(parsedToml); + await fs.promises.writeFile(balTomlPath, updatedTomlContent, "utf-8"); + } + + if (params.repo?.isBareRepo && ["", "/", "."].includes(params.subpath)) { + // if component is to be created in the root of a bare repo, + // the we can initialize the current directory as the repo root + await window.withProgress({ title: `Initializing currently opened directory as repository ${_repoUrl}...`, location: ProgressLocation.Notification }, async () => { + await newGit.init(params.cwd); + const dotGit = await newGit?.getRepositoryDotGit(params.cwd); + const repo = newGit.open(params.cwd, dotGit); + await repo.addRemote("origin", repoUrl); + await repo.add(["."]); + await repo.commit(`Add source for new ${extName} ${extName === "Devant" ? "Integration" : "Component"} (${params.componentName})`); + await repo.push("origin", "main"); + await repo.fetch(); + }); + return params.cwd; + } + + const clonedPath = await window.withProgress( + { + title: `Cloning repository ${params.repo?.orgHandler}/${params.repo.repo}`, + location: ProgressLocation.Notification, + }, + async (progress, cancellationToken) => + newGit.clone( + repoUrl, + { + recursive: true, + ref: params.repo.branch, + parentPath: join(params.cwd, ".."), + progress: { + report: ({ increment, ...rest }: { increment: number }) => progress.report({ increment: increment, ...rest }), + }, + }, + cancellationToken, + ), + ); + + // Move everything into cloned dir + const cwdFiled = readdirSync(params.cwd); + const newPath = join(clonedPath, params.subpath); + fs.mkdirSync(newPath, { recursive: true }); + + for (const file of cwdFiled) { + const cwdFilePath = join(params.cwd, file); + const destFilePath = join(newPath, file); + fs.cpSync(cwdFilePath, destFilePath, { recursive: true }); + } + + return newPath; + }); + + messenger.onRequest(PushEverythingToRemoteRepo, async (params: PushEverythingToRemoteRepoReq) => { + const newGit = await initGit(ext.context); + if (!newGit) { + throw new Error("failed to initGit"); + } + const extName = webviewStateStore.getState().state.extensionName; + const repoRoot = await newGit?.getRepositoryRoot(params.dirPath); + const dotGit = await newGit?.getRepositoryDotGit(params.dirPath); + const repo = newGit.open(repoRoot, dotGit); + await window.withProgress({ title: "Pushing the changes to your remote repository...", location: ProgressLocation.Notification }, async () => { + await repo.add(["."]); + await repo.commit(`Add source for new ${extName} ${extName === "Devant" ? "Integration" : "Component"} (${params.componentName})`); + await repo.push(); + }); + }); // Register Choreo CLL RPC handler registerChoreoRpcResolver(messenger, ext.clients.rpcClient); diff --git a/workspaces/wso2-platform/wso2-platform-extension/webpack.config.js b/workspaces/wso2-platform/wso2-platform-extension/webpack.config.js index 0dbaf0a01bb..0f28ec42806 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/webpack.config.js +++ b/workspaces/wso2-platform/wso2-platform-extension/webpack.config.js @@ -22,18 +22,19 @@ const CopyPlugin = require("copy-webpack-plugin"); const PermissionsOutputPlugin = require("webpack-permissions-plugin"); const webpack = require("webpack"); const dotenv = require("dotenv"); -const { createEnvDefinePlugin } = require('../../../common/scripts/env-webpack-helper'); +const { createEnvDefinePlugin } = require("../../../common/scripts/env-webpack-helper"); const envPath = path.resolve(__dirname, ".env"); const env = dotenv.config({ path: envPath }).parsed; + console.log("Fetching values for environment variables..."); const { envKeys, missingVars } = createEnvDefinePlugin(env); if (missingVars.length > 0) { - console.warn( - '\n⚠️ Environment Variable Configuration Warning:\n' + - `Missing required environment variables: ${missingVars.join(', ')}\n` + - `Please provide values in either .env file or runtime environment.\n` - ); + console.warn( + `\n⚠️ Environment Variable Configuration Warning:\n + Missing required environment variables: ${missingVars.join(", ")}\n + Please provide values in either .env file or runtime environment.\n`, + ); } //@ts-check @@ -81,7 +82,7 @@ const extensionConfig = { }, ], }, - devtool: !process.env.CI ? "source-map" : undefined, + devtool: "source-map", infrastructureLogging: { level: "log", }, diff --git a/workspaces/wso2-platform/wso2-platform-webviews/package.json b/workspaces/wso2-platform/wso2-platform-webviews/package.json index 5f74ed8270f..d82fb50ff8a 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/package.json +++ b/workspaces/wso2-platform/wso2-platform-webviews/package.json @@ -31,7 +31,7 @@ "clipboardy": "^4.0.0", "@formkit/auto-animate": "0.8.2", "timezone-support": "^3.1.0", - "swagger-ui-react": "^5.22.0", + "swagger-ui-react": "5.22.0", "@biomejs/biome": "^1.9.4", "@headlessui/react": "^2.1.2", "react-markdown": "^7.1.0", @@ -52,7 +52,6 @@ "@types/vscode-webview": "^1.57.5", "css-loader": "^7.1.2", "file-loader": "^6.2.0", - "node-sass": "^9.0.0", "sass-loader": "^16.0.5", "style-loader": "^4.0.0", "ts-loader": "^9.5.2", diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/components/FormElements/Dropdown/Dropdown.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/components/FormElements/Dropdown/Dropdown.tsx index 0e4740fe05f..c94e58d6e20 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/components/FormElements/Dropdown/Dropdown.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/components/FormElements/Dropdown/Dropdown.tsx @@ -28,7 +28,7 @@ interface Props { required?: boolean; loading?: boolean; control?: Control; - items?: ({ value: string; label?: string } | string)[]; + items?: ({ value: string; label?: string; type?: "separator" } | string)[]; disabled?: boolean; wrapClassName?: HTMLProps["className"]; } @@ -53,10 +53,20 @@ export const Dropdown: FC = (props) => { disabled={disabled || loading || undefined} {...field} > - {items?.map((item) => ( - - {typeof item === "string" ? item : item?.label || item.value} - + {items?.map((item, index) => ( + <> + {typeof item !== "string" && item.type === "separator" ? ( + + ) : ( + + {typeof item === "string" ? item : item?.label || item.value} + + )} + ))} diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/components/SwaggerUI/SwaggerUI.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/components/SwaggerUI/SwaggerUI.tsx index f6e6fb1b45e..0aa9802ca2c 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/components/SwaggerUI/SwaggerUI.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/components/SwaggerUI/SwaggerUI.tsx @@ -18,7 +18,7 @@ import React, { type HTMLProps, type FC } from "react"; import SwaggerUIReact from "swagger-ui-react"; -import "@wso2/ui-toolkit/src/styles/swagger/main.scss"; +import "@wso2/ui-toolkit/src/styles/swagger/styles.css"; import classNames from "classnames"; import type SwaggerUIProps from "swagger-ui-react/swagger-ui-react"; import { Codicon } from "../Codicon/Codicon"; diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/components/VerticalStepper/VerticalStepper.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/components/VerticalStepper/VerticalStepper.tsx index 322b22d49c7..d04696240cc 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/components/VerticalStepper/VerticalStepper.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/components/VerticalStepper/VerticalStepper.tsx @@ -71,22 +71,27 @@ export const VerticalStepperItem: FC = ({ return (
-
-
- {index < currentStep ? : {index + 1}} + {totalSteps > 1 && ( +
+
+ {index < currentStep ? : {index + 1}} +
+
{item.label}
-
{item.label}
-
+ )} +
-
-
-
+ {totalSteps > 1 && ( +
+
+
+ )}
{index === currentStep && (
diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/hooks/use-queries.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/hooks/use-queries.tsx index fd5993d28a4..009d5b75aac 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/hooks/use-queries.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/hooks/use-queries.tsx @@ -31,6 +31,7 @@ import { type DeploymentLogsData, type DeploymentTrack, type Environment, + type GetAuthorizedGitOrgsResp, type GetAutoBuildStatusResp, type GetTestKeyResp, type Organization, @@ -46,29 +47,30 @@ export const queryKeys = { "has-config-drift", { directoryPath, component: component?.metadata?.id, branch }, ], - getProjectEnvs: (project: Project, org: Organization) => ["get-project-envs", { organization: org.handle, project: project.handler }], + getProjectEnvs: (project: Project, org: Organization) => ["get-project-envs", { organization: org.uuid, project: project.id }], getTestKey: (endpointApimId: string, env: Environment, org: Organization) => [ "get-test-key", - { endpoint: endpointApimId, env: env.id, org: org.handle }, + { endpoint: endpointApimId, env: env.id, org: org.uuid }, ], - getSwaggerSpec: (apiRevisionId: string, org: Organization) => ["get-swagger-spec", { selectedEndpoint: apiRevisionId, org: org.handle }], + getSwaggerSpec: (apiRevisionId: string, org: Organization) => ["get-swagger-spec", { selectedEndpoint: apiRevisionId, org: org.uuid }], getBuildPacks: (selectedType: string, org: Organization) => ["build-packs", { selectedType, orgId: org?.id }], + getAuthorizedGitOrgs: (orgId: string, provider: string, credRef = "") => ["get-authorized-github-orgs", { orgId, provider, credRef }], getGitBranches: (repoUrl: string, org: Organization, credRef: string, isAccessible: boolean) => [ "get-git-branches", { repo: repoUrl, orgId: org?.id, credRef, isAccessible }, ], getDeployedEndpoints: (deploymentTrack: DeploymentTrack, component: ComponentKind, org: Organization) => [ "get-deployed-endpoints", - { organization: org.handle, component: component.metadata.id, deploymentTrackId: deploymentTrack?.id }, + { organization: org.uuid, component: component.metadata.id, deploymentTrackId: deploymentTrack?.id }, ], getProxyDeploymentInfo: (component: ComponentKind, org: Organization, env: Environment, apiVersion: ApiVersion) => [ "get-proxy-deployment-info", - { org: org.handle, component: component.metadata.id, env: env?.id, apiVersion: apiVersion?.id }, + { org: org.uuid, component: component.metadata.id, env: env?.id, apiVersion: apiVersion?.id }, ], getDeploymentStatus: (deploymentTrack: DeploymentTrack, component: ComponentKind, org: Organization, env: Environment) => [ "get-deployment-status", { - organization: org.handle, + organization: org.uuid, component: component.metadata.id, deploymentTrackId: deploymentTrack?.id, envId: env.id, @@ -77,28 +79,34 @@ export const queryKeys = { getWorkflowStatus: (org: Organization, env: Environment, buildId: string) => [ "get-workflow-status", { - organization: org?.handle, + organization: org?.uuid, envId: env?.id, buildId, }, ], getBuilds: (deploymentTrack: DeploymentTrack, component: ComponentKind, project: Project, org: Organization) => [ "get-builds", - { component: component.metadata.id, organization: org.handle, project: project.handler, branch: deploymentTrack?.branch }, + { component: component.metadata.id, organization: org.uuid, project: project.id, branch: deploymentTrack?.branch }, ], - getBuildsLogs: (component: ComponentKind, project: Project, org: Organization, build: BuildKind) => [ + getBuildsLogs: (component: ComponentKind, deploymentTrack: DeploymentTrack, project: Project, org: Organization, build: BuildKind) => [ "get-build-logs", - { component: component.metadata.id, organization: org.handle, project: project.handler, build: build?.status?.runId }, + { + component: component.metadata.id, + deploymentTrack: deploymentTrack.id, + organization: org.uuid, + project: project.id, + build: build?.status?.runId, + }, ], getComponentConnections: (component: ComponentKind, project: Project, org: Organization) => [ "get-component-connections", - { component: component.metadata.id, organization: org.handle, project: project.handler }, + { component: component.metadata.id, organization: org.uuid, project: project.id }, ], - useComponentList: (project: Project, org: Organization) => ["get-components", { organization: org.handle, project: project.handler }], - getProjectConnections: (project: Project, org: Organization) => ["get-project-connections", { organization: org.handle, project: project.handler }], + useComponentList: (project: Project, org: Organization) => ["get-components", { organization: org.uuid, project: project.id }], + getProjectConnections: (project: Project, org: Organization) => ["get-project-connections", { organization: org.uuid, project: project.id }], getAutoBuildStatus: (component: ComponentKind, deploymentTrack: DeploymentTrack, org: Organization) => [ "get-auto-build-status", - { component: component.metadata.id, organization: org.handle, versionId: deploymentTrack?.id }, + { component: component.metadata.id, organization: org.uuid, versionId: deploymentTrack?.id }, ], }; @@ -151,6 +159,13 @@ export const useGetBuildPacks = (selectedType: string, org: Organization, option options, ); +export const useGetAuthorizedGitOrgs = (orgId: string, provider: string, credRef = "", options?: UseQueryOptions) => + useQuery( + queryKeys.getAuthorizedGitOrgs(orgId, provider, credRef), + () => ChoreoWebViewAPI.getInstance().getChoreoRpcClient().getAuthorizedGitOrgs({ orgId, credRef }), + options, + ); + export const useGetGitBranches = (repoUrl: string, org: Organization, credRef = "", isAccessible = false, options?: UseQueryOptions) => useQuery( queryKeys.getGitBranches(repoUrl, org, credRef, isAccessible), @@ -386,13 +401,14 @@ export const useGoToSource = () => { export const useGetBuildLogs = ( component: ComponentKind, + deploymentTrack: DeploymentTrack, org: Organization, project: Project, build: BuildKind, options?: UseQueryOptions, ) => useQuery( - queryKeys.getBuildsLogs(component, project, org, build), + queryKeys.getBuildsLogs(component, deploymentTrack, project, org, build), async () => { try { const buildLog = await ChoreoWebViewAPI.getInstance().getChoreoRpcClient().getBuildLogs({ @@ -400,8 +416,12 @@ export const useGetBuildLogs = ( displayType: component.spec.type, orgHandler: org.handle, orgId: org.id.toString(), + orgUuid: org.uuid, projectId: project.id, buildId: build.status?.runId, + buildRef: build.status?.buildRef, + clusterId: build.status?.clusterId, + deploymentTrackId: deploymentTrack.id, }); return buildLog ?? null; } catch { diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/providers/react-query-provider.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/providers/react-query-provider.tsx index a1d782893a2..192a9ec3cc3 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/providers/react-query-provider.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/providers/react-query-provider.tsx @@ -53,7 +53,7 @@ export const ChoreoWebviewQueryClientProvider = ({ type, children }: { type: str } persistOptions={{ persister: webviewStatePersister(`react-query-persister-${type}`), - buster: "choreo-webview-cache-v2", + buster: "choreo-webview-cache-v5", }} > {children} diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/utilities/vscode-webview-rpc.ts b/workspaces/wso2-platform/wso2-platform-webviews/src/utilities/vscode-webview-rpc.ts index 2b22d597887..f3239497e6b 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/utilities/vscode-webview-rpc.ts +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/utilities/vscode-webview-rpc.ts @@ -19,8 +19,11 @@ import { type AuthState, AuthStoreChangedNotification, + ChoreoRpcGetAuthorizedGitOrgsRequest, ChoreoRpcWebview, ClearWebviewCache, + CloneRepositoryIntoCompDir, + type CloneRepositoryIntoCompDirReq, CloseComponentViewDrawer, CloseWebViewNotification, type CommitHistory, @@ -39,6 +42,7 @@ import { ExecuteCommandRequest, FileExists, GetAuthState, + GetAuthorizedGitOrgsReq, GetConfigFileDrifts, type GetConfigFileDriftsReq, GetContextState, @@ -59,6 +63,8 @@ import { OpenExternalChoreo, OpenSubDialogRequest, type OpenTestViewReq, + PushEverythingToRemoteRepo, + type PushEverythingToRemoteRepoReq, ReadFile, ReadLocalEndpointsConfig, type ReadLocalEndpointsConfigResp, @@ -245,6 +251,14 @@ export class ChoreoWebViewAPI { return this._messenger.sendRequest(TriggerGithubAuthFlow, HOST_EXTENSION, orgId); } + public async cloneRepositoryIntoCompDir(params: CloneRepositoryIntoCompDirReq): Promise { + return this._messenger.sendRequest(CloneRepositoryIntoCompDir, HOST_EXTENSION, params); + } + + public async pushEverythingToRemoteRepo(params: PushEverythingToRemoteRepoReq): Promise { + return this._messenger.sendRequest(PushEverythingToRemoteRepo, HOST_EXTENSION, params); + } + public async triggerGithubInstallFlow(orgId: string): Promise { return this._messenger.sendRequest(TriggerGithubInstallFlow, HOST_EXTENSION, orgId); } diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/ComponentDetailsView.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/ComponentDetailsView.tsx index ab02ea7c246..d1b5edf5e26 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/ComponentDetailsView.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/ComponentDetailsView.tsx @@ -17,7 +17,7 @@ */ import { useAutoAnimate } from "@formkit/auto-animate/react"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { type BuildKind, ChoreoComponentType, @@ -51,9 +51,11 @@ export const ComponentDetailsView: FC = (props) = const deploymentTracks = component?.deploymentTracks ?? []; const [rightPanelRef] = useAutoAnimate(); const type = getTypeForDisplayType(props.component.spec?.type); + const queryClient = useQueryClient(); const [deploymentTrack, setDeploymentTrack] = useState(deploymentTracks?.find((item) => item.latest)); const [hasOngoingBuilds, setHasOngoingBuilds] = useState(false); + const [prevBuildList, setPrevBuildList] = useState([]); const [buildDetailsPanel, setBuildDetailsPanel] = useState<{ open: boolean; build?: BuildKind }>({ open: false, build: null }); useEffect(() => { @@ -125,12 +127,12 @@ export const ComponentDetailsView: FC = (props) = refetchOnWindowFocus: true, }); - const buildLogsQueryData = useGetBuildLogs(component, organization, project, buildDetailsPanel?.build, { + const buildLogsQueryData = useGetBuildLogs(component, deploymentTrack, organization, project, buildDetailsPanel?.build, { enabled: !!buildDetailsPanel?.build, }); const buildListQueryData = useGetBuildList(deploymentTrack, component, project, organization, { - onSuccess: (builds) => { + onSuccess: async (builds) => { setHasOngoingBuilds(builds.some((item) => item.status?.conclusion === "")); if (buildDetailsPanel?.open && buildDetailsPanel?.build) { const matchingItem = builds.find((item) => item.status?.runId === buildDetailsPanel?.build?.status?.runId); @@ -139,9 +141,27 @@ export const ComponentDetailsView: FC = (props) = } buildLogsQueryData.refetch(); } + const hasPrevSucceedBuilds = prevBuildList.filter((item) => item.status.conclusion === "success").length > 0; + if (!hasPrevSucceedBuilds && builds.length > 0 && builds[0].status?.conclusion === "success" && envs.length > 0) { + // have a new succeeded build, which should be auto deployed + await new Promise((resolve) => setTimeout(resolve, 10000)); + if (getTypeForDisplayType(component.spec?.type) === ChoreoComponentType.ApiProxy) { + queryClient.refetchQueries({ + queryKey: queryKeys.getProxyDeploymentInfo( + component, + organization, + envs[0], + component?.apiVersions?.find((item) => item.latest), + ), + }); + } else { + queryClient.refetchQueries({ queryKey: queryKeys.getDeploymentStatus(deploymentTrack, component, organization, envs[0]) }); + } + } + setPrevBuildList(builds); }, enabled: !!deploymentTrack, - refetchInterval: hasOngoingBuilds ? 5000 : false, + refetchInterval: hasOngoingBuilds || (props.isNewComponent && prevBuildList.length === 0) ? 5000 : false, }); const succeededBuilds = useMemo( diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/BuildConfigsSection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/BuildConfigsSection.tsx index 8e8c5193b67..b36a3ca6c36 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/BuildConfigsSection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/BuildConfigsSection.tsx @@ -30,6 +30,9 @@ import { type IRightPanelSectionItem, RightPanelSection, RightPanelSectionItem } export const BuildConfigsSection: FC<{ component: ComponentKind }> = ({ component }) => { const buildConfigList = getBuildConfigViewList(component); + if (buildConfigList.length === 0) { + return null; + } return ( diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx index 268c7714aa4..762c816efe2 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx @@ -18,7 +18,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { ChoreoBuildPackNames, ChoreoComponentType, @@ -29,18 +29,20 @@ import { type NewComponentWebviewProps, type SubmitComponentCreateReq, WebAppSPATypes, + buildGitURL, getComponentTypeText, getIntegrationComponentTypeText, getRandomNumber, makeURLSafe, parseGitURL, } from "@wso2/wso2-platform-core"; -import React, { type FC, useState, useEffect } from "react"; +import React, { type FC, useState } from "react"; import { useForm } from "react-hook-form"; import type { z } from "zod"; import { HeaderSection } from "../../components/HeaderSection"; +import type { HeaderTag } from "../../components/HeaderSection/HeaderSection"; import { type StepItem, VerticalStepper } from "../../components/VerticalStepper"; -import { useComponentList } from "../../hooks/use-queries"; +import { queryKeys, useComponentList } from "../../hooks/use-queries"; import { useExtWebviewContext } from "../../providers/ext-vewview-ctx-provider"; import { ChoreoWebViewAPI } from "../../utilities/vscode-webview-rpc"; import { @@ -48,19 +50,23 @@ import { type componentEndpointsFormSchema, type componentGeneralDetailsSchema, type componentGitProxyFormSchema, + type componentRepoInitSchema, getComponentEndpointsFormSchema, getComponentFormSchemaBuildDetails, getComponentFormSchemaGenDetails, getComponentGitProxyFormSchema, + getRepoInitSchemaGenDetails, sampleEndpointItem, } from "./componentFormSchema"; import { ComponentFormBuildSection } from "./sections/ComponentFormBuildSection"; import { ComponentFormEndpointsSection } from "./sections/ComponentFormEndpointsSection"; import { ComponentFormGenDetailsSection } from "./sections/ComponentFormGenDetailsSection"; import { ComponentFormGitProxySection } from "./sections/ComponentFormGitProxySection"; +import { ComponentFormRepoInitSection } from "./sections/ComponentFormRepoInitSection"; import { ComponentFormSummarySection } from "./sections/ComponentFormSummarySection"; type ComponentFormGenDetailsType = z.infer; +type ComponentRepoInitType = z.infer; type ComponentFormBuildDetailsType = z.infer; type ComponentFormEndpointsType = z.infer; type ComponentFormGitProxyType = z.infer; @@ -76,6 +82,7 @@ export const ComponentFormView: FC = (props) => { existingComponents: existingComponentsCache, } = props; const type = initialValues?.type; + const queryClient = useQueryClient(); const [formSections] = useAutoAnimate(); const { extensionName } = useExtWebviewContext(); @@ -89,6 +96,20 @@ export const ComponentFormView: FC = (props) => { defaultValues: { name: initialValues?.name || "", subPath: "", gitRoot: "", repoUrl: "", branch: "", credential: "", gitProvider: "" }, }); + const repoInitForm = useForm({ + resolver: zodResolver(getRepoInitSchemaGenDetails(existingComponents), { async: true }, { mode: "async" }), + mode: "all", + defaultValues: { + org: "", + repo: "", + branch: "main", + subPath: "/", + name: initialValues?.name || "", + gitProvider: GitProvider.GITHUB, + serverUrl: "", + }, + }); + const name = genDetailsForm.watch("name"); const gitRoot = genDetailsForm.watch("gitRoot"); const subPath = genDetailsForm.watch("subPath"); @@ -156,16 +177,50 @@ export const ComponentFormView: FC = (props) => { }, }); - const { mutate: createComponent, isLoading: isCreatingComponent } = useMutation({ + const { mutateAsync: initializeRepoAsync, isLoading: initializingRepo } = useMutation({ mutationFn: async () => { + if (props.isNewCodeServerComp) { + const repoInitDetails = repoInitForm.getValues(); + const repoUrl = buildGitURL(repoInitDetails?.orgHandler, repoInitDetails.repo, repoInitDetails.gitProvider, false, repoInitDetails.serverUrl); + const branchesCache: string[] = queryClient.getQueryData(queryKeys.getGitBranches(repoUrl, organization, "", true)); + const newWorkspacePath = await ChoreoWebViewAPI.getInstance().cloneRepositoryIntoCompDir({ + cwd: props.directoryFsPath, + subpath: repoInitDetails.subPath, + org: props.organization, + componentName: makeURLSafe(repoInitDetails.name), + repo: { + orgHandler: repoInitDetails.orgHandler, + orgName: repoInitDetails.org, + branch: branchesCache?.length > 0 ? repoInitDetails.branch : undefined, + provider: repoInitDetails.gitProvider, + repo: repoInitDetails.repo, + serverUrl: repoInitDetails.serverUrl, + secretRef: repoInitDetails.credential || "", + isBareRepo: !(branchesCache?.length > 0), + }, + }); + + return newWorkspacePath; + } + }, + }); + + const { mutate: createComponent, isLoading: isCreatingComponent } = useMutation({ + mutationFn: async (newWorkspaceDir?: string) => { const genDetails = genDetailsForm.getValues(); + const repoInitDetails = repoInitForm.getValues(); const buildDetails = buildDetailsForm.getValues(); const gitProxyDetails = gitProxyForm.getValues(); - const componentName = makeURLSafe(genDetails.name); - + const name = props.isNewCodeServerComp ? repoInitDetails.name : genDetails.name; + const componentName = makeURLSafe(props.isNewCodeServerComp ? repoInitDetails.name : genDetails.name); + const branch = props.isNewCodeServerComp ? repoInitDetails.branch : genDetails.branch; const parsedRepo = parseGitURL(genDetails.repoUrl); - const provider = parsedRepo ? parsedRepo[2] : null; + const provider = props.isNewCodeServerComp ? repoInitDetails.gitProvider : parsedRepo[2]; + + const repoUrl = props.isNewCodeServerComp + ? buildGitURL(repoInitDetails.orgHandler, repoInitDetails.repo, repoInitDetails.gitProvider, false, repoInitDetails.serverUrl) + : genDetails.repoUrl; const createParams: Partial = { orgId: organization.id.toString(), @@ -173,14 +228,14 @@ export const ComponentFormView: FC = (props) => { projectId: project.id, projectHandle: project.handler, name: componentName, - displayName: genDetails.name, + displayName: name, type, componentSubType: initialValues?.subType || "", buildPackLang: buildDetails.buildPackLang, - componentDir: directoryFsPath, - repoUrl: genDetails.repoUrl, - gitProvider: genDetails.gitProvider, - branch: genDetails.branch, + componentDir: newWorkspaceDir || directoryFsPath, + repoUrl: repoUrl, + gitProvider: provider, + branch: branch, langVersion: buildDetails.langVersion, port: buildDetails.webAppPort, originCloud: extensionName === "Devant" ? "devant" : "choreo", @@ -248,8 +303,32 @@ export const ComponentFormView: FC = (props) => { onSuccess: () => setStepIndex(stepIndex + 1), }); - const steps: StepItem[] = [ - { + const steps: StepItem[] = []; + + if (props.isNewCodeServerComp) { + steps.push({ + label: "Repository Details", + content: ( + { + const newDirPath = await initializeRepoAsync(); + if (steps.length > 1) { + gitProxyForm.setValue("proxyContext", `/${makeURLSafe(genDetailsForm.getValues()?.name)}`); + setStepIndex(stepIndex + 1); + } else { + createComponent(newDirPath); + } + }} + /> + ), + }); + } else { + steps.push({ label: "General Details", content: ( = (props) => { form={genDetailsForm} componentType={type} onNextClick={() => { - gitProxyForm.setValue( - "proxyContext", - genDetailsForm.getValues()?.name ? `/${makeURLSafe(genDetailsForm.getValues()?.name)}` : `/path-${getRandomNumber()}`, - ); + gitProxyForm.setValue("proxyContext", `/${makeURLSafe(genDetailsForm.getValues()?.name)}`); setStepIndex(stepIndex + 1); }} /> ), - }, - ]; - - let showBuildDetails = false; - if (type !== ChoreoComponentType.ApiProxy) { - if (!initialValues?.buildPackLang) { - showBuildDetails = true; - } else { - if (initialValues?.buildPackLang === ChoreoBuildPackNames.Ballerina) { - showBuildDetails = type === ChoreoComponentType.Service; - } else if (initialValues?.buildPackLang === ChoreoBuildPackNames.MicroIntegrator) { - showBuildDetails = type === ChoreoComponentType.Service; - } else { + }); + + let showBuildDetails = false; + if (type !== ChoreoComponentType.ApiProxy) { + if (!initialValues?.buildPackLang) { + showBuildDetails = true; + } else if ( + ![ChoreoBuildPackNames.Ballerina, ChoreoBuildPackNames.MicroIntegrator].includes(initialValues?.buildPackLang as ChoreoBuildPackNames) + ) { showBuildDetails = true; } } - } - if (showBuildDetails) { - steps.push({ - label: "Build Details", - content: ( - setStepIndex(stepIndex + 1)} - onBackClick={() => setStepIndex(stepIndex - 1)} - form={buildDetailsForm} - selectedType={type} - subPath={subPath} - gitRoot={gitRoot} - baseUriPath={directoryUriPath} - /> - ), - }); - } + if (showBuildDetails) { + steps.push({ + label: "Build Details", + content: ( + setStepIndex(stepIndex + 1)} + onBackClick={() => setStepIndex(stepIndex - 1)} + form={buildDetailsForm} + selectedType={type} + subPath={subPath} + gitRoot={gitRoot} + baseUriPath={directoryUriPath} + /> + ), + }); + } - if (type === ChoreoComponentType.Service) { - if ( - ![ChoreoBuildPackNames.MicroIntegrator, ChoreoBuildPackNames.Ballerina].includes(buildPackLang as ChoreoBuildPackNames) || - ([ChoreoBuildPackNames.MicroIntegrator, ChoreoBuildPackNames.Ballerina].includes(buildPackLang as ChoreoBuildPackNames) && !useDefaultEndpoints) - ) { + if (type === ChoreoComponentType.Service && extensionName !== "Devant") { + if ( + ![ChoreoBuildPackNames.MicroIntegrator, ChoreoBuildPackNames.Ballerina].includes(buildPackLang as ChoreoBuildPackNames) || + ([ChoreoBuildPackNames.MicroIntegrator, ChoreoBuildPackNames.Ballerina].includes(buildPackLang as ChoreoBuildPackNames) && + !useDefaultEndpoints) + ) { + steps.push({ + label: "Endpoint Details", + content: ( + submitEndpoints(data.endpoints as Endpoint[])} + onBackClick={() => setStepIndex(stepIndex - 1)} + isSaving={isSubmittingEndpoints} + form={endpointDetailsForm} + /> + ), + }); + } + } + if (type === ChoreoComponentType.ApiProxy) { steps.push({ - label: "Endpoint Details", + label: "Proxy Details", content: ( - submitEndpoints(data.endpoints as Endpoint[])} + key="git-proxy-step" + onNextClick={(data) => submitProxyConfig(data)} onBackClick={() => setStepIndex(stepIndex - 1)} - isSaving={isSubmittingEndpoints} - form={endpointDetailsForm} + isSaving={isSubmittingProxyConfig} + form={gitProxyForm} /> ), }); } - } - if (type === ChoreoComponentType.ApiProxy) { + steps.push({ - label: "Proxy Details", + label: "Summary", content: ( - submitProxyConfig(data)} + key="summary-step" + genDetailsForm={genDetailsForm} + buildDetailsForm={buildDetailsForm} + endpointDetailsForm={endpointDetailsForm} + gitProxyForm={gitProxyForm} + onNextClick={() => createComponent(undefined)} onBackClick={() => setStepIndex(stepIndex - 1)} - isSaving={isSubmittingProxyConfig} - form={gitProxyForm} + isCreating={isCreatingComponent} /> ), }); } - steps.push({ - label: "Summary", - content: ( - createComponent()} - onBackClick={() => setStepIndex(stepIndex - 1)} - isCreating={isCreatingComponent} - /> - ), - }); - const componentTypeText = extensionName === "Devant" ? getIntegrationComponentTypeText(type, initialValues?.subType) : getComponentTypeText(type); + const headerTags: HeaderTag[] = []; + + if (!props.isNewCodeServerComp) { + headerTags.push({ label: "Source Directory", value: subPath && subPath !== "." ? subPath : directoryName }); + } + headerTags.push({ label: "Project", value: project.name }, { label: "Organization", value: organization.name }); + return (
+
diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/componentFormSchema.ts b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/componentFormSchema.ts index c1a75b6048d..2ad8863b618 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/componentFormSchema.ts +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/componentFormSchema.ts @@ -26,6 +26,7 @@ import { GitProvider, GoogleProviderBuildPackNames, type OpenApiSpec, + Organization, WebAppSPATypes, capitalizeFirstLetter, makeURLSafe, @@ -35,14 +36,33 @@ import * as yaml from "js-yaml"; import { z } from "zod"; import { ChoreoWebViewAPI } from "../../utilities/vscode-webview-rpc"; +export const componentRepoInitSchema = z.object({ + org: z.string().min(1, "Required"), + orgHandler: z.string(), + repo: z.string().min(1, "Required"), + branch: z.string(), + subPath: z.string().regex(/^(\/)?([a-zA-Z0-9_-]+(\/)?)*$/, "Invalid path"), + name: z + .string() + .min(1, "Required") + .min(3, "Needs to be at least characters") + .max(60, "Max length exceeded") + .regex(/^[A-Za-z]/, "Needs to start with alphabetic letter") + .regex(/^[A-Za-z\s\d\-_]+$/, "Cannot have special characters"), + gitProvider: z.string().min(1, "Required"), + credential: z.string(), + serverUrl: z.string(), +}); + export const componentGeneralDetailsSchema = z.object({ name: z .string() .min(1, "Required") + .min(3, "Needs to be at least characters") .max(60, "Max length exceeded") .regex(/^[A-Za-z]/, "Needs to start with alphabetic letter") .regex(/^[A-Za-z\s\d\-_]+$/, "Cannot have special characters"), - subPath: z.string(), + subPath: z.string(), // todo: add regex gitRoot: z.string(), repoUrl: z.string().min(1, "Required"), gitProvider: z.string().min(1, "Required"), @@ -201,6 +221,16 @@ export const getComponentFormSchemaGenDetails = (existingComponents: ComponentKi } }); +export const getRepoInitSchemaGenDetails = (existingComponents: ComponentKind[]) => + componentRepoInitSchema.partial().superRefine(async (data, ctx) => { + if (existingComponents.some((item) => item.metadata.name === makeURLSafe(data.name))) { + ctx.addIssue({ path: ["name"], code: z.ZodIssueCode.custom, message: "Name already exists" }); + } + if (data.gitProvider !== GitProvider.GITHUB && !data.credential) { + ctx.addIssue({ path: ["credential"], code: z.ZodIssueCode.custom, message: "Required" }); + } + }); + export const getComponentFormSchemaBuildDetails = (type: string, directoryFsPath: string, gitRoot: string) => componentBuildDetailsSchema.partial().superRefine(async (data, ctx) => { if ( diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGenDetailsSection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGenDetailsSection.tsx index a8ccdb4829a..fb5a6b05aea 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGenDetailsSection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGenDetailsSection.tsx @@ -200,8 +200,7 @@ export const ComponentFormGenDetailsSection: FC = ({ onNextClick, organiz if (isRepoAuthorizedResp?.retrievedRepos) { invalidRepoMsg = ( - {extensionName} lacks access to the selected repository.{" "} - (Only public repos are allowed within the free tier.) + {extensionName} lacks access to the selected repository. ); invalidRepoAction = "Grant Access"; @@ -216,10 +215,7 @@ export const ComponentFormGenDetailsSection: FC = ({ onNextClick, organiz onInvalidRepoActionClick = () => ChoreoWebViewAPI.getInstance().openExternalChoreo(`organizations/${organization.handle}/settings/credentials`); if (isRepoAuthorizedResp?.retrievedRepos) { invalidRepoMsg = ( - - Selected Credential does not have sufficient permissions.{" "} - (Only public repos are allowed within the free tier.) - + Selected Credential does not have sufficient permissions to access the repository. ); invalidRepoAction = "Manage Credentials"; } else { diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx new file mode 100644 index 00000000000..808fa12817c --- /dev/null +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx @@ -0,0 +1,368 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useAutoAnimate } from "@formkit/auto-animate/react"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { RequiredFormInput } from "@wso2/ui-toolkit"; +import { GitProvider, type NewComponentWebviewProps, buildGitURL } from "@wso2/wso2-platform-core"; +import debounce from "lodash.debounce"; +import React, { type FC, useCallback, useEffect, useState } from "react"; +import type { SubmitHandler, UseFormReturn } from "react-hook-form"; +import type { z } from "zod"; +import { Banner } from "../../../components/Banner"; +import { Button } from "../../../components/Button"; +import { Dropdown } from "../../../components/FormElements/Dropdown"; +import { TextField } from "../../../components/FormElements/TextField"; +import { useGetAuthorizedGitOrgs, useGetGitBranches } from "../../../hooks/use-queries"; +import { useExtWebviewContext } from "../../../providers/ext-vewview-ctx-provider"; +import { ChoreoWebViewAPI } from "../../../utilities/vscode-webview-rpc"; +import type { componentRepoInitSchema } from "../componentFormSchema"; + +type ComponentRepoInitSchemaType = z.infer; + +interface Props extends NewComponentWebviewProps { + onNextClick: () => void; + initializingRepo?: boolean; + initialFormValues?: ComponentRepoInitSchemaType; + form: UseFormReturn; + componentType: string; +} + +const connectMoreRepoText = "Connect More Repositories"; +const createNewRpoText = "Create New Repository"; +const createNewCredText = "Create New Credential"; + +export const ComponentFormRepoInitSection: FC = ({ onNextClick, organization, form, initializingRepo }) => { + const [compDetailsSections] = useAutoAnimate(); + const { extensionName } = useExtWebviewContext(); + const [creatingRepo, setCreatingRepo] = useState(false); + + const orgName = form.watch("org"); + const repo = form.watch("repo"); + const subPath = form.watch("subPath"); + const serverUrl = form.watch("serverUrl"); + const provider = form.watch("gitProvider"); + const credential = form.watch("credential"); + const repoError = form.formState?.errors?.repo; + const repoName = [connectMoreRepoText, createNewRpoText].includes(repo) ? "" : repo; + + const { + data: gitOrgs, + isLoading: loadingGitOrgs, + error: errorFetchingGitOrg, + } = useGetAuthorizedGitOrgs(organization.id?.toString(), provider, credential, { + refetchOnWindowFocus: true, + enabled: provider === GitProvider.GITHUB || !!credential, + }); + const matchingOrgItem = gitOrgs?.gitOrgs?.find((item) => item.orgName === orgName); + + const { data: gitCredentials = [], isLoading: isLoadingGitCred } = useQuery({ + queryKey: ["git-creds", { provider }], + queryFn: () => + ChoreoWebViewAPI.getInstance().getChoreoRpcClient().getCredentials({ orgId: organization?.id?.toString(), orgUuid: organization.uuid }), + select: (gitData) => gitData?.filter((item) => item.type === provider), + refetchOnWindowFocus: true, + enabled: provider !== GitProvider.GITHUB, + }); + + const { isLoading: isLoadingGitlabCreds } = useQuery({ + queryKey: ["gitlab-creds", { provider, credential }], + queryFn: () => + ChoreoWebViewAPI.getInstance() + .getChoreoRpcClient() + .getCredentialDetails({ orgId: organization?.id?.toString(), orgUuid: organization.uuid, credentialId: credential }), + enabled: provider === GitProvider.GITLAB_SERVER && !!credential, + onSuccess: (data) => form.setValue("serverUrl", data?.serverUrl), + }); + + useEffect(() => { + if (gitCredentials.length > 0 && (form.getValues("credential") || !gitCredentials.some((item) => item.id === form.getValues("credential")))) { + form.setValue("credential", gitCredentials[0]?.id); + } else if (gitCredentials.length === 0 && form.getValues("credential") !== "") { + form.setValue("credential", ""); + } + }, [gitCredentials]); + + const repoUrl = matchingOrgItem && repoName && buildGitURL(matchingOrgItem?.orgHandler, repoName, provider, false, serverUrl); + useEffect(() => { + if (gitOrgs?.gitOrgs.length > 0 && (form.getValues("org") === "" || !gitOrgs?.gitOrgs.some((item) => item.orgName === form.getValues("org")))) { + form.setValue("org", gitOrgs?.gitOrgs[0]?.orgName); + } else if (gitOrgs?.gitOrgs.length === 0 && form.getValues("org") !== "") { + form.setValue("org", ""); + } + }, [gitOrgs]); + + useEffect(() => { + if (matchingOrgItem?.repositories.length > 0 && !matchingOrgItem?.repositories?.some((item) => item.name === form.getValues("repo"))) { + setTimeout(() => form.setValue("repo", ""), 1000); + } + if (matchingOrgItem) { + form.setValue("orgHandler", matchingOrgItem.orgHandler); + } + }, [matchingOrgItem]); + + const { data: branches = [], isLoading: isLoadingBranches } = useGetGitBranches( + repoUrl, + organization, + provider === GitProvider.GITHUB ? "" : credential, + !errorFetchingGitOrg, + { + enabled: !!repoName && !!provider && !!repoUrl && (provider === GitProvider.GITHUB ? !errorFetchingGitOrg : !!credential), + refetchOnWindowFocus: true, + }, + ); + + useEffect(() => { + if (branches?.length > 0 && !branches.includes(form.getValues("branch"))) { + if (branches.includes("main")) { + form.setValue("branch", "main", { shouldValidate: true }); + } + if (branches.includes("master")) { + form.setValue("branch", "master", { shouldValidate: true }); + } else { + form.setValue("branch", branches[0], { shouldValidate: true }); + } + } + }, [branches]); + + useEffect(() => { + // TODO: avoid using useEffect and try to override the onChange handler + if (repo === createNewRpoText) { + setTimeout(() => form.setValue("repo", ""), 1000); + let newRepoLink = "https://github.com/new"; + if (provider === GitProvider.BITBUCKET) { + newRepoLink = `https://bitbucket.org/${orgName}/workspace/create/repository`; + } else if (provider === GitProvider.GITLAB_SERVER) { + newRepoLink = `${serverUrl}/projects/new`; + } + ChoreoWebViewAPI.getInstance().openExternal(newRepoLink); + setCreatingRepo(true); + } else if (repo === connectMoreRepoText) { + setTimeout(() => form.setValue("repo", ""), 1000); + ChoreoWebViewAPI.getInstance().triggerGithubInstallFlow(organization.id?.toString()); + } + }, [repo]); + + useEffect(() => { + // TODO: avoid using useEffect and try to override the onChange handler + if (credential === createNewCredText) { + setTimeout(() => form.setValue("credential", ""), 1000); + ChoreoWebViewAPI.getInstance().openExternalChoreo(`organizations/${organization.handle}/settings/credentials`); + } + }, [credential]); + + useEffect(() => { + setCreatingRepo(false); + }, [provider]); + + const debouncedUpdateName = useCallback( + debounce((subPath: string, repo: string) => { + if (subPath) { + const paths = subPath.split("/"); + const lastPath = paths.findLast((item) => !!item); + if (lastPath) { + form.setValue("name", lastPath); + return; + } + } + if (repo) { + form.setValue("name", repo); + return; + } + }, 1000), + [], + ); + + useEffect(() => { + debouncedUpdateName(subPath, repo); + }, [repo, subPath]); + + const { mutateAsync: getRepoMetadata, isLoading: isValidatingPath } = useMutation({ + mutationFn: (data: ComponentRepoInitSchemaType) => { + const subPath = data.subPath.startsWith("/") ? data.subPath.slice(1) : data.subPath; + return ChoreoWebViewAPI.getInstance() + .getChoreoRpcClient() + .getGitRepoMetadata({ + branch: data.branch, + gitOrgName: data.org, + gitRepoName: data.repo, + relativePath: subPath, + orgId: organization?.id?.toString(), + secretRef: data.credential || "", + }); + }, + }); + + const onSubmitForm: SubmitHandler = async (data) => { + try { + const resp = await getRepoMetadata(data); + if (resp?.metadata && !resp?.metadata?.isSubPathEmpty) { + form.setError("subPath", { message: "Path isn't empty in the remote repo" }); + } else { + onNextClick(); + } + } catch { + // the API will throw an error, if branch does not exist + onNextClick(); + } + }; + + const repoDropdownItems = [{ value: createNewRpoText }]; + if (provider === GitProvider.GITHUB) { + repoDropdownItems.push({ value: connectMoreRepoText }); + } + if (matchingOrgItem?.repositories?.length > 0) { + repoDropdownItems.push( + { type: "separator", value: "" } as { value: string }, + ...matchingOrgItem?.repositories?.map((item) => ({ value: item.name })), + ); + } + + const credentialDropdownItems = [{ value: createNewCredText }]; + if (gitCredentials?.length > 0) { + credentialDropdownItems.push( + { type: "separator", value: "" } as { value: string }, + ...gitCredentials?.map((item) => ({ value: item.id, label: item.name })), + ); + } + + return ( + <> +
+ + + {provider === GitProvider.GITHUB && errorFetchingGitOrg && ( + ChoreoWebViewAPI.getInstance().triggerGithubAuthFlow(organization.id?.toString()) }} + /> + )} + {provider !== GitProvider.GITHUB && ( + + )} + {(provider === GitProvider.GITHUB || credential) && ( + <> + ({ value: item.orgName }))} + loading={loadingGitOrgs || (provider === GitProvider.GITLAB_SERVER ? isLoadingGitlabCreds : false)} + /> + {creatingRepo ? ( +
+
+ + + + + {repoError?.message && ( + + )} +
+
+ + +
+
+ ) : ( + + )} + {repoName && branches?.length > 0 && ( + + )} + + )} + + + {repo && ( +
+ +
+ )} +
+ +
+ +
+ + ); +}; diff --git a/workspaces/wso2-platform/wso2-platform-webviews/webpack.config.js b/workspaces/wso2-platform/wso2-platform-webviews/webpack.config.js index 3fc07286d9c..7a4d27a6618 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/webpack.config.js +++ b/workspaces/wso2-platform/wso2-platform-webviews/webpack.config.js @@ -13,8 +13,8 @@ class RunTailwindCSSPlugin { module.exports = { entry: "./src/index.tsx", target: "web", - devtool: !process.env.CI ? "source-map" : undefined, - mode: !process.env.CI ? "development" : "production", + devtool: "source-map", + mode: "development", output: { path: path.resolve(__dirname, "build"), filename: "main.js", From 9a820185fdb042f5d3caf97edfd0d6560ed4830c Mon Sep 17 00:00:00 2001 From: kaje94 Date: Mon, 22 Sep 2025 14:27:15 +0530 Subject: [PATCH 06/41] revert versions in package json for choreo and platform --- workspaces/choreo/choreo-extension/package.json | 2 +- workspaces/wso2-platform/wso2-platform-extension/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/choreo/choreo-extension/package.json b/workspaces/choreo/choreo-extension/package.json index 7ad0b7cd9db..c421646dc46 100644 --- a/workspaces/choreo/choreo-extension/package.json +++ b/workspaces/choreo/choreo-extension/package.json @@ -3,7 +3,7 @@ "displayName": "Choreo", "description": "An extension for managing your Choreo projects and components", "license": "Apache-2.0", - "version": "2.2.5-2", + "version": "2.2.6", "cliVersion": "v1.2.92505041530", "publisher": "wso2", "bugs": { diff --git a/workspaces/wso2-platform/wso2-platform-extension/package.json b/workspaces/wso2-platform/wso2-platform-extension/package.json index 621a7b59232..05b4d246611 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/package.json +++ b/workspaces/wso2-platform/wso2-platform-extension/package.json @@ -3,7 +3,7 @@ "displayName": "WSO2 Platform", "description": "Manage WSO2 Choreo and Devant projects in VS Code.", "license": "Apache-2.0", - "version": "1.0.14-code-server-16", + "version": "1.0.13", "cliVersion": "v1.2.212509091800", "publisher": "wso2", "bugs": { From dce6c9d674c1b4196e774a0f10682ee3cc48a711 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Tue, 26 Aug 2025 10:39:53 +0530 Subject: [PATCH 07/41] update pipeline to publish platform ext to vscode public repo --- .github/workflows/publish-vsix.yml | 2 +- .github/workflows/release-vsix.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-vsix.yml b/.github/workflows/publish-vsix.yml index 51d0d895eb3..5f0dcf84dc7 100644 --- a/.github/workflows/publish-vsix.yml +++ b/.github/workflows/publish-vsix.yml @@ -90,7 +90,7 @@ jobs: elif [ "${{ github.event.inputs.extension }}" == "choreo" ]; then echo "repo=wso2/choreo-vscode" >> $GITHUB_OUTPUT elif [ "${{ github.event.inputs.extension }}" == "wso2-platform" ]; then - echo "repo=wso2/wso2-platform-vscode" >> $GITHUB_OUTPUT + echo "repo=wso2/vscode-extensions" >> $GITHUB_OUTPUT elif [ "${{ github.event.inputs.extension }}" == "apk" ]; then echo "repo=wso2/apk-vscode" >> $GITHUB_OUTPUT elif [ "${{ github.event.inputs.extension }}" == "micro-integrator" ]; then diff --git a/.github/workflows/release-vsix.yml b/.github/workflows/release-vsix.yml index fa6b0ff6d67..eebb8ce79f0 100644 --- a/.github/workflows/release-vsix.yml +++ b/.github/workflows/release-vsix.yml @@ -143,11 +143,11 @@ jobs: token: ${{ secrets.CHOREO_BOT_TOKEN }} chatAPI: ${{ steps.chat.outputs.chatAPI }} - - name: Create a release in wso2/platform-vscode repo + - name: Create a release in wso2/vscode-extensions repo if: ${{ github.event.inputs.wso2-platform == 'true' }} uses: ./.github/actions/release with: - repo: wso2/platform-vscode + repo: wso2/vscode-extensions name: wso2-platform token: ${{ secrets.CHOREO_BOT_TOKEN }} chatAPI: ${{ steps.chat.outputs.chatAPI }} From ec8aefebc0011a59fe06591e273ebdf7aed77b5d Mon Sep 17 00:00:00 2001 From: kaje94 Date: Mon, 22 Sep 2025 18:03:15 +0530 Subject: [PATCH 08/41] fix bug when pushing source to remote repo --- .../src/types/messenger-rpc.types.ts | 6 ---- .../src/webviews/WebviewRPC.ts | 29 +++++++------------ .../src/utilities/vscode-webview-rpc.ts | 6 ---- 3 files changed, 11 insertions(+), 30 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-core/src/types/messenger-rpc.types.ts b/workspaces/wso2-platform/wso2-platform-core/src/types/messenger-rpc.types.ts index 32c61097855..c3147d3a16f 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/types/messenger-rpc.types.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/types/messenger-rpc.types.ts @@ -77,7 +77,6 @@ export const CreateLocalProxyConfig: RequestType = { method: "createLocalConnectionsConfig" }; export const DeleteLocalConnectionsConfig: RequestType = { method: "deleteLocalConnectionsConfig" }; export const CloneRepositoryIntoCompDir: RequestType = { method: "cloneRepositoryIntoCompDir" }; -export const PushEverythingToRemoteRepo: RequestType = { method: "pushEverythingToRemoteRepo" }; const NotificationMethods = { onAuthStateChanged: "onAuthStateChanged", @@ -105,11 +104,6 @@ export interface OpenTestViewReq { endpoints: ComponentEP[]; } -export interface PushEverythingToRemoteRepoReq { - dirPath: string; - componentName: string; -} - export interface CloneRepositoryIntoCompDirReq { cwd: string; subpath: string; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts index 188185c3e14..158d44a1d12 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts @@ -75,8 +75,6 @@ import { OpenExternalChoreo, OpenSubDialogRequest, type ProxyConfig, - PushEverythingToRemoteRepo, - type PushEverythingToRemoteRepoReq, ReadFile, ReadLocalEndpointsConfig, ReadLocalProxyConfig, @@ -620,10 +618,10 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W const updatedTomlContent = toml.stringify(parsedToml); await fs.promises.writeFile(balTomlPath, updatedTomlContent, "utf-8"); } - + if (params.repo?.isBareRepo && ["", "/", "."].includes(params.subpath)) { // if component is to be created in the root of a bare repo, - // the we can initialize the current directory as the repo root + // then we can initialize the current directory as the repo root await window.withProgress({ title: `Initializing currently opened directory as repository ${_repoUrl}...`, location: ProgressLocation.Notification }, async () => { await newGit.init(params.cwd); const dotGit = await newGit?.getRepositoryDotGit(params.cwd); @@ -631,8 +629,8 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W await repo.addRemote("origin", repoUrl); await repo.add(["."]); await repo.commit(`Add source for new ${extName} ${extName === "Devant" ? "Integration" : "Component"} (${params.componentName})`); - await repo.push("origin", "main"); - await repo.fetch(); + const headRef = await repo.getHEADRef() + await repo.push("origin", headRef?.name); }); return params.cwd; } @@ -668,23 +666,18 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W fs.cpSync(cwdFilePath, destFilePath, { recursive: true }); } - return newPath; - }); - - messenger.onRequest(PushEverythingToRemoteRepo, async (params: PushEverythingToRemoteRepoReq) => { - const newGit = await initGit(ext.context); - if (!newGit) { - throw new Error("failed to initGit"); - } - const extName = webviewStateStore.getState().state.extensionName; - const repoRoot = await newGit?.getRepositoryRoot(params.dirPath); - const dotGit = await newGit?.getRepositoryDotGit(params.dirPath); + const repoRoot = await newGit?.getRepositoryRoot(newPath); + const dotGit = await newGit?.getRepositoryDotGit(newPath); const repo = newGit.open(repoRoot, dotGit); + await window.withProgress({ title: "Pushing the changes to your remote repository...", location: ProgressLocation.Notification }, async () => { await repo.add(["."]); await repo.commit(`Add source for new ${extName} ${extName === "Devant" ? "Integration" : "Component"} (${params.componentName})`); - await repo.push(); + const headRef = await repo.getHEADRef() + await repo.push(headRef?.upstream?.remote || "origin", headRef?.name || params.repo.branch); }); + + return newPath; }); // Register Choreo CLL RPC handler diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/utilities/vscode-webview-rpc.ts b/workspaces/wso2-platform/wso2-platform-webviews/src/utilities/vscode-webview-rpc.ts index f3239497e6b..cc76801f7bc 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/utilities/vscode-webview-rpc.ts +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/utilities/vscode-webview-rpc.ts @@ -63,8 +63,6 @@ import { OpenExternalChoreo, OpenSubDialogRequest, type OpenTestViewReq, - PushEverythingToRemoteRepo, - type PushEverythingToRemoteRepoReq, ReadFile, ReadLocalEndpointsConfig, type ReadLocalEndpointsConfigResp, @@ -255,10 +253,6 @@ export class ChoreoWebViewAPI { return this._messenger.sendRequest(CloneRepositoryIntoCompDir, HOST_EXTENSION, params); } - public async pushEverythingToRemoteRepo(params: PushEverythingToRemoteRepoReq): Promise { - return this._messenger.sendRequest(PushEverythingToRemoteRepo, HOST_EXTENSION, params); - } - public async triggerGithubInstallFlow(orgId: string): Promise { return this._messenger.sendRequest(TriggerGithubInstallFlow, HOST_EXTENSION, orgId); } From f96961715e5b0cf1fbbd6ddcce36e877707baa7f Mon Sep 17 00:00:00 2001 From: kaje94 Date: Tue, 23 Sep 2025 13:23:18 +0530 Subject: [PATCH 09/41] update the readme of platform extension --- .../wso2-platform/wso2-platform-extension/README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/README.md b/workspaces/wso2-platform/wso2-platform-extension/README.md index 5909f5aa5c0..3562cf8cee6 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/README.md +++ b/workspaces/wso2-platform/wso2-platform-extension/README.md @@ -5,15 +5,14 @@ The WSO2 Platform VS Code extension enhances your local development experience w ## Getting Started -1. **Create an Account:** Sign up for an account on [Choreo](https://console.choreo.dev/) or [Devant](https://console.devant.dev/). -2. **Install the Extension:** +1. **Install the Extension:** * Open Visual Studio Code. * Navigate to the Extensions view by pressing `Ctrl+Shift+X` (or `Cmd+Shift+X` on macOS). * Search for "WSO2 Platform" and click "Install." -3. **Sign In to Choreo:** +2. **Sign In to Choreo/Devant:** * Open the Command Palette by pressing `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS). - * Type "WSO2: Sign In" and press Enter. Follow the on-screen prompts to authenticate with your Choreo account. -4. **Explore Functionality:** + * Type "WSO2: Sign In" and press Enter. Follow the on-screen prompts to authenticate with your Choreo/Devant account. +3. **Explore Functionality:** * Once signed in, open the Command Palette again (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS). * Type "WSO2:" to see a list of available commands and functionalities provided by the extension. @@ -23,7 +22,7 @@ Refer to the [Choreo documentation](https://wso2.com/choreo/docs/develop-compone ## Get Help -Feel free to create [GitHub issues](https://github.com/wso2/choreo-vscode/issues) or reach out to us on [Discord](https://discord.com/invite/wso2). +Feel free to create [GitHub issues](https://github.com/wso2/vscode-extensions/issues) or reach out to us on [Discord](https://discord.com/invite/wso2). ## License From 531ec9a934dade59091156b39766cc042dbf1929 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Wed, 24 Sep 2025 11:02:44 +0530 Subject: [PATCH 10/41] sync choreo workspace changes into devant-main-sync branch --- .vscode/launch.json | 11 +- common/config/rush/pnpm-lock.yaml | 295 +----------------- .../choreo-extension/.vscode/launch.json | 8 +- workspaces/choreo/choreo-extension/README.md | 3 +- .../choreo/choreo-extension/package.json | 6 +- .../choreo-extension/src/webviews/utils.ts | 4 +- .../choreo/choreo-webviews/package.json | 16 +- 7 files changed, 33 insertions(+), 310 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 15b09d18dc9..58ebea53732 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -114,7 +114,8 @@ "CELL_VIEW_DEV_HOST": "http://localhost:3001/cellDiagram.js" }, "args": [ - "--extensionDevelopmentPath=${workspaceFolder}/workspaces/wso2-platform/wso2-platform-extension" + "--extensionDevelopmentPath=${workspaceFolder}/workspaces/wso2-platform/wso2-platform-extension", + "--enable-proposed-api=wso2.wso2-platform" ], "outFiles": [ "${workspaceFolder}/workspaces/wso2-platform/wso2-platform-extension/dist/**/*.js" @@ -127,8 +128,8 @@ "type": "extensionHost", "request": "launch", "env": { - "WEB_VIEW_DEV_MODE": "true", - "WEB_VIEW_DEV_HOST": "http://localhost:3000/main.js", + "WEB_VIEW_DEV_MODE_CHOREO": "true", + "WEB_VIEW_DEV_HOST_CHOREO": "http://localhost:3001/main.js", "CELL_VIEW_DEV_MODE": "true", "CELL_VIEW_DEV_HOST": "http://localhost:3001/cellDiagram.js" }, @@ -177,8 +178,8 @@ ], "envFile": "${workspaceFolder}/workspaces/choreo/choreo-extension/.env", "env": { - "WEB_VIEW_DEV_MODE": "true", - "WEB_VIEW_DEV_HOST": "http://localhost:3000/main.js", + "WEB_VIEW_DEV_MODE_CHOREO": "true", + "WEB_VIEW_DEV_HOST_CHOREO": "http://localhost:3001/main.js", }, "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 7e69a02a0d5..e32ace5f94d 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -545,7 +545,7 @@ importers: version: 1.89.2 sass-loader: specifier: ^16.0.5 - version: 16.0.5(node-sass@9.0.0)(sass@1.89.2)(webpack@5.101.0) + version: 16.0.5(sass@1.89.2)(webpack@5.101.0) storybook: specifier: ^8.6.14 version: 8.6.14(prettier@3.5.3) @@ -881,7 +881,7 @@ importers: version: 0.4.20(eslint@9.27.0(jiti@2.5.1)) sass-loader: specifier: ^16.0.5 - version: 16.0.5(node-sass@9.0.0)(sass@1.89.2)(webpack@5.101.0) + version: 16.0.5(sass@1.89.2)(webpack@5.101.0) source-map-loader: specifier: ^5.0.0 version: 5.0.0(webpack@5.101.0) @@ -2135,9 +2135,6 @@ importers: file-type: specifier: ^18.2.1 version: 18.7.0 - js-yaml: - specifier: ^4.1.0 - version: 4.1.0 jschardet: specifier: ^3.0.0 version: 3.1.4 @@ -2156,9 +2153,6 @@ importers: yaml: specifier: ^2.6.0 version: 2.8.0 - zustand: - specifier: ^5.0.5 - version: 5.0.6(@types/react@18.2.0)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) devDependencies: '@biomejs/biome': specifier: ^1.8.3 @@ -2169,9 +2163,6 @@ importers: '@types/byline': specifier: ^4.2.36 version: 4.2.36 - '@types/js-yaml': - specifier: ^4.0.9 - version: 4.0.9 '@types/mocha': specifier: ~10.0.1 version: 10.0.10 @@ -2235,9 +2226,6 @@ importers: '@formkit/auto-animate': specifier: 0.8.2 version: 0.8.2 - '@headlessui/react': - specifier: ^2.2.4 - version: 2.2.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@hookform/resolvers': specifier: ^5.0.1 version: 5.0.1(react-hook-form@7.56.4(react@18.2.0)) @@ -2262,12 +2250,6 @@ importers: classnames: specifier: ~2.5.1 version: 2.5.1 - clipboardy: - specifier: ^4.0.0 - version: 4.0.0 - js-yaml: - specifier: ^4.1.0 - version: 4.1.0 lodash.debounce: specifier: ~4.0.8 version: 4.0.8 @@ -2283,34 +2265,19 @@ importers: react-hook-form: specifier: 7.56.4 version: 7.56.4(react@18.2.0) - react-markdown: - specifier: ^7.1.0 - version: 7.1.2(@types/react@18.2.0)(react@18.2.0) rehype-raw: specifier: ^6.1.0 version: 6.1.1 remark-gfm: specifier: ^4.0.1 version: 4.0.1 - swagger-ui-react: - specifier: ^5.22.0 - version: 5.27.0(@types/react@18.2.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - timezone-support: - specifier: ^3.1.0 - version: 3.1.0 vscode-messenger-common: specifier: ^0.5.1 version: 0.5.1 vscode-messenger-webview: specifier: ^0.5.1 version: 0.5.1 - zod: - specifier: ^3.22.4 - version: 3.25.76 devDependencies: - '@types/js-yaml': - specifier: ^4.0.5 - version: 4.0.9 '@types/lodash.debounce': specifier: ^4.0.9 version: 4.0.9 @@ -2323,9 +2290,6 @@ importers: '@types/react-dom': specifier: 18.2.0 version: 18.2.0 - '@types/swagger-ui-react': - specifier: ^5.18.0 - version: 5.18.0 '@types/vscode-webview': specifier: ^1.57.5 version: 1.57.5 @@ -2341,9 +2305,6 @@ importers: file-loader: specifier: ^6.2.0 version: 6.2.0(webpack@5.101.0) - node-sass: - specifier: ^9.0.0 - version: 9.0.0 path: specifier: ^0.12.7 version: 0.12.7 @@ -2355,7 +2316,7 @@ importers: version: 8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@5.101.0) sass-loader: specifier: ^16.0.5 - version: 16.0.5(node-sass@9.0.0)(sass@1.89.2)(webpack@5.101.0) + version: 16.0.5(sass@1.89.2)(webpack@5.101.0) source-map-loader: specifier: ^5.0.0 version: 5.0.0(webpack@5.101.0) @@ -3561,7 +3522,7 @@ importers: version: 7.1.2(webpack@5.101.0) sass-loader: specifier: ^16.0.5 - version: 16.0.5(node-sass@9.0.0)(sass@1.89.2)(webpack@5.101.0) + version: 16.0.5(sass@1.89.2)(webpack@5.101.0) source-map-loader: specifier: ^5.0.0 version: 5.0.0(webpack@5.101.0) @@ -3660,6 +3621,9 @@ importers: '@aws-sdk/client-s3': specifier: ^3.817.0 version: 3.855.0 + '@iarna/toml': + specifier: ^2.2.5 + version: 2.2.5 '@vscode-logging/logger': specifier: ^2.0.0 version: 2.0.0 @@ -3752,7 +3716,7 @@ importers: specifier: workspace:* version: link:../../common-libs/playwright-vscode-tester axios: - specifier: ^1.12.0 + specifier: ^1.9.0 version: 1.12.0 copy-webpack-plugin: specifier: ^13.0.0 @@ -3851,8 +3815,8 @@ importers: specifier: ^4.0.1 version: 4.0.1 swagger-ui-react: - specifier: ^5.22.0 - version: 5.27.0(@types/react@18.2.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: 5.22.0 + version: 5.22.0(@types/react@18.2.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) timezone-support: specifier: ^3.1.0 version: 3.1.0 @@ -3899,9 +3863,6 @@ importers: file-loader: specifier: ^6.2.0 version: 6.2.0(webpack@5.101.0) - node-sass: - specifier: ^9.0.0 - version: 9.0.0 path: specifier: ^0.12.7 version: 0.12.7 @@ -3913,7 +3874,7 @@ importers: version: 8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@5.101.0) sass-loader: specifier: ^16.0.5 - version: 16.0.5(node-sass@9.0.0)(sass@1.89.2)(webpack@5.101.0) + version: 16.0.5(sass@1.89.2)(webpack@5.101.0) source-map-loader: specifier: ^5.0.0 version: 5.0.0(webpack@5.101.0) @@ -10101,9 +10062,6 @@ packages: async-each@1.0.6: resolution: {integrity: sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==} - async-foreach@0.1.3: - resolution: {integrity: sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==} - async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -10894,10 +10852,6 @@ packages: resolution: {integrity: sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==} engines: {node: '>=0.10.0'} - camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} - camelcase-keys@7.0.2: resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} engines: {node: '>=12'} @@ -13324,10 +13278,6 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. - gaze@1.1.3: - resolution: {integrity: sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==} - engines: {node: '>= 4.0.0'} - generic-names@4.0.0: resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==} @@ -13477,10 +13427,6 @@ packages: resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} deprecated: Glob versions prior to v9 are no longer supported - glob@7.1.7: - resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} - deprecated: Glob versions prior to v9 are no longer supported - glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -13562,10 +13508,6 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - globule@1.3.4: - resolution: {integrity: sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==} - engines: {node: '>= 0.10'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -15712,10 +15654,6 @@ packages: resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - make-fetch-happen@9.1.0: - resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} - engines: {node: '>= 10'} - makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} @@ -15897,10 +15835,6 @@ packages: resolution: {integrity: sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==} engines: {node: '>=0.10.0'} - meow@9.0.0: - resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} - engines: {node: '>=10'} - merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} @@ -16177,9 +16111,6 @@ packages: minimatch@3.0.3: resolution: {integrity: sha512-NyXjqu1IwcqH6nv5vmMtaG3iw7kdV3g6MwlUBZkc3Vn5b5AMIWYKfptvzipoyFfhlfOgBQ9zoTxQMravF1QTnw==} - minimatch@3.0.8: - resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -16210,10 +16141,6 @@ packages: resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} engines: {node: '>= 8'} - minipass-fetch@1.4.1: - resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==} - engines: {node: '>=8'} - minipass-fetch@2.1.2: resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -16468,11 +16395,6 @@ packages: engines: {node: '>= 0.8.0'} hasBin: true - node-gyp@8.4.1: - resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==} - engines: {node: '>= 10.12.0'} - hasBin: true - node-gyp@9.4.1: resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} engines: {node: ^12.13 || ^14.13 || >=16} @@ -16500,12 +16422,6 @@ packages: resolution: {integrity: sha512-Pzr3rol8fvhG/oJjIq2NTVB0vmdNNlz22FENhhPojYRZ4/ee08CfK4YuKmuL54V9MLhI1kpzxfOJ/63LzmZzDg==} engines: {node: '>=14'} - node-sass@9.0.0: - resolution: {integrity: sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg==} - engines: {node: '>=16'} - deprecated: Node Sass is no longer supported. Please use `sass` or `sass-embedded` instead. - hasBin: true - node-schedule@2.1.1: resolution: {integrity: sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==} engines: {node: '>=6'} @@ -16517,11 +16433,6 @@ packages: resolution: {integrity: sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==} hasBin: true - nopt@5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} - engines: {node: '>=6'} - hasBin: true - nopt@6.0.0: resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -17859,10 +17770,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -18792,11 +18699,6 @@ packages: sanitize-filename@1.6.3: resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} - sass-graph@4.0.1: - resolution: {integrity: sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==} - engines: {node: '>=12'} - hasBin: true - sass-loader@16.0.5: resolution: {integrity: sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==} engines: {node: '>= 18.12.0'} @@ -18867,9 +18769,6 @@ packages: resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==} engines: {node: '>= 10.13.0'} - scss-tokenizer@0.4.3: - resolution: {integrity: sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==} - secretlint@9.3.4: resolution: {integrity: sha512-iNOzgMX/+W1SQNW/TW6eikGChyaPiazr2AEXjzjpoB0R6QJEulvlwhn0KLT1/xjPfdYrk3yiXZM40csUqET8uQ==} engines: {node: ^14.13.1 || >=16.0.0} @@ -19127,10 +19026,6 @@ packages: sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} - socks-proxy-agent@6.2.1: - resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} - engines: {node: '>= 10'} - socks-proxy-agent@7.0.0: resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} engines: {node: '>= 10'} @@ -19299,9 +19194,6 @@ packages: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} - stdout-stream@1.4.1: - resolution: {integrity: sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==} - stealthy-require@1.1.1: resolution: {integrity: sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==} engines: {node: '>=0.10.0'} @@ -19706,8 +19598,8 @@ packages: react: '>=16.8.0 <19' react-dom: '>=16.8.0 <19' - swagger-ui-react@5.27.0: - resolution: {integrity: sha512-KQ1NPzRfpVICvYHmVZCmw79VJK9NYvT8+f9dTRE2ZOkZAG/hlBprCk0x1AC9ERiaPb2Wrwxuq94PkZoMM+J6fQ==} + swagger-ui-react@5.22.0: + resolution: {integrity: sha512-Y0TEWg2qD4u/dgZ9q9G16yM/Edvyz0ovkIZlpACN8X/2gzSoIzS/fhSpLSJfCOxRt2UqrKmajMB11VK6cGZk2g==} peerDependencies: react: '>=16.8.0 <19' react-dom: '>=16.8.0 <19' @@ -20063,10 +19955,6 @@ packages: resolution: {integrity: sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==} engines: {node: '>=0.10.0'} - trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - trim-newlines@4.1.1: resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} engines: {node: '>=12'} @@ -20096,9 +19984,6 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - true-case-path@2.2.1: - resolution: {integrity: sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==} - truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} @@ -20331,10 +20216,6 @@ packages: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} - type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} - type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -33290,8 +33171,6 @@ snapshots: async-each@1.0.6: {} - async-foreach@0.1.3: {} - async-function@1.0.0: {} async-hook-jl@1.7.6: @@ -34501,12 +34380,6 @@ snapshots: camelcase: 2.1.1 map-obj: 1.0.1 - camelcase-keys@6.2.2: - dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 - camelcase-keys@7.0.2: dependencies: camelcase: 6.3.0 @@ -37654,10 +37527,6 @@ snapshots: strip-ansi: 6.0.1 wide-align: 1.1.5 - gaze@1.1.3: - dependencies: - globule: 1.3.4 - generic-names@4.0.0: dependencies: loader-utils: 3.3.1 @@ -37817,15 +37686,6 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - glob@7.1.7: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.0.8 - once: 1.4.0 - path-is-absolute: 1.0.1 - glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -37945,12 +37805,6 @@ snapshots: globrex@0.1.2: {} - globule@1.3.4: - dependencies: - glob: 7.1.7 - lodash: 4.17.21 - minimatch: 3.0.8 - gopd@1.2.0: {} got@13.0.0: @@ -40944,27 +40798,6 @@ snapshots: transitivePeerDependencies: - supports-color - make-fetch-happen@9.1.0: - dependencies: - agentkeepalive: 4.6.0 - cacache: 15.3.0 - http-cache-semantics: 4.2.0 - http-proxy-agent: 4.0.1 - https-proxy-agent: 5.0.1 - is-lambda: 1.0.1 - lru-cache: 6.0.0 - minipass: 3.3.6 - minipass-collect: 1.0.2 - minipass-fetch: 1.4.1 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - negotiator: 0.6.4 - promise-retry: 2.0.1 - socks-proxy-agent: 6.2.1 - ssri: 8.0.1 - transitivePeerDependencies: - - supports-color - makeerror@1.0.12: dependencies: tmpl: 1.0.5 @@ -41312,21 +41145,6 @@ snapshots: redent: 1.0.0 trim-newlines: 1.0.0 - meow@9.0.0: - dependencies: - '@types/minimist': 1.2.5 - camelcase-keys: 6.2.2 - decamelize: 1.2.0 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 - merge-descriptors@1.0.3: {} merge-descriptors@2.0.0: {} @@ -41736,10 +41554,6 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimatch@3.0.8: - dependencies: - brace-expansion: 2.0.2 - minimatch@3.1.2: dependencies: brace-expansion: 2.0.2 @@ -41772,14 +41586,6 @@ snapshots: dependencies: minipass: 3.3.6 - minipass-fetch@1.4.1: - dependencies: - minipass: 3.3.6 - minipass-sized: 1.0.3 - minizlib: 2.1.2 - optionalDependencies: - encoding: 0.1.13 - minipass-fetch@2.1.2: dependencies: minipass: 3.3.6 @@ -42061,21 +41867,6 @@ snapshots: tar: 2.2.2 which: 1.3.1 - node-gyp@8.4.1: - dependencies: - env-paths: 2.2.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - make-fetch-happen: 9.1.0 - nopt: 5.0.0 - npmlog: 6.0.2 - rimraf: 3.0.2 - semver: 7.7.2 - tar: 6.2.1 - which: 2.0.2 - transitivePeerDependencies: - - supports-color - node-gyp@9.4.1: dependencies: env-paths: 2.2.1 @@ -42123,25 +41914,6 @@ snapshots: '@types/sarif': 2.1.7 fs-extra: 10.1.0 - node-sass@9.0.0: - dependencies: - async-foreach: 0.1.3 - chalk: 4.1.2 - cross-spawn: 7.0.6 - gaze: 1.1.3 - get-stdin: 4.0.1 - glob: 7.2.3 - lodash: 4.17.21 - make-fetch-happen: 10.2.1 - meow: 9.0.0 - nan: 2.23.0 - node-gyp: 8.4.1 - sass-graph: 4.0.1 - stdout-stream: 1.4.1 - true-case-path: 2.2.1 - transitivePeerDependencies: - - supports-color - node-schedule@2.1.1: dependencies: cron-parser: 4.9.0 @@ -42157,10 +41929,6 @@ snapshots: dependencies: abbrev: 1.0.9 - nopt@5.0.0: - dependencies: - abbrev: 1.1.1 - nopt@6.0.0: dependencies: abbrev: 1.1.1 @@ -43617,8 +43385,6 @@ snapshots: queue-microtask@1.2.3: {} - quick-lru@4.0.1: {} - quick-lru@5.1.1: {} raf@3.4.0: @@ -45010,18 +44776,10 @@ snapshots: dependencies: truncate-utf8-bytes: 1.0.2 - sass-graph@4.0.1: - dependencies: - glob: 7.2.3 - lodash: 4.17.21 - scss-tokenizer: 0.4.3 - yargs: 17.7.2 - - sass-loader@16.0.5(node-sass@9.0.0)(sass@1.89.2)(webpack@5.101.0): + sass-loader@16.0.5(sass@1.89.2)(webpack@5.101.0): dependencies: neo-async: 2.6.2 optionalDependencies: - node-sass: 9.0.0 sass: 1.89.2 webpack: 5.101.0(webpack-cli@6.0.1) @@ -45085,11 +44843,6 @@ snapshots: ajv-formats: 2.1.1 ajv-keywords: 5.1.0(ajv@8.17.1) - scss-tokenizer@0.4.3: - dependencies: - js-base64: 2.6.4 - source-map: 0.7.6 - secretlint@9.3.4: dependencies: '@secretlint/config-creator': 9.3.4 @@ -45410,14 +45163,6 @@ snapshots: uuid: 8.3.2 websocket-driver: 0.7.4 - socks-proxy-agent@6.2.1: - dependencies: - agent-base: 6.0.2 - debug: 4.4.1(supports-color@8.1.1) - socks: 2.8.6 - transitivePeerDependencies: - - supports-color - socks-proxy-agent@7.0.0: dependencies: agent-base: 6.0.2 @@ -45596,10 +45341,6 @@ snapshots: stdin-discarder@0.2.2: {} - stdout-stream@1.4.1: - dependencies: - readable-stream: 2.3.8 - stealthy-require@1.1.1: {} stop-iteration-iterator@1.1.0: @@ -46173,7 +45914,7 @@ snapshots: - '@types/react' - debug - swagger-ui-react@5.27.0(@types/react@18.2.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + swagger-ui-react@5.22.0(@types/react@18.2.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@babel/runtime-corejs3': 7.28.2 '@scarf/scarf': 1.4.0 @@ -46645,8 +46386,6 @@ snapshots: trim-newlines@1.0.0: {} - trim-newlines@3.0.1: {} - trim-newlines@4.1.1: {} trim-repeated@1.0.0: @@ -46665,8 +46404,6 @@ snapshots: trough@2.2.0: {} - true-case-path@2.2.1: {} - truncate-utf8-bytes@1.0.2: dependencies: utf8-byte-length: 1.0.5 @@ -47034,8 +46771,6 @@ snapshots: type-fest@0.16.0: {} - type-fest@0.18.1: {} - type-fest@0.20.2: {} type-fest@0.21.3: {} diff --git a/workspaces/choreo/choreo-extension/.vscode/launch.json b/workspaces/choreo/choreo-extension/.vscode/launch.json index 6aa28de0ef1..bd41a76ab95 100644 --- a/workspaces/choreo/choreo-extension/.vscode/launch.json +++ b/workspaces/choreo/choreo-extension/.vscode/launch.json @@ -10,8 +10,8 @@ "type": "extensionHost", "request": "launch", "env": { - "WEB_VIEW_DEV_MODE": "true", - "WEB_VIEW_DEV_HOST": "http://localhost:3000/main.js", + "WEB_VIEW_DEV_MODE_CHOREO": "true", + "WEB_VIEW_DEV_HOST_CHOREO": "http://localhost:3001/main.js", "REQUEST_TRACE_ENABLED": "true" }, "args": [ @@ -38,8 +38,8 @@ ], "envFile": "${workspaceFolder}/.env", "env": { - "WEB_VIEW_DEV_MODE": "true", - "WEB_VIEW_DEV_HOST": "http://localhost:3000/main.js", + "WEB_VIEW_DEV_MODE_CHOREO": "true", + "WEB_VIEW_DEV_HOST_CHOREO": "http://localhost:3001/main.js", }, "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", diff --git a/workspaces/choreo/choreo-extension/README.md b/workspaces/choreo/choreo-extension/README.md index ee061dafb96..b05fbd877ab 100644 --- a/workspaces/choreo/choreo-extension/README.md +++ b/workspaces/choreo/choreo-extension/README.md @@ -17,6 +17,7 @@ The Choreo VS Code extension enhances your local development experience with [Ch - **Deploy Builds**: Deploy your component builds to any chosen [environments](https://wso2.com/choreo/docs/choreo-concepts/environments/). - **Test Services**: Verify the functionality of publicly exposed services. - **Monitor Components**: Access runtime logs to monitor your deployed components. +- **Connect Locally to Dependencies**: Link your app to dependent connections while developing. See [guide](https://wso2.com/choreo/docs/develop-components/connect-to-remote-dependencies-while-developing/). ## Screenshots @@ -52,4 +53,4 @@ Feel free to create [GitHub issues](https://github.com/wso2/choreo-vscode/issues ## License -This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details. +This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/workspaces/choreo/choreo-extension/package.json b/workspaces/choreo/choreo-extension/package.json index c421646dc46..6f593037822 100644 --- a/workspaces/choreo/choreo-extension/package.json +++ b/workspaces/choreo/choreo-extension/package.json @@ -4,7 +4,6 @@ "description": "An extension for managing your Choreo projects and components", "license": "Apache-2.0", "version": "2.2.6", - "cliVersion": "v1.2.92505041530", "publisher": "wso2", "bugs": { "url": "https://github.com/wso2/choreo-vscode/issues" @@ -190,7 +189,6 @@ "devDependencies": { "@playwright/test": "1.52.0", "@types/byline": "^4.2.36", - "@types/js-yaml": "^4.0.9", "@types/mocha": "~10.0.1", "@types/node": "^22.15.24", "@types/vscode": "^1.100.0", @@ -224,13 +222,11 @@ "byline": "^5.0.0", "dotenv": "^16.0.3", "file-type": "^18.2.1", - "js-yaml": "^4.1.0", "yaml": "^2.6.0", "jschardet": "^3.0.0", "vscode-messenger": "^0.5.1", "vscode-messenger-common": "^0.5.1", "which": "^5.0.0", - "vscode-jsonrpc": "^8.2.1", - "zustand": "^5.0.5" + "vscode-jsonrpc": "^8.2.1" } } diff --git a/workspaces/choreo/choreo-extension/src/webviews/utils.ts b/workspaces/choreo/choreo-extension/src/webviews/utils.ts index 9fd0755817b..bf57acc1a95 100644 --- a/workspaces/choreo/choreo-extension/src/webviews/utils.ts +++ b/workspaces/choreo/choreo-extension/src/webviews/utils.ts @@ -22,13 +22,13 @@ import { ProjectActivityView } from "./ProjectActivityView"; export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) { if (shouldUseWebViewDevMode(pathList)) { - return process.env.WEB_VIEW_DEV_HOST; + return process.env.WEB_VIEW_DEV_HOST_CHOREO; } return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList)); } function shouldUseWebViewDevMode(pathList: string[]): boolean { - return pathList[pathList.length - 1] === "main.js" && process.env.WEB_VIEW_DEV_MODE === "true" && process.env.WEB_VIEW_DEV_HOST !== undefined; + return pathList[pathList.length - 1] === "main.js" && process.env.WEB_VIEW_DEV_MODE_CHOREO === "true" && process.env.WEB_VIEW_DEV_HOST_CHOREO !== undefined; } export function activateActivityWebViews(context: vscode.ExtensionContext) { diff --git a/workspaces/choreo/choreo-webviews/package.json b/workspaces/choreo/choreo-webviews/package.json index f5fda3cbd4f..3b6112f75dd 100644 --- a/workspaces/choreo/choreo-webviews/package.json +++ b/workspaces/choreo/choreo-webviews/package.json @@ -7,7 +7,7 @@ "scripts": { "lint": "biome check .", "lint:fix": "biome check --write --unsafe . ", - "start": "webpack serve --mode development", + "start": "webpack serve --mode development --port 3001", "build": "tsc --pretty && webpack && npm run copy:assets", "copy:assets": "copyfiles -u 1 \"src/**/*.scss\" \"src/**/*.svg\" \"src/**/*.css\" \"src/**/*.png\" \"src/**/*.txt\" \"src/**/*.json\" \"src/assets/fonts/Gilmer/*.*\" lib/" }, @@ -26,21 +26,14 @@ "classnames": "~2.5.1", "@tanstack/react-query-persist-client": "~4.28.0", "@tanstack/react-query": "~4.28.0", - "zod": "^3.22.4", "react-hook-form": "7.56.4", "@hookform/resolvers": "^5.0.1", - "clipboardy": "^4.0.0", "@formkit/auto-animate": "0.8.2", - "timezone-support": "^3.1.0", - "swagger-ui-react": "^5.22.0", "@biomejs/biome": "^1.9.4", - "@headlessui/react": "^2.2.4", - "react-markdown": "^7.1.0", "rehype-raw": "^6.1.0", "remark-gfm": "^4.0.1", "prism-react-renderer": "^2.4.1", - "lodash.debounce": "~4.0.8", - "js-yaml": "^4.1.0" + "lodash.debounce": "~4.0.8" }, "devDependencies": { "copyfiles": "~2.4.1", @@ -48,12 +41,10 @@ "@types/node": "^22.15.24", "@types/react": "18.2.0", "@types/react-dom": "18.2.0", - "@types/swagger-ui-react": "^5.18.0", "typescript": "5.8.3", "@types/vscode-webview": "^1.57.5", "css-loader": "^7.1.2", "file-loader": "^6.2.0", - "node-sass": "^9.0.0", "sass-loader": "^16.0.5", "style-loader": "^4.0.0", "ts-loader": "^9.5.2", @@ -65,8 +56,7 @@ "postcss-loader" :"^8.1.1", "autoprefixer": "^10.4.21", "tailwindcss": "^3.4.3", - "@types/lodash.debounce": "^4.0.9", - "@types/js-yaml": "^4.0.5" + "@types/lodash.debounce": "^4.0.9" }, "browserslist": { "production": [ From db2003a47ab16676a9b14c186a9fcc757b6f81d8 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Wed, 24 Sep 2025 11:32:15 +0530 Subject: [PATCH 11/41] disable existing directory init for bare repos --- .../wso2-platform-extension/src/webviews/WebviewRPC.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts index 158d44a1d12..43b56bf418b 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts @@ -619,6 +619,8 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W await fs.promises.writeFile(balTomlPath, updatedTomlContent, "utf-8"); } + // TODO: Enable this after fixing component creation from root + /* if (params.repo?.isBareRepo && ["", "/", "."].includes(params.subpath)) { // if component is to be created in the root of a bare repo, // then we can initialize the current directory as the repo root @@ -634,6 +636,7 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W }); return params.cwd; } + */ const clonedPath = await window.withProgress( { From bd26ad164b493f11cacc8d26d148d3cc65847faf Mon Sep 17 00:00:00 2001 From: kaje94 Date: Wed, 24 Sep 2025 15:03:14 +0530 Subject: [PATCH 12/41] avoid showing gitpat when checking access to repo --- .../src/cmds/commit-and-push-to-git-cmd.ts | 2 +- .../wso2-platform-extension/src/webviews/WebviewRPC.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts index be2f87da3eb..d422c7416fa 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts @@ -117,7 +117,7 @@ export function commitAndPushToGitCommand(context: ExtensionContext) { const urlObj = new URL(repoUrl); getLogger().debug(`Fetching PAT for org ${repoOrg} and repo ${repoName}`); const gitPat = await window.withProgress( - { title: `Accessing the repository ${repoUrl}...`, location: ProgressLocation.Notification }, + { title: `Accessing the repository ${repoOrg}/${repoName}...`, location: ProgressLocation.Notification }, () => ext.clients.rpcClient.getGitTokenForRepository({ orgId: selected.org?.id?.toString()!, diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts index 43b56bf418b..8649037bdf5 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts @@ -588,7 +588,7 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W if (process.env.CLOUD_STS_TOKEN) { try { - const gitPat = await window.withProgress({ title: `Accessing the repository ${_repoUrl}...`, location: ProgressLocation.Notification }, () => + const gitPat = await window.withProgress({ title: `Accessing the repository ${params.repo.orgName}/${params.repo.repo}...`, location: ProgressLocation.Notification }, () => ext.clients.rpcClient.getGitTokenForRepository({ orgId: params.org.id?.toString(), gitOrg: params.repo.orgName, From 9dc6688047d7ae46975183c93b0ccf918ed02ef5 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Thu, 25 Sep 2025 12:02:58 +0530 Subject: [PATCH 13/41] fix bitbucket repo based component creation --- .../src/PlatformExtensionApi.ts | 1 + .../src/cmds/commit-and-push-to-git-cmd.ts | 124 ++++++++++++++---- .../src/cmds/open-in-console-cmd.ts | 2 +- .../wso2-platform-extension/src/extension.ts | 4 +- .../wso2-platform-extension/src/utils.ts | 2 +- .../src/webviews/WebviewRPC.ts | 29 ++-- .../ComponentFormView/ComponentFormView.tsx | 2 +- 7 files changed, 111 insertions(+), 53 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts index 6e1946724b8..d22e458c484 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts @@ -24,6 +24,7 @@ import { contextStore } from "./stores/context-store"; import { webviewStateStore } from "./stores/webview-state-store"; import { openClonedDir } from "./uri-handlers"; import { isSamePath } from "./utils"; + export class PlatformExtensionApi implements IWso2PlatformExtensionAPI { public isLoggedIn = () => !!authStore.getState().state?.userInfo; public getDirectoryComponents = (fsPath: string) => diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts index d422c7416fa..e9a77620e43 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts @@ -16,7 +16,15 @@ * under the License. */ -import { CommandIds, ComponentKind, type ContextStoreComponentState, GitProvider, type ICommitAndPuhCmdParams, parseGitURL } from "@wso2/wso2-platform-core"; +import { + CommandIds, + ComponentKind, + type ContextStoreComponentState, + GitProvider, + type ICommitAndPuhCmdParams, + type Organization, + parseGitURL, +} from "@wso2/wso2-platform-core"; import { type ExtensionContext, ProgressLocation, type QuickPickItem, Uri, commands, env, window, workspace } from "vscode"; import { ext } from "../extensionVariables"; import { initGit } from "../git/main"; @@ -108,34 +116,26 @@ export function commitAndPushToGitCommand(context: ExtensionContext) { } }); - if (!matchingRemote && process.env.CLOUD_STS_TOKEN && remotes[0].fetchUrl) { - try { - const repoUrl = remotes[0].fetchUrl; - const parsed = parseGitURL(repoUrl); - if (parsed && parsed[2] === GitProvider.GITHUB) { - const [repoOrg, repoName] = parsed; - const urlObj = new URL(repoUrl); - getLogger().debug(`Fetching PAT for org ${repoOrg} and repo ${repoName}`); - const gitPat = await window.withProgress( - { title: `Accessing the repository ${repoOrg}/${repoName}...`, location: ProgressLocation.Notification }, - () => - ext.clients.rpcClient.getGitTokenForRepository({ - orgId: selected.org?.id?.toString()!, - gitOrg: repoOrg, - gitRepo: repoName, - secretRef: selectedComp.component?.spec?.source?.secretRef || "", - }), - ); - urlObj.username = gitPat.username || "x-access-token"; - urlObj.password = gitPat.token; - await window.withProgress({ title: "Setting new remote...", location: ProgressLocation.Notification }, async () => { - await repo.addRemote("cloud-editor-remote", urlObj.href); - const remotes = await repo.getRemotes(); - matchingRemote = remotes.find((item) => item.name === "cloud-editor-remote"); - }); - } - } catch { - getLogger().debug(`Failed to get token for ${remotes[0].fetchUrl}`); + if (!matchingRemote && remotes[0].fetchUrl) { + const repoUrl = remotes[0].fetchUrl; + const parsed = parseGitURL(repoUrl); + if (parsed) { + const [repoOrg, repoName, provider] = parsed; + const urlObj = new URL(repoUrl); + await enrichGitUsernamePassword( + selected.org!, + repoOrg, + repoName, + provider, + urlObj, + repoUrl, + selectedComp.component?.spec?.source?.secretRef || "", + ); + await window.withProgress({ title: "Setting new remote...", location: ProgressLocation.Notification }, async () => { + await repo.addRemote("cloud-editor-remote", urlObj.href); + const remotes = await repo.getRemotes(); + matchingRemote = remotes.find((item) => item.name === "cloud-editor-remote"); + }); } } @@ -185,3 +185,69 @@ export function commitAndPushToGitCommand(context: ExtensionContext) { }), ); } + +export const enrichGitUsernamePassword = async ( + org: Organization, + repoOrg: string, + repoName: string, + provider: string, + urlObj: URL, + fetchUrl: string, + secretRef: string, +) => { + if (process.env.CLOUD_STS_TOKEN && provider === GitProvider.GITHUB && !urlObj.password) { + try { + getLogger().debug(`Fetching PAT for org ${repoOrg} and repo ${repoName}`); + const gitPat = await window.withProgress( + { title: `Accessing the repository ${repoOrg}/${repoName}...`, location: ProgressLocation.Notification }, + () => + ext.clients.rpcClient.getGitTokenForRepository({ + orgId: org?.id?.toString()!, + gitOrg: repoOrg, + gitRepo: repoName, + secretRef: secretRef, + }), + ); + urlObj.username = gitPat.username || "x-access-token"; + urlObj.password = gitPat.token; + } catch { + getLogger().debug(`Failed to get token for ${fetchUrl}`); + } + } + + if (!urlObj.username) { + const username = await window.showInputBox({ + title: "Git Username", + ignoreFocusOut: true, + placeHolder: "username", + validateInput: (val) => { + if (!val) { + return "Git username is required"; + } + return null; + }, + }); + if (!username) { + throw new Error("Git username is required"); + } + urlObj.username = username; + } + if (!urlObj.password) { + const password = await window.showInputBox({ + title: "Git Password", + ignoreFocusOut: true, + placeHolder: "password", + password: true, + validateInput: (val) => { + if (!val) { + return "Git password is required"; + } + return null; + }, + }); + if (!password) { + throw new Error("Git password is required"); + } + urlObj.password = password; + } +}; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts index 7161b376e19..d20684123b7 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts @@ -58,7 +58,7 @@ export function openInConsoleCommand(context: ExtensionContext) { } } - let projectBaseUrl = `${ext.config?.choreoConsoleUrl}/organizations/${selectedOrg?.handle}/projects/${selectedProject.handler}`; + const projectBaseUrl = `${ext.config?.choreoConsoleUrl}/organizations/${selectedOrg?.handle}/projects/${selectedProject.handler}`; if (params?.component) { env.openExternal(Uri.parse(`${projectBaseUrl}/components/${params?.component.metadata.handler}/overview`)); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts index cf172b01c65..c3d499cc65a 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts @@ -21,6 +21,7 @@ import { type ConfigurationChangeEvent, commands, window, workspace } from "vsco import { PlatformExtensionApi } from "./PlatformExtensionApi"; import { ChoreoRPCClient } from "./choreo-rpc"; import { initRPCServer } from "./choreo-rpc/activate"; +import { getCliVersion } from "./choreo-rpc/cli-install"; import { activateCmds } from "./cmds"; import { continueCreateComponent } from "./cmds/create-component-cmd"; import { activateCodeLenses } from "./code-lens"; @@ -36,9 +37,8 @@ import { locationStore } from "./stores/location-store"; import { ChoreoConfigurationProvider, addTerminalHandlers } from "./tarminal-handlers"; import { activateTelemetry } from "./telemetry/telemetry"; import { activateURIHandlers } from "./uri-handlers"; -import { registerYamlLanguageServer } from "./yaml-ls"; -import { getCliVersion } from "./choreo-rpc/cli-install"; import { getExtVersion } from "./utils"; +import { registerYamlLanguageServer } from "./yaml-ls"; export async function activate(context: vscode.ExtensionContext) { activateTelemetry(context); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/utils.ts index 115f2467cd1..6fe5a7b6682 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/utils.ts @@ -441,4 +441,4 @@ export const parseJwt = (token: string): { iss: string } | null => { export const getExtVersion = (context: ExtensionContext): string => { const packageJson = JSON.parse(readFileSync(path.join(context?.extensionPath, "package.json"), "utf8")); return packageJson?.version; -}; \ No newline at end of file +}; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts index 8649037bdf5..762dc2973fa 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts @@ -64,6 +64,7 @@ import { GetLocalGitData, GetSubPath, GetWebviewStoreState, + GitProvider, GoToSource, HasDirtyLocalGitRepo, JoinFsFilePaths, @@ -106,6 +107,7 @@ import { deepEqual, getShortenedHash, makeURLSafe, + parseGitURL, } from "@wso2/wso2-platform-core"; import * as yaml from "js-yaml"; import { ProgressLocation, QuickPickItemKind, Uri, type WebviewPanel, type WebviewView, commands, env, window, workspace } from "vscode"; @@ -115,6 +117,7 @@ import { BROADCAST } from "vscode-messenger-common"; import { registerChoreoRpcResolver } from "../choreo-rpc"; import { getChoreoExecPath } from "../choreo-rpc/cli-install"; import { quickPickWithLoader } from "../cmds/cmd-utils"; +import { enrichGitUsernamePassword } from "../cmds/commit-and-push-to-git-cmd"; import { submitCreateComponentHandler } from "../cmds/create-component-cmd"; import { ext } from "../extensionVariables"; import { initGit } from "../git/main"; @@ -586,22 +589,10 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W } const urlObj = new URL(_repoUrl); - if (process.env.CLOUD_STS_TOKEN) { - try { - const gitPat = await window.withProgress({ title: `Accessing the repository ${params.repo.orgName}/${params.repo.repo}...`, location: ProgressLocation.Notification }, () => - ext.clients.rpcClient.getGitTokenForRepository({ - orgId: params.org.id?.toString(), - gitOrg: params.repo.orgName, - gitRepo: params.repo.repo, - secretRef: params.repo.secretRef || "", - }), - ); - - urlObj.username = gitPat.username || "x-access-token"; - urlObj.password = gitPat.token; - } catch { - getLogger().debug(`Failed to get token for ${params}`); - } + const parsed = parseGitURL(_repoUrl); + if (parsed) { + const [repoOrg, repoName, provider] = parsed; + await enrichGitUsernamePassword(params.org, repoOrg, repoName, provider, urlObj, _repoUrl, params.repo.secretRef || ""); } const repoUrl = urlObj.href; @@ -618,13 +609,13 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W const updatedTomlContent = toml.stringify(parsedToml); await fs.promises.writeFile(balTomlPath, updatedTomlContent, "utf-8"); } - + // TODO: Enable this after fixing component creation from root /* if (params.repo?.isBareRepo && ["", "/", "."].includes(params.subpath)) { // if component is to be created in the root of a bare repo, // then we can initialize the current directory as the repo root - await window.withProgress({ title: `Initializing currently opened directory as repository ${_repoUrl}...`, location: ProgressLocation.Notification }, async () => { + await window.withProgress({ title: `Initializing currently opened directory as repository...`, location: ProgressLocation.Notification }, async () => { await newGit.init(params.cwd); const dotGit = await newGit?.getRepositoryDotGit(params.cwd); const repo = newGit.open(params.cwd, dotGit); @@ -676,7 +667,7 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W await window.withProgress({ title: "Pushing the changes to your remote repository...", location: ProgressLocation.Notification }, async () => { await repo.add(["."]); await repo.commit(`Add source for new ${extName} ${extName === "Devant" ? "Integration" : "Component"} (${params.componentName})`); - const headRef = await repo.getHEADRef() + const headRef = await repo.getHEADRef(); await repo.push(headRef?.upstream?.remote || "origin", headRef?.name || params.repo.branch); }); diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx index 762c816efe2..373f03fd3c2 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx @@ -242,7 +242,7 @@ export const ComponentFormView: FC = (props) => { }; if (provider !== GitProvider.GITHUB) { - createParams.gitCredRef = genDetails?.credential; + createParams.gitCredRef = props.isNewCodeServerComp ? repoInitDetails.credential : genDetails?.credential; } if (buildDetails.buildPackLang === ChoreoImplementationType.Docker) { From df83dfcec66cb60bd1b194a773c770346e3b2676 Mon Sep 17 00:00:00 2001 From: "Joseph Fonseka hevayo@gmail.com" Date: Thu, 25 Sep 2025 07:53:36 +0530 Subject: [PATCH 14/41] Add customization for devant editor --- .../src/interfaces/constants.ts | 1 + .../src/utils/identifier-utils.ts | 18 +++ .../ballerina-extension/package.json | 14 ++ .../src/features/devant/activator.ts | 148 ++++++++++++++++++ .../src/features/project/cmds/run.ts | 30 ++++ .../ballerina-extension/src/stateMachine.ts | 2 + .../src/views/visualizer/webview.ts | 6 + .../src/views/BI/Overview/index.tsx | 134 ++++++++++++++-- 8 files changed, 337 insertions(+), 16 deletions(-) create mode 100644 workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts diff --git a/workspaces/ballerina/ballerina-core/src/interfaces/constants.ts b/workspaces/ballerina/ballerina-core/src/interfaces/constants.ts index b35efce4625..cf21963ae70 100644 --- a/workspaces/ballerina/ballerina-core/src/interfaces/constants.ts +++ b/workspaces/ballerina/ballerina-core/src/interfaces/constants.ts @@ -53,4 +53,5 @@ export const BI_COMMANDS = { BI_EDIT_TEST_FUNCTION_DEF: 'BI.test.edit.function.def', ADD_NATURAL_FUNCTION: 'BI.project-explorer.add-natural-function', TOGGLE_TRACE_LOGS: 'BI.toggle.trace.logs', + DEVANT_PUSH_TO_CLOUD: 'BI.devant.push.cloud' }; diff --git a/workspaces/ballerina/ballerina-core/src/utils/identifier-utils.ts b/workspaces/ballerina/ballerina-core/src/utils/identifier-utils.ts index 261b8a8575e..790974eeb2c 100644 --- a/workspaces/ballerina/ballerina-core/src/utils/identifier-utils.ts +++ b/workspaces/ballerina/ballerina-core/src/utils/identifier-utils.ts @@ -19,6 +19,24 @@ import { ComponentInfo } from "../interfaces/ballerina"; import { BallerinaProjectComponents } from "../interfaces/extended-lang-client"; +import { SCOPE } from "../state-machine-types"; + +const INTEGRATION_API_MODULES = ["http", "graphql", "tcp"]; +const EVENT_INTEGRATION_MODULES = ["kafka", "rabbitmq", "salesforce", "trigger.github", "mqtt", "asb"]; +const FILE_INTEGRATION_MODULES = ["ftp", "file"]; +const AI_AGENT_MODULE = "ai"; + +export function findScopeByModule(moduleName: string): SCOPE { + if (AI_AGENT_MODULE === moduleName) { + return SCOPE.AI_AGENT; + } else if (INTEGRATION_API_MODULES.includes(moduleName)) { + return SCOPE.INTEGRATION_AS_API; + } else if (EVENT_INTEGRATION_MODULES.includes(moduleName)) { + return SCOPE.EVENT_INTEGRATION; + } else if (FILE_INTEGRATION_MODULES.includes(moduleName)) { + return SCOPE.FILE_INTEGRATION; + } +} export function getAllVariablesForAiFrmProjectComponents(projectComponents: BallerinaProjectComponents): { [key: string]: any } { const variableCollection: { [key: string]: any } = {}; diff --git a/workspaces/ballerina/ballerina-extension/package.json b/workspaces/ballerina/ballerina-extension/package.json index 6712b460331..779749efe85 100644 --- a/workspaces/ballerina/ballerina-extension/package.json +++ b/workspaces/ballerina/ballerina-extension/package.json @@ -726,6 +726,14 @@ "command": "BI.toggle.trace.logs", "title": "Toggle Trace Logs", "category": "BI" + }, + { + "command": "BI.devant.push.cloud", + "title": "Save Changes in the cloud", + "icon": "$(save)", + "group": "navigation", + "category": "BI", + "enablement": "isBIProject && devant.editor" } ], "views": { @@ -846,6 +854,12 @@ "group": "navigation@3", "title": "Debug Integration", "when": "isBalVisualizerActive && isBIProject" + }, + { + "command": "BI.devant.push.cloud", + "group": "navigation", + "title": "Save Changes in the cloud", + "when": "isBIProject && devant.editor" } ], "notebook/toolbar": [ diff --git a/workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts new file mode 100644 index 00000000000..3f733b47887 --- /dev/null +++ b/workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { BI_COMMANDS, DIRECTORY_MAP, EVENT_TYPE, MACHINE_VIEW, SCOPE, findScopeByModule } from "@wso2/ballerina-core"; +import { + CommandIds as PlatformCommandIds, + IWso2PlatformExtensionAPI, + ICommitAndPuhCmdParams, + ICreateComponentCmdParams, +} from "@wso2/wso2-platform-core"; +import { BallerinaExtension } from "../../core"; +import { openView, StateMachine } from "../../stateMachine"; +import { commands, extensions, window } from "vscode"; +import * as path from "path"; +import * as fs from "fs"; +import { debug } from "../../utils"; + +export function activateDevantFeatures(_ballerinaExtInstance: BallerinaExtension) { + const cloudToken = process.env.CLOUD_STS_TOKEN; + if (cloudToken) { + // Set the connection token context + commands.executeCommand("setContext", "devant.editor", true); + } + + commands.registerCommand(BI_COMMANDS.DEVANT_PUSH_TO_CLOUD, handleComponentPushToDevant); +} + +const handleComponentPushToDevant = async () => { + const projectRoot = StateMachine.context().projectUri; + if (!projectRoot) { + return; + } + + const platformExt = extensions.getExtension("wso2.wso2-platform"); + if (!platformExt) { + return; + } + if (!platformExt.isActive) { + await platformExt.activate(); + } + const platformExtAPI: IWso2PlatformExtensionAPI = platformExt.exports; + if (isGitRepo(projectRoot)) { + // push changes to repo if component for the directory already exists + await commands.executeCommand(PlatformCommandIds.CommitAndPushToGit, { + componentPath: projectRoot, + } as ICommitAndPuhCmdParams); + } else if (platformExtAPI.getDirectoryComponents(projectRoot)?.length) { + debug(`project url: ${projectRoot}`); + // push changes to repo if component for the directory already exists + const hasChanges = await platformExtAPI.localRepoHasChanges(projectRoot); + if (!hasChanges) { + window.showInformationMessage("There are no new changes to push to cloud"); + return; + } + await commands.executeCommand(PlatformCommandIds.CommitAndPushToGit, { + componentPath: projectRoot, + } as ICommitAndPuhCmdParams); + } else { + // create a new component if it doesn't exist for the directory + if (!StateMachine.context().projectStructure) { + return; + } + + const services = StateMachine.context().projectStructure.directoryMap[DIRECTORY_MAP.SERVICE]; + const automation = StateMachine.context().projectStructure.directoryMap[DIRECTORY_MAP.AUTOMATION]; + + const scopeSet = new Set(); + + if (services) { + services.find((svc) => { + const scope = findScopeByModule(svc?.moduleName); + if (scope) { + scopeSet.add(scope); + } + }); + } + + if (automation?.length > 0) { + scopeSet.add(SCOPE.AUTOMATION); + } + + let integrationType: SCOPE; + + if (scopeSet.size === 0) { + window + .showInformationMessage( + "Please add a construct and try again to deploy your integration", + "Add Construct" + ) + .then((resp) => { + if (resp === "Add Construct") { + openView(EVENT_TYPE.OPEN_VIEW, { view: MACHINE_VIEW.BIComponentView }); + } + }); + return; + } else if (scopeSet.size === 1) { + integrationType = [...scopeSet][0]; + } else { + const selectedScope = await window.showQuickPick([...scopeSet], { + placeHolder: "Multiple types of artifacts detected. Please select the artifact type to be deployed", + }); + integrationType = selectedScope as SCOPE; + } + + const deployementParams: ICreateComponentCmdParams = { + integrationType: integrationType as any, + buildPackLang: "ballerina", + componentDir: StateMachine.context().projectUri, + extName: "Devant", + }; + commands.executeCommand(PlatformCommandIds.CreateNewComponent, deployementParams); + } +}; + + +function isGitRepo(dir: string): boolean { + let currentDir = dir; + while (true) { + const gitDir = path.join(currentDir, ".git"); + if (fs.existsSync(gitDir)) { + return true; + } + const parentDir = path.dirname(currentDir); + if (parentDir === currentDir) { + // Reached the root directory + break; + } + currentDir = parentDir; + } + return false; +} + + diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/run.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/run.ts index 0a569981c9a..36c5d7e3ccb 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/run.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/run.ts @@ -76,6 +76,36 @@ function activateRunCmdCommand() { return; } + // TODO: Test in the cloud editor environment and remove the comments if working + // This should be handles automatically by the platform + + // Check if we should update auth token (only in cloud editor with private package dependencies) + // const shouldUpdate = await shouldUpdateAuthToken(); + + // if (shouldUpdate) { + // try { + // // Get the STS token from platform extension for authenticated operations + // const stsToken = await getDevantStsToken(); + // console.log("Cloud editor detected with dependencies, checking STS token..."); + + // // Only update Settings.toml if token needs updating + // if (stsToken && stsToken.trim() !== "") { + // const currentToken = await getCurrentAccessToken(); + + // if (shouldUpdateToken(currentToken, stsToken)) { + // await updateBallerinaSettingsWithStsToken(stsToken); + // console.log('Token updated in Settings.toml for cloud editor'); + // // Don't show notification in cloud editor to avoid noise + // } + // } else { + // console.warn('Unable to retrieve STS token in cloud editor environment'); + // } + // } catch (error) { + // console.warn('Failed to update authentication token in cloud editor:', error); + // // Continue execution even if token update fails + // } + // } + if (currentProject.kind !== PROJECT_TYPE.SINGLE_FILE) { const configPath: string = extension.ballerinaExtInstance.getBallerinaConfigPath(); extension.ballerinaExtInstance.setBallerinaConfigPath(''); diff --git a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts index 5c67df6841b..a9eb2f74c66 100644 --- a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts +++ b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts @@ -16,6 +16,7 @@ import { AIStateMachine } from './views/ai-panel/aiMachine'; import { StateMachinePopup } from './stateMachinePopup'; import { checkIsBallerina, checkIsBI, fetchScope, getOrgPackageName } from './utils'; import { buildProjectArtifactsStructure } from './utils/project-artifacts'; +import { activateDevantFeatures } from './features/devant/activator'; interface MachineContext extends VisualizerLocation { langClient: ExtendedLangClient | null; @@ -311,6 +312,7 @@ const stateMachine = createMachine( if (!ls.biSupported) { commands.executeCommand('setContext', 'BI.status', 'updateNeed'); } + activateDevantFeatures(ls); resolve({ langClient: ls.langClient, isBISupported: ls.biSupported }); } catch (error) { throw new Error("LS Activation failed", error); diff --git a/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts b/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts index ffa3423d621..e55bde02524 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts @@ -137,6 +137,9 @@ export class VisualizerWebview { } private getWebviewContent(webView: Webview) { + // Check if devant.editor extension is active + const isDevantEditor = vscode.commands.executeCommand('getContext', 'devant.editor') !== undefined; + const body = `
@@ -243,6 +246,9 @@ export class VisualizerWebview { } `; const scripts = ` + // Flag to check if devant.editor is active + window.isDevantEditor = ${isDevantEditor}; + function loadedScript() { function renderDiagrams() { visualizerWebview.renderWebview("visualizer", document.getElementById("webview-container")); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Overview/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Overview/index.tsx index 0b5d319922f..62c5b3373b7 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Overview/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Overview/index.tsx @@ -26,7 +26,8 @@ import { DevantMetadata, SHARED_COMMANDS, DIRECTORY_MAP, - SCOPE + SCOPE, + findScopeByModule } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { Typography, Codicon, ProgressRing, Button, Icon, Divider, CheckBox, ProgressIndicator, Overlay } from "@wso2/ui-toolkit"; @@ -39,7 +40,6 @@ import ReactMarkdown from "react-markdown"; import { useQuery } from '@tanstack/react-query' import { IOpenInConsoleCmdParams, CommandIds as PlatformExtCommandIds } from "@wso2/wso2-platform-core"; import { AlertBoxWithClose } from "../../AIPanel/AlertBoxWithClose"; -import { findScopeByModule } from "./utils"; const SpinnerContainer = styled.div` display: flex; @@ -338,7 +338,7 @@ function DeploymentOption({ {isExpanded ? ( ) : ( void, goToDevant: () => void, devantMetadata: DevantMetadata }) { + const { rpcClient } = useRpcContext(); + + const handleSaveAndDeployToDevant = () => { + handleDeploy(); + } + + const handlePushChanges = () => { + rpcClient.getCommonRpcClient().executeCommand({ commands: [BI_COMMANDS.DEVANT_PUSH_TO_CLOUD] }); + } + + // Check if project has automation or service + const hasAutomationOrService = projectStructure?.directoryMap && ( + (projectStructure.directoryMap.AUTOMATION && projectStructure.directoryMap.AUTOMATION.length > 0) || + (projectStructure.directoryMap.SERVICE && projectStructure.directoryMap.SERVICE.length > 0) + ); + + console.log(">>> devantMetadata", devantMetadata); + + return ( + + {devantMetadata?.hasComponent ? Deployed in Devant : Deploy to Devant} + {!hasAutomationOrService ? ( + + Before you can deploy your integration to Devant, please add an artifact (such as a Service or Automation) to your project. + + ) : ( + <> + {devantMetadata?.hasComponent ? ( + <> + + This integration is deployed in Devant. + + + + + ) : ( + + + Deploy your integration to Devant and run it in the cloud. + + + + )} + + )} + + ); +} + + interface ComponentDiagramProps { projectPath: string; } @@ -517,7 +605,7 @@ export function Overview(props: ComponentDiagramProps) { const { data: devantMetadata } = useQuery({ queryKey: ["devant-metadata", props.projectPath], queryFn: () => rpcClient.getBIDiagramRpcClient().getDevantMetadata(), - refetchInterval: 5000 + refetchInterval: 60000 // TODO: remove this with an event }) const [showAlert, setShowAlert] = React.useState(false); @@ -720,6 +808,8 @@ export function Overview(props: ComponentDiagramProps) { return resp; } + const isDevantEditor = (window as any).isDevantEditor !== undefined ? true : false; + return ( @@ -743,7 +833,7 @@ export function Overview(props: ComponentDiagramProps) { - {showAlert && ( + {/*showAlert && ( handleClose()} btn2Id="Close" /> - )} + )*/} Design {!isEmptyProject() && ( @@ -827,16 +917,28 @@ export function Overview(props: ComponentDiagramProps) { - 0} - /> - - + {!isDevantEditor && + <> + 0} + /> + + + + } + {isDevantEditor && + + } From bb87ec51ef903ee605fe7ea41f0e7280568b7e91 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Fri, 26 Sep 2025 13:32:13 +0530 Subject: [PATCH 15/41] update the package versions of react-markdown --- common/config/rush/pnpm-lock.yaml | 352 +----------------- .../wso2-platform-webviews/package.json | 6 +- .../src/components/Markdown/Markdown.tsx | 7 +- 3 files changed, 12 insertions(+), 353 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index e32ace5f94d..f1de2ccea94 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -3806,13 +3806,13 @@ importers: specifier: 7.56.4 version: 7.56.4(react@18.2.0) react-markdown: - specifier: ^7.1.0 - version: 7.1.2(@types/react@18.2.0)(react@18.2.0) + specifier: 10.1.0 + version: 10.1.0(@types/react@18.2.0)(react@18.2.0) rehype-raw: - specifier: ^6.1.0 - version: 6.1.1 + specifier: 7.0.0 + version: 7.0.0 remark-gfm: - specifier: ^4.0.1 + specifier: 4.0.1 version: 4.0.1 swagger-ui-react: specifier: 5.22.0 @@ -8701,9 +8701,6 @@ packages: '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - '@types/mdurl@1.0.5': - resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} - '@types/mdx@2.0.13': resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} @@ -13684,9 +13681,6 @@ packages: hast-util-to-parse5@8.0.0: resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} - hast-util-whitespace@2.0.1: - resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} - hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} @@ -15284,10 +15278,6 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - klona@2.0.6: resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} engines: {node: '>= 8'} @@ -15711,15 +15701,9 @@ packages: mdast-util-definitions@4.0.0: resolution: {integrity: sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==} - mdast-util-definitions@5.1.2: - resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} - mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - mdast-util-from-markdown@1.3.1: - resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} - mdast-util-from-markdown@2.0.2: resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} @@ -15759,9 +15743,6 @@ packages: mdast-util-to-hast@10.0.1: resolution: {integrity: sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==} - mdast-util-to-hast@11.3.0: - resolution: {integrity: sha512-4o3Cli3hXPmm1LhB+6rqhfsIUBjnKFlIUZvudaermXB+4/KONdd/W4saWWkC+LBLbPMqhFSSTSRgafHsT5fVJw==} - mdast-util-to-hast@13.2.0: resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} @@ -15771,9 +15752,6 @@ packages: mdast-util-to-string@1.1.0: resolution: {integrity: sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==} - mdast-util-to-string@3.2.0: - resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} - mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} @@ -15879,9 +15857,6 @@ packages: microevent.ts@0.1.1: resolution: {integrity: sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==} - micromark-core-commonmark@1.1.0: - resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} - micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -15906,123 +15881,63 @@ packages: micromark-extension-gfm@3.0.0: resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} - micromark-factory-destination@1.1.0: - resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} - micromark-factory-destination@2.0.1: resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - micromark-factory-label@1.1.0: - resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} - micromark-factory-label@2.0.1: resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - micromark-factory-space@1.1.0: - resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} - micromark-factory-space@2.0.1: resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} - micromark-factory-title@1.1.0: - resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} - micromark-factory-title@2.0.1: resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - micromark-factory-whitespace@1.1.0: - resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} - micromark-factory-whitespace@2.0.1: resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} - micromark-util-character@1.2.0: - resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} - micromark-util-character@2.1.1: resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - micromark-util-chunked@1.1.0: - resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} - micromark-util-chunked@2.0.1: resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - micromark-util-classify-character@1.1.0: - resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} - micromark-util-classify-character@2.0.1: resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - micromark-util-combine-extensions@1.1.0: - resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} - micromark-util-combine-extensions@2.0.1: resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - micromark-util-decode-numeric-character-reference@1.1.0: - resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} - micromark-util-decode-numeric-character-reference@2.0.2: resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - micromark-util-decode-string@1.1.0: - resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} - micromark-util-decode-string@2.0.1: resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - micromark-util-encode@1.1.0: - resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} - micromark-util-encode@2.0.1: resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - micromark-util-html-tag-name@1.2.0: - resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} - micromark-util-html-tag-name@2.0.1: resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - micromark-util-normalize-identifier@1.1.0: - resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} - micromark-util-normalize-identifier@2.0.1: resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - micromark-util-resolve-all@1.1.0: - resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} - micromark-util-resolve-all@2.0.1: resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - micromark-util-sanitize-uri@1.2.0: - resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} - micromark-util-sanitize-uri@2.0.1: resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - micromark-util-subtokenize@1.1.0: - resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} - micromark-util-subtokenize@2.1.0: resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} - micromark-util-symbol@1.1.0: - resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} - micromark-util-symbol@2.0.1: resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - micromark-util-types@1.1.0: - resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} - micromark-util-types@2.0.2: resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} - micromark@3.2.0: - resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} - micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} @@ -18018,12 +17933,6 @@ packages: '@types/react': '>=18' react: '>=18' - react-markdown@7.1.2: - resolution: {integrity: sha512-ibMcc0EbfmbwApqJD8AUr0yls8BSrKzIbHaUsPidQljxToCqFh34nwtu3CXNEItcVJNzpjDHrhK8A+MAh2JW3A==} - peerDependencies: - '@types/react': '>=16' - react: '>=16' - react-monaco-editor@0.58.0: resolution: {integrity: sha512-e8JH0TQEzO96Wd/EXgzc9M9tQK1pxBECD+8GNob9slMURcCM36TiVrgc4topWCDGYxRuMj8IEkaX+s3eQcUUqw==} peerDependencies: @@ -18360,9 +18269,6 @@ packages: remark-mdx@1.6.22: resolution: {integrity: sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==} - remark-parse@10.0.2: - resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} - remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} @@ -18372,9 +18278,6 @@ packages: remark-rehype@11.1.2: resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} - remark-rehype@9.1.0: - resolution: {integrity: sha512-oLa6YmgAYg19zb0ZrBACh40hpBLteYROaPLhBXzLgjqyHQrN+gVP9N/FJvfzuNNuzCutktkroXEZBrxAxKhh7Q==} - remark-slug@6.1.0: resolution: {integrity: sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ==} @@ -20428,15 +20331,9 @@ packages: unist-builder@2.0.3: resolution: {integrity: sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==} - unist-builder@3.0.1: - resolution: {integrity: sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==} - unist-util-generated@1.1.6: resolution: {integrity: sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==} - unist-util-generated@2.0.1: - resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} - unist-util-is@4.1.0: resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} @@ -20461,9 +20358,6 @@ packages: unist-util-remove@2.1.0: resolution: {integrity: sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==} - unist-util-stringify-position@3.0.3: - resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} - unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} @@ -20685,11 +20579,6 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true - uvu@0.5.6: - resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} - engines: {node: '>=8'} - hasBin: true - v8-compile-cache@2.4.0: resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} @@ -31367,8 +31256,6 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@types/mdurl@1.0.5': {} - '@types/mdx@2.0.13': {} '@types/mime-types@2.1.4': {} @@ -38100,8 +37987,6 @@ snapshots: web-namespaces: 2.0.1 zwitch: 2.0.4 - hast-util-whitespace@2.0.1: {} - hast-util-whitespace@3.0.0: dependencies: '@types/hast': 3.0.4 @@ -40419,8 +40304,6 @@ snapshots: kleur@3.0.3: {} - kleur@4.1.5: {} - klona@2.0.6: {} known-css-properties@0.37.0: {} @@ -40853,12 +40736,6 @@ snapshots: dependencies: unist-util-visit: 2.0.3 - mdast-util-definitions@5.1.2: - dependencies: - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - unist-util-visit: 4.1.2 - mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -40866,23 +40743,6 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - mdast-util-from-markdown@1.3.1: - dependencies: - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - decode-named-character-reference: 1.2.0 - mdast-util-to-string: 3.2.0 - micromark: 3.2.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-decode-string: 1.1.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - unist-util-stringify-position: 3.0.3 - uvu: 0.5.6 - transitivePeerDependencies: - - supports-color - mdast-util-from-markdown@2.0.2: dependencies: '@types/mdast': 4.0.4 @@ -41017,18 +40877,6 @@ snapshots: unist-util-position: 3.1.0 unist-util-visit: 2.0.3 - mdast-util-to-hast@11.3.0: - dependencies: - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - '@types/mdurl': 1.0.5 - mdast-util-definitions: 5.1.2 - mdurl: 1.0.1 - unist-builder: 3.0.1 - unist-util-generated: 2.0.1 - unist-util-position: 4.0.4 - unist-util-visit: 4.1.2 - mdast-util-to-hast@13.2.0: dependencies: '@types/hast': 3.0.4 @@ -41055,10 +40903,6 @@ snapshots: mdast-util-to-string@1.1.0: {} - mdast-util-to-string@3.2.0: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-to-string@4.0.0: dependencies: '@types/mdast': 4.0.4 @@ -41169,25 +41013,6 @@ snapshots: microevent.ts@0.1.1: {} - micromark-core-commonmark@1.1.0: - dependencies: - decode-named-character-reference: 1.2.0 - micromark-factory-destination: 1.1.0 - micromark-factory-label: 1.1.0 - micromark-factory-space: 1.1.0 - micromark-factory-title: 1.1.0 - micromark-factory-whitespace: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-chunked: 1.1.0 - micromark-util-classify-character: 1.1.0 - micromark-util-html-tag-name: 1.2.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-resolve-all: 1.1.0 - micromark-util-subtokenize: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.2.0 @@ -41265,25 +41090,12 @@ snapshots: micromark-util-combine-extensions: 2.0.1 micromark-util-types: 2.0.2 - micromark-factory-destination@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - micromark-factory-destination@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-factory-label@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-factory-label@2.0.1: dependencies: devlop: 1.1.0 @@ -41291,23 +41103,11 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-factory-space@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-types: 1.1.0 - micromark-factory-space@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-types: 2.0.2 - micromark-factory-title@1.1.0: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - micromark-factory-title@2.0.1: dependencies: micromark-factory-space: 2.0.1 @@ -41315,13 +41115,6 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-factory-whitespace@1.1.0: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - micromark-factory-whitespace@2.0.1: dependencies: micromark-factory-space: 2.0.1 @@ -41329,61 +41122,30 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-util-character@1.2.0: - dependencies: - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - micromark-util-character@2.1.1: dependencies: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-util-chunked@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - micromark-util-chunked@2.0.1: dependencies: micromark-util-symbol: 2.0.1 - micromark-util-classify-character@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - micromark-util-classify-character@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-util-combine-extensions@1.1.0: - dependencies: - micromark-util-chunked: 1.1.0 - micromark-util-types: 1.1.0 - micromark-util-combine-extensions@2.0.1: dependencies: micromark-util-chunked: 2.0.1 micromark-util-types: 2.0.2 - micromark-util-decode-numeric-character-reference@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - micromark-util-decode-numeric-character-reference@2.0.2: dependencies: micromark-util-symbol: 2.0.1 - micromark-util-decode-string@1.1.0: - dependencies: - decode-named-character-reference: 1.2.0 - micromark-util-character: 1.2.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-decode-string@2.0.1: dependencies: decode-named-character-reference: 1.2.0 @@ -41391,49 +41153,24 @@ snapshots: micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 - micromark-util-encode@1.1.0: {} - micromark-util-encode@2.0.1: {} - micromark-util-html-tag-name@1.2.0: {} - micromark-util-html-tag-name@2.0.1: {} - micromark-util-normalize-identifier@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - micromark-util-normalize-identifier@2.0.1: dependencies: micromark-util-symbol: 2.0.1 - micromark-util-resolve-all@1.1.0: - dependencies: - micromark-util-types: 1.1.0 - micromark-util-resolve-all@2.0.1: dependencies: micromark-util-types: 2.0.2 - micromark-util-sanitize-uri@1.2.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-encode: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-sanitize-uri@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-encode: 2.0.1 micromark-util-symbol: 2.0.1 - micromark-util-subtokenize@1.1.0: - dependencies: - micromark-util-chunked: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-util-subtokenize@2.1.0: dependencies: devlop: 1.1.0 @@ -41441,36 +41178,10 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-util-symbol@1.1.0: {} - micromark-util-symbol@2.0.1: {} - micromark-util-types@1.1.0: {} - micromark-util-types@2.0.2: {} - micromark@3.2.0: - dependencies: - '@types/debug': 4.1.12 - debug: 4.4.1(supports-color@8.1.1) - decode-named-character-reference: 1.2.0 - micromark-core-commonmark: 1.1.0 - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-chunked: 1.1.0 - micromark-util-combine-extensions: 1.1.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-encode: 1.1.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-resolve-all: 1.1.0 - micromark-util-sanitize-uri: 1.2.0 - micromark-util-subtokenize: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - transitivePeerDependencies: - - supports-color - micromark@4.0.2: dependencies: '@types/debug': 4.1.12 @@ -43750,27 +43461,6 @@ snapshots: transitivePeerDependencies: - supports-color - react-markdown@7.1.2(@types/react@18.2.0)(react@18.2.0): - dependencies: - '@types/hast': 2.3.10 - '@types/react': 18.2.0 - '@types/unist': 2.0.11 - comma-separated-tokens: 2.0.3 - hast-util-whitespace: 2.0.1 - prop-types: 15.8.1 - property-information: 6.5.0 - react: 18.2.0 - react-is: 17.0.2 - remark-parse: 10.0.2 - remark-rehype: 9.1.0 - space-separated-tokens: 2.0.2 - style-to-object: 0.3.0 - unified: 10.1.2 - unist-util-visit: 4.1.2 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - react-monaco-editor@0.58.0(monaco-editor@0.52.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: monaco-editor: 0.52.2 @@ -44333,14 +44023,6 @@ snapshots: transitivePeerDependencies: - supports-color - remark-parse@10.0.2: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-from-markdown: 1.3.1 - unified: 10.1.2 - transitivePeerDependencies: - - supports-color - remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 @@ -44377,13 +44059,6 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 - remark-rehype@9.1.0: - dependencies: - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-to-hast: 11.3.0 - unified: 10.1.2 - remark-slug@6.1.0: dependencies: github-slugger: 1.5.0 @@ -46987,14 +46662,8 @@ snapshots: unist-builder@2.0.3: {} - unist-builder@3.0.1: - dependencies: - '@types/unist': 2.0.11 - unist-util-generated@1.1.6: {} - unist-util-generated@2.0.1: {} - unist-util-is@4.1.0: {} unist-util-is@5.2.1: @@ -47023,10 +46692,6 @@ snapshots: dependencies: unist-util-is: 4.1.0 - unist-util-stringify-position@3.0.3: - dependencies: - '@types/unist': 2.0.11 - unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.3 @@ -47274,13 +46939,6 @@ snapshots: uuid@9.0.1: {} - uvu@0.5.6: - dependencies: - dequal: 2.0.3 - diff: 5.2.0 - kleur: 4.1.5 - sade: 1.8.1 - v8-compile-cache@2.4.0: {} v8-to-istanbul@4.1.4: diff --git a/workspaces/wso2-platform/wso2-platform-webviews/package.json b/workspaces/wso2-platform/wso2-platform-webviews/package.json index d82fb50ff8a..286e81b4b41 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/package.json +++ b/workspaces/wso2-platform/wso2-platform-webviews/package.json @@ -34,9 +34,9 @@ "swagger-ui-react": "5.22.0", "@biomejs/biome": "^1.9.4", "@headlessui/react": "^2.1.2", - "react-markdown": "^7.1.0", - "rehype-raw": "^6.1.0", - "remark-gfm": "^4.0.1", + "react-markdown": "10.1.0", + "rehype-raw": "7.0.0", + "remark-gfm": "4.0.1", "prism-react-renderer": "^2.4.1", "lodash.debounce": "~4.0.8", "js-yaml": "^4.1.0" diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/components/Markdown/Markdown.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/components/Markdown/Markdown.tsx index 6322c87fb83..1eeca98d067 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/components/Markdown/Markdown.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/components/Markdown/Markdown.tsx @@ -82,7 +82,8 @@ const markDownOverrides: { [key: string]: FC> } = {
{children}
), // TODO: move into separate component - code: ({ inline, children, className }) => { + code: ({ children, className, node }) => { + const isInline = !className && node?.position?.end?.line === node?.position?.start?.line // Extract language from className const match = /language-(\w+)/.exec(className || ""); const language: any = match != null ? match[1] : "markdown"; @@ -92,7 +93,7 @@ const markDownOverrides: { [key: string]: FC> } = { mutationFn: (text: string) => clipboardy.write(text), onSuccess: () => ChoreoWebViewAPI.getInstance().showInfoMsg("Code has been copied to the clipboard."), }); - if (inline) { + if (isInline) { return {children}; } @@ -216,7 +217,7 @@ const markDownOverrides: { [key: string]: FC> } = { export const Markdown: FC = ({ children }) => { return ( - + {children} ); From db79943d25157886bb827beb06d0f1f1d5b882e2 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Tue, 30 Sep 2025 13:36:57 +0530 Subject: [PATCH 16/41] bump zod package version in platform ext --- .../wso2-platform-extension/src/config.ts | 2 +- .../wso2-platform-webviews/package.json | 6 +++--- .../CreateConnection/CreateConnection.tsx | 2 +- .../sections/ApiTestSection.tsx | 2 +- .../sections/DeploymentsSection.tsx | 12 +++--------- .../views/ComponentFormView/ComponentFormView.tsx | 14 +++++++------- .../views/ComponentFormView/componentFormSchema.ts | 6 +++--- .../sections/ComponentFormBuildSection.tsx | 2 +- .../sections/ComponentFormEndpointsSection.tsx | 8 ++++---- .../sections/ComponentFormGenDetailsSection.tsx | 2 +- .../sections/ComponentFormGitProxySection.tsx | 2 +- .../sections/ComponentFormRepoInitSection.tsx | 2 +- .../sections/ComponentFormSummarySection.tsx | 2 +- 13 files changed, 28 insertions(+), 34 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/config.ts b/workspaces/wso2-platform/wso2-platform-extension/src/config.ts index 72c597d3e40..5e18a4d5ebc 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/config.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/config.ts @@ -17,7 +17,7 @@ */ import { window } from "vscode"; -import { z } from "zod"; +import { z } from "zod/v3"; import { ext } from "./extensionVariables"; const envSchemaItem = z.object({}); diff --git a/workspaces/wso2-platform/wso2-platform-webviews/package.json b/workspaces/wso2-platform/wso2-platform-webviews/package.json index 286e81b4b41..194a91da6c5 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/package.json +++ b/workspaces/wso2-platform/wso2-platform-webviews/package.json @@ -25,9 +25,9 @@ "classnames": "~2.5.1", "@tanstack/react-query-persist-client": "~4.28.0", "@tanstack/react-query": "~4.28.0", - "zod": "^3.22.4", - "react-hook-form": "7.56.4", - "@hookform/resolvers": "^5.0.1", + "zod": "4.1.11", + "react-hook-form": "7.63.0", + "@hookform/resolvers": "5.2.2", "clipboardy": "^4.0.0", "@formkit/auto-animate": "0.8.2", "timezone-support": "^3.1.0", diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/components/Connections/CreateConnection/CreateConnection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/components/Connections/CreateConnection/CreateConnection.tsx index 86b6437478c..e3be1465d65 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/components/Connections/CreateConnection/CreateConnection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/components/Connections/CreateConnection/CreateConnection.tsx @@ -35,7 +35,7 @@ import { } from "@wso2/wso2-platform-core"; import React, { useEffect, type FC } from "react"; import { type SubmitHandler, useForm } from "react-hook-form"; -import { z } from "zod"; +import { z } from "zod/v3"; import { queryKeys } from "../../../hooks/use-queries"; import { ChoreoWebViewAPI } from "../../../utilities/vscode-webview-rpc"; import { Button } from "../../Button"; diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/ApiTestSection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/ApiTestSection.tsx index 107df001ec0..450666ab8e7 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/ApiTestSection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/ApiTestSection.tsx @@ -23,7 +23,7 @@ import classNames from "classnames"; import clipboardy from "clipboardy"; import React, { useMemo, useState, type FC } from "react"; import { useForm } from "react-hook-form"; -import { z } from "zod"; +import { z } from "zod/v3"; import { Button } from "../../../components/Button"; import { Codicon } from "../../../components/Codicon"; import { Dropdown } from "../../../components/FormElements/Dropdown"; diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/DeploymentsSection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/DeploymentsSection.tsx index 4c42a0c67ef..72cdbe492fa 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/DeploymentsSection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentDetailsView/sections/DeploymentsSection.tsx @@ -16,21 +16,18 @@ * under the License. */ -import { type } from "node:os"; import { useAutoAnimate } from "@formkit/auto-animate/react"; import { zodResolver } from "@hookform/resolvers/zod"; -import { UseQueryResult, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { VSCodeDropdown, VSCodeLink, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; import { type BuildKind, type CheckWorkflowStatusResp, ChoreoComponentType, type ComponentDeployment, - ComponentDisplayType, type ComponentEP, type ComponentKind, type CreateDeploymentReq, - DeploymentLogsData, DeploymentStatus, type DeploymentTrack, EndpointDeploymentStatus, @@ -38,8 +35,6 @@ import { type Organization, type Project, type ProxyDeploymentInfo, - type StateReason, - type WebviewQuickPickItem, WebviewQuickPickItemKind, WorkflowInstanceStatus, capitalizeFirstLetter, @@ -50,12 +45,11 @@ import { toTitleCase, } from "@wso2/wso2-platform-core"; import classNames from "classnames"; -import classnames from "classnames"; import clipboardy from "clipboardy"; import React, { type FC, type ReactNode, useState, useEffect } from "react"; import { type SubmitHandler, useForm } from "react-hook-form"; import { listTimeZones } from "timezone-support"; -import { z } from "zod"; +import { z } from "zod/v3"; import { Banner } from "../../../components/Banner"; import { Button } from "../../../components/Button"; import { Codicon } from "../../../components/Codicon"; diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx index 373f03fd3c2..3b4111b7dff 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/ComponentFormView.tsx @@ -38,7 +38,7 @@ import { } from "@wso2/wso2-platform-core"; import React, { type FC, useState } from "react"; import { useForm } from "react-hook-form"; -import type { z } from "zod"; +import type { z } from "zod/v3"; import { HeaderSection } from "../../components/HeaderSection"; import type { HeaderTag } from "../../components/HeaderSection/HeaderSection"; import { type StepItem, VerticalStepper } from "../../components/VerticalStepper"; @@ -91,13 +91,13 @@ export const ComponentFormView: FC = (props) => { const { data: existingComponents = [] } = useComponentList(project, organization, { initialData: existingComponentsCache }); const genDetailsForm = useForm({ - resolver: zodResolver(getComponentFormSchemaGenDetails(existingComponents), { async: true }, { mode: "async" }), + resolver: zodResolver(getComponentFormSchemaGenDetails(existingComponents)), mode: "all", defaultValues: { name: initialValues?.name || "", subPath: "", gitRoot: "", repoUrl: "", branch: "", credential: "", gitProvider: "" }, }); const repoInitForm = useForm({ - resolver: zodResolver(getRepoInitSchemaGenDetails(existingComponents), { async: true }, { mode: "async" }), + resolver: zodResolver(getRepoInitSchemaGenDetails(existingComponents)), mode: "all", defaultValues: { org: "", @@ -115,7 +115,7 @@ export const ComponentFormView: FC = (props) => { const subPath = genDetailsForm.watch("subPath"); const buildDetailsForm = useForm({ - resolver: zodResolver(getComponentFormSchemaBuildDetails(type, directoryFsPath, gitRoot), { async: true }, { mode: "async" }), + resolver: zodResolver(getComponentFormSchemaBuildDetails(type, directoryFsPath, gitRoot)), mode: "all", defaultValues: { buildPackLang: initialValues?.buildPackLang ?? "", @@ -133,14 +133,14 @@ export const ComponentFormView: FC = (props) => { const useDefaultEndpoints = buildDetailsForm.watch("useDefaultEndpoints"); const buildPackLang = buildDetailsForm.watch("buildPackLang"); - const endpointDetailsForm = useForm({ - resolver: zodResolver(getComponentEndpointsFormSchema(directoryFsPath), { async: true }, { mode: "async" }), + const endpointDetailsForm = useForm({ + resolver: zodResolver(getComponentEndpointsFormSchema(directoryFsPath)), mode: "all", defaultValues: { endpoints: [] }, }); const gitProxyForm = useForm({ - resolver: zodResolver(getComponentGitProxyFormSchema(directoryFsPath), { async: true }, { mode: "async" }), + resolver: zodResolver(getComponentGitProxyFormSchema(directoryFsPath)), mode: "all", defaultValues: { proxyTargetUrl: "", diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/componentFormSchema.ts b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/componentFormSchema.ts index 2ad8863b618..87868c0ee0a 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/componentFormSchema.ts +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/componentFormSchema.ts @@ -33,7 +33,7 @@ import { parseGitURL, } from "@wso2/wso2-platform-core"; import * as yaml from "js-yaml"; -import { z } from "zod"; +import { z } from "zod/v3"; import { ChoreoWebViewAPI } from "../../utilities/vscode-webview-rpc"; export const componentRepoInitSchema = z.object({ @@ -45,7 +45,7 @@ export const componentRepoInitSchema = z.object({ name: z .string() .min(1, "Required") - .min(3, "Needs to be at least characters") + .min(3, "Needs to be at least 3 characters") .max(60, "Max length exceeded") .regex(/^[A-Za-z]/, "Needs to start with alphabetic letter") .regex(/^[A-Za-z\s\d\-_]+$/, "Cannot have special characters"), @@ -58,7 +58,7 @@ export const componentGeneralDetailsSchema = z.object({ name: z .string() .min(1, "Required") - .min(3, "Needs to be at least characters") + .min(3, "Needs to be at least 3 characters") .max(60, "Max length exceeded") .regex(/^[A-Za-z]/, "Needs to start with alphabetic letter") .regex(/^[A-Za-z\s\d\-_]+$/, "Cannot have special characters"), diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormBuildSection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormBuildSection.tsx index 5c313420ac1..be4d7ecb585 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormBuildSection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormBuildSection.tsx @@ -27,7 +27,7 @@ import { } from "@wso2/wso2-platform-core"; import React, { type FC, type ReactNode, useEffect } from "react"; import type { SubmitHandler, UseFormReturn } from "react-hook-form"; -import type { z } from "zod"; +import type { z } from "zod/v3"; import { Button } from "../../../components/Button"; import { CheckBox } from "../../../components/FormElements/CheckBox"; import { Dropdown } from "../../../components/FormElements/Dropdown"; diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormEndpointsSection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormEndpointsSection.tsx index 981b556edd6..bf2860f82da 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormEndpointsSection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormEndpointsSection.tsx @@ -17,12 +17,12 @@ */ import { useAutoAnimate } from "@formkit/auto-animate/react"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; import { EndpointType, type NewComponentWebviewProps } from "@wso2/wso2-platform-core"; import React, { type FC, type ReactNode } from "react"; import { type SubmitHandler, type UseFieldArrayAppend, type UseFieldArrayRemove, type UseFormReturn, useFieldArray, useWatch } from "react-hook-form"; -import type { z } from "zod"; +import type { z } from "zod/v3"; import { Button } from "../../../components/Button"; import { Codicon } from "../../../components/Codicon"; import { Divider } from "../../../components/Divider"; @@ -43,7 +43,7 @@ interface Props extends NewComponentWebviewProps { isSaving?: boolean; onNextClick: (data: ComponentFormEndpointsType) => void; onBackClick: () => void; - form: UseFormReturn; + form: UseFormReturn; } export const ComponentFormEndpointsSection: FC = ({ @@ -95,7 +95,7 @@ export const ComponentFormEndpointsSection: FC = ({ interface ComponentEndpointItemProps extends Pick { item: ComponentFormEndpointItemType & { id: string }; endpoints: ComponentFormEndpointItemType[]; - form: UseFormReturn; + form: UseFormReturn; index: number; append: UseFieldArrayAppend; remove: UseFieldArrayRemove; diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGenDetailsSection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGenDetailsSection.tsx index fb5a6b05aea..1d38c681b30 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGenDetailsSection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGenDetailsSection.tsx @@ -21,7 +21,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { GitProvider, type NewComponentWebviewProps, parseGitURL, toSentenceCase } from "@wso2/wso2-platform-core"; import React, { type FC, type ReactNode, useEffect } from "react"; import type { SubmitHandler, UseFormReturn } from "react-hook-form"; -import type { z } from "zod"; +import type { z } from "zod/v3"; import { Banner } from "../../../components/Banner"; import { Button } from "../../../components/Button"; import { Dropdown } from "../../../components/FormElements/Dropdown"; diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGitProxySection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGitProxySection.tsx index 874ac899195..09263f5b8b2 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGitProxySection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormGitProxySection.tsx @@ -22,7 +22,7 @@ import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; import type { NewComponentWebviewProps } from "@wso2/wso2-platform-core"; import React, { type FC } from "react"; import type { SubmitHandler, UseFormReturn } from "react-hook-form"; -import type { z } from "zod"; +import type { z } from "zod/v3"; import { Button } from "../../../components/Button"; import { Dropdown } from "../../../components/FormElements/Dropdown"; import { PathSelect } from "../../../components/FormElements/PathSelect"; diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx index 808fa12817c..880c82ec998 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx @@ -23,7 +23,7 @@ import { GitProvider, type NewComponentWebviewProps, buildGitURL } from "@wso2/w import debounce from "lodash.debounce"; import React, { type FC, useCallback, useEffect, useState } from "react"; import type { SubmitHandler, UseFormReturn } from "react-hook-form"; -import type { z } from "zod"; +import type { z } from "zod/v3"; import { Banner } from "../../../components/Banner"; import { Button } from "../../../components/Button"; import { Dropdown } from "../../../components/FormElements/Dropdown"; diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormSummarySection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormSummarySection.tsx index bc1bfeb5ef4..40ba4391f9e 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormSummarySection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormSummarySection.tsx @@ -30,7 +30,7 @@ import { import classNames from "classnames"; import React, { type HTMLProps, type FC, type ReactNode, useMemo } from "react"; import type { UseFormReturn } from "react-hook-form"; -import type { z } from "zod"; +import type { z } from "zod/v3"; import { Banner } from "../../../components/Banner"; import { Button } from "../../../components/Button"; import { queryKeys } from "../../../hooks/use-queries"; From 752296015b3b96868d8f3dbcbb0f20353fd17019 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Thu, 2 Oct 2025 10:51:57 +0530 Subject: [PATCH 17/41] update pnpm lock file --- common/config/rush/pnpm-lock.yaml | 86 ++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 23 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index f1de2ccea94..858408d7ea9 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -827,7 +827,7 @@ importers: devDependencies: '@ai-sdk/openai': specifier: ^1.3.22 - version: 1.3.23(zod@3.25.76) + version: 1.3.23(zod@4.1.11) '@types/lodash.debounce': specifier: ^4.0.6 version: 4.0.9 @@ -860,7 +860,7 @@ importers: version: 8.33.1(eslint@9.27.0(jiti@2.5.1))(typescript@5.8.3) ai: specifier: ^4.3.16 - version: 4.3.19(react@18.2.0)(zod@3.25.76) + version: 4.3.19(react@18.2.0)(zod@4.1.11) buffer: specifier: ^6.0.3 version: 6.0.3 @@ -3764,8 +3764,8 @@ importers: specifier: ^2.1.2 version: 2.2.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@hookform/resolvers': - specifier: ^5.0.1 - version: 5.0.1(react-hook-form@7.56.4(react@18.2.0)) + specifier: 5.2.2 + version: 5.2.2(react-hook-form@7.63.0(react@18.2.0)) '@tanstack/react-query': specifier: ~4.28.0 version: 4.28.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -3803,8 +3803,8 @@ importers: specifier: 18.2.0 version: 18.2.0(react@18.2.0) react-hook-form: - specifier: 7.56.4 - version: 7.56.4(react@18.2.0) + specifier: 7.63.0 + version: 7.63.0(react@18.2.0) react-markdown: specifier: 10.1.0 version: 10.1.0(@types/react@18.2.0)(react@18.2.0) @@ -3827,8 +3827,8 @@ importers: specifier: ^0.5.1 version: 0.5.1 zod: - specifier: ^3.22.4 - version: 3.25.76 + specifier: 4.1.11 + version: 4.1.11 devDependencies: '@types/js-yaml': specifier: ^4.0.5 @@ -5433,6 +5433,11 @@ packages: peerDependencies: react-hook-form: ^7.55.0 + '@hookform/resolvers@5.2.2': + resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} + peerDependencies: + react-hook-form: ^7.55.0 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -17845,6 +17850,12 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-hook-form@7.63.0: + resolution: {integrity: sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-hot-loader@4.13.1: resolution: {integrity: sha512-ZlqCfVRqDJmMXTulUGic4lN7Ic1SXgHAFw7y/Jb7t25GBgTR0fYAJ8uY4mrpxjRyWGWmqw77qJQGnYbzCvBU7g==} engines: {node: '>= 6'} @@ -21338,6 +21349,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.1.11: + resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==} + zustand@5.0.6: resolution: {integrity: sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==} engines: {node: '>=12.20.0'} @@ -21381,11 +21395,11 @@ snapshots: '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) zod: 3.25.76 - '@ai-sdk/openai@1.3.23(zod@3.25.76)': + '@ai-sdk/openai@1.3.23(zod@4.1.11)': dependencies: '@ai-sdk/provider': 1.1.3 - '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) - zod: 3.25.76 + '@ai-sdk/provider-utils': 2.2.8(zod@4.1.11) + zod: 4.1.11 '@ai-sdk/provider-utils@2.2.8(zod@3.25.76)': dependencies: @@ -21394,19 +21408,26 @@ snapshots: secure-json-parse: 2.7.0 zod: 3.25.76 + '@ai-sdk/provider-utils@2.2.8(zod@4.1.11)': + dependencies: + '@ai-sdk/provider': 1.1.3 + nanoid: 3.3.11 + secure-json-parse: 2.7.0 + zod: 4.1.11 + '@ai-sdk/provider@1.1.3': dependencies: json-schema: 0.4.0 - '@ai-sdk/react@1.2.12(react@18.2.0)(zod@3.25.76)': + '@ai-sdk/react@1.2.12(react@18.2.0)(zod@4.1.11)': dependencies: - '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) - '@ai-sdk/ui-utils': 1.2.11(zod@3.25.76) + '@ai-sdk/provider-utils': 2.2.8(zod@4.1.11) + '@ai-sdk/ui-utils': 1.2.11(zod@4.1.11) react: 18.2.0 swr: 2.3.6(react@18.2.0) throttleit: 2.1.0 optionalDependencies: - zod: 3.25.76 + zod: 4.1.11 '@ai-sdk/react@1.2.12(react@19.1.0)(zod@3.25.76)': dependencies: @@ -21425,6 +21446,13 @@ snapshots: zod: 3.25.76 zod-to-json-schema: 3.24.6(zod@3.25.76) + '@ai-sdk/ui-utils@1.2.11(zod@4.1.11)': + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@4.1.11) + zod: 4.1.11 + zod-to-json-schema: 3.24.6(zod@4.1.11) + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -23620,6 +23648,11 @@ snapshots: '@standard-schema/utils': 0.3.0 react-hook-form: 7.56.4(react@18.2.0) + '@hookform/resolvers@5.2.2(react-hook-form@7.63.0(react@18.2.0))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.63.0(react@18.2.0) + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -32690,15 +32723,15 @@ snapshots: clean-stack: 4.2.0 indent-string: 5.0.0 - ai@4.3.19(react@18.2.0)(zod@3.25.76): + ai@4.3.19(react@18.2.0)(zod@4.1.11): dependencies: '@ai-sdk/provider': 1.1.3 - '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) - '@ai-sdk/react': 1.2.12(react@18.2.0)(zod@3.25.76) - '@ai-sdk/ui-utils': 1.2.11(zod@3.25.76) + '@ai-sdk/provider-utils': 2.2.8(zod@4.1.11) + '@ai-sdk/react': 1.2.12(react@18.2.0)(zod@4.1.11) + '@ai-sdk/ui-utils': 1.2.11(zod@4.1.11) '@opentelemetry/api': 1.9.0 jsondiffpatch: 0.6.0 - zod: 3.25.76 + zod: 4.1.11 optionalDependencies: react: 18.2.0 @@ -39422,10 +39455,7 @@ snapshots: pretty-format: 25.5.0 throat: 5.0.0 transitivePeerDependencies: - - bufferutil - - canvas - supports-color - - utf-8-validate jest-leak-detector@25.5.0: dependencies: @@ -43328,6 +43358,10 @@ snapshots: dependencies: react: 18.2.0 + react-hook-form@7.63.0(react@18.2.0): + dependencies: + react: 18.2.0 + react-hot-loader@4.13.1(@types/react@18.2.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: fast-levenshtein: 2.0.6 @@ -48148,8 +48182,14 @@ snapshots: dependencies: zod: 3.25.76 + zod-to-json-schema@3.24.6(zod@4.1.11): + dependencies: + zod: 4.1.11 + zod@3.25.76: {} + zod@4.1.11: {} + zustand@5.0.6(@types/react@18.2.0)(react@18.2.0)(use-sync-external-store@1.5.0(react@18.2.0)): optionalDependencies: '@types/react': 18.2.0 From 5fa51c3d777830b0761fe4113edd5da124b8b290 Mon Sep 17 00:00:00 2001 From: Anjana Supun Date: Wed, 8 Oct 2025 11:25:41 +0530 Subject: [PATCH 18/41] Update devant token env variables --- .../ballerina/ballerina-extension/src/features/ai/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts index f76ca876363..1a8edd1001c 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts @@ -35,9 +35,9 @@ export const AUTH_ORG: string = config.get('authOrg') || process.env.BALLERINA_A export const AUTH_CLIENT_ID: string = config.get('authClientID') || process.env.BALLERINA_AUTH_CLIENT_ID; export const AUTH_REDIRECT_URL: string = config.get('authRedirectURL') || process.env.BALLERINA_AUTH_REDIRECT_URL; -export const DEVANT_API_KEY: string = config.get('devantApiKey') || process.env.DEVANT_API_KEY; -export const DEVANT_API_KEY_FOR_ASK: string = config.get('devantApiKeyForAsk') || process.env.DEVANT_API_KEY_FOR_ASK; -export const DEVANT_STS_TOKEN: string = config.get('cloudStsToken') || process.env.CLOUD_STS_TOKEN; +export const DEVANT_API_KEY: string = config.get('devantApiKey') || process.env.BI_INTELLIGENCE_COPILOT_TOKEN; +export const DEVANT_API_KEY_FOR_ASK: string = config.get('devantApiKeyForAsk') || process.env.ASK_COPILOT_TOKEN; +export const DEVANT_STS_TOKEN: string = config.get('cloudStsToken') || process.env.CLOUD_ENV; // This refers to old backend before FE Migration. We need to eventually remove this. export const OLD_BACKEND_URL: string = BACKEND_URL + "/v2.0"; From e7aac200ab0bcfb2a0ae3111dd3aec4a3cfde6dc Mon Sep 17 00:00:00 2001 From: Anjana Supun Date: Wed, 8 Oct 2025 11:43:48 +0530 Subject: [PATCH 19/41] Update sts token var --- .../ballerina/ballerina-extension/src/features/ai/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts index 1a8edd1001c..8ec2c00861b 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts @@ -37,7 +37,7 @@ export const AUTH_REDIRECT_URL: string = config.get('authRedirectURL') || proces export const DEVANT_API_KEY: string = config.get('devantApiKey') || process.env.BI_INTELLIGENCE_COPILOT_TOKEN; export const DEVANT_API_KEY_FOR_ASK: string = config.get('devantApiKeyForAsk') || process.env.ASK_COPILOT_TOKEN; -export const DEVANT_STS_TOKEN: string = config.get('cloudStsToken') || process.env.CLOUD_ENV; +export const DEVANT_STS_TOKEN: string = config.get('cloudStsToken') || process.env.CLOUD_STS_TOKEN; // This refers to old backend before FE Migration. We need to eventually remove this. export const OLD_BACKEND_URL: string = BACKEND_URL + "/v2.0"; From 53f1f903a289d372573bdbb2394e590f87f3d66e Mon Sep 17 00:00:00 2001 From: kaje94 Date: Wed, 8 Oct 2025 11:46:23 +0530 Subject: [PATCH 20/41] check for choreo ext before activating choreo features in platform ext --- .../src/choreo-rpc/connection.ts | 2 +- .../src/cmds/commit-and-push-to-git-cmd.ts | 2 +- .../src/cmds/create-component-cmd.ts | 6 +++--- .../wso2-platform-extension/src/devant-utils.ts | 6 ++---- .../wso2-platform-extension/src/extension.ts | 14 +++++++++----- .../src/extensionVariables.ts | 6 +++++- .../wso2-platform-extension/src/mcp.ts | 6 +++++- .../src/stores/context-store.ts | 4 ++-- 8 files changed, 28 insertions(+), 18 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/connection.ts b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/connection.ts index 02d6ba9e93e..104951d5e2d 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/connection.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/connection.ts @@ -37,7 +37,7 @@ export class StdioConnection { this._serverProcess = spawn(executablePath, ["start-rpc-server"], { env: { ...process.env, - SKIP_KEYRING: process.env.CLOUD_STS_TOKEN ? "true" : "", + SKIP_KEYRING: ext.isDevantCloudEditor ? "true" : "", CHOREO_ENV: ext.choreoEnv, CHOREO_REGION: region, }, diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts index e9a77620e43..dc0f7ca16f8 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/commit-and-push-to-git-cmd.ts @@ -195,7 +195,7 @@ export const enrichGitUsernamePassword = async ( fetchUrl: string, secretRef: string, ) => { - if (process.env.CLOUD_STS_TOKEN && provider === GitProvider.GITHUB && !urlObj.password) { + if (ext.isDevantCloudEditor && provider === GitProvider.GITHUB && !urlObj.password) { try { getLogger().debug(`Fetching PAT for org ${repoOrg} and repo ${repoName}`); const gitPat = await window.withProgress( diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts index b83f59dcefe..ed5d6337a67 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts @@ -245,7 +245,7 @@ export function createNewComponentCommand(context: ExtensionContext) { organization: selectedOrg!, project: selectedProject!, extensionName: webviewStateStore.getState().state.extensionName, - isNewCodeServerComp: isGitInitialized === false && !!process.env.CLOUD_STS_TOKEN, + isNewCodeServerComp: isGitInitialized === false && ext.isDevantCloudEditor, initialValues: { type: selectedType, subType: selectedSubType, @@ -331,7 +331,7 @@ export const submitCreateComponentHandler = async ({ createParams, org, project const dotGit = await newGit?.getRepositoryDotGit(createParams.componentDir); const projectCache = dataCacheStore.getState().getProjects(org.handle); if (newGit && gitRoot && dotGit) { - if (process.env.CLOUD_STS_TOKEN) { + if (ext.isDevantCloudEditor) { // update the code server, to attach itself to the created component const repo = newGit.open(gitRoot, dotGit); const head = await repo.getHEAD(); @@ -378,7 +378,7 @@ export const submitCreateComponentHandler = async ({ createParams, org, project const isWithinWorkspace = workspace.workspaceFolders?.some((item) => isSubpath(item.uri?.fsPath, createParams.componentDir)); - if (process.env.CLOUD_STS_TOKEN) { + if (ext.isDevantCloudEditor) { await ext.context.globalState.update("code-server-component-id", createdComponent.metadata?.id); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/devant-utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/devant-utils.ts index 196c3677926..48e63bf667e 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/devant-utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/devant-utils.ts @@ -22,10 +22,8 @@ import { initGit } from "./git/main"; import { getLogger } from "./logger/logger"; export const activateDevantFeatures = () => { - if (process.env.CLOUD_STS_TOKEN) { - autoRefetchDevantStsToken(); - showRepoSyncNotification(); - } + autoRefetchDevantStsToken(); + showRepoSyncNotification(); }; const autoRefetchDevantStsToken = () => { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts index c3d499cc65a..731ccb4d428 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts @@ -28,7 +28,7 @@ import { activateCodeLenses } from "./code-lens"; import { activateDevantFeatures } from "./devant-utils"; import { ext } from "./extensionVariables"; import { getLogger, initLogger } from "./logger/logger"; -import { activateMcp } from "./mcp"; +import { activateChoreoMcp } from "./mcp"; import { activateStatusbar } from "./status-bar"; import { authStore } from "./stores/auth-store"; import { contextStore } from "./stores/context-store"; @@ -79,10 +79,14 @@ export async function activate(context: vscode.ExtensionContext) { await ext.clients.rpcClient.init(); authStore.getState().initAuth(); continueCreateComponent(); - addTerminalHandlers(); - context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("*", new ChoreoConfigurationProvider())); - activateMcp(context); - activateDevantFeatures(); + if (ext.isChoreoExtInstalled) { + addTerminalHandlers(); + context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("*", new ChoreoConfigurationProvider())); + activateChoreoMcp(context); + } + if (ext.isDevantCloudEditor) { + activateDevantFeatures(); + } getLogger().debug("WSO2 Platform Extension activated"); ext.config = await ext.clients.rpcClient.getConfigFromCli(); }) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts b/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts index 6bdddd2c01c..ac693995485 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts @@ -17,7 +17,7 @@ */ import type { GetCliRpcResp } from "@wso2/wso2-platform-core"; -import type { ExtensionContext, StatusBarItem } from "vscode"; +import { type ExtensionContext, type StatusBarItem, extensions } from "vscode"; import type { PlatformExtensionApi } from "./PlatformExtensionApi"; import type { ChoreoRPCClient } from "./choreo-rpc"; @@ -28,9 +28,13 @@ export class ExtensionVariables { public statusBarItem!: StatusBarItem; public config?: GetCliRpcResp; public choreoEnv: string; + public isChoreoExtInstalled: boolean; + public isDevantCloudEditor: boolean; public constructor() { this.choreoEnv = "prod"; + this.isDevantCloudEditor = !!process.env.CLOUD_STS_TOKEN; + this.isChoreoExtInstalled = !!extensions.getExtension("wso2.choreo"); } public clients!: { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/mcp.ts b/workspaces/wso2-platform/wso2-platform-extension/src/mcp.ts index 3aaa7f02c6a..07713649ddf 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/mcp.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/mcp.ts @@ -21,7 +21,7 @@ import { getChoreoExecPath } from "./choreo-rpc/cli-install"; import { getUserInfoForCmd } from "./cmds/cmd-utils"; import { ext } from "./extensionVariables"; -export function activateMcp(context: vscode.ExtensionContext) { +export function activateChoreoMcp(context: vscode.ExtensionContext) { const didChangeEmitter = new vscode.EventEmitter(); context.subscriptions.push( vscode.lm.registerMcpServerDefinitionProvider("choreo", { @@ -40,11 +40,15 @@ export function activateMcp(context: vscode.ExtensionContext) { return servers; }, resolveMcpServerDefinition: async (def, _token) => { + return def; + // Uncomment below, if we want to ask user to login when MCP server is started + /* const userInfo = await getUserInfoForCmd("connect with Choreo MCP server"); if (userInfo) { return def; } return undefined; + */ }, }), ); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts index 8a01fb4ea16..2120c34ceb9 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts @@ -197,7 +197,7 @@ const getAllContexts = async (previousItems: { [key: string]: ContextItemEnriche }; const getSelected = async (items: { [key: string]: ContextItemEnriched }, prevSelected?: ContextItemEnriched) => { - if (process.env.CLOUD_STS_TOKEN && process.env.CLOUD_INITIAL_ORG_ID && process.env.CLOUD_INITIAL_PROJECT_ID) { + if (ext.isDevantCloudEditor && process.env.CLOUD_INITIAL_ORG_ID && process.env.CLOUD_INITIAL_PROJECT_ID) { // Give priority to project provided as env variable, when running in the cloud editor const userOrgs = authStore.getState().state.userInfo?.organizations; const matchingOrg = userOrgs?.find( @@ -340,7 +340,7 @@ const getComponentsInfo = async (selected?: ContextItemEnriched): Promise { const workspaceCompId: string | null | undefined = ext.context.workspaceState.get("code-server-component-id") || process.env.SOURCE_COMPONENT_ID; // - if (process.env.CLOUD_STS_TOKEN && process.env.CLOUD_INITIAL_ORG_ID && process.env.CLOUD_INITIAL_PROJECT_ID && workspaceCompId) { + if (ext.isDevantCloudEditor && process.env.CLOUD_INITIAL_ORG_ID && process.env.CLOUD_INITIAL_PROJECT_ID && workspaceCompId) { const filteredComps = components.filter((item) => item.metadata?.id === workspaceCompId); if (filteredComps.length === 1) { return filteredComps; From 13763c9c48ee6d16aceed7527b1ba296909ef4ad Mon Sep 17 00:00:00 2001 From: kaje94 Date: Tue, 14 Oct 2025 13:01:42 +0530 Subject: [PATCH 21/41] allow copilot to switch between dev & prod envs --- .../ballerina-extension/.env.example | 19 +++++++++++++++---- .../src/features/ai/utils.ts | 17 +++++++++-------- .../src/features/devant/activator.ts | 17 ++++++++++++++++- .../src/views/ai-panel/utils.ts | 10 ++++++---- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/.env.example b/workspaces/ballerina/ballerina-extension/.env.example index e48ddcef710..dfd30ccbeaa 100644 --- a/workspaces/ballerina/ballerina-extension/.env.example +++ b/workspaces/ballerina/ballerina-extension/.env.example @@ -1,4 +1,15 @@ -BALLERINA_ROOT_URL=https://dev-tools.wso2.com/ballerina-copilot -BALLERINA_AUTH_ORG= -BALLERINA_AUTH_CLIENT_ID= -BALLERINA_AUTH_REDIRECT_URL=https://eae690d5-80c3-4fb7-9bc5-e8d747cca11b.e1-us-east-azure.choreoapps.dev +# Prod Copilot +BALLERINA_DEFAULT_COPLIOT_ROOT_URL=https://dev-tools.wso2.com/ballerina-copilot +BALLERINA_DEFAULT_COPLIOT_AUTH_ORG= +BALLERINA_DEFAULT_COPLIOT_AUTH_CLIENT_ID= +BALLERINA_DEFAULT_COPLIOT_AUTH_REDIRECT_URL=https://eae690d5-80c3-4fb7-9bc5-e8d747cca11b.e1-us-east-azure.choreoapps.dev +BALLERINA_DEFAULT_COPLIOT_CODE_API_KEY= +BALLERINA_DEFAULT_COPLIOT_ASK_API_KEY= + +# Dev Copilot +BALLERINA_DEV_COPLIOT_ROOT_URL= +BALLERINA_DEV_COPLIOT_AUTH_ORG= +BALLERINA_DEV_COPLIOT_AUTH_CLIENT_ID= +BALLERINA_DEV_COPLIOT_AUTH_REDIRECT_URL= +BALLERINA_DEV_COPLIOT_CODE_API_KEY= +BALLERINA_DEV_COPLIOT_ASK_API_KEY= \ No newline at end of file diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts index 8ec2c00861b..a855f5a330b 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts @@ -30,14 +30,15 @@ import { BallerinaProject } from '@wso2/ballerina-core'; import { BallerinaExtension } from 'src/core'; const config = workspace.getConfiguration('ballerina'); -export const BACKEND_URL: string = config.get('rootUrl') || process.env.BALLERINA_ROOT_URL; -export const AUTH_ORG: string = config.get('authOrg') || process.env.BALLERINA_AUTH_ORG; -export const AUTH_CLIENT_ID: string = config.get('authClientID') || process.env.BALLERINA_AUTH_CLIENT_ID; -export const AUTH_REDIRECT_URL: string = config.get('authRedirectURL') || process.env.BALLERINA_AUTH_REDIRECT_URL; - -export const DEVANT_API_KEY: string = config.get('devantApiKey') || process.env.BI_INTELLIGENCE_COPILOT_TOKEN; -export const DEVANT_API_KEY_FOR_ASK: string = config.get('devantApiKeyForAsk') || process.env.ASK_COPILOT_TOKEN; -export const DEVANT_STS_TOKEN: string = config.get('cloudStsToken') || process.env.CLOUD_STS_TOKEN; +const isDevantDev = process.env.CLOUD_ENV === "dev"; +export const BACKEND_URL: string = config.get('rootUrl') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_ROOT_URL : process.env.BALLERINA_DEFAULT_COPLIOT_ROOT_URL; +export const AUTH_ORG: string = config.get('authOrg') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_AUTH_ORG : process.env.BALLERINA_DEFAULT_COPLIOT_AUTH_ORG; +export const AUTH_CLIENT_ID: string = config.get('authClientID') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_AUTH_CLIENT_ID : process.env.BALLERINA_DEFAULT_COPLIOT_AUTH_CLIENT_ID; +export const AUTH_REDIRECT_URL: string = config.get('authRedirectURL') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_AUTH_REDIRECT_URL : process.env.BALLERINA_DEFAULT_COPLIOT_AUTH_REDIRECT_URL; + +export const DEVANT_API_KEY: string = config.get('devantApiKey') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_CODE_API_KEY : process.env.BALLERINA_DEFAULT_COPLIOT_CODE_API_KEY; +export const DEVANT_API_KEY_FOR_ASK: string = config.get('devantApiKeyForAsk') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_ASK_API_KEY : process.env.BALLERINA_DEFAULT_COPLIOT_ASK_API_KEY; +export const DEVANT_STS_TOKEN_CONFIG: string = config.get('cloudStsToken'); // This refers to old backend before FE Migration. We need to eventually remove this. export const OLD_BACKEND_URL: string = BACKEND_URL + "/v2.0"; diff --git a/workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts index 3f733b47887..8aa3c529bd6 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts @@ -145,4 +145,19 @@ function isGitRepo(dir: string): boolean { return false; } - +// TODO: +// need to move all platform ext api calls to separate client. +// after that, delete this function +export const getDevantStsToken = async (): Promise => { + try { + const platformExt = extensions.getExtension("wso2.wso2-platform"); + if (!platformExt) { + return ""; + } + const platformExtAPI: IWso2PlatformExtensionAPI = await platformExt.activate(); + const stsToken = await platformExtAPI.getStsToken(); + return stsToken; + } catch (err) { + return ""; + } +}; \ No newline at end of file diff --git a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts index 57369367c40..eb4f2ed1d1b 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts @@ -24,8 +24,9 @@ import { generateText } from 'ai'; import { getAuthUrl, getLogoutUrl } from './auth'; import { extension } from '../../BalExtensionContext'; import { getAccessToken, clearAuthCredentials, storeAuthCredentials, getLoginMethod } from '../../utils/ai/auth'; -import { DEVANT_API_KEY, DEVANT_STS_TOKEN } from '../../features/ai/utils'; +import { DEVANT_API_KEY, DEVANT_STS_TOKEN_CONFIG } from '../../features/ai/utils'; import { getBedrockRegionalPrefix } from '../../features/ai/service/connection'; +import { getDevantStsToken } from '../../features/devant/activator'; const LEGACY_ACCESS_TOKEN_SECRET_KEY = 'BallerinaAIUser'; const LEGACY_REFRESH_TOKEN_SECRET_KEY = 'BallerinaAIRefreshToken'; @@ -133,9 +134,10 @@ export const validateApiKey = async (apiKey: string, loginMethod: LoginMethod): }; export const checkDevantEnvironment = async (): Promise => { + const devantStsToken = DEVANT_STS_TOKEN_CONFIG || await getDevantStsToken(); // Check if both required devant environment variables are present and non-empty - if (!DEVANT_API_KEY || !DEVANT_STS_TOKEN || - DEVANT_API_KEY.trim() === '' || DEVANT_STS_TOKEN.trim() === '') { + if (!DEVANT_API_KEY || !devantStsToken || + DEVANT_API_KEY.trim() === '' || devantStsToken.trim() === '') { return undefined; } @@ -144,7 +146,7 @@ export const checkDevantEnvironment = async (): Promise Date: Thu, 16 Oct 2025 12:54:09 +0530 Subject: [PATCH 22/41] update choreo connect to work on choreo property --- .../wso2-platform-extension/src/tarminal-handlers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts b/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts index dd0856dd0dd..4963fea8654 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts @@ -27,9 +27,9 @@ import { delay, getSubPath } from "./utils"; export class ChoreoConfigurationProvider implements vscode.DebugConfigurationProvider { resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration): vscode.DebugConfiguration | undefined { - if (config.request === "launch" && (config.choreo === true || typeof config.choreo === "object")) { + if (config.request === "launch" && (config.choreo === true || typeof config.choreo === "object" || config.choreoConnect === true || typeof config.choreoConnect === "object")) { config.console = "integratedTerminal"; - const choreoConfig: { project?: string; component?: string; env?: string; skipConnection?: string[] } | true = config.choreo; + const choreoConfig: { project?: string; component?: string; env?: string; skipConnection?: string[] } | true = config.choreo || config.choreoConnect; let connectCmd = "connect"; if (choreoConfig === true) { if (contextStore.getState().state?.selected?.projectHandle) { From ba0c20c8f71b947cb02bec8c786c85f114f3f1ca Mon Sep 17 00:00:00 2001 From: kaje94 Date: Thu, 16 Oct 2025 15:38:38 +0530 Subject: [PATCH 23/41] fix build failure in bal ext --- workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts b/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts index 62fb18fb8e3..8362212b370 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts @@ -178,7 +178,7 @@ export const getAccessToken = async (): Promise => // Priority 2: Check stored credentials if (process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.trim() !== "") { - resolve(process.env.ANTHROPIC_API_KEY.trim()); + resolve({loginMethod: LoginMethod.ANTHROPIC_KEY, secrets: {apiKey: process.env.ANTHROPIC_API_KEY.trim()}}); return; } const credentials = await getAuthCredentials(); From 7018a64f074b773a429181d2edd4ce00cf14cbd7 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Thu, 16 Oct 2025 18:49:39 +0530 Subject: [PATCH 24/41] update version of platform extension --- workspaces/wso2-platform/wso2-platform-extension/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/package.json b/workspaces/wso2-platform/wso2-platform-extension/package.json index 05b4d246611..13cee1cf381 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/package.json +++ b/workspaces/wso2-platform/wso2-platform-extension/package.json @@ -3,7 +3,7 @@ "displayName": "WSO2 Platform", "description": "Manage WSO2 Choreo and Devant projects in VS Code.", "license": "Apache-2.0", - "version": "1.0.13", + "version": "1.0.14", "cliVersion": "v1.2.212509091800", "publisher": "wso2", "bugs": { From 018f859531c660e0b5589e811f2b32f24dd62504 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Thu, 16 Oct 2025 19:16:33 +0530 Subject: [PATCH 25/41] update choreo ext version number --- workspaces/choreo/choreo-extension/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/choreo/choreo-extension/package.json b/workspaces/choreo/choreo-extension/package.json index 838e5fa41dd..668a9ed8b00 100644 --- a/workspaces/choreo/choreo-extension/package.json +++ b/workspaces/choreo/choreo-extension/package.json @@ -3,7 +3,7 @@ "displayName": "Choreo", "description": "An extension for managing your Choreo projects and components", "license": "Apache-2.0", - "version": "2.2.6", + "version": "2.2.7", "publisher": "wso2", "bugs": { "url": "https://github.com/wso2/choreo-vscode/issues" From 1c55642cc4a1583e202fe8cb9e12b913ab27ba0a Mon Sep 17 00:00:00 2001 From: kaje94 Date: Fri, 17 Oct 2025 11:01:59 +0530 Subject: [PATCH 26/41] fix error thrown when creating connection --- .../wso2-platform/wso2-platform-extension/package.json | 2 +- .../src/components/BreadCrumb/BreadCrumb.tsx | 6 +++--- .../Connections/CreateConnection/CreateConnection.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/package.json b/workspaces/wso2-platform/wso2-platform-extension/package.json index 13cee1cf381..3a0b1b65de7 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/package.json +++ b/workspaces/wso2-platform/wso2-platform-extension/package.json @@ -3,7 +3,7 @@ "displayName": "WSO2 Platform", "description": "Manage WSO2 Choreo and Devant projects in VS Code.", "license": "Apache-2.0", - "version": "1.0.14", + "version": "1.0.15", "cliVersion": "v1.2.212509091800", "publisher": "wso2", "bugs": { diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/components/BreadCrumb/BreadCrumb.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/components/BreadCrumb/BreadCrumb.tsx index f261ed1e39e..374ceb6a036 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/components/BreadCrumb/BreadCrumb.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/components/BreadCrumb/BreadCrumb.tsx @@ -33,16 +33,16 @@ export const BreadCrumb: FC = ({ items }) => { for (const [index, item] of items.entries()) { if (item.onClick) { nodes.push( - + {item.label} , ); } else { - nodes.push({item.label}); + nodes.push({item.label}); } if (index + 1 < items.length) { - nodes.push(/); + nodes.push(/); } } return
{nodes}
; diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/components/Connections/CreateConnection/CreateConnection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/components/Connections/CreateConnection/CreateConnection.tsx index e3be1465d65..bd4a125d7e8 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/components/Connections/CreateConnection/CreateConnection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/components/Connections/CreateConnection/CreateConnection.tsx @@ -90,7 +90,7 @@ const getInitialVisibility = (visibilities: string[] = []) => { if (visibilities.includes(ServiceInfoVisibilityEnum.Organization)) { return ServiceInfoVisibilityEnum.Organization; } - return; + return ""; }; const getPossibleSchemas = (selectedVisibility: string, connectionSchemas: MarketplaceItemSchema[] = []) => { From 4ac567df1ddd58a1e34e2f2be99ae50e0242179f Mon Sep 17 00:00:00 2001 From: Anjana Supun Date: Fri, 24 Oct 2025 11:48:38 +0530 Subject: [PATCH 27/41] Fix devant chat thread persistance --- .../ballerina-extension/src/features/ai/utils.ts | 8 ++++---- .../src/rpc-managers/ai-panel/rpc-manager.ts | 11 +++++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts index a855f5a330b..0eb5c52f34d 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts @@ -31,10 +31,10 @@ import { BallerinaExtension } from 'src/core'; const config = workspace.getConfiguration('ballerina'); const isDevantDev = process.env.CLOUD_ENV === "dev"; -export const BACKEND_URL: string = config.get('rootUrl') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_ROOT_URL : process.env.BALLERINA_DEFAULT_COPLIOT_ROOT_URL; -export const AUTH_ORG: string = config.get('authOrg') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_AUTH_ORG : process.env.BALLERINA_DEFAULT_COPLIOT_AUTH_ORG; -export const AUTH_CLIENT_ID: string = config.get('authClientID') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_AUTH_CLIENT_ID : process.env.BALLERINA_DEFAULT_COPLIOT_AUTH_CLIENT_ID; -export const AUTH_REDIRECT_URL: string = config.get('authRedirectURL') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_AUTH_REDIRECT_URL : process.env.BALLERINA_DEFAULT_COPLIOT_AUTH_REDIRECT_URL; +export const BACKEND_URL: string = config.get('rootUrl') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_ROOT_URL : process.env.BALLERINA_ROOT_URL; +export const AUTH_ORG: string = config.get('authOrg') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_AUTH_ORG : process.env.BALLERINA_AUTH_ORG; +export const AUTH_CLIENT_ID: string = config.get('authClientID') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_AUTH_CLIENT_ID : process.env.BALLERINA_AUTH_CLIENT_ID; +export const AUTH_REDIRECT_URL: string = config.get('authRedirectURL') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_AUTH_REDIRECT_URL : process.env.BALLERINA_AUTH_REDIRECT_URL; export const DEVANT_API_KEY: string = config.get('devantApiKey') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_CODE_API_KEY : process.env.BALLERINA_DEFAULT_COPLIOT_CODE_API_KEY; export const DEVANT_API_KEY_FOR_ASK: string = config.get('devantApiKeyForAsk') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_ASK_API_KEY : process.env.BALLERINA_DEFAULT_COPLIOT_ASK_API_KEY; diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts index b3089dfa11c..dc641a695a0 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts @@ -141,10 +141,17 @@ export class AiPanelRpcManager implements AIPanelAPI { } try { - const workspaceFolderPath = workspace.workspaceFolders[0].uri.fsPath; + let projectIdentifier: string; + const cloudProjectId = process.env.CLOUD_INITIAL_PROJECT_ID; + + if (cloudProjectId) { + projectIdentifier = cloudProjectId; + } else { + projectIdentifier = workspace.workspaceFolders[0].uri.fsPath; + } const hash = crypto.createHash('sha256') - .update(workspaceFolderPath) + .update(projectIdentifier) .digest('hex'); resolve(hash); From 9cf944aedf789c1b8bdbc24bb9e257a7573a4ac8 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Fri, 24 Oct 2025 12:26:29 +0530 Subject: [PATCH 28/41] update env.example file --- workspaces/ballerina/ballerina-extension/.env.example | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/.env.example b/workspaces/ballerina/ballerina-extension/.env.example index dfd30ccbeaa..a216162b012 100644 --- a/workspaces/ballerina/ballerina-extension/.env.example +++ b/workspaces/ballerina/ballerina-extension/.env.example @@ -1,8 +1,8 @@ # Prod Copilot -BALLERINA_DEFAULT_COPLIOT_ROOT_URL=https://dev-tools.wso2.com/ballerina-copilot -BALLERINA_DEFAULT_COPLIOT_AUTH_ORG= -BALLERINA_DEFAULT_COPLIOT_AUTH_CLIENT_ID= -BALLERINA_DEFAULT_COPLIOT_AUTH_REDIRECT_URL=https://eae690d5-80c3-4fb7-9bc5-e8d747cca11b.e1-us-east-azure.choreoapps.dev +BALLERINA_ROOT_URL=https://dev-tools.wso2.com/ballerina-copilot +BALLERINA_AUTH_ORG= +BALLERINA_AUTH_CLIENT_ID= +BALLERINA_AUTH_REDIRECT_URL=https://eae690d5-80c3-4fb7-9bc5-e8d747cca11b.e1-us-east-azure.choreoapps.dev BALLERINA_DEFAULT_COPLIOT_CODE_API_KEY= BALLERINA_DEFAULT_COPLIOT_ASK_API_KEY= From 67b99a7c7e355209f6ee4324a3b6b746209558ad Mon Sep 17 00:00:00 2001 From: gigara Date: Tue, 4 Nov 2025 11:43:35 +0530 Subject: [PATCH 29/41] Update fonts --- workspaces/ballerina/ballerina-extension/package.json | 8 ++++---- workspaces/choreo/choreo-extension/package.json | 2 +- workspaces/mi/mi-extension/package.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/package.json b/workspaces/ballerina/ballerina-extension/package.json index 47e922dae0b..a52662d3a3a 100644 --- a/workspaces/ballerina/ballerina-extension/package.json +++ b/workspaces/ballerina/ballerina-extension/package.json @@ -980,21 +980,21 @@ "description": "design-view", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f199" + "fontCharacter": "\\f19a" } }, "distro-start": { "description": "start", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f20f" + "fontCharacter": "\\f210" } }, "distro-debug": { "description": "debug", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f194" + "fontCharacter": "\\f195" } }, "distro-source-view": { @@ -1008,7 +1008,7 @@ "description": "persist-diagram", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f1ea" + "fontCharacter": "\\f1eb" } }, "distro-cached-rounded": { diff --git a/workspaces/choreo/choreo-extension/package.json b/workspaces/choreo/choreo-extension/package.json index f75cd102920..02ccafc5137 100644 --- a/workspaces/choreo/choreo-extension/package.json +++ b/workspaces/choreo/choreo-extension/package.json @@ -157,7 +157,7 @@ "description": "choreo-2", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f17f" + "fontCharacter": "\\f180" } } } diff --git a/workspaces/mi/mi-extension/package.json b/workspaces/mi/mi-extension/package.json index 741a3a18504..c211615f305 100644 --- a/workspaces/mi/mi-extension/package.json +++ b/workspaces/mi/mi-extension/package.json @@ -932,14 +932,14 @@ "description": "design-view", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f199" + "fontCharacter": "\\f19a" } }, "distro-build-package": { "description": "build-package", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f174" + "fontCharacter": "\\f175" } } } From af25cea27d6fc4da350b8f194147211b5a1a0d77 Mon Sep 17 00:00:00 2001 From: Anjana Supun Date: Wed, 5 Nov 2025 13:28:30 +0530 Subject: [PATCH 30/41] Migrate Copilot auth for devant --- .../ballerina-core/src/state-machine-types.ts | 4 +- .../src/features/ai/service/ask/ask.ts | 2 +- .../src/features/ai/service/connection.ts | 67 +++++++++++------- .../src/features/ai/utils.ts | 41 +++++++++-- .../ballerina-extension/src/utils/ai/auth.ts | 68 ++++++++++++++++++- .../src/views/ai-panel/utils.ts | 55 +++++++++++---- 6 files changed, 187 insertions(+), 50 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/state-machine-types.ts b/workspaces/ballerina/ballerina-core/src/state-machine-types.ts index 59e07d3268e..638788c98e9 100644 --- a/workspaces/ballerina/ballerina-core/src/state-machine-types.ts +++ b/workspaces/ballerina/ballerina-core/src/state-machine-types.ts @@ -358,8 +358,8 @@ export interface AnthropicKeySecrets { } export interface DevantEnvSecrets { - apiKey: string; - stsToken: string; + accessToken: string; + expiresAt: number; } interface AwsBedrockSecrets { diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/ask/ask.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/ask/ask.ts index 7ed350163a7..4e5f58413c0 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/ask/ask.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/ask/ask.ts @@ -103,7 +103,7 @@ async function fetchDocumentationFromVectorStore(query: string): Promise */ -export async function fetchWithAuth(input: string | URL | Request, options: RequestInit = {}, isAskRequest: boolean = false): Promise { +export async function fetchWithAuth(input: string | URL | Request, options: RequestInit = {}): Promise { try { const credentials = await getAccessToken(); const loginMethod = credentials.loginMethod; @@ -74,17 +73,12 @@ export async function fetchWithAuth(input: string | URL | Request, options: Requ }; if (credentials && loginMethod === LoginMethod.DEVANT_ENV) { - // For DEVANT_ENV, use api-key and x-Authorization headers + // For DEVANT_ENV, use Bearer token (exchanged from STS token) const secrets = credentials.secrets as DevantEnvSecrets; - // TEMPORARY HACK: Use DEVANT_API_KEY_FOR_ASK env variable for ask requests - const apiKey = isAskRequest ? DEVANT_API_KEY_FOR_ASK : secrets.apiKey; - const stsToken = secrets.stsToken; - - if (apiKey && stsToken && apiKey.trim() !== "" && stsToken.trim() !== "") { - headers["api-key"] = apiKey; - headers["x-Authorization"] = stsToken; + if (secrets.accessToken && secrets.accessToken.trim() !== "") { + headers["Authorization"] = `Bearer ${secrets.accessToken}`; } else { - console.warn("DevantEnv secrets missing, this may cause authentication issues"); + console.warn("DevantEnv access token missing, this may cause authentication issues"); } } else if (credentials && loginMethod === LoginMethod.BI_INTEL) { // For BI_INTEL, use Bearer token @@ -101,19 +95,40 @@ export async function fetchWithAuth(input: string | URL | Request, options: Requ let response = await fetch(input, options); console.log("Response status: ", response.status); - // Handle token expiration (only for BI_INTEL method) - if (response.status === 401 && loginMethod === LoginMethod.BI_INTEL) { - console.log("Token expired. Refreshing token..."); - const newToken = await getRefreshedAccessToken(); - if (newToken) { - options.headers = { - ...options.headers, - 'Authorization': `Bearer ${newToken}`, - }; - response = await fetch(input, options); - } else { - AIStateMachine.service().send(AIMachineEventType.LOGOUT); - return; + // Handle token expiration for both BI_INTEL and DEVANT_ENV methods + if (response.status === 401) { + if (loginMethod === LoginMethod.BI_INTEL) { + console.log("Token expired. Refreshing BI_INTEL token..."); + const newToken = await getRefreshedAccessToken(); + if (newToken) { + options.headers = { + ...options.headers, + 'Authorization': `Bearer ${newToken}`, + }; + response = await fetch(input, options); + } else { + AIStateMachine.service().send(AIMachineEventType.LOGOUT); + return; + } + } else if (loginMethod === LoginMethod.DEVANT_ENV) { + console.log("Token expired. Refreshing DEVANT_ENV token..."); + try { + const newToken = await refreshDevantToken(); + if (newToken) { + options.headers = { + ...options.headers, + 'Authorization': `Bearer ${newToken}`, + }; + response = await fetch(input, options); + } else { + AIStateMachine.service().send(AIMachineEventType.LOGOUT); + return; + } + } catch (error) { + console.error("Failed to refresh Devant token:", error); + AIStateMachine.service().send(AIMachineEventType.LOGOUT); + return; + } } } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts index 0eb5c52f34d..9fe381d222b 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts @@ -26,8 +26,9 @@ import { AIStateMachine } from '../../../src/views/ai-panel/aiMachine'; import { AIMachineEventType } from '@wso2/ballerina-core/lib/state-machine-types'; import { CONFIG_FILE_NAME, ERROR_NO_BALLERINA_SOURCES, PROGRESS_BAR_MESSAGE_FROM_WSO2_DEFAULT_MODEL } from './constants'; import { getCurrentBallerinaProjectFromContext } from '../config-generator/configGenerator'; -import { BallerinaProject } from '@wso2/ballerina-core'; +import { BallerinaProject, LoginMethod } from '@wso2/ballerina-core'; import { BallerinaExtension } from 'src/core'; +import { getAuthCredentials } from '../../utils/ai/auth'; const config = workspace.getConfiguration('ballerina'); const isDevantDev = process.env.CLOUD_ENV === "dev"; @@ -36,9 +37,19 @@ export const AUTH_ORG: string = config.get('authOrg') || isDevantDev ? process.e export const AUTH_CLIENT_ID: string = config.get('authClientID') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_AUTH_CLIENT_ID : process.env.BALLERINA_AUTH_CLIENT_ID; export const AUTH_REDIRECT_URL: string = config.get('authRedirectURL') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_AUTH_REDIRECT_URL : process.env.BALLERINA_AUTH_REDIRECT_URL; -export const DEVANT_API_KEY: string = config.get('devantApiKey') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_CODE_API_KEY : process.env.BALLERINA_DEFAULT_COPLIOT_CODE_API_KEY; -export const DEVANT_API_KEY_FOR_ASK: string = config.get('devantApiKeyForAsk') || isDevantDev ? process.env.BALLERINA_DEV_COPLIOT_ASK_API_KEY : process.env.BALLERINA_DEFAULT_COPLIOT_ASK_API_KEY; -export const DEVANT_STS_TOKEN_CONFIG: string = config.get('cloudStsToken'); +export const DEVANT_STS_TOKEN_CONFIG: string = config.get('cloudStsToken') || process.env.CLOUD_STS_TOKEN; + +//TODO: Move to configs after custom URL approved +const DEVANT_DEV_EXCHANGE_URL = 'https://e95488c8-8511-4882-967f-ec3ae2a0f86f-dev.e1-us-east-azure.choreoapis.dev/ballerina-copilot/devant-token-exchange-ser/v1.0/exchange'; +const DEVANT_PROD_EXCHANGE_URL = 'https://e95488c8-8511-4882-967f-ec3ae2a0f86f-prod.e1-us-east-azure.choreoapis.dev/ballerina-copilot/devant-token-exchange-ser/v1.0/exchange'; + +export function getDevantExchangeUrl(): string { + if (isDevantDev) { + return DEVANT_DEV_EXCHANGE_URL; + } else { + return DEVANT_PROD_EXCHANGE_URL; + } +} // This refers to old backend before FE Migration. We need to eventually remove this. export const OLD_BACKEND_URL: string = BACKEND_URL + "/v2.0"; @@ -135,8 +146,26 @@ export async function getConfigFilePath(ballerinaExtInstance: BallerinaExtension export async function getTokenForDefaultModel() { try { - const token = await getRefreshedAccessToken(); - return token; + const credentials = await getAuthCredentials(); + + if (!credentials) { + throw new Error('No authentication credentials found.'); + } + + // Check login method and handle accordingly + if (credentials.loginMethod === LoginMethod.BI_INTEL) { + // Keep existing behavior for BI Intel - refresh token + const token = await getRefreshedAccessToken(); + return token; + } else if (credentials.loginMethod === LoginMethod.DEVANT_ENV) { + // For Devant, return stored access token + return credentials.secrets.accessToken; + } else { + // For anything else, show error + const errorMessage = 'This feature is only available for BI Intelligence users.'; + vscode.window.showErrorMessage(errorMessage); + throw new Error(errorMessage); + } } catch (error) { throw error; } diff --git a/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts b/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts index 8362212b370..ae521dc1807 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/ai/auth.ts @@ -18,11 +18,12 @@ import * as vscode from 'vscode'; import { extension } from "../../BalExtensionContext"; -import { AUTH_CLIENT_ID, AUTH_ORG } from '../../features/ai/utils'; +import { AUTH_CLIENT_ID, AUTH_ORG, getDevantExchangeUrl } from '../../features/ai/utils'; import axios from 'axios'; import { jwtDecode, JwtPayload } from 'jwt-decode'; -import { AuthCredentials, LoginMethod } from '@wso2/ballerina-core'; +import { AuthCredentials, DevantEnvSecrets, LoginMethod } from '@wso2/ballerina-core'; import { checkDevantEnvironment } from '../../views/ai-panel/utils'; +import { getDevantStsToken } from '../../features/devant/activator'; export const REFRESH_TOKEN_NOT_AVAILABLE_ERROR_MESSAGE = "Refresh token is not available."; export const TOKEN_REFRESH_ONLY_SUPPORTED_FOR_BI_INTEL = "Token refresh is only supported for BI Intelligence authentication"; @@ -302,3 +303,66 @@ export const getRefreshedAccessToken = async (): Promise => { } }); }; + +// ================================== +// Devant STS Token Exchange Utils +// ================================== + +/** + * Exchanges a Choreo STS token for a Devant Bearer token + * @param choreoStsToken The Choreo STS token to exchange + * @returns DevantEnvSecrets containing the access token and calculated expiry time + */ +export const exchangeStsToken = async (choreoStsToken: string): Promise => { + try { + const response = await axios.post(getDevantExchangeUrl(), { + choreo_sts_token: choreoStsToken + }, { + headers: { + 'Content-Type': 'application/json' + } + }); + + const { access_token, expires_in } = response.data; + const devantEnv: DevantEnvSecrets = { + accessToken: access_token, + expiresAt: Date.now() + (expires_in * 1000) // Convert seconds to milliseconds + }; + + await storeAuthCredentials({ + loginMethod: LoginMethod.DEVANT_ENV, + secrets: devantEnv + }); + return devantEnv; + } catch (error: any) { + console.error('Error exchanging STS token:', error); + throw new Error(`Failed to exchange STS token: ${error.message}`); + } +}; + +/** + * Refreshes the Devant token by fetching a new STS token and exchanging it + * This is called when a 401 error occurs during DEVANT_ENV authentication + * @returns The new access token + */ +export const refreshDevantToken = async (): Promise => { + try { + // Get fresh STS token from platform extension + const newStsToken = await getDevantStsToken(); + + if (!newStsToken) { + throw new Error('Failed to retrieve STS token from platform extension'); + } + + // Exchange for new Bearer token + const newSecrets = await exchangeStsToken(newStsToken); + + // Update stored credentials (this is in-memory only for DEVANT_ENV) + // Note: checkDevantEnvironment already handles the storage, so we just return the token + + return newSecrets.accessToken; + } catch (error: any) { + console.error('Error refreshing Devant token:', error); + throw error; + } +}; diff --git a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts index dfb87b2deb3..8033adafaca 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/utils.ts @@ -23,8 +23,8 @@ import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'; import { generateText } from 'ai'; import { getAuthUrl, getLogoutUrl } from './auth'; import { extension } from '../../BalExtensionContext'; -import { getAccessToken, clearAuthCredentials, storeAuthCredentials, getLoginMethod } from '../../utils/ai/auth'; -import { DEVANT_API_KEY, DEVANT_STS_TOKEN_CONFIG } from '../../features/ai/utils'; +import { getAccessToken, clearAuthCredentials, storeAuthCredentials, getLoginMethod, exchangeStsToken, getAuthCredentials } from '../../utils/ai/auth'; +import { DEVANT_STS_TOKEN_CONFIG } from '../../features/ai/utils'; import { getBedrockRegionalPrefix } from '../../features/ai/service/connection'; import { getDevantStsToken } from '../../features/devant/activator'; @@ -134,21 +134,50 @@ export const validateApiKey = async (apiKey: string, loginMethod: LoginMethod): }; export const checkDevantEnvironment = async (): Promise => { - const devantStsToken = DEVANT_STS_TOKEN_CONFIG || await getDevantStsToken(); - // Check if both required devant environment variables are present and non-empty - if (!DEVANT_API_KEY || !devantStsToken || - DEVANT_API_KEY.trim() === '' || devantStsToken.trim() === '') { + // Check if CLOUD_STS_TOKEN environment variable exists (Devant flow identifier) + if (!('CLOUD_STS_TOKEN' in process.env)) { return undefined; } - // Return devant credentials without storing (always read from env) - return { - loginMethod: LoginMethod.DEVANT_ENV, - secrets: { - apiKey: DEVANT_API_KEY, - stsToken: devantStsToken + try { + // Check if a valid access token already exists to avoid redundant exchanges + const existingCredentials = await getAuthCredentials(); + + if (existingCredentials && existingCredentials.loginMethod === LoginMethod.DEVANT_ENV) { + // existing session, check expiry + const { expiresAt } = existingCredentials.secrets; + const now = Date.now(); + + // If token is still valid (not expired), return existing credentials + if (expiresAt && expiresAt > now) { + return existingCredentials; + } + } + if (existingCredentials && existingCredentials.loginMethod !== LoginMethod.DEVANT_ENV) { + // not devant + return undefined; + } + + // Get STS token from config or platform extension + const choreoStsToken = await getDevantStsToken() || DEVANT_STS_TOKEN_CONFIG; + + if (!choreoStsToken || choreoStsToken.trim() === '') { + console.warn('CLOUD_STS_TOKEN env variable exists but no STS token available'); + return undefined; } - }; + + // Exchange STS token for Bearer token (if no valid token exists or token expired) + const devantSecrets = await exchangeStsToken(choreoStsToken); + + // Return devant credentials without storing (always read from env and exchange on demand) + return { + loginMethod: LoginMethod.DEVANT_ENV, + secrets: devantSecrets + }; + } catch (error) { + console.error('Error in checkDevantEnvironment:', error); + return undefined; + } }; export const validateAwsCredentials = async (credentials: { From e79aba9abc90d3af4a6bcb18325b997d0b2f65aa Mon Sep 17 00:00:00 2001 From: kaje94 Date: Mon, 10 Nov 2025 16:36:46 +0530 Subject: [PATCH 31/41] fix cloud editor org dropdown in component creation flow --- .../src/cmds/open-in-console-cmd.ts | 5 +- .../FormElements/Dropdown/Dropdown.tsx | 8 +- .../sections/ComponentFormRepoInitSection.tsx | 131 ++++++++++-------- 3 files changed, 85 insertions(+), 59 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts index d20684123b7..cba6202da21 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/open-in-console-cmd.ts @@ -58,7 +58,10 @@ export function openInConsoleCommand(context: ExtensionContext) { } } - const projectBaseUrl = `${ext.config?.choreoConsoleUrl}/organizations/${selectedOrg?.handle}/projects/${selectedProject.handler}`; + let projectBaseUrl = `${ext.config?.choreoConsoleUrl}/organizations/${selectedOrg?.handle}/projects/${selectedProject.handler}`; + if(extensionName === "Devant"){ + projectBaseUrl = `${ext.config?.devantConsoleUrl}/organizations/${selectedOrg?.handle}/projects/${selectedProject.id}`; + } if (params?.component) { env.openExternal(Uri.parse(`${projectBaseUrl}/components/${params?.component.metadata.handler}/overview`)); diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/components/FormElements/Dropdown/Dropdown.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/components/FormElements/Dropdown/Dropdown.tsx index c94e58d6e20..3400766f102 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/components/FormElements/Dropdown/Dropdown.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/components/FormElements/Dropdown/Dropdown.tsx @@ -31,15 +31,16 @@ interface Props { items?: ({ value: string; label?: string; type?: "separator" } | string)[]; disabled?: boolean; wrapClassName?: HTMLProps["className"]; + onChange?: ((e: Event) => unknown) & React.FormEventHandler; } export const Dropdown: FC = (props) => { - const { label, required, items, loading, control, name, disabled, wrapClassName } = props; + const { label, required, items, loading, control, name, disabled, wrapClassName, onChange: onChangeRoot } = props; return ( ( + render={({ field: { onChange, ...restFields }, fieldState }) => ( = (props) => { {items?.map((item, index) => ( <> diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx index 880c82ec998..63659cafc75 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx @@ -20,6 +20,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { RequiredFormInput } from "@wso2/ui-toolkit"; import { GitProvider, type NewComponentWebviewProps, buildGitURL } from "@wso2/wso2-platform-core"; +import classNames from "classnames"; import debounce from "lodash.debounce"; import React, { type FC, useCallback, useEffect, useState } from "react"; import type { SubmitHandler, UseFormReturn } from "react-hook-form"; @@ -46,19 +47,20 @@ interface Props extends NewComponentWebviewProps { const connectMoreRepoText = "Connect More Repositories"; const createNewRpoText = "Create New Repository"; const createNewCredText = "Create New Credential"; +const addOrganization = "Add"; export const ComponentFormRepoInitSection: FC = ({ onNextClick, organization, form, initializingRepo }) => { const [compDetailsSections] = useAutoAnimate(); const { extensionName } = useExtWebviewContext(); const [creatingRepo, setCreatingRepo] = useState(false); - const orgName = form.watch("org"); + const org = form.watch("org"); const repo = form.watch("repo"); const subPath = form.watch("subPath"); const serverUrl = form.watch("serverUrl"); const provider = form.watch("gitProvider"); const credential = form.watch("credential"); - const repoError = form.formState?.errors?.repo; + const orgName = [addOrganization].includes(org) ? "" : org; const repoName = [connectMoreRepoText, createNewRpoText].includes(repo) ? "" : repo; const { @@ -83,9 +85,11 @@ export const ComponentFormRepoInitSection: FC = ({ onNextClick, organizat const { isLoading: isLoadingGitlabCreds } = useQuery({ queryKey: ["gitlab-creds", { provider, credential }], queryFn: () => - ChoreoWebViewAPI.getInstance() - .getChoreoRpcClient() - .getCredentialDetails({ orgId: organization?.id?.toString(), orgUuid: organization.uuid, credentialId: credential }), + ChoreoWebViewAPI.getInstance().getChoreoRpcClient().getCredentialDetails({ + orgId: organization?.id?.toString(), + orgUuid: organization.uuid, + credentialId: credential, + }), enabled: provider === GitProvider.GITLAB_SERVER && !!credential, onSuccess: (data) => form.setValue("serverUrl", data?.serverUrl), }); @@ -140,31 +144,16 @@ export const ComponentFormRepoInitSection: FC = ({ onNextClick, organizat } }, [branches]); - useEffect(() => { - // TODO: avoid using useEffect and try to override the onChange handler - if (repo === createNewRpoText) { - setTimeout(() => form.setValue("repo", ""), 1000); - let newRepoLink = "https://github.com/new"; - if (provider === GitProvider.BITBUCKET) { - newRepoLink = `https://bitbucket.org/${orgName}/workspace/create/repository`; - } else if (provider === GitProvider.GITLAB_SERVER) { - newRepoLink = `${serverUrl}/projects/new`; - } - ChoreoWebViewAPI.getInstance().openExternal(newRepoLink); - setCreatingRepo(true); - } else if (repo === connectMoreRepoText) { - setTimeout(() => form.setValue("repo", ""), 1000); - ChoreoWebViewAPI.getInstance().triggerGithubInstallFlow(organization.id?.toString()); - } - }, [repo]); - - useEffect(() => { - // TODO: avoid using useEffect and try to override the onChange handler - if (credential === createNewCredText) { - setTimeout(() => form.setValue("credential", ""), 1000); - ChoreoWebViewAPI.getInstance().openExternalChoreo(`organizations/${organization.handle}/settings/credentials`); + const handleCreateNewRepo = () => { + let newRepoLink = "https://github.com/new"; + if (provider === GitProvider.BITBUCKET) { + newRepoLink = `https://bitbucket.org/${orgName}/workspace/create/repository`; + } else if (provider === GitProvider.GITLAB_SERVER) { + newRepoLink = `${serverUrl}/projects/new`; } - }, [credential]); + ChoreoWebViewAPI.getInstance().openExternal(newRepoLink); + setCreatingRepo(true); + }; useEffect(() => { setCreatingRepo(false); @@ -241,6 +230,13 @@ export const ComponentFormRepoInitSection: FC = ({ onNextClick, organizat ); } + const orgDropdownItems = []; + if (provider === GitProvider.GITHUB) { + orgDropdownItems.push({ value: addOrganization }, { type: "separator", value: "" } as { value: string }); + } + + orgDropdownItems.push(...(gitOrgs?.gitOrgs ?? [])?.map((item) => ({ value: item.orgName }))); + return ( <>
@@ -264,7 +260,10 @@ export const ComponentFormRepoInitSection: FC = ({ onNextClick, organizat className="col-span-full" key="invalid-repo-banner" title={`Please authorize ${extensionName} to access your GitHub repositories.`} - actionLink={{ title: "Authorize", onClick: () => ChoreoWebViewAPI.getInstance().triggerGithubAuthFlow(organization.id?.toString()) }} + actionLink={{ + title: "Authorize", + onClick: () => ChoreoWebViewAPI.getInstance().triggerGithubAuthFlow(organization.id?.toString()), + }} /> )} {provider !== GitProvider.GITHUB && ( @@ -276,6 +275,15 @@ export const ComponentFormRepoInitSection: FC = ({ onNextClick, organizat control={form.control} items={credentialDropdownItems} loading={isLoadingGitCred} + onChange={(e) => { + const value = (e.target as HTMLSelectElement).value; + if (credential === createNewCredText) { + form.setValue("credential", ""); + ChoreoWebViewAPI.getInstance().openExternalChoreo(`organizations/${organization.handle}/settings/credentials`); + } else { + form.setValue("credential", value); + } + }} /> )} {(provider === GitProvider.GITHUB || credential) && ( @@ -286,22 +294,44 @@ export const ComponentFormRepoInitSection: FC = ({ onNextClick, organizat required name="org" control={form.control} - items={gitOrgs?.gitOrgs?.map((item) => ({ value: item.orgName }))} + items={orgDropdownItems} loading={loadingGitOrgs || (provider === GitProvider.GITLAB_SERVER ? isLoadingGitlabCreds : false)} + onChange={(e) => { + const value = (e.target as HTMLSelectElement).value; + if (value === addOrganization) { + ChoreoWebViewAPI.getInstance().triggerGithubInstallFlow(organization.id?.toString()); + form.setValue("org", ""); + } else { + form.setValue("org", value); + } + }} /> - {creatingRepo ? ( -
-
- - - - - {repoError?.message && ( - - )} -
+
+ { + const value = (e.target as HTMLSelectElement).value; + if (value === createNewRpoText) { + handleCreateNewRepo(); + form.setValue("repo", ""); + } else if (value === connectMoreRepoText) { + ChoreoWebViewAPI.getInstance().triggerGithubInstallFlow(organization.id?.toString()); + form.setValue("repo", ""); + } else { + form.setValue("repo", value); + } + }} + /> +
- ) : ( - - )} +
+ {repoName && branches?.length > 0 && ( Date: Fri, 14 Nov 2025 01:31:03 +0530 Subject: [PATCH 32/41] fix: update project URI references to project path in activator.ts --- .../ballerina-extension/src/features/devant/activator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts index 8aa3c529bd6..ca046f1ec6b 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/devant/activator.ts @@ -41,7 +41,7 @@ export function activateDevantFeatures(_ballerinaExtInstance: BallerinaExtension } const handleComponentPushToDevant = async () => { - const projectRoot = StateMachine.context().projectUri; + const projectRoot = StateMachine.context().projectPath; if (!projectRoot) { return; } @@ -120,7 +120,7 @@ const handleComponentPushToDevant = async () => { const deployementParams: ICreateComponentCmdParams = { integrationType: integrationType as any, buildPackLang: "ballerina", - componentDir: StateMachine.context().projectUri, + componentDir: StateMachine.context().projectPath, extName: "Devant", }; commands.executeCommand(PlatformCommandIds.CreateNewComponent, deployementParams); From 66f27c3e7fa5cbc66637b4e5ae677bb9a935ac49 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Wed, 10 Dec 2025 16:29:13 +0530 Subject: [PATCH 33/41] pack choreo cli with wso2-platform extension --- .../wso2-platform-extension/.gitignore | 1 + .../wso2-platform-extension/package.json | 3 +- .../scripts/download-choreo-cli.js | 432 ++++++++++++++++++ .../src/choreo-rpc/activate.ts | 9 +- .../src/choreo-rpc/cli-install.ts | 177 +++---- .../wso2-platform-extension/src/extension.ts | 33 +- 6 files changed, 548 insertions(+), 107 deletions(-) create mode 100644 workspaces/wso2-platform/wso2-platform-extension/scripts/download-choreo-cli.js diff --git a/workspaces/wso2-platform/wso2-platform-extension/.gitignore b/workspaces/wso2-platform/wso2-platform-extension/.gitignore index 870f942784c..f81de3e3f57 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/.gitignore +++ b/workspaces/wso2-platform/wso2-platform-extension/.gitignore @@ -1,6 +1,7 @@ .nyc_output resources/jslibs/main.js resources/font-wso2-vscode +resources/choreo-cli/ !src/test/lib .vscode-test test-resources diff --git a/workspaces/wso2-platform/wso2-platform-extension/package.json b/workspaces/wso2-platform/wso2-platform-extension/package.json index a6699c9b602..67368b60c32 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/package.json +++ b/workspaces/wso2-platform/wso2-platform-extension/package.json @@ -217,7 +217,8 @@ "package": "if [ $isPreRelease = true ]; then vsce package --no-dependencies --pre-release --baseImagesUrl https://github.com/wso2/choreo-vscode/raw/HEAD/; else vsce package --no-dependencies --baseImagesUrl https://github.com/wso2/choreo-vscode/raw/HEAD/; fi", "copyVSIX": "copyfiles *.vsix ./vsix", "copyVSIXToRoot": "copyfiles -f ./vsix/* ../../..", - "postbuild": "pnpm run package && pnpm run copyVSIX" + "postbuild": "pnpm run download-choreo-cli && pnpm run package && pnpm run copyVSIX", + "download-choreo-cli": "node scripts/download-choreo-cli.js" }, "devDependencies": { "@playwright/test": "1.55.1", diff --git a/workspaces/wso2-platform/wso2-platform-extension/scripts/download-choreo-cli.js b/workspaces/wso2-platform/wso2-platform-extension/scripts/download-choreo-cli.js new file mode 100644 index 00000000000..729432634ca --- /dev/null +++ b/workspaces/wso2-platform/wso2-platform-extension/scripts/download-choreo-cli.js @@ -0,0 +1,432 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const https = require('https'); +const { execSync } = require('child_process'); +const os = require('os'); + +const PROJECT_ROOT = path.join(__dirname, '..'); +const REPO_ROOT = path.join(PROJECT_ROOT, '..', '..', '..'); +// Primary location used by the extension +const CLI_RESOURCES_DIR = path.join(PROJECT_ROOT, 'resources', 'choreo-cli'); +// Persistent cache that survives 'rush purge' +const CLI_CACHE_DIR = path.join(REPO_ROOT, 'common', 'temp', 'choreo-cli'); +const PACKAGE_JSON_PATH = path.join(PROJECT_ROOT, 'package.json'); +const GITHUB_REPO_URL = 'https://api.github.com/repos/wso2/choreo-cli'; + +// Platform-specific file patterns for CLI downloads +const CLI_ASSET_PATTERNS = [ + 'choreo-cli-{version}-darwin-amd64.zip', + 'choreo-cli-{version}-darwin-arm64.zip', + 'choreo-cli-{version}-linux-amd64.tar.gz', + 'choreo-cli-{version}-linux-arm64.tar.gz', + 'choreo-cli-{version}-windows-amd64.zip' +]; + +// ============================================================================ +// Version Management +// ============================================================================ + +function getCliVersion() { + const packageJson = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, 'utf8')); + const cliVersion = packageJson.cliVersion; + + if (!cliVersion) { + throw new Error('cliVersion not found in package.json'); + } + + console.log(`Choreo CLI version for WSO2 platform extension: ${cliVersion}`); + return cliVersion; +} + +function getCombinedZipFileName(version) { + return `choreo-cli-${version}.zip`; +} + +function getCombinedZipPath(version, baseDir) { + return path.join(baseDir, getCombinedZipFileName(version)); +} + +function getResourcesZipPath(version) { + return getCombinedZipPath(version, CLI_RESOURCES_DIR); +} + +function getCacheZipPath(version) { + return getCombinedZipPath(version, CLI_CACHE_DIR); +} + +function getExpectedAssetNames(version) { + return CLI_ASSET_PATTERNS.map(pattern => pattern.replace('{version}', version)); +} + +// ============================================================================ +// File System Utilities +// ============================================================================ + +function ensureDirectoryExists(dirPath) { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +} + +function getFileSize(filePath) { + try { + const stats = fs.statSync(filePath); + return stats.size; + } catch (error) { + return 'unknown'; + } +} + +function deleteFile(filePath) { + try { + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + } catch (error) { + console.warn(`Failed to delete file ${filePath}:`, error.message); + } +} + +function createTempDirectory(prefix) { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); +} + +function deleteDirectory(dirPath) { + try { + if (fs.existsSync(dirPath)) { + fs.rmSync(dirPath, { recursive: true, force: true }); + } + } catch (error) { + console.warn(`Failed to delete directory ${dirPath}:`, error.message); + } +} + +// ============================================================================ +// CLI File Validation & Cache Management +// ============================================================================ + +function checkExistingCLI(version) { + const resourcesZipPath = getResourcesZipPath(version); + const cacheZipPath = getCacheZipPath(version); + + const resourcesExists = fs.existsSync(resourcesZipPath); + const cacheExists = fs.existsSync(cacheZipPath); + + // Both exist - we're good + if (resourcesExists && cacheExists) { + console.log(`✓ Choreo CLI for version ${version} exists`); + return true; + } + + // Resources exists but cache doesn't (e.g., after rush purge) + if (resourcesExists && !cacheExists) { + console.log(`✓ CLI zip exists in resources/choreo-cli`); + console.log(`Restoring cache (common/temp) from resources...`); + ensureDirectoryExists(CLI_CACHE_DIR); + fs.copyFileSync(resourcesZipPath, cacheZipPath); + console.log(`✓ Restored cache from resources/choreo-cli`); + return true; + } + + // Cache exists but resources doesn't + if (!resourcesExists && cacheExists) { + console.log(`Found CLI zip in cache (common/temp), copying to resources/choreo-cli...`); + ensureDirectoryExists(CLI_RESOURCES_DIR); + fs.copyFileSync(cacheZipPath, resourcesZipPath); + console.log(`✓ Copied CLI zip to resources/choreo-cli`); + return true; + } + + // Neither exists + console.log(`CLI zip for version ${version} not found in resources or cache`); + return false; +} + +function cleanupOldFilesInDirectory(directory, currentVersion) { + if (!fs.existsSync(directory)) { + return; + } + + const currentZipName = getCombinedZipFileName(currentVersion); + const entries = fs.readdirSync(directory); + + for (const entry of entries) { + if (entry === currentZipName) { + continue; // Skip the current version + } + + const entryPath = path.join(directory, entry); + const stats = fs.statSync(entryPath); + + console.log(`Removing old ${stats.isDirectory() ? 'directory' : 'file'}: ${entry} from ${path.basename(directory)}`); + + if (stats.isDirectory()) { + fs.rmSync(entryPath, { recursive: true, force: true }); + } else { + fs.unlinkSync(entryPath); + } + } +} + +function cleanupOldFiles(currentVersion) { + // Clean up old files from both locations + cleanupOldFilesInDirectory(CLI_RESOURCES_DIR, currentVersion); + cleanupOldFilesInDirectory(CLI_CACHE_DIR, currentVersion); +} + +// ============================================================================ +// GitHub API Utilities +// ============================================================================ + +function getAuthHeaders() { + const token = process.env.CHOREO_BOT_TOKEN || process.env.GITHUB_TOKEN; + return token ? { 'Authorization': `Bearer ${token}` } : {}; +} + +function logRateLimitError(headers) { + console.error('HTTP 403: Forbidden. This may be due to GitHub API rate limiting.'); + console.error('Set GITHUB_TOKEN environment variable with a personal access token to increase rate limits.'); + + if (headers['x-ratelimit-limit']) { + console.error(`Rate limit: ${headers['x-ratelimit-remaining']}/${headers['x-ratelimit-limit']}`); + const resetTime = new Date(headers['x-ratelimit-reset'] * 1000).toLocaleString(); + console.error(`Rate limit resets at: ${resetTime}`); + } +} + +function httpsRequest(url, options = {}) { + return new Promise((resolve, reject) => { + const req = https.request(url, { + ...options, + headers: { + 'User-Agent': 'Choreo-CLI-Downloader', + 'Accept': 'application/vnd.github.v3+json', + ...getAuthHeaders(), + ...options.headers + } + }, (res) => { + if (res.statusCode === 403) { + logRateLimitError(res.headers); + } + + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve({ data, statusCode: res.statusCode, headers: res.headers }); + } else { + reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`)); + } + }); + }); + + req.on('error', reject); + req.end(); + }); +} + +async function getReleaseByTag(tag) { + console.log(`Fetching release information for tag: ${tag}...`); + const response = await httpsRequest(`${GITHUB_REPO_URL}/releases/tags/${tag}`); + return JSON.parse(response.data); +} + +// ============================================================================ +// File Download +// ============================================================================ + +function isRedirect(statusCode) { + return statusCode >= 300 && statusCode < 400; +} + +function isSuccess(statusCode) { + return statusCode >= 200 && statusCode < 300; +} + +function downloadFile(url, outputPath, maxRedirects = 5) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(outputPath); + + const cleanupAndReject = (error) => { + file.close(); + deleteFile(outputPath); + reject(error); + }; + + const makeRequest = (requestUrl, redirectCount = 0) => { + const req = https.request(requestUrl, { + headers: { + 'User-Agent': 'Choreo-CLI-Downloader', + 'Accept': 'application/octet-stream' + } + }, (res) => { + if (isRedirect(res.statusCode) && res.headers.location) { + if (redirectCount >= maxRedirects) { + cleanupAndReject(new Error(`Too many redirects (${redirectCount})`)); + return; + } + makeRequest(res.headers.location, redirectCount + 1); + return; + } + + if (isSuccess(res.statusCode)) { + res.pipe(file); + file.on('finish', () => { + file.close(); + resolve(); + }); + file.on('error', cleanupAndReject); + } else { + cleanupAndReject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`)); + } + }); + + req.on('error', cleanupAndReject); + req.end(); + }; + + makeRequest(url); + }); +} + +async function downloadAsset(asset, tempDir) { + const finalPath = path.join(tempDir, asset.name); + const tempPath = `${finalPath}.tmp`; + const downloadUrl = `${GITHUB_REPO_URL}/releases/assets/${asset.id}`; + + console.log(`Downloading ${asset.name}...`); + + try { + await downloadFile(downloadUrl, tempPath); + fs.renameSync(tempPath, finalPath); // Atomic operation + + const fileSize = getFileSize(finalPath); + console.log(`✓ Downloaded ${asset.name} (${fileSize} bytes)`); + } catch (error) { + deleteFile(tempPath); + console.error(`✗ Failed to download ${asset.name}: ${error.message}`); + throw error; + } +} + +function getZipCommand(files, outputZipPath, tempDir) { + const isWindows = os.platform() === 'win32'; + + if (isWindows) { + const filesArg = files.map(f => `'${f}'`).join(','); + return { + command: `powershell.exe -Command "Compress-Archive -Path ${filesArg} -DestinationPath '${outputZipPath}' -Force"`, + cwd: tempDir + }; + } + + // macOS/Linux + const filesArg = files.map(f => `'${f}'`).join(' '); + return { + command: `zip -q '${outputZipPath}' ${filesArg}`, + cwd: tempDir + }; +} + +function createCombinedZip(tempDir, outputZipPath) { + console.log('\nCreating Choreo CLI zip file...'); + const files = fs.readdirSync(tempDir).filter(f => !f.startsWith('.')); + const { command, cwd } = getZipCommand(files, outputZipPath, tempDir); + + try { + execSync(command, { cwd, stdio: 'inherit' }); + + const zipSize = getFileSize(outputZipPath); + const relativePath = path.relative(PROJECT_ROOT, outputZipPath); + console.log(`✓ Created Choreo CLI combined zip: ${relativePath} (${zipSize} bytes)`); + } catch (error) { + throw new Error(`Failed to create zip file: ${error.message}`); + } +} + +// ============================================================================ +// Main Download Logic +// ============================================================================ + +async function downloadAllAssets(releaseData, expectedAssetNames, tempDir) { + const downloadPromises = expectedAssetNames.map(assetName => { + const asset = releaseData.assets?.find(a => a.name === assetName); + + if (!asset) { + console.warn(`Warning: Choreo CLI Asset not found: ${assetName}`); + return Promise.resolve(); + } + + return downloadAsset(asset, tempDir); + }); + + await Promise.all(downloadPromises); +} + +async function downloadAndCombineCLI(version) { + const tempDir = createTempDirectory(`choreo-cli-${version}-`); + + try { + // Ensure both directories exist + ensureDirectoryExists(CLI_RESOURCES_DIR); + ensureDirectoryExists(CLI_CACHE_DIR); + + const releaseData = await getReleaseByTag(version); + const expectedAssetNames = getExpectedAssetNames(version); + + await downloadAllAssets(releaseData, expectedAssetNames, tempDir); + + // Create zip in cache directory first + const cacheZipPath = getCacheZipPath(version); + createCombinedZip(tempDir, cacheZipPath); + + // Copy to resources directory + const resourcesZipPath = getResourcesZipPath(version); + console.log('Copying CLI zip to resources/choreo-cli...'); + fs.copyFileSync(cacheZipPath, resourcesZipPath); + console.log('✓ Copied CLI zip to resources/choreo-cli'); + + } finally { + console.log('Cleaning up temporary directory...'); + deleteDirectory(tempDir); + } +} + +// ============================================================================ +// Main Entry Point +// ============================================================================ + +async function main() { + try { + const cliVersion = getCliVersion(); + + // Check if combined CLI zip already exists + if (checkExistingCLI(cliVersion)) { + console.log('✓ Combined CLI zip is already present'); + process.exit(0); + } + + console.log(`\nDownloading Choreo CLI version ${cliVersion}...`); + + // Clean up old files before downloading new one + cleanupOldFiles(cliVersion); + + // Download all CLI assets and combine into single zip + await downloadAndCombineCLI(cliVersion); + + console.log(`\n✓ Successfully created Choreo CLI zip for version ${cliVersion}`); + + } catch (error) { + console.error('\n✗ Error:', error.message); + process.exit(1); + } +} + +// Run if executed directly +if (require.main === module) { + main(); +} + +module.exports = { main, checkExistingCLI }; \ No newline at end of file diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/activate.ts b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/activate.ts index 584db3f4117..cbcaf6d29e8 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/activate.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/activate.ts @@ -20,11 +20,12 @@ import { exec } from "child_process"; import * as fs from "fs"; import { downloadCLI, getChoreoExecPath, getCliVersion } from "./cli-install"; import { RPCClient } from "./client"; +import { getLogger } from "../logger/logger"; function isChoreoCliInstalled(): Promise { return new Promise((resolve) => { const rpcPath = getChoreoExecPath(); - console.log("RPC path: ", rpcPath); + getLogger().info(`RPC path: ${rpcPath}`); if (!fs.existsSync(rpcPath)) { return resolve(false); @@ -42,9 +43,9 @@ function isChoreoCliInstalled(): Promise { const timeout = setTimeout(() => { process.kill(); // Kill the process if it exceeds 5 seconds - console.error("Timeout: Process took too long"); + getLogger().error("Timeout: Process took too long"); fs.rmSync(rpcPath); - console.error("Delete RPC path and try again", rpcPath); + getLogger().error(`Delete RPC path and try again ${rpcPath}`); resolve(false); }, 5000); @@ -55,7 +56,7 @@ function isChoreoCliInstalled(): Promise { export async function initRPCServer() { const installed = await isChoreoCliInstalled(); if (!installed) { - console.log(`WSO2 Platform RPC version ${getCliVersion()} not installed`); + getLogger().trace(`WSO2 Platform RPC version ${getCliVersion()} not installed`); await downloadCLI(); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/cli-install.ts b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/cli-install.ts index 1ba448c6678..76afc0bc65b 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/cli-install.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/cli-install.ts @@ -20,10 +20,9 @@ import { execSync } from "child_process"; import * as fs from "fs"; import * as os from "os"; import * as path from "path"; -import axios from "axios"; -import { ProgressLocation, window, workspace } from "vscode"; -import { choreoEnvConfig } from "../config"; +import { workspace } from "vscode"; import { ext } from "../extensionVariables"; +import { getLogger } from "../logger/logger"; export const getCliVersion = (): string => { const packageJson = JSON.parse(fs.readFileSync(path.join(ext.context.extensionPath, "package.json"), "utf8")); @@ -62,106 +61,118 @@ export const downloadCLI = async () => { const CHOREO_BIN_DIR = getChoreoBinPath(); const CHOREO_CLI_EXEC = getChoreoExecPath(); const CLI_VERSION = getCliVersion(); - const CHOREO_TMP_DIR = await fs.promises.mkdtemp(path.join(os.tmpdir(), `choreo-cli-rpc-${CLI_VERSION}`)); - - fs.mkdirSync(CHOREO_BIN_DIR, { recursive: true }); + + // Path to the combined zip file in resources + const COMBINED_ZIP_PATH = path.join(ext.context.extensionPath, "resources", "choreo-cli", `choreo-cli-${CLI_VERSION}.zip`); + + if (!fs.existsSync(COMBINED_ZIP_PATH)) { + throw new Error(`Combined CLI zip not found at: ${COMBINED_ZIP_PATH}\nPlease run 'pnpm run download-choreo-cli' to download the CLI.`); + } - const FILE_NAME = `choreo-cli-${CLI_VERSION}-${OS === "win32" ? "windows" : OS}-${ARCH}`; - let FILE_TYPE = ""; + getLogger().trace(`Extracting Choreo CLI from: ${COMBINED_ZIP_PATH}`); - if (OS === "linux") { - FILE_TYPE = ".tar.gz"; - } else if (OS === "darwin") { - FILE_TYPE = ".zip"; - } else if (OS === "win32") { - FILE_TYPE = ".zip"; - } else { - throw new Error(`Unsupported OS: ${OS}`); - } - const CHOREO_TMP_FILE_DEST = path.join(CHOREO_TMP_DIR, `${FILE_NAME}${FILE_TYPE}`); + const CHOREO_TMP_DIR = await fs.promises.mkdtemp(path.join(os.tmpdir(), `choreo-cli-rpc-${CLI_VERSION}-`)); - const INSTALLER_URL = `${choreoEnvConfig.getCliInstallUrl()}${CLI_VERSION}/${FILE_NAME}${FILE_TYPE}`; + try { + fs.mkdirSync(CHOREO_BIN_DIR, { recursive: true }); - console.log(`WSO2 Platform RPC download URL: ${INSTALLER_URL}`); + // Extract the combined zip to temp directory + getLogger().trace(`Extracting combined zip to temp dir: ${CHOREO_TMP_DIR}`); + try { + if (OS === "win32") { + execSync(`powershell.exe -Command "Expand-Archive -Path '${COMBINED_ZIP_PATH}' -DestinationPath '${CHOREO_TMP_DIR}' -Force"`); + } else { + execSync(`unzip -q '${COMBINED_ZIP_PATH}' -d '${CHOREO_TMP_DIR}'`); + } + } catch (error) { + throw new Error(`Failed to extract combined zip: ${error instanceof Error ? error.message : String(error)}`); + } - await downloadFile(INSTALLER_URL, CHOREO_TMP_FILE_DEST); + // Determine the specific file to extract based on OS and architecture + const FILE_NAME = `choreo-cli-${CLI_VERSION}-${OS === "win32" ? "windows" : OS}-${ARCH}`; + let FILE_TYPE = ""; - console.log(`Extracting archive into temp dir: ${CHOREO_TMP_DIR}`); - if (FILE_TYPE === ".tar.gz") { - execSync(`tar -xzf ${CHOREO_TMP_FILE_DEST} -C ${CHOREO_TMP_DIR}`); - } else if (FILE_TYPE === ".zip") { - if (OS === "darwin") { - execSync(`unzip -q ${CHOREO_TMP_FILE_DEST} -d ${CHOREO_TMP_DIR}`); + if (OS === "linux") { + FILE_TYPE = ".tar.gz"; + } else if (OS === "darwin") { + FILE_TYPE = ".zip"; } else if (OS === "win32") { - execSync(`powershell.exe -Command "Expand-Archive '${CHOREO_TMP_FILE_DEST}' -DestinationPath '${CHOREO_TMP_DIR}' -Force"`); + FILE_TYPE = ".zip"; + } else { + throw new Error(`Unsupported OS: ${OS}`); + } + + const PLATFORM_ARCHIVE = path.join(CHOREO_TMP_DIR, `${FILE_NAME}${FILE_TYPE}`); + + if (!fs.existsSync(PLATFORM_ARCHIVE)) { + throw new Error(`Platform-specific archive not found: ${FILE_NAME}${FILE_TYPE}`); + } + + getLogger().trace(`Extracting platform-specific archive: ${FILE_NAME}${FILE_TYPE}`); + const PLATFORM_TMP_DIR = path.join(CHOREO_TMP_DIR, "platform-extract"); + fs.mkdirSync(PLATFORM_TMP_DIR, { recursive: true }); + + // Extract the platform-specific archive + try { + if (FILE_TYPE === ".tar.gz") { + execSync(`tar -xzf '${PLATFORM_ARCHIVE}' -C '${PLATFORM_TMP_DIR}'`); + } else if (FILE_TYPE === ".zip") { + if (OS === "darwin") { + execSync(`unzip -q '${PLATFORM_ARCHIVE}' -d '${PLATFORM_TMP_DIR}'`); + } else if (OS === "win32") { + execSync(`powershell.exe -Command "Expand-Archive -Path '${PLATFORM_ARCHIVE}' -DestinationPath '${PLATFORM_TMP_DIR}' -Force"`); + } + } + } catch (error) { + throw new Error(`Failed to extract platform-specific archive: ${error instanceof Error ? error.message : String(error)}`); + } + + // Copy the executable to the bin directory + const executableName = OS === "win32" ? "choreo.exe" : "choreo"; + const extractedExecutable = path.join(PLATFORM_TMP_DIR, executableName); + + if (!fs.existsSync(extractedExecutable)) { + throw new Error(`Executable not found after extraction: ${extractedExecutable}`); } - } - console.log(`Moving executable to ${CHOREO_BIN_DIR}`); - await fs.promises.copyFile(`${CHOREO_TMP_DIR}/${OS === "win32" ? "choreo.exe" : "choreo"}`, CHOREO_CLI_EXEC); - await fs.promises.rm(`${CHOREO_TMP_DIR}/${OS === "win32" ? "choreo.exe" : "choreo"}`); + getLogger().trace(`Copying executable to ${CHOREO_BIN_DIR}`); + try { + await fs.promises.copyFile(extractedExecutable, CHOREO_CLI_EXEC); + } catch (error) { + throw new Error(`Failed to copy executable: ${error instanceof Error ? error.message : String(error)}`); + } - console.log("Cleaning up..."); - await fs.promises.rm(CHOREO_TMP_DIR, { recursive: true }); + // Set executable permissions on Unix systems + if (OS !== "win32") { + try { + await fs.promises.chmod(CHOREO_CLI_EXEC, 0o755); + } catch (error) { + throw new Error(`Failed to set executable permissions: ${error instanceof Error ? error.message : String(error)}`); + } + } - process.chdir(CHOREO_BIN_DIR); - if (OS !== "win32") { - await fs.promises.chmod(CHOREO_CLI_EXEC, 0o755); + getLogger().trace("WSO2 Platform RPC server was installed successfully 🎉"); + } catch (error) { + // Clean up temp directory on error and re-throw + getLogger().error("Error during CLI installation:", error); + await fs.promises.rm(CHOREO_TMP_DIR, { recursive: true, force: true }).catch(() => { + // Ignore cleanup errors + }); + throw error; } - console.log("WSO2 Platform RPC server was installed successfully 🎉"); + // Clean up temp directory on success + getLogger().trace("Cleaning up temporary files..."); + await fs.promises.rm(CHOREO_TMP_DIR, { recursive: true, force: true }); }; -async function downloadFile(url: string, dest: string) { - const controller = new AbortController(); - const response = await axios({ url, method: "GET", responseType: "stream", signal: controller.signal }); - await window.withProgress( - { - title: "Initializing WSO2 Platform extension", - location: ProgressLocation.Notification, - cancellable: true, - }, - async (progress, cancellationToken) => { - return new Promise((resolve, reject) => { - const writer = fs.createWriteStream(dest); - const totalSize = Number.parseInt(response.headers["content-length"], 10); - let downloadedSize = 0; - let previousPercentage = 0; - - response.data.on("data", (chunk: string) => { - downloadedSize += chunk.length; - - const progressPercentage = Math.round((downloadedSize / totalSize) * 100); - if (progressPercentage !== previousPercentage) { - progress.report({ - increment: progressPercentage - previousPercentage, - message: `${progressPercentage}%`, - }); - previousPercentage = progressPercentage; - } - }); - - response.data.pipe(writer); - - cancellationToken.onCancellationRequested(() => { - controller.abort(); - reject(); - }); - - writer.on("finish", resolve); - writer.on("error", reject); - }); - }, - ); -} - function getArchitecture() { const ARCH = os.arch(); switch (ARCH) { case "x64": return "amd64"; - case "x32": - return "386"; + // case "x32": + // return "386"; case "arm64": case "aarch64": return "arm64"; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts index 731ccb4d428..93d2cf5d1eb 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts @@ -74,31 +74,26 @@ export async function activate(context: vscode.ExtensionContext) { const rpcClient = new ChoreoRPCClient(); ext.clients = { rpcClient: rpcClient }; - initRPCServer() - .then(async () => { - await ext.clients.rpcClient.init(); - authStore.getState().initAuth(); - continueCreateComponent(); - if (ext.isChoreoExtInstalled) { - addTerminalHandlers(); - context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("*", new ChoreoConfigurationProvider())); - activateChoreoMcp(context); - } - if (ext.isDevantCloudEditor) { - activateDevantFeatures(); - } - getLogger().debug("WSO2 Platform Extension activated"); - ext.config = await ext.clients.rpcClient.getConfigFromCli(); - }) - .catch((e) => { - getLogger().error("Failed to initialize rpc client", e); - }); + await initRPCServer() + await ext.clients.rpcClient.init(); + authStore.getState().initAuth(); + continueCreateComponent(); + if (ext.isChoreoExtInstalled) { + addTerminalHandlers(); + context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("*", new ChoreoConfigurationProvider())); + activateChoreoMcp(context); + } + if (ext.isDevantCloudEditor) { + activateDevantFeatures(); + } + ext.config = await ext.clients.rpcClient.getConfigFromCli(); activateCmds(context); activateURIHandlers(); activateCodeLenses(context); registerPreInitHandlers(); registerYamlLanguageServer(); activateStatusbar(context); + getLogger().debug("WSO2 Platform Extension activated"); return ext.api; } From 4106e78e360056c4c4e63114e6ce56f3cecdf874 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Wed, 10 Dec 2025 16:38:27 +0530 Subject: [PATCH 34/41] fix typo in component repo init form --- .../ComponentFormView/sections/ComponentFormRepoInitSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx index 63659cafc75..ae34163f5e1 100644 --- a/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx +++ b/workspaces/wso2-platform/wso2-platform-webviews/src/views/ComponentFormView/sections/ComponentFormRepoInitSection.tsx @@ -240,7 +240,7 @@ export const ComponentFormRepoInitSection: FC = ({ onNextClick, organizat return ( <>
- + Date: Thu, 11 Dec 2025 00:11:56 +0530 Subject: [PATCH 35/41] Fix devant mode checker logic --- .../ballerina/ballerina-core/src/state-machine-types.ts | 1 + workspaces/ballerina/ballerina-extension/src/RPCLayer.ts | 1 + .../ballerina/ballerina-extension/src/stateMachine.ts | 4 +++- .../ballerina/ballerina-visualizer/src/MainPanel.tsx | 1 + .../src/views/BI/PackageOverview/index.tsx | 8 ++++---- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/state-machine-types.ts b/workspaces/ballerina/ballerina-core/src/state-machine-types.ts index c9ac28532e8..1040cb0d65f 100644 --- a/workspaces/ballerina/ballerina-core/src/state-machine-types.ts +++ b/workspaces/ballerina/ballerina-core/src/state-machine-types.ts @@ -132,6 +132,7 @@ export interface VisualizerLocation { position?: NodePosition; syntaxTree?: STNode; isBI?: boolean; + isInDevant?: boolean; focusFlowDiagramView?: FocusFlowDiagramView; serviceType?: string; type?: Type; diff --git a/workspaces/ballerina/ballerina-extension/src/RPCLayer.ts b/workspaces/ballerina/ballerina-extension/src/RPCLayer.ts index 2550a32a896..f8c729538b4 100644 --- a/workspaces/ballerina/ballerina-extension/src/RPCLayer.ts +++ b/workspaces/ballerina/ballerina-extension/src/RPCLayer.ts @@ -130,6 +130,7 @@ async function getContext(): Promise { position: context.position, syntaxTree: context.syntaxTree, isBI: context.isBI, + isInDevant: context.isInDevant, projectPath: context.projectPath, serviceType: context.serviceType, type: context.type, diff --git a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts index 02eefb8efcb..16586c0b920 100644 --- a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts +++ b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts @@ -67,6 +67,7 @@ interface MachineContext extends VisualizerLocation { isBISupported: boolean; errorCode: string | null; dependenciesResolved?: boolean; + isInDevant: boolean; } export let history: History; @@ -84,7 +85,8 @@ const stateMachine = createMachine( errorCode: null, isBISupported: false, view: MACHINE_VIEW.PackageOverview, - dependenciesResolved: false + dependenciesResolved: false, + isInDevant: !!process.env.CLOUD_STS_TOKEN }, on: { RESET_TO_EXTENSION_READY: { diff --git a/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx b/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx index 52de4b84a17..147dae0c14e 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx @@ -286,6 +286,7 @@ const MainPanel = () => { setViewComponent( ); break; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/index.tsx index e5bdba9f626..d2653a308c0 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/index.tsx @@ -608,10 +608,11 @@ function DevantDashboard({ projectStructure, handleDeploy, goToDevant, devantMet interface PackageOverviewProps { projectPath: string; + isInDevant: boolean; } export function PackageOverview(props: PackageOverviewProps) { - const { projectPath } = props; + const { projectPath, isInDevant } = props; const { rpcClient } = useRpcContext(); const [readmeContent, setReadmeContent] = useState(""); const [projectStructure, setProjectStructure] = useState(); @@ -624,7 +625,6 @@ export function PackageOverview(props: PackageOverviewProps) { refetchInterval: 5000 }); const [showAlert, setShowAlert] = useState(false); - const isDevantEditor = (window as any).isDevantEditor !== undefined ? true : false; const fetchContext = () => { rpcClient @@ -941,7 +941,7 @@ export function PackageOverview(props: PackageOverviewProps) { - {!isDevantEditor && + {!isInDevant && <> } - {isDevantEditor && + {isInDevant && Date: Thu, 11 Dec 2025 12:57:42 +0530 Subject: [PATCH 36/41] add new apis to wso2 platform extension --- .../src/types/common.types.ts | 13 +++++++++- .../wso2-platform-extension/package.json | 2 +- .../src/PlatformExtensionApi.ts | 26 ++++++++++++++----- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts b/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts index 52e4cf42a5c..e0cef78c396 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts @@ -17,11 +17,12 @@ */ import type { DeploymentStatus } from "../enums"; -import type { ContextStoreState, WebviewState } from "./store.types"; +import type { AuthState, ContextItemEnriched, ContextStoreState, WebviewState } from "./store.types"; export type ExtensionName = "WSO2" | "Choreo" | "Devant"; export interface IWso2PlatformExtensionAPI { + getAuthState(): AuthState; isLoggedIn(): boolean; getDirectoryComponents(fsPath: string): ComponentKind[]; localRepoHasChanges(fsPath: string): Promise; @@ -29,6 +30,16 @@ export interface IWso2PlatformExtensionAPI { getContextStateStore(): ContextStoreState; openClonedDir(params: openClonedDirReq): Promise; getStsToken(): Promise; + getSelectedContext(): ContextItemEnriched | null; + getDevantConsoleUrl: () => Promise; + + // Auth Subscription + subscribeAuthState(callback: (state: AuthState)=>void): () => void; + subscribeIsLoggedIn(callback: (isLoggedIn: boolean)=>void): () => void; + + // Context Subscription + subscribeDirComponents(fsPath: string, callback: (comps: ComponentKind[])=>void): () => void; + subscribeContextState(callback: (state: ContextItemEnriched | undefined)=>void): () => void; } export interface openClonedDirReq { diff --git a/workspaces/wso2-platform/wso2-platform-extension/package.json b/workspaces/wso2-platform/wso2-platform-extension/package.json index 67368b60c32..3ffec77e5b0 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/package.json +++ b/workspaces/wso2-platform/wso2-platform-extension/package.json @@ -3,7 +3,7 @@ "displayName": "WSO2 Platform", "description": "Manage WSO2 Choreo and Devant projects in VS Code.", "license": "Apache-2.0", - "version": "1.0.15", + "version": "1.0.17", "cliVersion": "v1.2.212509091800", "publisher": "wso2", "bugs": { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts index d22e458c484..4dd64f58801 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts @@ -16,7 +16,7 @@ * under the License. */ -import type { ComponentKind, IWso2PlatformExtensionAPI, openClonedDirReq } from "@wso2/wso2-platform-core"; +import type { AuthState, ComponentKind, ContextItemEnriched, ContextStoreComponentState, IWso2PlatformExtensionAPI, openClonedDirReq } from "@wso2/wso2-platform-core"; import { ext } from "./extensionVariables"; import { hasDirtyRepo } from "./git/util"; import { authStore } from "./stores/auth-store"; @@ -26,16 +26,28 @@ import { openClonedDir } from "./uri-handlers"; import { isSamePath } from "./utils"; export class PlatformExtensionApi implements IWso2PlatformExtensionAPI { - public isLoggedIn = () => !!authStore.getState().state?.userInfo; - public getDirectoryComponents = (fsPath: string) => - (contextStore - .getState() - .state?.components?.filter((item) => isSamePath(item?.componentFsPath, fsPath)) + private getComponentsOfDir = (fsPath: string, components?: ContextStoreComponentState[]) => { + return (components?.filter((item) => isSamePath(item?.componentFsPath, fsPath)) ?.map((item) => item?.component) - ?.filter((item) => !!item) as ComponentKind[]) ?? []; + ?.filter((item) => !!item) as ComponentKind[]) ?? [] + } + + public getAuthState = () => authStore.getState().state; + public isLoggedIn = () => !!authStore.getState().state?.userInfo; + public getDirectoryComponents = (fsPath: string) => this.getComponentsOfDir(fsPath, contextStore.getState().state?.components); public localRepoHasChanges = (fsPath: string) => hasDirtyRepo(fsPath, ext.context, ["context.yaml"]); public getWebviewStateStore = () => webviewStateStore.getState().state; public getContextStateStore = () => contextStore.getState().state; public openClonedDir = (params: openClonedDirReq) => openClonedDir(params); public getStsToken = () => ext.clients.rpcClient.getStsToken(); + public getSelectedContext = () => contextStore.getState().state?.selected || null; + public getDevantConsoleUrl = async() => (await ext.clients.rpcClient.getConfigFromCli()).devantConsoleUrl; + + // Auth state subscriptions + public subscribeAuthState = (callback: (state: AuthState)=>void) => authStore.subscribe((state)=>callback(state.state)); + public subscribeIsLoggedIn = (callback: (isLoggedIn: boolean)=>void) => authStore.subscribe((state)=>callback(!!state.state?.userInfo)); + + // Context state subscriptions + public subscribeContextState = (callback: (state: ContextItemEnriched | undefined)=>void) => contextStore.subscribe((state)=>callback(state.state?.selected)); + public subscribeDirComponents = (fsPath: string, callback: (comps: ComponentKind[])=>void) => contextStore.subscribe((state)=>callback(this.getComponentsOfDir(fsPath, state.state.components))); } From 958451453b99f84244895a0d79e753fa6d4a9cda Mon Sep 17 00:00:00 2001 From: gigara Date: Sat, 13 Dec 2025 00:12:05 +0530 Subject: [PATCH 37/41] Refactor authentication handling by removing authStore and integrating WSO2AuthenticationProvider - Removed all references to authStore and replaced them with ext.authProvider for user authentication state management. - Updated commands related to user login, logout, and context management to utilize the new authentication provider. - Ensured that user information retrieval and context updates are consistent with the new authentication flow. - Deleted the authStore implementation and adjusted related stores and utilities to maintain functionality. --- .../src/PlatformExtensionApi.ts | 3 +- .../src/auth/wso2-auth-provider.ts | 406 ++++++++++++++++++ .../src/cmds/clone-project-cmd.ts | 3 +- .../src/cmds/cmd-utils.ts | 6 +- .../src/cmds/create-component-cmd.ts | 12 +- .../src/cmds/refresh-directory-context-cmd.ts | 3 +- .../src/cmds/sign-in-cmd.ts | 18 - .../src/cmds/sign-in-with-code-cmd.ts | 3 +- .../src/cmds/sign-out-cmd.ts | 5 +- .../src/error-utils.ts | 17 +- .../wso2-platform-extension/src/extension.ts | 29 +- .../src/extensionVariables.ts | 2 + .../wso2-platform-extension/src/status-bar.ts | 6 +- .../src/stores/auth-store.ts | 83 ---- .../src/stores/context-store.ts | 9 +- .../src/stores/data-cache-store.ts | 3 +- .../src/tarminal-handlers.ts | 5 +- .../src/telemetry/utils.ts | 6 +- .../src/uri-handlers.ts | 9 +- .../src/webviews/WebviewRPC.ts | 7 +- 20 files changed, 468 insertions(+), 167 deletions(-) create mode 100644 workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts delete mode 100644 workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts index d22e458c484..9bd82ea5f71 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts @@ -19,14 +19,13 @@ import type { ComponentKind, IWso2PlatformExtensionAPI, openClonedDirReq } from "@wso2/wso2-platform-core"; import { ext } from "./extensionVariables"; import { hasDirtyRepo } from "./git/util"; -import { authStore } from "./stores/auth-store"; import { contextStore } from "./stores/context-store"; import { webviewStateStore } from "./stores/webview-state-store"; import { openClonedDir } from "./uri-handlers"; import { isSamePath } from "./utils"; export class PlatformExtensionApi implements IWso2PlatformExtensionAPI { - public isLoggedIn = () => !!authStore.getState().state?.userInfo; + public isLoggedIn = () => !!ext.authProvider?.getState().state?.userInfo; public getDirectoryComponents = (fsPath: string) => (contextStore .getState() diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts new file mode 100644 index 00000000000..7e68b2cb8c8 --- /dev/null +++ b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts @@ -0,0 +1,406 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import type { AuthState, UserInfo } from "@wso2/wso2-platform-core"; +import { + type AuthenticationProvider, + type AuthenticationProviderAuthenticationSessionsChangeEvent, + type AuthenticationProviderSessionOptions, + type AuthenticationSession, + Disposable, + EventEmitter, + type SecretStorage, + window, +} from "vscode"; +import { ext } from "../extensionVariables"; +import { getLogger } from "../logger/logger"; +import { contextStore } from "../stores/context-store"; +import { dataCacheStore } from "../stores/data-cache-store"; + +export const WSO2_AUTH_PROVIDER_ID = "wso2-platform"; +const WSO2_SESSIONS_SECRET_KEY = `${WSO2_AUTH_PROVIDER_ID}.sessions`; + +interface SessionData { + id: string; + accessToken: string; + account: { + id: string; + label: string; + }; + scopes: string[]; + userInfo: UserInfo; + region: "US" | "EU"; +} + +export class WSO2AuthenticationProvider implements AuthenticationProvider, Disposable { + private _sessionChangeEmitter = new EventEmitter(); + private _stateChangeEmitter = new EventEmitter<{ state: AuthState }>(); + private _disposable: Disposable; + private _state: AuthState = { userInfo: null, region: "US" }; + + constructor(private readonly secretStorage: SecretStorage) { + this._disposable = Disposable.from(this._sessionChangeEmitter, this._stateChangeEmitter); + } + + get onDidChangeSessions() { + return this._sessionChangeEmitter.event; + } + + /** + * Subscribe to auth state changes + */ + public subscribe(callback: (store: { state: AuthState }) => void): () => void { + const disposable = this._stateChangeEmitter.event(callback); + // Call immediately with current state + callback({ state: this._state }); + return () => disposable.dispose(); + } + + /** + * Get the current state + */ + public getState() { + return { + state: this._state, + resetState: this.resetState.bind(this), + loginSuccess: this.loginSuccess.bind(this), + logout: this.logout.bind(this), + initAuth: this.initAuth.bind(this), + }; + } + + /** + * Get current auth state + */ + get state(): AuthState { + return this._state; + } + + /** + * Get the existing sessions + */ + public async getSessions(scopes: readonly string[] | undefined, options: AuthenticationProviderSessionOptions): Promise { + const allSessions = await this.readSessions(); + + if (scopes && scopes.length > 0) { + const sessions = allSessions.filter((session) => scopes.every((scope) => session.scopes.includes(scope))); + return sessions; + } + + return allSessions; + } + + /** + * Create a new auth session - NOT USED (auth is handled by RPC) + * This is required by the AuthenticationProvider interface but not called directly + */ + public async createSession(scopes: string[]): Promise { + throw new Error("Direct session creation not supported. Use RPC authentication flow."); + } + + /** + * Reset state to initial values + */ + public resetState() { + this._state = { userInfo: null, region: "US" }; + this._stateChangeEmitter.fire({ state: this._state }); + } + + /** + * Handle successful login - updates state and stores session + */ + public async loginSuccess(userInfo: UserInfo, region: "US" | "EU") { + // Update local state + this._state = { userInfo, region }; + + // Update related stores + dataCacheStore.getState().setOrgs(userInfo.organizations); + contextStore.getState().refreshState(); + + // Store session in secure storage + await this.storeSession(userInfo, region); + + // Notify subscribers + this._stateChangeEmitter.fire({ state: this._state }); + } + + /** + * Handle logout - signs out from RPC and clears all state + */ + public async logout(silent = false, skipClearSessions = false) { + getLogger().debug("Signing out from WSO2 Platform"); + + // Call RPC signOut first + try { + await ext.clients.rpcClient.signOut(); + } catch (error) { + getLogger().error("Error during RPC signOut", error); + } + + // Clear VS Code session storage (unless already cleared by removeSession) + if (!skipClearSessions) { + try { + await this.clearSessions(); + } catch (error) { + getLogger().error("Error clearing sessions", error); + } + } + + // Clear local state + this.resetState(); + + if (!silent) { + window.showInformationMessage("Successfully signed out from WSO2 Platform!"); + } + } + + /** + * Initialize authentication on startup + */ + public async initAuth() { + try { + // Try to get session from VS Code secure storage first + const sessionData = await this.getSessionData(); + if (sessionData?.userInfo) { + // We have a stored session, update the state + this._state = { userInfo: sessionData.userInfo, region: sessionData.region }; + dataCacheStore.getState().setOrgs(sessionData.userInfo.organizations); + contextStore.getState().refreshState(); + this._stateChangeEmitter.fire({ state: this._state }); + + const contextStoreState = contextStore.getState().state; + if (contextStoreState.selected?.org) { + ext?.clients?.rpcClient?.changeOrgContext(contextStoreState.selected?.org?.id?.toString()); + } + return; + } + + // Fallback: check RPC for existing session + const userInfo = await ext.clients.rpcClient.getUserInfo(); + if (userInfo) { + const region = await ext.clients.rpcClient.getCurrentRegion(); + await this.loginSuccess(userInfo, region); + const contextStoreState = contextStore.getState().state; + if (contextStoreState.selected?.org) { + ext?.clients?.rpcClient?.changeOrgContext(contextStoreState.selected?.org?.id?.toString()); + } + } else { + await this.logout(true); + } + } catch (err) { + getLogger().error("Error during auth initialization", err); + await this.logout(true); + } + } + + /** + * Store or update a session with user info and region + * Called internally after successful RPC authentication + */ + private async storeSession(userInfo: UserInfo, region: "US" | "EU"): Promise { + // Remove any existing sessions first (single account support) + const existingSessions = await this.readSessions(); + const removedSessions = [...existingSessions]; + + const sessionId = this.generateSessionId(); + const sessionData: SessionData = { + id: sessionId, + accessToken: "rpc-authenticated", // Placeholder since RPC handles auth + account: { + label: userInfo.displayName || userInfo.userEmail, + id: userInfo.userId, + }, + scopes: [], + userInfo, + region, + }; + + const session: AuthenticationSession = { + id: sessionData.id, + accessToken: sessionData.accessToken, + account: sessionData.account, + scopes: sessionData.scopes, + }; + + await this.storeSessions([session], sessionData); + + this._sessionChangeEmitter.fire({ + added: [session], + removed: removedSessions, + changed: [] + }); + + return session; + } + + /** + * Remove an existing session + * This is called when user signs out from VS Code's Accounts menu + */ + public async removeSession(sessionId: string): Promise { + const allSessions = await this.readSessions(); + const sessionIdx = allSessions.findIndex((s) => s.id === sessionId); + const session = allSessions[sessionIdx]; + if (!session) { + return; + } + + allSessions.splice(sessionIdx, 1); + await this.storeSessions(allSessions); + this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] }); + + // Trigger full logout flow (skipClearSessions=true to avoid loop) + await this.logout(false, true); + } + + /** + * Remove all sessions + */ + public async clearSessions(): Promise { + const allSessions = await this.readSessions(); + if (allSessions.length === 0) { + return; + } + + await this.secretStorage.delete(WSO2_SESSIONS_SECRET_KEY); + + this._sessionChangeEmitter.fire({ added: [], removed: allSessions, changed: [] }); + } + + /** + * Get session data including userInfo and region + */ + public async getSessionData(sessionId?: string): Promise { + const sessions = await this.readSessionsData(); + if (sessionId) { + return sessions.find((s) => s.id === sessionId); + } + // Return the first session if no ID is provided (single account support) + return sessions[0]; + } + + /** + * Dispose the provider + */ + public async dispose() { + this._disposable.dispose(); + } + + /** + * Get the user info from stored session (for backward compatibility) + */ + public getUserInfo(): UserInfo | null { + return this._state.userInfo; + } + + /** + * Get the region from stored session (for backward compatibility) + */ + public getRegion(): "US" | "EU" { + return this._state.region; + } + + /** + * Read sessions from secret storage + */ + private async readSessions(): Promise { + try { + const sessionsJson = await this.secretStorage.get(WSO2_SESSIONS_SECRET_KEY); + if (!sessionsJson) { + return []; + } + + const sessionData: SessionData[] = JSON.parse(sessionsJson); + return sessionData.map((data) => ({ + id: data.id, + accessToken: data.accessToken, + account: data.account, + scopes: data.scopes, + })); + } catch (e) { + getLogger().error("Error reading sessions", e); + return []; + } + } + + /** + * Store sessions to secret storage + */ + private async storeSessions(sessions: readonly AuthenticationSession[], newSessionData?: SessionData): Promise { + try { + const existingSessions = await this.readSessionsData(); + let updatedSessions: SessionData[]; + + if (newSessionData) { + // Add or update session + const existingIndex = existingSessions.findIndex((s) => s.id === newSessionData.id); + if (existingIndex >= 0) { + updatedSessions = [...existingSessions]; + updatedSessions[existingIndex] = newSessionData; + } else { + updatedSessions = [...existingSessions, newSessionData]; + } + } else { + // Filter out removed sessions + const sessionIds = sessions.map((s) => s.id); + updatedSessions = existingSessions.filter((s) => sessionIds.includes(s.id)); + } + + await this.secretStorage.store(WSO2_SESSIONS_SECRET_KEY, JSON.stringify(updatedSessions)); + } catch (e) { + getLogger().error("Error storing sessions", e); + } + } + + /** + * Read full session data including userInfo and region + */ + private async readSessionsData(): Promise { + try { + const sessionsJson = await this.secretStorage.get(WSO2_SESSIONS_SECRET_KEY); + if (!sessionsJson) { + return []; + } + + return JSON.parse(sessionsJson); + } catch (e) { + getLogger().error("Error reading session data", e); + return []; + } + } + + /** + * Generate a session ID + */ + private generateSessionId(): string { + return `wso2-${Date.now()}-${Math.random().toString(36).substring(2)}`; + } +} + +/** + * Helper function to wait for user login + */ +export const waitForLogin = async (): Promise => { + return new Promise((resolve) => { + ext.authProvider?.subscribe(({ state }) => { + if (state.userInfo) { + resolve(state.userInfo); + } + }); + }); +}; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts index 41880c2f6e8..18ebf65df0a 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts @@ -31,7 +31,6 @@ import { import { type ExtensionContext, ProgressLocation, type QuickPickItem, QuickPickItemKind, Uri, commands, window } from "vscode"; import { ext } from "../extensionVariables"; import { initGit } from "../git/main"; -import { authStore } from "../stores/auth-store"; import { dataCacheStore } from "../stores/data-cache-store"; import { webviewStateStore } from "../stores/webview-state-store"; import { createDirectory, openDirectory } from "../utils"; @@ -168,7 +167,7 @@ export function cloneRepoCommand(context: ExtensionContext) { ]); // set context.yaml - updateContextFile(clonedResp[0].clonedPath, authStore.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); + updateContextFile(clonedResp[0].clonedPath, ext.authProvider?.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); const subDir = params?.component?.spec?.source ? getComponentKindRepoSource(params?.component?.spec?.source)?.path || "" : ""; const subDirFullPath = join(clonedResp[0].clonedPath, subDir); if (params?.technology === "ballerina") { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/cmd-utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/cmd-utils.ts index da900aeedcf..01f4863135d 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/cmd-utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/cmd-utils.ts @@ -19,7 +19,7 @@ import { CommandIds, type ComponentKind, type ExtensionName, type Organization, type Project, type UserInfo } from "@wso2/wso2-platform-core"; import { ProgressLocation, type QuickPickItem, QuickPickItemKind, type WorkspaceFolder, commands, window, workspace } from "vscode"; import { type ExtensionVariables, ext } from "../extensionVariables"; -import { authStore, waitForLogin } from "../stores/auth-store"; +import { waitForLogin } from "../auth/wso2-auth-provider"; import { dataCacheStore } from "../stores/data-cache-store"; import { webviewStateStore } from "../stores/webview-state-store"; @@ -326,7 +326,7 @@ export async function quickPickWithLoader(params: { } export const getUserInfoForCmd = async (message: string): Promise => { - let userInfo = authStore.getState().state.userInfo; + let userInfo = ext.authProvider?.getState().state.userInfo; const extensionName = webviewStateStore.getState().state.extensionName; if (!userInfo) { const loginSelection = await window.showInformationMessage( @@ -351,7 +351,7 @@ export const getUserInfoForCmd = async (message: string): Promise { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts index ed5d6337a67..e7dd6fac0a8 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts @@ -20,15 +20,12 @@ import { existsSync, readFileSync } from "fs"; import * as os from "os"; import * as path from "path"; import { - ChoreoBuildPackNames, ChoreoComponentType, CommandIds, type ComponentKind, DevantScopes, type ExtensionName, type ICreateComponentCmdParams, - type Organization, - type Project, type SubmitComponentCreateReq, type WorkspaceConfig, getComponentKindRepoSource, @@ -37,16 +34,15 @@ import { getTypeOfIntegrationType, parseGitURL, } from "@wso2/wso2-platform-core"; -import { type ExtensionContext, ProgressLocation, type QuickPickItem, Uri, commands, env, window, workspace } from "vscode"; +import { type ExtensionContext, ProgressLocation, type QuickPickItem, Uri, commands, window, workspace } from "vscode"; import { ext } from "../extensionVariables"; import { initGit } from "../git/main"; import { getGitRemotes, getGitRoot } from "../git/util"; import { getLogger } from "../logger/logger"; -import { authStore } from "../stores/auth-store"; import { contextStore, waitForContextStoreToLoad } from "../stores/context-store"; import { dataCacheStore } from "../stores/data-cache-store"; import { webviewStateStore } from "../stores/webview-state-store"; -import { convertFsPathToUriPath, delay, isSamePath, isSubpath, openDirectory } from "../utils"; +import { convertFsPathToUriPath, isSamePath, isSubpath, openDirectory } from "../utils"; import { showComponentDetailsView } from "../webviews/ComponentDetailsView"; import { ComponentFormView, type IComponentCreateFormParams } from "../webviews/ComponentFormView"; import { getUserInfoForCmd, isRpcActive, selectOrg, selectProjectWithCreateNew, setExtensionName } from "./cmd-utils"; @@ -221,7 +217,7 @@ export function createNewComponentCommand(context: ExtensionContext) { ); if (resp !== "Proceed") { const projectCache = dataCacheStore.getState().getProjects(selectedOrg?.handle); - updateContextFile(gitRoot, authStore.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); + updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); contextStore.getState().refreshState(); return; } @@ -362,7 +358,7 @@ export const submitCreateComponentHandler = async ({ createParams, org, project } } } else { - updateContextFile(gitRoot, authStore.getState().state.userInfo!, project, org, projectCache); + updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, project, org, projectCache); contextStore.getState().refreshState(); } } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/refresh-directory-context-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/refresh-directory-context-cmd.ts index 3903645d3f0..b41e159c173 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/refresh-directory-context-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/refresh-directory-context-cmd.ts @@ -19,7 +19,6 @@ import { CommandIds, type ICmdParamsBase } from "@wso2/wso2-platform-core"; import { type ExtensionContext, commands, window } from "vscode"; import { ext } from "../extensionVariables"; -import { authStore } from "../stores/auth-store"; import { contextStore } from "../stores/context-store"; import { isRpcActive, setExtensionName } from "./cmd-utils"; @@ -29,7 +28,7 @@ export function refreshContextCommand(context: ExtensionContext) { try { isRpcActive(ext); setExtensionName(params?.extName); - const userInfo = authStore.getState().state.userInfo; + const userInfo = ext.authProvider?.getState().state.userInfo; if (!userInfo) { throw new Error("You are not logged in. Please log in and retry."); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts index 589900bb5b1..239cc43e218 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts @@ -1,21 +1,3 @@ -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - import { CommandIds, type ICmdParamsBase } from "@wso2/wso2-platform-core"; import { type ExtensionContext, ProgressLocation, commands, window } from "vscode"; import * as vscode from "vscode"; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts index 1825dba26d6..159f6a2da11 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts @@ -23,7 +23,6 @@ import { ResponseError } from "vscode-jsonrpc"; import { ErrorCode } from "../choreo-rpc/constants"; import { ext } from "../extensionVariables"; import { getLogger } from "../logger/logger"; -import { authStore } from "../stores/auth-store"; import { isRpcActive, setExtensionName } from "./cmd-utils"; export function signInWithAuthCodeCommand(context: ExtensionContext) { @@ -45,7 +44,7 @@ export function signInWithAuthCodeCommand(context: ExtensionContext) { ext.clients.rpcClient.signInWithAuthCode(authCode).then(async (userInfo) => { if (userInfo) { const region = await ext.clients.rpcClient.getCurrentRegion(); - authStore.getState().loginSuccess(userInfo, region); + ext.authProvider?.getState().loginSuccess(userInfo, region); } }); } else { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-out-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-out-cmd.ts index ccd9bf49dfb..61e0a42b46d 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-out-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-out-cmd.ts @@ -20,7 +20,6 @@ import { CommandIds } from "@wso2/wso2-platform-core"; import { type ExtensionContext, commands, window } from "vscode"; import { ext } from "../extensionVariables"; import { getLogger } from "../logger/logger"; -import { authStore } from "../stores/auth-store"; import { isRpcActive } from "./cmd-utils"; export function signOutCommand(context: ExtensionContext) { @@ -28,9 +27,7 @@ export function signOutCommand(context: ExtensionContext) { commands.registerCommand(CommandIds.SignOut, async () => { try { isRpcActive(ext); - getLogger().debug("Signing out from WSO2 Platform"); - authStore.getState().logout(); - window.showInformationMessage("Successfully signed out from WSO2 Platform!"); + ext.authProvider?.getState().logout(); } catch (error: any) { getLogger().error(`Error while signing out from WSO2 Platform. ${error?.message}${error?.cause ? `\nCause: ${error.cause.message}` : ""}`); if (error instanceof Error) { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts index cde75f4fc3b..c8f83763278 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts @@ -21,7 +21,6 @@ import { ResponseError } from "vscode-jsonrpc"; import { ErrorCode } from "./choreo-rpc/constants"; import { ext } from "./extensionVariables"; import { getLogger } from "./logger/logger"; -import { authStore } from "./stores/auth-store"; import { webviewStateStore } from "./stores/webview-state-store"; export function handlerError(err: any) { @@ -44,20 +43,20 @@ export function handlerError(err: any) { getLogger().error("InternalError", err); break; case ErrorCode.UnauthorizedError: - if (authStore.getState().state?.userInfo) { - authStore.getState().logout(); + if (ext.authProvider?.getState().state?.userInfo) { + ext.authProvider?.getState().logout(); w.showErrorMessage("Unauthorized. Please sign in again."); } break; case ErrorCode.TokenNotFoundError: - if (authStore.getState().state?.userInfo) { - authStore.getState().logout(); + if (ext.authProvider?.getState().state?.userInfo) { + ext.authProvider?.getState().logout(); w.showErrorMessage("Token not found. Please sign in again."); } break; case ErrorCode.InvalidTokenError: - if (authStore.getState().state?.userInfo) { - authStore.getState().logout(); + if (ext.authProvider?.getState().state?.userInfo) { + ext.authProvider?.getState().logout(); w.showErrorMessage("Invalid token. Please sign in again."); } break; @@ -65,8 +64,8 @@ export function handlerError(err: any) { getLogger().error("ForbiddenError", err); break; case ErrorCode.RefreshTokenError: - if (authStore.getState().state?.userInfo) { - authStore.getState().logout(); + if (ext.authProvider?.getState().state?.userInfo) { + ext.authProvider?.getState().logout(); w.showErrorMessage("Failed to refresh user session. Please sign in again."); } break; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts index 93d2cf5d1eb..fc41f6841ab 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts @@ -17,7 +17,8 @@ */ import * as vscode from "vscode"; -import { type ConfigurationChangeEvent, commands, window, workspace } from "vscode"; +import { type ConfigurationChangeEvent, authentication, commands, window, workspace } from "vscode"; +import { WSO2AuthenticationProvider, WSO2_AUTH_PROVIDER_ID } from "./auth/wso2-auth-provider"; import { PlatformExtensionApi } from "./PlatformExtensionApi"; import { ChoreoRPCClient } from "./choreo-rpc"; import { initRPCServer } from "./choreo-rpc/activate"; @@ -30,7 +31,6 @@ import { ext } from "./extensionVariables"; import { getLogger, initLogger } from "./logger/logger"; import { activateChoreoMcp } from "./mcp"; import { activateStatusbar } from "./status-bar"; -import { authStore } from "./stores/auth-store"; import { contextStore } from "./stores/context-store"; import { dataCacheStore } from "./stores/data-cache-store"; import { locationStore } from "./stores/location-store"; @@ -53,15 +53,12 @@ export async function activate(context: vscode.ExtensionContext) { getLogger().info(`CLI version: ${getCliVersion()}`); // Initialize stores - await authStore.persist.rehydrate(); await contextStore.persist.rehydrate(); await dataCacheStore.persist.rehydrate(); await locationStore.persist.rehydrate(); // Set context values - authStore.subscribe(({ state }) => { - vscode.commands.executeCommand("setContext", "isLoggedIn", !!state.userInfo); - }); + // Note: authProvider will be set up below, so we'll subscribe to it in initAuth contextStore.subscribe(({ state }) => { vscode.commands.executeCommand("setContext", "isLoadingContextDirs", state.loading); vscode.commands.executeCommand("setContext", "hasSelectedProject", !!state.selected); @@ -74,9 +71,23 @@ export async function activate(context: vscode.ExtensionContext) { const rpcClient = new ChoreoRPCClient(); ext.clients = { rpcClient: rpcClient }; - await initRPCServer() + // Initialize and register authentication provider + const authProvider = new WSO2AuthenticationProvider(context.secrets); + ext.authProvider = authProvider; + context.subscriptions.push( + authentication.registerAuthenticationProvider(WSO2_AUTH_PROVIDER_ID, "WSO2 Platform", authProvider, { + supportsMultipleAccounts: false, + }), + ); + + // Subscribe to auth state changes + authProvider.subscribe(({ state }) => { + vscode.commands.executeCommand("setContext", "isLoggedIn", !!state.userInfo); + }); + + await initRPCServer(); await ext.clients.rpcClient.init(); - authStore.getState().initAuth(); + authProvider.getState().initAuth(); continueCreateComponent(); if (ext.isChoreoExtInstalled) { addTerminalHandlers(); @@ -116,7 +127,7 @@ function registerPreInitHandlers(): any { ); if (selection === "Restart Now") { if (affectsConfiguration("WSO2.WSO2-Platform.Advanced.ChoreoEnvironment")) { - authStore.getState().logout(); + ext.authProvider?.getState().logout(); } commands.executeCommand("workbench.action.reloadWindow"); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts b/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts index ac693995485..8002472c69e 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts @@ -18,6 +18,7 @@ import type { GetCliRpcResp } from "@wso2/wso2-platform-core"; import { type ExtensionContext, type StatusBarItem, extensions } from "vscode"; +import type { WSO2AuthenticationProvider } from "./auth/wso2-auth-provider"; import type { PlatformExtensionApi } from "./PlatformExtensionApi"; import type { ChoreoRPCClient } from "./choreo-rpc"; @@ -30,6 +31,7 @@ export class ExtensionVariables { public choreoEnv: string; public isChoreoExtInstalled: boolean; public isDevantCloudEditor: boolean; + public authProvider?: WSO2AuthenticationProvider; public constructor() { this.choreoEnv = "prod"; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/status-bar.ts b/workspaces/wso2-platform/wso2-platform-extension/src/status-bar.ts index 9c7a9b95a61..fa7baa8059e 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/status-bar.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/status-bar.ts @@ -1,6 +1,6 @@ import { type AuthState, CommandIds, type ContextStoreState, type WebviewState } from "@wso2/wso2-platform-core"; import { type ExtensionContext, StatusBarAlignment, type StatusBarItem, window } from "vscode"; -import { authStore } from "./stores/auth-store"; +import { ext } from "./extensionVariables"; import { contextStore } from "./stores/context-store"; import { webviewStateStore } from "./stores/webview-state-store"; @@ -12,14 +12,14 @@ export function activateStatusbar({ subscriptions }: ExtensionContext) { subscriptions.push(statusBarItem); let webviewState: WebviewState = webviewStateStore.getState()?.state; - let authState: AuthState | null = authStore.getState()?.state; + let authState: AuthState | undefined = ext.authProvider?.getState()?.state; let contextStoreState: ContextStoreState | null = contextStore.getState()?.state; webviewStateStore.subscribe((state) => { webviewState = state.state; updateStatusBarItem(webviewState, authState, contextStoreState); }); - authStore.subscribe((state) => { + ext.authProvider?.subscribe((state) => { authState = state.state; updateStatusBarItem(webviewState, authState, contextStoreState); }); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts deleted file mode 100644 index 923cbdda230..00000000000 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import type { AuthState, Organization, UserInfo } from "@wso2/wso2-platform-core"; -import { createStore } from "zustand"; -import { persist } from "zustand/middleware"; -import { ext } from "../extensionVariables"; -import { contextStore } from "./context-store"; -import { dataCacheStore } from "./data-cache-store"; -import { getGlobalStateStore } from "./store-utils"; - -interface AuthStore { - state: AuthState; - resetState: () => void; - loginSuccess: (userInfo: UserInfo, region: "US" | "EU") => void; - logout: () => Promise; - initAuth: () => Promise; -} - -const initialState: AuthState = { userInfo: null, region: "US" }; - -export const authStore = createStore( - persist( - (set, get) => ({ - state: initialState, - resetState: () => set(() => ({ state: initialState })), - loginSuccess: (userInfo, region) => { - dataCacheStore.getState().setOrgs(userInfo.organizations); - set(({ state }) => ({ state: { ...state, userInfo, region } })); - contextStore.getState().refreshState(); - }, - logout: async () => { - get().resetState(); - ext.clients.rpcClient.signOut().catch(() => { - // ignore error - }); - }, - initAuth: async () => { - try { - const userInfo = await ext.clients.rpcClient.getUserInfo(); - if (userInfo) { - const region = await ext.clients.rpcClient.getCurrentRegion(); - get().loginSuccess(userInfo, region); - const contextStoreState = contextStore.getState().state; - if (contextStoreState.selected?.org) { - ext?.clients?.rpcClient?.changeOrgContext(contextStoreState.selected?.org?.id?.toString()); - } - } else { - get().logout(); - } - } catch (err) { - get().logout(); - } - }, - }), - getGlobalStateStore("auth-zustand-storage"), - ), -); - -export const waitForLogin = async (): Promise => { - return new Promise((resolve) => { - authStore.subscribe(({ state }) => { - if (state.userInfo) { - resolve(state.userInfo); - } - }); - }); -}; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts index 2120c34ceb9..ff59fef387d 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts @@ -37,7 +37,6 @@ import { persist } from "zustand/middleware"; import { ext } from "../extensionVariables"; import { getGitRemotes, getGitRoot } from "../git/util"; import { isSamePath, isSubpath } from "../utils"; -import { authStore } from "./auth-store"; import { dataCacheStore } from "./data-cache-store"; import { locationStore } from "./location-store"; import { getWorkspaceStateStore } from "./store-utils"; @@ -60,7 +59,7 @@ export const contextStore = createStore( resetState: () => set(() => ({ state: initialState })), refreshState: async () => { try { - if (authStore.getState().state?.userInfo) { + if (ext.authProvider?.getState().state?.userInfo) { set(({ state }) => ({ state: { ...state, loading: true } })); let items = await getAllContexts(get().state?.items); let selected = await getSelected(items, get().state?.selected); @@ -153,7 +152,7 @@ const getAllContexts = async (previousItems: { [key: string]: ContextItemEnriche } else if (previousItems?.[key]?.org && previousItems?.[key].project) { contextItems[key] = { ...previousItems?.[key], contextDirs: [contextDir] }; } else { - const userOrgs = authStore.getState().state.userInfo?.organizations; + const userOrgs = ext.authProvider?.getState().state.userInfo?.organizations; const matchingOrg = userOrgs?.find((item) => item.handle === contextItem.org); const projectsOfOrg = dataCacheStore.getState().getProjects(contextItem.org); @@ -199,7 +198,7 @@ const getAllContexts = async (previousItems: { [key: string]: ContextItemEnriche const getSelected = async (items: { [key: string]: ContextItemEnriched }, prevSelected?: ContextItemEnriched) => { if (ext.isDevantCloudEditor && process.env.CLOUD_INITIAL_ORG_ID && process.env.CLOUD_INITIAL_PROJECT_ID) { // Give priority to project provided as env variable, when running in the cloud editor - const userOrgs = authStore.getState().state.userInfo?.organizations; + const userOrgs = ext.authProvider?.getState().state.userInfo?.organizations; const matchingOrg = userOrgs?.find( (item) => item.uuid === process.env.CLOUD_INITIAL_ORG_ID || item.id?.toString() === process.env.CLOUD_INITIAL_ORG_ID, ); @@ -261,7 +260,7 @@ const getSelected = async (items: { [key: string]: ContextItemEnriched }, prevSe }; const getEnrichedContexts = async (items: { [key: string]: ContextItemEnriched }) => { - const userOrgs = authStore.getState().state.userInfo?.organizations; + const userOrgs = ext.authProvider?.getState().state.userInfo?.organizations; const orgsSet = new Set(); Object.values(items).forEach((item) => { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts index d57723e2db3..c4e35b6b9a7 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts @@ -20,7 +20,6 @@ import type { CommitHistory, ComponentKind, DataCacheState, Environment, Organiz import { createStore } from "zustand"; import { persist } from "zustand/middleware"; import { ext } from "../extensionVariables"; -import { authStore } from "./auth-store"; import { getGlobalStateStore } from "./store-utils"; interface DataCacheStore { @@ -161,7 +160,7 @@ export const dataCacheStore = createStore( ); const getRootKey = (orgHandle: string) => { - const region = authStore.getState().state.region; + const region = ext.authProvider?.getState().state.region; const env = ext.choreoEnv; let orgRegionHandle = `${region}-${orgHandle}`; if (env !== "prod") { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts b/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts index 4963fea8654..f1763451667 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts @@ -20,10 +20,9 @@ import { CommandIds, type ComponentKind } from "@wso2/wso2-platform-core"; import type vscode from "vscode"; import { commands, window, workspace } from "vscode"; import { getChoreoExecPath } from "./choreo-rpc/cli-install"; -import { authStore } from "./stores/auth-store"; import { contextStore } from "./stores/context-store"; -import { dataCacheStore } from "./stores/data-cache-store"; import { delay, getSubPath } from "./utils"; +import { ext } from "./extensionVariables"; export class ChoreoConfigurationProvider implements vscode.DebugConfigurationProvider { resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration): vscode.DebugConfiguration | undefined { @@ -65,7 +64,7 @@ export function addTerminalHandlers() { let cliCommand = e.name.split("[choreo-shell]").pop()?.replaceAll(")", ""); const terminalPath = (e.creationOptions as any)?.cwd; const rpcPath = getChoreoExecPath(); - const userInfo = authStore.getState().state?.userInfo; + const userInfo = ext.authProvider?.getState().state?.userInfo; if (terminalPath) { if (!e.name?.includes("--project")) { window diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts index 35b4aa7a182..60a1b700c3c 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts @@ -16,7 +16,7 @@ * under the License. */ -import { authStore } from "../stores/auth-store"; +import { ext } from "../extensionVariables"; import { getTelemetryReporter } from "./telemetry"; // export async function sendProjectTelemetryEvent(eventName: string, properties?: { [key: string]: string; }, measurements?: { [key: string]: number; }) { @@ -46,8 +46,8 @@ export function sendTelemetryException(error: Error, properties?: { [key: string // Create common properties for all events export function getCommonProperties(): { [key: string]: string } { return { - idpId: authStore.getState().state?.userInfo?.userId!, + idpId: ext.authProvider?.getState().state?.userInfo?.userId!, // check if the email ends with "@wso2.com" - isWSO2User: authStore.getState().state?.userInfo?.userEmail?.endsWith("@wso2.com") ? "true" : "false", + isWSO2User: ext.authProvider?.getState().state?.userInfo?.userEmail?.endsWith("@wso2.com") ? "true" : "false", }; } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts b/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts index b413bfe0b4c..592804e8f49 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts @@ -36,7 +36,6 @@ import { updateContextFile } from "./cmds/create-directory-context-cmd"; import { ext } from "./extensionVariables"; import { getGitRemotes, getGitRoot } from "./git/util"; import { getLogger } from "./logger/logger"; -import { authStore } from "./stores/auth-store"; import { contextStore, getContextKey, waitForContextStoreToLoad } from "./stores/context-store"; import { dataCacheStore } from "./stores/data-cache-store"; import { locationStore } from "./stores/location-store"; @@ -81,9 +80,9 @@ export function activateURIHandlers() { contextStore.getState().resetState(); } } - const region = await ext.clients.rpcClient.getCurrentRegion(); - authStore.getState().loginSuccess(userInfo, region); - window.showInformationMessage(`Successfully signed into ${extName}`); + const region = await ext.clients.rpcClient.getCurrentRegion(); + await ext.authProvider?.getState().loginSuccess(userInfo, region); + window.showInformationMessage(`Successfully signed into ${extName}`); } } catch (error: any) { if (!(error instanceof ResponseError) || ![ErrorCode.NoOrgsAvailable, ErrorCode.NoAccountAvailable].includes(error.code)) { @@ -265,7 +264,7 @@ const switchContextAndOpenDir = async (selectedPath: string, org: Organization, return; } const projectCache = dataCacheStore.getState().getProjects(org?.handle); - const contextFilePath = updateContextFile(gitRoot, authStore.getState().state.userInfo!, project, org, projectCache); + const contextFilePath = updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, project, org, projectCache); const isWithinWorkspace = workspace.workspaceFolders?.some((item) => isSamePath(item.uri?.fsPath, selectedPath)); if (isWithinWorkspace) { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts index 762dc2973fa..3e181a7a8ea 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts @@ -123,7 +123,6 @@ import { ext } from "../extensionVariables"; import { initGit } from "../git/main"; import { getGitHead, getGitRemotes, getGitRoot, hasDirtyRepo, removeCredentialsFromGitURL } from "../git/util"; import { getLogger } from "../logger/logger"; -import { authStore } from "../stores/auth-store"; import { contextStore } from "../stores/context-store"; import { dataCacheStore } from "../stores/data-cache-store"; import { webviewStateStore } from "../stores/webview-state-store"; @@ -132,11 +131,11 @@ import { getConfigFileDrifts, getNormalizedPath, getSubPath, goTosource, readLoc // Register handlers function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | WebviewView) { - authStore.subscribe((store) => messenger.sendNotification(AuthStoreChangedNotification, BROADCAST, store.state)); + ext.authProvider?.subscribe((store) => messenger.sendNotification(AuthStoreChangedNotification, BROADCAST, store.state)); webviewStateStore.subscribe((store) => messenger.sendNotification(WebviewStateChangedNotification, BROADCAST, store.state)); contextStore.subscribe((store) => messenger.sendNotification(ContextStoreChangedNotification, BROADCAST, store.state)); - messenger.onRequest(GetAuthState, () => authStore.getState().state); + messenger.onRequest(GetAuthState, () => ext.authProvider?.getState().state); messenger.onRequest(GetWebviewStoreState, async () => webviewStateStore.getState().state); messenger.onRequest(GetContextState, async () => contextStore.getState().state); @@ -399,7 +398,7 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W rmSync(join(params.componentDir, ".choreo", "component-config.yaml")); } - const org = authStore?.getState().state?.userInfo?.organizations?.find((item) => item.uuid === params.marketplaceItem?.organizationId); + const org = ext.authProvider?.getState().state?.userInfo?.organizations?.find((item) => item.uuid === params.marketplaceItem?.organizationId); if (!org) { return; } From fc1cccbf2870b20b1abd1b26bf4eb6000e8ba985 Mon Sep 17 00:00:00 2001 From: gigara Date: Sat, 13 Dec 2025 00:25:38 +0530 Subject: [PATCH 38/41] Add license header --- .../src/cmds/sign-in-cmd.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts index 239cc43e218..589900bb5b1 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts @@ -1,3 +1,21 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + import { CommandIds, type ICmdParamsBase } from "@wso2/wso2-platform-core"; import { type ExtensionContext, ProgressLocation, commands, window } from "vscode"; import * as vscode from "vscode"; From 607be11ae2c5a5aa83fcc80d7834853e927e301c Mon Sep 17 00:00:00 2001 From: gigara Date: Sat, 13 Dec 2025 10:14:29 +0530 Subject: [PATCH 39/41] Enhance getAuthState method to provide default user info and region --- .../wso2-platform-extension/src/PlatformExtensionApi.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts index 2db5f700428..a3a2ae96902 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts @@ -31,7 +31,7 @@ export class PlatformExtensionApi implements IWso2PlatformExtensionAPI { ?.filter((item) => !!item) as ComponentKind[]) ?? [] } - public getAuthState = () => ext.authProvider?.getState().state; + public getAuthState = () => ext.authProvider?.getState().state ?? { userInfo: null, region: "US" as const }; public isLoggedIn = () => !!ext.authProvider?.getState().state?.userInfo; public getDirectoryComponents = (fsPath: string) => this.getComponentsOfDir(fsPath, contextStore.getState().state?.components); public localRepoHasChanges = (fsPath: string) => hasDirtyRepo(fsPath, ext.context, ["context.yaml"]); @@ -43,8 +43,8 @@ export class PlatformExtensionApi implements IWso2PlatformExtensionAPI { public getDevantConsoleUrl = async() => (await ext.clients.rpcClient.getConfigFromCli()).devantConsoleUrl; // Auth state subscriptions - public subscribeAuthState = (callback: (state: AuthState)=>void) => ext.authProvider?.subscribe((state)=>callback(state.state)); - public subscribeIsLoggedIn = (callback: (isLoggedIn: boolean)=>void) => ext.authProvider?.subscribe((state)=>callback(!!state.state?.userInfo)); + public subscribeAuthState = (callback: (state: AuthState)=>void) => ext.authProvider?.subscribe((state)=>callback(state.state)) ?? (() => {}); + public subscribeIsLoggedIn = (callback: (isLoggedIn: boolean)=>void) => ext.authProvider?.subscribe((state)=>callback(!!state.state?.userInfo)) ?? (() => {}); // Context state subscriptions public subscribeContextState = (callback: (state: ContextItemEnriched | undefined)=>void) => contextStore.subscribe((state)=>callback(state.state?.selected)); From f3f0e8d2e09d2adac3456a151f716f502510a5a8 Mon Sep 17 00:00:00 2001 From: Chamupathi Gigara Hettige Date: Sat, 13 Dec 2025 10:18:57 +0530 Subject: [PATCH 40/41] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/cmds/clone-project-cmd.ts | 6 +++++- .../src/cmds/create-component-cmd.ts | 17 ++++++++++++++--- .../src/telemetry/utils.ts | 2 +- .../wso2-platform-extension/src/uri-handlers.ts | 7 ++++++- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts index 18ebf65df0a..4d7178bea0d 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts @@ -167,7 +167,11 @@ export function cloneRepoCommand(context: ExtensionContext) { ]); // set context.yaml - updateContextFile(clonedResp[0].clonedPath, ext.authProvider?.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); + const userInfo = ext.authProvider?.getState()?.state?.userInfo; + if (!userInfo) { + throw new Error("User information is not available. Please ensure you are logged in."); + } + updateContextFile(clonedResp[0].clonedPath, userInfo, selectedProject, selectedOrg, projectCache); const subDir = params?.component?.spec?.source ? getComponentKindRepoSource(params?.component?.spec?.source)?.path || "" : ""; const subDirFullPath = join(clonedResp[0].clonedPath, subDir); if (params?.technology === "ballerina") { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts index e7dd6fac0a8..a65bd5732ed 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts @@ -217,7 +217,13 @@ export function createNewComponentCommand(context: ExtensionContext) { ); if (resp !== "Proceed") { const projectCache = dataCacheStore.getState().getProjects(selectedOrg?.handle); - updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); + const authProvider = ext.authProvider; + const userInfo = authProvider?.getState().state.userInfo; + if (!authProvider || !userInfo) { + window.showErrorMessage("User information is not available. Please sign in and try again."); + return; + } + updateContextFile(gitRoot, userInfo, selectedProject, selectedOrg, projectCache); contextStore.getState().refreshState(); return; } @@ -358,8 +364,13 @@ export const submitCreateComponentHandler = async ({ createParams, org, project } } } else { - updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, project, org, projectCache); - contextStore.getState().refreshState(); + const userInfo = ext.authProvider?.getState().state.userInfo; + if (userInfo) { + updateContextFile(gitRoot, userInfo, project, org, projectCache); + contextStore.getState().refreshState(); + } else { + getLogger().error("Cannot update context file: userInfo is undefined."); + } } } } catch (err) { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts index 60a1b700c3c..8a5f79e8407 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts @@ -46,7 +46,7 @@ export function sendTelemetryException(error: Error, properties?: { [key: string // Create common properties for all events export function getCommonProperties(): { [key: string]: string } { return { - idpId: ext.authProvider?.getState().state?.userInfo?.userId!, + idpId: ext.authProvider?.getState().state?.userInfo?.userId ?? "", // check if the email ends with "@wso2.com" isWSO2User: ext.authProvider?.getState().state?.userInfo?.userEmail?.endsWith("@wso2.com") ? "true" : "false", }; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts b/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts index 592804e8f49..3aa77705b58 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts @@ -264,7 +264,12 @@ const switchContextAndOpenDir = async (selectedPath: string, org: Organization, return; } const projectCache = dataCacheStore.getState().getProjects(org?.handle); - const contextFilePath = updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, project, org, projectCache); + const userInfo = ext.authProvider?.getState().state.userInfo; + if (!userInfo) { + window.showErrorMessage("User information is not available. Please sign in and try again."); + return; + } + const contextFilePath = updateContextFile(gitRoot, userInfo, project, org, projectCache); const isWithinWorkspace = workspace.workspaceFolders?.some((item) => isSamePath(item.uri?.fsPath, selectedPath)); if (isWithinWorkspace) { From bf5484255b0035da8717f2eb1bbf04545c9c64f6 Mon Sep 17 00:00:00 2001 From: gigara Date: Mon, 15 Dec 2025 11:02:58 +0530 Subject: [PATCH 41/41] Always check user status from choreo cli --- .../src/auth/wso2-auth-provider.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts index 7e68b2cb8c8..bad8b1f7a26 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts @@ -174,23 +174,6 @@ export class WSO2AuthenticationProvider implements AuthenticationProvider, Dispo */ public async initAuth() { try { - // Try to get session from VS Code secure storage first - const sessionData = await this.getSessionData(); - if (sessionData?.userInfo) { - // We have a stored session, update the state - this._state = { userInfo: sessionData.userInfo, region: sessionData.region }; - dataCacheStore.getState().setOrgs(sessionData.userInfo.organizations); - contextStore.getState().refreshState(); - this._stateChangeEmitter.fire({ state: this._state }); - - const contextStoreState = contextStore.getState().state; - if (contextStoreState.selected?.org) { - ext?.clients?.rpcClient?.changeOrgContext(contextStoreState.selected?.org?.id?.toString()); - } - return; - } - - // Fallback: check RPC for existing session const userInfo = await ext.clients.rpcClient.getUserInfo(); if (userInfo) { const region = await ext.clients.rpcClient.getCurrentRegion();