hof 24.0.0-vite-beta.11 → 24.0.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 (72) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/README.md +128 -160
  3. package/build/tasks/images/index.js +20 -6
  4. package/build/tasks/vite/index.js +5 -20
  5. package/build/tasks/vite/vite.config.js +2 -1
  6. package/build/tasks/watch/index.js +9 -2
  7. package/components/amount-with-unit-select/fields.js +15 -0
  8. package/components/amount-with-unit-select/hooks.js +168 -0
  9. package/components/amount-with-unit-select/index.js +107 -0
  10. package/components/amount-with-unit-select/templates/amount-with-unit-select.html +20 -0
  11. package/components/amount-with-unit-select/utils.js +197 -0
  12. package/components/amount-with-unit-select/validation.js +175 -0
  13. package/components/date/templates/date.html +1 -1
  14. package/components/index.js +1 -1
  15. package/config/builder-defaults.js +9 -2
  16. package/config/hof-defaults.js +2 -1
  17. package/controller/controller.js +5 -3
  18. package/controller/validation/index.js +1 -1
  19. package/controller/validation/validators.js +0 -1
  20. package/frontend/govuk-template/build/config.js +2 -0
  21. package/frontend/govuk-template/build/govuk_template.html +59 -43
  22. package/frontend/template-mixins/mixins/template-mixins.js +60 -9
  23. package/frontend/template-mixins/partials/forms/checkbox.html +5 -1
  24. package/frontend/template-mixins/partials/forms/grouped-inputs-select.html +13 -0
  25. package/frontend/template-mixins/partials/forms/grouped-inputs-text.html +37 -0
  26. package/frontend/template-mixins/partials/forms/input-text-date.html +2 -1
  27. package/frontend/template-mixins/partials/forms/input-text-group.html +1 -1
  28. package/frontend/template-mixins/partials/forms/textarea-group.html +1 -1
  29. package/frontend/template-partials/views/layout.html +1 -1
  30. package/frontend/template-partials/views/partials/confirmation-alert.html +3 -1
  31. package/frontend/template-partials/views/partials/cookie-banner.html +50 -32
  32. package/frontend/template-partials/views/partials/navigation.html +2 -2
  33. package/frontend/themes/gov-uk/client-js/cookieSettings.js +35 -23
  34. package/frontend/themes/gov-uk/client-js/session-timeout-dialog.js +5 -3
  35. package/frontend/themes/gov-uk/styles/_grouped-input.scss +5 -0
  36. package/frontend/themes/gov-uk/styles/govuk.scss +1 -0
  37. package/frontend/toolkit/assets/javascript/form-focus.js +4 -0
  38. package/frontend/toolkit/assets/rebrand/images/govuk-logo.svg +25 -0
  39. package/index.js +28 -20
  40. package/lib/encryption.js +43 -17
  41. package/lib/sessions.js +22 -8
  42. package/lib/settings.js +1 -1
  43. package/package.json +15 -17
  44. package/sandbox/apps/sandbox/fields.js +18 -1
  45. package/sandbox/apps/sandbox/index.js +4 -0
  46. package/sandbox/apps/sandbox/sections/summary-data-sections.js +7 -1
  47. package/sandbox/apps/sandbox/translations/src/en/fields.json +10 -0
  48. package/sandbox/apps/sandbox/translations/src/en/pages.json +3 -0
  49. package/sandbox/apps/sandbox/translations/src/en/validation.json +12 -0
  50. package/sandbox/package.json +1 -1
  51. package/sandbox/server.js +4 -1
  52. package/sandbox/yarn.lock +4 -4
  53. package/utilities/autofill/index.js +169 -145
  54. package/components/emailer/assets/images/govuk_logotype_email.png +0 -0
  55. package/components/emailer/assets/images/ho_crest_27px.png +0 -0
  56. package/components/emailer/assets/images/spacer.gif +0 -0
  57. package/components/emailer/email-service.js +0 -51
  58. package/components/emailer/emailer.js +0 -53
  59. package/components/emailer/index.js +0 -74
  60. package/components/emailer/transports/debug.js +0 -74
  61. package/components/emailer/transports/index.js +0 -8
  62. package/components/emailer/transports/ses.js +0 -36
  63. package/components/emailer/transports/smtp.js +0 -26
  64. package/components/emailer/transports/stub.js +0 -5
  65. package/components/emailer/views/layout.html +0 -63
  66. package/frontend/govuk-template/govuk_template_generated.html +0 -102
  67. package/sandbox/apps/sandbox/translations/en/default.json +0 -245
  68. package/sandbox/public/css/app.css +0 -10036
  69. package/sandbox/public/images/icons/icon-caret-left.png +0 -0
  70. package/sandbox/public/images/icons/icon-complete.png +0 -0
  71. package/sandbox/public/images/icons/icon-cross-remove-sign.png +0 -0
  72. package/sandbox/public/js/bundle.js +0 -60
