Skip to content

Commit 1503e0f

Browse files
committed
feat: auto deploy
1 parent a5c8335 commit 1503e0f

File tree

10 files changed

+865
-63
lines changed

10 files changed

+865
-63
lines changed

messages/migrate.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,5 +198,14 @@
198198
"incompleteMigrationDetected": "We couldn’t complete the migration process",
199199
"errorCheckingGlobalAutoNumber": "We couldn’t check whether the Global Auto Number setting is enabled: %s. Try again later.",
200200
"errorMigrationMessage": "Error migrating object: %s",
201-
"nameMappingUndefined": "Name Mapping is undefined"
201+
"nameMappingUndefined": "Name Mapping is undefined",
202+
"autoDeployConsentMessage": "Do you want to auto deploy the migrated components? [y/n]",
203+
"errorDeployingComponents": "Error deploying components post migration",
204+
"installingDependency": "Installing node dependency %s",
205+
"dependencyInstalled": "Node dependency %s installed",
206+
"deployingFromManifest": "Deploying from metadata packages",
207+
"manifestDeployed": "Metadata packages are deployed",
208+
"installingRequiredDependencies": "Installing required node dependencies",
209+
"creatingNPMConfigFile": "Creating npm config file",
210+
"npmConfigFileCreated": "Npm config file created"
202211
}

src/commands/omnistudio/migration/assess.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ export default class Assess extends OmniStudioBaseCommand {
176176
this.logger,
177177
messages,
178178
this.ux,
179-
objectsToProcess
179+
objectsToProcess,
180+
false,
181+
undefined
180182
);
181183

182184
const userActionMessages: string[] = [];

src/commands/omnistudio/migration/migrate.ts

