@mysf/plugin-sf-data-export 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +206 -0
- package/README.md +149 -0
- package/bin/run.js +30 -0
- package/lib/commands/data/bulk.js +56 -0
- package/lib/commands/data/caseCreationController.js +58 -0
- package/lib/commands/data/compare.js +57 -0
- package/lib/commands/data/export.js +50 -0
- package/lib/commands/data/gitDifferencePlugin.js +67 -0
- package/lib/commands/data/restExport.js +59 -0
- package/lib/commands/data/restImport.js +54 -0
- package/lib/index.js +17 -0
- package/messages/export.json +37 -0
- package/oclif.manifest.json +469 -0
- package/package.json +212 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { simpleGit } from 'simple-git';
|
|
2
|
+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
|
|
3
|
+
import { Messages } from '@salesforce/core';
|
|
4
|
+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
5
|
+
const messages = Messages.loadMessages('@mysf/plugin-sf-data-export', 'export');
|
|
6
|
+
export class GitDifferencePlugin extends SfCommand {
|
|
7
|
+
// 1. Inherit default flags (json, loglevel, etc.) by extending SfCommand
|
|
8
|
+
static summary = messages.getMessage('gitDiffSummary');
|
|
9
|
+
static examples = messages.getMessages('examples');
|
|
10
|
+
// Custom flags stay here
|
|
11
|
+
static flags = {
|
|
12
|
+
'base-sha': Flags.string({
|
|
13
|
+
char: 'b',
|
|
14
|
+
summary: messages.getMessage('flags.base-sha.summary'),
|
|
15
|
+
default: 'HEAD~1',
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
// Explicit accessibility modifier (private) for ESLint
|
|
19
|
+
git;
|
|
20
|
+
// Explicit accessibility modifier (public) for ESLint
|
|
21
|
+
// You must include these arguments to satisfy the SfCommand signature
|
|
22
|
+
constructor(argv, config) {
|
|
23
|
+
super(argv, config); // The required super call
|
|
24
|
+
this.git = simpleGit();
|
|
25
|
+
}
|
|
26
|
+
/*
|
|
27
|
+
* Detects filenames changed between two points in Git history.
|
|
28
|
+
* @param baseSha The starting commit (defaults to previous commit)
|
|
29
|
+
* @returns Array of modified filenames
|
|
30
|
+
*/
|
|
31
|
+
async getChangedFiles(baseSha = 'HEAD~1') {
|
|
32
|
+
try {
|
|
33
|
+
const isRepo = await this.git.checkIsRepo();
|
|
34
|
+
if (!isRepo) {
|
|
35
|
+
throw new Error('Current directory is not a git repository');
|
|
36
|
+
}
|
|
37
|
+
// --name-only returns just the paths of the files
|
|
38
|
+
const diffString = await this.git.diff(['--name-only', baseSha, 'HEAD']);
|
|
39
|
+
// Filter out empty strings from the resulting array
|
|
40
|
+
return diffString.split('\n').filter(Boolean);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
44
|
+
throw new Error(`Git Detection Failed: ${message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async run() {
|
|
48
|
+
const { flags } = await this.parse(GitDifferencePlugin);
|
|
49
|
+
this.spinner.start('Analyzing git diff');
|
|
50
|
+
try {
|
|
51
|
+
const changedFiles = await this.getChangedFiles(flags['base-sha']);
|
|
52
|
+
this.spinner.stop();
|
|
53
|
+
if (changedFiles.length === 0) {
|
|
54
|
+
this.log('No files changed.');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.log('Changed files:');
|
|
58
|
+
changedFiles.forEach((f) => this.log(` - ${f}`));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
this.spinner.stop('Error');
|
|
63
|
+
this.error(err instanceof Error ? err.message : String(err));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=gitDifferencePlugin.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
|
|
4
|
+
import { Messages } from '@salesforce/core';
|
|
5
|
+
// Load messages for help text
|
|
6
|
+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
7
|
+
const messages = Messages.loadMessages('@mysf/plugin-sf-data-export', 'export');
|
|
8
|
+
export default class RestExport extends SfCommand {
|
|
9
|
+
// Enables the use of --target-org
|
|
10
|
+
static requiresOrg = true;
|
|
11
|
+
static summary = messages.getMessage('restExportSummary');
|
|
12
|
+
static examples = messages.getMessages('restExportExamples');
|
|
13
|
+
static flags = {
|
|
14
|
+
'target-org': Flags.requiredOrg({
|
|
15
|
+
char: 'o',
|
|
16
|
+
summary: messages.getMessage('flags.target-org.summary'),
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
output: Flags.string({
|
|
20
|
+
char: 'f',
|
|
21
|
+
summary: messages.getMessage('flags.output.summary'),
|
|
22
|
+
default: './data/restExport.json',
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
const { flags } = await this.parse(RestExport);
|
|
27
|
+
// EXPLICIT ACCESS: Access the org via the parsed flags
|
|
28
|
+
// This is often more stable in the latest SF CLI versions
|
|
29
|
+
const org = flags['target-org'];
|
|
30
|
+
if (!org) {
|
|
31
|
+
throw new Error('Could not find the target org.');
|
|
32
|
+
}
|
|
33
|
+
const conn = org.getConnection('60.0');
|
|
34
|
+
this.spinner.start('Calling Apex REST Export Resource');
|
|
35
|
+
try {
|
|
36
|
+
// 1. Hit the custom endpoint you created in Org A
|
|
37
|
+
const result = await conn.request({
|
|
38
|
+
method: 'GET',
|
|
39
|
+
url: '/services/apexrest/DataExport/',
|
|
40
|
+
});
|
|
41
|
+
// 2. Prepare the directory
|
|
42
|
+
const filePath = path.resolve(flags['output']);
|
|
43
|
+
const directory = path.dirname(filePath);
|
|
44
|
+
if (!fs.existsSync(directory)) {
|
|
45
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
// 3. Write the JSON data to disk
|
|
48
|
+
fs.writeFileSync(filePath, JSON.stringify(result, null, 2), 'utf8');
|
|
49
|
+
this.spinner.stop('Done');
|
|
50
|
+
this.log(`\nSUCCESS: Data written to ${filePath}`);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
this.spinner.stop('Failed');
|
|
54
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
55
|
+
this.error(`REST Call Failed: ${errorMessage}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=restExport.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
|
|
4
|
+
import { Messages } from '@salesforce/core';
|
|
5
|
+
// Load messages for help text
|
|
6
|
+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
7
|
+
const messages = Messages.loadMessages('@mysf/plugin-sf-data-export', 'export');
|
|
8
|
+
export default class RestImport extends SfCommand {
|
|
9
|
+
// Enables the use of --target-org
|
|
10
|
+
static requiresOrg = true;
|
|
11
|
+
static summary = messages.getMessage('restImportSummary');
|
|
12
|
+
static examples = messages.getMessages('restImportExamples');
|
|
13
|
+
static flags = {
|
|
14
|
+
'target-org': Flags.requiredOrg({
|
|
15
|
+
char: 'o',
|
|
16
|
+
summary: messages.getMessage('flags.target-org.summary'),
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
'input-file': Flags.string({
|
|
20
|
+
char: 'f',
|
|
21
|
+
summary: messages.getMessage('flags.input-file.summary'),
|
|
22
|
+
default: './data/restExport.json',
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
try {
|
|
27
|
+
const { flags } = await this.parse(RestImport);
|
|
28
|
+
const org = flags['target-org'];
|
|
29
|
+
if (!org) {
|
|
30
|
+
throw new Error('Could not find the target org.');
|
|
31
|
+
}
|
|
32
|
+
const conn = org.getConnection('60.0');
|
|
33
|
+
// 1. Read the JSON file from your local data folder
|
|
34
|
+
const data = fs.readFileSync(path.resolve(flags['input-file']), 'utf8');
|
|
35
|
+
// 2. POST the data to Org B's Apex REST endpoint
|
|
36
|
+
const result = await conn.request({
|
|
37
|
+
method: 'POST',
|
|
38
|
+
url: '/services/apexrest/DataImport/',
|
|
39
|
+
body: data,
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
Accept: 'text/plain',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
this.log('Data successfully pushed to Org B');
|
|
46
|
+
this.log(`Response from Salesforce: ${result}`);
|
|
47
|
+
// this.styledJSON(result);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
this.error(err instanceof Error ? err.message : String(err));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=restImport.js.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026, Salesforce, Inc.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
export default {};
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"summary": "Export data from an org in JSON format.",
|
|
3
|
+
"bulkSummary": "Export all fields from a specific object.",
|
|
4
|
+
"objectCompareSummary": "Compare fields of an object between two orgs.",
|
|
5
|
+
"callApexClassSummary": "Calling a specific method of the Apex class present in the target org.",
|
|
6
|
+
"gitDiffSummary": "Detects changed files using Git",
|
|
7
|
+
"restExportSummary": "Exports data from Org A via Apex REST for Git versioning.",
|
|
8
|
+
"restImportSummary": "Import data to the Org B via Apex REST for Git versioning.",
|
|
9
|
+
|
|
10
|
+
"examples": [
|
|
11
|
+
"sf data export --soql \"SELECT Name FROM Account\" --target-org my-org",
|
|
12
|
+
"sf data export --soql \"SELECT Id, Status FROM Case\" --output results.json"
|
|
13
|
+
],
|
|
14
|
+
|
|
15
|
+
"bulkExamples": [
|
|
16
|
+
"sf data bulk --object Account --target-org my-org",
|
|
17
|
+
"sf data bulk --object Contact --target-org my-org"
|
|
18
|
+
],
|
|
19
|
+
|
|
20
|
+
"compareExamples": ["sf data compare --object Account --source-org \"DevOrg\" --target-org \"ProdOrg\""],
|
|
21
|
+
|
|
22
|
+
"restExportExamples": [
|
|
23
|
+
"sf data restExport --target-org OrgA",
|
|
24
|
+
"sf data restExport --target-org OrgA --output ./custom/path.json"
|
|
25
|
+
],
|
|
26
|
+
|
|
27
|
+
"restImportExamples": ["sf data restImport --target-org OrgB --input-file ./data/contact.json"],
|
|
28
|
+
|
|
29
|
+
"flags.base-sha.summary": "The commit SHA to compare against",
|
|
30
|
+
"flags.object.summary": "The API name of the object.",
|
|
31
|
+
"flags.soql.summary": "The SOQL query to execute.",
|
|
32
|
+
"flags.output.summary": "The file path to save the results.",
|
|
33
|
+
"flags.input-file.summary": "The file path to be taken as an input for the import.",
|
|
34
|
+
"flags.source-org.summary": "The first org (Source)",
|
|
35
|
+
"flags.target-org.summary": "The second org (Target)",
|
|
36
|
+
"success": "Successfully exported %s records to %s."
|
|
37
|
+
}
|