mcdev 3.1.3 → 4.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.
Files changed (134) hide show
  1. package/.eslintrc.json +67 -7
  2. package/.github/ISSUE_TEMPLATE/bug.yml +2 -1
  3. package/.github/PULL_REQUEST_TEMPLATE.md +5 -3
  4. package/.github/dependabot.yml +14 -0
  5. package/.github/workflows/code-analysis.yml +57 -0
  6. package/.husky/commit-msg +10 -0
  7. package/.husky/post-checkout +5 -0
  8. package/.husky/pre-commit +2 -1
  9. package/.prettierrc +8 -0
  10. package/.vscode/settings.json +1 -1
  11. package/LICENSE +2 -2
  12. package/README.md +134 -45
  13. package/boilerplate/config.json +5 -11
  14. package/boilerplate/files/.prettierrc +8 -0
  15. package/boilerplate/files/.vscode/extensions.json +0 -1
  16. package/boilerplate/files/.vscode/settings.json +30 -2
  17. package/boilerplate/files/README.md +2 -2
  18. package/boilerplate/forcedUpdates.json +10 -0
  19. package/boilerplate/npm-dependencies.json +5 -5
  20. package/docs/dist/documentation.md +2807 -1730
  21. package/jsconfig.json +1 -1
  22. package/lib/Builder.js +171 -74
  23. package/lib/Deployer.js +244 -96
  24. package/lib/MetadataTypeDefinitions.js +2 -0
  25. package/lib/MetadataTypeInfo.js +2 -0
  26. package/lib/Retriever.js +61 -84
  27. package/lib/cli.js +116 -11
  28. package/lib/index.js +241 -561
  29. package/lib/metadataTypes/AccountUser.js +117 -103
  30. package/lib/metadataTypes/Asset.js +705 -255
  31. package/lib/metadataTypes/AttributeGroup.js +23 -12
  32. package/lib/metadataTypes/Automation.js +489 -392
  33. package/lib/metadataTypes/Campaign.js +33 -93
  34. package/lib/metadataTypes/ContentArea.js +31 -11
  35. package/lib/metadataTypes/DataExtension.js +387 -372
  36. package/lib/metadataTypes/DataExtensionField.js +131 -54
  37. package/lib/metadataTypes/DataExtensionTemplate.js +22 -4
  38. package/lib/metadataTypes/DataExtract.js +61 -48
  39. package/lib/metadataTypes/DataExtractType.js +14 -8
  40. package/lib/metadataTypes/Discovery.js +21 -16
  41. package/lib/metadataTypes/Email.js +32 -12
  42. package/lib/metadataTypes/EmailSendDefinition.js +85 -80
  43. package/lib/metadataTypes/EventDefinition.js +61 -43
  44. package/lib/metadataTypes/FileTransfer.js +72 -52
  45. package/lib/metadataTypes/Filter.js +11 -4
  46. package/lib/metadataTypes/Folder.js +149 -117
  47. package/lib/metadataTypes/FtpLocation.js +14 -8
  48. package/lib/metadataTypes/ImportFile.js +61 -64
  49. package/lib/metadataTypes/Interaction.js +19 -4
  50. package/lib/metadataTypes/List.js +54 -13
  51. package/lib/metadataTypes/MetadataType.js +664 -454
  52. package/lib/metadataTypes/MobileCode.js +46 -0
  53. package/lib/metadataTypes/MobileKeyword.js +114 -0
  54. package/lib/metadataTypes/Query.js +206 -105
  55. package/lib/metadataTypes/Role.js +76 -61
  56. package/lib/metadataTypes/Script.js +147 -83
  57. package/lib/metadataTypes/SetDefinition.js +20 -8
  58. package/lib/metadataTypes/TriggeredSendDefinition.js +78 -58
  59. package/lib/metadataTypes/definitions/Asset.definition.js +21 -10
  60. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +12 -0
  61. package/lib/metadataTypes/definitions/Automation.definition.js +10 -5
  62. package/lib/metadataTypes/definitions/Campaign.definition.js +44 -1
  63. package/lib/metadataTypes/definitions/DataExtension.definition.js +4 -0
  64. package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +6 -0
  65. package/lib/metadataTypes/definitions/DataExtract.definition.js +18 -14
  66. package/lib/metadataTypes/definitions/Discovery.definition.js +12 -0
  67. package/lib/metadataTypes/definitions/EmailSendDefinition.definition.js +4 -0
  68. package/lib/metadataTypes/definitions/EventDefinition.definition.js +22 -0
  69. package/lib/metadataTypes/definitions/FileTransfer.definition.js +4 -0
  70. package/lib/metadataTypes/definitions/Filter.definition.js +4 -0
  71. package/lib/metadataTypes/definitions/Folder.definition.js +6 -0
  72. package/lib/metadataTypes/definitions/FtpLocation.definition.js +4 -0
  73. package/lib/metadataTypes/definitions/ImportFile.definition.js +10 -5
  74. package/lib/metadataTypes/definitions/Interaction.definition.js +4 -0
  75. package/lib/metadataTypes/definitions/MobileCode.definition.js +163 -0
  76. package/lib/metadataTypes/definitions/MobileKeyword.definition.js +253 -0
  77. package/lib/metadataTypes/definitions/Query.definition.js +4 -0
  78. package/lib/metadataTypes/definitions/Role.definition.js +5 -0
  79. package/lib/metadataTypes/definitions/Script.definition.js +4 -0
  80. package/lib/metadataTypes/definitions/SetDefinition.definition.js +28 -0
  81. package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +4 -0
  82. package/lib/retrieveChangelog.js +7 -6
  83. package/lib/util/auth.js +117 -0
  84. package/lib/util/businessUnit.js +55 -66
  85. package/lib/util/cache.js +194 -0
  86. package/lib/util/cli.js +90 -116
  87. package/lib/util/config.js +302 -0
  88. package/lib/util/devops.js +250 -50
  89. package/lib/util/file.js +141 -201
  90. package/lib/util/init.config.js +208 -75
  91. package/lib/util/init.git.js +45 -50
  92. package/lib/util/init.js +72 -59
  93. package/lib/util/init.npm.js +48 -39
  94. package/lib/util/util.js +280 -564
  95. package/package.json +45 -34
  96. package/test/dataExtension.test.js +152 -0
  97. package/test/mockRoot/.mcdev-auth.json +8 -0
  98. package/test/mockRoot/.mcdevrc.json +67 -0
  99. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/childBU_dataextension_test.dataExtension-meta.json +39 -0
  100. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/testDataExtension.dataExtension-meta.json +23 -0
  101. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +11 -0
  102. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.sql +4 -0
  103. package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.json +11 -0
  104. package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.sql +4 -0
  105. package/test/query.test.js +149 -0
  106. package/test/resourceFactory.js +142 -0
  107. package/test/resources/1111111/dataFolder/retrieve-response.xml +43 -0
  108. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +18 -0
  109. package/test/resources/9999999/automation/v1/queries/get-response.json +24 -0
  110. package/test/resources/9999999/automation/v1/queries/post-response.json +18 -0
  111. package/test/resources/9999999/dataExtension/build-expected.json +51 -0
  112. package/test/resources/9999999/dataExtension/create-expected.json +23 -0
  113. package/test/resources/9999999/dataExtension/create-response.xml +54 -0
  114. package/test/resources/9999999/dataExtension/retrieve-expected.json +51 -0
  115. package/test/resources/9999999/dataExtension/retrieve-response.xml +47 -0
  116. package/test/resources/9999999/dataExtension/template-expected.json +51 -0
  117. package/test/resources/9999999/dataExtension/update-expected.json +55 -0
  118. package/test/resources/9999999/dataExtension/update-response.xml +52 -0
  119. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +93 -0
  120. package/test/resources/9999999/dataExtensionTemplate/retrieve-response.xml +303 -0
  121. package/test/resources/9999999/dataFolder/retrieve-response.xml +65 -0
  122. package/test/resources/9999999/query/build-expected.json +8 -0
  123. package/test/resources/9999999/query/get-expected.json +11 -0
  124. package/test/resources/9999999/query/patch-expected.json +11 -0
  125. package/test/resources/9999999/query/post-expected.json +11 -0
  126. package/test/resources/9999999/query/template-expected.json +8 -0
  127. package/test/resources/auth.json +32 -0
  128. package/test/resources/rest404-response.json +5 -0
  129. package/test/resources/retrieve-response.xml +21 -0
  130. package/test/utils.js +107 -0
  131. package/types/mcdev.d.js +301 -0
  132. package/CHANGELOG.md +0 -126
  133. package/PULL_REQUEST_TEMPLATE.md +0 -19
  134. package/test/util/file.js +0 -51
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ const TYPE = require('../../types/mcdev.d');
4
+ const MetadataType = require('./MetadataType');
5
+
6
+ /**
7
+ * MobileCode MetadataType
8
+ *
9
+ * @augments MetadataType
10
+ */
11
+ class MobileCode extends MetadataType {
12
+ /**
13
+ * Retrieves Metadata of Mobile Keywords
14
+ * Endpoint /legacy/v1/beta/mobile/code/ return all Mobile Codes with all details.
15
+ *
16
+ * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
17
+ * @param {void} [_] unused parameter
18
+ * @param {void} [__] unused parameter
19
+ * @param {void} [___] unused parameter
20
+ * @param {string} [key] customer key of single item to retrieve
21
+ * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
22
+ */
23
+ static retrieve(retrieveDir, _, __, ___, key) {
24
+ return super.retrieveREST(
25
+ retrieveDir,
26
+ '/legacy/v1/beta/mobile/code/' + (key ? `?$where=keyword%20eq%20%27${key}%27%20` : ''),
27
+ null,
28
+ null,
29
+ key
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Retrieves event definition metadata for caching
35
+ *
36
+ * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
37
+ */
38
+ static retrieveForCache() {
39
+ return super.retrieveREST(null, '/legacy/v1/beta/mobile/code/');
40
+ }
41
+ }
42
+
43
+ // Assign definition to static attributes
44
+ MobileCode.definition = require('../MetadataTypeDefinitions').mobileCode;
45
+
46
+ module.exports = MobileCode;
@@ -0,0 +1,114 @@
1
+ 'use strict';
2
+
3
+ const TYPE = require('../../types/mcdev.d');
4
+ const MetadataType = require('./MetadataType');
5
+ const Util = require('../util/util');
6
+ const File = require('../util/file');
7
+ const cache = require('../util/cache');
8
+
9
+ /**
10
+ * MobileKeyword MetadataType
11
+ *
12
+ * @augments MetadataType
13
+ */
14
+ class MobileKeyword extends MetadataType {
15
+ /**
16
+ * Retrieves Metadata of Mobile Keywords
17
+ * Endpoint /legacy/v1/beta/mobile/keyword/ return all Mobile Keywords with all details.
18
+ *
19
+ * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
20
+ * @param {void} [_] unused parameter
21
+ * @param {void} [__] unused parameter
22
+ * @param {void} [___] unused parameter
23
+ * @param {string} [key] customer key of single item to retrieve
24
+ * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
25
+ */
26
+ static retrieve(retrieveDir, _, __, ___, key) {
27
+ return super.retrieveREST(
28
+ retrieveDir,
29
+ '/legacy/v1/beta/mobile/keyword/?view=simple' +
30
+ (key ? `&$where=keyword%20eq%20%27${key}%27%20` : ''),
31
+ null,
32
+ null,
33
+ key
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Retrieves event definition metadata for caching
39
+ *
40
+ * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
41
+ */
42
+ static retrieveForCache() {
43
+ return super.retrieveREST(null, '/legacy/v1/beta/mobile/keyword/?view=simple');
44
+ }
45
+
46
+ /**
47
+ * Retrieve a specific keyword
48
+ *
49
+ * @param {string} templateDir Directory where retrieved metadata directory will be saved
50
+ * @param {string} name name of the metadata file
51
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
52
+ * @returns {Promise.<TYPE.MetadataTypeItemObj>} Promise of metadata
53
+ */
54
+ static async retrieveAsTemplate(templateDir, name, templateVariables) {
55
+ // TODO: Decide if we want to keep default handling (and move other types) or keep unique parsing?
56
+ try {
57
+ const res = await this.client.rest.get(
58
+ `/legacy/v1/beta/mobile/keyword/?view=simple&$where=keyword%20eq%20%27${name}%27%20`
59
+ );
60
+ const metadata = JSON.parse(
61
+ Util.replaceByObject(
62
+ JSON.stringify(Util.templateSearchResult(res.entry, 'keyword', name)),
63
+ templateVariables
64
+ )
65
+ );
66
+ if (!metadata.code.id) {
67
+ throw new Error(
68
+ `MobileKeyword.parseMetadata:: ` +
69
+ `No Mobile Code was found for ` +
70
+ `event: ${metadata.name}. ` +
71
+ `This cannot be templated`
72
+ );
73
+ }
74
+ // remove all fields listed in Definition for templating
75
+ this.keepTemplateFields(metadata);
76
+ await File.writeJSONToFile(
77
+ [templateDir, this.definition.type].join('/'),
78
+ metadata.keyword + '.' + this.definition.type + '-meta',
79
+ metadata
80
+ );
81
+ Util.logger.info(`- templated ${this.definition.type}: ${name}`);
82
+ return { metadata: metadata, type: this.definition.type };
83
+ } catch (ex) {
84
+ Util.logger.error('MobileKeyword.retrieveAsTemplate:: ' + ex);
85
+ return null;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Creates a single Event Definition
91
+ *
92
+ * @param {TYPE.MetadataTypeItem} MobileKeyword a single Event Definition
93
+ * @returns {Promise} Promise
94
+ */
95
+ static create(MobileKeyword) {
96
+ return super.createREST(MobileKeyword, '/legacy/v1/beta/mobile/keyword/');
97
+ }
98
+
99
+ /**
100
+ * prepares an event definition for deployment
101
+ *
102
+ * @param {TYPE.MetadataTypeItem} metadata a single MobileKeyword
103
+ * @returns {TYPE.MetadataTypeItem} Promise
104
+ */
105
+ static preDeployTasks(metadata) {
106
+ metadata.code.id = cache.searchForField('mobileCode', metadata.code.code, 'code', 'id');
107
+ return metadata;
108
+ }
109
+ }
110
+
111
+ // Assign definition to static attributes
112
+ MobileKeyword.definition = require('../MetadataTypeDefinitions').mobileKeyword;
113
+
114
+ module.exports = MobileKeyword;
@@ -1,97 +1,98 @@
1
1
  'use strict';
2
2
 
3
+ const TYPE = require('../../types/mcdev.d');
3
4
  const MetadataType = require('./MetadataType');
4
5
  const Util = require('../util/util');
5
6
  const File = require('../util/file');
7
+ const cache = require('../util/cache');
6
8
  const Mustache = require('mustache');
7
9
 
8
- /**
9
- * @typedef {Object} QueryItem
10
- * @property {string} name name
11
- * @property {string} key key
12
- * @property {string} description -
13
- * @property {string} targetKey key of target data extension
14
- * @property {string} createdDate e.g. "2020-09-14T01:42:03.017"
15
- * @property {string} modifiedDate e.g. "2020-09-14T01:42:03.017"
16
- * @property {'Overwrite'|'Update'|'Append'} targetUpdateTypeName defines how the query writes into the target data extension
17
- * @property {0|1|2} [targetUpdateTypeId] mapped to targetUpdateTypeName via this.definition.targetUpdateTypeMapping
18
- * @property {string} [targetId] Object ID of DE (removed before save)
19
- * @property {string} [targetDescription] Description DE (removed before save)
20
- * @property {boolean} isFrozen looks like this is always set to false
21
- * @property {string} [queryText] contains SQL query with line breaks converted to '\n'. The content is extracted during retrieval and written into a separate *.sql file
22
- * @property {string} [categoryId] holds folder ID, replaced with r__folder_Path during retrieve
23
- * @property {string} r__folder_Path folder path in which this DE is saved
24
- *
25
- * @typedef {Object.<string, QueryItem>} QueryMap
26
- *
27
- * @typedef {Object} CodeExtractItem
28
- * @property {QueryItem} json metadata of one item w/o code
29
- * @property {MetadataType.CodeExtract[]} codeArr list of code snippets in this item
30
- * @property {string[]} subFolder mostly set to null, otherwise list of subfolders
31
- */
32
-
33
10
  /**
34
11
  * Query MetadataType
12
+ *
35
13
  * @augments MetadataType
36
14
  */
37
15
  class Query extends MetadataType {
38
16
  /**
39
17
  * Retrieves Metadata of queries
18
+ *
40
19
  * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
41
- * @returns {Promise<{metadata:QueryMap,type:string}>} Promise of metadata
20
+ * @param {void} [_] unused parameter
21
+ * @param {void} [__] unused parameter
22
+ * @param {void} [___] unused parameter
23
+ * @param {string} [key] customer key of single item to retrieve
24
+ * @returns {Promise.<{metadata: TYPE.QueryMap, type: string}>} Promise of metadata
25
+ */
26
+ static async retrieve(retrieveDir, _, __, ___, key) {
27
+ await File.initPrettier('sql');
28
+ const objectId = key ? await this._getObjectIdForSingleRetrieve(key) : null;
29
+ return super.retrieveREST(
30
+ retrieveDir,
31
+ '/automation/v1/queries/' + (objectId || ''),
32
+ null,
33
+ null,
34
+ key
35
+ );
36
+ }
37
+ /**
38
+ * helper to allow us to select single metadata entries via REST
39
+ *
40
+ * @private
41
+ * @param {string} key customer key
42
+ * @returns {Promise.<string>} objectId or enpty string
42
43
  */
43
- static async retrieve(retrieveDir) {
44
- return super.retrieveREST(retrieveDir, '/automation/v1/queries/', null);
44
+ static async _getObjectIdForSingleRetrieve(key) {
45
+ const response = await this.client.soap.retrieve('QueryDefinition', ['ObjectID'], {
46
+ filter: {
47
+ leftOperand: 'CustomerKey',
48
+ operator: 'equals',
49
+ rightOperand: key,
50
+ },
51
+ });
52
+ return response?.Results?.length ? response.Results[0].ObjectID : null;
45
53
  }
46
54
 
47
55
  /**
48
56
  * Retrieves query metadata for caching
49
- * @returns {Promise<{metadata:QueryMap,type:string}>} Promise of metadata
57
+ *
58
+ * @returns {Promise.<{metadata: TYPE.QueryMap, type: string}>} Promise of metadata
50
59
  */
51
60
  static async retrieveForCache() {
52
- return super.retrieveREST(null, '/automation/v1/queries/', null);
61
+ return super.retrieveREST(null, '/automation/v1/queries/');
53
62
  }
54
63
 
55
64
  /**
56
65
  * Retrieve a specific Query by Name
66
+ *
57
67
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
58
68
  * @param {string} name name of the metadata file
59
- * @param {Util.TemplateMap} templateVariables variables to be replaced in the metadata
60
- * @returns {Promise<{metadata:QueryMap,type:string}>} Promise of metadata
69
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
70
+ * @returns {Promise.<{metadata: Query, type: string}>} Promise of metadata
61
71
  */
62
72
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
63
- const response = await super.retrieveREST(
73
+ await File.initPrettier('sql');
74
+ return super.retrieveREST(
64
75
  templateDir,
65
- '/automation/v1/queries/?$filter=Name%20eq%20' + name.split(' ').join('%20'),
76
+ '/automation/v1/queries/?$filter=Name%20eq%20' + encodeURIComponent(name),
66
77
  null,
67
78
  templateVariables
68
79
  );
69
- if (response && response.metadata && !Object.keys(response.metadata).length) {
70
- Util.logger.error(`${this.definition.type} '${name}' not found on server.`);
71
- }
72
- return response;
73
80
  }
74
81
 
75
82
  /**
76
83
  * manages post retrieve steps
77
- * @param {QueryItem} metadata a single query
78
- * @param {string} _ unused
79
- * @param {boolean} isTemplating signals that we are retrieving templates
80
- * @returns {CodeExtractItem} Array with one metadata object and one query string
84
+ *
85
+ * @param {TYPE.QueryItem} metadata a single query
86
+ * @returns {TYPE.CodeExtractItem} Array with one metadata object and one query string
81
87
  */
82
- static postRetrieveTasks(metadata, _, isTemplating) {
83
- // if retrieving template, replace the name with customer key if that wasn't already the case
84
- if (isTemplating) {
85
- const warningMsg =
86
- 'Ensure that Automations using this query are updated with the new query-key before deployment.';
87
- this.overrideKeyWithName(metadata, warningMsg);
88
- }
88
+ static postRetrieveTasks(metadata) {
89
89
  return this.parseMetadata(metadata);
90
90
  }
91
91
 
92
92
  /**
93
93
  * Creates a single query
94
- * @param {QueryItem} query a single query
94
+ *
95
+ * @param {TYPE.QueryItem} query a single query
95
96
  * @returns {Promise} Promise
96
97
  */
97
98
  static create(query) {
@@ -101,7 +102,8 @@ class Query extends MetadataType {
101
102
 
102
103
  /**
103
104
  * Updates a single query
104
- * @param {QueryItem} query a single query
105
+ *
106
+ * @param {TYPE.QueryItem} query a single query
105
107
  * @returns {Promise} Promise
106
108
  */
107
109
  static update(query) {
@@ -111,112 +113,179 @@ class Query extends MetadataType {
111
113
 
112
114
  /**
113
115
  * prepares a Query for deployment
114
- * @param {QueryItem} metadata a single query activity
116
+ *
117
+ * @param {TYPE.QueryItem} metadata a single query activity
115
118
  * @param {string} deployDir directory of deploy files
116
- * @returns {Promise<QueryItem>} Promise
119
+ * @returns {Promise.<TYPE.QueryItem>} Promise
117
120
  */
118
121
  static async preDeployTasks(metadata, deployDir) {
119
- metadata.queryText = await File.readFile(
122
+ metadata.queryText = await File.readFilteredFilename(
120
123
  deployDir + '/' + this.definition.type,
121
124
  metadata.key + '.' + this.definition.type + '-meta',
122
125
  'sql'
123
126
  );
124
- try {
125
- metadata.targetKey = Util.getFromCache(
126
- this.cache,
127
- 'dataExtension',
128
- metadata.targetKey,
129
- 'CustomerKey',
130
- 'CustomerKey'
131
- );
132
- } catch (ex) {
133
- throw new Error(`Query '${metadata.key}': ${ex.message}`);
134
- }
135
- try {
136
- metadata.categoryId = Util.getFromCache(
137
- this.cache,
138
- 'folder',
139
- metadata.r__folder_Path,
140
- 'Path',
141
- 'ID'
142
- );
143
- delete metadata.r__folder_Path;
144
- } catch (ex) {
145
- throw new Error(`Query '${metadata.key}': ${ex.message}`);
146
- }
127
+ metadata.targetKey = cache.searchForField(
128
+ 'dataExtension',
129
+ metadata.targetKey,
130
+ 'CustomerKey',
131
+ 'CustomerKey'
132
+ );
133
+ metadata.categoryId = cache.searchForField('folder', metadata.r__folder_Path, 'Path', 'ID');
134
+ delete metadata.r__folder_Path;
147
135
  metadata.targetUpdateTypeId =
148
136
  this.definition.targetUpdateTypeMapping[metadata.targetUpdateTypeName];
149
137
  return metadata;
150
138
  }
139
+ /**
140
+ * helper for buildDefinitionForNested
141
+ * searches extracted SQL file for template variables and applies the market values
142
+ *
143
+ * @param {string} code code from extracted code
144
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
145
+ * @returns {string} code with markets applied
146
+ */
147
+ static applyTemplateValues(code, templateVariables) {
148
+ // fix bad formatting applied by SQL Formatter Plus
149
+ code = code
150
+ .split(' { { { ')
151
+ .join('{{{')
152
+ .split('{ { { ')
153
+ .join('{{{')
154
+ .split(' } } } ')
155
+ .join('}}}')
156
+ .split(' } } }')
157
+ .join('}}}');
151
158
 
159
+ // replace template variables with their values
160
+ return Mustache.render(code, templateVariables);
161
+ }
152
162
  /**
153
163
  * helper for buildDefinition
154
164
  * handles extracted code if any are found for complex types
165
+ *
155
166
  * @param {string} templateDir Directory where metadata templates are stored
156
167
  * @param {string|string[]} targetDir (List of) Directory where built definitions will be saved
157
- * @param {Object} metadata main JSON file that was read from file system
158
- * @param {Object} variables variables to be replaced in the metadata
168
+ * @param {TYPE.QueryItem} metadata main JSON file that was read from file system
169
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
159
170
  * @param {string} templateName name of the template to be built
160
- * @returns {Promise} Promise
171
+ * @returns {Promise.<string[][]>} list of extracted files with path-parts provided as an array
172
+ */
173
+ static buildDefinitionForNested(
174
+ templateDir,
175
+ targetDir,
176
+ metadata,
177
+ templateVariables,
178
+ templateName
179
+ ) {
180
+ return this._buildForNested(
181
+ templateDir,
182
+ targetDir,
183
+ metadata,
184
+ templateVariables,
185
+ templateName,
186
+ 'definition'
187
+ );
188
+ }
189
+ /**
190
+ * helper for buildTemplate
191
+ * handles extracted code if any are found for complex types
192
+ *
193
+ * @example queries are saved as 1 json and 1 sql file. both files need to be run through templating
194
+ * @param {string} templateDir Directory where metadata templates are stored
195
+ * @param {string|string[]} targetDir (List of) Directory where built definitions will be saved
196
+ * @param {TYPE.QueryItem} metadata main JSON file that was read from file system
197
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
198
+ * @param {string} templateName name of the template to be built
199
+ * @returns {Promise.<string[][]>} list of extracted files with path-parts provided as an array
161
200
  */
162
- static async buildDefinitionForExtracts(
201
+ static buildTemplateForNested(
163
202
  templateDir,
164
203
  targetDir,
165
204
  metadata,
166
- variables,
205
+ templateVariables,
167
206
  templateName
207
+ ) {
208
+ return this._buildForNested(
209
+ templateDir,
210
+ targetDir,
211
+ metadata,
212
+ templateVariables,
213
+ templateName,
214
+ 'template'
215
+ );
216
+ }
217
+ /**
218
+ * helper for buildTemplateForNested / buildDefinitionForNested
219
+ * handles extracted code if any are found for complex types
220
+ *
221
+ * @private
222
+ * @param {string} templateDir Directory where metadata templates are stored
223
+ * @param {string|string[]} targetDir (List of) Directory where built definitions will be saved
224
+ * @param {TYPE.QueryItem} metadata main JSON file that was read from file system
225
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
226
+ * @param {string} templateName name of the template to be built
227
+ * @param {'definition'|'template'} mode defines what we use this helper for
228
+ * @returns {Promise.<string[][]>} list of extracted files with path-parts provided as an array
229
+ */
230
+ static async _buildForNested(
231
+ templateDir,
232
+ targetDir,
233
+ metadata,
234
+ templateVariables,
235
+ templateName,
236
+ mode
168
237
  ) {
169
238
  // get SQL from filesystem
170
- let code = await File.readFile(
239
+ let code = await File.readFilteredFilename(
171
240
  [templateDir, this.definition.type],
172
241
  templateName + '.' + this.definition.type + '-meta',
173
242
  'sql'
174
243
  );
175
- // fix bad formatting applied by SQL Formatter Plus
176
- code = code
177
- .split(' { { { ')
178
- .join('{{{')
179
- .split('{ { { ')
180
- .join('{{{')
181
- .split(' } } } ')
182
- .join('}}}')
183
- .split(' } } }')
184
- .join('}}}');
185
244
 
186
- // replace template variables with their values
187
245
  try {
188
- code = Mustache.render(code, variables);
189
- } catch (ex) {
246
+ if (mode === 'definition') {
247
+ code = this.applyTemplateValues(code, templateVariables);
248
+ } else if (mode === 'template') {
249
+ code = this.applyTemplateNames(code, templateVariables);
250
+ }
251
+ } catch {
190
252
  throw new Error(
191
253
  `${this.definition.type}:: Error applying template variables on ${
192
- metadata[this.definition.keyField] + '.' + this.definition.type
254
+ templateName + '.' + this.definition.type
193
255
  }-meta.sql.`
194
256
  );
195
257
  }
196
258
 
197
259
  // write to file
198
260
  const targetDirArr = Array.isArray(targetDir) ? targetDir : [targetDir];
261
+ const nestedFilePaths = [];
199
262
 
200
263
  for (const targetDir of targetDirArr) {
201
264
  File.writeToFile(
202
265
  [targetDir, this.definition.type],
203
- metadata[this.definition.keyField] + '.' + this.definition.type + '-meta',
266
+ templateName + '.' + this.definition.type + '-meta',
204
267
  'sql',
205
268
  code
206
269
  );
270
+ nestedFilePaths.push([
271
+ targetDir,
272
+ this.definition.type,
273
+ templateName + '.' + this.definition.type + '-meta.sql',
274
+ ]);
207
275
  }
276
+ return nestedFilePaths;
208
277
  }
209
278
 
210
279
  /**
211
280
  * parses retrieved Metadata before saving
212
- * @param {QueryItem} metadata a single query activity definition
213
- * @returns {CodeExtractItem} a single item with code parts extracted
281
+ *
282
+ * @param {TYPE.QueryItem} metadata a single query activity definition
283
+ * @returns {TYPE.CodeExtractItem} a single item with code parts extracted
214
284
  */
215
285
  static parseMetadata(metadata) {
216
286
  // folder
217
287
  try {
218
- metadata.r__folder_Path = Util.getFromCache(
219
- this.cache,
288
+ metadata.r__folder_Path = cache.searchForField(
220
289
  'folder',
221
290
  metadata.categoryId,
222
291
  'ID',
@@ -224,7 +293,7 @@ class Query extends MetadataType {
224
293
  );
225
294
  delete metadata.categoryId;
226
295
  } catch (ex) {
227
- Util.logger.warn(`Query '${metadata.key}': ${ex.message}`);
296
+ Util.logger.warn(` - Query '${metadata.key}': ${ex.message}`);
228
297
  }
229
298
 
230
299
  // extract SQL
@@ -240,11 +309,43 @@ class Query extends MetadataType {
240
309
 
241
310
  return { json: metadata, codeArr: codeArr, subFolder: null };
242
311
  }
312
+ /**
313
+ * should return only the json for all but asset, query and script that are saved as multiple files
314
+ * additionally, the documentation for dataExtension and automation should be returned
315
+ *
316
+ * @param {string[]} keyArr customerkey of the metadata
317
+ * @returns {string[]} list of all files that need to be committed in a flat array ['path/file1.ext', 'path/file2.ext']
318
+ */
319
+ static getFilesToCommit(keyArr) {
320
+ const path = File.normalizePath([
321
+ this.properties.directories.retrieve,
322
+ this.buObject.credential,
323
+ this.buObject.businessUnit,
324
+ this.definition.type,
325
+ ]);
326
+
327
+ const fileList = keyArr.flatMap((key) => [
328
+ File.normalizePath([path, `${key}.${this.definition.type}-meta.json`]),
329
+ File.normalizePath([path, `${key}.${this.definition.type}-meta.sql`]),
330
+ ]);
331
+ return fileList;
332
+ }
333
+
334
+ /**
335
+ * Standardizes a check for multiple messages but adds query specific filters to error texts
336
+ *
337
+ * @param {object} ex response payload from REST API
338
+ * @returns {string[]} formatted Error Message
339
+ */
340
+ static checkForErrors(ex) {
341
+ const errors = super.checkForErrors(ex);
342
+ if (errors?.length > 0) {
343
+ return errors.map((msg) => msg.split('Error saving the Query field.').join(''));
344
+ }
345
+ }
243
346
  }
244
347
 
245
348
  // Assign definition & cache to static attributes
246
349
  Query.definition = require('../MetadataTypeDefinitions').query;
247
- Query.cache = {};
248
- Query.client = undefined;
249
350
 
250
351
  module.exports = Query;