@xh/hoist 76.0.0-SNAPSHOT.1755386207928 → 76.0.0-SNAPSHOT.1755394070476

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.
@@ -9,16 +9,17 @@ at the build process.
9
9
 
10
10
  **_At a high level, the build process:_**
11
11
 
12
- - Builds the Grails back-end via Gradle, producing a WAR file.
13
- - Builds the JS front-end via Webpack, producing a set of production ready client assets.
14
- - Copies both outputs into a pair of Docker containers and publishes those containers as the
15
- end-product of the build.
16
- - Deploys the new images, immediately or in a later step.
12
+ - Builds the Grails back-end via Gradle, producing a WAR file.
13
+ - Builds the JS front-end via Webpack, producing a set of production ready client assets.
14
+ - Copies both outputs into a pair of Docker containers and publishes those containers as the
15
+ end-product of the build.
16
+ - Deploys the new images, immediately or in a later step.
17
17
 
18
18
  Hoist does not mandate the use of any particular CI system, although we recommend and use
19
19
  [Jetbrains Teamcity](https://www.jetbrains.com/teamcity/) across multiple project deployments. The
20
20
  examples in this section do reference some Teamcity terms (although these concepts are very general
21
- and applicable to any common CI system).
21
+ and applicable to any common CI system). In addition to TC, Hoist applications are currently built
22
+ and deployed using GitHub Actions and Gitlab pipelines.
22
23
 
23
24
  Hoist also does not require the use of Docker or containerization in general, nor does it rely on
24
25
  any particular container orchestration technology (e.g. Kubernetes). That said, the move towards
@@ -29,20 +30,21 @@ to bundle up and somewhat abstract away the two-part nature of full-stack Hoist
29
30
  assigned to your application. This is a short, camelCased variant of the longer `appName` and is set
30
31
  within the application source code via both the Gradle and Webpack configs.
31
32
 
32
- ### 1\) Setup/Prep
33
+ ## Setup/Prep
33
34
 
34
- #### 1.1) Refresh and Lint JS Client
35
+ ### Refresh and Lint JS Client
35
36
 
36
37
  We do this first to fail fast if the client code doesn't pass the linter checks, which is relatively
37
38
  common. This step could also run any preflight unit tests, etc. should you be diligent enough to
38
- have them.
39
+ have them. The below assumes `yarn` is used for the project's package manager, sub with `npm` if
40
+ using that instead.
39
41
 
40
42
  ```bash
41
43
  yarn
42
44
  yarn lint
43
45
  ```
44
46
 
45
- #### 1.2) Set Gradle project name (optional)
47
+ ### Set Gradle project name (optional)
46
48
 
47
49
  It’s best to be explicit with Gradle about the name of the project. By default it uses the name of
48
50
  the containing directory, which in a CI build is probably a random hash.
