cht-conf 3.16.0 → 3.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [3.16.1](https://github.com/medic/cht-conf/compare/v3.16.0...v3.16.1) (2023-03-18)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **#524:** Don't display usage() information for commands which run successfully ([5b265e7](https://github.com/medic/cht-conf/commit/5b265e753d035b326ff8d6bac0a9aabc923048fb)), closes [#524](https://github.com/medic/cht-conf/issues/524) [#524](https://github.com/medic/cht-conf/issues/524)
7
+
1
8
  # [3.16.0](https://github.com/medic/cht-conf/compare/v3.15.3...v3.16.0) (2023-03-09)
2
9
 
3
10
 
package/Dockerfile CHANGED
@@ -1,23 +1,29 @@
1
- FROM ubuntu:18.04
2
- MAINTAINER DevOps "devops@medicmobile.org"
1
+ FROM node:16-slim
3
2
 
3
+ RUN apt update \
4
+ && apt install --no-install-recommends -y \
5
+ build-essential \
6
+ chromium \
7
+ curl \
8
+ git \
9
+ openssh-client \
10
+ python3-pip \
11
+ python3-setuptools \
12
+ python3-wheel \
13
+ xsltproc \
14
+ # Remove chromium to save space. We only installed it to get the transitive dependencies that are needed
15
+ # when running tests with puppeteer. (puppeteer-chromium-resolver will always download its own version of chromium)
16
+ && apt remove -y chromium \
17
+ && rm -rf /var/lib/apt/lists/*
4
18
 
5
- RUN echo "==> Installing Python dependencies" && \
6
- apt-get update -y && \
7
- apt-get install --no-install-recommends -y -q \
8
- build-essential python-setuptools \
9
- python python-pip python-dev \
10
- libffi-dev libssl-dev \
11
- libxml2-dev libxslt1-dev zlib1g-dev \
12
- git wget python-wheel curl
19
+ RUN python3 -m pip install git+https://github.com/medic/pyxform.git@medic-conf-1.17#egg=pyxform-medic
13
20
 
14
- RUN echo "====> Installing cht-conf python stuff" &&\
15
- python -m pip install git+https://github.com/medic/pyxform.git@medic-conf-1.17#egg=pyxform-medic
21
+ RUN npm install -g cht-conf
16
22
 
17
- RUN curl -sL https://deb.nodesource.com/setup_12.x -o nodesource_setup.sh
18
- RUN bash nodesource_setup.sh
19
- RUN apt-get install -y nodejs
23
+ # Using the 1000:1000 user is recommended for VSCode dev containers
24
+ # https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user
25
+ USER node
20
26
 
21
- RUN npm install -g cht-conf
27
+ WORKDIR /workdir
22
28
 
23
- CMD ["tail", "-f", "/dev/null"]
29
+ ENTRYPOINT ["cht"]
package/README.md CHANGED
@@ -32,22 +32,75 @@ As Administrator:
32
32
 
33
33
  ### Docker
34
34
 
35
- **NB** - `cht-conf` in a Docker container should only be used if you're familiar with running containers. You will have to copy or mount your config files into the container in order to be able to use the `cht` call to interact with a remote CHT instance.
35
+ CHT Conf can also be run from within a Docker container. This is useful if you are already familiar with Docker and do not wish to configure the various dependencies required for developing CHT apps on your local machine. The necessary dependencies are pre-packaged in the Docker image.
36
36
 
37
- Build and then run the `cht-conf` container:
37
+ #### Building the image
38
38
 
39
- docker build -t cht-conf:v0 .
40
- docker run -d --name cht-conf cht-conf:v0
39
+ Install [Docker](https://www.docker.com/). If you are using Windows, you also need to enable the [Windows Subsystem for Linux (WSL2)](https://learn.microsoft.com/en-us/windows/wsl/install) to perform the following steps.
41
40
 
42
- You now have a container running in the background called `cht-conf`. You can get a shell on this container with:
41
+ Using the terminal (or the WLS shell on Windows: _Start > wsl_), run the following commands to create a new project directory and build the Docker image:
43
42
 
44
- docker exec -it cht-conf /bin/bash
43
+ ```shell
44
+ mkdir -p ~/cht-project
45
+ cd ~/cht-project
46
+ curl https://raw.githubusercontent.com/medic/cht-conf/main/Dockerfile > Dockerfile
47
+ docker build -t cht-ide .
48
+ ```
45
49
 
46
- And then you can run the utility with `cht`.
50
+ #### Using the image
47
51
 
48
- When you are done with the container, you can stop it with:
52
+ The resulting Docker image can be used as a [VSCode Development Container](https://code.visualstudio.com/docs/devcontainers/containers) (easiest) or as a standalone Docker utility.
49
53
 
50
- docker stop cht-conf
54
+ ##### VSCode Development Container
55
+
56
+ If you want to develop CHT apps with VSCode, you can use the Docker image as a Development Container. This will allow you to use the `cht-conf` utility and its associated tech stack from within VSCode (without needing to install dependencies like NodeJS on your host system).
57
+
58
+ [Install VSCode](https://code.visualstudio.com/) if you do not have it already.
59
+
60
+ Using the terminal (or the WLS shell on Windows: _Start > wsl_), run the following commands from within your project directory (created above) to download the `.devcontainer.json` config file, install the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), and open the project directory in VSCode:
61
+
62
+ ```shell
63
+ cd ~/cht-project
64
+ curl -s https://raw.githubusercontent.com/medic/cht-conf/main/devcontainer.cht-ide/.devcontainer.json > .devcontainer.json
65
+ code --install-extension ms-vscode-remote.remote-containers
66
+ code -n .
67
+ ```
68
+
69
+ When opening VSCode, you may be prompted with the question:
70
+
71
+ > **Do you trust the authors of the files in this folder?**
72
+
73
+ Choose, "Yes, I trust the authors".
74
+
75
+ Open the Command Palette in VSCode (_Ctrl+Shift+P_ or _Cmd+Shift+P_) and select `Reopen in Container`. This will open your workspace inside a container based on the `cht-ide` image. You can use the `cht` commands by opening a terminal in VSCode (_Ctrl+Shift+\`_ or _Cmd+Shift+\`_). If prompted "Do you trust the authors..." choose "Trust Folder & Continue".
76
+
77
+ Run the following command in the VSCode terminal to bootstrap your new CHT project:
78
+
79
+ ```shell
80
+ cht initialise-project-layout
81
+ ```
82
+
83
+ ###### Terminal environment
84
+
85
+ When opening a terminal in VSCode in a development container, the terminal will be running on the _container environment_ by default. This is what gives you access to the various `cht` commands. However, this also means you do NOT have access, within the default VSCode terminal, to commands from your _host environment_. So, for example, you cannot run `docker` commands since Docker is not installed inside the container.
86
+
87
+ To open a terminal running on you _host environment_ in VSCode, open the Command Palette (_Ctrl+Shift+P_ or _Cmd+Shift+P_) and select `Create New Integrated Terminal (Local)`. Just remember that you will NOT be able to run `cht` commands from this terminal since cht-conf is not installed on your host machine.
88
+
89
+ ##### Standalone Docker utility
90
+
91
+ If you are not using VSCode, you can use the Docker image as a standalone utility from the command line. Instead of using the `cht ...` command, you can run `docker run -it --rm -v "$PWD":/workdir cht-ide ...`. This will create an ephemeral container with access to your current directory that will run the given cht command. (Do not include the `cht` part of the command, just your desired actions/parameters.)
92
+
93
+ Run the following command inside the project directory to bootstrap your new CHT project:
94
+
95
+ ```shell
96
+ docker run -it --rm -v "$PWD":/workdir cht-ide initialise-project-layout
97
+ ```
98
+
99
+ #### Note on connecting to a local CHT instance
100
+
101
+ When using `cht-conf` within a Docker container to connect to a CHT instance that is running on your local machine (e.g. a development instance), you cannot use the `--local` flag or `localhost` in your `--url` parameter (since these will be interpreted as "local to the container").
102
+
103
+ It is recommended to run a local CHT instance using the [CHT Docker Helper script](https://docs.communityhealthtoolkit.org/apps/guides/hosting/4.x/app-developer/). You can connect to the resulting `...my.local-ip.co` URL from the Docker container (or the VSCode terminal). (Just make sure the port your CHT instance is hosted on is not blocked by your firewall).
51
104
 
52
105
  ## Bash completion
53
106
 
@@ -0,0 +1,6 @@
1
+ {
2
+ "image": "cht-ide",
3
+ // Required to run tests with Puppeteer
4
+ // https://pptr.dev/guides/docker
5
+ "capAdd": ["SYS_ADMIN"]
6
+ }
@@ -0,0 +1,3 @@
1
+ # Dev Container Resources for CHT Apps
2
+
3
+ This directory contains resources useful for developing CHT applications with [Dev Containers](https://containers.dev/).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cht-conf",
3
- "version": "3.16.0",
3
+ "version": "3.16.1",
4
4
  "description": "Configure CHT deployments",
5
5
  "main": "./src/lib/main.js",
6
6
  "engines": {
@@ -1,21 +1,20 @@
1
1
  const userPrompt = require('./user-prompt');
2
2
  const url = require('url');
3
- const usage = require('../cli/usage');
4
3
 
5
4
  const emoji = require('./emoji');
6
- const { error, info } = require('./log');
5
+ const { info } = require('./log');
6
+ const usage = require('../cli/usage');
7
7
 
8
8
  const getApiUrl = (cmdArgs, env = {}) => {
9
9
  const specifiedModes = [cmdArgs.local, cmdArgs.instance, cmdArgs.url, cmdArgs.archive].filter(mode => mode);
10
10
  if (specifiedModes.length !== 1) {
11
- error('Require exactly one of these parameter: --local --instance --url --archive');
12
11
  usage();
13
- return false;
12
+ throw Error('One of these parameters is required: --local --instance --url --archive');
14
13
  }
15
14
 
16
15
  if (cmdArgs.user && !cmdArgs.instance) {
17
- error('The --user switch can only be used if followed by --instance');
18
- return false;
16
+ usage();
17
+ throw Error('The --user switch must be accompanied with --instance');
19
18
  }
20
19
 
21
20
  let instanceUrl;
@@ -26,8 +25,7 @@ const getApiUrl = (cmdArgs, env = {}) => {
26
25
  // See ./archiving-db.js
27
26
  instanceUrl = parseLocalUrl(env.COUCH_URL);
28
27
  if (instanceUrl.hostname !== 'localhost') {
29
- error(`You asked to configure localhost, but the COUCH_URL env var is set to '${instanceUrl.hostname}'. This may be a remote server.`);
30
- return false;
28
+ throw Error(`--local was specified but COUCH_URL env var is set to '${instanceUrl.hostname}'. Please use --url for remote servers.`);
31
29
  }
32
30
  } else if (cmdArgs.instance) {
33
31
  const password = userPrompt.question(`${emoji.key} Password: `, { hideEchoBack: true });
package/src/lib/main.js CHANGED
@@ -134,71 +134,16 @@ module.exports = async (argv, env) => {
134
134
  //
135
135
  // Build up actions
136
136
  //
137
- let actions = cmdArgs._;
138
- if (actions.length) {
139
- const unsupported = actions.filter(a => !supportedActions.includes(a));
140
- if(unsupported.length) {
141
- throw new Error(`Unsupported action(s): ${unsupported.join(' ')}`);
142
- }
143
- } else {
144
- actions = !cmdArgs.archive ? defaultActions : defaultArchiveActions;
145
- }
146
-
147
- if (cmdArgs['skip-git-check']) {
148
- actions = actions.filter(a => a !== 'check-git');
149
- }
150
-
151
137
  const skipValidate = cmdArgs['skip-validate'];
152
- if(skipValidate) {
153
- warn('Skipping all form validation.');
154
- const validateActions = [
155
- 'validate-app-forms',
156
- 'validate-collect-forms',
157
- 'validate-contact-forms',
158
- 'validate-training-forms',
159
- ];
160
- actions = actions.filter(action => !validateActions.includes(action));
161
- } else {
162
- const addFormValidationIfNecessary = (formType) => {
163
- const updateFormsIndex = actions.indexOf(`upload-${formType}-forms`);
164
- if (updateFormsIndex >= 0 && actions.indexOf(`validate-${formType}-forms`) < 0) {
165
- actions.splice(updateFormsIndex, 0, `validate-${formType}-forms`);
166
- }
167
- };
168
- addFormValidationIfNecessary('app');
169
- addFormValidationIfNecessary('collect');
170
- addFormValidationIfNecessary('contact');
171
- addFormValidationIfNecessary('training');
172
- }
173
-
174
- actions = actions.map(actionName => {
175
- const action = require(`../fn/${actionName}`);
176
-
177
- if (typeof action.execute !== 'function') {
178
- throw new Error(`${actionName} has not been implemented correctly: no 'execute' function`);
179
- }
180
-
181
- if (!Object.hasOwnProperty.call(action, 'requiresInstance')) {
182
- action.requiresInstance = true;
183
- }
184
-
185
- action.name = actionName;
186
-
187
- return action;
188
- });
138
+ const actions = buildActions(cmdArgs, skipValidate);
189
139
 
190
140
  //
191
141
  // Initialize the environment
192
142
  //
193
143
  const projectName = fs.path.basename(pathToProject);
194
144
 
195
- const apiUrl = getApiUrl(cmdArgs, env);
196
145
  const requiresInstance = actions.some(action => action.requiresInstance);
197
- if (requiresInstance) {
198
- if (!apiUrl) {
199
- throw new Error('Failed to obtain a url to the API');
200
- }
201
- }
146
+ const apiUrl = requiresInstance && getApiUrl(cmdArgs, env);
202
147
 
203
148
  let extraArgs = cmdArgs['--'];
204
149
  if (!extraArgs.length) {
@@ -252,5 +197,60 @@ module.exports = async (argv, env) => {
252
197
  }
253
198
  };
254
199
 
200
+ function buildActions(cmdArgs, skipValidate) {
201
+ let actions = cmdArgs._;
202
+ if (actions.length) {
203
+ const unsupported = actions.filter(a => !supportedActions.includes(a));
204
+ if (unsupported.length) {
205
+ usage();
206
+ throw new Error(`Unsupported action(s): ${unsupported.join(' ')}`);
207
+ }
208
+ } else {
209
+ actions = !cmdArgs.archive ? defaultActions : defaultArchiveActions;
210
+ }
211
+
212
+ if (cmdArgs['skip-git-check']) {
213
+ actions = actions.filter(a => a !== 'check-git');
214
+ }
215
+
216
+ if(skipValidate) {
217
+ warn('Skipping all form validation.');
218
+ const validateActions = [
219
+ 'validate-app-forms',
220
+ 'validate-collect-forms',
221
+ 'validate-contact-forms',
222
+ 'validate-training-forms',
223
+ ];
224
+ actions = actions.filter(action => !validateActions.includes(action));
225
+ } else {
226
+ const addFormValidationIfNecessary = (formType) => {
227
+ const updateFormsIndex = actions.indexOf(`upload-${formType}-forms`);
228
+ if (updateFormsIndex >= 0 && actions.indexOf(`validate-${formType}-forms`) < 0) {
229
+ actions.splice(updateFormsIndex, 0, `validate-${formType}-forms`);
230
+ }
231
+ };
232
+ addFormValidationIfNecessary('app');
233
+ addFormValidationIfNecessary('collect');
234
+ addFormValidationIfNecessary('contact');
235
+ addFormValidationIfNecessary('training');
236
+ }
237
+
238
+ return actions.map(actionName => {
239
+ const action = require(`../fn/${actionName}`);
240
+
241
+ if (typeof action.execute !== 'function') {
242
+ throw new Error(`${actionName} has not been implemented correctly: no 'execute' function`);
243
+ }
244
+
245
+ if (!Object.hasOwnProperty.call(action, 'requiresInstance')) {
246
+ action.requiresInstance = true;
247
+ }
248
+
249
+ action.name = actionName;
250
+
251
+ return action;
252
+ });
253
+ }
254
+
255
255
  // Exists for generic mocking purposes
256
256
  const executeAction = action => action.execute();