Skip to content

Commit 5192c48

Browse files
committed
adding run command
1 parent dc00cb5 commit 5192c48

File tree

6 files changed

+484
-2
lines changed

6 files changed

+484
-2
lines changed

messages/pythonChecker.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Python version %s is installed, but version %s or higher is required.
88

99
# actions.pythonNotFound
1010

11-
- Install Python 3.11 or higher from https://www.python.org/downloads/
11+
- Install Python 3.11 from https://www.python.org/downloads/
1212
- On macOS with Homebrew: brew install python@3.11
1313
- On Ubuntu/Debian: sudo apt-get install python3.11
1414
- On Windows: Download from https://www.python.org/downloads/windows/
@@ -17,7 +17,7 @@ Python version %s is installed, but version %s or higher is required.
1717

1818
# actions.versionMismatch
1919

20-
- Update Python to version 3.11 or higher
20+
- Update Python to version 3.11
2121
- On macOS with Homebrew: brew upgrade python@3.11
2222
- On Ubuntu/Debian: sudo apt-get update && sudo apt-get install python3.11
2323
- On Windows: Download the latest version from https://www.python.org/downloads/windows/

messages/run.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# summary
2+
3+
Run a Data Code Extension %s package locally using data from your Salesforce Org
4+
5+
# description
6+
7+
Executes an initialized Data Cloud custom code package against a Salesforce org. The package must be initialized before running. Supports both script and function packages with optional config file, dependencies, and profile overrides.
8+
9+
# examples
10+
11+
- Run a %s package against the default org:
12+
13+
<%= config.bin %> data-code-extension %s run --package-dir ./my-package --target-org myorg
14+
15+
- Run with a custom config file:
16+
17+
<%= config.bin %> data-code-extension %s run --package-dir ./my-package --target-org myorg --config-file ./payload/config.json
18+
19+
- Run with dependencies and a profile:
20+
21+
<%= config.bin %> data-code-extension %s run --package-dir ./my-package --target-org myorg --dependencies "pandas==2.0.0" --profile dev
22+
23+
# info.checkingPython
24+
25+
Checking Python version...
26+
27+
# info.pythonFound
28+
29+
Python %s found at '%s'
30+
31+
# info.checkingPackages
32+
33+
Checking required Python packages...
34+
35+
# info.packageFound
36+
37+
Package '%s' version %s found
38+
39+
# info.checkingBinary
40+
41+
Checking datacustomcode binary...
42+
43+
# info.binaryFound
44+
45+
Datacustomcode binary version %s found
46+
47+
# info.authenticating
48+
49+
Authenticating with Salesforce org '%s'...
50+
51+
# info.authenticated
52+
53+
Successfully authenticated with org '%s'
54+
55+
# info.runningPackage
56+
57+
Running package against Salesforce org...
58+
59+
# info.runComplete
60+
61+
Package at '%s' executed successfully!
62+
63+
# info.runStatus
64+
65+
Run Status: %s
66+
67+
# info.runOutput
68+
69+
Output: %s
70+
71+
# info.runSuccess
72+
73+
Data Code Extension run completed successfully!
74+
75+
# error.runFailed
76+
77+
Failed to run Data Code Extension package
78+
79+
# flags.packageDir.summary
80+
81+
Directory containing the package to run.
82+
83+
# flags.packageDir.description
84+
85+
The path to the directory containing your initialized Data Cloud custom code package. This directory should contain the package files created by the 'init' command.
86+
87+
# flags.targetOrg.summary
88+
89+
Target Salesforce org to run against.
90+
91+
# flags.targetOrg.description
92+
93+
The alias of the Salesforce org where you want to run the Data Cloud custom code package. The org must have Data Cloud enabled and appropriate permissions.
94+
95+
# flags.configFile.summary
96+
97+
Path to a config file.
98+
99+
# flags.configFile.description
100+
101+
Optional path to a JSON config file that provides input payload for the run. Defaults to the package's payload/config.json if not specified.
102+
103+
# flags.dependencies.summary
104+
105+
Dependencies override for the run.
106+
107+
# flags.dependencies.description
108+
109+
Optional comma-separated list of Python package dependencies to use during the run, overriding those defined in the package's requirements.txt.
110+
111+
# flags.profile.summary
112+
113+
Profile to use for the run.
114+
115+
# flags.profile.description
116+
117+
Optional profile name to select a specific configuration profile defined in the package for this run.

