Skip to content
This repository was archived by the owner on Mar 18, 2024. It is now read-only.

Commit d783041

Browse files
feat(profiles): add profile merge and retrieve to sfp (#1353)
* move from sfpowerkit * fix: remove useWorkspaces from lerna removing deprecated configuration value for lerna 7 compat * feat(profiles): merge * feat(profiles): include merge and retrieve * Revert "fix: remove useWorkspaces from lerna" This reverts commit a090f91. * fix(profiles): update profile commands to latest libs --------- Co-authored-by: Azlam <43767972+azlam-abdulsalam@users.noreply.github.com> Co-authored-by: azlam <azlam@adiza.dev>
1 parent 6ca7222 commit d783041

File tree

5 files changed

+289
-5
lines changed

5 files changed

+289
-5
lines changed

packages/sfpowerscripts-cli/messages/profile_retrieve.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
"commandDescription": "Retrieve profiles from the salesforce org with all its associated permissions. Common use case for this command is to migrate profile changes from a integration environment to other higher environments [overcomes SFDX CLI Profile retrieve issue where it doesnt fetch the full profile unless the entire metadata is present in source], or retrieving profiles from production to lower environments for testing.",
33
"folderFlagDescription": "retrieve only updated versions of profiles found in this directory, If ignored, all profiles will be retrieved.",
44
"profileListFlagDescription": "comma separated list of profiles to be retrieved. Use it for selectively retrieving an existing profile or retrieving a new profile",
5-
"deleteFlagDescription": "set this flag to delete profile files that does not exist in the org, when retrieving in bulk"
5+
"deleteFlagDescription": "set this flag to delete profile files that does not exist in the org, when retrieving in bulk",
6+
"retriveDelayWarning":"Retrieving profiles may take a significant time depending on the number of profiles \nand managed package components installed in your org,Please be patient"
67
}

