diff --git a/packages/salesforcedx-vscode-apex/src/commands/apexActionController.ts b/packages/salesforcedx-vscode-apex/src/commands/apexActionController.ts index 65240d1936..31548b65d0 100644 --- a/packages/salesforcedx-vscode-apex/src/commands/apexActionController.ts +++ b/packages/salesforcedx-vscode-apex/src/commands/apexActionController.ts @@ -4,16 +4,21 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { notificationService, workspaceUtils } from '@salesforce/salesforcedx-utils-vscode'; +import { notificationService, WorkspaceContextUtil, workspaceUtils } from '@salesforce/salesforcedx-utils-vscode'; import { RegistryAccess } from '@salesforce/source-deploy-retrieve-bundle'; import { XMLBuilder, XMLParser } from 'fast-xml-parser'; import * as fs from 'fs'; import * as path from 'path'; import { URL } from 'url'; import * as vscode from 'vscode'; +import { parse } from 'yaml'; import { workspaceContext } from '../context'; import { nls } from '../messages'; -import { ApexClassOASEligibleResponse, ApexClassOASGatherContextResponse } from '../openApiUtilities/schemas'; +import { + ApexClassOASEligibleResponse, + ApexClassOASGatherContextResponse, + ApexOASInfo +} from '../openApiUtilities/schemas'; import { getTelemetryService } from '../telemetry/telemetry'; import { MetadataOrchestrator } from './metadataOrchestrator'; @@ -129,7 +134,7 @@ export class ApexActionController { } const namedCredential = await this.showNamedCredentialsQuickPick(); - const updatedContent = this.buildESRXml(existingContent, fullPath, namedCredential, oasSpec); + const updatedContent = await this.buildESRXml(existingContent, fullPath, namedCredential, oasSpec); try { // Step 3: Write File fs.writeFileSync(fullPath, updatedContent); @@ -230,7 +235,7 @@ export class ApexActionController { return finalNamedCredential; }; - private buildESRXml = ( + private buildESRXml = async ( existingContent: string | undefined, fullPath: string, namedCredential: string | undefined, @@ -238,6 +243,11 @@ export class ApexActionController { ) => { const baseName = path.basename(fullPath).split('.')[0]; const safeOasSpec = oasSpec.replaceAll('"', ''').replaceAll('type: Id', 'type: string'); + const { description, version } = this.extractInfoProperties(safeOasSpec); + const orgVersion = await (await WorkspaceContextUtil.getInstance().getConnection()).retrieveMaxApiVersion(); + if (!orgVersion) { + throw new Error(nls.localize('error_retrieving_org_version')); + } const parser = new XMLParser({ ignoreAttributes: false }); let jsonObj; @@ -256,16 +266,29 @@ export class ApexActionController { '?xml': { '@_version': '1.0', '@_encoding': 'UTF-8' }, ExternalServiceRegistration: { '@_xmlns': 'http://soap.sforce.com/2006/04/metadata', - description: `${baseName} External Service`, + description, label: baseName, - namedCredentialReference: namedCredential ?? 'Type here the Named Credential', - registrationProviderType: 'Custom', schema: safeOasSpec, schemaType: 'OpenApi3', schemaUploadFileExtension: 'yaml', schemaUploadFileName: `${baseName.toLowerCase()}_openapi`, status: 'Complete', - systemVersion: '3' + systemVersion: '3', + registrationProvider: baseName, + ...(this.isVersionGte(orgVersion, '63.0') // Guarded inclusion for API version 254 and above (instance api version 63.0 and above) + ? { + registrationProviderType: 'ApexRest', + namedCredential: null, + namedCredentialReferenceId: null, + catalogedApiVersion: null, + isStartSchemaVersion: true, + isHeadSchemaVersion: true, + schemaArtifactVersion: version + } + : { + registrationProviderType: 'Custom', + namedCredentialReference: namedCredential + }) } }; } @@ -274,4 +297,21 @@ export class ApexActionController { const builder = new XMLBuilder({ ignoreAttributes: false, format: true }); return builder.build(jsonObj); }; + + private isVersionGte = (version: string, targetVersion: string): boolean => { + const major = parseInt(version.split('.')[0], 10); + const targetMajor = parseInt(targetVersion.split('.')[0], 10); + return major >= targetMajor; + }; + + private extractInfoProperties = (oasSpec: string): ApexOASInfo => { + const parsed = parse(oasSpec); + if (!parsed?.info?.description || !parsed?.info?.version) { + throw new Error(nls.localize('error_parsing_yaml')); + } + return { + description: parsed?.info?.description, + version: parsed?.info?.version + }; + }; } diff --git a/packages/salesforcedx-vscode-apex/src/commands/metadataOrchestrator.ts b/packages/salesforcedx-vscode-apex/src/commands/metadataOrchestrator.ts index 3afb090c3f..cbbf311d17 100644 --- a/packages/salesforcedx-vscode-apex/src/commands/metadataOrchestrator.ts +++ b/packages/salesforcedx-vscode-apex/src/commands/metadataOrchestrator.ts @@ -203,7 +203,7 @@ export class MetadataOrchestrator { Ensure no sensitive details are included. Decline requests unrelated to OpenAPI v3 specs or asking for sensitive information.`; const userPrompt = - 'Generate an OpenAPI v3 specification for the following Apex class. The OpenAPI v3 specification should be a YAML file. The paths should be in the format of /{ClassName}/{MethodName} for all the @AuraEnabled methods specified. For every `type: object`, generate a `#/components/schemas` entry for that object. The method should have a $ref entry pointing to the generated `#/components/schemas` entry. Only include methods that have the @AuraEnabled annotation in the paths of the OpenAPI v3 specification. I do not want AUTHOR_PLACEHOLDER in the result.'; + 'Generate an OpenAPI v3 specification for the following Apex class. The OpenAPI v3 specification should be a YAML file. The paths should be in the format of /{ClassName}/{MethodName} for all the @AuraEnabled methods specified. For every `type: object`, generate a `#/components/schemas` entry for that object. The method should have a $ref entry pointing to the generated `#/components/schemas` entry. Only include methods that have the @AuraEnabled annotation in the paths of the OpenAPI v3 specification. I do not want AUTHOR_PLACEHOLDER in the result. Do not forget info.description property'; const systemTag = '<|system|>'; const endOfPromptTag = '<|endofprompt|>'; diff --git a/packages/salesforcedx-vscode-apex/src/messages/i18n.ja.ts b/packages/salesforcedx-vscode-apex/src/messages/i18n.ja.ts index 32e8879271..d106baf7f0 100644 --- a/packages/salesforcedx-vscode-apex/src/messages/i18n.ja.ts +++ b/packages/salesforcedx-vscode-apex/src/messages/i18n.ja.ts @@ -100,5 +100,7 @@ export const messages = { enter_nc_name: 'Enter the name of the Named Credential', registry_access_failed: 'Failed to retrieve ESR directory name from the registry.', full_path_failed: 'Failed to determine the full path for the OpenAPI document.', - schema_element_not_found: 'The element was not found in the provided XML.' + schema_element_not_found: 'The element was not found in the provided XML.', + error_retrieving_org_version: 'Failed to retrieve org version', + error_parsing_yaml: 'Error parsing YAML' }; diff --git a/packages/salesforcedx-vscode-apex/src/messages/i18n.ts b/packages/salesforcedx-vscode-apex/src/messages/i18n.ts index 7d67d2550c..de459aefab 100644 --- a/packages/salesforcedx-vscode-apex/src/messages/i18n.ts +++ b/packages/salesforcedx-vscode-apex/src/messages/i18n.ts @@ -120,5 +120,7 @@ export const messages = { enter_nc_name: 'Enter the name of the Named Credential', registry_access_failed: 'Failed to retrieve ESR directory name from the registry.', full_path_failed: 'Failed to determine the full path for the OpenAPI document.', - schema_element_not_found: 'The element was not found in the provided XML.' + schema_element_not_found: 'The element was not found in the provided XML.', + error_retrieving_org_version: 'Failed to retrieve org version', + error_parsing_yaml: 'Error parsing YAML' }; diff --git a/packages/salesforcedx-vscode-apex/src/openApiUtilities/schemas.ts b/packages/salesforcedx-vscode-apex/src/openApiUtilities/schemas.ts index 4dddbaf671..7ab084cfb6 100644 --- a/packages/salesforcedx-vscode-apex/src/openApiUtilities/schemas.ts +++ b/packages/salesforcedx-vscode-apex/src/openApiUtilities/schemas.ts @@ -78,6 +78,11 @@ export enum ApexOASResource { singleMethodOrProp = 'METHOD or PROPERTY', folder = 'FOLDER' } + +export type ApexOASInfo = { + description: string; + version: string; +}; // export interface ApexClassOASFilter { // modifiers: string[] | null; // }