mcdev 8.4.0 → 9.0.1
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/.github/ISSUE_TEMPLATE/bug.yml +2 -0
- package/.github/workflows/code-test.yml +3 -2
- package/.husky/commit-msg +1 -1
- package/.husky/post-checkout +4 -6
- package/.prettierrc +1 -1
- package/@types/lib/Deployer.d.ts.map +1 -1
- package/@types/lib/index.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
- package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Folder.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
- package/@types/lib/metadataTypes/List.d.ts +1 -1
- package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
- package/@types/lib/util/auth.d.ts.map +1 -1
- package/@types/lib/util/cli.d.ts.map +1 -1
- package/@types/lib/util/file.d.ts.map +1 -1
- package/@types/lib/util/init.config.d.ts.map +1 -1
- package/@types/lib/util/init.d.ts +1 -1
- package/@types/lib/util/init.d.ts.map +1 -1
- package/@types/lib/util/init.git.d.ts.map +1 -1
- package/@types/lib/util/util.d.ts +1 -0
- package/@types/lib/util/util.d.ts.map +1 -1
- package/lib/Deployer.js +5 -4
- package/lib/index.js +14 -14
- package/lib/metadataTypes/Asset.js +15 -9
- package/lib/metadataTypes/Automation.js +8 -10
- package/lib/metadataTypes/DataExtension.js +4 -3
- package/lib/metadataTypes/DataExtensionField.js +3 -3
- package/lib/metadataTypes/Event.js +6 -5
- package/lib/metadataTypes/Folder.js +28 -14
- package/lib/metadataTypes/Journey.js +2 -2
- package/lib/metadataTypes/List.js +1 -1
- package/lib/metadataTypes/MetadataType.js +17 -21
- package/lib/metadataTypes/User.js +2 -2
- package/lib/metadataTypes/definitions/Folder.definition.js +1 -1
- package/lib/util/auth.js +20 -24
- package/lib/util/businessUnit.js +1 -1
- package/lib/util/cli.js +2 -1
- package/lib/util/file.js +2 -1
- package/lib/util/init.config.js +3 -1
- package/lib/util/init.git.js +2 -1
- package/lib/util/init.js +2 -1
- package/lib/util/util.js +5 -3
- package/package.json +19 -19
- package/test/mockRoot/.mcdevrc.json +1 -1
- package/test/mockRoot/deploy/testInstance/testBU/asset/block/test_slash.asset-block-meta.html +12 -0
- package/test/mockRoot/deploy/testInstance/testBU/asset/block/test_slash.asset-block-meta.json +29 -0
- package/test/resourceFactory.js +1 -1
- package/test/resources/9999999/asset/v1/content/assets/16992/get-response.json +60 -0
- package/test/resources/9999999/asset/v1/content/assets/post-response-key=test_slash.json +60 -0
- package/test/resources/9999999/asset/v1/content/assets/query/+post-response-assetType.idIN3,195,196,197,198,199,200,201,202,203,210,211,212,213-slashfolder.json +198 -0
- package/test/resources/9999999/asset/v1/content/categories/post-response.json +9 -0
- package/test/resources/9999999/asset-slashfolder-deploy/block/test_slash.asset-block-meta.html +12 -0
- package/test/resources/9999999/asset-slashfolder-deploy/block/test_slash.asset-block-meta.json +29 -0
- package/test/resources/9999999/dataFolder/+retrieve-ContentTypeINasset,asset-shared,cloudpages-slashfolder-response.xml +136 -0
- package/test/resources/9999999/dataFolder/create-ContentType=asset,Name=testFolder_samePath,ParentFolderID=89397-response.xml +33 -0
- package/test/resources/9999999/dataFolder/create-response.xml +33 -0
- package/test/resources/9999999/dataFolder/retrieve-samePathOtherBU-response.xml +564 -0
- package/test/resources/9999999/folder-deploy-samepath/Content Builder/testFolder_samePath.folder-meta.json +9 -0
- package/test/resources/9999999/folder-deploy-slash/Content Builder/Headers%2FFolders.folder-meta.json +9 -0
- package/test/type.folder.test.js +157 -0
- package/tsconfig.json +1 -1
- package/tsconfig.npmScripts.json +1 -1
- package/tsconfig.precommit.json +1 -1
|
@@ -56,8 +56,7 @@ class Automation extends MetadataType {
|
|
|
56
56
|
static async retrieve(retrieveDir, _, __, key) {
|
|
57
57
|
let metadataMap;
|
|
58
58
|
if (key && this._cachedMetadataMap?.[key]) {
|
|
59
|
-
metadataMap = {};
|
|
60
|
-
metadataMap[key] = this._cachedMetadataMap[key];
|
|
59
|
+
metadataMap = { [key]: this._cachedMetadataMap[key] };
|
|
61
60
|
delete this._cachedMetadataMap;
|
|
62
61
|
} else if (!key && this._cachedMetadataMap) {
|
|
63
62
|
metadataMap = this._cachedMetadataMap;
|
|
@@ -849,8 +848,7 @@ class Automation extends MetadataType {
|
|
|
849
848
|
}
|
|
850
849
|
);
|
|
851
850
|
if (response?.id === automationLegacyId) {
|
|
852
|
-
const item = {};
|
|
853
|
-
item[this.definition.keyField] = key;
|
|
851
|
+
const item = { [this.definition.keyField]: key };
|
|
854
852
|
Util.logger.info(
|
|
855
853
|
` - ${mode === 'schedule' ? '✅ activated' : '🛑 paused'} scheduled ${Util.getTypeKeyName(this.definition, item)}${mode === 'schedule' ? ' (' + description + ')' : ''}`
|
|
856
854
|
);
|
|
@@ -1089,12 +1087,12 @@ class Automation extends MetadataType {
|
|
|
1089
1087
|
durationUnits: waitUnit,
|
|
1090
1088
|
});
|
|
1091
1089
|
} else if (!waitUnit) {
|
|
1092
|
-
// convert 24 hrs based time in waitDuration back into 12 hrs based time
|
|
1093
|
-
let waitTime12 = waitDuration;
|
|
1094
|
-
waitTime12 =
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1090
|
+
// // convert 24 hrs based time in waitDuration back into 12 hrs based time
|
|
1091
|
+
// let waitTime12 = waitDuration;
|
|
1092
|
+
// waitTime12 =
|
|
1093
|
+
// Number(waitTime12) > 12
|
|
1094
|
+
// ? (Number(waitTime12) - 12).toString() + ' AM'
|
|
1095
|
+
// : waitTime12 + ' PM';
|
|
1098
1096
|
// @ts-expect-error - serializedObject is only used to create/update wait activities
|
|
1099
1097
|
activity.serializedObject = JSON.stringify({
|
|
1100
1098
|
specifiedTime: waitDuration,
|
|
@@ -320,7 +320,7 @@ class DataExtension extends MetadataType {
|
|
|
320
320
|
// reset here to get the correct field list
|
|
321
321
|
item.Fields = Object.keys(existingFields)
|
|
322
322
|
.map((el) => existingFields[el])
|
|
323
|
-
.
|
|
323
|
+
.toSorted((a, b) => a.Ordinal - b.Ordinal);
|
|
324
324
|
} else if (existingFields) {
|
|
325
325
|
// get list of updated fields
|
|
326
326
|
/** @type {DataExtensionFieldItem[]} */ // @ts-ignore Fields.Field is a special case that cannot be properly typed; only required for SOAP API
|
|
@@ -344,7 +344,7 @@ class DataExtension extends MetadataType {
|
|
|
344
344
|
field.FieldType = existingField.FieldType;
|
|
345
345
|
return field;
|
|
346
346
|
})
|
|
347
|
-
.
|
|
347
|
+
.toSorted((a, b) => a.Ordinal - b.Ordinal);
|
|
348
348
|
|
|
349
349
|
// get list of new fields
|
|
350
350
|
/** @type {DataExtensionFieldItem[]} */ // @ts-ignore Fields.Field is a special case that cannot be properly typed; only required for SOAP API
|
|
@@ -1128,7 +1128,8 @@ class DataExtension extends MetadataType {
|
|
|
1128
1128
|
// A workaround for cross-BU deploy exists but it's likely not beneficial to explain to users:
|
|
1129
1129
|
// Create a DE based on the not-supported template on the target BU, retrieve it, copy the Template.CustomerKey into the to-be-deployed DE (or use mcdev-templating), done
|
|
1130
1130
|
throw new Error(
|
|
1131
|
-
`Could not find specified DataExtension Template. Please note that DataExtensions based on SMSMessageTracking and SMSSubscriptionLog cannot be deployed automatically across BUs at this point
|
|
1131
|
+
`Could not find specified DataExtension Template. Please note that DataExtensions based on SMSMessageTracking and SMSSubscriptionLog cannot be deployed automatically across BUs at this point.`,
|
|
1132
|
+
{ cause: ex }
|
|
1132
1133
|
);
|
|
1133
1134
|
}
|
|
1134
1135
|
}
|
|
@@ -103,7 +103,7 @@ class DataExtensionField extends MetadataType {
|
|
|
103
103
|
Object.keys(fieldsObj)
|
|
104
104
|
.map((field) => fieldsObj[field])
|
|
105
105
|
// the API returns the fields not sorted
|
|
106
|
-
.
|
|
106
|
+
.toSorted(this.sortDeFields)
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -352,7 +352,7 @@ class DataExtensionField extends MetadataType {
|
|
|
352
352
|
Util.logger.info(
|
|
353
353
|
` - Found ${deletionQueue.length} Data Extensions with field ${fieldName} in your BU:\n - ${deletionQueue
|
|
354
354
|
.map((item) => item.deKey)
|
|
355
|
-
.
|
|
355
|
+
.toSorted()
|
|
356
356
|
.join('\n - ')}`
|
|
357
357
|
);
|
|
358
358
|
if (deletionQueue.length > 0 && !Util.skipInteraction) {
|
|
@@ -436,7 +436,7 @@ class DataExtensionField extends MetadataType {
|
|
|
436
436
|
Util.getGrayMsg(
|
|
437
437
|
`To refresh your local files, run mcdev r ${this.buObject.credential}/${this.buObject.businessUnit} -m ${uniqueDEKeys
|
|
438
438
|
.map((key) => 'dataExtension:"' + key + '"')
|
|
439
|
-
.
|
|
439
|
+
.toSorted()
|
|
440
440
|
.join(' ')}`
|
|
441
441
|
)
|
|
442
442
|
);
|
|
@@ -695,7 +695,7 @@ class Event extends MetadataType {
|
|
|
695
695
|
...new Set(
|
|
696
696
|
Object.values(this.sfObjects.referencedObjects[objectAPIName])
|
|
697
697
|
.map((el) => el.referenceObjectName)
|
|
698
|
-
.
|
|
698
|
+
.toSorted()
|
|
699
699
|
),
|
|
700
700
|
]
|
|
701
701
|
: [];
|
|
@@ -793,7 +793,8 @@ class Event extends MetadataType {
|
|
|
793
793
|
} catch (ex) {
|
|
794
794
|
if (ex.code === 'ERR_BAD_RESPONSE') {
|
|
795
795
|
throw new Error(
|
|
796
|
-
`Could not find Salesforce entry object ${objectAPIName} on connected org
|
|
796
|
+
`Could not find Salesforce entry object ${objectAPIName} on connected org.`,
|
|
797
|
+
{ cause: ex }
|
|
797
798
|
);
|
|
798
799
|
}
|
|
799
800
|
}
|
|
@@ -861,7 +862,7 @@ class Event extends MetadataType {
|
|
|
861
862
|
*/
|
|
862
863
|
static checkSalesforceEntryEvents(ca, isPublished) {
|
|
863
864
|
// 1 check eventDataConfig
|
|
864
|
-
const edcObjects = ca.eventDataConfig.objects.
|
|
865
|
+
const edcObjects = ca.eventDataConfig.objects.toSorted((a, b) =>
|
|
865
866
|
a.dePrefix.localeCompare(b.dePrefix)
|
|
866
867
|
);
|
|
867
868
|
const warnings = [];
|
|
@@ -1156,7 +1157,7 @@ class Event extends MetadataType {
|
|
|
1156
1157
|
ca.eventDataSummary =
|
|
1157
1158
|
'string' === typeof ca.eventDataSummary
|
|
1158
1159
|
? // @ts-expect-error transforming this from API-string-format to from mcdev-format
|
|
1159
|
-
ca.eventDataSummary.split('; ').filter(Boolean).
|
|
1160
|
+
ca.eventDataSummary.split('; ').filter(Boolean).toSorted()
|
|
1160
1161
|
: ca.eventDataSummary;
|
|
1161
1162
|
ca.passThroughArgument =
|
|
1162
1163
|
'string' === typeof ca.passThroughArgument
|
|
@@ -1204,7 +1205,7 @@ class Event extends MetadataType {
|
|
|
1204
1205
|
eventDataSummary =
|
|
1205
1206
|
'string' === typeof eventDataSummary
|
|
1206
1207
|
? // @ts-expect-error transforming this from API-string-format to from mcdev-format
|
|
1207
|
-
eventDataSummary.split('; ').filter(Boolean).
|
|
1208
|
+
eventDataSummary.split('; ').filter(Boolean).toSorted()
|
|
1208
1209
|
: eventDataSummary;
|
|
1209
1210
|
|
|
1210
1211
|
const errors = [];
|
|
@@ -93,12 +93,18 @@ class Folder extends MetadataType {
|
|
|
93
93
|
if (idMap[idMap[id].ParentFolder.ID].Path) {
|
|
94
94
|
const parent = idMap[idMap[id].ParentFolder.ID];
|
|
95
95
|
// we use / here not system separator as it is important to keep metadata consistent
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
// replace '/' in folder names with escape char to avoid confusion with the path separator
|
|
97
|
+
idMap[id].Path = [
|
|
98
|
+
parent.Path,
|
|
99
|
+
idMap[id].Name.replaceAll('/', Util.folderNameSlashEscapeChar),
|
|
100
|
+
].join(Util.standardizedSplitChar);
|
|
99
101
|
idMap[id].ParentFolder.Path = parent.Path;
|
|
100
102
|
} else {
|
|
101
|
-
|
|
103
|
+
// replace '/' in folder names with escape char to avoid confusion with the path separator
|
|
104
|
+
idMap[id].Path = idMap[id].Name.replaceAll(
|
|
105
|
+
'/',
|
|
106
|
+
Util.folderNameSlashEscapeChar
|
|
107
|
+
);
|
|
102
108
|
}
|
|
103
109
|
} else {
|
|
104
110
|
Util.logger.info(
|
|
@@ -224,9 +230,12 @@ class Folder extends MetadataType {
|
|
|
224
230
|
let existingId;
|
|
225
231
|
try {
|
|
226
232
|
// perform a secondary check based on path
|
|
227
|
-
|
|
233
|
+
// do not allow mapping folders from other BUs to avoid
|
|
234
|
+
// treating a same-path folder from another BU as already existing
|
|
228
235
|
const cachedVersion = cache.getFolderByPath(
|
|
229
|
-
deployableMetadata.Path
|
|
236
|
+
deployableMetadata.Path,
|
|
237
|
+
undefined,
|
|
238
|
+
false
|
|
230
239
|
);
|
|
231
240
|
existingId = cachedVersion?.ID;
|
|
232
241
|
if (
|
|
@@ -309,11 +318,12 @@ class Folder extends MetadataType {
|
|
|
309
318
|
parsed[normalizedKey] = parsed['undefined'];
|
|
310
319
|
delete parsed['undefined'];
|
|
311
320
|
}
|
|
312
|
-
const newObject = {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
321
|
+
const newObject = {
|
|
322
|
+
[normalizedKey]: Object.assign(
|
|
323
|
+
beforeMetadata,
|
|
324
|
+
parsed[normalizedKey]
|
|
325
|
+
),
|
|
326
|
+
};
|
|
317
327
|
cache.mergeMetadata('folder', newObject);
|
|
318
328
|
|
|
319
329
|
upsertResults[metadataKey] = beforeMetadata;
|
|
@@ -642,9 +652,13 @@ class Folder extends MetadataType {
|
|
|
642
652
|
);
|
|
643
653
|
|
|
644
654
|
if (fileContent.Name === fileNameWithoutEnding) {
|
|
655
|
+
// replace '/' in folder names with escape char to match how paths are built during retrieve
|
|
645
656
|
fileContent.Path =
|
|
646
657
|
(standardSubDir ? standardSubDir + '/' : '') +
|
|
647
|
-
fileNameWithoutEnding
|
|
658
|
+
fileNameWithoutEnding.replaceAll(
|
|
659
|
+
'/',
|
|
660
|
+
Util.folderNameSlashEscapeChar
|
|
661
|
+
);
|
|
648
662
|
fileContent.ParentFolder = {
|
|
649
663
|
Path: standardSubDir,
|
|
650
664
|
};
|
|
@@ -678,7 +692,7 @@ class Folder extends MetadataType {
|
|
|
678
692
|
return fileName2FileContent;
|
|
679
693
|
} catch (ex) {
|
|
680
694
|
Util.metadataLogger('error', this.definition.type, 'getJsonFromFS', ex);
|
|
681
|
-
throw
|
|
695
|
+
throw ex;
|
|
682
696
|
}
|
|
683
697
|
}
|
|
684
698
|
|
|
@@ -697,7 +711,7 @@ class Folder extends MetadataType {
|
|
|
697
711
|
leftOperand: 'ContentType',
|
|
698
712
|
operator: contentTypeList.length === 1 ? 'equals' : 'IN',
|
|
699
713
|
rightOperand:
|
|
700
|
-
contentTypeList.length === 1 ? contentTypeList[0] : contentTypeList.
|
|
714
|
+
contentTypeList.length === 1 ? contentTypeList[0] : contentTypeList.toSorted(),
|
|
701
715
|
};
|
|
702
716
|
options.filter = options.filter
|
|
703
717
|
? {
|
|
@@ -1323,7 +1323,7 @@ class Journey extends MetadataType {
|
|
|
1323
1323
|
}
|
|
1324
1324
|
|
|
1325
1325
|
// apply sorting by activity key to work around the API shuffling activities around
|
|
1326
|
-
metadata.activities = metadata.activities.
|
|
1326
|
+
metadata.activities = metadata.activities.toSorted((a, b) => a.key.localeCompare(b.key));
|
|
1327
1327
|
}
|
|
1328
1328
|
|
|
1329
1329
|
/**
|
|
@@ -2362,7 +2362,7 @@ class Journey extends MetadataType {
|
|
|
2362
2362
|
}
|
|
2363
2363
|
|
|
2364
2364
|
// good practice to return the published keys in alphabetical order
|
|
2365
|
-
return executedKeyArr.filter(Boolean).
|
|
2365
|
+
return executedKeyArr.filter(Boolean).toSorted();
|
|
2366
2366
|
}
|
|
2367
2367
|
|
|
2368
2368
|
/**
|
|
@@ -92,7 +92,7 @@ class List extends MetadataType {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
* helper for @link retrieveForCache and @link retrieve
|
|
95
|
+
* helper for {@link retrieveForCache} and {@link retrieve}
|
|
96
96
|
*
|
|
97
97
|
* @private
|
|
98
98
|
* @param {MetadataTypeMapObj} results metadata from retrieve for current BU
|
|
@@ -108,7 +108,7 @@ class MetadataType {
|
|
|
108
108
|
} catch (ex) {
|
|
109
109
|
// this will catch issues with readdirSync
|
|
110
110
|
Util.metadataLogger('debug', this.definition.type, 'getJsonFromFS', ex);
|
|
111
|
-
throw
|
|
111
|
+
throw ex;
|
|
112
112
|
}
|
|
113
113
|
return fileName2FileContent;
|
|
114
114
|
}
|
|
@@ -229,7 +229,8 @@ class MetadataType {
|
|
|
229
229
|
// postRetrieveTasks will be run automatically on this via super.saveResult
|
|
230
230
|
} catch (ex) {
|
|
231
231
|
throw new Error(
|
|
232
|
-
`Could not get details for new ${this.definition.type} ${id} from server (${ex.message})
|
|
232
|
+
`Could not get details for new ${this.definition.type} ${id} from server (${ex.message})`,
|
|
233
|
+
{ cause: ex }
|
|
233
234
|
);
|
|
234
235
|
}
|
|
235
236
|
}
|
|
@@ -451,7 +452,7 @@ class MetadataType {
|
|
|
451
452
|
|
|
452
453
|
return { metadata: metadata, type: this.definition.type };
|
|
453
454
|
} catch (ex) {
|
|
454
|
-
throw new Error(`${this.definition.type}:: ${ex.message}
|
|
455
|
+
throw new Error(`${this.definition.type}:: ${ex.message}`, { cause: ex });
|
|
455
456
|
}
|
|
456
457
|
}
|
|
457
458
|
|
|
@@ -845,8 +846,7 @@ class MetadataType {
|
|
|
845
846
|
|
|
846
847
|
// make this newly created item available in cache for other itmes that might reference it
|
|
847
848
|
/** @type {MetadataTypeMap} */
|
|
848
|
-
const newObject = {};
|
|
849
|
-
newObject[metadataKey] = metadataMap[metadataKey];
|
|
849
|
+
const newObject = { [metadataKey]: metadataMap[metadataKey] };
|
|
850
850
|
cache.mergeMetadata(this.definition.type, newObject);
|
|
851
851
|
}
|
|
852
852
|
} else if (action === 'update' && !Util.OPTIONS.noUpdate) {
|
|
@@ -872,8 +872,10 @@ class MetadataType {
|
|
|
872
872
|
updateResults.push(result);
|
|
873
873
|
|
|
874
874
|
// make this newly created item available in cache for other itmes that might reference it
|
|
875
|
-
const newObject = {
|
|
876
|
-
|
|
875
|
+
const newObject = {
|
|
876
|
+
[metadataKey]: structuredClone(metadataMap[metadataKey]),
|
|
877
|
+
};
|
|
878
|
+
|
|
877
879
|
if (result.objectID) {
|
|
878
880
|
// required for assets
|
|
879
881
|
newObject[metadataKey].objectID = result.objectID;
|
|
@@ -1704,8 +1706,7 @@ class MetadataType {
|
|
|
1704
1706
|
(typeof singleRetrieve === 'string' || typeof singleRetrieve === 'number')
|
|
1705
1707
|
) {
|
|
1706
1708
|
// in case we really just wanted one entry but couldnt do so in the api call, filter it here
|
|
1707
|
-
const single = {};
|
|
1708
|
-
single[singleRetrieve] = metadataStructure[singleRetrieve];
|
|
1709
|
+
const single = { [singleRetrieve]: metadataStructure[singleRetrieve] };
|
|
1709
1710
|
return single;
|
|
1710
1711
|
} else if (singleRetrieve) {
|
|
1711
1712
|
return {};
|
|
@@ -2547,8 +2548,7 @@ class MetadataType {
|
|
|
2547
2548
|
* @returns {Promise.<boolean>} deletion success flag
|
|
2548
2549
|
*/
|
|
2549
2550
|
static async deleteByKeySOAP(key, overrideKeyField, codeNotFound, handleOutside) {
|
|
2550
|
-
const metadata = {};
|
|
2551
|
-
metadata[overrideKeyField || this.definition.keyField] = key;
|
|
2551
|
+
const metadata = { [overrideKeyField || this.definition.keyField]: key };
|
|
2552
2552
|
const soapType = this.definition.soapType || this.definition.type;
|
|
2553
2553
|
try {
|
|
2554
2554
|
await this.client.soap.delete(Util.capitalizeFirstLetter(soapType), metadata, null);
|
|
@@ -2639,16 +2639,12 @@ class MetadataType {
|
|
|
2639
2639
|
static async readBUMetadataForType(readDir, listBadKeys, buMetadata) {
|
|
2640
2640
|
buMetadata ||= {};
|
|
2641
2641
|
readDir = File.normalizePath([readDir, this.definition.type]);
|
|
2642
|
-
|
|
2643
|
-
if
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
}
|
|
2648
|
-
throw new Error(`Directory '${readDir}' does not exist.`);
|
|
2649
|
-
}
|
|
2650
|
-
} catch (ex) {
|
|
2651
|
-
throw new Error(ex.message);
|
|
2642
|
+
if (await File.pathExists(readDir)) {
|
|
2643
|
+
// check if folder name is a valid metadataType, then check if the user limited to a certain type in the command params
|
|
2644
|
+
buMetadata[this.definition.type] = await this.getJsonFromFS(readDir, listBadKeys);
|
|
2645
|
+
return buMetadata;
|
|
2646
|
+
} else {
|
|
2647
|
+
throw new Error(`Directory '${readDir}' does not exist.`);
|
|
2652
2648
|
}
|
|
2653
2649
|
}
|
|
2654
2650
|
|
|
@@ -1055,7 +1055,7 @@ class User extends MetadataType {
|
|
|
1055
1055
|
associatedBus = [
|
|
1056
1056
|
...new Set(user.c__AssociatedBusinessUnits.map((mid) => this._getBuName(mid))),
|
|
1057
1057
|
]
|
|
1058
|
-
.
|
|
1058
|
+
.toSorted((a, b) => (a < b ? -1 : a > b ? 1 : 0))
|
|
1059
1059
|
.join(',<br> ');
|
|
1060
1060
|
}
|
|
1061
1061
|
const defaultBUName = this._getBuName(user.DefaultBusinessUnit);
|
|
@@ -1215,7 +1215,7 @@ class User extends MetadataType {
|
|
|
1215
1215
|
// individual role (which are not manageable nor visible in the GUI)
|
|
1216
1216
|
(roleName) => !roleName.startsWith('Individual role for ')
|
|
1217
1217
|
)
|
|
1218
|
-
.
|
|
1218
|
+
.toSorted((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
1219
1219
|
} else {
|
|
1220
1220
|
// set to empty array
|
|
1221
1221
|
roles = [];
|
|
@@ -57,7 +57,7 @@ export default {
|
|
|
57
57
|
'triggered_send',
|
|
58
58
|
],
|
|
59
59
|
deployFolderTypesEmailRest: ['automations', 'journey', 'triggered_send_journeybuilder'],
|
|
60
|
-
deployFolderTypesAssetRest: ['cloudpages'],
|
|
60
|
+
deployFolderTypesAssetRest: ['cloudpages', 'asset', 'asset-shared'],
|
|
61
61
|
deployFolderBlacklist: [
|
|
62
62
|
// lower-case values!
|
|
63
63
|
'shared data extensions',
|
package/lib/util/auth.js
CHANGED
|
@@ -40,32 +40,28 @@ const Auth = {
|
|
|
40
40
|
*/
|
|
41
41
|
async saveCredential(authObject, credential) {
|
|
42
42
|
const sdk = setupSDK(credential, authObject);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.filter((element) => !test.scope.includes(element));
|
|
43
|
+
// check credentials to allow clear log output and stop execution
|
|
44
|
+
const test = await sdk.auth.getAccessToken();
|
|
45
|
+
if (test.error) {
|
|
46
|
+
throw new Error(test.error_description);
|
|
47
|
+
} else if (test.scope) {
|
|
48
|
+
// find missing rights
|
|
49
|
+
const missingAccess = sdk.auth
|
|
50
|
+
.getSupportedScopes()
|
|
51
|
+
.filter((element) => !test.scope.includes(element));
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
const existingAuth = (await File.pathExists(Util.authFileName))
|
|
61
|
-
? await File.readJSON(Util.authFileName)
|
|
62
|
-
: {};
|
|
63
|
-
existingAuth[credential] = authObject;
|
|
64
|
-
await File.writeJSONToFile('./', Util.authFileName.split('.json')[0], existingAuth);
|
|
65
|
-
authfile = existingAuth;
|
|
53
|
+
if (missingAccess.length) {
|
|
54
|
+
Util.logger.warn(
|
|
55
|
+
'Installed package has insufficient access. You might encounter malfunctions!'
|
|
56
|
+
);
|
|
57
|
+
Util.logger.warn('Missing scope: ' + missingAccess.join(', '));
|
|
66
58
|
}
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
const existingAuth = (await File.pathExists(Util.authFileName))
|
|
60
|
+
? await File.readJSON(Util.authFileName)
|
|
61
|
+
: {};
|
|
62
|
+
existingAuth[credential] = authObject;
|
|
63
|
+
await File.writeJSONToFile('./', Util.authFileName.split('.json')[0], existingAuth);
|
|
64
|
+
authfile = existingAuth;
|
|
69
65
|
}
|
|
70
66
|
},
|
|
71
67
|
|
package/lib/util/businessUnit.js
CHANGED
package/lib/util/cli.js
CHANGED
|
@@ -304,7 +304,8 @@ const Cli = {
|
|
|
304
304
|
async _setCredential(properties, credName, refreshBUs = true) {
|
|
305
305
|
const skipInteraction = Util.skipInteraction;
|
|
306
306
|
// Get user input
|
|
307
|
-
|
|
307
|
+
/** @type {boolean} */
|
|
308
|
+
let credentialsGood;
|
|
308
309
|
let inputData;
|
|
309
310
|
do {
|
|
310
311
|
if (skipInteraction) {
|
package/lib/util/file.js
CHANGED
|
@@ -271,7 +271,8 @@ const File = {
|
|
|
271
271
|
*/
|
|
272
272
|
_beautify_prettier: async function (directory, filename, filetype, content) {
|
|
273
273
|
const properties = await config.getProperties();
|
|
274
|
-
|
|
274
|
+
/** @type {string} */
|
|
275
|
+
let formatted;
|
|
275
276
|
try {
|
|
276
277
|
if (!FileFs.prettierConfig) {
|
|
277
278
|
// either no prettier config in project directory or initPrettier was not run before this
|
package/lib/util/init.config.js
CHANGED
|
@@ -444,13 +444,15 @@ const Init = {
|
|
|
444
444
|
);
|
|
445
445
|
boilerplateFileContent ||= await File.readFile(boilerplateFileName, 'utf8');
|
|
446
446
|
|
|
447
|
-
|
|
447
|
+
/** @type {string} */
|
|
448
|
+
let todo;
|
|
448
449
|
|
|
449
450
|
if (await File.pathExists(fileName)) {
|
|
450
451
|
if (relevantForced.deletes.includes(path.normalize(fileName))) {
|
|
451
452
|
Util.logger.info(
|
|
452
453
|
`- ✋ ${fileName} found but it is required to delete it. Commencing rename instead for your convenience:`
|
|
453
454
|
);
|
|
455
|
+
// eslint-disable-next-line no-useless-assignment
|
|
454
456
|
todo = 'delete';
|
|
455
457
|
} else {
|
|
456
458
|
const existingFileContent = await File.readFile(fileName, 'utf8');
|
package/lib/util/init.git.js
CHANGED
|
@@ -30,7 +30,8 @@ const Init = {
|
|
|
30
30
|
}
|
|
31
31
|
// 3. test if in git repo
|
|
32
32
|
const gitRepoFoundInCWD = await File.pathExists('.git');
|
|
33
|
-
|
|
33
|
+
/** @type {boolean} */
|
|
34
|
+
let newRepoInitialized;
|
|
34
35
|
if (gitRepoFoundInCWD) {
|
|
35
36
|
Util.logger.info(`✔️ Git repository found`);
|
|
36
37
|
newRepoInitialized = false;
|
package/lib/util/init.js
CHANGED
|
@@ -91,6 +91,7 @@ const Init = {
|
|
|
91
91
|
}
|
|
92
92
|
} while (error && !skipInteraction);
|
|
93
93
|
Util.logger.debug('reloading config');
|
|
94
|
+
// eslint-disable-next-line no-useless-assignment
|
|
94
95
|
properties = await config.getProperties(true);
|
|
95
96
|
} else if (missingCredentials.length) {
|
|
96
97
|
// forced update-credential mode - user likely cloned repo and is missing mcdev-auth.json
|
|
@@ -284,7 +285,7 @@ const Init = {
|
|
|
284
285
|
},
|
|
285
286
|
|
|
286
287
|
/**
|
|
287
|
-
* helper for @initProject that optionally creates markets and market lists for all BUs
|
|
288
|
+
* helper for {@link Init.initProject} that optionally creates markets and market lists for all BUs
|
|
288
289
|
*/
|
|
289
290
|
async _initMarkets() {
|
|
290
291
|
const skipInteraction = Util.skipInteraction;
|
package/lib/util/util.js
CHANGED
|
@@ -45,6 +45,8 @@ export const Util = {
|
|
|
45
45
|
defaultGitBranch: 'main',
|
|
46
46
|
parentBuName: '_ParentBU_',
|
|
47
47
|
standardizedSplitChar: '/',
|
|
48
|
+
/** used to replace '/' in folder names to avoid confusion with the path separator */
|
|
49
|
+
folderNameSlashEscapeChar: '\u2215',
|
|
48
50
|
/** @type {SkipInteraction} */
|
|
49
51
|
skipInteraction: null,
|
|
50
52
|
packageJsonMcdev: readJsonSync(path.join(__dirname, '../../package.json')),
|
|
@@ -1018,7 +1020,7 @@ export const Util = {
|
|
|
1018
1020
|
if (subTypeArr && subTypeArr.length > 0) {
|
|
1019
1021
|
Util.logger.info(
|
|
1020
1022
|
Util.getGrayMsg(
|
|
1021
|
-
`${indent} - Subtype${subTypeArr.length > 1 ? 's' : ''}: ${[...subTypeArr].
|
|
1023
|
+
`${indent} - Subtype${subTypeArr.length > 1 ? 's' : ''}: ${[...subTypeArr].toSorted().join(', ')}`
|
|
1022
1024
|
)
|
|
1023
1025
|
);
|
|
1024
1026
|
}
|
|
@@ -1341,7 +1343,7 @@ export const Util = {
|
|
|
1341
1343
|
values.push(...this.findLeafVals(object[k], key));
|
|
1342
1344
|
}
|
|
1343
1345
|
});
|
|
1344
|
-
return [...new Set(values.
|
|
1346
|
+
return [...new Set(values.toSorted())];
|
|
1345
1347
|
},
|
|
1346
1348
|
/**
|
|
1347
1349
|
* helper that returns a new object with sorted attributes of the given object
|
|
@@ -1351,7 +1353,7 @@ export const Util = {
|
|
|
1351
1353
|
*/
|
|
1352
1354
|
sortObjectAttributes(obj) {
|
|
1353
1355
|
return Object.keys(obj)
|
|
1354
|
-
.
|
|
1356
|
+
.toSorted()
|
|
1355
1357
|
.reduce((acc, key) => {
|
|
1356
1358
|
acc[key] = obj[key];
|
|
1357
1359
|
return acc;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcdev",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.1",
|
|
4
4
|
"description": "Accenture Salesforce Marketing Cloud DevTools",
|
|
5
5
|
"author": "Accenture: joern.berkefeld, douglas.midgley, robert.zimmermann, maciej.barnas",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"mcdev": "./lib/cli.js"
|
|
46
46
|
},
|
|
47
47
|
"engines": {
|
|
48
|
-
"node": "^
|
|
48
|
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"start": "node lib/cli.js",
|
|
@@ -70,47 +70,47 @@
|
|
|
70
70
|
"beauty-amp-core2": "0.4.9",
|
|
71
71
|
"cli-progress": "3.12.0",
|
|
72
72
|
"command-exists": "1.2.9",
|
|
73
|
-
"conf": "
|
|
73
|
+
"conf": "15.1.0",
|
|
74
74
|
"console.table": "0.10.0",
|
|
75
75
|
"deep-equal": "2.2.3",
|
|
76
76
|
"fs-extra": "11.3.3",
|
|
77
|
-
"inquirer": "
|
|
77
|
+
"inquirer": "13.3.0",
|
|
78
78
|
"json-to-table": "4.2.1",
|
|
79
79
|
"mustache": "4.2.0",
|
|
80
|
-
"p-limit": "
|
|
80
|
+
"p-limit": "7.3.0",
|
|
81
81
|
"prettier": "3.8.1",
|
|
82
82
|
"prettier-plugin-sql": "0.19.2",
|
|
83
83
|
"semver": "7.7.4",
|
|
84
|
-
"sfmc-sdk": "
|
|
85
|
-
"simple-git": "3.
|
|
84
|
+
"sfmc-sdk": "3.0.0",
|
|
85
|
+
"simple-git": "3.32.3",
|
|
86
86
|
"toposort": "2.0.2",
|
|
87
87
|
"update-notifier": "7.3.1",
|
|
88
88
|
"winston": "3.19.0",
|
|
89
|
-
"yargs": "
|
|
89
|
+
"yargs": "18.0.0",
|
|
90
90
|
"yocto-spinner": "1.1.0"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
|
-
"@eslint/js": "
|
|
93
|
+
"@eslint/js": "10.0.1",
|
|
94
94
|
"@types/fs-extra": "11.0.4",
|
|
95
|
-
"@types/inquirer": "9.0.
|
|
95
|
+
"@types/inquirer": "9.0.9",
|
|
96
96
|
"@types/mocha": "10.0.8",
|
|
97
|
-
"@types/node": "25.
|
|
98
|
-
"@types/yargs": "17.0.
|
|
97
|
+
"@types/node": "25.3.3",
|
|
98
|
+
"@types/yargs": "17.0.35",
|
|
99
99
|
"assert": "2.1.0",
|
|
100
100
|
"axios-mock-adapter": "2.0.0",
|
|
101
|
-
"c8": "
|
|
101
|
+
"c8": "11.0.0",
|
|
102
102
|
"chai": "6.2.2",
|
|
103
103
|
"chai-files": "1.4.0",
|
|
104
|
-
"eslint": "
|
|
104
|
+
"eslint": "10.0.2",
|
|
105
105
|
"eslint-config-ssjs": "2.0.0",
|
|
106
|
-
"eslint-plugin-jsdoc": "
|
|
106
|
+
"eslint-plugin-jsdoc": "62.7.1",
|
|
107
107
|
"eslint-plugin-mocha": "11.2.0",
|
|
108
108
|
"eslint-plugin-prettier": "5.5.5",
|
|
109
|
-
"eslint-plugin-unicorn": "
|
|
110
|
-
"fast-xml-parser": "5.
|
|
111
|
-
"globals": "17.
|
|
109
|
+
"eslint-plugin-unicorn": "63.0.0",
|
|
110
|
+
"fast-xml-parser": "5.4.1",
|
|
111
|
+
"globals": "17.4.0",
|
|
112
112
|
"husky": "9.1.7",
|
|
113
|
-
"lint-staged": "16.
|
|
113
|
+
"lint-staged": "16.3.1",
|
|
114
114
|
"mocha": "11.7.5",
|
|
115
115
|
"mock-fs": "5.3.0",
|
|
116
116
|
"npm-run-all": "4.1.5",
|