Lines changed: 91 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010
import * as os from 'os';
1111
import { flags } from '@salesforce/command';
12-
import { Messages } from '@salesforce/core';
12+
import { Connection, Messages } from '@salesforce/core';
1313
import OmniStudioBaseCommand from '../../basecommand';
1414
import { DataRaptorMigrationTool } from '../../../migration/dataraptor';
1515
import { DebugTimer, MigratedObject, MigratedRecordInfo } from '../../../utils';
@@ -134,32 +134,19 @@ export default class Migrate extends OmniStudioBaseCommand {
134134
let targetApexNamespace: string;
135135
const preMigrate: PreMigrate = new PreMigrate(this.org, namespace, conn, this.logger, messages, this.ux);
136136
const isExperienceBundleMetadataAPIProgramaticallyEnabled: { value: boolean } = { value: false };
137+
138+
let autoDeploy = false;
137139
if (relatedObjects) {
138-
// To-Do: Add LWC to valid options when GA is released
139-
const validOptions = [Constants.Apex, Constants.ExpSites, Constants.FlexiPage];
140-
objectsToProcess = relatedObjects.split(',').map((obj) => obj.trim());
141-
// Validate input
142-
for (const obj of objectsToProcess) {
143-
if (!validOptions.includes(obj)) {
144-
Logger.error(messages.getMessage('invalidRelatedObjectsOption', [obj]));
145-
process.exit(1);
146-
}
147-
}
148-
// Check for general consent to make modifications with OMT
149-
const generalConsent = await this.getGeneralConsent();
150-
if (generalConsent) {
151-
// Use ProjectPathUtil for APEX project folder selection (matches assess.ts logic)
152-
projectPath = await ProjectPathUtil.getProjectPath(messages, true);
153-
targetApexNamespace = await this.getTargetApexNamespace(objectsToProcess, targetApexNamespace);
154-
await preMigrate.handleExperienceSitePrerequisites(
155-
objectsToProcess,
156-
conn,
157-
isExperienceBundleMetadataAPIProgramaticallyEnabled
158-
);
159-
Logger.logVerbose(
160-
'The objects to process after handleExpSitePrerequisite are ' + JSON.stringify(objectsToProcess)
161-
);
162-
} // TODO - What if general consent is no
140+
const relatedObjectMigrationResult = await this.migrateRelatedObjects(
141+
relatedObjects,
142+
preMigrate,
143+
conn,
144+
isExperienceBundleMetadataAPIProgramaticallyEnabled
145+
);
146+
objectsToProcess = relatedObjectMigrationResult.objectsToProcess;
147+
projectPath = relatedObjectMigrationResult.projectPath;
148+
targetApexNamespace = relatedObjectMigrationResult.targetApexNamespace;
149+
autoDeploy = relatedObjectMigrationResult.autoDeploy;
163150
}
164151

165152
Logger.log(messages.getMessage('migrationInitialization', [String(namespace)]));
@@ -204,9 +191,17 @@ export default class Migrate extends OmniStudioBaseCommand {
204191
this.logger,
205192
messages,
206193
this.ux,
207-
objectsToProcess
194+
objectsToProcess,
195+
autoDeploy,
196+
projectPath
208197
);
209198

199+
try {
200+
postMigrate.deploy();
201+
} catch (error) {
202+
Logger.error(messages.getMessage('errorDeployingComponents'), error);
203+
}
204+
210205
if (!migrateOnly) {
211206
await postMigrate.setDesignersToUseStandardDataModel(namespace, actionItems);
212207
}
@@ -223,7 +218,8 @@ export default class Migrate extends OmniStudioBaseCommand {
223218
relatedObjectMigrationResult.apexAssessmentInfos,
224219
relatedObjectMigrationResult.lwcAssessmentInfos,
225220
relatedObjectMigrationResult.experienceSiteAssessmentInfos,
226-
relatedObjectMigrationResult.flexipageAssessmentInfos
221+
relatedObjectMigrationResult.flexipageAssessmentInfos,
222+
this.org.getConnection().version
227223
);
228224

229225
await ResultsBuilder.generateReport(
@@ -237,7 +233,73 @@ export default class Migrate extends OmniStudioBaseCommand {
237233
);
238234

239235
// Return results needed for --json flag
240-
return { objectMigrationResults };
236+
return { objectMigrationResults: [] };
237+
}
238+
239+
private async migrateRelatedObjects(
240+
relatedObjects: string,
241+
preMigrate: PreMigrate,
242+
conn: Connection,
243+
isExperienceBundleMetadataAPIProgramaticallyEnabled: { value: boolean }
244+
): Promise<{ objectsToProcess: string[]; projectPath: string; targetApexNamespace: string; autoDeploy: boolean }> {
245+
const validOptions = [Constants.Apex, Constants.ExpSites, Constants.FlexiPage];
246+
const objectsToProcess = relatedObjects.split(',').map((obj) => obj.trim());
247+
// Validate input
248+
for (const obj of objectsToProcess) {
249+
if (!validOptions.includes(obj)) {
250+
Logger.error(messages.getMessage('invalidRelatedObjectsOption', [obj]));
251+
process.exit(1);
252+
}
253+
}
254+
255+
const autoDeploy = await this.getAutoDeployConsent();
256+
let projectPath: string;
257+
let targetApexNamespace: string;
258+
// Check for general consent to make modifications with OMT
259+
const generalConsent = await this.getGeneralConsent();
260+
if (generalConsent) {
261+
// Use ProjectPathUtil for APEX project folder selection (matches assess.ts logic)
262+
projectPath = await ProjectPathUtil.getProjectPath(messages, true);
263+
targetApexNamespace = await this.getTargetApexNamespace(objectsToProcess, targetApexNamespace);
264+
await preMigrate.handleExperienceSitePrerequisites(
265+
objectsToProcess,
266+
conn,
267+
isExperienceBundleMetadataAPIProgramaticallyEnabled
268+
);
269+
Logger.logVerbose(
270+
'The objects to process after handleExpSitePrerequisite are ' + JSON.stringify(objectsToProcess)
271+
);
272+
}
273+
274+
return { objectsToProcess, projectPath, targetApexNamespace, autoDeploy };
275+
}
276+
277+
private async getAutoDeployConsent(): Promise<boolean> {
278+
const askWithTimeOut = PromptUtil.askWithTimeOut(messages);
279+
let validResponse = false;
280+
let consent = false;
281+
282+
while (!validResponse) {
283+
try {
284+
const resp = await askWithTimeOut(Logger.prompt.bind(Logger), messages.getMessage('autoDeployConsentMessage'));
285+
const response = typeof resp === 'string' ? resp.trim().toLowerCase() : '';
286+
287+
if (response === YES_SHORT || response === YES_LONG) {
288+
consent = true;
289+
validResponse = true;
290+
} else if (response === NO_SHORT || response === NO_LONG) {
291+
consent = false;
292+
validResponse = true;
293+
} else {
294+
Logger.error(messages.getMessage('invalidYesNoResponse'));
295+
}
296+
} catch (err) {
297+
Logger.error(messages.getMessage('requestTimedOut'));
298+
process.exit(1);
299+
}
300+
}
301+
302+
return consent;
241303
}
242304

243305
private async getMigrationConsent(): Promise<boolean> {

src/migration/deployer.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as path from 'path';
2+
import { Messages } from '@salesforce/core';
3+
import * as shell from 'shelljs';
4+
import { sfProject } from '../utils/sfcli/project/sfProject';
5+
import { Logger } from '../utils/logger';
6+
7+
export class Deployer {
8+
private readonly projectPath: string;
9+
private readonly authEnvKey = 'OMA_AUTH_KEY';
10+
private readonly authKey: string;
11+
private readonly requiredNodeDependency = '@omnistudio/[email protected]';
12+
private readonly username: string;
13+
private readonly messages: Messages;
14+
15+
public constructor(projectPath: string, messages: Messages, username: string) {
16+
this.projectPath = projectPath;
17+
this.username = username;
18+
this.authKey = process.env[this.authEnvKey];
19+
this.messages = messages;
20+
21+
if (!this.authKey) {
22+
throw new Error(`${this.authEnvKey} environment variable is not set`);
23+
}
24+
}
25+
26+
public deploy(): void {
27+
shell.cd(this.projectPath);
28+
sfProject.createNPMConfigFile(this.authKey);
29+
Logger.logVerbose(this.messages.getMessage('installingRequiredDependencies'));
30+
sfProject.installDependency();
31+
sfProject.installDependency(this.requiredNodeDependency);
32+
sfProject.deployFromManifest(path.join(process.cwd(), 'package.xml'), this.username);
33+
}
34+
}

src/migration/postMigrate.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ import { AnonymousApexRunner } from '../utils/apex/executor/AnonymousApexRunner'
88
import { Constants } from '../utils/constants/stringContants';
99
import { OrgPreferences } from '../utils/orgPreferences';
1010
import { BaseMigrationTool } from './base';
11+
import { Deployer } from './deployer';
1112

1213
export class PostMigrate extends BaseMigrationTool {
1314
private readonly org: Org;
1415
private readonly relatedObjectsToProcess: string[];
16+
private readonly projectPath: string;
17+
private readonly autoDeploy: boolean;
1518

1619
// Source Custom Object Names
1720
constructor(
@@ -21,11 +24,15 @@ export class PostMigrate extends BaseMigrationTool {
2124
logger: Logger,
2225
messages: Messages,
2326
ux: UX,
24-
relatedObjectsToProcess: string[]
27+
relatedObjectsToProcess: string[],
28+
autoDeploy: boolean,
29+
projectPath: string
2530
) {
2631
super(namespace, connection, logger, messages, ux);
2732
this.org = org;
2833
this.relatedObjectsToProcess = relatedObjectsToProcess;
34+
this.autoDeploy = autoDeploy;
35+
this.projectPath = projectPath;
2936
}
3037

3138
public async setDesignersToUseStandardDataModel(
@@ -77,4 +84,16 @@ export class PostMigrate extends BaseMigrationTool {
7784
}
7885
}
7986
}
87+
88+
public deploy(): void {
89+
if (!this.autoDeploy) {
90+
return;
91+
}
92+
try {
93+
const deployer = new Deployer(this.projectPath, this.messages, this.org.getUsername());
94+
deployer.deploy();
95+
} catch (error) {
96+
Logger.error(this.messages.getMessage('errorDeployingComponents'), error);
97+
}
98+
}
8099
}

src/utils/generatePackageXml.ts

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export class generatePackageXml {
1313
apexAssementInfos: ApexAssessmentInfo[],
1414
lwcAssessmentInfos: LWCAssessmentInfo[],
1515
experienceSiteAssessmentInfo: ExperienceSiteAssessmentInfo[],
16-
flexipageAssessmentInfos: FlexiPageAssessmentInfo[]
16+
flexipageAssessmentInfos: FlexiPageAssessmentInfo[],
17+
version: string
1718
): void {
1819
const apexXml = generatePackageXml.getXmlElementforMembers(this.getApexclasses(apexAssementInfos), 'ApexClass');
1920
const lwcXml = generatePackageXml.getXmlElementforMembers(
@@ -31,38 +32,18 @@ export class generatePackageXml {
3132
'FlexiPage'
3233
);
3334

34-
const additionalTypes = `
35-
<types>
36-
<members>*</members>
37-
<name>OmniDataTransform</name>
38-
</types>
39-
<types>
40-
<members>*</members>
41-
<name>OmniIntegrationProcedure</name>
42-
</types>
43-
<types>
44-
<members>*</members>
45-
<name>OmniScript</name>
46-
</types>
47-
<types>
48-
<members>*</members>
49-
<name>OmniUiCard</name>
50-
</types>
51-
`;
52-
5335
const packageXmlContent = `
5436
<?xml version="1.0" encoding="UTF-8"?>
5537
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
5638
${apexXml}
5739
${lwcXml}
5840
${expsiteXml}
5941
${flexipageXml}
60-
${additionalTypes}
61-
<version>57.0</version>
42+
<version>${version}</version>
6243
</Package>
6344
`;
6445

65-
const filePath = path.join(__dirname, 'package.xml');
46+
const filePath = path.join(process.cwd(), 'package.xml');
6647
fs.writeFileSync(filePath, packageXmlContent.trim());
6748
}
6849

src/utils/sfcli/project/sfProject.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as fs from 'fs';
12
import { Messages } from '@salesforce/core';
23
import { Logger } from '../../logger';
34
import { cli } from '../../shell/cli';
@@ -27,9 +28,32 @@ export class sfProject {
2728
Logger.log(messages.getMessage('metadataDeployed', [metadataName, username]));
2829
}
2930

31+
public static installDependency(dependency?: string): void {
32+
Logger.logVerbose(messages.getMessage('installingDependency', [dependency]));
33+
const cmd = `npm install ${dependency || ''}`;
34+
sfProject.executeCommand(cmd);
35+
Logger.logVerbose(messages.getMessage('dependencyInstalled', [dependency]));
36+
}
37+
38+
public static deployFromManifest(manifestPath: string, username: string): void {
39+
Logger.logVerbose(messages.getMessage('deployingFromManifest'));
40+
const cmd = `sf project deploy start --manifest ${manifestPath} --target-org ${username}`;
41+
sfProject.executeCommand(cmd);
42+
Logger.logVerbose(messages.getMessage('manifestDeployed'));
43+
}
44+
45+
public static createNPMConfigFile(authKey: string): void {
46+
Logger.logVerbose(messages.getMessage('creatingNPMConfigFile'));
47+
fs.writeFileSync(
48+
'.npmrc',
49+
`always-auth=true\nregistry=https://repo.vlocity.com/repository/npm-public/\n//repo.vlocity.com/repository/npm-public/:_auth="${authKey}"`
50+
);
51+
Logger.logVerbose(messages.getMessage('npmConfigFileCreated'));
52+
}
53+
3054
private static executeCommand(cmd: string): void {
3155
try {
32-
cli.exec(`${cmd} --json > /dev/null 2>&1`);
56+
cli.exec(`${cmd} --json`);
3357
} catch (error) {
3458
Logger.error(messages.getMessage('sfProjectCommandError', [String(error)]));
3559
throw error;

src/utils/shell/cli.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,10 @@ import * as shell from 'shelljs';
66

77
export class cli {
88
public static exec(cmd: string): string {
9-
const exec = shell.exec(cmd);
10-
let stdout: string;
9+
const exec = shell.exec(cmd, { silent: true });
1110
if (exec.code !== 0) {
12-
stdout = exec.stderr;
13-
} else {
14-
stdout = exec.stdout;
11+
throw new Error(`Command failed with exit code ${exec.code}: ${exec.stderr || exec.stdout}`);
1512
}
16-
17-
return stdout;
13+
return exec.stdout;
1814
}
1915
}

0 commit comments

Comments
 (0)