cht-conf 4.4.0 → 4.5.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 (63) hide show
  1. package/.editorconfig +15 -0
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +9 -1
  4. package/package.json +27 -26
  5. package/src/bin/shell-completion.js +5 -5
  6. package/src/cli/supported-actions.js +2 -2
  7. package/src/cli/usage.js +2 -0
  8. package/src/fn/backup-all-forms.js +1 -1
  9. package/src/fn/compile-app-settings.js +41 -14
  10. package/src/fn/compress-pngs.js +10 -8
  11. package/src/fn/compress-svgs.js +5 -2
  12. package/src/fn/create-users.js +6 -3
  13. package/src/fn/csv-to-docs.js +73 -52
  14. package/src/fn/delete-contacts.js +2 -0
  15. package/src/fn/edit-contacts.js +14 -11
  16. package/src/fn/fetch-csvs-from-google-drive.js +3 -3
  17. package/src/fn/fetch-forms-from-google-drive.js +3 -3
  18. package/src/fn/initialise-project-layout.js +15 -9
  19. package/src/fn/merge-contacts.js +10 -2
  20. package/src/fn/move-contacts.js +2 -0
  21. package/src/fn/upload-custom-translations.js +29 -22
  22. package/src/fn/upload-docs.js +30 -13
  23. package/src/fn/upload-privacy-policies.js +4 -1
  24. package/src/fn/upload-sms-from-csv.js +11 -11
  25. package/src/fn/watch-project.js +212 -204
  26. package/src/lib/api.js +22 -22
  27. package/src/lib/args-form-filter.js +1 -1
  28. package/src/lib/attachments-from-dir.js +3 -1
  29. package/src/lib/check-cht-conf-dependency-version.js +4 -2
  30. package/src/lib/check-for-updates.js +4 -3
  31. package/src/lib/compilation/compile-contact-summary.js +12 -2
  32. package/src/lib/compilation/compile-tasks-and-targets.js +5 -2
  33. package/src/lib/compilation/package-lib.js +5 -2
  34. package/src/lib/compilation/validate-declarative-schema.js +49 -32
  35. package/src/lib/convert-forms.js +27 -9
  36. package/src/lib/exec-promise.js +27 -19
  37. package/src/lib/fetch-document-list.js +2 -2
  38. package/src/lib/generate-users-csv.js +1 -1
  39. package/src/lib/get-api-url.js +4 -1
  40. package/src/lib/google-auth.js +15 -5
  41. package/src/lib/hierarchy-operations/delete-hierarchy.js +4 -1
  42. package/src/lib/hierarchy-operations/hierarchy-data-source.js +7 -2
  43. package/src/lib/hierarchy-operations/index.js +16 -4
  44. package/src/lib/hierarchy-operations/jsdocFolder.js +4 -1
  45. package/src/lib/hierarchy-operations/lineage-constraints.js +12 -6
  46. package/src/lib/hierarchy-operations/replace-lineage.js +20 -18
  47. package/src/lib/insert-or-replace.js +6 -2
  48. package/src/lib/log.js +6 -2
  49. package/src/lib/main.js +4 -3
  50. package/src/lib/nools-utils.js +5 -3
  51. package/src/lib/parse-purge.js +4 -1
  52. package/src/lib/parse-targets.js +3 -1
  53. package/src/lib/polyfill.js +3 -1
  54. package/src/lib/progress-bar.js +26 -13
  55. package/src/lib/skip-fn.js +5 -3
  56. package/src/lib/sync-fs.js +24 -12
  57. package/src/lib/upload-forms.js +17 -7
  58. package/src/lib/validation/form/can-generate-xform.js +3 -1
  59. package/src/lib/validation/form/check-xpaths-exist.js +8 -3
  60. package/src/lib/validation/form/deprecated-appearance.js +4 -2
  61. package/src/lib/validation/form/has-instance-id.js +4 -1
  62. package/src/lib/validation/form/no-required-notes.js +4 -1
  63. package/src/lib/warn-upload-overwrite.js +6 -3
