@rockcarver/frodo-lib 0.11.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.
Files changed (112) hide show
  1. package/.eslintrc +32 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +30 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. package/.github/README.md +121 -0
  5. package/.github/workflows/pipeline.yml +287 -0
  6. package/.prettierrc +6 -0
  7. package/CHANGELOG.md +512 -0
  8. package/CODE_OF_CONDUCT.md +128 -0
  9. package/LICENSE +21 -0
  10. package/README.md +8 -0
  11. package/docs/CONTRIBUTE.md +96 -0
  12. package/docs/PIPELINE.md +169 -0
  13. package/docs/images/npm_versioning_guidelines.png +0 -0
  14. package/docs/images/release_pipeline.png +0 -0
  15. package/jsconfig.json +6 -0
  16. package/package.json +95 -0
  17. package/resources/sampleEntitiesFile.json +8 -0
  18. package/resources/sampleEnvFile.env +2 -0
  19. package/src/api/AuthenticateApi.js +33 -0
  20. package/src/api/BaseApi.js +242 -0
  21. package/src/api/CirclesOfTrustApi.js +87 -0
  22. package/src/api/EmailTemplateApi.js +37 -0
  23. package/src/api/IdmConfigApi.js +88 -0
  24. package/src/api/LogApi.js +45 -0
  25. package/src/api/ManagedObjectApi.js +62 -0
  26. package/src/api/OAuth2ClientApi.js +69 -0
  27. package/src/api/OAuth2OIDCApi.js +73 -0
  28. package/src/api/OAuth2ProviderApi.js +32 -0
  29. package/src/api/RealmApi.js +99 -0
  30. package/src/api/Saml2Api.js +176 -0
  31. package/src/api/ScriptApi.js +84 -0
  32. package/src/api/SecretsApi.js +151 -0
  33. package/src/api/ServerInfoApi.js +41 -0
  34. package/src/api/SocialIdentityProvidersApi.js +114 -0
  35. package/src/api/StartupApi.js +45 -0
  36. package/src/api/ThemeApi.js +181 -0
  37. package/src/api/TreeApi.js +207 -0
  38. package/src/api/VariablesApi.js +104 -0
  39. package/src/api/utils/ApiUtils.js +77 -0
  40. package/src/api/utils/ApiUtils.test.js +96 -0
  41. package/src/api/utils/Base64.js +62 -0
  42. package/src/index.js +32 -0
  43. package/src/index.test.js +13 -0
  44. package/src/ops/AdminOps.js +901 -0
  45. package/src/ops/AuthenticateOps.js +342 -0
  46. package/src/ops/CirclesOfTrustOps.js +350 -0
  47. package/src/ops/ConnectionProfileOps.js +254 -0
  48. package/src/ops/EmailTemplateOps.js +326 -0
  49. package/src/ops/IdmOps.js +227 -0
  50. package/src/ops/IdpOps.js +342 -0
  51. package/src/ops/JourneyOps.js +2026 -0
  52. package/src/ops/LogOps.js +357 -0
  53. package/src/ops/ManagedObjectOps.js +34 -0
  54. package/src/ops/OAuth2ClientOps.js +151 -0
  55. package/src/ops/OrganizationOps.js +85 -0
  56. package/src/ops/RealmOps.js +139 -0
  57. package/src/ops/SamlOps.js +541 -0
  58. package/src/ops/ScriptOps.js +211 -0
  59. package/src/ops/SecretsOps.js +288 -0
  60. package/src/ops/StartupOps.js +114 -0
  61. package/src/ops/ThemeOps.js +379 -0
  62. package/src/ops/VariablesOps.js +185 -0
  63. package/src/ops/templates/OAuth2ClientTemplate.json +270 -0
  64. package/src/ops/templates/OrgModelUserAttributesTemplate.json +149 -0
  65. package/src/ops/templates/cloud/GenericExtensionAttributesTemplate.json +392 -0
  66. package/src/ops/templates/cloud/managed.json +4119 -0
  67. package/src/ops/utils/Console.js +434 -0
  68. package/src/ops/utils/DataProtection.js +92 -0
  69. package/src/ops/utils/DataProtection.test.js +28 -0
  70. package/src/ops/utils/ExportImportUtils.js +146 -0
  71. package/src/ops/utils/ExportImportUtils.test.js +119 -0
  72. package/src/ops/utils/OpsUtils.js +76 -0
  73. package/src/ops/utils/Wordwrap.js +11 -0
  74. package/src/storage/SessionStorage.js +45 -0
  75. package/src/storage/StaticStorage.js +15 -0
  76. package/test/e2e/journey/baseline/ForgottenUsername.journey.json +216 -0
  77. package/test/e2e/journey/baseline/Login.journey.json +205 -0
  78. package/test/e2e/journey/baseline/PasswordGrant.journey.json +139 -0
  79. package/test/e2e/journey/baseline/ProgressiveProfile.journey.json +198 -0
  80. package/test/e2e/journey/baseline/Registration.journey.json +249 -0
  81. package/test/e2e/journey/baseline/ResetPassword.journey.json +268 -0
  82. package/test/e2e/journey/baseline/UpdatePassword.journey.json +323 -0
  83. package/test/e2e/journey/baseline/allAlphaJourneys.journeys.json +1520 -0
  84. package/test/e2e/journey/delete/ForgottenUsername.journey.json +216 -0
  85. package/test/e2e/journey/delete/Login.journey.json +205 -0
  86. package/test/e2e/journey/delete/PasswordGrant.journey.json +139 -0
  87. package/test/e2e/journey/delete/ProgressiveProfile.journey.json +198 -0
  88. package/test/e2e/journey/delete/Registration.journey.json +249 -0
  89. package/test/e2e/journey/delete/ResetPassword.journey.json +268 -0
  90. package/test/e2e/journey/delete/UpdatePassword.journey.json +323 -0
  91. package/test/e2e/journey/delete/deleteMe.journey.json +230 -0
  92. package/test/e2e/journey/list/Disabled.journey.json +43 -0
  93. package/test/e2e/journey/list/ForgottenUsername.journey.json +216 -0
  94. package/test/e2e/journey/list/Login.journey.json +205 -0
  95. package/test/e2e/journey/list/PasswordGrant.journey.json +139 -0
  96. package/test/e2e/journey/list/ProgressiveProfile.journey.json +198 -0
  97. package/test/e2e/journey/list/Registration.journey.json +249 -0
  98. package/test/e2e/journey/list/ResetPassword.journey.json +268 -0
  99. package/test/e2e/journey/list/UpdatePassword.journey.json +323 -0
  100. package/test/e2e/setup.js +107 -0
  101. package/test/e2e/theme/baseline/Contrast.theme.json +95 -0
  102. package/test/e2e/theme/baseline/Highlander.theme.json +95 -0
  103. package/test/e2e/theme/baseline/Robroy.theme.json +95 -0
  104. package/test/e2e/theme/baseline/Starter-Theme.theme.json +94 -0
  105. package/test/e2e/theme/baseline/Zardoz.theme.json +95 -0
  106. package/test/e2e/theme/import/Contrast.theme.json +95 -0
  107. package/test/e2e/theme/import/Highlander.theme.json +95 -0
  108. package/test/e2e/theme/import/Robroy.theme.json +95 -0
  109. package/test/e2e/theme/import/Starter-Theme.theme.json +94 -0
  110. package/test/e2e/theme/import/Zardoz.default.theme.json +95 -0
  111. package/test/fs_tmp/.gitkeep +2 -0
  112. package/test/global/setup.js +65 -0
