cht-conf 3.10.0 → 3.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.
@@ -1,9 +1,16 @@
1
1
  const environment = require('../lib/environment');
2
- const uploadForms = require('../lib/upload-forms');
2
+ const uploadForms = require('../lib/upload-forms').execute;
3
+ const { APP_FORMS_PATH } = require('../lib/project-paths');
4
+
5
+ const uploadAppForms = (forms) => {
6
+ return uploadForms(environment.pathToProject, 'app', {
7
+ forms: forms,
8
+ });
9
+ };
3
10
 
4
11
  module.exports = {
5
12
  requiresInstance: true,
6
- execute: () => uploadForms(environment.pathToProject, 'app', {
7
- forms: environment.extraArgs,
8
- })
13
+ uploadAppForms,
14
+ APP_FORMS_PATH: APP_FORMS_PATH,
15
+ execute: () => uploadAppForms(environment.extraArgs)
9
16
  };
@@ -2,29 +2,35 @@ const api = require('../lib/api');
2
2
  const environment = require('../lib/environment');
3
3
  const fs = require('../lib/sync-fs');
4
4
  const { info } = require('../lib/log');
5
+ const { APP_SETTINGS_DIR_PATH, APP_SETTINGS_JSON_PATH } = require('../lib/project-paths');
5
6
 
