Skip to content

Commit d4c6955

Browse files
Merge pull request #306 from mdmehran-qureshi/prerelease/alpha
@W-18432119 [Assess Mode][Tooling Enhancements] Project directory enhancements
2 parents da77663 + 65a3970 commit d4c6955

File tree

2 files changed

+121
-2
lines changed

2 files changed

+121
-2
lines changed

messages/assess.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@
9292
"integrationProcedureNameChangeMessage": "Integration Procedure reference {0} will be changed to {1} during migration.",
9393
"integrationProcedureManualUpdateMessage": "Integration Procedure reference {0} may need manual updates after migration.",
9494
"duplicateCardNameMessage": "Potential duplicate: Another card has the same name {0} after name cleaning. This may cause conflicts during migration",
95+
"existingApexPrompt": "Do you have a sfdc project that already contains the APEX classes retrieved from your org? [y/n]",
96+
"enterExistingProjectPath": "Enter the path to the project folder that contains the retrieved APEX classes:",
97+
"invalidProjectFolderPath": "Provided project folder does not exist. Please provide a valid project folder path",
98+
"requestTimedOut": "Request timed out",
99+
"retrieveApexPrompt": "Omnistudio Migration Assistant can connect to your org and retrieve the APEX classes. Provide an empty project folder to store the retrieved APEX classes. Would you like to proceed with the retrieval? [y/n]",
100+
"enterEmptyProjectPath": "Enter the path to an empty project folder to retrieve to and process the APEX classes:",
101+
"notEmptyProjectFolderPath": "Provided project folder is not empty. Please provide a valid empty project folder name and path",
102+
"operationCancelled": "Operation cancelled.",
103+
"invalidYesNoResponse": "Invalid response. Please answer y or n.",
104+
"notSfdxProjectFolderPath": "Provided folder is not a valid Salesforce DX project. Please select a folder containing sfdx-project.json",
105+
"enableVerboseOutput": "Enable verbose output",
95106
"apexFileChangesIdentifiedNotApplied": "Changes identified for Apex class {0} but not applied (assessment mode)",
96107
"apexFileHasMultipleInterfaces": "File {0} has multiple interfaces including Callable, standardizing to System.Callable only",
97108
"apexFileImplementsVlocityOpenInterface2": "File {0} implements VlocityOpenInterface2, replacing with System.Callable",

src/commands/omnistudio/migration/assess.ts

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import * as os from 'os';
2+
import * as path from 'path';
3+
import * as fs from 'fs';
24
import { flags } from '@salesforce/command';
35
import { Messages, Connection } from '@salesforce/core';
46
import OmniStudioBaseCommand from '../../basecommand';
@@ -13,10 +15,30 @@ import OmnistudioRelatedObjectMigrationFacade from '../../../migration/related/O
1315
import { OmnistudioOrgDetails, OrgUtils } from '../../../utils/orgUtils';
1416
import { OrgPreferences } from '../../../utils/orgPreferences';
1517
import { Constants } from '../../../utils/constants/stringContants';
18+
import { sfProject } from '../../../utils/sfcli/project/sfProject';
1619

1720
Messages.importMessagesDirectory(__dirname);
1821
const messages = Messages.loadMessages('@salesforce/plugin-omnistudio-migration-tool', 'assess');
1922

23+
const EXISTING_MODE = 'existing';
24+
const EMPTY_MODE = 'empty';
25+
const YES_SHORT = 'y';
26+
const NO_SHORT = 'n';
27+
const YES_LONG = 'yes';
28+
const NO_LONG = 'no';
29+
30+
// Helper to create SFDX project if needed
31+
function createSfdxProject(folderPath: string): void {
32+
const projectName = path.basename(folderPath);
33+
const parentDir = path.dirname(folderPath);
34+
sfProject.create(projectName, parentDir);
35+
}
36+
37+
function isSfdxProject(folderPath: string): boolean {
38+
const sfdxProjectJson = path.join(folderPath, 'sfdx-project.json');
39+
return fs.existsSync(sfdxProjectJson);
40+
}
41+
2042
export default class Assess extends OmniStudioBaseCommand {
2143
public static description = messages.getMessage('commandDescription');
2244

@@ -44,7 +66,7 @@ export default class Assess extends OmniStudioBaseCommand {
4466
}),
4567
verbose: flags.builtin({
4668
type: 'builtin',
47-
description: 'Enable verbose output',
69+
description: messages.getMessage('enableVerboseOutput'),
4870
}),
4971
};
5072