package/CHANGELOG.md CHANGED
@@ -1,3 +1,144 @@
1
+ ## 2026-06-17, Version 24.0.0 (Stable), @gregwolversonHO, @nzorba
2
+
3
+ ⚠️ Major release which removes nodemailer dependency and email functionality
4
+
5
+ ### Changed
6
+ - Removed built-in email functionality from HOF, including the emailer component and transport implementations.
7
+ - Removed `nodemailer` and related transport dependencies from HOF.
8
+
9
+ ### Breaking Changes
10
+ - Services that previously relied on HOF email functionality must now implement and maintain their own email sending logic.
11
+ - Services should migrate to [Gov.uk Notify](https://www.notifications.service.gov.uk) to send notification emails
12
+
13
+ ## 2026-05-12, Version 23.1.0 (Stable), @gregwolversonHO
14
+
15
+ ### Changed
16
+ - Replaced nodemailer-ses-transport library with @aws-sdk/client-sesv2 to remove dependency on deprecated aws-sdk-v2 library
17
+ - Replaced usage of nodemailer-ses-transport in the [emailer ses client](./components/emailer/transports/ses.js).
18
+
19
+ ## 2026-04-16, Version 23.0.4 (Stable), @PaolaDMadd-Pro
20
+
21
+ ### Fixed
22
+ - Decoupled session timeout keep alive from analytics configuration.
23
+ - Updated CSP defaults to always include `connect-src 'self'` so same-origin keep alive requests are allowed.
24
+ - Updated GA CSP behavior to **extend** `connect-src` with analytics endpoints when `gaTagId` is set, rather than replacing defaults.
25
+
26
+ ### Changed
27
+ - Improved timeout dialog refresh behavior:
28
+ * On keep alive success, `timeSessionRefreshed` is updated and timeout controller logic is restarted.
29
+ * On keep alive failure a console.error is triggered.
30
+
31
+ ### Tests
32
+ - Added integration regression coverage for CSP:
33
+ * `gaTagId` set: GA and region analytics `connect-src` endpoints are present.
34
+ * `gaTagId` not set: default `connect-src 'self'` remains and GA region endpoints are absent.
35
+ - Added frontend Jest coverage for timeout refresh behavior:
36
+ * Success path (`$.get().done`) updates refresh time and calls controller.
37
+
38
+
39
+ ## 2026-03-18, Version 23.0.3 (Stable), @vinodhasamiyappan-ho
40
+
41
+ ### Security
42
+ - Upgraded WebdriverIO from v4 to v5, resolving security vulnerabilities caused by transitive dependencies (`minimist@0.0.10`, `form-data@~2.3.2`) in `webdriverio@4.14.4`.
43
+
44
+ ### Changed
45
+ - Migrated to WebdriverIO v5 (`webdriverio@5.23.0`), requiring the following compatibility updates:
46
+ - Refactored all functional tests to use async/await and the new WebdriverIO v5 command API.
47
+ - Updated test utilities and browser setup for async execution and modern WebdriverIO patterns.
48
+ - Removed deprecated synchronous and promise-chaining test code.
49
+ - Improved test isolation and reliability by ensuring each test runs with a fresh app and browser instance.
50
+ - Enhanced sessionDialog Jest tests with new and improved negative test scenarios
51
+
52
+ ## 2026-03-17, Version 23.0.2 (Stable), @vivekkumar-ho
53
+
54
+ ### Fixed
55
+ - Improved session secret validation so missing or blank values now fail fast with a clearer startup error.
56
+ - Reused the validated session secret consistently across cookie parsing, session encryption, and Redis session storage.
57
+
58
+ ## 2025-03-04, Version 23.0.1 (Stable), @PaolaDMadd-Pro @Rhodine-orleans-lindsay
59
+ ### ⚠️ Versioning Notice
60
+ v23.0.1 is published as a patch due to a prior publishing issue.
61
+ This is the official major release that replaces a previously published beta-tagged v23.0.0, and it should be treated as such in terms of features and breaking changes.
62
+ ### Changed
63
+ - Replaced Browserify with Vite
64
+ - removed browserify dependencies including in tests
65
+ - added vite configuration and related tests
66
+ - Updated readme.md
67
+ - Potential **breaking changes**:
68
+ - Node version required for Vite is `20.19.0 || >=22.12.0`
69
+ - Updated engine requirements to use node to `>=20.19.0`
70
+ - Vite uses Rollup in production and requires optional rollup dependencies to be loaded. Setting `--ignore-optional flag` in Dockerfiles will result in "Module not found" errors in CI/CD or Docker.
71
+
72
+ ## 2026-02-23, Version 22.14.1 (Stable), @vivekkumar-ho
73
+
74
+ ### Security
75
+
76
+ - Upgraded Jest to address a security issue: uncontrolled resource consumption in `braces`.
77
+ - Previous dependency chain (via transitive deps) could pin `braces` to `2.3.2` (from `jest@26.6.3` → `micromatch@3.1.10` → `braces@^2.3.1`), while the earliest fixed `braces` version is `3.0.3`.
78
+ - Jest upgrade updates the transitive dependency chain so `braces` can be resolved to a fixed version (`>= 3.0.3`).
79
+ - Updates patch and minor dependency versions
80
+
81
+ ## 2026-01-27, v22.14.0 (Stable), @rayhannurrohmanho
82
+
83
+ ### Added
84
+ - Created the amountWithUnitSelect component.
85
+ - Separated the amountWithUnitSelect component logic into into hooks, utils, and validators - all documented via JSDoc.
86
+ - Created templates and partials for the amountWithUnitSelect component.
87
+ - Configured template mixin for amountWithUnitSelect component.
88
+ - Added sandbox example for the amountWithUnitSelect component.
89
+ - Documented the amountWithUnitSelect component in readme.
90
+ - Added tests for the amountWithUnitSelect components.
91
+
92
+ ### Changed
93
+ - Added optional parent key ('pKey') parameter to the template-mixins.js 'optionGroup' function to allow the assignment of a group component's parent's properties.
94
+ - Modified validation to check if a "groupedFieldsWithOptions" property exists (and is set to true) for a component with "options" before the "equals" validator is applied to the component.
95
+
96
+ ## 2025-12-09, Version 22.13.0 (Stable), @Rhodine-orleans-lindsay
97
+ ### Changed
98
+ - Updated cookie banner view to follow GOV.UK design system. Updated cookieSettings.js to allow for different confirmation text based on whether cookies were accepted or rejected
99
+ - Added inputmode='numeric' to date component
100
+ - Added span for checkbox error messages
101
+ - Updated imported javascript
102
+ - Added data-module attribute for govuk-skip-link for screen readers
103
+ - Removed aria-polite=live for character-count and removed pattern attribute for date input
104
+ - Replaced deprecated govuk-header__link--service-name with govuk-header__service-name
105
+ - Added a modifier for phone text input styles that accept sequences of digits
106
+ - Added govuk-template--rebranded class to govuk template html to enable govuk rebrand styles:
107
+ - Updated header and footer to follow GOV.UK design system as part of rebrand
108
+
109
+ ## 2025-11-20, Version 22.12.0 (Stable), @dk4g @jamiecarterHO
110
+
111
+ ### Infrastructure
112
+ - Updated CI/CD pipeline to test against Node.js 20.x, 22.x, and 24.x
113
+ - Updated Redis testing versions to 7 and 8
114
+ - Added `NODE_VERSION` environment variable for consistent Node.js version across jobs
115
+ - Updated release process to use Node.js 24 for tagging and publishing operations
116
+
117
+ ### Security
118
+ - Replaced deprecated `crypto.createCipher`/`crypto.createDecipher` with `crypto.createCipheriv`/`crypto.createDecipheriv`
119
+ - Added proper initialisation vector (IV) handling for enhanced security
120
+ - Enforced 32-byte session secret requirement for AES-256 encryption compatibility
121
+ - Removed insecure default session secret ('changethis') - now requires explicit configuration
122
+
123
+ ### Migration Notes
124
+ - **Session Reset Required**: Due to enhanced encryption security, existing user sessions will be invalidated and users will need to re-authenticate after this update
125
+ - **Session Secret**: You must now set a unique `SESSION_SECRET` environment variable of exactly 32 bytes for encryption compatibility.
126
+ For testing purposes, you can use the following command to generate a random value. For production environments, consult a security expert or refer to official cryptographic guidelines to generate a secure secret
127
+ `node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"`
128
+
129
+ ## 2025-11-15, Version 22.11.0 (Stable), @Rhodine-orleans-lindsay
130
+
131
+ ### Changed
132
+ - Updated custom session-timeout handling so that custom behaviours are not blocked by a `404` middleware error.
133
+
134
+ ### Added
135
+ - Added a `CUSTOM_SESSION_EXPIRY` environment variable so that a time other than the redis session ttl can be used for the session timeout. **IMPORTANT**: The `CUSTOM_SESSION_EXPIRY` variable must always be a time before the redis session ttl would expire so that behaviours can run before the `SESSION_TIMEOUT` middleware is triggered.
136
+ - Added a `USE_CUSTOM_SESSION_TIMEOUT` that is `false` by default. When set to `true` the '/session-timeout' page can run before the session expires without triggering a `404` middleware error.
137
+
138
+ - 🎬 Action:
139
+ - For custom session timeout handling that is not linked to the redis session ttl, The following variables must be set: `CUSTOM_SESSION_EXPIRY` to the relevant expiry time e.g.600 and `USE_CUSTOM_SESSION_TIMEOUT` to true.
140
+ - If a behaviour is required on the '/session-timeout` step, the '/session-timeout' step must be set in the project's index.js, along with any relevant behaviours.
141
+
1
142
  ## 2025-10-10, Version 22.10.4 (Stable), @dk4g
2
143
  ### Security
3
144
  - Upgraded axios version to address a security vulnerability
package/README.md CHANGED
@@ -4,6 +4,9 @@
4
4
  [![npm version](https://badge.fury.io/js/hof.svg)](https://badge.fury.io/js/hof)
5
5
  [![Known Vulnerabilities](https://snyk.io/test/npm/hof/badge.svg)](https://snyk.io/test/npm/hof)
6
6
 
7
+ > **WARNING - Breaking change from v24.0.0:** HOF no longer includes built-in email functionality and no longer depends on `nodemailer`.
8
+ > Services must provide and maintain their own email sending implementation using [GOV.UK Notify](https://docs.notifications.service.gov.uk/node.html).
9
+
7
10
  HOF (Home Office Forms) is a framework designed to assist developers in creating form-based workflows in a rapid, repeatable and secure way. It aims to reduce simple applications as much as possible to being configuration-only.
8
11
 
9
12
  ## Server Settings
@@ -60,7 +63,8 @@ It is recommended to alias `hof-build` to an npm script in your package.json.
60
63
 
61
64
  ## Tasks
62
65
 
63
- - `browserify` - compiles client-side js with browserify
66
+ - `vite` - compiles client-side js with vite.
67
+ Requires node version `^20.19.0 || >=22.12.0`.
64
68
  - `sass` - compiles sass
65
69
  - `images` - copies images from ./assets/images directory to ./public/images
66
70
  - `translate` - compiles translation files
@@ -70,16 +74,16 @@ Note: For SASS compilation it's possible to additionally configure the following
70
74
  - `quietDeps` - This controls whether you get deprecation warning shown in the console output, if set to false (default) SASS deprecation warnings will be shown in the console, if set to true then deprecation warnings will not be shown in the console output.
71
75
  - `sourceMaps` - This controls whether the build will output css sourcemaps to help with debugging. These will be output to the same directory as the css output as a .map file. This option is not currently available in production.
72
76
 
73
- For JavaScript compilation, browserify can be set to debug mode by setting the `debug` option to true. This will cause browserify to output JavaScript sourcemaps as a .js.map file to the same directory as the js bundle.
77
+ Vite outputs JavaScript sourcemaps as a .js.map file to the same directory as the js bundle.
74
78
 
75
79
  Debugging example (in hof.settings or your build config)
76
- ```
80
+ ```js
77
81
  "build": {
78
82
  "sass": {
79
83
  "sourceMaps": true
80
84
  },
81
- "browserify": {
82
- "debug": true
85
+ "js": {
86
+ "sourceMaps": true
83
87
  }
84
88
  }
85
89
  ```
@@ -109,7 +113,7 @@ hof-build watch --env .envdev
109
113
 
110
114
  ## Configuration
111
115
 
112
- The default settings will match those for an app generated using [`hof-generator`](https://npmjs.com/hof-generator).
116
+ The default settings will match those for an app generated using [`hof-generator`](https://npmjs.com/hof-generator) (⚠️ This package has been deprecated).
113
117
 
114
118
  If a `hof.settings.json` file is found in the application root, then the `build` section of the settings file will be used to override [the default configuration](./defaults.js).
115
119
 
@@ -118,18 +122,17 @@ Alternatively you can define a path to a local config file by passing a `--confi
118
122
  ```
119
123
  hof-build --config /path/to/my/config.js
120
124
  ```
121
-
122
125
  Any task can be disabled by setting its configuration to `false` (or any falsy value).
123
126
 
124
127
  ```js
125
128
  module.exports = {
126
- browserify: false,
129
+ sass: false,
127
130
  };
128
131
  ```
129
132
 
130
133
  ### Configuration options
131
134
 
132
- Each task has a common configuration format with the following options:
135
+ Each task (except Vite) has a common configuration format with the following options:
133
136
 
134
137
  - `src` - defines the input file or files for the build task
135
138
  - `out` - defines the output location of the built code where relevant
@@ -867,7 +870,7 @@ Kubernetes healthcheck URLs are provided as defaults if no overrides are supplie
867
870
  |----------|-------------|---------|---------|
868
871
  | `SHOW_COOKIES_BANNER` | Controls whether the cookies banner is displayed | Auto-detected based on GA tags | `true`, `false` |
869
872
 
870
- **Behavior:**
873
+ **Behaviour:**
871
874
  - If `SHOW_COOKIES_BANNER` is explicitly set, that value is used
872
875
  - If not set, banner is automatically shown when `GA_TAG` or `GA_4_TAG` is present
873
876
  - If no GA tags are configured, banner is hidden by default
@@ -1012,6 +1015,85 @@ Using the translation key `fields.field-name.label` will return different values
1012
1015
 
1013
1016
  # HOF Components
1014
1017
 
1018
+ ## AmountWithUnitSelect Component
1019
+
1020
+ > **Available from version 22.14.0**
1021
+
1022
+ A component for handling the rendering, processing, and validation of a text input (amount) field and a select-dropdown input field (unit) used in HOF applications.
1023
+
1024
+ ### Usage
1025
+
1026
+ In your fields config:
1027
+
1028
+ ```js
1029
+ const amountWithUnitSelectComponent = require("hof").components.amountWithUnitSelect;
1030
+
1031
+ module.exports = {
1032
+ "amountWithUnitSelect-field": amountWithUnitSelectComponent("amountWithUnitSelect", {
1033
+ mixin: 'input-amount-with-unit-select',
1034
+ amountLabel: "Amount:", // If not specified, defaults to 'Amount'
1035
+ unitLabel: "Unit:", // If not specified, defaults to 'Unit'
1036
+ options: [
1037
+ { "null": "Select..." }, // If a null option is not specified, a default null option with the label 'Select...' is included in the options
1038
+ { "label": "untranslated option label 1", "value": "1" },
1039
+ { "label": "untranslated option label 2", "value": "2" }
1040
+ ],
1041
+ hint: "E.g: 5 Kilogram",
1042
+ legend: 'Enter An Amount',
1043
+ isPageHeading: 'true',
1044
+ amountOptional: 'false', // If not specified, defaults to false if the required validator is not applied, otherwise true
1045
+ unitOptional: 'true', // If not specified, defaults to false if the required validator is not applied, otherwise true
1046
+ validate: ['alphanum']
1047
+ }),
1048
+ };
1049
+ ```
1050
+
1051
+ The above example will create a new AmountWithUnitSelect component with the key `'amountWithUnitSelect-field'`.
1052
+ It will set the AmountWithUnitSelect component's text input label to `'Amount:'` (instead of the default `'Amount'`) and the select input label to `'Unit:'` (instead of the default `'Unit'`).
1053
+ The component's Select input will have the dropdown options `'Select...'` (mapping to the value `'null'`), `'untranslated option label 1'` (mapping to the value `'1'`), and `'untranslated option label 2'` (mapping to the value `'2'`).
1054
+ The component's hint text will be set to `'E.G: 5 Kilogram'`, the field title/legend will be set to `'Enter An Amount'`, and the page heading will be the field's title.
1055
+ In terms of validation, the text input (amount) will have the `'required'` validator applied, but the select input (unit) will not (a value will not be required to be selected), and the text field will have the `'alphanum'` validator applied.
1056
+
1057
+ ### Configuration
1058
+
1059
+ The following optional configuration options are supported:
1060
+
1061
+ - `validate {String|Array}` – Validators to use on the text input field. The `'alphanum'` and `'required'` validators are likely to be used.
1062
+ - `template` – An absolute path to an alternate template.
1063
+ - `amountLabel {String}` – A custom label for the text input field (amount). Defaults to `'Amount'` if omitted. This can also be specified and defined in the field translations.
1064
+ - `unitLabel {String}` – A custom label for the select input field (unit). Defaults to `'Unit'` if omitted. This can also be specified and defined in the field translations.
1065
+ - `options {Array}` – A list of labels and corresponding values (options) for the select input field to present. Each option has the format `{ "label": "", "value": ""}`. The default/null option is defined with the format `{ "null": "Select" }` (where the text `'Select'` is the label and can be modified). If the null option is not defined, a default null option will be included in the options with the label `'Select...'`. This can also be specified and defined in the field translations.
1066
+ - `hint {String}` – Hint text displayed for both fields. This can also be specified and defined in the field translations.
1067
+ - `legend {String}` – Legend text displayed for both fields. This can also be specified and defined in the field translations.
1068
+ - `isPageHeading {Boolean}` – Sets the legend as the page heading on single-page questions.
1069
+ - `amountOptional {Boolean}` – The text input (amount) defaults to `''` (empty string) if omitted. Defaults to `false`. If the `'required'` validator is defined in the `validate` configuration option, then this configuration is ignored (both fields are made mandatory).
1070
+ - `unitOptional {Boolean}` – The select input (unit) defaults to `null` if omitted. Defaults to `false`. If the `'required'` validator is defined in the `validate` configuration option, then this configuration is ignored (both fields are made mandatory).
1071
+
1072
+ ### Validation Error Messages
1073
+
1074
+ In Validation.json (within the translations):
1075
+
1076
+ ```js
1077
+ "amountWithUnitSelect": {
1078
+ "default": "Enter the amount in the correct format; for example, 10 Litres",
1079
+ "alphanum": "The amount must not contain any special characters",
1080
+ "required": "Enter an amount and a unit value"
1081
+ },
1082
+ "amountWithUnitSelect-unit": {
1083
+ "default": "A valid value must be selected as the amount unit",
1084
+ "required": "A unit must be selected for the amount"
1085
+ },
1086
+ "amountWithUnitSelect-amount": {
1087
+ "alphanum": "The amount must not be an alphanum"
1088
+ }
1089
+ ```
1090
+
1091
+ Validation error messages can be defined for a specific child component that errored by adding a JSON object with a key that has the component's name (I.E. `'amountWithUnitSelect'`) followed by a hyphen and the child component's name (I.E. '-amount' or '-unit'). So to define an error message, for the `'required'` validation error, for specifically the unit component, an `'amountWithUnitSelect-unit'` object can be created (like in the example above) with a validator's name and the error message to show for it respectively, set as a key:value pair in the object (E.G. `"required": "A unit must be selected for the amount" within "amountWithUnitSelect-unit"` - like in the example).
1092
+ Validation error messages defined specifically for child components, for a given type of validation error, (such as `'required'` for `'amountWithUnitSelect-unit'`) will take precedence over error messages defined for the same validation error in the parent. So in this case, the `"required": "Enter an amount and a unit value"` validation message defined in the `"amountWithUnitSelect"` JSON object in the above example will not show when the `"amountWithUnitSelect-unit"` `'required'` validation error message is defined. If there was no `'required'` error message defined for, say, the `"amountWithUnitSelect-amount"` object (like in the example above), the `"amountWithUnitSelect"` object's `'required'` error message would show if the `'required'` validation error is triggered on the 'amount' child component (I.E. When an amount value is not provided).
1093
+ The `'default'` catch all validation error message can also be defined in a similar manner for the child components to override the parent component's defined `'default'` error message.
1094
+
1095
+ So the example above will create a scenario where `'required'` validation errors triggered on the 'unit' field will display the error message `"A unit must be selected for the amount"` (specified in the `"amountWithUnitSelect-unit"` JSON object). Any `'required'` validation errors triggered on the 'amount' field will display the error message `"Enter an amount and a unit value"` (specified in the `"amountWithUnitSelect"` JSON object - as there is no `'required'` error message defined specifically for the 'amount' field (withing `"amountWithUnitSelect-amount"`)).
1096
+
1015
1097
  ## Date Component
1016
1098
 
1017
1099
  A component for handling the rendering and processing of 3-input date fields used in HOF Applications.
@@ -1173,162 +1255,17 @@ Translations for field labels are looked for in the following order:
1173
1255
  - `fields.${key}.label`
1174
1256
  - `fields.${key}.legend`
1175
1257
 
1176
- ## Emailer Component
1177
-
1178
- HOF behaviour to send emails
1179
-
1180
- ### Usage
1181
-
1182
- ```js
1183
- const EmailBehaviour = require('hof').components.emailer;
1184
-
1185
- // configure email behaviour
1186
- const emailer = EmailBehaviour({
1187
- transport: 'ses',
1188
- transportOptions: {
1189
- accessKeyId: '...',
1190
- secretAccessKey: '...'
1191
- },
1192
- template: path.resolve(__dirname, './views/emails/confirm.html'),
1193
- from: 'confirmation@homeoffice.gov.uk',
1194
- recipient: 'customer-email',
1195
- subject: 'Application Successful'
1196
- });
1197
-
1198
- // in steps config
1199
- steps: {
1200
- ...
1201
- '/confirm': {
1202
- behaviours: ['complete', emailer],
1203
- next: '/confirmation',
1204
- ...
1205
- },
1206
- ...
1207
- }
1208
- ```
1209
-
1210
- ### Options
1211
-
1212
- In addition to the options passed to `hof-emailer`, the following options can be used:
1213
-
1214
- - `recipient` - _Required_ - defines the address to which email will be sent. This can be set either as a key to retrieve an email address from the session, or explicitly to an email address.
1215
- - `template` - _Required_ - defines the mustache template used to render the email content.
1216
- - `subject` - defines the subject line of the email.
1217
- - `parse` - parses the session model into an object used to populate the template.
1218
-
1219
- `recipient` and `subject` options can also be defined as functions, which will be passed a copy of the session model and a translation function as arguments, and should return a string value.
1220
-
1221
- ```js
1222
- // use a translated value for the email subject line
1223
- const emailer = EmailBehaviour({
1224
- // ...
1225
- subject: (model, translate) => translate("email.success.subject"),
1226
- });
1227
- ```
1228
-
1229
- ## HOF Emailer
1230
-
1231
- An emailer service for HOF applications.
1232
-
1233
- ### Installation
1234
-
1235
- ```bash
1236
- $ npm install hof-emailer --save
1237
- ```
1238
-
1239
- ### Usage
1240
-
1241
- ```js
1242
- // first create an emailer instance
1243
- const Emailer = require("hof").components.email.emailer;
1244
- const emailer = new Emailer({
1245
- from: "sender@example.com",
1246
- transport: "smtp",
1247
- transportOptions: {
1248
- host: "my.smtp.host",
1249
- port: 25,
1250
- },
1251
- });
1252
-
1253
- // then you can use your emailer to send emails
1254
- const to = "recipient@example.com";
1255
- const body = "This is the email body";
1256
- const subject = "Important email!";
1257
- emailer.send(to, body, subject).then(() => {
1258
- console.log(`Email sent to ${to}!`);
1259
- });
1260
- ```
1261
-
1262
- ### Options
1263
-
1264
- - `from`: <String>: Address to send emails from. Required.
1265
- - `transport`: <String>: Select what mechanism to use to send emails. Defaults: 'smtp'.
1266
- - `transportOptions`: <Object>: Set the options for the chosen transport, as defined below. Required.
1267
- - `layout`: <String>: Optional path to use a custom layout for email content.
1268
-
1269
- ### Transports
1270
-
1271
- The following transport options are available:
1272
-
1273
- #### `smtp`
1274
-
1275
- [nodemailer](https://github.com/nodemailer/nodemailer)
1276
-
1277
- ##### Options
1278
-
1279
- - `host` <String>: Address of the mailserver. Required.
1280
- - `port` <String|Number>: Port of the mailserver. Required.
1281
- - `ignoreTLS` <Boolean>: Defaults to false.
1282
- - `secure` <Boolean>: Defaults to true.
1283
- - `auth.user` <String>: Mailserver authorisation username.
1284
- - `auth.pass` <String>: Mailserver authorisation password.
1285
-
1286
- #### `ses`
1287
-
1288
- [nodemailer-ses-transport](https://github.com/andris9/nodemailer-ses-transport)
1289
-
1290
- ##### Options
1291
-
1292
- - `accessKeyId` <String>: AWS accessKeyId. Required.
1293
- - `secretAccessKey` <String>: AWS accessKeyId. Required.
1294
- - `sessionToken` <String>
1295
- - `region` <String>. Defaults to 'eu-west-1'.
1296
- - `httpOptions` <String>
1297
- - `rateLimit` <String>
1298
- - `maxConnections` <String>
1299
-
1300
- #### `debug`
1301
-
1302
- A development option to write the html content of the email to a file for inspection.
1303
-
1304
- `transport: 'debug'`
1305
-
1306
- ##### debug options
1307
-
1308
- - `dir` <String>: The location to save html to. Default: `./.emails`. This directory will be created if it does not exist.
1309
- - `open` <Boolean>: If set to true, will automatically open the created html file in a browser.
1310
-
1311
- ##### debug example
1312
-
1313
- ```
1314
- transport: 'debug'
1315
- transportOptions: {
1316
- dir: './emails',
1317
- open: true
1318
- }
1319
- ```
1320
-
1321
- #### `stub`
1322
-
1323
- Disables sending email. No options are required.
1324
-
1325
1258
  ## Session Timeout Warning Component
1326
1259
  HOF component for customising session timeout related pages
1327
1260
  This feature allows you to customise the content related to the session timeout warning, including the messages displayed in the session timeout warning dialog and on the exit page after a user exits the form due to a session timeout.
1328
1261
 
1329
1262
  ### Usage
1330
1263
 
1331
- To enable and customise the session timeout behavior, you need to set the component and translations in your project's `hof.settings.json` file:
1264
+ By default, the session timeout is set to the redis session ttl. To bypass this and display the session timeout message before the redis session ttl the following environment variables must be set:
1265
+ `CUSTOM_SESSION_EXPIRY` - e.g. `600`. Configure to expire before the project's redis session ttl.
1266
+ `USE_CUSTOM_SESSION_TIMEOUT` - `false` by default. When set to `true` the '/session-timeout' page can run before the session expires without triggering a `404` middleware error.
1267
+
1268
+ To enable and customise the session timeout behaviour, you need to set the component and translations in your project's `hof.settings.json` file:
1332
1269
  ```json
1333
1270
  "behaviours": [
1334
1271
  "hof/components/session-timeout-warning"
@@ -1348,6 +1285,36 @@ By default, the framework uses the standard content provided by HOF. If you wish
1348
1285
  "saveExitFormContent": true // allows you to customise the content on the save-and-exit page
1349
1286
  ```
1350
1287
 
1288
+ To override the default session-timeout page completely, the path to the session-timeout.html should be set in the views property in the hof.settings.json file e.g. 
1289
+ ```json
1290
+ "behaviours": [
1291
+ "hof/components/session-timeout-warning"
1292
+ ],
1293
+ "translations": "./apps/common/translations",
1294
+ "views": ["./apps/common/views"], // allows you to override the HOF default session-timeout page and use a custom one from the specified views
1295
+ ...
1296
+ ```
1297
+ or in the project's `server.js` e.g.
1298
+ ```js
1299
+ settings.views = path.resolve(__dirname, './apps/common/views');
1300
+ ```
1301
+
1302
+
1303
+ ### Session Timeout Keep-alive and CSP
1304
+
1305
+ From version 23.0.4 , session timeout keep alive is now independent of analytics tags.
1306
+
1307
+ - Default CSP always includes: `connect-src 'self'`
1308
+ - If `GA_TAG` (`gaTagId`) is configured:
1309
+ - Google Analytics endpoints are added to `connect-src`.
1310
+ - If `GA_TAG` is not configured: Only default same-origin `connect-src` is used (no GA region endpoints).
1311
+
1312
+ ### Timeout Dialog Behavior
1313
+ When a user clicks **Stay on this page** in the timeout dialog:
1314
+
1315
+ - If keep-alive succeeds: session refresh timestamp is updated and the timeout countdown/controller is restarted.
1316
+
1317
+
1351
1318
  ### Customising content in `pages.json`
1352
1319
  Once the variables are set, you can customise the session timeout warning and exit messages in your project's pages.json:
1353
1320
 
@@ -1625,6 +1592,7 @@ currency
1625
1592
  select
1626
1593
  input-text
1627
1594
  input-date
1595
+ input-amount-with-unit-select
1628
1596
  input-text-compound
1629
1597
  input-text-code
1630
1598
  input-number
@@ -4,20 +4,34 @@
4
4
  const fs = require('fs');
5
5
  const chalk = require('chalk');
6
6
  const spawn = require('../../lib/spawn');
7
- const mkdir = require('../../lib/mkdir');
8
7
 
9
8
  module.exports = config => {
10
9
  if (!config.images) {
11
10
  return Promise.resolve();
12
11
  }
12
+ const imagesOutput = config.images.out;
13
13
 
14
- return new Promise((resolve, reject) => {
15
- fs.stat(config.images.src, err => err ? reject(err) : resolve());
16
- })
17
- .then(() => mkdir(config.images.out))
18
- .then(() => spawn('cp', ['-r', config.images.src, config.images.out]))
14
+ // Due to govuk rebrand logo, images are copied from multiple sources & mapping can cause 'File exists' error.
15
+ // Remove if exists as a file
16
+ if (fs.existsSync(imagesOutput) && !fs.lstatSync(imagesOutput).isDirectory()) {
17
+ fs.unlinkSync(imagesOutput);
18
+ }
19
+ // Ensure directory exists
20
+ if (!fs.existsSync(imagesOutput)) {
21
+ fs.mkdirSync(imagesOutput, { recursive: true });
22
+ }
23
+ const srcs = Array.isArray(config.images.src) ? config.images.src : [config.images.src];
24
+
25
+ return Promise.all(srcs.map(src => {
26
+ if (!fs.existsSync(src)) {
27
+ console.log(`${chalk.yellow('warning')}: Skipping missing images folder: ${src}`);
28
+ return Promise.resolve();
29
+ }
30
+ return spawn('cp', ['-r', `${src}/.`, imagesOutput]);
31
+ }))
19
32
  .catch(e => {
20
33
  if (e.code !== 'ENOENT') {
34
+ console.error(`${chalk.red('error')}: Failed to copy images - ${e.message}`);
21
35
  throw e;
22
36
  } else {
23
37
  console.log(`${chalk.yellow('warning')}: no images directory found at ${config.images.src}`);
@@ -1,37 +1,22 @@
1
- /* eslint-disable no-console */
2
1
  'use strict';
3
2
 
4
3
  const vite = require('vite');
5
4
  const path = require('path');
6
5
  const viteConfig = path.resolve(__dirname, './vite.config.js');
7
6
  const hofDefaults = require('../../../config/hof-defaults');
8
- module.exports = config => {
9
- // The config.production’s value is set up with a command flag
10
- // in the package.json’s script. process.env.NODE_ENV);
11
- console.log('Vite build - production:', config.production, 'env: ', config.env);
12
- console.log('hofDefaults:', hofDefaults);
13
7
 
14
- // if (config.production === true) {
15
- // return vite.build({
16
- // configFile: viteConfig
17
- // });
18
- // }
19
- // process.env.NODE_ENV = 'development'; // TO DO make this dynamic to Ensure
20
- // // the environment is set to development for non-production build
21
- // return vite.build({
22
- // configFile: viteConfig,
23
- // mode: 'development'
24
- // });
8
+ module.exports = config => {
25
9
  process.env.NODE_ENV = hofDefaults.env;
26
10
 
27
11
  if(!config.production) {
28
- console.log('Vite build - expecting development/local:', process.env.NODE_ENV);
29
12
  return vite.build({
30
13
  configFile: viteConfig,
31
- mode: 'development'
14
+ mode: 'development',
15
+ build: {
16
+ sourcemap: config.js.sourceMaps
17
+ }
32
18
  });
33
19
  }
34
- console.log('Vite build - expecting production:', process.env.NODE_ENV);
35
20
  return vite.build({
36
21
  configFile: viteConfig
37
22
  });
@@ -26,7 +26,7 @@ export default defineConfig({
26
26
  // Vite does not process and optimise static images and assets files, it uses a src/ folder.
27
27
  // the images and assets folders are therefore loaded at runtime from the public/ folder.
28
28
  // This should be resolved with the v5/nunjucks work so we can ignore these warnings for now.
29
- // TO DO: Remove this when v5/nunjucks is implemented.
29
+ // TODO: Remove this when v5/nunjucks is implemented.
30
30
  {
31
31
  name: 'suppress-resolve-warnings',
32
32
  apply: 'build',
@@ -51,6 +51,7 @@ export default defineConfig({
51
51
  build: {
52
52
  outDir: publicDirectory,
53
53
  emptyOutDir: false,
54
+ sourcemap: false,
54
55
  rollupOptions: {
55
56
  input: {
56
57
  index: entryFile
@@ -32,7 +32,14 @@ module.exports = config => {
32
32
  }, {});
33
33
 
34
34
  function triggersTask(file) {
35
- return Object.keys(matchers).filter(key => match(file, matchers[key]));
35
+ return Object.keys(matchers).filter(key => {
36
+ const pattern = matchers[key];
37
+ // Only call minimatch if pattern is a non-empty string or array of strings
38
+ if (Array.isArray(pattern)) {
39
+ return pattern.some(p => typeof p === 'string' && p.length > 0 && match(file, p));
40
+ }
41
+ return typeof pattern === 'string' && pattern.length > 0 && match(file, pattern);
42
+ });
36
43
  }
37
44
 
38
45
  function restart() {
@@ -94,7 +101,7 @@ module.exports = config => {
94
101
  process.stdin.setEncoding('utf8');
95
102
  process.stdin.on('data', data => {
96
103
  const dataType = (data + '').trim().toLowerCase();
97
- if (dataType === config.watch.restart) {
104
+ if (dataType === config.watch.restart) {
98
105
  restart();
99
106
  }
100
107
  });
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ module.exports = key => ({
4
+ [`${key}-amount`]: {
5
+ label: 'Amount',
6
+ autocomplete: 'off',
7
+ validate: []
8
+ },
9
+ [`${key}-unit`]: {
10
+ label: 'Unit',
11
+ groupedFieldsWithOptions: true,
12
+ options: {},
13
+ validate: []
14
+ }
15
+ });