cht-conf 3.10.2 → 3.12.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/.eslintignore +2 -1
- package/.releaserc.json +1 -1
- package/CHANGELOG.md +22 -1
- package/README.md +1 -1
- package/package.json +6 -3
- package/release-notes.md +1 -1
- package/src/cli/usage.js +3 -0
- package/src/fn/compile-app-settings.js +25 -8
- package/src/fn/compress-svgs.js +1 -1
- package/src/fn/convert-app-forms.js +13 -6
- package/src/fn/convert-collect-forms.js +11 -4
- package/src/fn/convert-contact-forms.js +62 -59
- package/src/fn/delete-forms.js +17 -16
- package/src/fn/move-contacts.js +2 -2
- package/src/fn/upload-app-forms.js +11 -4
- package/src/fn/upload-app-settings.js +28 -22
- package/src/fn/upload-collect-forms.js +10 -5
- package/src/fn/upload-contact-forms.js +13 -6
- package/src/fn/upload-custom-translations.js +7 -4
- package/src/fn/upload-resources.js +6 -2
- package/src/fn/validate-app-forms.js +12 -0
- package/src/fn/validate-collect-forms.js +12 -0
- package/src/fn/validate-contact-forms.js +12 -0
- package/src/fn/watch-project.js +287 -0
- package/src/lib/convert-forms.js +46 -31
- package/src/lib/environment.js +5 -2
- package/src/lib/git-exec.js +1 -1
- package/src/lib/main.js +39 -11
- package/src/lib/project-paths.js +12 -0
- package/src/lib/upload-forms.js +36 -8
- package/src/lib/validate-forms.js +57 -39
- package/src/lib/validation/form/README.md +32 -0
- package/src/lib/validation/form/can-generate-xform.js +18 -0
- package/src/lib/validation/form/has-instance-id.js +14 -0
|
@@ -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}
|
|
14
|
-
const directoryPath = `${environment.pathToProject}
|
|
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,12 @@
|
|
|
1
|
+
const validateForms = require('../lib/validate-forms');
|
|
2
|
+
const environment = require('../lib/environment');
|
|
3
|
+
|
|
4
|
+
const validateAppForms = (forms) => {
|
|
5
|
+
return validateForms(environment.pathToProject, 'app', { forms });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
requiresInstance: false,
|
|
10
|
+
validateAppForms,
|
|
11
|
+
execute: () => validateAppForms(environment.extraArgs)
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const validateForms = require('../lib/validate-forms');
|
|
2
|
+
const environment = require('../lib/environment');
|
|
3
|
+
|
|
4
|
+
const validateCollectForms = (forms) => {
|
|
5
|
+
return validateForms(environment.pathToProject, 'collect', { forms });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
requiresInstance: false,
|
|
10
|
+
validateCollectForms,
|
|
11
|
+
execute: () => validateCollectForms(environment.extraArgs)
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const validateForms = require('../lib/validate-forms');
|
|
2
|
+
const environment = require('../lib/environment');
|
|
3
|
+
|
|
4
|
+
const validateContactForms = (forms) => {
|
|
5
|
+
return validateForms(environment.pathToProject, 'contact', { forms });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
requiresInstance: false,
|
|
10
|
+
validateContactForms,
|
|
11
|
+
execute: () => validateContactForms(environment.extraArgs)
|
|
12
|
+
};
|
|
@@ -0,0 +1,287 @@
|
|
|
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 { validateAppForms } = require('./validate-app-forms');
|
|
9
|
+
const { validateContactForms } = require('./validate-contact-forms');
|
|
10
|
+
const { validateCollectForms } = require('./validate-collect-forms');
|
|
11
|
+
const { uploadAppForms } = require('./upload-app-forms');
|
|
12
|
+
const { uploadContactForms } = require('./upload-contact-forms');
|
|
13
|
+
const { uploadCollectForms } = require('./upload-collect-forms');
|
|
14
|
+
const convertForms = require('../lib/convert-forms');
|
|
15
|
+
const uploadForms = require('../lib/upload-forms');
|
|
16
|
+
const { deleteForms } = require('../fn/delete-forms');
|
|
17
|
+
const { convertAppForms, APP_FORMS_PATH } = require('./convert-app-forms');
|
|
18
|
+
const { convertContactForm, CONTACT_FORMS_PATH } = require('./convert-contact-forms');
|
|
19
|
+
const { convertCollectForms, COLLECT_FORMS_PATH } = require('./convert-collect-forms');
|
|
20
|
+
const { uploadAppSettings, APP_SETTINGS_DIR_PATH, APP_SETTINGS_JSON_PATH } = require('./upload-app-settings');
|
|
21
|
+
const { execute: compileAppSettings, configFileMatcher } = require('./compile-app-settings');
|
|
22
|
+
const { execute: uploadCustomTranslations, TRANSLATIONS_DIR_PATH } = require('./upload-custom-translations');
|
|
23
|
+
const { execute: uploadResources, RESOURCES_DIR_PATH, RESOURCE_CONFIG_PATH } = require('./upload-resources');
|
|
24
|
+
|
|
25
|
+
const watcherEvents = {
|
|
26
|
+
CreateEvent: 'create',
|
|
27
|
+
DeleteEvent: 'delete',
|
|
28
|
+
UpdateEvent: 'update'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const runValidation = (validation, forms) => {
|
|
32
|
+
if(environment.skipValidate) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
return validation(forms);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const uploadInitialState = async (api) => {
|
|
39
|
+
await uploadResources();
|
|
40
|
+
await runValidation(validateAppForms, environment.extraArgs);
|
|
41
|
+
await runValidation(validateContactForms, environment.extraArgs);
|
|
42
|
+
await uploadAppForms(environment.extraArgs);
|
|
43
|
+
await uploadContactForms(environment.extraArgs);
|
|
44
|
+
await uploadCustomTranslations();
|
|
45
|
+
await uploadAppSettings(api);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
let fsEventsSubscription;
|
|
49
|
+
let eventQueue;
|
|
50
|
+
|
|
51
|
+
const cleanUp = async () => {
|
|
52
|
+
if (fsEventsSubscription) {
|
|
53
|
+
await fsEventsSubscription.unsubscribe();
|
|
54
|
+
fsEventsSubscription = undefined;
|
|
55
|
+
}
|
|
56
|
+
if (eventQueue) {
|
|
57
|
+
await eventQueue.clear();
|
|
58
|
+
eventQueue = undefined;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const waitForKillSignal = () => {
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
process.on('SIGINT', async () => {
|
|
65
|
+
await cleanUp();
|
|
66
|
+
resolve();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const deleteForm = (fileName, formDir) => {
|
|
72
|
+
const form = uploadForms.formFileMatcher(fileName) || convertForms.formFileMatcher(fileName);
|
|
73
|
+
if (!form) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const absFormDirPath = path.join(environment.pathToProject, formDir);
|
|
77
|
+
const formFiles = fs.readdirSync(absFormDirPath).map(file => (uploadForms.formFileMatcher(file) || convertForms.formFileMatcher(file))).filter(_form => _form === form);
|
|
78
|
+
const canDelete = formFiles.length <= 0;
|
|
79
|
+
if (canDelete) {
|
|
80
|
+
eventQueue.enqueue(async () => {
|
|
81
|
+
await deleteForms([form]);
|
|
82
|
+
return fileName;
|
|
83
|
+
});
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
warn(`You have to delete all form files (.xlsx, .properties.json, .xml) associated with the ${form} form to delete it from ${environment.apiUrl}`);
|
|
87
|
+
return false;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const processAppForm = (eventType, fileName) => {
|
|
91
|
+
if (eventType === watcherEvents.DeleteEvent && deleteForm(fileName, APP_FORMS_PATH)) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
let form = uploadForms.formFileMatcher(fileName);
|
|
95
|
+
if (form) {
|
|
96
|
+
eventQueue.enqueue(async () => {
|
|
97
|
+
await runValidation(validateAppForms, [form]);
|
|
98
|
+
await uploadAppForms([form]);
|
|
99
|
+
return fileName;
|
|
100
|
+
});
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
form = convertForms.formFileMatcher(fileName);
|
|
105
|
+
if (form) {
|
|
106
|
+
eventQueue.enqueue(async () => {
|
|
107
|
+
await convertAppForms([form]);
|
|
108
|
+
return fileName;
|
|
109
|
+
});
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const processAppFormMedia = (formMediaDir, fileName) => {
|
|
116
|
+
const form = uploadForms.formMediaMatcher(formMediaDir);
|
|
117
|
+
if (form) {
|
|
118
|
+
eventQueue.enqueue(async () => {
|
|
119
|
+
await runValidation(validateAppForms,[form]);
|
|
120
|
+
await uploadAppForms([form]);
|
|
121
|
+
return fileName;
|
|
122
|
+
});
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const processContactForm = (eventType, fileName) => {
|
|
129
|
+
if (eventType === watcherEvents.DeleteEvent && deleteForm(fileName, CONTACT_FORMS_PATH)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
let form = convertForms.formFileMatcher(fileName);
|
|
133
|
+
if (form) {
|
|
134
|
+
eventQueue.enqueue(async () => {
|
|
135
|
+
await convertContactForm([form]);
|
|
136
|
+
return fileName;
|
|
137
|
+
});
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
form = uploadForms.formFileMatcher(fileName);
|
|
142
|
+
if (form) {
|
|
143
|
+
eventQueue.enqueue(async () => {
|
|
144
|
+
await runValidation(validateContactForms,[form]);
|
|
145
|
+
await uploadContactForms([form]);
|
|
146
|
+
return fileName;
|
|
147
|
+
});
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const processCollectForm = (eventType, fileName) => {
|
|
154
|
+
if (eventType === watcherEvents.DeleteEvent && deleteForm(fileName, COLLECT_FORMS_PATH)) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
let form = uploadForms.formFileMatcher(fileName);
|
|
158
|
+
if (form) {
|
|
159
|
+
eventQueue.enqueue(async () => {
|
|
160
|
+
await runValidation(validateCollectForms,[form]);
|
|
161
|
+
await uploadCollectForms([form]);
|
|
162
|
+
return fileName;
|
|
163
|
+
});
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
form = convertForms.formFileMatcher(fileName);
|
|
168
|
+
if (form) {
|
|
169
|
+
eventQueue.enqueue(async () => {
|
|
170
|
+
await convertCollectForms([form]);
|
|
171
|
+
return fileName;
|
|
172
|
+
});
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const processConfigFiles = (api, fileName) => {
|
|
179
|
+
if (fileName === APP_SETTINGS_JSON_PATH) {
|
|
180
|
+
eventQueue.enqueue(async () => {
|
|
181
|
+
await uploadAppSettings(api);
|
|
182
|
+
return fileName;
|
|
183
|
+
});
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
if (configFileMatcher(fileName)) {
|
|
187
|
+
eventQueue.enqueue(async () => {
|
|
188
|
+
await compileAppSettings();
|
|
189
|
+
return fileName;
|
|
190
|
+
});
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const watchProject = {
|
|
197
|
+
watch: async (api, blockFn, callback = {}, uploadStateOnStart = false) => {
|
|
198
|
+
if (uploadStateOnStart) {
|
|
199
|
+
await uploadInitialState(api);
|
|
200
|
+
info('Initial State Uploaded');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
eventQueue = new Queue({ concurrent: 1, start: true });
|
|
204
|
+
|
|
205
|
+
fsEventsSubscription = await watcher.subscribe(environment.pathToProject, async (err, events) => {
|
|
206
|
+
if (err) {
|
|
207
|
+
error(err);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
for (const event of events) {
|
|
211
|
+
const changePath = event.path;
|
|
212
|
+
const parsedPath = path.parse(changePath);
|
|
213
|
+
const fileName = parsedPath.base;
|
|
214
|
+
|
|
215
|
+
if (parsedPath.dir === path.join(environment.pathToProject, APP_FORMS_PATH)) {
|
|
216
|
+
processAppForm(event.type, fileName);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (parsedPath.dir.startsWith(path.join(environment.pathToProject, APP_FORMS_PATH))
|
|
221
|
+
&& path.parse(parsedPath.dir).dir.endsWith(APP_FORMS_PATH)) { // check if the directory's immediate parent is forms/app
|
|
222
|
+
const dirName = path.parse(parsedPath.dir).base;
|
|
223
|
+
processAppFormMedia(dirName, fileName);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (parsedPath.dir === path.join(environment.pathToProject, CONTACT_FORMS_PATH)) {
|
|
228
|
+
processContactForm(event.type, fileName);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (parsedPath.dir === path.join(environment.pathToProject, COLLECT_FORMS_PATH)) {
|
|
233
|
+
processCollectForm(event.type, fileName);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if (parsedPath.dir === path.join(environment.pathToProject, TRANSLATIONS_DIR_PATH)) {
|
|
239
|
+
eventQueue.enqueue(async () => {
|
|
240
|
+
await uploadCustomTranslations();
|
|
241
|
+
return fileName;
|
|
242
|
+
});
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (parsedPath.dir === path.join(environment.pathToProject, APP_SETTINGS_DIR_PATH)) {
|
|
247
|
+
processConfigFiles(api, fileName);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (parsedPath.dir === environment.pathToProject && parsedPath.base !== RESOURCE_CONFIG_PATH) {
|
|
252
|
+
processConfigFiles(api, fileName);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (parsedPath.base === RESOURCE_CONFIG_PATH || parsedPath.dir === path.join(environment.pathToProject, RESOURCES_DIR_PATH)) {
|
|
257
|
+
eventQueue.enqueue(async () => {
|
|
258
|
+
await uploadResources();
|
|
259
|
+
return fileName;
|
|
260
|
+
});
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
eventQueue.on('resolve', file => {
|
|
267
|
+
if (callback) {
|
|
268
|
+
callback(file);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
eventQueue.on('reject', err => error('watch-mode: Error while processing change,', err));
|
|
272
|
+
|
|
273
|
+
info('watching', environment.pathToProject, 'for changes');
|
|
274
|
+
await blockFn();
|
|
275
|
+
},
|
|
276
|
+
close: async () => {
|
|
277
|
+
info('stopping watchers');
|
|
278
|
+
await cleanUp();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const api = require('../lib/api');
|
|
283
|
+
module.exports = {
|
|
284
|
+
watchProject,
|
|
285
|
+
requiresInstance: true,
|
|
286
|
+
execute: () => watchProject.watch(api(), waitForKillSignal, false, true)
|
|
287
|
+
};
|
package/src/lib/convert-forms.js
CHANGED
|
@@ -20,25 +20,34 @@ E
|
|
|
20
20
|
E pip uninstall pyxform-medic
|
|
21
21
|
E` + INSTALLATION_INSTRUCTIONS;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const FORM_EXTENSION = '.xlsx';
|
|
24
|
+
const formFileMatcher = (fileName) => {
|
|
25
|
+
if (fileName.endsWith(FORM_EXTENSION) &&
|
|
26
|
+
!fileName.startsWith('~$') // ignore Excel "owner files"
|
|
27
|
+
&& fileName !== 'PLACE_TYPE-create.xlsx' && fileName !== 'PLACE_TYPE-edit.xlsx') {
|
|
28
|
+
return fileName.slice(0, fileName.length - FORM_EXTENSION.length);
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const execute = async (projectDir, subDirectory, options) => {
|
|
34
|
+
if (!options) options = {};
|
|
25
35
|
|
|
26
36
|
const formsDir = getFormDir(projectDir, subDirectory);
|
|
27
37
|
|
|
28
|
-
if(!fs.exists(formsDir)) {
|
|
38
|
+
if (!fs.exists(formsDir)) {
|
|
29
39
|
warn(`Forms dir not found: ${formsDir}`);
|
|
30
40
|
return Promise.resolve();
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
const filesToConvert = argsFormFilter(formsDir,
|
|
34
|
-
.filter(name =>
|
|
35
|
-
.filter(name => name !== 'PLACE_TYPE-create.xlsx' && name !== 'PLACE_TYPE-edit.xlsx');
|
|
43
|
+
const filesToConvert = argsFormFilter(formsDir, FORM_EXTENSION, options)
|
|
44
|
+
.filter(name => formFileMatcher(name));
|
|
36
45
|
|
|
37
46
|
for (let xls of filesToConvert) {
|
|
38
47
|
const originalSourcePath = `${formsDir}/${xls}`;
|
|
39
48
|
let sourcePath;
|
|
40
49
|
|
|
41
|
-
if(options.force_data_node) {
|
|
50
|
+
if (options.force_data_node) {
|
|
42
51
|
const temporaryPath = `${fs.mkdtemp()}/${options.force_data_node}.xlsx`;
|
|
43
52
|
fs.copy(originalSourcePath, temporaryPath);
|
|
44
53
|
sourcePath = temporaryPath;
|
|
@@ -54,15 +63,21 @@ module.exports = async (projectDir, subDirectory, options) => {
|
|
|
54
63
|
}
|
|
55
64
|
};
|
|
56
65
|
|
|
66
|
+
module.exports = {
|
|
67
|
+
SUPPORTED_EXTENSIONS: [FORM_EXTENSION],
|
|
68
|
+
formFileMatcher,
|
|
69
|
+
execute
|
|
70
|
+
};
|
|
71
|
+
|
|
57
72
|
const xls2xform = (sourcePath, targetPath) =>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
exec([XLS2XFORM, '--skip_validate', sourcePath, targetPath])
|
|
74
|
+
.catch(e => {
|
|
75
|
+
if (executableAvailable()) {
|
|
76
|
+
if (e.includes('unrecognized arguments: --skip_validate')) {
|
|
77
|
+
throw new Error('Your xls2xform installation appears to be out of date.' + UPDATE_INSTRUCTIONS);
|
|
78
|
+
} else throw e;
|
|
79
|
+
} else throw new Error('There was a problem executing xls2xform. It may not be installed.' + INSTALLATION_INSTRUCTIONS);
|
|
80
|
+
});
|
|
66
81
|
|
|
67
82
|
// here we fix the form content in arcane ways. Seeing as we have out own fork
|
|
68
83
|
// of pyxform, we should probably be doing this fixing there.
|
|
@@ -72,33 +87,33 @@ const fixXml = (path, hiddenFields, transformer, enketo) => {
|
|
|
72
87
|
// future changes to the output of xls2xform.
|
|
73
88
|
let xml = fs.read(path)
|
|
74
89
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
// The following copies behaviour from old bash scripts, and will create a
|
|
91
|
+
// second <meta> element if one already existed. We may want to actually
|
|
92
|
+
// merge the two instead.
|
|
93
|
+
.replace(/<inputs>/, META_XML_SECTION)
|
|
79
94
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
// XLSForm does not allow converting a field without a label, so we use
|
|
96
|
+
// the placeholder NO_LABEL.
|
|
97
|
+
.replace(/NO_LABEL/g, '')
|
|
83
98
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
// No comment.
|
|
100
|
+
.replace(/.*DELETE_THIS_LINE.*(\r|\n)/g, '')
|
|
101
|
+
;
|
|
87
102
|
|
|
88
103
|
// Enketo _may_ not work with forms which define a default language - see
|
|
89
104
|
// https://github.com/medic/cht-core/issues/3174
|
|
90
|
-
if(enketo) xml = xml.replace(/ default="true\(\)"/g, '');
|
|
105
|
+
if (enketo) xml = xml.replace(/ default="true\(\)"/g, '');
|
|
91
106
|
|
|
92
|
-
if(hiddenFields) {
|
|
107
|
+
if (hiddenFields) {
|
|
93
108
|
const r = new RegExp(`<(${hiddenFields.join('|')})(/?)>`, 'g');
|
|
94
109
|
xml = xml.replace(r, '<$1 tag="hidden"$2>');
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
if(transformer) xml = transformer(xml, path);
|
|
112
|
+
if (transformer) xml = transformer(xml, path);
|
|
98
113
|
|
|
99
114
|
|
|
100
115
|
// Check for deprecations
|
|
101
|
-
if(xml.includes('repeat-relevant')) {
|
|
116
|
+
if (xml.includes('repeat-relevant')) {
|
|
102
117
|
warn('From webapp version 2.14.0, repeat-relevant is no longer required. See https://github.com/medic/cht-core/issues/3449 for more info.');
|
|
103
118
|
}
|
|
104
119
|
|
|
@@ -106,7 +121,7 @@ const fixXml = (path, hiddenFields, transformer, enketo) => {
|
|
|
106
121
|
};
|
|
107
122
|
|
|
108
123
|
function getHiddenFields(propsJson) {
|
|
109
|
-
if(!fs.exists(propsJson)) return [];
|
|
124
|
+
if (!fs.exists(propsJson)) return [];
|
|
110
125
|
else return fs.readJson(propsJson).hidden_fields;
|
|
111
126
|
}
|
|
112
127
|
|
|
@@ -116,7 +131,7 @@ function executableAvailable() {
|
|
|
116
131
|
stdio: ['ignore', 'ignore', 'ignore'],
|
|
117
132
|
});
|
|
118
133
|
return true;
|
|
119
|
-
} catch(e) {
|
|
134
|
+
} catch (e) {
|
|
120
135
|
return false;
|
|
121
136
|
}
|
|
122
137
|
}
|
package/src/lib/environment.js
CHANGED
|
@@ -12,7 +12,8 @@ const initialize = (
|
|
|
12
12
|
extraArgs,
|
|
13
13
|
apiUrl,
|
|
14
14
|
force,
|
|
15
|
-
skipTranslationCheck
|
|
15
|
+
skipTranslationCheck,
|
|
16
|
+
skipValidate
|
|
16
17
|
) => {
|
|
17
18
|
if (state.initialized) {
|
|
18
19
|
throw Error('environment is already initialized');
|
|
@@ -26,7 +27,8 @@ const initialize = (
|
|
|
26
27
|
isArchiveMode,
|
|
27
28
|
pathToProject,
|
|
28
29
|
force,
|
|
29
|
-
skipTranslationCheck
|
|
30
|
+
skipTranslationCheck,
|
|
31
|
+
skipValidate
|
|
30
32
|
});
|
|
31
33
|
};
|
|
32
34
|
|
|
@@ -51,6 +53,7 @@ module.exports = {
|
|
|
51
53
|
get apiUrl() { return getState('apiUrl'); },
|
|
52
54
|
get force() { return getState('force'); },
|
|
53
55
|
get skipTranslationCheck() { return getState('skipTranslationCheck'); },
|
|
56
|
+
get skipValidate() { return getState('skipValidate'); },
|
|
54
57
|
|
|
55
58
|
/**
|
|
56
59
|
* Return `true` if the environment **seems** to be production.
|
package/src/lib/git-exec.js
CHANGED
|
@@ -64,7 +64,7 @@ module.exports.getDefaultRemote = async () => {
|
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Returns the upstream of the current branch,
|
|
67
|
-
* in the form of repo/branch, eg. 'origin/
|
|
67
|
+
* in the form of repo/branch, eg. 'origin/main'.
|
|
68
68
|
*
|
|
69
69
|
* Returns `null` if there are no upstreams repos configured.
|
|
70
70
|
*/
|
package/src/lib/main.js
CHANGED
|
@@ -21,6 +21,9 @@ const defaultActions = [
|
|
|
21
21
|
'convert-app-forms',
|
|
22
22
|
'convert-collect-forms',
|
|
23
23
|
'convert-contact-forms',
|
|
24
|
+
'validate-app-forms',
|
|
25
|
+
'validate-collect-forms',
|
|
26
|
+
'validate-contact-forms',
|
|
24
27
|
'backup-all-forms',
|
|
25
28
|
'delete-all-forms',
|
|
26
29
|
'upload-app-forms',
|
|
@@ -38,6 +41,9 @@ const defaultArchiveActions = [
|
|
|
38
41
|
'convert-app-forms',
|
|
39
42
|
'convert-collect-forms',
|
|
40
43
|
'convert-contact-forms',
|
|
44
|
+
'validate-app-forms',
|
|
45
|
+
'validate-collect-forms',
|
|
46
|
+
'validate-contact-forms',
|
|
41
47
|
'upload-app-forms',
|
|
42
48
|
'upload-collect-forms',
|
|
43
49
|
'upload-contact-forms',
|
|
@@ -123,19 +129,40 @@ module.exports = async (argv, env) => {
|
|
|
123
129
|
// Build up actions
|
|
124
130
|
//
|
|
125
131
|
let actions = cmdArgs._;
|
|
126
|
-
if (
|
|
132
|
+
if (actions.length) {
|
|
133
|
+
const unsupported = actions.filter(a => !supportedActions.includes(a));
|
|
134
|
+
if(unsupported.length) {
|
|
135
|
+
throw new Error(`Unsupported action(s): ${unsupported.join(' ')}`);
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
127
138
|
actions = !cmdArgs.archive ? defaultActions : defaultArchiveActions;
|
|
128
139
|
}
|
|
129
140
|
|
|
130
|
-
const unsupported = actions.filter(a => !supportedActions.includes(a));
|
|
131
|
-
if(unsupported.length) {
|
|
132
|
-
throw new Error(`Unsupported action(s): ${unsupported.join(' ')}`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
141
|
if (cmdArgs['skip-git-check']) {
|
|
136
142
|
actions = actions.filter(a => a !== 'check-git');
|
|
137
143
|
}
|
|
138
144
|
|
|
145
|
+
const skipValidate = cmdArgs['skip-validate'];
|
|
146
|
+
if(skipValidate) {
|
|
147
|
+
warn('Skipping all form validation.');
|
|
148
|
+
const validateActions = [
|
|
149
|
+
'validate-app-forms',
|
|
150
|
+
'validate-collect-forms',
|
|
151
|
+
'validate-contact-forms'
|
|
152
|
+
];
|
|
153
|
+
actions = actions.filter(action => !validateActions.includes(action));
|
|
154
|
+
} else {
|
|
155
|
+
const addFormValidationIfNecessary = (formType) => {
|
|
156
|
+
const updateFormsIndex = actions.indexOf(`upload-${formType}-forms`);
|
|
157
|
+
if (updateFormsIndex >= 0 && actions.indexOf(`validate-${formType}-forms`) < 0) {
|
|
158
|
+
actions.splice(updateFormsIndex, 0, `validate-${formType}-forms`);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
addFormValidationIfNecessary('app');
|
|
162
|
+
addFormValidationIfNecessary('collect');
|
|
163
|
+
addFormValidationIfNecessary('contact');
|
|
164
|
+
}
|
|
165
|
+
|
|
139
166
|
actions = actions.map(actionName => {
|
|
140
167
|
const action = require(`../fn/${actionName}`);
|
|
141
168
|
|
|
@@ -157,9 +184,9 @@ module.exports = async (argv, env) => {
|
|
|
157
184
|
//
|
|
158
185
|
const projectName = fs.path.basename(pathToProject);
|
|
159
186
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
187
|
+
const apiUrl = getApiUrl(cmdArgs, env);
|
|
188
|
+
const requiresInstance = actions.some(action => action.requiresInstance);
|
|
189
|
+
if (requiresInstance) {
|
|
163
190
|
if (!apiUrl) {
|
|
164
191
|
throw new Error('Failed to obtain a url to the API');
|
|
165
192
|
}
|
|
@@ -177,10 +204,11 @@ module.exports = async (argv, env) => {
|
|
|
177
204
|
extraArgs,
|
|
178
205
|
apiUrl,
|
|
179
206
|
cmdArgs.force,
|
|
180
|
-
cmdArgs['skip-translation-check']
|
|
207
|
+
cmdArgs['skip-translation-check'],
|
|
208
|
+
skipValidate
|
|
181
209
|
);
|
|
182
210
|
|
|
183
|
-
if (apiUrl) {
|
|
211
|
+
if (requiresInstance && apiUrl) {
|
|
184
212
|
await api().available();
|
|
185
213
|
}
|
|
186
214
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
// Paths Relative to the Project
|
|
3
|
+
module.exports = {
|
|
4
|
+
APP_SETTINGS_DIR_PATH: 'app_settings',
|
|
5
|
+
APP_SETTINGS_JSON_PATH: 'app_settings.json',
|
|
6
|
+
APP_FORMS_PATH: path.join('forms', 'app'),
|
|
7
|
+
CONTACT_FORMS_PATH: path.join('forms', 'contact'),
|
|
8
|
+
COLLECT_FORMS_PATH: path.join('forms', 'collect'),
|
|
9
|
+
TRANSLATIONS_DIR_PATH: 'translations',
|
|
10
|
+
RESOURCE_CONFIG_PATH: 'resources.json',
|
|
11
|
+
RESOURCES_DIR_PATH: 'resources'
|
|
12
|
+
};
|