@@ -73,9 +75,9 @@ As a workaround, we can have the build system take the app name (we use a projec
73
75
  echo "rootProject.name = \"%appCode%\"" > settings.gradle
74
76
  ```
75
77
 
76
- ### 2\) Server and Client Builds
78
+ ## Server and Client Builds
77
79
 
78
- #### 2.1) Build Grails server WAR with Gradle
80
+ ### Build Grails server WAR with Gradle
79
81
 
80
82
  This step calls into a `build.gradle` script checked in with each project to build the Grails
81
83
  server-side into a WAR.
@@ -83,11 +85,11 @@ server-side into a WAR.
83
85
  This step takes an application version as well as an optional build tag, both of which are baked
84
86
  into the build:
85
87
 
86
- - `xhAppVersion` - an x.y.z version for releases, or `x.y-SNAPSHOT` for transient builds.
87
- - `xhAppBuild` - this is an optional arg that gets baked into the server and client and exposed as a
88
- variable for display in the built-in Hoist admin client. We use it to pass a git commit hash,
89
- which then provides another way to cross-reference exactly what snapshot of the codebase was used
90
- to build any given running application.
88
+ - `xhAppVersion` - an x.y.z version for releases, or `x.y-SNAPSHOT` for transient builds.
89
+ - `xhAppBuild` - this is an optional arg that gets baked into the server and client and exposed as a
90
+ variable for display in the built-in Hoist admin client. We use it to pass a git commit hash,
91
+ which then provides another way to cross-reference exactly what snapshot of the codebase was used
92
+ to build any given running application.
91
93
 
92
94
  Both version and build can be left unspecified, in which case the version will default to the
93
95
  version specified within the app’s `gradle.properties` file (the build tag is nullable). Our
@@ -108,18 +110,21 @@ project and used according to Gradle best practices.
108
110
 
109
111
  In both cases, the output is a `appCode-appVersion.war` file within `/build/libs`.
110
112
 
111
- #### 2.2) Build JS Client with Webpack
113
+ ### Build TS Client with Webpack
112
114
 
113
- This step builds all the client-side assets (JS/CSS/static resources) with Webpack, taking the
115
+ This step builds all the client-side assets (TS/CSS/static resources) with Webpack, taking the
114
116
  source and dependencies and producing concatenated, minified, and hashed files suitable for serving
115
- to browsers. We use `yarn` as our package manager / runner tool.
117
+ to browsers.
118
+
119
+ In the example below, we use `yarn` as our package manager / runner tool.
116
120
 
117
121
  This step takes several arguments that are passed via a script in `package.json` to Webpack. Each
118
122
  project has a `webpack.config.js` file checked into the root of its `client-app` directory that
119
123
  accepts any args and runs them through a script provided by
120
124
  [hoist-dev-utils](https://github.com/xh/hoist-dev-utils/blob/master/configureWebpack.js) to produce
121
- a fully-based Webpack configuration object. The appVersion and appBuild params, detailed above, are
122
- the most common options passed in at build-time.
125
+ a fully-based Webpack configuration object. See that project for additional details.
126
+
127
+ The appVersion and appBuild params, detailed above, are the most common options set at build-time.
123
128
 
124
129
  An example Teamcity command line runner. ⚠️ Note this must run with `client-app` as its working
125
130
  directory:
@@ -136,9 +141,9 @@ yarn build --env.appVersion=$appVersion --env.appBuild=$appBuild
136
141
 
137
142
  The output is a set of files within `/client-app/build/` .
138
143
 
139
- ### 3\) Docker images
144
+ ## Docker Container images
140
145
 
141
- 🐳 The primary outputs of the overall build process are a pair of Docker containers, one for the
146
+ The primary outputs of the overall build process are a pair of Docker containers, one for the
142
147
  Grails server and one for the client JS assets. These include the build assets and are tagged with
143
148
  the desired version, making them (as a pair) a complete and deployable instance of the application.
144
149
 
@@ -148,30 +153,32 @@ both the server and client containers. Both can be based on
148
153
  [those](https://github.com/xh/xh-tomcat) [images](https://github.com/xh/xh-nginx) will show that
149
154
  they are very thin layers on top of the official Tomcat and nginx images on Docker Hub.
150
155
 
151
- #### 3.1) Build and Publish Tomcat Docker image
156
+ ### Build and Publish Tomcat Docker image
152
157
 
153
158
  The Grails server component is deployed within a Tomcat container. The app should have a minimal
154
159
  `/docker/tomcat/Dockerfile` (checked into source control) such as:
155
160
 
156
161
  ```dockerfile
157
- FROM xhio/xh-tomcat:latest
162
+ # Update the tag below to a fixed version for stability, or next to get a regularly updated build
163
+ FROM xhio/xh-tomcat:next-jdk17
158
164
  COPY setenv.sh bin/
159
165
  COPY *.war webapps/ROOT.war
160
166
  ```
161
167
 
162
- The `setenv.sh` referenced here can also be checked in with the app project and used to set
163
- environment variables / Java Opts required by Tomcat. This typically contains a reasonable `Xmx`
164
- (max JVM heap) value for the app and a pointer to an “instance config” file used by Hoist apps to
165
- bootstrap themselves with DB credentials and other low-level configuration required at startup. By
166
- convention we place this file within a Docker volume that’s mounted to `/appCode` within each
167
- container
168
+ ------
168
169
 
169
- All this means that a `/docker/tomcat/setenv.sh` typically looks like:
170
+ #### Optional `setenv.sh`
171
+
172
+ The optional `setenv.sh` referenced here can be checked in with the app project and used to set
173
+ environment variables / Java Opts required by Tomcat. This typically contains a reasonable `Xmx`
174
+ (max JVM heap) value for the app, either hard-coded or referencing a CI param or env variable:
170
175
 
171
176
  ```bash
172
- export JAVA_OPTS="$JAVA_OPTS -Xmx2G -Dio.xh.hoist.instanceConfigFile=/appCode/conf.yml"
177
+ export JAVA_OPTS="$JAVA_OPTS -Xmx2G"
173
178
  ```
174
179
 
180
+ ------
181
+
175
182
  That leaves the build with the job of generating a suitable tag for the container, running the
176
183
  Docker build, and then pushing to an appropriate (likely internal) Docker registry. The container
177
184
  tag should include the appCode + `-tomcat` to indicate that this is the Grails-side container.
@@ -203,28 +210,33 @@ rm *.war
203
210
  exit $ret
204
211
  ```
205
212
 
206
- #### 3.2) Build and Publish nginx Docker image
213
+ ### Build and Publish nginx Docker image
207
214
 
208
215
  The static JS resources are deployed within an nginx container. The app should have a minimal
209
216
  `/docker/nginx/Dockerfile/ ` (checked into source control) such as:
210
217
 
211
218
  ```dockerfile
212
- FROM xhio/xh-nginx:latest
213
- COPY app.conf $/XH_NGINX_CONFIG_PATH/
214
- COPY build/ $/XH_NGINX_CONTENT_PATH/
219
+ # Update the tag below to a fixed version for stability, or next to get a regularly updated build
220
+ FROM xhio/xh-nginx:next
221
+ COPY app.conf $XH_NGINX_CONFIG_PATH/
222
+ COPY build/ $XH_NGINX_CONTENT_PATH/
215
223
  ```
216
224
 
217
- Note that the `$XH` environment variables are set for convenience within the `xh-nginx` base image
225
+ Note that the `$XH_` environment variables are set for convenience within the `xh-nginx` base image
218
226
  Dockerfile.
219
227
 
220
- The `app.conf` referenced here is an app-specific nginx configuration that should be checked in
228
+ ------
229
+
230
+ #### About the `app.conf` nginx configuration file
231
+
232
+ The `app.conf` referenced above is an app-specific nginx configuration that should be checked in
221
233
  alongside the Dockerfile. It should setup the available routes to serve each bundled client app,
222
- configure SSL certificates if needed, do any required redirects, and (importantly) include a _proxy
223
- configuration_ to pass traffic through from the nginx container to the Tomcat container. Hoist
224
- deploys typically bind only the nginx ports to the host machine, then link the nginx and Tomcat
225
- containers together via Docker so there’s a single point of entry (nginx) for incoming requests.
226
- This means that no CORS or further, external proxy configuration is required to have the
227
- nginx-dosted client communicate with its Tomcat back-end.
234
+ handle any required redirects, and (importantly) include a _proxy configuration_ to pass traffic
235
+ through from the nginx container to the Tomcat container. Hoist deploys typically bind only the
236
+ nginx ports to the host machine, then link the nginx and Tomcat containers together via Docker/k8s
237
+ so there’s a single point of entry (nginx) for incoming requests. This means that no CORS or
238
+ further, external proxy configuration is required to have the nginx-hosted client communicate with
239
+ its Tomcat back-end.
228
240
 
229
241
  While the exact content of the `app.conf` file will vary depending on the app, a representative
230
242
  example is below:
@@ -232,20 +244,19 @@ example is below:
232
244
  ```
233
245
  server {
234
246
  server_name localhost;
235
- include includes/xh-secure-redirect.conf;
236
- }
237
-
238
- server {
239
- server_name localhost;
240
- listen 443 ssl;
247
+ listen 80;
241
248
  root /usr/share/nginx/html;
242
249
 
243
- ssl_certificate /appCode/ssl/appCode.crt;
244
- ssl_certificate_key /appCode/ssl/appCode.pem;
250
+ # NOT included here - CSP and other security-related headers. See Toolbox for recommendations,
251
+ # ensuring you tailor to your particular deployment environment and requirements.
245
252
 
246
- # Redirect root to /app/
253
+ # Redirect root to platform-appropriate default client app - either app or mobile.
247
254
  location = / {
248
- return 301 $scheme://$host/app/;
255
+ if ($is_mobile) {
256
+ return 302 https://$host/mobile/;
257
+ }
258
+
259
+ return 302 https://$host/app/;
249
260
  }
250
261
 
251
262
  # Static JS/CSS/etc assets not matching a more specific selector below
@@ -253,60 +264,60 @@ server {
253
264
  expires $expires;
254
265
  }
255
266
 
256
- # App entry points - ensure trailing slash, match or fallback to index for sub-routes
257
- location = /admin {
258
- return 301 $uri/;
259
- }
260
-
261
- location /admin/ {
262
- try_files $uri /admin/index.html;
267
+ # Entry points for all of this project's SPWA clients
268
+ location ~ ^/(admin|app|mobile)/?$ {
269
+ # Add trailing slash if not present. Use explicit redirect w/leading https as nginx is
270
+ # behind a load balancer and will first redirect to http otherwise, resulting in an excess
271
+ # redirect with the load balancer sending the browser back to https.
272
+ if ($request_uri ~ ^/(admin|app|mobile)$) {
273
+ return 301 https://$host$uri/;
274
+ }
275
+
276
+ # When at the correct path, check if the requested path exists as a file - if so, serve it.
277
+ # If not, serve index.html - this allows app routing to work - e.g. /admin/general/config
278
+ # will load /admin/index.html and allow the client app code to interpret the rest.
279
+ try_files $uri /$1/index.html;
263
280
  expires $expires;
264
281
  }
265
282
 
266
- location = /app {
267
- return 301 $uri/;
268
- }
269
-
270
- location /app/ {
271
- try_files $uri /app/index.html;
272
- expires $expires;
273
- }
274
-
275
- # Proxy to Grails back-end - appCode-tomcat is defined by Docker (e.g. via link)
283
+ # Proxy to Grails back-end, accessible to nginx on localhost as per container deployment.
276
284
  location /api/ {
277
- proxy_pass http://appCode-tomcat:8080/;
285
+ proxy_pass http://localhost:8080/;
278
286
  include includes/xh-proxy.conf;
287
+ include includes/xh-hardeners.conf;
279
288
  }
280
289
  }
281
290
  ```
282
291
 
283
292
  **_Note that this example configuration:_**
284
293
 
285
- - Uses `appCode` as a placeholder - use the same code as configured in the app’s server and client
286
- builds!
287
- - Calls several optional nginx config includes, sourced from the base `xh-nginx` image. The base
288
- image also copies in [an overall config](https://github.com/xh/xh-nginx/blob/master/xh.conf) that
289
- enables gzip compression and sets the `$expires` variable referenced above.
290
- - Redirects insecure requests to HTTPS on port 443 and terminates SSL itself, using certificates
291
- sourced from `/appCode/ssl` - the conventional location for Hoist apps to store certs and keys
292
- within an attached Docker volume.
293
- - Sets up locations for each client-app entry point / bundle - here we are shipping two JS apps with
294
- this container: `app` - the business-user facing app itself - and `admin` - the built-in Hoist
295
- Admin console. Apps might have other entry points, such as `mobile` or other more specific
296
- bundles.
297
- - Uses the `try_files` directive to attempt to service requests at sub-paths by handing back asset
298
- files if they exist, but otherwise falling back to `index.html` within that path. This allows for
299
- the use of HTML5 “pushState” routing, where in-app routes are written to the URL without the use
300
- of a traditional `#` symbol (e.g. <http://host/app/details/123>).
301
- - Creates a proxy endpoint at `/api/` to pass traffic through to the Tomcat back-end. This path is
302
- expected by the JS client, which will automatically prepend it to the path of any local/relative
303
- Ajax requests. This can be customized if needed on the client by adjusting the `baserUrl` param
304
- passed to `configureWebpack()`.
294
+ - Uses nginx config includes sourced from the base `xh-nginx` image. The base image also copies
295
+ in [an overall config](https://github.com/xh/xh-nginx/blob/master/xh.conf) that enables gzip
296
+ compression and sets the `$expires` variable referenced above.
297
+ - Assumes a load balancer / ingress is handling SSL termination and that the nginx container is not
298
+ directly exposed to the internet. While uncommon for Hoist apps, nginx can handle SSL if needed.
299
+ - Sets up locations for three client-app entry points / bundles: `app`, `admin`, and `mobile`, with
300
+ `mobile` being a dedicated app for mobile clients. Not all apps will have these three - some might
301
+ lack a mobile client entirely, or ship other dedicated client apps.
302
+ - Uses the `try_files` directive to attempt to service requests at sub-paths by handing back asset
303
+ files if they exist, but otherwise falling back to `index.html` within that path. This allows for
304
+ the use of HTML5 “pushState” routing, where in-app routes are written to the URL without the use
305
+ of a traditional `#` symbol (e.g. <http://host/app/details/123>).
306
+ - Creates a proxy endpoint at `/api/` to pass traffic through to the Tomcat back-end, which here is
307
+ expected to be reachable from nginx via `localhost`.
308
+ - The `/api/ `path is expected by the JS client, which will automatically prepend it to the path
309
+ of any local/relative fetch requests. This can be customized if needed on the client by
310
+ adjusting the `baserUrl` param passed to `configureWebpack()`.
311
+ - The use of `localhost` is enabled via a deployment configuration that runs the two containers
312
+ on the same pod / task / workload. This will vary based on the deployment environment.
313
+
314
+ ------
305
315
 
306
316
  The build system now simply needs to copy the built client-side resources into the Docker context
307
317
  and build the image. The sample below is simplified, but could also include the return code checks
308
- in the Tomcat example above. Note the `-nginx` suffix on the container tag. ⚠️ This example must
309
- also run with `docker/nginx` as its working directory:
318
+ in the Tomcat example above. Note the `-nginx` suffix on the container tag.
319
+
320
+ ⚠️ This example must run with `docker/nginx` as its working directory:
310
321
 
311
322
  ```bash
312
323
  cp -R ../../client-app/build/ .
@@ -316,43 +327,40 @@ sudo docker build --pull -t "$containerTag" .
316
327
  sudo docker push "$containerTag"
317
328
  ```
318
329
 
319
- #### 3.3) Docker cleanup
330
+ ### Docker cleanup
320
331
 
321
332
  ✨ At this point the build is complete and new versioned or snapshot images containing all the
322
333
  runtime code have been pushed to a Docker registry and are ready for deployment.
323
334
 
324
335
  It might be beneficial to add one more step to clean up local Docker images on the build agent, to
325
336
  avoid them continuing to grow and take up disk space indefinitely. Note this forces Docker to pull
326
- the base images anew each time, which takes a small amount of time/bandwidth. It could probably be
327
- made more targeted if desired:
337
+ the base images anew each time, which takes a small amount of time/bandwidth. It could be made more
338
+ targeted if desired:
328
339
 
329
340
  ```bash
330
341
  sudo docker system prune -af
331
342
  ```
332
343
 
333
- ### 4\) Docker deployment
344
+ ## Docker deployment
334
345
 
335
- 🚢 We typically setup distinct targets for build vs. deploy, and configure deployment targets to
346
+ 🚢 XH typically creates distinct targets for build vs. deploy, and configure deployment targets to
336
347
  prompt for the version number and/or Docker hostname. This process will differ significantly
337
348
  depending on the use (or not) of orchestration technology such as Kubernetes or AWS Elastic
338
349
  Container Service (ECS).
339
350
 
340
351
  Regardless of the specific implementation, the following points should apply:
341
352
 
342
- - Both `appCode-tomcat` and `appCode-nginx` containers should be deployed as a service / pair, and
343
- be kept at the same version.
344
- - The Tomcat container does not need to have any ports exposed/mapped onto the host (although it
345
- could if direct access is desired).
346
- - The nginx container typically exposes ports 80 and 443, although if a load balancer or similar is
347
- also in play that might vary (and would require appropriate adjustments to the `app.conf` nginx
348
- file outlined above).
349
- - The nginx container must be able to reach the Tomcat container at the same name included in its
350
- `app.conf` file - by convention, it expects to use `appCode-tomcat`. With straight Docker, this
351
- can be accomplished via the `--link` option (see below).
352
- - A shared volume can be used to host the instance config .yml file for the Grails server, SSL certs
353
- as required for nginx, and logs if so configured. This volume must be created in advance on the
354
- host and populated with any required bootstrap files. How that’s done again will depend on the
355
- particular Docker environment in play.
353
+ - Both `appCode-tomcat` and `appCode-nginx` containers should be deployed as a service / pair, and
354
+ be kept at the same version.
355
+ - The Tomcat container does not need to have any ports exposed/mapped onto the host (although it
356
+ could if direct access is desired).
357
+ - The nginx container typically exposes ports 80 and 443, although if a load balancer or similar is
358
+ also in play that might vary (and would require appropriate adjustments to the `app.conf` nginx
359
+ file outlined above).
360
+ - The nginx container must be able to reach the Tomcat container at the same name included in its
361
+ `app.conf` file, typically `localhost` (as in the example above).
362
+ - A persistent volume can be used to store logs if so configured. How that’s done again will depend
363
+ on the particular deployment environment and is beyond the scope of this doc.
356
364
 
357
365
  A sample Teamcity SSH-exec runner using Docker directly:
358
366
 
@@ -384,8 +392,8 @@ sudo docker container rm $nginxName
384
392
  # Pull nginx image at specified version
385
393
  sudo docker image pull $nginxImage
386
394
 
387
- # Run nginx image - link to Tomcat for proxying, expose ports, and mount local docker volume for SSL certificate access
388
- sudo docker run -d --name $nginxName --link $tomcatName:$tomcatName -p 80:80 -p 443:443 --mount type=volume,src=$appCode,dst=/$appCode --restart always $nginxImage
395
+ # Run nginx image - link to Tomcat for proxying, expose ports, and mount local docker volume for log storage
396
+ sudo docker run -d --name $nginxName --link $tomcatName:$tomcatName -p 80:80 --mount type=volume,src=$appCode,dst=/$appCode --restart always $nginxImage
389
397
  echo "Deploying $nginxImage complete"
390
398
 
391
399
  # Prune Docker, cleaning up dangling images and avoiding disk space bloat
@@ -394,5 +402,6 @@ sudo docker system prune -af
394
402
 
395
403
  ------------------------------------------
396
404
 
397
- 📫☎️🌎 info@xh.io | https://xh.io/contact
398
- Copyright © 2025 Extremely Heavy Industries Inc. - all rights reserved
405
+ ☎️ info@xh.io | <https://xh.io>
406
+
407
+ Copyright © 2025 Extremely Heavy Industries Inc.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "76.0.0-SNAPSHOT.1755386207928",
3
+ "version": "76.0.0-SNAPSHOT.1755394070476",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",