Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 1 addition & 130 deletions src/client/metadataApiDeploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,14 @@
*/
import { join, relative, resolve as pathResolve, sep } from 'node:path';
import { format } from 'node:util';
import { EOL } from 'node:os';
import { isString } from '@salesforce/ts-types';
import JSZip from 'jszip';
import fs from 'graceful-fs';
import { Lifecycle } from '@salesforce/core/lifecycle';
import { Messages } from '@salesforce/core/messages';
import { SfError } from '@salesforce/core/sfError';
import { envVars } from '@salesforce/core/envVars';
import { Connection } from '@salesforce/core';
import { ensureArray, env } from '@salesforce/kit';
import { SourceComponent } from '../resolve/sourceComponent';
import { ensureArray } from '@salesforce/kit';
import { RegistryAccess } from '../registry';
import { ReplacementEvent } from '../convert/types';
import { MetadataConverter } from '../convert';
Expand Down Expand Up @@ -207,15 +204,6 @@ export class MetadataApiDeploy extends MetadataTransfer<
// this is used as the version in the manifest (package.xml).
this.components.sourceApiVersion ??= apiVersion;
}
if (this.options.components) {
// we must ensure AiAuthoringBundles compile before deployment
// Use optimized getter method instead of filtering all components
const aabComponents = this.options.components.getAiAuthoringBundles().toArray();

if (aabComponents.length > 0 && env.getBoolean('SF_AAB_COMPILATION', true)) {
await compileAABComponents(connection, aabComponents);
}
}
// only do event hooks if source, (NOT a metadata format) deploy
if (this.options.components) {
await LifecycleInstance.emit('scopedPreDeploy', {
Expand Down Expand Up @@ -436,123 +424,6 @@ export class MetadataApiDeploy extends MetadataTransfer<
}
}

const compileAABComponents = async (connection: Connection, aabComponents: SourceComponent[]): Promise<void> => {
// we need to use a namedJWT connection for this request
const { accessToken, instanceUrl } = connection.getConnectionOptions();
if (!instanceUrl) {
throw SfError.create({
name: 'ApiAccessError',
message: 'Missing Instance URL for org connection',
});
}
if (!accessToken) {
throw SfError.create({
name: 'ApiAccessError',
message: 'Missing Access Token for org connection',
});
}
const url = `${instanceUrl}/agentforce/bootstrap/nameduser`;
// For the namdeduser endpoint request to work we need to delete the access token
delete connection.accessToken;
const response = await connection.request<{
access_token: string;
}>(
{
method: 'GET',
url,
headers: {
'Content-Type': 'application/json',
Cookie: `sid=${accessToken}`,
},
},
{ retry: { maxRetries: 3 } }
);
connection.accessToken = response.access_token;
const results = await Promise.all(
aabComponents.map(async (aab) => {
// aab.content points to a directory, we need to find the .agent file and read it
if (!aab.content) {
throw new SfError(
messages.getMessage('error_expected_source_files', [aab.fullName, 'aiauthoringbundle']),
'ExpectedSourceFilesError'
);
}

const contentPath = aab.tree.find('content', aab.name, aab.content);

if (!contentPath) {
// if this didn't exist, they'll have deploy issues anyways, but we can check here for type reasons
throw new SfError(`No .agent file found in directory: ${aab.content}`, 'MissingAgentFileError');
}

const agentContent = await fs.promises.readFile(contentPath, 'utf-8');

let result: {
// minimal typings here, more is returned, just using what we need
status: 'failure' | 'success';
errors: Array<{
description: string;
lineStart: number;
colStart: number;
}>;
name: string;
};
try {
// to avoid circular dependencies between libraries, just call the compile endpoint here
result = await connection.request<typeof result>({
method: 'POST',
url: `https://${
env.getBoolean('SF_TEST_API') ? 'test.' : ''
}api.salesforce.com/einstein/ai-agent/v1.1/authoring/scripts`,
headers: {
'x-client-name': 'afdx',
'content-type': 'application/json',
},
body: JSON.stringify({
assets: [
{
type: 'AFScript',
name: 'AFScript',
content: agentContent,
},
],
afScriptVersion: '1.0.1',
}),
});
result.name = aab.name;
return result;
} catch (e) {
const error = SfError.wrap(e);
if (error.name.includes('ERROR_HTTP_404')) {
error.message = 'HTTP 404 error encountered when compiling AiAuthoringBundles';
error.actions = [
"Ensure the org's agent functionality is working outside of deployments",
"Try the 'sf agent validate authoring-bundle' command",
];
}
throw error;
} finally {
// regardless of success or failure, we don't need the named user jwt access token anymore
delete connection.accessToken;
await connection.refreshAuth();
}
})
);

const errors = results
.filter((result) => result.status === 'failure')
.map((result) =>
result.errors.map((r) => `${result.name}.agent: ${r.description} ${r.lineStart}:${r.colStart}`).join(EOL)
);

if (errors.length > 0) {
throw SfError.create({
message: `${EOL}${errors.join(EOL)}`,
name: 'AgentCompilationError',
});
}
};

/**
* If a component fails to delete because it doesn't exist in the org, you get a message like
* key: 'ApexClass#destructiveChanges.xml'
Expand Down
18 changes: 0 additions & 18 deletions src/collections/componentSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,6 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
// used to store components meant for a "constructive" (not destructive) manifest
private manifestComponents = new DecodeableMap<string, DecodeableMap<string, SourceComponent>>();

// optimization: track AiAuthoringBundles separately for faster access during compilation check
private aiAuthoringBundles = new Set<SourceComponent>();

private destructiveChangesType = DestructiveChangesType.POST;

public constructor(components: Iterable<ComponentLike> = [], registry = new RegistryAccess()) {
Expand Down Expand Up @@ -532,16 +529,6 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
return new LazyCollection(iter).filter((c) => c instanceof SourceComponent) as LazyCollection<SourceComponent>;
}

/**
* Get all constructive AiAuthoringBundle components in the set, which require compilation before deploy.
* This is an optimized method that uses a cached Set of AAB components.
*
* @returns Collection of AiAuthoringBundle source components
*/
public getAiAuthoringBundles(): LazyCollection<SourceComponent> {
return new LazyCollection(this.aiAuthoringBundles);
}

public add(component: ComponentLike, deletionType?: DestructiveChangesType): void {
const key = simpleKey(component);
if (!this.components.has(key)) {
Expand Down Expand Up @@ -571,11 +558,6 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
// we're working with SourceComponents now
this.components.get(key)?.set(srcKey, component);

// track AiAuthoringBundles separately for fast access (exclude destructive changes - no need to compile something that will be deleted)
if (component.type.id === 'aiauthoringbundle' && !deletionType) {
this.aiAuthoringBundles.add(component);
}

// Build maps of destructive components and regular components as they are added
// as an optimization when building manifests.
if (deletionType) {
Expand Down
Loading
Loading