Skip to content
Open
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
4 changes: 4 additions & 0 deletions messages/ftc.generate.code.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ The flow file to convert to pseudocode.

The output directory for the pseudocode. If not provided, the pseudocode will be written to the same directory as the flow file.

# flags.output-format.summary

Output file format. Default 'ftc' or 'ts' for typescript-similar file.

# examples

- <%= config.bin %> <%= command.id %> -f ./test.flow
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"@salesforce/sf-plugins-core": "^9.1.1",
"@types/xml2js": "^0.4.14",
"lodash": "4.17.21",
"prettier": "^3.6.2",
"wireit": "^0.14.12",
"xml2js": "^0.6.2"
},
Expand Down Expand Up @@ -197,4 +198,4 @@
"lib": "lib",
"test": "test"
}
}
}
34 changes: 25 additions & 9 deletions src/commands/ftc/generate/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as xml2js from 'xml2js';
import { Flow } from '../../../flow/Flow.js';
import { FlowParser, ParseTreeNode } from '../../../parse/FlowParser.js';
import { DefaultFtcFormatter } from '../../../format/DefaultFtcFormatter.js';
import { TsFormatter } from '../../../format/TsFormatter.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('flowtocode', 'ftc.generate.code');
Expand All @@ -18,7 +19,7 @@ type ParsedXml = {
};

export interface FormatterInterface {
convertToPseudocode(node: ParseTreeNode, tabLevel?: number): string;
convertToPseudocode(node: ParseTreeNode, tabLevel?: number): Promise<string>;
}
export default class FtcGenerateCode extends SfCommand<FtcGenerateCodeResult> {
public static readonly summary = messages.getMessage('summary');
Expand All @@ -36,28 +37,43 @@ export default class FtcGenerateCode extends SfCommand<FtcGenerateCodeResult> {
char: 'd',
required: false,
}),
'output-format': Flags.string({
summary: messages.getMessage('flags.output-format.summary'),
required: false,
default: 'ftc',
})
};

private static getOutputPath(filepath: string, outputDir?: string): string {
private static getOutputPath(filepath: string, extension: string, outputDir?: string): string {
if (outputDir) {
const filename = filepath.split('/').pop()?.replace('.flow-meta.xml', '.ftc') ?? 'flow.ftc';
const filename = filepath.split('/').pop()?.replace('.flow-meta.xml', `.${extension}`) ?? `flow.${extension}`;
return `${outputDir}/${filename}`;
}
return filepath.replace('.flow-meta.xml', '.ftc');
return filepath.replace('.flow-meta.xml', `.${extension}`);
}

private static getFormatter(format: string): FormatterInterface {
if (format === 'ts') {
return new TsFormatter();
} else if (format === 'ftc') {
return new DefaultFtcFormatter();
} else {
throw new Error(`Unsupported format: ${format}. Supported formats are 'ftc' and 'ts'.`);
}
}

public async run(): Promise<FtcGenerateCodeResult> {
const { flags } = await this.parse(FtcGenerateCode);

const filepath: string = flags.file;
const fileContent: string = await fs.readFile(filepath, 'utf-8');
const parser: xml2js.Parser = new xml2js.Parser({ explicitArray: false });
const flow: Flow = ((await parser.parseStringPromise(fileContent)) as ParsedXml).Flow;
const xmlParser: xml2js.Parser = new xml2js.Parser({ explicitArray: false, valueProcessors: [xml2js.processors.parseBooleans] });
const flow: Flow = ((await xmlParser.parseStringPromise(fileContent)) as ParsedXml).Flow;
const flowParser: FlowParser = new FlowParser();
const formatter: FormatterInterface = new DefaultFtcFormatter();
const formatter: FormatterInterface = FtcGenerateCode.getFormatter(flags['output-format']);
const treeNode: ParseTreeNode = flowParser.parse(flow);
const parseTree: string = formatter.convertToPseudocode(treeNode);
const outputPath: string = FtcGenerateCode.getOutputPath(filepath, flags.output);
const parseTree: string = await formatter.convertToPseudocode(treeNode);
const outputPath: string = FtcGenerateCode.getOutputPath(filepath, flags['output-format'], flags.output);
await fs.writeFile(outputPath, parseTree, 'utf-8');
this.log(`Output written to ${outputPath}`);
return {
Expand Down
22 changes: 22 additions & 0 deletions src/flow/Flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type Flow = Metadata & {
timeZoneSidKey?: string;
transforms?: FlowTransform[];
triggerOrder?: number;
variables?: FlowVariable[];
};
export function isFlow(obj: unknown): obj is Flow {
return typeof obj === 'object' && obj !== null && 'description' in obj;
Expand Down Expand Up @@ -108,11 +109,17 @@ export type FlowActionCall = FlowNode & {
actionType: string;
connector: FlowConnector;
faultConnector: FlowConnector;
inputParameters: FlowActionCallInputParameter[];
};
export function isFlowActionCall(obj: unknown): obj is FlowActionCall {
return typeof obj === 'object' && obj !== null && 'actionName' in obj;
}

export type FlowActionCallInputParameter = {
name: string;
value: FlowElementReferenceOrValue;
};

export type FlowLoop = FlowNode & {
assignNextValueToReference?: string;
collectionReference: string;
Expand Down Expand Up @@ -233,6 +240,17 @@ export type FlowTransformValue = {
rightValue: FlowElementReferenceOrValue;
};

export type FlowVariable = FlowElement & {
apexClass: string;
dataType: FlowDataType;
isCollection: boolean;
isInput: boolean;
isOutput: boolean;
objectType: string;
scale: number;
value: FlowElementReferenceOrValue;
};

// =====================================================================================================================
// Flow Enums
// =====================================================================================================================
Expand All @@ -253,12 +271,16 @@ export enum FlowAssignmentOperator {
}

export enum FlowDataType {
Apex = 'Apex',
Boolean = 'Boolean',
Currency = 'Currency',
Date = 'Date',
DateTime = 'DateTime',
Number = 'Number',
Multipicklist = 'Multipicklist',
Picklist = 'Picklist',
String = 'String',
sObject = 'sObject',
}

export enum FlowWaitConditionType {
Expand Down
10 changes: 8 additions & 2 deletions src/format/DefaultFtcFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import * as Flow from '../flow/Flow.js';
import { FormatterInterface } from '../commands/ftc/generate/code.js';

export class DefaultFtcFormatter implements FormatterInterface {
public convertToPseudocode(node: ParseTreeNode, tabLevel: number = -1): string {
public convertToPseudocode(node: ParseTreeNode): Promise<string> {
return Promise.resolve(
this.formatPseudocode(node, -1)
);
}

public formatPseudocode(node: ParseTreeNode, tabLevel: number = -1): string {
let result = '';
if (node.getType() !== NodeType.ROOT) {
result += `${' '.repeat(tabLevel)}${this.formatNodeStatement(node)}\n`;
}
for (const child of node.getChildren()) {
result += this.convertToPseudocode(child, tabLevel + 1);
result += this.formatPseudocode(child, tabLevel + 1);
}
return result;
}
Expand Down
Loading