6
- module.exports = {
7
- requiresInstance: true,
8
- execute: () => {
9
- const settings = fs.read(`${environment.pathToProject}/app_settings.json`);
10
- return api().updateAppSettings(settings)
11
- .then(JSON.parse)
12
- .then(json => {
13
- // As per https://github.com/medic/cht-core/issues/3674, this endpoint
14
- // will return 200 even when upload fails.
15
- if (!json.success) {
16
- throw new Error(json.error);
17
- }
7
+ const uploadAppSettings = (api) => {
8
+ const settings = fs.read(`${environment.pathToProject}/${APP_SETTINGS_JSON_PATH}`);
9
+ return api.updateAppSettings(settings)
10
+ .then(JSON.parse)
11
+ .then(json => {
12
+ // As per https://github.com/medic/cht-core/issues/3674, this endpoint
13
+ // will return 200 even when upload fails.
14
+ if (!json.success) {
15
+ throw new Error(json.error);
16
+ }
18
17
 
19
- if ('updated' in json) {
20
- // the `updated` param was added in 3.9
21
- // https://github.com/medic/cht-core/issues/6315
22
- if (!json.updated) {
23
- info('Settings not updated - no changes detected');
24
- } else {
25
- info('Settings updated successfully');
26
- }
18
+ if ('updated' in json) {
19
+ // the `updated` param was added in 3.9
20
+ // https://github.com/medic/cht-core/issues/6315
21
+ if (!json.updated) {
22
+ info('Settings not updated - no changes detected');
23
+ } else {
24
+ info('Settings updated successfully');
27
25
  }
28
- });
29
- }
26
+ }
27
+ });
28
+ };
29
+
30
+ module.exports = {
31
+ uploadAppSettings,
32
+ APP_SETTINGS_DIR_PATH,
33
+ APP_SETTINGS_JSON_PATH,
34
+ requiresInstance: true,
35
+ execute: () => uploadAppSettings(api())
30
36
  };
@@ -1,10 +1,15 @@
1
1
  const environment = require('../lib/environment');
2
- const uploadForms = require('../lib/upload-forms');
2
+ const uploadForms = require('../lib/upload-forms').execute;
3
+
4
+ const uploadCollectForms = (forms) => {
5
+ return uploadForms(environment.pathToProject, 'collect', {
6
+ forms: forms,
7
+ default_context: { collect: true },
8
+ });
9
+ };
3
10
 
4
11
  module.exports = {
5
12
  requiresInstance: true,
6
- execute: () => uploadForms(environment.pathToProject, 'collect', {
7
- forms: environment.extraArgs,
8
- default_context: { collect:true },
9
- })
13
+ uploadCollectForms,
14
+ execute: () => uploadCollectForms(environment.extraArgs)
10
15
  };
@@ -1,11 +1,18 @@
1
1
  const environment = require('../lib/environment');
2
- const uploadForms = require('../lib/upload-forms');
2
+ const uploadForms = require('../lib/upload-forms').execute;
3
+ const CONTACT_FORMS_PATH = require('../lib/project-paths');
4
+
5
+ const uploadContactForms = (forms) => {
6
+ return uploadForms(environment.pathToProject, 'contact', {
7
+ id_prefix: 'contact:',
8
+ forms: forms,
9
+ default_context: { person: false, place: false },
10
+ });
11
+ };
3
12
 
4
13
  module.exports = {
5
14
  requiresInstance: true,
6
- execute: () => uploadForms(environment.pathToProject, 'contact', {
7
- id_prefix: 'contact:',
8
- forms: environment.extraArgs,
9
- default_context: { person:false, place:false },
10
- })
15
+ uploadContactForms,
16
+ CONTACT_FORMS_PATH,
17
+ execute: () => uploadContactForms(environment.extraArgs)
11
18
  };
@@ -13,10 +13,12 @@ const {
13
13
  TranslationException
14
14
  } = require('@medic/translation-checker');
15
15
 
16
+ const { TRANSLATIONS_DIR_PATH } = require('../lib/project-paths');
17
+
16
18
  const execute = async () => {
17
19
  const db = pouch(environment.apiUrl);
18
20
 
19
- const dir = `${environment.pathToProject}/translations`;
21
+ const dir = `${environment.pathToProject}/${TRANSLATIONS_DIR_PATH}`;
20
22
 
21
23
  let fileNames;
22
24
  let formatErrorsFound = 0;
@@ -65,8 +67,7 @@ const execute = async () => {
65
67
  if (placeholderErrorsFound > 0) {
66
68
  errMsg += '\nYou can use messages-ex.properties to add placeholders missing from the reference context.';
67
69
  }
68
- log.error(errMsg);
69
- return process.exit(1);
70
+ throw new Error(errMsg);
70
71
  }
71
72
  } else {
72
73
  throw err;
@@ -77,8 +78,7 @@ const execute = async () => {
77
78
  const id = idFor(fileName);
78
79
  const languageCode = id.substring('messages-'.length);
79
80
  if (!isLanguageCodeValid(languageCode)) {
80
- log.error(`The language code '${languageCode}' is not valid. It must begin with a letter(a–z, A-Z), followed by any number of hyphens, underscores, letters, or numbers.`);
81
- return process.exit(1);
81
+ throw new Error(`The language code '${languageCode}' is not valid. It must begin with a letter(a-z, A-Z), followed by any number of hyphens, underscores, letters, or numbers.`);
82
82
  }
83
83
 
84
84
  let languageName = iso639.getName(languageCode);
@@ -87,7 +87,7 @@ const execute = async () => {
87
87
  languageName = 'TODO: please ask admin to set this in settings UI';
88
88
  } else {
89
89
  let languageNativeName = iso639.getNativeName(languageCode);
90
- if (languageNativeName !== languageName){
90
+ if (languageNativeName !== languageName) {
91
91
  languageName = `${languageNativeName} (${languageName})`;
92
92
  }
93
93
  }
@@ -97,7 +97,7 @@ const execute = async () => {
97
97
  let doc;
98
98
  try {
99
99
  doc = await db.get(id);
100
- } catch(e) {
100
+ } catch (e) {
101
101
  if (e.status === 404) {
102
102
  doc = await newDocFor(fileName, db, languageName, languageCode);
103
103
  }
@@ -129,7 +129,7 @@ function parse(filePath, options) {
129
129
  }
130
130
 
131
131
  function overwriteProperties(doc, props) {
132
- if(doc.generic) {
132
+ if (doc.generic) {
133
133
  // 3.4.0 translation structure
134
134
  doc.custom = props;
135
135
  } else if (doc.values) {
@@ -181,5 +181,6 @@ async function genericTranslationsStructure(db) {
181
181
 
182
182
  module.exports = {
183
183
  requiresInstance: true,
184
+ TRANSLATIONS_DIR_PATH,
184
185
  execute
185
186
  };
@@ -8,7 +8,7 @@ const log = require('../lib/log');
8
8
  const pouch = require('../lib/db');
9
9
  const progressBar = require('../lib/progress-bar');
10
10
 
11
- const { info, trace, warn, error } = log;
11
+ const { info, trace, warn } = log;
12
12
 
13
13
  const FILE_EXTENSION = '.doc.json';
14
14
  const INITIAL_BATCH_SIZE = 100;
@@ -34,9 +34,8 @@ const execute = async () => {
34
34
  }
35
35
 
36
36
  warn(`This operation will permanently write ${totalCount} docs. Are you sure you want to continue?`);
37
- if(!userPrompt.keyInYN()) {
38
- error('User failed to confirm action.');
39
- process.exit(1);
37
+ if (!userPrompt.keyInYN()) {
38
+ throw new Error('User aborted execution.');
40
39
  }
41
40
 
42
41
  const results = { ok:[], failed:{} };
@@ -6,13 +6,17 @@ const processJson = (json) => {
6
6
  resources: json
7
7
  };
8
8
  };
9
+ const { RESOURCE_CONFIG_PATH, RESOURCES_DIR_PATH } = require('../lib/project-paths');
9
10
 
10
11
  module.exports = {
11
12
  requiresInstance: true,
13
+ RESOURCE_CONFIG_PATH,
14
+ RESOURCES_DIR_PATH,
12
15
  execute: () => {
13
- const configurationPath = `${environment.pathToProject}/resources.json`;
14
- const directoryPath = `${environment.pathToProject}/resources`;
16
+ const configurationPath = `${environment.pathToProject}/${RESOURCE_CONFIG_PATH}`;
17
+ const directoryPath = `${environment.pathToProject}/${RESOURCES_DIR_PATH}`;
15
18
 
16
19
  return uploadConfigurationDocs(configurationPath, directoryPath, 'resources', processJson);
17
20
  }
18
21
  };
22
+
@@ -0,0 +1,271 @@
1
+ const environment = require('../lib/environment');
2
+ const process = require('process');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const { error, warn, info } = require('../lib/log');
6
+ const Queue = require('queue-promise');
7
+ const watcher = require('@parcel/watcher');
8
+ const { uploadAppForms } = require('./upload-app-forms');
9
+ const { uploadContactForms } = require('./upload-contact-forms');
10
+ const { uploadCollectForms } = require('./upload-collect-forms');
11
+ const convertForms = require('../lib/convert-forms');
12
+ const uploadForms = require('../lib/upload-forms');
13
+ const { deleteForms } = require('../fn/delete-forms');
14
+ const { convertAppForms, APP_FORMS_PATH } = require('./convert-app-forms');
15
+ const { convertContactForm, CONTACT_FORMS_PATH } = require('./convert-contact-forms');
16
+ const { convertCollectForms, COLLECT_FORMS_PATH } = require('./convert-collect-forms');
17
+ const { uploadAppSettings, APP_SETTINGS_DIR_PATH, APP_SETTINGS_JSON_PATH } = require('./upload-app-settings');
18
+ const { execute: compileAppSettings, configFileMatcher } = require('./compile-app-settings');
19
+ const { execute: uploadCustomTranslations, TRANSLATIONS_DIR_PATH } = require('./upload-custom-translations');
20
+ const { execute: uploadResources, RESOURCES_DIR_PATH, RESOURCE_CONFIG_PATH } = require('./upload-resources');
21
+
22
+ const watcherEvents = {
23
+ CreateEvent: 'create',
24
+ DeleteEvent: 'delete',
25
+ UpdateEvent: 'update'
26
+ };
27
+
28
+ const uploadInitialState = async (api) => {
29
+ await uploadResources();
30
+ await uploadAppForms(environment.extraArgs);
31
+ await uploadContactForms(environment.extraArgs);
32
+ await uploadCustomTranslations();
33
+ await uploadAppSettings(api);
34
+ };
35
+
36
+ let fsEventsSubscription;
37
+ let eventQueue;
38
+
39
+ const cleanUp = async () => {
40
+ if (fsEventsSubscription) {
41
+ await fsEventsSubscription.unsubscribe();
42
+ fsEventsSubscription = undefined;
43
+ }
44
+ if (eventQueue) {
45
+ await eventQueue.clear();
46
+ eventQueue = undefined;
47
+ }
48
+ };
49
+
50
+ const waitForKillSignal = () => {
51
+ return new Promise((resolve) => {
52
+ process.on('SIGINT', async () => {
53
+ await cleanUp();
54
+ resolve();
55
+ });
56
+ });
57
+ };
58
+
59
+ const deleteForm = (fileName, formDir) => {
60
+ const form = uploadForms.formFileMatcher(fileName) || convertForms.formFileMatcher(fileName);
61
+ if (!form) {
62
+ return false;
63
+ }
64
+ const absFormDirPath = path.join(environment.pathToProject, formDir);
65
+ const formFiles = fs.readdirSync(absFormDirPath).map(file => (uploadForms.formFileMatcher(file) || convertForms.formFileMatcher(file))).filter(_form => _form === form);
66
+ const canDelete = formFiles.length <= 0;
67
+ if (canDelete) {
68
+ eventQueue.enqueue(async () => {
69
+ await deleteForms([form]);
70
+ return fileName;
71
+ });
72
+ return true;
73
+ }
74
+ warn(`You have to delete all form files (.xlsx, .properties.json, .xml) associated with the ${form} form to delete it from ${environment.apiUrl}`);
75
+ return false;
76
+ };
77
+
78
+ const processAppForm = (eventType, fileName) => {
79
+ if (eventType === watcherEvents.DeleteEvent && deleteForm(fileName, APP_FORMS_PATH)) {
80
+ return true;
81
+ }
82
+ let form = uploadForms.formFileMatcher(fileName);
83
+ if (form) {
84
+ eventQueue.enqueue(async () => {
85
+ await uploadAppForms([form]);
86
+ return fileName;
87
+ });
88
+ return true;
89
+ }
90
+
91
+ form = convertForms.formFileMatcher(fileName);
92
+ if (form) {
93
+ eventQueue.enqueue(async () => {
94
+ await convertAppForms([form]);
95
+ return fileName;
96
+ });
97
+ return true;
98
+ }
99
+ return false;
100
+ };
101
+
102
+ const processAppFormMedia = (formMediaDir, fileName) => {
103
+ const form = uploadForms.formMediaMatcher(formMediaDir);
104
+ if (form) {
105
+ eventQueue.enqueue(async () => {
106
+ await uploadAppForms([form]);
107
+ return fileName;
108
+ });
109
+ return true;
110
+ }
111
+ return false;
112
+ };
113
+
114
+ const processContactForm = (eventType, fileName) => {
115
+ if (eventType === watcherEvents.DeleteEvent && deleteForm(fileName, CONTACT_FORMS_PATH)) {
116
+ return true;
117
+ }
118
+ let form = convertForms.formFileMatcher(fileName);
119
+ if (form) {
120
+ eventQueue.enqueue(async () => {
121
+ await convertContactForm([form]);
122
+ return fileName;
123
+ });
124
+ return true;
125
+ }
126
+
127
+ form = uploadForms.formFileMatcher(fileName);
128
+ if (form) {
129
+ eventQueue.enqueue(async () => {
130
+ await uploadContactForms([form]);
131
+ return fileName;
132
+ });
133
+ return true;
134
+ }
135
+ return false;
136
+ };
137
+
138
+ const processCollectForm = (eventType, fileName) => {
139
+ if (eventType === watcherEvents.DeleteEvent && deleteForm(fileName, COLLECT_FORMS_PATH)) {
140
+ return true;
141
+ }
142
+ let form = uploadForms.formFileMatcher(fileName);
143
+ if (form) {
144
+ eventQueue.enqueue(async () => {
145
+ await uploadCollectForms([form]);
146
+ return fileName;
147
+ });
148
+ return true;
149
+ }
150
+
151
+ form = convertForms.formFileMatcher(fileName);
152
+ if (form) {
153
+ eventQueue.enqueue(async () => {
154
+ await convertCollectForms([form]);
155
+ return fileName;
156
+ });
157
+ return true;
158
+ }
159
+ return false;
160
+ };
161
+
162
+ const processConfigFiles = (api, fileName) => {
163
+ if (fileName === APP_SETTINGS_JSON_PATH) {
164
+ eventQueue.enqueue(async () => {
165
+ await uploadAppSettings(api);
166
+ return fileName;
167
+ });
168
+ return true;
169
+ }
170
+ if (configFileMatcher(fileName)) {
171
+ eventQueue.enqueue(async () => {
172
+ await compileAppSettings();
173
+ return fileName;
174
+ });
175
+ return true;
176
+ }
177
+ return false;
178
+ };
179
+
180
+ const watchProject = {
181
+ watch: async (api, blockFn, callback = {}, uploadStateOnStart = false) => {
182
+ if (uploadStateOnStart) {
183
+ await uploadInitialState(api);
184
+ info('Initial State Uploaded');
185
+ }
186
+
187
+ eventQueue = new Queue({ concurrent: 1, start: true });
188
+
189
+ fsEventsSubscription = await watcher.subscribe(environment.pathToProject, async (err, events) => {
190
+ if (err) {
191
+ error(err);
192
+ return;
193
+ }
194
+ for (const event of events) {
195
+ const changePath = event.path;
196
+ const parsedPath = path.parse(changePath);
197
+ const fileName = parsedPath.base;
198
+
199
+ if (parsedPath.dir === path.join(environment.pathToProject, APP_FORMS_PATH)) {
200
+ processAppForm(event.type, fileName);
201
+ continue;
202
+ }
203
+
204
+ if (parsedPath.dir.startsWith(path.join(environment.pathToProject, APP_FORMS_PATH))
205
+ && path.parse(parsedPath.dir).dir.endsWith(APP_FORMS_PATH)) { // check if the directory's immediate parent is forms/app
206
+ const dirName = path.parse(parsedPath.dir).base;
207
+ processAppFormMedia(dirName, fileName);
208
+ continue;
209
+ }
210
+
211
+ if (parsedPath.dir === path.join(environment.pathToProject, CONTACT_FORMS_PATH)) {
212
+ processContactForm(event.type, fileName);
213
+ continue;
214
+ }
215
+
216
+ if (parsedPath.dir === path.join(environment.pathToProject, COLLECT_FORMS_PATH)) {
217
+ processCollectForm(event.type, fileName);
218
+ continue;
219
+ }
220
+
221
+
222
+ if (parsedPath.dir === path.join(environment.pathToProject, TRANSLATIONS_DIR_PATH)) {
223
+ eventQueue.enqueue(async () => {
224
+ await uploadCustomTranslations();
225
+ return fileName;
226
+ });
227
+ continue;
228
+ }
229
+
230
+ if (parsedPath.dir === path.join(environment.pathToProject, APP_SETTINGS_DIR_PATH)) {
231
+ processConfigFiles(api, fileName);
232
+ continue;
233
+ }
234
+
235
+ if (parsedPath.dir === environment.pathToProject && parsedPath.base !== RESOURCE_CONFIG_PATH) {
236
+ processConfigFiles(api, fileName);
237
+ continue;
238
+ }
239
+
240
+ if (parsedPath.base === RESOURCE_CONFIG_PATH || parsedPath.dir === path.join(environment.pathToProject, RESOURCES_DIR_PATH)) {
241
+ eventQueue.enqueue(async () => {
242
+ await uploadResources();
243
+ return fileName;
244
+ });
245
+ continue;
246
+ }
247
+ }
248
+ });
249
+
250
+ eventQueue.on('resolve', file => {
251
+ if (callback) {
252
+ callback(file);
253
+ }
254
+ });
255
+ eventQueue.on('reject', err => error('watch-mode: Error while processing change,', err));
256
+
257
+ info('watching', environment.pathToProject, 'for changes');
258
+ await blockFn();
259
+ },
260
+ close: async () => {
261
+ info('stopping watchers');
262
+ await cleanUp();
263
+ }
264
+ };
265
+
266
+ const api = require('../lib/api');
267
+ module.exports = {
268
+ watchProject,
269
+ requiresInstance: true,
270
+ execute: () => watchProject.watch(api(), waitForKillSignal, false, true)
271
+ };
package/src/lib/api.js CHANGED
@@ -94,25 +94,24 @@ const api = {
94
94
  */
95
95
  available: async () => {
96
96
  const url = `${environment.apiUrl}/`;
97
+ log.info(`Checking that ${url} is available...`);
97
98
  try {
98
- log.info(`Checking that ${url} is available...`);
99
99
  await request.get(url);
100
- return true;
101
100
  } catch (err) {
102
101
  if (err.statusCode === 401) {
103
- log.error(`Authentication failed connecting to ${url}. `
104
- + 'Check the supplied username and password and try again.');
105
- } else if (err.statusCode === 403) {
106
- log.error(`Insufficient permissions connecting to ${url}. `
107
- + 'You need to use admin permissions to execute this command.');
108
- } else if (err.statusCode) {
109
- log.error(`Received error code ${err.statusCode} connecting to ${url}. `
110
- + 'Check the server and and try again.');
111
- } else {
112
- log.error(`Failed to get a response from ${url}. Maybe you entered the wrong URL, `
113
- + 'wrong port or the instance is not started. Please check and try again.');
102
+ throw new Error(`Authentication failed connecting to ${url}. `
103
+ + 'Check the supplied username and password and try again.');
104
+ }
105
+ if (err.statusCode === 403) {
106
+ throw new Error(`Insufficient permissions connecting to ${url}. `
107
+ + 'You need to use admin permissions to execute this command.');
108
+ }
109
+ if (err.statusCode) {
110
+ throw new Error(`Received error code ${err.statusCode} connecting to ${url}. `
111
+ + 'Check the server and and try again.');
114
112
  }
115
- return false;
113
+ throw new Error(`Failed to get a response from ${url}. Maybe you entered the wrong URL, `
114
+ + 'wrong port or the instance is not started. Please check and try again.');
116
115
  }
117
116
  },
118
117