mcdev 4.0.2 → 4.1.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/README.md CHANGED
@@ -123,7 +123,7 @@ If you experience issues installing Accenture SFMC DevTools, please check out th
123
123
  1. Install Accenture SFMC DevTools by running `npm install -g mcdev` (prefix with `sudo` on MacOS)
124
124
  - If you get an error, please see the below troubleshooting section.
125
125
 
126
- When completed run `mcdev --version` and it will show you which version you installed (e.g. `4.0.2`).
126
+ When completed run `mcdev --version` and it will show you which version you installed (e.g. `4.1.0`).
127
127
 
128
128
  > **_Side note for proud nerds_:**
129
129
  >
@@ -277,10 +277,10 @@ _Note: Regardless of which tag or branch you install_
277
277
  **Install specific version (using a version tag on npm):**
278
278
 
279
279
  ```bash
280
- npm install -g mcdev@4.0.2
280
+ npm install -g mcdev@4.1.0
281
281
  ```
282
282
 
283
- **Warning**: When you used the above method to install Accenture SFMC DevTools for a specific version or tag, trying to [update Accenture SFMC DevTools](#updating-mcdev) might not download the most recently published official version but instead stay on the version or branch you previously selected (in the above examples: develop, 4.0.2)!
283
+ **Warning**: When you used the above method to install Accenture SFMC DevTools for a specific version or tag, trying to [update Accenture SFMC DevTools](#updating-mcdev) might not download the most recently published official version but instead stay on the version or branch you previously selected (in the above examples: develop, 4.1.0)!
284
284
 
285
285
  > **Note**: The version is currently _not_ updated on the developer branch until a new release is published. Hence, you will not see a change if you run `mcdev --version`.
286
286
 
@@ -1485,25 +1485,21 @@ If you want to enhance Accenture SFMC DevTools you are welcome to fork the repo
1485
1485
 
1486
1486
  Instead of installing Accenture SFMC DevTools as a npm dependency from our git repo, we recommend cloning our repo and then linking it locally:
1487
1487
 
1488
- Assuming you cloned Accenture SFMC DevTools into `C:\repos\sfmc-devtools\` (or `~/repos/sfmc-devtools/` on Mac) you can then go into your project directory, open a terminal there and run:
1488
+ Assuming you cloned Accenture SFMC DevTools into `C:\repos\sfmc-devtools\` (or `~/repos/sfmc-devtools/` on Mac):
1489
1489
 
1490
- _Local install:_
1490
+ 1. Open a terminal in your repo folder (`repos/sfmc-devtools/`)
1491
+ 2. Execute `npm install` to download all the dependencies.
1492
+ 3. Execute `npx husky install` to enable our git hooks.
1493
+ 4. Execute `npm install -g "C:\repos\sfmc-devtools"` (this installs mcdev globally on your computer based on your cloned repo folder. Any changes you make in there will take immediate effect without the need for publishing or re-installing it).
1491
1494
 
1492
- ```bash
1493
- npm install --save-dev "C:\repos\sfmc-devtools"
1494
- ```
1495
+ This should tell npm to create a symlink to your cloned local directoty, allowing you to see updates you make in your mcdev repo instantly.
1495
1496
 
1496
- or
1497
+ To test your new **global** developer setup, run `mcdev --version` in CLI which should return the current version (e.g. `4.1.0`). Then, go into your mcdev repo and update the version with the suffix `-dev`, e.g. to `4.1.0-dev` and then run `mcdev --version` again to verify that your change propagates instantly.
1497
1498
 
1498
- _Global install **(recommended)**:_
1499
+ > **Not recommended:** Alternatively, you can install it locally only by opening a terminal in your project directory and executing `npm install --save-dev "C:\repos\sfmc-devtools"`
1500
+ > To run the local version you need to prepend "npx" before your commands, e.g. `npx mcdev --version`
1499
1501
 
1500
- ```bash
1501
- npm install -g "C:\repos\sfmc-devtools"
1502
- ```
1503
-
1504
- This should tell npm to create a symlink to your cloned local directoty, allowing you to see updates you make in your mcdev repo instantly.
1505
-
1506
- > Note: on MacOS you might need to prepend `sudo` to elevate your command.
1502
+ **Note:** On MacOS you might need to prepend `sudo` to elevate your command.
1507
1503
 
1508
1504
  **Local vs. Global developer installation:**
1509
1505
 
@@ -1511,8 +1507,6 @@ If you use Accenture SFMC DevTools in your team it is recommended to install you
1511
1507
 
1512
1508
  If you do need to install it locally, make sure you don't commit your project's package.json with this change or you might break mcdev for other developers in your team that either didn't clone the Accenture SFMC DevTools repo or stored in a different directory.
1513
1509
 
1514
- To test your new **global** developer setup, run `mcdev --version` in CLI which should return the current version (e.g. `4.0.2`). Then, go into your mcdev repo and update the version with the suffix `-dev`, e.g. to `4.0.2-dev` and then run `mcdev --version` again to verify that your change propagates instantly.
1515
-
1516
1510
  <a name="local-install"></a>
1517
1511
 
1518
1512
  ### 9.2. Local install
@@ -1538,7 +1532,7 @@ The following explains how you _could_ install it locally for certain edge cases
1538
1532
  4. Afterwards, install Accenture SFMC DevTools by running `npm install --save-dev mcdev`
1539
1533
  - If you get an error, please see the below troubleshooting section.
1540
1534
 
1541
- When completed run `mcdev --version` and it will show you which version you installed (e.g. `4.0.2`).
1535
+ When completed run `mcdev --version` and it will show you which version you installed (e.g. `4.1.0`).
1542
1536
 
1543
1537
  ### 9.3. NPM Scripts
1544
1538
 
@@ -180,8 +180,9 @@ Provides default functionality that can be overwritten by child metadata type cl
180
180
  ## Typedefs
181
181
 
182
182
  <dl>
183
- <dt><a href="#SupportedMetadataTypes">SupportedMetadataTypes</a> : <code>Object.&lt;string, string&gt;</code></dt>
184
- <dd></dd>
183
+ <dt><a href="#TypeKeyCombo">TypeKeyCombo</a> : <code>Object.&lt;string, string&gt;</code></dt>
184
+ <dd><p>object-key=metadata type, value=array of external keys</p>
185
+ </dd>
185
186
  <dt><a href="#Cache">Cache</a> : <code>Object.&lt;string, any&gt;</code></dt>
186
187
  <dd><p>key=customer key</p>
187
188
  </dd>
@@ -346,7 +347,6 @@ Source and target business units are also compared before the deployment to appl
346
347
  * _instance_
347
348
  * [.metadata](#Deployer+metadata) : <code>TYPE.MultiMetadataTypeMap</code>
348
349
  * [._deploy([typeArr], [keyArr], [fromRetrieve])](#Deployer+_deploy) ⇒ <code>Promise</code>
349
- * [.deployCallback(result, metadataType)](#Deployer+deployCallback) ⇒ <code>void</code>
350
350
  * _static_
351
351
  * [.deploy(businessUnit, [selectedTypesArr], [keyArr], [fromRetrieve])](#Deployer.deploy) ⇒ <code>Promise.&lt;void&gt;</code>
352
352
  * [._deployBU(cred, bu, properties, [typeArr], [keyArr], [fromRetrieve])](#Deployer._deployBU) ⇒ <code>Promise</code>
@@ -378,22 +378,10 @@ Deploy all metadata that is located in the deployDir
378
378
 
379
379
  | Param | Type | Description |
380
380
  | --- | --- | --- |
381
- | [typeArr] | <code>Array.&lt;string&gt;</code> | limit deployment to given metadata type (can include subtype) |
381
+ | [typeArr] | <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code> | limit deployment to given metadata type (can include subtype) |
382
382
  | [keyArr] | <code>Array.&lt;string&gt;</code> | limit deployment to given metadata keys |
383
383
  | [fromRetrieve] | <code>boolean</code> | if true, no folders will be updated/created |
384
384
 
385
- <a name="Deployer+deployCallback"></a>
386
-
387
- ### deployer.deployCallback(result, metadataType) ⇒ <code>void</code>
388
- Gets called for every deployed metadata entry
389
-
390
- **Kind**: instance method of [<code>Deployer</code>](#Deployer)
391
-
392
- | Param | Type | Description |
393
- | --- | --- | --- |
394
- | result | <code>object</code> | Deployment result |
395
- | metadataType | <code>string</code> | Name of metadata type |
396
-
397
385
  <a name="Deployer.deploy"></a>
398
386
 
399
387
  ### Deployer.deploy(businessUnit, [selectedTypesArr], [keyArr], [fromRetrieve]) ⇒ <code>Promise.&lt;void&gt;</code>
@@ -405,7 +393,7 @@ Deploys all metadata located in the 'deploy' directory to the specified business
405
393
  | Param | Type | Description |
406
394
  | --- | --- | --- |
407
395
  | businessUnit | <code>string</code> | references credentials from properties.json |
408
- | [selectedTypesArr] | <code>Array.&lt;string&gt;</code> | limit deployment to given metadata type |
396
+ | [selectedTypesArr] | <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code> | limit deployment to given metadata type |
409
397
  | [keyArr] | <code>Array.&lt;string&gt;</code> | limit deployment to given metadata keys |
410
398
  | [fromRetrieve] | <code>boolean</code> | optionally deploy whats defined via selectedTypesArr + keyArr directly from retrieve folder instead of from deploy folder |
411
399
 
@@ -422,7 +410,7 @@ helper for deploy()
422
410
  | cred | <code>string</code> | name of Credential |
423
411
  | bu | <code>string</code> | name of BU |
424
412
  | properties | <code>TYPE.Mcdevrc</code> | General configuration to be used in retrieve |
425
- | [typeArr] | <code>Array.&lt;string&gt;</code> | limit deployment to given metadata type |
413
+ | [typeArr] | <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code> | limit deployment to given metadata type |
426
414
  | [keyArr] | <code>Array.&lt;string&gt;</code> | limit deployment to given metadata keys |
427
415
  | [fromRetrieve] | <code>boolean</code> | optionally deploy whats defined via selectedTypesArr + keyArr directly from retrieve folder instead of from deploy folder |
428
416
 
@@ -518,6 +506,7 @@ handler for 'mcdev createDeltaPkg
518
506
  | argv | <code>object</code> | yargs parameters |
519
507
  | [argv.range] | <code>string</code> | git commit range into deploy directory |
520
508
  | [argv.filter] | <code>string</code> | filter file paths that start with any |
509
+ | [argv.diffArr] | <code>Array.&lt;TYPE.DeltaPkgItem&gt;</code> | list of files to include in delta package (skips git diff when provided) |
521
510
  | [argv.skipInteraction] | <code>TYPE.skipInteraction</code> | allows to skip interactive wizard |
522
511
 
523
512
  <a name="Mcdev.selectTypes"></a>
@@ -550,7 +539,7 @@ Retrieve all metadata from the specified business unit into the local file syste
550
539
  | Param | Type | Description |
551
540
  | --- | --- | --- |
552
541
  | businessUnit | <code>string</code> | references credentials from properties.json |
553
- | [selectedTypesArr] | <code>Array.&lt;string&gt;</code> | limit retrieval to given metadata type |
542
+ | [selectedTypesArr] | <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code> \| <code>TYPE.TypeKeyCombo</code> | limit retrieval to given metadata type |
554
543
  | [keys] | <code>Array.&lt;string&gt;</code> | limit retrieval to given metadata key |
555
544
  | [changelogOnly] | <code>boolean</code> | skip saving, only create json in memory |
556
545
 
@@ -565,7 +554,7 @@ Deploys all metadata located in the 'deploy' directory to the specified business
565
554
  | Param | Type | Default | Description |
566
555
  | --- | --- | --- | --- |
567
556
  | businessUnit | <code>string</code> | | references credentials from properties.json |
568
- | [selectedTypesArr] | <code>Array.&lt;string&gt;</code> | | limit deployment to given metadata type |
557
+ | [selectedTypesArr] | <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code> | | limit deployment to given metadata type |
569
558
  | [keyArr] | <code>Array.&lt;string&gt;</code> | | limit deployment to given metadata keys |
570
559
  | [fromRetrieve] | <code>boolean</code> | <code>false</code> | optionally deploy whats defined via selectedTypesArr + keyArr directly from retrieve folder instead of from deploy folder |
571
560
 
@@ -840,7 +829,7 @@ FileTransfer MetadataType
840
829
  * [.requestSubType(subType, subTypeArray, [retrieveDir], [templateName], [templateVariables], key)](#Asset.requestSubType) ⇒ <code>Promise</code>
841
830
  * [.requestAndSaveExtended(items, subType, retrieveDir, [templateVariables])](#Asset.requestAndSaveExtended) ⇒ <code>Promise</code>
842
831
  * [._retrieveExtendedFile(metadata, subType, retrieveDir)](#Asset._retrieveExtendedFile) ⇒ <code>Promise.&lt;void&gt;</code>
843
- * [._readExtendedFileFromFS(metadata, subType, deployDir)](#Asset._readExtendedFileFromFS) ⇒ <code>Promise.&lt;void&gt;</code>
832
+ * [._readExtendedFileFromFS(metadata, subType, deployDir, [pathOnly])](#Asset._readExtendedFileFromFS) ⇒ <code>Promise.&lt;string&gt;</code>
844
833
  * [.postRetrieveTasks(metadata)](#Asset.postRetrieveTasks) ⇒ <code>TYPE.CodeExtractItem</code>
845
834
  * [.preDeployTasks(metadata, deployDir, buObject)](#Asset.preDeployTasks) ⇒ <code>Promise.&lt;TYPE.AssetItem&gt;</code>
846
835
  * [._getMainSubtype(extendedSubType)](#Asset._getMainSubtype) ⇒ <code>string</code>
@@ -974,19 +963,20 @@ This method retrieves these and saves them alongside the metadata json
974
963
 
975
964
  <a name="Asset._readExtendedFileFromFS"></a>
976
965
 
977
- ### Asset.\_readExtendedFileFromFS(metadata, subType, deployDir) ⇒ <code>Promise.&lt;void&gt;</code>
966
+ ### Asset.\_readExtendedFileFromFS(metadata, subType, deployDir, [pathOnly]) ⇒ <code>Promise.&lt;string&gt;</code>
978
967
  helper for this.preDeployTasks()
979
968
  Some metadata types store their actual content as a separate file, e.g. images
980
969
  This method reads these from the local FS stores them in the metadata object allowing to deploy it
981
970
 
982
971
  **Kind**: static method of [<code>Asset</code>](#Asset)
983
- **Returns**: <code>Promise.&lt;void&gt;</code> - -
972
+ **Returns**: <code>Promise.&lt;string&gt;</code> - if found will return the path of the binary file
984
973
 
985
- | Param | Type | Description |
986
- | --- | --- | --- |
987
- | metadata | <code>TYPE.AssetItem</code> | a single asset |
988
- | subType | <code>TYPE.AssetSubType</code> | group of similar assets to put in a folder (ie. images) |
989
- | deployDir | <code>string</code> | directory of deploy files |
974
+ | Param | Type | Default | Description |
975
+ | --- | --- | --- | --- |
976
+ | metadata | <code>TYPE.AssetItem</code> | | a single asset |
977
+ | subType | <code>TYPE.AssetSubType</code> | | group of similar assets to put in a folder (ie. images) |
978
+ | deployDir | <code>string</code> | | directory of deploy files |
979
+ | [pathOnly] | <code>boolean</code> | <code>false</code> | used by getFilesToCommit which does not need the binary file to be actually read |
990
980
 
991
981
  <a name="Asset.postRetrieveTasks"></a>
992
982
 
@@ -4418,8 +4408,8 @@ Retrieve metadata of specified types into local file system and Retriever.metada
4418
4408
 
4419
4409
  | Param | Type | Description |
4420
4410
  | --- | --- | --- |
4421
- | metadataTypes | <code>Array.&lt;string&gt;</code> | String list of metadata types to retrieve |
4422
- | [namesOrKeys] | <code>Array.&lt;string&gt;</code> | name of Metadata to retrieveAsTemplate or list of keys for normal retrieval |
4411
+ | metadataTypes | <code>Array.&lt;TYPE.SupportedMetadataTypes&gt;</code> | list of metadata types to retrieve; can include subtypes! |
4412
+ | [namesOrKeys] | <code>Array.&lt;string&gt;</code> \| <code>TYPE.TypeKeyCombo</code> | name of Metadata to retrieveAsTemplate or list of keys for normal retrieval |
4423
4413
  | [templateVariables] | <code>TYPE.TemplateMap</code> | Object of values which can be replaced (in case of templating) |
4424
4414
  | [changelogOnly] | <code>boolean</code> | skip saving, only create json in memory |
4425
4415
 
@@ -4943,7 +4933,7 @@ DevOps helper class
4943
4933
  * [.getDeltaList(properties, [range], [saveToDeployDir], [filterPaths])](#DevOps.getDeltaList) ⇒ <code>Promise.&lt;Array.&lt;TYPE.DeltaPkgItem&gt;&gt;</code>
4944
4934
  * [~delta](#DevOps.getDeltaList..delta) : <code>Array.&lt;TYPE.DeltaPkgItem&gt;</code>
4945
4935
  * [~copied](#DevOps.getDeltaList..copied) : <code>TYPE.DeltaPkgItem</code>
4946
- * [.buildDeltaDefinitions(properties, range, [skipInteraction])](#DevOps.buildDeltaDefinitions) ⇒ <code>Promise.&lt;Array.&lt;TYPE.DeltaPkgItem&gt;&gt;</code>
4936
+ * [.buildDeltaDefinitions(properties, range, [diffArr], [skipInteraction])](#DevOps.buildDeltaDefinitions) ⇒ <code>Promise.&lt;Array.&lt;TYPE.DeltaPkgItem&gt;&gt;</code>
4947
4937
  * [~deltaDeployAll](#DevOps.buildDeltaDefinitions..deltaDeployAll) : <code>Array.&lt;TYPE.DeltaPkgItem&gt;</code>
4948
4938
  * [.document(directory, jsonReport)](#DevOps.document) ⇒ <code>void</code>
4949
4939
  * [.getFilesToCommit(properties, buObject, metadataType, keyArr)](#DevOps.getFilesToCommit) ⇒ <code>Promise.&lt;Array.&lt;string&gt;&gt;</code>
@@ -4979,7 +4969,7 @@ Interactive commit selection if no commits are passed.
4979
4969
  **Kind**: inner constant of [<code>getDeltaList</code>](#DevOps.getDeltaList)
4980
4970
  <a name="DevOps.buildDeltaDefinitions"></a>
4981
4971
 
4982
- ### DevOps.buildDeltaDefinitions(properties, range, [skipInteraction]) ⇒ <code>Promise.&lt;Array.&lt;TYPE.DeltaPkgItem&gt;&gt;</code>
4972
+ ### DevOps.buildDeltaDefinitions(properties, range, [diffArr], [skipInteraction]) ⇒ <code>Promise.&lt;Array.&lt;TYPE.DeltaPkgItem&gt;&gt;</code>
4983
4973
  wrapper around DevOps.getDeltaList, Builder.buildTemplate and M
4984
4974
 
4985
4975
  **Kind**: static method of [<code>DevOps</code>](#DevOps)
@@ -4989,6 +4979,7 @@ wrapper around DevOps.getDeltaList, Builder.buildTemplate and M
4989
4979
  | --- | --- | --- |
4990
4980
  | properties | <code>TYPE.Mcdevrc</code> | project config file |
4991
4981
  | range | <code>string</code> | git commit range |
4982
+ | [diffArr] | <code>Array.&lt;TYPE.DeltaPkgItem&gt;</code> | instead of running git diff the method can also get a list of files to process |
4992
4983
  | [skipInteraction] | <code>TYPE.SkipInteraction</code> | allows to skip interactive wizard |
4993
4984
 
4994
4985
  <a name="DevOps.buildDeltaDefinitions..deltaDeployAll"></a>
@@ -6493,9 +6484,11 @@ wrapper around our standard winston logging to console and logfile
6493
6484
  initiate winston logger
6494
6485
 
6495
6486
  **Kind**: global function
6496
- <a name="SupportedMetadataTypes"></a>
6487
+ <a name="TypeKeyCombo"></a>
6488
+
6489
+ ## TypeKeyCombo : <code>Object.&lt;string, string&gt;</code>
6490
+ object-key=metadata type, value=array of external keys
6497
6491
 
6498
- ## SupportedMetadataTypes : <code>Object.&lt;string, string&gt;</code>
6499
6492
  **Kind**: global typedef
6500
6493
  <a name="Cache"></a>
6501
6494
 
@@ -6778,7 +6771,7 @@ SOAP format
6778
6771
  | binary | <code>boolean</code> | is a binary file |
6779
6772
  | moved | <code>boolean</code> | git thinks this file was moved |
6780
6773
  | [fromPath] | <code>string</code> | git thinks this relative path is where the file was before |
6781
- | type | [<code>SupportedMetadataTypes</code>](#SupportedMetadataTypes) | metadata type |
6774
+ | type | <code>SupportedMetadataTypes</code> | metadata type |
6782
6775
  | externalKey | <code>string</code> | key |
6783
6776
  | name | <code>string</code> | name |
6784
6777
  | gitAction | <code>&#x27;move&#x27;</code> \| <code>&#x27;add/update&#x27;</code> \| <code>&#x27;delete&#x27;</code> | what git recognized as an action |
package/lib/Deployer.js CHANGED
@@ -45,7 +45,7 @@ class Deployer {
45
45
  * Deploys all metadata located in the 'deploy' directory to the specified business unit
46
46
  *
47
47
  * @param {string} businessUnit references credentials from properties.json
48
- * @param {string[]} [selectedTypesArr] limit deployment to given metadata type
48
+ * @param {TYPE.SupportedMetadataTypes[]} [selectedTypesArr] limit deployment to given metadata type
49
49
  * @param {string[]} [keyArr] limit deployment to given metadata keys
50
50
  * @param {boolean} [fromRetrieve] optionally deploy whats defined via selectedTypesArr + keyArr directly from retrieve folder instead of from deploy folder
51
51
  * @returns {Promise.<void>} -
@@ -158,7 +158,7 @@ class Deployer {
158
158
  * @param {string} cred name of Credential
159
159
  * @param {string} bu name of BU
160
160
  * @param {TYPE.Mcdevrc} properties General configuration to be used in retrieve
161
- * @param {string[]} [typeArr] limit deployment to given metadata type
161
+ * @param {TYPE.SupportedMetadataTypes[]} [typeArr] limit deployment to given metadata type
162
162
  * @param {string[]} [keyArr] limit deployment to given metadata keys
163
163
  * @param {boolean} [fromRetrieve] optionally deploy whats defined via selectedTypesArr + keyArr directly from retrieve folder instead of from deploy folder
164
164
  * @returns {Promise} ensure that BUs are worked on sequentially
@@ -182,7 +182,7 @@ class Deployer {
182
182
  /**
183
183
  * Deploy all metadata that is located in the deployDir
184
184
  *
185
- * @param {string[]} [typeArr] limit deployment to given metadata type (can include subtype)
185
+ * @param {TYPE.SupportedMetadataTypes[]} [typeArr] limit deployment to given metadata type (can include subtype)
186
186
  * @param {string[]} [keyArr] limit deployment to given metadata keys
187
187
  * @param {boolean} [fromRetrieve] if true, no folders will be updated/created
188
188
  * @returns {Promise} Promise
@@ -247,28 +247,10 @@ class Deployer {
247
247
  this.buObject
248
248
  );
249
249
  cache.mergeMetadata(type, result);
250
- this.deployCallback(result, type);
251
250
  }
252
251
  }
253
252
  }
254
253
 
255
- /**
256
- * Gets called for every deployed metadata entry
257
- *
258
- * @param {object} result Deployment result
259
- * @param {string} metadataType Name of metadata type
260
- * @returns {void}
261
- */
262
- deployCallback(result, metadataType) {
263
- if (result) {
264
- File.writeJSONToFile('logs/', 'deployResult_' + metadataType, result);
265
- Util.logger.verbose(
266
- 'Deployer.deployCallback:: Results written to: ' +
267
- path.normalize(process.cwd() + '/logs/deployResult_' + metadataType + '.json')
268
- );
269
- }
270
- }
271
-
272
254
  /**
273
255
  * Returns metadata of a business unit that is saved locally
274
256
  *
package/lib/Retriever.js CHANGED
@@ -31,8 +31,8 @@ class Retriever {
31
31
  /**
32
32
  * Retrieve metadata of specified types into local file system and Retriever.metadata
33
33
  *
34
- * @param {string[]} metadataTypes String list of metadata types to retrieve
35
- * @param {string[]} [namesOrKeys] name of Metadata to retrieveAsTemplate or list of keys for normal retrieval
34
+ * @param {TYPE.SupportedMetadataTypes[]} metadataTypes list of metadata types to retrieve; can include subtypes!
35
+ * @param {string[]|TYPE.TypeKeyCombo} [namesOrKeys] name of Metadata to retrieveAsTemplate or list of keys for normal retrieval
36
36
  * @param {TYPE.TemplateMap} [templateVariables] Object of values which can be replaced (in case of templating)
37
37
  * @param {boolean} [changelogOnly] skip saving, only create json in memory
38
38
  * @returns {Promise.<TYPE.MultiMetadataTypeList>} Promise of a list of retrieved items grouped by type {automation:[...], query:[...]}
@@ -42,12 +42,31 @@ class Retriever {
42
42
  * @type {TYPE.MultiMetadataTypeList}
43
43
  */
44
44
  const retrieveChangelog = {};
45
- if (!namesOrKeys || !namesOrKeys.length) {
46
- // make iterating over namesOrKeys easier
45
+ if (!namesOrKeys || (Array.isArray(namesOrKeys) && !namesOrKeys.length)) {
46
+ // no keys were provided, ensure we retrieve all
47
47
  namesOrKeys = [null];
48
48
  }
49
+ /** @type {TYPE.TypeKeyCombo} */
50
+ let typeKeyMap = {};
51
+ if (Array.isArray(namesOrKeys)) {
52
+ // no keys or array of keys was provided (likely called via CLI or to retrieve all)
53
+ // transform into TypeKeyCombo to iterate over it
54
+ for (const type of metadataTypes) {
55
+ typeKeyMap[type] = namesOrKeys;
56
+ }
57
+ } else {
58
+ // assuming TypeKeyCombo was provided
59
+ typeKeyMap = namesOrKeys;
60
+ }
61
+ // defined colors for optionally printing the keys we filtered by
62
+ const color = {
63
+ reset: '\x1B[0m',
64
+ dim: '\x1B[2m',
65
+ };
49
66
  for (const metadataType of Util.getMetadataHierachy(metadataTypes)) {
50
67
  const [type, subType] = metadataType.split('-');
68
+ // if types were added by getMetadataHierachy() for caching, make sure the key-list is set to [null] for them which will retrieve all
69
+ typeKeyMap[metadataType] = typeKeyMap[metadataType] || [null];
51
70
  // add client to metadata process class instead of passing every time
52
71
  MetadataTypeInfo[type].client = auth.getSDK(this.buObject);
53
72
  MetadataTypeInfo[type].properties = this.properties;
@@ -65,7 +84,7 @@ class Retriever {
65
84
  Util.logger.info(`Retrieving as Template: ${metadataType}`);
66
85
 
67
86
  result = await Promise.all(
68
- namesOrKeys.map((name) =>
87
+ typeKeyMap[metadataType].map((name) =>
69
88
  MetadataTypeInfo[type].retrieveAsTemplate(
70
89
  this.templateDir,
71
90
  name,
@@ -75,11 +94,18 @@ class Retriever {
75
94
  )
76
95
  );
77
96
  } else {
78
- Util.logger.info('Retrieving: ' + metadataType);
97
+ Util.logger.info(
98
+ `Retrieving: ${metadataType}` +
99
+ (typeKeyMap[metadataType][0] !== null
100
+ ? ` ${color.dim}(Keys: ${typeKeyMap[metadataType].join(', ')})${
101
+ color.reset
102
+ }`
103
+ : '')
104
+ );
79
105
  result = await (changelogOnly
80
106
  ? MetadataTypeInfo[type].retrieveChangelog(this.buObject, null, subType)
81
107
  : Promise.all(
82
- namesOrKeys.map((key) =>
108
+ typeKeyMap[metadataType].map((key) =>
83
109
  MetadataTypeInfo[type].retrieve(
84
110
  this.savePath,
85
111
  null,
package/lib/index.js CHANGED
@@ -49,6 +49,7 @@ class Mcdev {
49
49
  * @param {string} [argv.range] git commit range
50
50
  into deploy directory
51
51
  * @param {string} [argv.filter] filter file paths that start with any
52
+ * @param {TYPE.DeltaPkgItem[]} [argv.diffArr] list of files to include in delta package (skips git diff when provided)
52
53
  * @param {TYPE.skipInteraction} [argv.skipInteraction] allows to skip interactive wizard
53
54
  * @returns {Promise.<TYPE.DeltaPkgItem[]>} list of changed items
54
55
  */
@@ -64,7 +65,12 @@ class Mcdev {
64
65
  ? // get source market and source BU from config
65
66
  DevOps.getDeltaList(properties, argv.range, true, argv.filter)
66
67
  : // If no custom filter was provided, use deployment marketLists & templating
67
- DevOps.buildDeltaDefinitions(properties, argv.range, argv.skipInteraction);
68
+ DevOps.buildDeltaDefinitions(
69
+ properties,
70
+ argv.range,
71
+ argv.diffArr,
72
+ argv.skipInteraction
73
+ );
68
74
  }
69
75
 
70
76
  /**
@@ -105,7 +111,7 @@ class Mcdev {
105
111
  * Retrieve all metadata from the specified business unit into the local file system.
106
112
  *
107
113
  * @param {string} businessUnit references credentials from properties.json
108
- * @param {string[]} [selectedTypesArr] limit retrieval to given metadata type
114
+ * @param {TYPE.SupportedMetadataTypes[]|TYPE.TypeKeyCombo} [selectedTypesArr] limit retrieval to given metadata type
109
115
  * @param {string[]} [keys] limit retrieval to given metadata key
110
116
  * @param {boolean} [changelogOnly] skip saving, only create json in memory
111
117
  * @returns {Promise.<object>} -
@@ -120,7 +126,9 @@ class Mcdev {
120
126
 
121
127
  // assume a list was passed in and check each entry's validity
122
128
  if (selectedTypesArr) {
123
- for (const selectedType of selectedTypesArr) {
129
+ for (const selectedType of Array.isArray(selectedTypesArr)
130
+ ? selectedTypesArr
131
+ : Object.keys(selectedTypesArr)) {
124
132
  if (!Util._isValidType(selectedType)) {
125
133
  return;
126
134
  }
@@ -195,7 +203,7 @@ class Mcdev {
195
203
  * @private
196
204
  * @param {string} cred name of Credential
197
205
  * @param {string} bu name of BU
198
- * @param {string[]} [selectedTypesArr] limit retrieval to given metadata type/subtype
206
+ * @param {TYPE.SupportedMetadataTypes[]|TYPE.TypeKeyCombo} [selectedTypesArr] limit retrieval to given metadata type/subtype
199
207
  * @param {string[]} [keys] limit retrieval to given metadata key
200
208
  * @param {boolean} [changelogOnly] skip saving, only create json in memory
201
209
  * @returns {Promise.<object>} ensure that BUs are worked on sequentially
@@ -218,7 +226,9 @@ class Mcdev {
218
226
  Util.logger.info(`\n :: Retrieving ${cred}/${bu}\n`);
219
227
  const retrieveTypesArr = [];
220
228
  if (selectedTypesArr) {
221
- for (const selectedType of selectedTypesArr) {
229
+ for (const selectedType of Array.isArray(selectedTypesArr)
230
+ ? selectedTypesArr
231
+ : Object.keys(selectedTypesArr)) {
222
232
  const [type, subType] = selectedType ? selectedType.split('-') : [];
223
233
  const removePathArr = [properties.directories.retrieve, cred, bu, type];
224
234
  if (
@@ -255,7 +265,7 @@ class Mcdev {
255
265
  // await is required or the calls end up conflicting
256
266
  const retrieveChangelog = await retriever.retrieve(
257
267
  retrieveTypesArr,
258
- keys,
268
+ Array.isArray(selectedTypesArr) ? keys : selectedTypesArr,
259
269
  null,
260
270
  changelogOnly
261
271
  );
@@ -272,7 +282,7 @@ class Mcdev {
272
282
  * Deploys all metadata located in the 'deploy' directory to the specified business unit
273
283
  *
274
284
  * @param {string} businessUnit references credentials from properties.json
275
- * @param {string[]} [selectedTypesArr] limit deployment to given metadata type
285
+ * @param {TYPE.SupportedMetadataTypes[]} [selectedTypesArr] limit deployment to given metadata type
276
286
  * @param {string[]} [keyArr] limit deployment to given metadata keys
277
287
  * @param {boolean} [fromRetrieve] optionally deploy whats defined via selectedTypesArr + keyArr directly from retrieve folder instead of from deploy folder
278
288
  * @returns {Promise.<void>} -
@@ -276,7 +276,11 @@ class Asset extends MetadataType {
276
276
  } else if (retrieveDir && !items.length) {
277
277
  Util.logger.info(` Downloaded asset-${subType}: ${items.length}`);
278
278
  }
279
- return items;
279
+
280
+ return items.map((item) => {
281
+ item._subType = subType;
282
+ return item;
283
+ });
280
284
  }
281
285
  /**
282
286
  * Retrieves extended metadata (files or extended content) of asset
@@ -446,20 +450,33 @@ class Asset extends MetadataType {
446
450
  * @param {TYPE.AssetItem} metadata a single asset
447
451
  * @param {TYPE.AssetSubType} subType group of similar assets to put in a folder (ie. images)
448
452
  * @param {string} deployDir directory of deploy files
449
- * @returns {Promise.<void>} -
453
+ * @param {boolean} [pathOnly=false] used by getFilesToCommit which does not need the binary file to be actually read
454
+ * @returns {Promise.<string>} if found will return the path of the binary file
450
455
  */
451
- static async _readExtendedFileFromFS(metadata, subType, deployDir) {
452
- if (metadata.fileProperties && metadata.fileProperties.extension) {
453
- // to handle uploaded files that bear the same name, SFMC engineers decided to add a number after the fileName
454
- // however, their solution was not following standards: fileName="header.png (4) " and then extension="png (4) "
455
- const fileExt = metadata.fileProperties.extension.split(' ')[0];
456
+ static async _readExtendedFileFromFS(metadata, subType, deployDir, pathOnly = false) {
457
+ // to handle uploaded files that bear the same name, SFMC engineers decided to add a number after the fileName
458
+ // however, their solution was not following standards: fileName="header.png (4) " and then extension="png (4) "
459
+ if (!metadata?.fileProperties?.extension) {
460
+ return;
461
+ }
462
+ const fileExt = metadata.fileProperties.extension.split(' ')[0];
456
463
 
457
- metadata.file = await File.readFilteredFilename(
458
- [deployDir, this.definition.type, subType],
459
- metadata.customerKey,
460
- fileExt,
461
- 'base64'
462
- );
464
+ const path = File.normalizePath([
465
+ deployDir,
466
+ this.definition.type,
467
+ subType,
468
+ `${metadata.customerKey}.${fileExt}`,
469
+ ]);
470
+ if (await File.pathExists(path)) {
471
+ if (!pathOnly) {
472
+ metadata.file = await File.readFilteredFilename(
473
+ [deployDir, this.definition.type, subType],
474
+ metadata.customerKey,
475
+ fileExt,
476
+ 'base64'
477
+ );
478
+ }
479
+ return path;
463
480
  }
464
481
  }
465
482
  /**
@@ -512,8 +529,8 @@ class Asset extends MetadataType {
512
529
  metadata.memberId !== buObject.mid &&
513
530
  !metadata[this.definition.keyField].startsWith(buObject.mid)
514
531
  ) {
515
- // #3 make sure customer key is unique by prefixing it with target MID (unless we are deploying to the same MID)
516
- // check if this prefixed with the source MID
532
+ // #3 make sure customer key is unique by suffixing it with target MID (unless we are deploying to the same MID)
533
+ // check if this suffixed with the source MID
517
534
  const suffix = '-' + buObject.mid;
518
535
  // for customer key max is 36 chars
519
536
  metadata[this.definition.keyField] =
@@ -1362,6 +1379,7 @@ class Asset extends MetadataType {
1362
1379
  const fileList = (
1363
1380
  await Promise.all(
1364
1381
  keyArr.map(async (key) => {
1382
+ // get subType, path an fileName by scanning the retrieve folder
1365
1383
  let subType;
1366
1384
  let filePath;
1367
1385
  let fileName;
@@ -1391,8 +1409,13 @@ class Asset extends MetadataType {
1391
1409
  break;
1392
1410
  }
1393
1411
  }
1394
- if (await File.pathExists(File.normalizePath([...filePath, fileName]))) {
1412
+ if (
1413
+ Array.isArray(filePath) &&
1414
+ (await File.pathExists(File.normalizePath([...filePath, fileName])))
1415
+ ) {
1416
+ // #1 load json to be able to find extracted text files & binary files
1395
1417
  const metadata = File.readJSONFile(filePath, fileName, true, false);
1418
+ // #2 find all extracted text files
1396
1419
  const fileListNested = (
1397
1420
  await this._mergeCode(metadata, basePath, subType, metadata.customerKey)
1398
1421
  ).map((item) =>
@@ -1402,8 +1425,22 @@ class Asset extends MetadataType {
1402
1425
  `${item.fileName}.${item.fileExt}`,
1403
1426
  ])
1404
1427
  );
1428
+ const response = [
1429
+ File.normalizePath([...filePath, fileName]),
1430
+ ...fileListNested,
1431
+ ];
1432
+ // #3 get binary file
1433
+ const binaryFilePath = await this._readExtendedFileFromFS(
1434
+ metadata,
1435
+ subType,
1436
+ basePath,
1437
+ false
1438
+ );
1439
+ if (binaryFilePath) {
1440
+ response.push(binaryFilePath);
1441
+ }
1405
1442
 
1406
- return [File.normalizePath([...filePath, fileName]), ...fileListNested];
1443
+ return response;
1407
1444
  } else {
1408
1445
  return [];
1409
1446
  }
@@ -284,7 +284,7 @@ class Script extends MetadataType {
284
284
  ssjs = metadata.script;
285
285
  fileExt = 'html';
286
286
  Util.logger.warn(
287
- ' - Could not find script tags, saving whole text in SSJS: ' + metadata.name
287
+ ` - Could not find script tags, saving code in ${metadata.name}.script-meta.html instead of as SSJS file.`
288
288
  );
289
289
  }
290
290
  delete metadata.script;
@@ -315,6 +315,7 @@ class Script extends MetadataType {
315
315
  const fileList = keyArr.flatMap((key) => [
316
316
  File.normalizePath([path, `${key}.${this.definition.type}-meta.json`]),
317
317
  File.normalizePath([path, `${key}.${this.definition.type}-meta.ssjs`]),
318
+ File.normalizePath([path, `${key}.${this.definition.type}-meta.html`]),
318
319
  ]);
319
320
  return fileList;
320
321
  }
@@ -248,10 +248,11 @@ const DevOps = {
248
248
  *
249
249
  * @param {TYPE.Mcdevrc} properties project config file
250
250
  * @param {string} range git commit range
251
+ * @param {TYPE.DeltaPkgItem[]} [diffArr] instead of running git diff the method can also get a list of files to process
251
252
  * @param {TYPE.SkipInteraction} [skipInteraction] allows to skip interactive wizard
252
253
  * @returns {Promise.<TYPE.DeltaPkgItem[]>} -
253
254
  */
254
- async buildDeltaDefinitions(properties, range, skipInteraction) {
255
+ async buildDeltaDefinitions(properties, range, diffArr, skipInteraction) {
255
256
  // check if sourceTargetMapping is valid
256
257
  if (
257
258
  !properties.options.deployment.sourceTargetMapping ||
@@ -304,7 +305,9 @@ const DevOps = {
304
305
  const sourceBU = Object.keys(properties.marketList[sourceMlName])[0];
305
306
  const sourceMarket = Object.values(properties.marketList[sourceMlName])[0];
306
307
 
307
- const delta = await DevOps.getDeltaList(properties, range, false, sourceBU);
308
+ const delta = Array.isArray(diffArr)
309
+ ? diffArr
310
+ : await DevOps.getDeltaList(properties, range, false, sourceBU);
308
311
  // If only chaing templating and buildDefinition if required
309
312
  if (!delta || delta.length === 0) {
310
313
  // info/error messages was printed by DevOps.createDeltaPkg() already
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcdev",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "description": "Accenture Salesforce Marketing Cloud DevTools",
5
5
  "author": "joern.berkefeld, douglas.midgley, robert.zimmermann, maciej.barnas",
6
6
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "lint-type": "eslint types/*.js",
37
37
  "lint-test": "eslint test/**/*.js",
38
38
  "upgrade": "npm-check --update",
39
- "prepare": "husky install",
39
+ "manual-prepare": "husky install",
40
40
  "test": "mocha"
41
41
  },
42
42
  "dependencies": {
@@ -67,14 +67,14 @@
67
67
  "eslint": "8.25.0",
68
68
  "eslint-config-prettier": "8.5.0",
69
69
  "eslint-config-ssjs": "1.1.11",
70
- "eslint-plugin-jsdoc": "39.3.6",
70
+ "eslint-plugin-jsdoc": "39.3.13",
71
71
  "eslint-plugin-mocha": "10.1.0",
72
72
  "eslint-plugin-prettier": "4.2.1",
73
73
  "eslint-plugin-unicorn": "44.0.2",
74
74
  "husky": "8.0.1",
75
75
  "jsdoc-to-markdown": "7.1.1",
76
76
  "lint-staged": "13.0.3",
77
- "mocha": "10.0.0",
77
+ "mocha": "10.1.0",
78
78
  "mock-fs": "5.1.4",
79
79
  "npm-check": "6.0.1",
80
80
  "npm-run-all": "4.1.5",
package/types/mcdev.d.js CHANGED
@@ -12,7 +12,8 @@ const SDK = require('sfmc-sdk');
12
12
  */
13
13
  /**
14
14
  * @typedef {Object.<string, string>} TemplateMap
15
- * @typedef {'accountUser'|'asset'|'attributeGroup'|'automation'|'campaign'|'contentArea'|'dataExtension'|'dataExtensionField'|'dataExtensionTemplate'|'dataExtract'|'dataExtractType'|'discovery'|'email'|'emailSendDefinition'|'eventDefinition'|'fileTransfer'|'filter'|'folder'|'ftpLocation'|'importFile'|'interaction'|'list'|'mobileCode'|'mobileKeyword'|'query'|'role'|'script'|'setDefinition'|'triggeredSendDefinition'} SupportedMetadataTypes
15
+ * @typedef {'accountUser'|'asset'|'asset-archive'|'asset-asset'|'asset-audio'|'asset-block'|'asset-code'|'asset-document'|'asset-image'|'asset-message'|'asset-other'|'asset-rawimage'|'asset-template'|'asset-textfile'|'asset-video'|'attributeGroup'|'automation'|'campaign'|'contentArea'|'dataExtension'|'dataExtensionField'|'dataExtensionTemplate'|'dataExtract'|'dataExtractType'|'discovery'|'email'|'emailSendDefinition'|'eventDefinition'|'fileTransfer'|'filter'|'folder'|'ftpLocation'|'importFile'|'interaction'|'list'|'mobileCode'|'mobileKeyword'|'query'|'role'|'script'|'setDefinition'|'triggeredSendDefinition'} SupportedMetadataTypes
16
+ * @typedef {Object.<SupportedMetadataTypes, string[]>} TypeKeyCombo object-key=metadata type, value=array of external keys
16
17
  */
17
18
 
18
19
  /**