package/.editorconfig ADDED
@@ -0,0 +1,15 @@
1
+ # top-most EditorConfig file
2
+ root = true
3
+
4
+ # Unix-style newlines with a newline ending every file
5
+ [*]
6
+ end_of_line = lf
7
+ insert_final_newline = true
8
+ trim_trailing_whitespace = true
9
+ charset = utf-8
10
+ indent_style = space
11
+ indent_size = 2
12
+
13
+ # Markdown files
14
+ [*.md]
15
+ trim_trailing_whitespace = false
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [4.5.0](https://github.com/medic/cht-conf/compare/v4.4.1...v4.5.0) (2025-04-15)
2
+
3
+
4
+ ### Features
5
+
6
+ * **#675:** support `duplicate_check` contact form property ([#677](https://github.com/medic/cht-conf/issues/677)) ([503bb7d](https://github.com/medic/cht-conf/commit/503bb7d94dc9ee87078f183e43e5d1141123a05c)), closes [#675](https://github.com/medic/cht-conf/issues/675)
7
+
8
+ ## [4.4.1](https://github.com/medic/cht-conf/compare/v4.4.0...v4.4.1) (2025-02-03)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * remove typo from .eslintrc file created by initialize-project-layout ([#658](https://github.com/medic/cht-conf/issues/658)) ([1aaad4c](https://github.com/medic/cht-conf/commit/1aaad4c336843bd85db8ca7d0373a0f87d629827))
14
+
1
15
  # [4.4.0](https://github.com/medic/cht-conf/compare/v4.3.0...v4.4.0) (2024-12-16)
2
16
 
3
17
 
package/README.md CHANGED
@@ -112,6 +112,14 @@ CHT Conf supports authentication using a session token by adding `--session-toke
112
112
 
113
113
  cht --url=https://example.com:12345 --session-token=*my_token*
114
114
 
115
+ The `my_token` can be obtained by doing a POST request to `/_session` [endpoint](https://docs.couchdb.org/en/stable/api/server/authn.html#cookie-authentication) with `name` and `password` as form parameters.
116
+
117
+ For example, if your CHT instance is `my.cht.com`, you could use this `curl` call to specify your user `medic` and your password `secret123` to retrieve the header with the `AuthSession` value which is the token:
118
+
119
+ ```
120
+ curl -v -H 'Content-Type: application/json' -d '{"name":"medic","password":"secret123"}' https://my.cht.com/_session 2>&1 | grep AuthSession
121
+ < set-cookie: AuthSession=bWVkaWM6NjdBRTM4MkE6EguRnzpSiK0t8wFaOQ_jgkZE8UWcgNWgpyStzbbHreI; Version=1; Expires=Fri, 13-Feb-2026 18:21:30 GMT; Max-Age=31536000; Path=/; HttpOnly
122
+ ```
115
123
  ### Into an archive to be uploaded later
116
124
 
117
125
  cht --archive
@@ -357,7 +365,7 @@ Builds brought to you courtesy of GitHub actions.
357
365
 
358
366
  # Copyright
359
367
 
360
- Copyright 2013-2024 Medic Mobile, Inc. <hello@medic.org>
368
+ Copyright 2013-2025 Medic Mobile, Inc. <hello@medic.org>
361
369
 
362
370
  # License
363
371
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cht-conf",
3
- "version": "4.4.0",
3
+ "version": "4.5.0",
4
4
  "description": "Configure CHT deployments",
5
5
  "main": "./src/lib/main.js",
6
6
  "engines": {
@@ -35,20 +35,20 @@
35
35
  "homepage": "https://github.com/medic/cht-conf#readme",
36
36
  "dependencies": {
37
37
  "@medic/translation-checker": "^1.1.0",
38
- "@parcel/watcher": "^2.1.0",
38
+ "@parcel/watcher": "^2.5.1",
39
39
  "@xmldom/xmldom": "^0.8.10",
40
40
  "async-retry": "^1.3.3",
41
41
  "canonical-json": "0.0.4",
42
- "csv-parse": "^5.5.6",
42
+ "csv-parse": "^5.6.0",
43
43
  "dom-compare": "^0.6.0",
44
- "eslint": "^8.57.0",
45
- "eslint-webpack-plugin": "^2.7.0",
46
- "googleapis": "^140.0.1",
47
- "iso-639-1": "^3.1.2",
44
+ "eslint": "^8.57.1",
45
+ "eslint-webpack-plugin": "^4.2.0",
46
+ "googleapis": "^144.0.0",
47
+ "iso-639-1": "^3.1.5",
48
48
  "joi": "^17.13.3",
49
49
  "json-diff": "^1.0.6",
50
50
  "json-stringify-safe": "^5.0.1",
51
- "json2csv": "^4.5.4",
51
+ "json2csv": "^5.0.7",
52
52
  "mime-types": "^2.1.35",
53
53
  "minimist": "^1.2.8",
54
54
  "mkdirp": "^3.0.1",
@@ -64,34 +64,35 @@
64
64
  "redact-basic-auth": "^1.0.1",
65
65
  "request": "^2.88.2",
66
66
  "request-promise-native": "^1.0.9",
67
- "semver": "^7.6.3",
67
+ "semver": "^7.7.1",
68
68
  "svgo": "^3.3.2",
69
- "terser-webpack-plugin": "^5.3.10",
70
- "uuid": "^10.0.0",
71
- "webpack": "^5.93.0",
72
- "xpath": "0.0.33"
69
+ "terser-webpack-plugin": "^5.3.11",
70
+ "uuid": "^11.1.0",
71
+ "webpack": "^5.98.0",
72
+ "xpath": "0.0.34"
73
73
  },
74
74
  "devDependencies": {
75
- "@commitlint/cli": "^19.3.0",
76
- "@commitlint/config-conventional": "^19.2.2",
75
+ "@commitlint/cli": "^19.7.1",
76
+ "@commitlint/config-conventional": "^19.7.1",
77
+ "@medic/eslint-config": "^1.1.0",
77
78
  "@semantic-release/changelog": "^6.0.3",
78
79
  "@semantic-release/git": "^10.0.1",
79
- "body-parser": "^1.20.2",
80
- "chai": "^4.3.10",
81
- "chai-as-promised": "^7.1.1",
82
- "chai-exclude": "^2.1.0",
80
+ "body-parser": "^1.20.3",
81
+ "chai": "^4.5.0",
82
+ "chai-as-promised": "^7.1.2",
83
+ "chai-exclude": "^2.1.1",
83
84
  "chai-shallow-deep-equal": "^1.4.6",
84
85
  "chai-xml": "^0.4.1",
85
- "eslint-plugin-n": "^17.9.0",
86
- "express": "^4.19.2",
86
+ "eslint-plugin-n": "^17.15.1",
87
+ "express": "^4.21.2",
87
88
  "express-pouchdb": "^4.2.0",
88
- "fs-extra": "11.2.0",
89
- "husky": "^9.1.1",
90
- "mocha": "^10.7.0",
91
- "nyc": "^17.0.0",
89
+ "fs-extra": "11.3.0",
90
+ "husky": "^9.1.7",
91
+ "mocha": "^11.1.0",
92
+ "nyc": "^17.1.0",
92
93
  "pouchdb-adapter-memory": "^7.2.2",
93
94
  "rewire": "^7.0.0",
94
95
  "semantic-release": "^22.0.0",
95
- "sinon": "^18.0.0"
96
+ "sinon": "^19.0.2"
96
97
  }
97
98
  }
@@ -3,11 +3,11 @@
3
3
  /* eslint-disable no-console */
4
4
 
5
5
  const options = [
6
- '--instance', '--local', '--url', '--user',
7
- '--help', '--shell-completion', '--supported-actions',
8
- '--version', '--accept-self-signed-certs', '--skip-dependency-check',
9
- '--skip-git-check', '--skip-translation-check', '--force',
10
- ...require('../cli/supported-actions'),
6
+ '--instance', '--local', '--url', '--user',
7
+ '--help', '--shell-completion', '--supported-actions',
8
+ '--version', '--accept-self-signed-certs', '--skip-dependency-check',
9
+ '--skip-git-check', '--skip-translation-check', '--force',
10
+ ...require('../cli/supported-actions'),
11
11
  ];
12
12
 
13
13
  console.log(...options);
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs');
2
2
 
3
3
  const actions = fs.readdirSync(`${__dirname}/../fn`)
4
- .filter(name => name.endsWith('.js'))
5
- .map(name => name.substring(0, name.length - 3));
4
+ .filter(name => name.endsWith('.js'))
5
+ .map(name => name.substring(0, name.length - 3));
6
6
 
7
7
  module.exports = actions;
package/src/cli/usage.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const supportedActions = require('./supported-actions');
2
2
 
3
3
  module.exports = () => {
4
+ /* eslint-disable max-len */
4
5
  console.log(`
5
6
  ${bold('NAME')}
6
7
  cht - Configure your CHT instances
@@ -69,6 +70,7 @@ ${bold('OPTIONS')}
69
70
  --force
70
71
  CAN BE DANGEROUS! Passes yes to all commands and any where that would prompt to overwrite changes will overwrite automatically.
71
72
  `);
73
+ /* eslint-enable max-len */
72
74
  };
73
75
 
74
76
  const bold = text => `\x1b[1m${text}\x1b[0m`;
@@ -34,5 +34,5 @@ module.exports = {
34
34
  return formsList(db)
35
35
  .then(res => res.rows)
36
36
  .then(forms => Promise.all(forms.map(backup)));
37
- }
37
+ }
38
38
  };
@@ -16,10 +16,14 @@ const JSON_FILE_MATCHER = /^(.+)(\.json)$/;
16
16
  const configFileMatcher = (fileName) => {
17
17
  const jsFileMatchResult = fileName.match(JS_FILE_MATCHER);
18
18
  // the first element is always the whole matched string, then our first file name group
19
- if (jsFileMatchResult) return jsFileMatchResult[1];
19
+ if (jsFileMatchResult) {
20
+ return jsFileMatchResult[1];
21
+ }
20
22
 
21
23
  const jsonFileMatchResult = fileName.match(JSON_FILE_MATCHER);
22
- if (jsonFileMatchResult) return jsonFileMatchResult[1];
24
+ if (jsonFileMatchResult) {
25
+ return jsonFileMatchResult[1];
26
+ }
23
27
 
24
28
  return null;
25
29
  };
@@ -48,7 +52,9 @@ const compileAppSettingsForProject = async (projectDir, options) => {
48
52
  const oldTaskSchedulesPath = path.join(projectDir, 'tasks.json');
49
53
  if (fs.exists(oldTaskSchedulesPath)) {
50
54
  if (fs.exists(taskSchedulesPath)) {
51
- throw new Error(`You have both ${taskSchedulesPath} and ${oldTaskSchedulesPath}. Please remove one to continue!`);
55
+ throw new Error(
56
+ `You have both ${taskSchedulesPath} and ${oldTaskSchedulesPath}. Please remove one to continue!`
57
+ );
52
58
  }
53
59
  warn(`tasks.json file is deprecated. Please rename ${oldTaskSchedulesPath} to ${taskSchedulesPath}`);
54
60
  taskSchedulesPath = oldTaskSchedulesPath;
@@ -66,7 +72,10 @@ const compileAppSettingsForProject = async (projectDir, options) => {
66
72
  }
67
73
 
68
74
  if (!fs.exists(baseSettingsPath) && !fs.exists(appSettingsPath)) {
69
- throw new Error('No configuration defined please create a base_settings.json file in app_settings folder with the desired configuration');
75
+ throw new Error(
76
+ 'No configuration defined please create a base_settings.json file in app_settings folder ' +
77
+ 'with the desired configuration'
78
+ );
70
79
  }
71
80
  if (fs.exists(baseSettingsPath)) {
72
81
  // using modular config so should override anything already defined in app_settings.json
@@ -105,8 +114,10 @@ const compileAppSettingsForProject = async (projectDir, options) => {
105
114
  appSettings.assetlinks = assetlinks;
106
115
  }
107
116
  } else {
108
- warn(`app_settings.json file should not be edited directly.
109
- Please create a base_settings.json file in app_settings folder and move any manually defined configurations there.`);
117
+ warn(
118
+ `app_settings.json file should not be edited directly.
119
+ Please create a base_settings.json file in app_settings folder and move any manually defined configurations there.`
120
+ );
110
121
  appSettings = fs.readJson(appSettingsPath);
111
122
  }
112
123
  appSettings.contact_summary = await compilation.compileContactSummary(projectDir, options);
@@ -128,7 +139,9 @@ const compileAppSettingsForProject = async (projectDir, options) => {
128
139
 
129
140
  function applyTransforms(app_settings, inherited) {
130
141
  function doDelete(target, rules) {
131
- if (!Array.isArray(rules)) throw new Error('.delete should be an array');
142
+ if (!Array.isArray(rules)) {
143
+ throw new Error('.delete should be an array');
144
+ }
132
145
 
133
146
  rules.forEach(k => {
134
147
  const parts = k.split('.');
@@ -142,7 +155,9 @@ function applyTransforms(app_settings, inherited) {
142
155
  }
143
156
 
144
157
  function doReplace(target, rules) {
145
- if (typeof rules !== 'object') throw new Error('.replace should be an object');
158
+ if (typeof rules !== 'object') {
159
+ throw new Error('.replace should be an object');
160
+ }
146
161
 
147
162
  Object.keys(rules)
148
163
  .forEach(k => {
@@ -159,14 +174,22 @@ function applyTransforms(app_settings, inherited) {
159
174
  function doMerge(target, source) {
160
175
  Object.keys(target)
161
176
  .forEach(k => {
162
- if (Array.isArray(source[k])) target[k] = target[k].concat(source[k]);
163
- else if (typeof source[k] === 'object') doMerge(target[k], source[k]);
164
- else source[k] = target[k];
177
+ if (Array.isArray(source[k])) {
178
+ target[k] = target[k].concat(source[k]);
179
+ }
180
+ else if (typeof source[k] === 'object') {
181
+ doMerge(target[k], source[k]);
182
+ }
183
+ else {
184
+ source[k] = target[k];
185
+ }
165
186
  });
166
187
  }
167
188
 
168
189
  function doFilter(target, rules) {
169
- if (typeof rules !== 'object') throw new Error('.filter should be an object');
190
+ if (typeof rules !== 'object') {
191
+ throw new Error('.filter should be an object');
192
+ }
170
193
 
171
194
  Object.keys(rules)
172
195
  .forEach(k => {
@@ -177,11 +200,15 @@ function applyTransforms(app_settings, inherited) {
177
200
  parts.shift();
178
201
  }
179
202
 
180
- if (!Array.isArray(rules[k])) throw new Error('.filter values must be arrays!');
203
+ if (!Array.isArray(rules[k])) {
204
+ throw new Error('.filter values must be arrays!');
205
+ }
181
206
 
182
207
  Object.keys(t[parts[0]])
183
208
  .forEach(tK => {
184
- if (!rules[k].includes(tK)) delete t[parts[0]][tK];
209
+ if (!rules[k].includes(tK)) {
210
+ delete t[parts[0]][tK];
211
+ }
185
212
  });
186
213
  });
187
214
  }
@@ -12,12 +12,14 @@ module.exports = {
12
12
  promiseChain
13
13
  .then(() => info('Compressing PNG:', png, '…'))
14
14
  .then(() =>
15
- exec(['pngout-medic', `'${png}'`])
16
- .then(() => trace('Compressed', png))
17
- .catch(e => {
18
- if(e.status === 2) {
19
- info('Unable to compress further.');
20
- } else throw e;
21
- })),
22
- Promise.resolve())
15
+ exec(['pngout-medic', `'${png}'`])
16
+ .then(() => trace('Compressed', png))
17
+ .catch(e => {
18
+ if(e.status === 2) {
19
+ info('Unable to compress further.');
20
+ } else {
21
+ throw e;
22
+ }
23
+ })),
24
+ Promise.resolve())
23
25
  };
@@ -15,6 +15,9 @@ module.exports = {
15
15
  .then(() => svgo.optimize(fs.read(path), { path }))
16
16
  .then(result => fs.write(path, result.data))
17
17
  .then(() => trace('Compressed', path))
18
- .then(() => warn('Make sure you compare the content of your SVG files before merging to the default branch - sometimes optimising can change their contents!')),
19
- Promise.resolve())
18
+ .then(() => warn(
19
+ 'Make sure you compare the content of your SVG files before merging to the default branch ' +
20
+ '- sometimes optimising can change their contents!'
21
+ )),
22
+ Promise.resolve())
20
23
  };
@@ -67,10 +67,13 @@ const execute = async () => {
67
67
 
68
68
  const users = parseUsersData(fs.read(csvPath));
69
69
  const warnings = [];
70
- for (let user of users) {
70
+ for (const user of users) {
71
71
  const userInfo = await getUserInfo(user);
72
72
  if (userInfo && userInfo.warn) {
73
- warnings.push(`The user "${user.username}" would replicate ${userInfo.total_docs}, which is above the recommended limit of ${userInfo.limit}.`);
73
+ warnings.push(
74
+ `The user "${user.username}" would replicate ${userInfo.total_docs}, `
75
+ + `which is above the recommended limit of ${userInfo.limit}.`
76
+ );
74
77
  }
75
78
  }
76
79
  if (warnings.length) {
@@ -81,7 +84,7 @@ const execute = async () => {
81
84
  }
82
85
  }
83
86
 
84
- for (let user of users) {
87
+ for (const user of users) {
85
88
  info(`Creating user ${user.username}`);
86
89
  await api().createUser(user);
87
90
  }
@@ -56,23 +56,23 @@ const execute = () => {
56
56
  return fs.recurseFiles(csvDir)
57
57
  .filter(name => name.endsWith('.csv'))
58
58
  .reduce((promiseChain, csv) =>
59
- promiseChain
60
- .then(() => {
61
- info('Processing CSV file:', csv, '…');
62
-
63
- const nameParts = fs.path.basename(csv).split('.');
64
- const prefix = nameParts[0];
65
- switch(prefix) {
66
- case 'contact': return processContacts('contact', csv);
67
- case 'person': return processPersons(csv);
68
- case 'place': return processPlaces(csv);
69
- case 'report': return processReports(nameParts[1], csv);
70
- case 'users' : return processUsers(csv);
71
- default: throw new Error(`Unrecognised CSV type ${prefix} for file ${csv}`);
72
- }
73
- })
74
- .then(docs => addToModel(csv, docs)),
75
- Promise.resolve())
59
+ promiseChain
60
+ .then(() => {
61
+ info('Processing CSV file:', csv, '…');
62
+
63
+ const nameParts = fs.path.basename(csv).split('.');
64
+ const prefix = nameParts[0];
65
+ switch(prefix) {
66
+ case 'contact': return processContacts('contact', csv);
67
+ case 'person': return processPersons(csv);
68
+ case 'place': return processPlaces(csv);
69
+ case 'report': return processReports(nameParts[1], csv);
70
+ case 'users' : return processUsers(csv);
71
+ default: throw new Error(`Unrecognised CSV type ${prefix} for file ${csv}`);
72
+ }
73
+ })
74
+ .then(docs => addToModel(csv, docs)),
75
+ Promise.resolve())
76
76
 
77
77
  .then(() => model.references.forEach(updateRef))
78
78
  .then(() => model.exclusions.forEach(removeExcludedField))
@@ -132,16 +132,20 @@ const execute = () => {
132
132
  for(let i=0; i<cols.length; ++i) {
133
133
  const { col, val, reference, excluded } = parseColumn(cols[i], row[i]);
134
134
  setCol(doc, col, val);
135
- if(reference) model.references.push({
136
- doc: doc,
137
- matcher: reference,
138
- colVal: val,
139
- targetProperty: col,
140
- });
141
- if(excluded) model.exclusions.push({
142
- doc: doc,
143
- propertyName: col,
144
- });
135
+ if(reference) {
136
+ model.references.push({
137
+ doc: doc,
138
+ matcher: reference,
139
+ colVal: val,
140
+ targetProperty: col,
141
+ });
142
+ }
143
+ if(excluded) {
144
+ model.exclusions.push({
145
+ doc: doc,
146
+ propertyName: col,
147
+ });
148
+ }
145
149
  }
146
150
 
147
151
  return withId(doc);
@@ -157,25 +161,36 @@ const execute = () => {
157
161
  function setCol(doc, col, val) {
158
162
  const colParts = col.split('.');
159
163
 
160
- if(RESERVED_COL_NAMES.includes(colParts[0]))
164
+ if(RESERVED_COL_NAMES.includes(colParts[0])) {
161
165
  throw new Error(`Cannot set property defined by column '${col}' - this property name is protected.`);
166
+ }
162
167
 
163
168
  while(colParts.length > 1) {
164
169
  col = colParts.shift();
165
- if(!doc[col]) doc[col] = {};
170
+ if(!doc[col]) {
171
+ doc[col] = {};
172
+ }
166
173
  doc = doc[col];
167
174
  }
168
175
  doc[colParts[0]] = val;
169
176
  }
170
177
 
171
178
  function parseTimestamp(t) {
172
- if(isIntegerString(t)) return int(t);
173
- else return Date.parse(t);
179
+ if(isIntegerString(t)) {
180
+ return int(t);
181
+ }
182
+ else {
183
+ return Date.parse(t);
184
+ }
174
185
  }
175
186
 
176
187
  function parseBool(b) {
177
- if(isIntegerString(b)) return b !== '0';
178
- else return b.toLowerCase() === 'true';
188
+ if(isIntegerString(b)) {
189
+ return b !== '0';
190
+ }
191
+ else {
192
+ return b.toLowerCase() === 'true';
193
+ }
179
194
  }
180
195
 
181
196
  function calcRelTimestampInDays(b) {
@@ -206,7 +221,9 @@ function matchesType(type, doc) {
206
221
 
207
222
  function matchesWhereClause(where, doc, colVal) {
208
223
  const whereMatch = where.match(/^([\w-]+)=COL_VAL$/);
209
- if(!whereMatch) throw new Error(`Cannot interpret WHERE clause: ${where}`);
224
+ if(!whereMatch) {
225
+ throw new Error(`Cannot interpret WHERE clause: ${where}`);
226
+ }
210
227
 
211
228
  const [, propertyName] = whereMatch;
212
229
 
@@ -218,7 +235,9 @@ function removeExcludedField(exclusion) {
218
235
  }
219
236
 
220
237
  function parseColumn(rawCol, rawVal) {
221
- let val, reference, excluded = false;
238
+ let val;
239
+ let reference;
240
+ let excluded = false;
222
241
 
223
242
  const parts = rawCol.split(/[:>]/);
224
243
  const col = parts[0];
@@ -228,26 +247,28 @@ function parseColumn(rawCol, rawVal) {
228
247
  } else if(parts.length === 2) {
229
248
  const type = parts[1];
230
249
  switch(type) {
231
- case 'date': val = new Date(rawVal); break;
232
- case 'rel-date': val = (new Date(calcRelTimestampInDays(rawVal))).toISOString().substring(0, 10); break;
233
- case 'timestamp': val = parseTimestamp(rawVal); break;
234
- case 'rel-timestamp': val = calcRelTimestampInMilliseconds(rawVal); break;
235
- case 'int': val = int(rawVal); break;
236
- case 'bool': val = parseBool(rawVal); break;
237
- case 'string': val = rawVal; break;
238
- case 'float': val = Number.parseFloat(rawVal); break;
239
- case 'excluded': val = rawVal; excluded = true; break;
240
- default: {
241
- if(isReference(type)) {
242
- val = rawVal;
243
- reference = type;
244
- } else {
245
- throw new Error(`Unrecognised column type: ${type} for ${rawCol}`);
246
- }
250
+ case 'date': val = new Date(rawVal); break;
251
+ case 'rel-date': val = (new Date(calcRelTimestampInDays(rawVal))).toISOString().substring(0, 10); break;
252
+ case 'timestamp': val = parseTimestamp(rawVal); break;
253
+ case 'rel-timestamp': val = calcRelTimestampInMilliseconds(rawVal); break;
254
+ case 'int': val = int(rawVal); break;
255
+ case 'bool': val = parseBool(rawVal); break;
256
+ case 'string': val = rawVal; break;
257
+ case 'float': val = Number.parseFloat(rawVal); break;
258
+ case 'excluded': val = rawVal; excluded = true; break;
259
+ default: {
260
+ if(isReference(type)) {
261
+ val = rawVal;
262
+ reference = type;
263
+ } else {
264
+ throw new Error(`Unrecognised column type: ${type} for ${rawCol}`);
247
265
  }
248
266
  }
267
+ }
249
268
  } else {
250
- throw new Error(`Wrong number of parts in column definition: ${rawCol} (should be 1, 2 or 4, but found ${parts.length}).`);
269
+ throw new Error(
270
+ `Wrong number of parts in column definition: ${rawCol} (should be 1, 2 or 4, but found ${parts.length}).`
271
+ );
251
272
  }
252
273
  return { col:col, val:val, reference:reference, excluded:excluded };
253
274
  }
@@ -44,6 +44,7 @@ const parseExtraArgs = (projectDir, extraArgs = []) => {
44
44
 
45
45
  const bold = text => `\x1b[1m${text}\x1b[0m`;
46
46
  const usage = () => {
47
+ /* eslint-disable max-len */
47
48
  info(`
48
49
  ${bold('cht-conf\'s delete-contacts action')}
49
50
  When combined with 'upload-docs' this action recursively deletes a contact and all of their descendant contacts and data. ${bold('This operation is permanent. It cannot be undone.')}
@@ -61,4 +62,5 @@ ${bold('OPTIONS')}
61
62
  --docDirectoryPath=<path to stage docs>
62
63
  Specifies the folder used to store the documents representing the changes in hierarchy.
63
64
  `);
65
+ /* eslint-enable max-len */
64
66
  };
@@ -17,7 +17,10 @@ const jsonDocPath = (directoryPath, docID) => `${directoryPath}/${docID}.doc.jso
17
17
 
18
18
  // check to see if we should write/overwrite the file
19
19
  const overwriteFileCheck = (doc, args, overwriteAllFiles) => {
20
- return args.updateOfflineDocs || environment.force || overwriteAllFiles || !fs.exists(jsonDocPath(args.docDirectoryPath, doc._id));
20
+ return args.updateOfflineDocs
21
+ || environment.force
22
+ || overwriteAllFiles
23
+ || !fs.exists(jsonDocPath(args.docDirectoryPath, doc._id));
21
24
  };
22
25
 
23
26
  const saveJsonDoc = (doc, args) => {
@@ -62,19 +65,19 @@ const execute = () => {
62
65
  .filter(name => name.endsWith('.csv'))
63
66
  .reduce((promiseChain, csv) =>
64
67
  promiseChain
65
- .then(() => {
66
- info('Processing CSV file:', csv, '…');
68
+ .then(() => {
69
+ info('Processing CSV file:', csv, '…');
67
70
 
68
- const nameParts = fs.path.basename(csv).split('.');
69
- const prefix = nameParts[0];
70
- switch(prefix) {
71
+ const nameParts = fs.path.basename(csv).split('.');
72
+ const prefix = nameParts[0];
73
+ switch(prefix) {
71
74
  case 'contact': return processDocuments('contact', csv, getIDs(csv, prefix), db, args);
72
75
  case 'users': return processDocuments('user', csv, getIDs(csv, prefix), db, args);
73
76
  default: throw new Error(`Unrecognised CSV type ${prefix} for file ${csv}`);
74
- }
75
- })
76
- .then(docs => addToModel(csv, docs)),
77
- Promise.resolve())
77
+ }
78
+ })
79
+ .then(docs => addToModel(csv, docs)),
80
+ Promise.resolve())
78
81
 
79
82
  .then(() => model.exclusions.forEach(toDocs.removeExcludedField))
80
83
  .then(() => writeDocs(model.docs, args));
@@ -98,7 +101,7 @@ function getIDs(csv, docType) {
98
101
  const { rows, cols } = fs.readCsv(csv);
99
102
  const index = cols.indexOf(DOCUMENT_ID);
100
103
  if (index === -1){
101
- throw Error('missing "documentID" column.');
104
+ throw Error('missing "documentID" column.');
102
105
  }
103
106
 
104
107
  const idPrefix = docType === 'contact' ? '' : 'org.couchdb.user:';
@@ -5,8 +5,8 @@ module.exports = {
5
5
  requiresInstance: false,
6
6
  execute: async () => {
7
7
  await fetchFilesFromGoogleDrive(
8
- `${environment.pathToProject}/csvs-on-google-drive.json`,
9
- `${environment.pathToProject}/csv`,
10
- 'text/csv');
8
+ `${environment.pathToProject}/csvs-on-google-drive.json`,
9
+ `${environment.pathToProject}/csv`,
10
+ 'text/csv');
11
11
  }
12
12
  };
@@ -5,8 +5,8 @@ module.exports = {
5
5
  requiresInstance: false,
6
6
  execute: async () => {
7
7
  await fetchFilesFromGoogleDrive(
8
- `${environment.pathToProject}/forms-on-google-drive.json`,
9
- `${environment.pathToProject}/forms`,
10
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
8
+ `${environment.pathToProject}/forms-on-google-drive.json`,
9
+ `${environment.pathToProject}/forms`,
10
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
11
11
  }
12
12
  };