packages/sfpowerscripts-cli/src/SfpowerscriptsCommand.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export default abstract class SfpowerscriptsCommand extends Command {
8989
SFPLogger.printHeaderLine('',COLOR_HEADER,LoggerLevel.INFO);
9090
SFPLogger.log(
9191
COLOR_HEADER(
92-
`sfpowerscripts -- The DX@Scale CI/CD Orchestrator -Version:${this.config.version} -Release:${this.config.pjson.release}`
92+
`sfp -- The DX@Scale CLI -Version:${this.config.version} -Release:${this.config.pjson.release}`
9393
)
9494
);
9595

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { Messages, Org } from '@salesforce/core';
2+
import { isNil } from 'lodash';
3+
import { Sfpowerkit } from '@dxatscale/sfprofiles/lib/utils/sfpowerkit';
4+
import SFPLogger, { LoggerLevel } from '@dxatscale/sfp-logger';
5+
import ProfileRetriever from '@dxatscale/sfprofiles/lib/impl/metadata/retriever/profileRetriever';
6+
import ProfileMerge from '@dxatscale/sfprofiles/lib/impl/source/profileMerge';
7+
import SfpowerscriptsCommand from '../../SfpowerscriptsCommand';
8+
import Table from 'cli-table';
9+
import { ZERO_BORDER_TABLE } from '../../ui/TableConstants';
10+
import { arrayFlagSfdxStyle, loglevel, orgApiVersionFlagSfdxStyle, requiredUserNameFlag } from '../../flags/sfdxflags';
11+
import { Flags } from '@oclif/core';
12+
13+
Messages.importMessagesDirectory(__dirname);
14+
15+
const messages = Messages.loadMessages('@dxatscale/sfpowerscripts', 'profile_merge');
16+
17+
export default class Merge extends SfpowerscriptsCommand {
18+
public static description = messages.getMessage('commandDescription');
19+
20+
public static examples = [
21+
`$ sfp profile:merge -u sandbox`,
22+
`$ sfp profile:merge -f force-app -n "My Profile" -u sandbox`,
23+
`$ sfp profile:merge -f "module1, module2, module3" -n "My Profile1, My profile2" -u sandbox`,
24+
];
25+
26+
public static flags = {
27+
folder: arrayFlagSfdxStyle({
28+
char: 'f',
29+
description: messages.getMessage('folderFlagDescription'),
30+
required: false,
31+
}),
32+
profilelist: arrayFlagSfdxStyle({
33+
char: 'n',
34+
description: messages.getMessage('profileListFlagDescription'),
35+
required: false,
36+
}),
37+
metadata: arrayFlagSfdxStyle({
38+
char: 'm',
39+
description: messages.getMessage('metadataFlagDescription'),
40+
required: false,
41+
}),
42+
delete: Flags.boolean({
43+
char: 'd',
44+
description: messages.getMessage('deleteFlagDescription'),
45+
required: false,
46+
}),
47+
targetorg: requiredUserNameFlag,
48+
'apiversion': orgApiVersionFlagSfdxStyle,
49+
loglevel,
50+
};
51+
52+
// Comment this out if your command does not require an org username
53+
protected static requiresUsername = true
54+
55+
// Set this to true if your command requires a project workspace; 'requiresProject' is false by default
56+
protected static requiresProject = true;
57+
58+
public async execute(): Promise<any> {
59+
let argFolder = this.flags.folder;
60+
let argProfileList = this.flags.profilelist;
61+
let argMetadatas = this.flags.metadata;
62+
63+
// argMetadatas = (val: string) => {
64+
// let parts = val.split(':');
65+
// return {
66+
// MetadataType: parts[0].trim(),
67+
// ApiName: parts.length >= 2 ? parts[1].trim() : '*',
68+
// };
69+
// };
70+
71+
Sfpowerkit.initCache();
72+
73+
let metadatas = undefined;
74+
let invalidArguments = [];
75+
76+
if (argMetadatas !== undefined) {
77+
metadatas = {};
78+
ProfileRetriever.supportedMetadataTypes.forEach((val) => {
79+
metadatas[val] = [];
80+
});
81+
for (let i = 0; i < argMetadatas.length; i++) {
82+
if (ProfileRetriever.supportedMetadataTypes.includes(argMetadatas[i].MetadataType)) {
83+
metadatas[argMetadatas[i].MetadataType].push(argMetadatas[i].ApiName);
84+
} else {
85+
invalidArguments.push(argMetadatas[i].MetadataType);
86+
}
87+
}
88+
if (invalidArguments.length > 0) {
89+
throw new Error(
90+
'Metadata(s) ' + invalidArguments.join(', ') + ' is/are not supported.'
91+
);
92+
}
93+
}
94+
95+
if (!isNil(argFolder) && argFolder.length !== 0) {
96+
Sfpowerkit.setDefaultFolder(argFolder[0]);
97+
}
98+
``;
99+
100+
101+
this.org = await Org.create({ aliasOrUsername: this.flags.targetorg });
102+
const profileUtils = new ProfileMerge(this.org);
103+
104+
let mergedProfiles = await profileUtils.merge(argFolder, argProfileList || [], metadatas, this.flags.delete);
105+
106+
const table = new Table({
107+
head: ['State', 'Full Name', 'Type', 'Path'],
108+
chars: ZERO_BORDER_TABLE,
109+
});
110+
if (mergedProfiles.added) {
111+
mergedProfiles.added.forEach((profile) => {
112+
table.push({
113+
state: 'Add',
114+
fullName: profile.name,
115+
type: 'Profile',
116+
path: profile.path,
117+
});
118+
});
119+
}
120+
if (mergedProfiles.updated) {
121+
mergedProfiles.updated.forEach((profile) => {
122+
table.push({
123+
state: 'Merged',
124+
fullName: profile.name,
125+
type: 'Profile',
126+
path: profile.path,
127+
});
128+
});
129+
}
130+
if (this.flags.delete) {
131+
if (mergedProfiles.deleted) {
132+
mergedProfiles.deleted.forEach((profile) => {
133+
table.push({
134+
state: 'Deleted',
135+
fullName: profile.name,
136+
type: 'Profile',
137+
path: profile.path,
138+
});
139+
});
140+
}
141+
} else {
142+
if (mergedProfiles.deleted) {
143+
mergedProfiles.deleted.forEach((profile) => {
144+
table.push({
145+
state: 'Skipped',
146+
fullName: profile.name,
147+
type: 'Profile',
148+
path: profile.path,
149+
});
150+
});
151+
}
152+
}
153+
SFPLogger.log(table.toString(), LoggerLevel.INFO);
154+
155+
return mergedProfiles;
156+
}
157+
}

packages/sfpowerscripts-cli/src/commands/profile/reconcile.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@ import { ZERO_BORDER_TABLE } from '../../ui/TableConstants';
1212
import { Flags } from '@oclif/core';
1313
import { arrayFlagSfdxStyle, loglevel, orgApiVersionFlagSfdxStyle, requiredUserNameFlag } from '../../flags/sfdxflags';
1414

15-
// Initialize Messages with the current plugin directory
1615
Messages.importMessagesDirectory(__dirname);
1716

18-
// Load the specific messages for this file. Messages from @salesforce/command, @salesforce/core,
19-
// or any library that is using the messages framework can also be loaded this way.
17+
2018
const messages = Messages.loadMessages('@dxatscale/sfpowerscripts', 'profile_reconcile');
2119

2220
export default class Reconcile extends SfpowerscriptsCommand {
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { Messages, Org } from '@salesforce/core';
2+
import * as fs from 'fs-extra';
3+
import { isNil } from 'lodash';
4+
import { Sfpowerkit } from '@dxatscale/sfprofiles/lib/utils/sfpowerkit';
5+
import ProfileSync from '@dxatscale/sfprofiles/lib/impl/source/profileSync';
6+
import SfpowerscriptsCommand from '../../SfpowerscriptsCommand';
7+
import Table from 'cli-table';
8+
import { ZERO_BORDER_TABLE } from '../../ui/TableConstants';
9+
import { arrayFlagSfdxStyle, loglevel, orgApiVersionFlagSfdxStyle, requiredUserNameFlag } from '../../flags/sfdxflags';
10+
import { Flags } from '@oclif/core';
11+
import SFPLogger, { COLOR_KEY_MESSAGE, COLOR_WARNING, LoggerLevel } from '@dxatscale/sfp-logger';
12+
13+
14+
Messages.importMessagesDirectory(__dirname);
15+
const messages = Messages.loadMessages('@dxatscale/sfpowerscripts', 'profile_retrieve');
16+
17+
export default class Retrieve extends SfpowerscriptsCommand {
18+
public static description = messages.getMessage('commandDescription');
19+
20+
public static examples = [
21+
`$ sfp profile:retrieve -u prod`,
22+
`$ sfp profile:retrieve -f force-app -n "My Profile" -u prod`,
23+
`$ sfp profile:retrieve -f "module1, module2, module3" -n "My Profile1, My profile2" -u prod`,
24+
];
25+
26+
27+
public static flags = {
28+
folder: arrayFlagSfdxStyle({
29+
char: 'f',
30+
description: messages.getMessage('folderFlagDescription'),
31+
required: false,
32+
}),
33+
profilelist: arrayFlagSfdxStyle({
34+
char: 'n',
35+
description: messages.getMessage('profileListFlagDescription'),
36+
required: false,
37+
}),
38+
delete: Flags.boolean({
39+
char: 'd',
40+
description: messages.getMessage('deleteFlagDescription'),
41+
required: false,
42+
}),
43+
targetorg: requiredUserNameFlag,
44+
'apiversion': orgApiVersionFlagSfdxStyle,
45+
loglevel,
46+
};
47+
48+
// Comment this out if your command does not require an org username
49+
protected static requiresUsername = true;
50+
51+
// Set this to true if your command requires a project workspace; 'requiresProject' is false by default
52+
protected static requiresProject = true;
53+
54+
public async execute(): Promise<any> {
55+
let argFolder: string = this.flags.folder;
56+
let argProfileList: string[] = this.flags.profilelist;
57+
58+
let folders: string[] = [];
59+
if (!isNil(argFolder) && argFolder.length !== 0) {
60+
for (let dir of argFolder) {
61+
if (!fs.existsSync(dir)) {
62+
throw new Error(`The profile path ${dir} does not exist.`);
63+
}
64+
}
65+
folders.push(...argFolder);
66+
}
67+
68+
Sfpowerkit.initCache();
69+
70+
SFPLogger.log(COLOR_WARNING(messages.getMessage('retriveDelayWarning')),LoggerLevel.INFO);
71+
SFPLogger.log(COLOR_KEY_MESSAGE(`Retrieving profiles from ${this.flags.targetorg}`),LoggerLevel.INFO );
72+
73+
this.org = await Org.create({ aliasOrUsername: this.flags.targetorg });
74+
const profileUtils = new ProfileSync(this.org);
75+
76+
let syncProfiles = await profileUtils.sync(folders, argProfileList || [], this.flags.delete);
77+
78+
const table = new Table({
79+
head: ['State', 'Full Name', 'Type', 'Path'],
80+
chars: ZERO_BORDER_TABLE,
81+
});
82+
if (syncProfiles.added) {
83+
syncProfiles.added.forEach((profile) => {
84+
table.push({
85+
state: 'Add',
86+
fullName: profile.name,
87+
type: 'Profile',
88+
path: profile.path,
89+
});
90+
});
91+
}
92+
if (syncProfiles.updated) {
93+
syncProfiles.updated.forEach((profile) => {
94+
table.push({
95+
state: 'Updated',
96+
fullName: profile.name,
97+
type: 'Profile',
98+
path: profile.path,
99+
});
100+
});
101+
}
102+
if (this.flags.delete) {
103+
if (syncProfiles.deleted) {
104+
syncProfiles.deleted.forEach((profile) => {
105+
table.push({
106+
state: 'Deleted',
107+
fullName: profile.name,
108+
type: 'Profile',
109+
path: profile.path,
110+
});
111+
});
112+
}
113+
} else {
114+
if (syncProfiles.deleted) {
115+
syncProfiles.deleted.forEach((profile) => {
116+
table.push({
117+
state: 'Skipped',
118+
fullName: profile.name,
119+
type: 'Profile',
120+
path: profile.path,
121+
});
122+
});
123+
}
124+
}
125+
126+
return syncProfiles;
127+
}
128+
}

0 commit comments

Comments
 (0)