@@ -0,0 +1,254 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import storage from '../storage/SessionStorage.js';
4
+ import DataProtection from './utils/DataProtection.js';
5
+ import {
6
+ createObjectTable,
7
+ createTable,
8
+ printMessage,
9
+ } from './utils/Console.js';
10
+
11
+ const dataProtection = new DataProtection();
12
+
13
+ const fileOptions = {
14
+ options: 'utf8',
15
+ indentation: 4,
16
+ };
17
+
18
+ const getConnectionProfilesFolder = () => `${os.homedir()}/.frodo`;
19
+
20
+ /**
21
+ * Get connection profiles file name
22
+ * @returns {String} connection profiles file name
23
+ */
24
+ export function getConnectionProfilesFileName() {
25
+ return `${os.homedir()}/.frodo/.frodorc`;
26
+ }
27
+
28
+ /**
29
+ * Find connection profile
30
+ * @param {Object} connectionProfiles connection profile object
31
+ * @param {String} host tenant host url or unique substring
32
+ * @returns {Object} connection profile object or null
33
+ */
34
+ function findConnectionProfile(connectionProfiles, host) {
35
+ for (const tenant in connectionProfiles) {
36
+ if (tenant.includes(host)) {
37
+ const profile = connectionProfiles[tenant];
38
+ profile.tenant = tenant;
39
+ return profile;
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+
45
+ /**
46
+ * List connection profiles
47
+ * @param {boolean} long Long list format with details
48
+ */
49
+ export function listConnectionProfiles(long = false) {
50
+ const filename = getConnectionProfilesFileName();
51
+ try {
52
+ const data = fs.readFileSync(filename, 'utf8');
53
+ const connectionsData = JSON.parse(data);
54
+ if (long) {
55
+ const table = createTable(['Host', 'Username', 'Log API Key']);
56
+ Object.keys(connectionsData).forEach((c) => {
57
+ table.push([
58
+ c,
59
+ connectionsData[c].username,
60
+ connectionsData[c].logApiKey,
61
+ ]);
62
+ });
63
+ printMessage(table.toString(), 'data');
64
+ } else {
65
+ Object.keys(connectionsData).forEach((c) => {
66
+ printMessage(`${c}`, 'data');
67
+ });
68
+ }
69
+ printMessage(
70
+ 'Any unique substring of a saved host can be used as the value for host parameter in all commands',
71
+ 'info'
72
+ );
73
+ } catch (e) {
74
+ printMessage(`No connections found in ${filename} (${e.message})`, 'error');
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Initialize connection profiles
80
+ */
81
+ export function initConnectionProfiles() {
82
+ // create connections.json file if it doesn't exist
83
+ const folderName = getConnectionProfilesFolder();
84
+ const filename = getConnectionProfilesFileName();
85
+ if (!fs.existsSync(folderName)) {
86
+ fs.mkdirSync(folderName, { recursive: true });
87
+ if (!fs.existsSync(filename)) {
88
+ fs.writeFileSync(
89
+ filename,
90
+ JSON.stringify({}, null, fileOptions.indentation)
91
+ );
92
+ }
93
+ }
94
+ // encrypt the password from clear text to aes-256-GCM
95
+ else {
96
+ const data = fs.readFileSync(filename, fileOptions.options);
97
+ const connectionsData = JSON.parse(data);
98
+ let convert = false;
99
+ Object.keys(connectionsData).forEach(async (conn) => {
100
+ if (connectionsData[conn].password) {
101
+ convert = true;
102
+ connectionsData[conn].encodedPassword = await dataProtection.encrypt(
103
+ connectionsData[conn].password
104
+ ); // Buffer.from(connectionsData[conn].password).toString('base64');
105
+ delete connectionsData[conn].password;
106
+ }
107
+ });
108
+ if (convert) {
109
+ fs.writeFileSync(
110
+ filename,
111
+ JSON.stringify(connectionsData, null, fileOptions.indentation)
112
+ );
113
+ }
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Get connection profile by host
119
+ * @param {String} host host tenant host url or unique substring
120
+ * @returns {Object} connection profile or null
121
+ */
122
+ export async function getConnectionProfileByHost(host) {
123
+ try {
124
+ const filename = getConnectionProfilesFileName();
125
+ const connectionsData = JSON.parse(
126
+ fs.readFileSync(filename, fileOptions.options)
127
+ );
128
+ const profile = findConnectionProfile(connectionsData, host);
129
+ if (!profile) {
130
+ printMessage(
131
+ `Profile for ${host} not found. Please specify credentials on command line`,
132
+ 'error'
133
+ );
134
+ return null;
135
+ }
136
+ return {
137
+ tenant: profile.tenant,
138
+ username: profile.username ? profile.username : null,
139
+ password: profile.encodedPassword
140
+ ? await dataProtection.decrypt(profile.encodedPassword)
141
+ : null,
142
+ key: profile.logApiKey ? profile.logApiKey : null,
143
+ secret: profile.logApiSecret ? profile.logApiSecret : null,
144
+ };
145
+ } catch (e) {
146
+ printMessage(
147
+ `Can not read saved connection info, please specify credentials on command line: ${e}`,
148
+ 'error'
149
+ );
150
+ return null;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Get connection profile
156
+ * @returns {Object} connection profile or null
157
+ */
158
+ export async function getConnectionProfile() {
159
+ return getConnectionProfileByHost(storage.session.getTenant());
160
+ }
161
+
162
+ /**
163
+ * Save connection profile
164
+ */
165
+ export async function saveConnectionProfile() {
166
+ const filename = getConnectionProfilesFileName();
167
+ printMessage(`Saving creds in ${filename}...`);
168
+ let connectionsData = {};
169
+ let existingData = {};
170
+ try {
171
+ fs.statSync(filename);
172
+ const data = fs.readFileSync(filename, 'utf8');
173
+ connectionsData = JSON.parse(data);
174
+ if (connectionsData[storage.session.getTenant()]) {
175
+ existingData = connectionsData[storage.session.getTenant()];
176
+ printMessage(
177
+ `Updating existing connection profile ${storage.session.getTenant()}`
178
+ );
179
+ } else
180
+ printMessage(`Adding connection profile ${storage.session.getTenant()}`);
181
+ } catch (e) {
182
+ printMessage(
183
+ `Creating connection profile file ${filename} with ${storage.session.getTenant()}`
184
+ );
185
+ }
186
+ if (storage.session.getUsername())
187
+ existingData.username = storage.session.getUsername();
188
+ if (storage.session.getPassword())
189
+ existingData.encodedPassword = await dataProtection.encrypt(
190
+ storage.session.getPassword()
191
+ ); // Buffer.from(storage.session.getPassword()).toString('base64');
192
+ if (storage.session.getLogApiKey())
193
+ existingData.logApiKey = storage.session.getLogApiKey();
194
+ if (storage.session.getLogApiSecret())
195
+ existingData.logApiSecret = storage.session.getLogApiSecret();
196
+ connectionsData[storage.session.getTenant()] = existingData;
197
+
198
+ fs.writeFileSync(filename, JSON.stringify(connectionsData, null, 2));
199
+ }
200
+
201
+ /**
202
+ * Delete connection profile
203
+ * @param {String} host host tenant host url or unique substring
204
+ */
205
+ export function deleteConnectionProfile(host) {
206
+ const filename = getConnectionProfilesFileName();
207
+ let connectionsData = {};
208
+ fs.stat(filename, (err) => {
209
+ if (err == null) {
210
+ const data = fs.readFileSync(filename, 'utf8');
211
+ connectionsData = JSON.parse(data);
212
+ const profile = findConnectionProfile(connectionsData, host);
213
+ if (profile) {
214
+ printMessage(`Deleting connection profile ${profile.tenant}`);
215
+ delete connectionsData[profile.tenant];
216
+ fs.writeFileSync(filename, JSON.stringify(connectionsData, null, 2));
217
+ } else {
218
+ printMessage(`No connection profile ${host} found`);
219
+ }
220
+ } else if (err.code === 'ENOENT') {
221
+ printMessage(`Connection profile file ${filename} not found`);
222
+ } else {
223
+ printMessage(
224
+ `Error in deleting connection profile: ${err.code}`,
225
+ 'error'
226
+ );
227
+ }
228
+ });
229
+ }
230
+
231
+ export async function describeConnectionProfile(host, showSecrets) {
232
+ const profile = await getConnectionProfileByHost(host);
233
+ if (profile) {
234
+ if (!showSecrets) {
235
+ delete profile.password;
236
+ delete profile.secret;
237
+ }
238
+ if (!profile.key) {
239
+ delete profile.key;
240
+ delete profile.secret;
241
+ }
242
+ const keyMap = {
243
+ tenant: 'Host',
244
+ username: 'Username',
245
+ password: 'Password',
246
+ key: 'Log API Key',
247
+ secret: 'Log API Secret',
248
+ };
249
+ const table = createObjectTable(profile, keyMap);
250
+ printMessage(table.toString(), 'data');
251
+ } else {
252
+ printMessage(`No connection profile ${host} found`);
253
+ }
254
+ }
@@ -0,0 +1,326 @@
1
+ import fs from 'fs';
2
+ import {
3
+ EMAIL_TEMPLATE_TYPE,
4
+ getEmailTemplate,
5
+ getEmailTemplates,
6
+ putEmailTemplate,
7
+ } from '../api/EmailTemplateApi.js';
8
+ import {
9
+ createProgressBar,
10
+ createTable,
11
+ printMessage,
12
+ stopProgressBar,
13
+ updateProgressBar,
14
+ } from './utils/Console.js';
15
+ import {
16
+ getTypedFilename,
17
+ saveJsonToFile,
18
+ validateImport,
19
+ } from './utils/ExportImportUtils.js';
20
+ import wordwrap from './utils/Wordwrap.js';
21
+
22
+ /**
23
+ * Maintain the file type centrally
24
+ */
25
+ const EMAIL_TEMPLATE_FILE_TYPE = 'template.email';
26
+
27
+ // use a function vs a template variable to avoid problems in loops
28
+ function getFileDataTemplate() {
29
+ return {
30
+ meta: {},
31
+ emailTemplate: {},
32
+ };
33
+ }
34
+
35
+ /**
36
+ * List email templates
37
+ * @param {boolean} long Long list format with details
38
+ */
39
+ export async function listEmailTemplates(long = false) {
40
+ let emailTemplates = [];
41
+ try {
42
+ emailTemplates = (await getEmailTemplates()).data.result;
43
+ } catch (error) {
44
+ printMessage(`${error.message}`, 'error');
45
+ printMessage(error.response.data, 'error');
46
+ }
47
+ emailTemplates.sort((a, b) => a._id.localeCompare(b._id));
48
+ if (!long) {
49
+ for (const [, emailTemplate] of emailTemplates.entries()) {
50
+ printMessage(
51
+ `${emailTemplate._id.replace(`${EMAIL_TEMPLATE_TYPE}/`, '')}`,
52
+ 'data'
53
+ );
54
+ }
55
+ } else {
56
+ const table = createTable([
57
+ 'Id'.brightCyan,
58
+ 'Name'.brightCyan,
59
+ 'Status'.brightCyan,
60
+ 'Locale(s)'.brightCyan,
61
+ 'From'.brightCyan,
62
+ 'Subject'.brightCyan,
63
+ ]);
64
+ emailTemplates.forEach((emailTemplate) => {
65
+ table.push([
66
+ // Id
67
+ `${emailTemplate._id.replace(`${EMAIL_TEMPLATE_TYPE}/`, '')}`,
68
+ // Name
69
+ `${emailTemplate.displayName ? emailTemplate.displayName : ''}`,
70
+ // Status
71
+ emailTemplate.enabled === false
72
+ ? 'disabled'.brightRed
73
+ : 'enabled'.brightGreen,
74
+ // Locale(s)
75
+ `${emailTemplate.defaultLocale}${
76
+ Object.keys(emailTemplate.subject).length > 1
77
+ ? ` (${Object.keys(emailTemplate.subject)
78
+ .filter((locale) => locale !== emailTemplate.defaultLocale)
79
+ .join(',')})`
80
+ : ''
81
+ }`,
82
+ // From
83
+ `${emailTemplate.from ? emailTemplate.from : ''}`,
84
+ // Subject
85
+ wordwrap(emailTemplate.subject[emailTemplate.defaultLocale], 40),
86
+ ]);
87
+ });
88
+ printMessage(table.toString(), 'data');
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Export a single email template to file
94
+ * @param {String} templateId email template id
95
+ * @param {String} file optional filename
96
+ */
97
+ export async function exportEmailTemplateToFile(templateId, file) {
98
+ let fileName = file;
99
+ if (!fileName) {
100
+ fileName = getTypedFilename(templateId, EMAIL_TEMPLATE_FILE_TYPE);
101
+ }
102
+ createProgressBar(1, `Exporting ${templateId}`);
103
+ getEmailTemplate(templateId)
104
+ .then(async (response) => {
105
+ const templateData = response.data;
106
+ updateProgressBar(`Writing file ${fileName}`);
107
+ const fileData = getFileDataTemplate();
108
+ fileData.emailTemplate[templateId] = templateData;
109
+ saveJsonToFile(fileData, fileName);
110
+ stopProgressBar(
111
+ `Exported ${templateId.brightCyan} to ${fileName.brightCyan}.`
112
+ );
113
+ })
114
+ .catch((err) => {
115
+ stopProgressBar(`${err}`);
116
+ printMessage(err, 'error');
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Export all email templates to file
122
+ * @param {String} file optional filename
123
+ */
124
+ export async function exportEmailTemplatesToFile(file) {
125
+ let fileName = file;
126
+ if (!fileName) {
127
+ fileName = getTypedFilename(`allEmailTemplates`, EMAIL_TEMPLATE_FILE_TYPE);
128
+ }
129
+ const fileData = getFileDataTemplate();
130
+ getEmailTemplates()
131
+ .then((response) => {
132
+ const templates = response.data.result;
133
+ createProgressBar(response.data.resultCount, 'Exporting email templates');
134
+ for (const template of templates) {
135
+ const templateId = template._id.replace(`${EMAIL_TEMPLATE_TYPE}/`, '');
136
+ updateProgressBar(`Exporting ${templateId}`);
137
+ fileData.emailTemplate[templateId] = template;
138
+ }
139
+ saveJsonToFile(fileData, fileName);
140
+ stopProgressBar(
141
+ `${response.data.resultCount} templates exported to ${fileName}.`
142
+ );
143
+ })
144
+ .catch((err) => {
145
+ stopProgressBar(`${err}`);
146
+ printMessage(err, 'error');
147
+ });
148
+ }
149
+
150
+ /**
151
+ * Export all email templates to separate files
152
+ */
153
+ export async function exportEmailTemplatesToFiles() {
154
+ getEmailTemplates()
155
+ .then((response) => {
156
+ const templates = response.data.result;
157
+ createProgressBar(response.data.resultCount, 'Exporting email templates');
158
+ for (const template of templates) {
159
+ const templateId = template._id.replace(`${EMAIL_TEMPLATE_TYPE}/`, '');
160
+ const fileName = getTypedFilename(templateId, EMAIL_TEMPLATE_FILE_TYPE);
161
+ const fileData = getFileDataTemplate();
162
+ updateProgressBar(`Exporting ${templateId}`);
163
+ fileData.emailTemplate[templateId] = template;
164
+ saveJsonToFile(fileData, fileName);
165
+ }
166
+ stopProgressBar(`${response.data.resultCount} templates exported.`);
167
+ })
168
+ .catch((err) => {
169
+ stopProgressBar(`${err}`);
170
+ printMessage(err, 'error');
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Import email template by id from file
176
+ * @param {String} templateId email template id
177
+ * @param {String} file optional filename
178
+ */
179
+ export async function importEmailTemplateFromFile(templateId, file) {
180
+ // eslint-disable-next-line no-param-reassign
181
+ templateId = templateId.replaceAll(`${EMAIL_TEMPLATE_TYPE}/`, '');
182
+ fs.readFile(file, 'utf8', (err, data) => {
183
+ if (err) throw err;
184
+ const fileData = JSON.parse(data);
185
+ if (validateImport(fileData.meta)) {
186
+ createProgressBar(1, `Importing ${templateId}`);
187
+ if (fileData.emailTemplate[templateId]) {
188
+ putEmailTemplate(templateId, fileData.emailTemplate[templateId])
189
+ .then(() => {
190
+ updateProgressBar(`Importing ${templateId}`);
191
+ stopProgressBar(`Imported ${templateId}`);
192
+ })
193
+ .catch((putEmailTemplateError) => {
194
+ stopProgressBar(`${putEmailTemplateError}`);
195
+ printMessage(putEmailTemplateError, 'error');
196
+ });
197
+ } else {
198
+ stopProgressBar(
199
+ `Email template ${templateId.brightCyan} not found in ${file.brightCyan}!`
200
+ );
201
+ printMessage(
202
+ `Email template ${templateId.brightCyan} not found in ${file.brightCyan}!`,
203
+ 'error'
204
+ );
205
+ }
206
+ } else {
207
+ printMessage('Import validation failed...', 'error');
208
+ }
209
+ });
210
+ }
211
+
212
+ /**
213
+ * Import all email templates from file
214
+ * @param {String} file optional filename
215
+ */
216
+ export async function importEmailTemplatesFromFile(file) {
217
+ fs.readFile(file, 'utf8', async (err, data) => {
218
+ if (err) throw err;
219
+ const fileData = JSON.parse(data);
220
+ if (validateImport(fileData.meta)) {
221
+ createProgressBar(
222
+ Object.keys(fileData.emailTemplate).length,
223
+ `Importing email templates`
224
+ );
225
+ for (const id in fileData.emailTemplate) {
226
+ if ({}.hasOwnProperty.call(fileData.emailTemplate, id)) {
227
+ const templateId = id.replaceAll(`${EMAIL_TEMPLATE_TYPE}/`, '');
228
+ try {
229
+ // eslint-disable-next-line no-await-in-loop
230
+ await putEmailTemplate(
231
+ templateId,
232
+ fileData.emailTemplate[templateId]
233
+ );
234
+ updateProgressBar(`Imported ${templateId}`);
235
+ } catch (putEmailTemplateError) {
236
+ printMessage(`\nError importing ${templateId}`, 'error');
237
+ printMessage(putEmailTemplateError.response.data, 'error');
238
+ }
239
+ }
240
+ }
241
+ stopProgressBar(`Done.`);
242
+ } else {
243
+ printMessage('Import validation failed...', 'error');
244
+ }
245
+ });
246
+ }
247
+
248
+ /**
249
+ * Import all email templates from separate files
250
+ */
251
+ export async function importEmailTemplatesFromFiles() {
252
+ const names = fs.readdirSync('.');
253
+ const jsonFiles = names.filter((name) =>
254
+ name.toLowerCase().endsWith(`${EMAIL_TEMPLATE_FILE_TYPE}.json`)
255
+ );
256
+ createProgressBar(jsonFiles.length, 'Importing email templates...');
257
+ let total = 0;
258
+ let totalErrors = 0;
259
+ for (const file of jsonFiles) {
260
+ const data = fs.readFileSync(file, 'utf8');
261
+ const fileData = JSON.parse(data);
262
+ if (validateImport(fileData.meta)) {
263
+ total += Object.keys(fileData.emailTemplate).length;
264
+ let errors = 0;
265
+ for (const id in fileData.emailTemplate) {
266
+ if ({}.hasOwnProperty.call(fileData.emailTemplate, id)) {
267
+ const templateId = id.replaceAll(`${EMAIL_TEMPLATE_TYPE}/`, '');
268
+ try {
269
+ // eslint-disable-next-line no-await-in-loop
270
+ await putEmailTemplate(
271
+ templateId,
272
+ fileData.emailTemplate[templateId]
273
+ );
274
+ } catch (putEmailTemplateError) {
275
+ errors += 1;
276
+ printMessage(`\nError importing ${templateId}`, 'error');
277
+ printMessage(putEmailTemplateError.response.data, 'error');
278
+ }
279
+ }
280
+ }
281
+ totalErrors += errors;
282
+ updateProgressBar(`Imported ${file}`);
283
+ } else {
284
+ printMessage(`Validation of ${file} failed!`, 'error');
285
+ }
286
+ }
287
+ stopProgressBar(
288
+ `Imported ${total - totalErrors} of ${total} email template(s) from ${
289
+ jsonFiles.length
290
+ } file(s).`
291
+ );
292
+ }
293
+
294
+ /**
295
+ * Import first email template from file
296
+ * @param {String} file optional filename
297
+ */
298
+ export async function importFirstEmailTemplateFromFile(file) {
299
+ fs.readFile(file, 'utf8', (err, data) => {
300
+ if (err) throw err;
301
+ const fileData = JSON.parse(data);
302
+ if (validateImport(fileData.meta)) {
303
+ createProgressBar(1, `Importing first email template`);
304
+ for (const id in fileData.emailTemplate) {
305
+ if ({}.hasOwnProperty.call(fileData.emailTemplate, id)) {
306
+ putEmailTemplate(
307
+ id.replaceAll('emailTemplate/', ''),
308
+ fileData.emailTemplate[id]
309
+ )
310
+ .then(() => {
311
+ updateProgressBar(`Imported ${id}`);
312
+ stopProgressBar(`Imported ${id}`);
313
+ })
314
+ .catch((putEmailTemplateError) => {
315
+ stopProgressBar(`Error importing email template ${id}`);
316
+ printMessage(`\nError importing email template ${id}`, 'error');
317
+ printMessage(putEmailTemplateError.response.data, 'error');
318
+ });
319
+ break;
320
+ }
321
+ }
322
+ } else {
323
+ printMessage('Import validation failed...', 'error');
324
+ }
325
+ });
326
+ }