@@ -92,6 +114,91 @@ export default class Assess extends OmniStudioBaseCommand {
92114

93115
const namespace = orgs.packageDetails.namespace;
94116

117+
let projectPath = '';
118+
let mode: string = EXISTING_MODE;
119+
120+
// Prompt for project type
121+
const askWithTimeout = async (
122+
promptFn: (...args: unknown[]) => Promise<unknown>,
123+
...args: unknown[]
124+
): Promise<string> => {
125+
const TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
126+
let timeoutHandle: NodeJS.Timeout;
127+
const timeoutPromise = new Promise((_, reject) => {
128+
timeoutHandle = setTimeout(() => {
129+
reject(new Error(messages.getMessage('requestTimedOut')));
130+
}, TIMEOUT_MS);
131+
});
132+
try {
133+
const result = await Promise.race([promptFn(...args), timeoutPromise]);
134+
clearTimeout(timeoutHandle);
135+
if (typeof result === 'string') {
136+
return result;
137+
} else {
138+
throw new Error('Prompt did not return a string');
139+
}
140+
} catch (err) {
141+
clearTimeout(timeoutHandle);
142+
throw err;
143+
}
144+
};
145+
146+
// Prompt: Existing project?
147+
let response = '';
148+
try {
149+
const resp = await askWithTimeout(Logger.prompt.bind(Logger), messages.getMessage('existingApexPrompt'));
150+
response = typeof resp === 'string' ? resp.trim().toLowerCase() : '';
151+
} catch (err) {
152+
Logger.error(messages.getMessage('requestTimedOut'));
153+
process.exit(1);
154+
}
155+
156+
if (response === YES_SHORT || response === YES_LONG) {
157+
mode = EXISTING_MODE;
158+
} else if (response === NO_SHORT || response === NO_LONG) {
159+
mode = EMPTY_MODE;
160+
} else {
161+
Logger.error(messages.getMessage('invalidYesNoResponse'));
162+
return;
163+
}
164+
165+
// Prompt for project path
166+
let gotValidPath = false;
167+
while (!gotValidPath) {
168+
let folderPath = '';
169+
try {
170+
const resp = await askWithTimeout(
171+
Logger.prompt.bind(Logger),
172+
mode === EXISTING_MODE
173+
? messages.getMessage('enterExistingProjectPath')
174+
: messages.getMessage('enterEmptyProjectPath')
175+
);
176+
folderPath = typeof resp === 'string' ? resp.trim() : '';
177+
} catch (err) {
178+
Logger.error(messages.getMessage('requestTimedOut'));
179+
process.exit(1);
180+
}
181+
folderPath = path.resolve(folderPath);
182+
183+
if (!fs.existsSync(folderPath) || !fs.lstatSync(folderPath).isDirectory()) {
184+
Logger.error(messages.getMessage('invalidProjectFolderPath'));
185+
continue;
186+
}
187+
if (mode === EMPTY_MODE && fs.readdirSync(folderPath).length > 0) {
188+
Logger.error(messages.getMessage('notEmptyProjectFolderPath'));
189+
continue;
190+
}
191+
// If empty, create SFDX project
192+
if (mode === EMPTY_MODE) {
193+
createSfdxProject(folderPath);
194+
} else if (!isSfdxProject(folderPath)) {
195+
Logger.error(messages.getMessage('notSfdxProjectFolderPath'));
196+
continue;
197+
}
198+
projectPath = folderPath;
199+
gotValidPath = true;
200+
}
201+
95202
const assesmentInfo: AssessmentInfo = {
96203
lwcAssessmentInfos: [],
97204
apexAssessmentInfos: [],
@@ -128,7 +235,8 @@ export default class Assess extends OmniStudioBaseCommand {
128235
namespace,
129236
assessOnly,
130237
allVersions,
131-
this.org
238+
this.org,
239+
projectPath
132240
);
133241
const relatedObjectAssessmentResult = omnistudioRelatedObjectsMigration.assessAll(objectsToProcess);
134242
assesmentInfo.lwcAssessmentInfos = relatedObjectAssessmentResult.lwcAssessmentInfos;

0 commit comments

Comments
 (0)