src/base/runBase.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
2+
import { Messages } from '@salesforce/core';
3+
import { PythonChecker, type PythonVersionInfo } from '../utils/pythonChecker.js';
4+
import { PipChecker, type PipPackageInfo } from '../utils/pipChecker.js';
5+
import { DatacodeBinaryChecker, type DatacodeBinaryInfo, type DatacodeRunExecutionResult } from '../utils/datacodeBinaryChecker.js';
6+
7+
export type RunResult = {
8+
success: boolean;
9+
pythonVersion: PythonVersionInfo;
10+
packageInfo?: PipPackageInfo;
11+
binaryInfo?: DatacodeBinaryInfo;
12+
codeType: 'script' | 'function';
13+
packageDir: string;
14+
targetOrg: string;
15+
status?: string;
16+
output?: string;
17+
message: string;
18+
executionResult?: DatacodeRunExecutionResult;
19+
};
20+
21+
// eslint-disable-next-line sf-plugin/command-summary, sf-plugin/command-example
22+
export abstract class RunBase extends SfCommand<RunResult> {
23+
// Override baseFlags to hide global flags
24+
public static readonly baseFlags = {
25+
...SfCommand.baseFlags,
26+
// eslint-disable-next-line sf-plugin/no-hardcoded-messages-flags
27+
'flags-dir': Flags.directory({
28+
summary: 'Import flag values from a directory.',
29+
helpGroup: 'GLOBAL',
30+
hidden: false,
31+
}),
32+
// eslint-disable-next-line sf-plugin/no-json-flag, sf-plugin/no-hardcoded-messages-flags
33+
json: Flags.boolean({
34+
summary: 'Format output as json.',
35+
helpGroup: 'GLOBAL',
36+
hidden: true,
37+
}),
38+
};
39+
40+
public async run(): Promise<RunResult> {
41+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
42+
const { flags } = await this.parse(this.constructor as any);
43+
const codeType = this.getCodeType();
44+
const messages = this.getMessages();
45+
46+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
47+
const packageDir = flags['package-dir'];
48+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
49+
const targetOrg = flags['target-org'];
50+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
51+
const configFile = flags['config-file'];
52+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
53+
const dependencies = flags['dependencies'];
54+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
55+
const profile = flags['profile'];
56+
57+
this.spinner.start(messages.getMessage('info.checkingPython'));
58+
59+
try {
60+
// Check Python 3.11+ is installed
61+
const pythonInfo = await PythonChecker.checkPython311();
62+
63+
this.spinner.stop();
64+
this.log(messages.getMessage('info.pythonFound', [pythonInfo.version, pythonInfo.command]));
65+
66+
// Check required pip packages
67+
this.spinner.start(messages.getMessage('info.checkingPackages'));
68+
const packageInfo = await PipChecker.checkPackage('salesforce-data-customcode');
69+
70+
this.spinner.stop();
71+
this.log(messages.getMessage('info.packageFound', [packageInfo.name, packageInfo.version]));
72+
73+
// Check datacustomcode binary
74+
this.spinner.start(messages.getMessage('info.checkingBinary'));
75+
const binaryInfo = await DatacodeBinaryChecker.checkBinary();
76+
77+
this.spinner.stop();
78+
this.log(messages.getMessage('info.binaryFound', [binaryInfo.version]));
79+
80+
// Authenticate with the target org
81+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
82+
const orgUsername = targetOrg.getUsername() || 'target org';
83+
this.spinner.start(messages.getMessage('info.authenticating', [orgUsername]));
84+
85+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
86+
const connection = targetOrg.getConnection();
87+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
88+
await connection.refreshAuth();
89+
90+
this.spinner.stop();
91+
this.log(messages.getMessage('info.authenticated', [orgUsername]));
92+
93+
// Execute datacustomcode run
94+
this.spinner.start(messages.getMessage('info.runningPackage'));
95+
const executionResult = await DatacodeBinaryChecker.executeBinaryRun(
96+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
97+
packageDir,
98+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
99+
orgUsername,
100+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
101+
configFile,
102+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
103+
dependencies,
104+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
105+
profile
106+
);
107+
108+
this.spinner.stop();
109+
this.log(messages.getMessage('info.runComplete', [packageDir]));
110+
111+
if (executionResult.status) {
112+
this.log(messages.getMessage('info.runStatus', [executionResult.status]));
113+
}
114+
115+
if (executionResult.output) {
116+
this.log(messages.getMessage('info.runOutput', [executionResult.output]));
117+
}
118+
119+
this.log(messages.getMessage('info.runSuccess'));
120+
121+
return {
122+
success: true,
123+
pythonVersion: pythonInfo,
124+
packageInfo,
125+
binaryInfo,
126+
codeType,
127+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
128+
packageDir,
129+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
130+
targetOrg: orgUsername,
131+
status: executionResult.status,
132+
output: executionResult.output,
133+
executionResult,
134+
message: messages.getMessage('info.runSuccess'),
135+
};
136+
} catch (error) {
137+
this.spinner.stop();
138+
throw error;
139+
}
140+
}
141+
142+
// Abstract methods that subclasses must implement
143+
protected abstract getCodeType(): 'script' | 'function';
144+
protected abstract getMessages(): Messages<string>;
145+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Flags } from '@salesforce/sf-plugins-core';
2+
import { Messages } from '@salesforce/core';
3+
import { RunBase } from '../../../base/runBase.js';
4+
5+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
6+
const messages = Messages.loadMessages('data-code-extension', 'run');
7+
8+
export default class Run extends RunBase {
9+
public static readonly summary = messages.getMessage('summary', ['function']);
10+
public static readonly description = messages.getMessage('description');
11+
// eslint-disable-next-line sf-plugin/no-missing-messages
12+
public static readonly examples = messages.getMessages('examples').map(example =>
13+
example.replace(/%s/g, 'function')
14+
);
15+
16+
public static readonly flags = {
17+
'package-dir': Flags.directory({
18+
char: 'p',
19+
summary: messages.getMessage('flags.packageDir.summary'),
20+
description: messages.getMessage('flags.packageDir.description'),
21+
required: true,
22+
exists: true,
23+
}),
24+
'target-org': Flags.requiredOrg({
25+
char: 'o',
26+
summary: messages.getMessage('flags.targetOrg.summary'),
27+
description: messages.getMessage('flags.targetOrg.description'),
28+
required: true,
29+
}),
30+
'config-file': Flags.file({
31+
summary: messages.getMessage('flags.configFile.summary'),
32+
description: messages.getMessage('flags.configFile.description'),
33+
required: false,
34+
exists: true,
35+
}),
36+
'dependencies': Flags.string({
37+
summary: messages.getMessage('flags.dependencies.summary'),
38+
description: messages.getMessage('flags.dependencies.description'),
39+
required: false,
40+
}),
41+
'profile': Flags.string({
42+
summary: messages.getMessage('flags.profile.summary'),
43+
description: messages.getMessage('flags.profile.description'),
44+
required: false,
45+
}),
46+
};
47+
48+
// eslint-disable-next-line class-methods-use-this
49+
protected getCodeType(): 'function' {
50+
return 'function';
51+
}
52+
53+
// eslint-disable-next-line class-methods-use-this
54+
protected getMessages(): Messages<string> {
55+
return messages;
56+
}
57+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Flags } from '@salesforce/sf-plugins-core';
2+
import { Messages } from '@salesforce/core';
3+
import { RunBase } from '../../../base/runBase.js';
4+
5+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
6+
const messages = Messages.loadMessages('data-code-extension', 'run');
7+
8+
export default class Run extends RunBase {
9+
public static readonly summary = messages.getMessage('summary', ['script']);
10+
public static readonly description = messages.getMessage('description');
11+
// eslint-disable-next-line sf-plugin/no-missing-messages
12+
public static readonly examples = messages.getMessages('examples').map(example =>
13+
example.replace(/%s/g, 'script')
14+
);
15+
16+
public static readonly flags = {
17+
'package-dir': Flags.directory({
18+
char: 'p',
19+
summary: messages.getMessage('flags.packageDir.summary'),
20+
description: messages.getMessage('flags.packageDir.description'),
21+
required: true,
22+
exists: true,
23+
}),
24+
'target-org': Flags.requiredOrg({
25+
char: 'o',
26+
summary: messages.getMessage('flags.targetOrg.summary'),
27+
description: messages.getMessage('flags.targetOrg.description'),
28+
required: true,
29+
}),
30+
'config-file': Flags.file({
31+
summary: messages.getMessage('flags.configFile.summary'),
32+
description: messages.getMessage('flags.configFile.description'),
33+
required: false,
34+
exists: true,
35+
}),
36+
'dependencies': Flags.string({
37+
summary: messages.getMessage('flags.dependencies.summary'),
38+
description: messages.getMessage('flags.dependencies.description'),
39+
required: false,
40+
}),
41+
'profile': Flags.string({
42+
summary: messages.getMessage('flags.profile.summary'),
43+
description: messages.getMessage('flags.profile.description'),
44+
required: false,
45+
}),
46+
};
47+
48+
// eslint-disable-next-line class-methods-use-this
49+
protected getCodeType(): 'script' {
50+
return 'script';
51+
}
52+
53+
// eslint-disable-next-line class-methods-use-this
54+
protected getMessages(): Messages<string> {
55+
return messages;
56+
}
57+
}

0 commit comments

Comments
 (0)