@dwp/govuk-casa 9.7.0 → 10.1.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 (278) hide show
  1. package/dist/assets/css/casa.css +1 -1
  2. package/dist/assets/css/casa.css.map +1 -1
  3. package/dist/casa.d.ts +90 -44
  4. package/dist/casa.d.ts.map +1 -0
  5. package/dist/casa.js +85 -101
  6. package/dist/core-plugins/edit-snapshot/src/configure.d.ts +1 -0
  7. package/dist/core-plugins/edit-snapshot/src/configure.d.ts.map +1 -0
  8. package/dist/core-plugins/edit-snapshot/src/configure.js +7 -14
  9. package/dist/core-plugins/edit-snapshot/src/index.d.ts +1 -0
  10. package/dist/core-plugins/edit-snapshot/src/index.d.ts.map +1 -0
  11. package/dist/core-plugins/edit-snapshot/src/index.js +3 -9
  12. package/dist/core-plugins/edit-snapshot/src/post-steer-hook.d.ts +2 -1
  13. package/dist/core-plugins/edit-snapshot/src/post-steer-hook.d.ts.map +1 -0
  14. package/dist/core-plugins/edit-snapshot/src/post-steer-hook.js +8 -13
  15. package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.d.ts +2 -1
  16. package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.d.ts.map +1 -0
  17. package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.js +13 -18
  18. package/dist/core-plugins/edit-snapshot/src/utils.d.ts +8 -5
  19. package/dist/core-plugins/edit-snapshot/src/utils.d.ts.map +1 -0
  20. package/dist/core-plugins/edit-snapshot/src/utils.js +37 -19
  21. package/dist/core-plugins/index.d.ts +1 -0
  22. package/dist/core-plugins/index.d.ts.map +1 -0
  23. package/dist/core-plugins/index.js +1 -9
  24. package/dist/lib/CasaTemplateLoader.d.ts +1 -0
  25. package/dist/lib/CasaTemplateLoader.d.ts.map +1 -0
  26. package/dist/lib/CasaTemplateLoader.js +2 -6
  27. package/dist/lib/JourneyContext.d.ts +22 -11
  28. package/dist/lib/JourneyContext.d.ts.map +1 -0
  29. package/dist/lib/JourneyContext.js +40 -45
  30. package/dist/lib/MutableRouter.d.ts +79 -55
  31. package/dist/lib/MutableRouter.d.ts.map +1 -0
  32. package/dist/lib/MutableRouter.js +53 -45
  33. package/dist/lib/NullObject.d.ts +3 -0
  34. package/dist/lib/NullObject.d.ts.map +1 -0
  35. package/dist/lib/NullObject.js +3 -0
  36. package/dist/lib/Plan.d.ts +2 -1
  37. package/dist/lib/Plan.d.ts.map +1 -0
  38. package/dist/lib/Plan.js +44 -62
  39. package/dist/lib/ValidationError.d.ts +2 -1
  40. package/dist/lib/ValidationError.d.ts.map +1 -0
  41. package/dist/lib/ValidationError.js +3 -11
  42. package/dist/lib/ValidatorFactory.d.ts +5 -6
  43. package/dist/lib/ValidatorFactory.d.ts.map +1 -0
  44. package/dist/lib/ValidatorFactory.js +4 -12
  45. package/dist/lib/configuration-ingestor.d.ts +10 -22
  46. package/dist/lib/configuration-ingestor.d.ts.map +1 -0
  47. package/dist/lib/configuration-ingestor.js +61 -143
  48. package/dist/lib/configure.d.ts +2 -1
  49. package/dist/lib/configure.d.ts.map +1 -0
  50. package/dist/lib/configure.js +40 -52
  51. package/dist/lib/constants.d.ts +1 -0
  52. package/dist/lib/constants.d.ts.map +1 -0
  53. package/dist/lib/constants.js +8 -12
  54. package/dist/lib/context-id-generators.d.ts +1 -0
  55. package/dist/lib/context-id-generators.d.ts.map +1 -0
  56. package/dist/lib/context-id-generators.js +4 -9
  57. package/dist/lib/end-session.d.ts +2 -1
  58. package/dist/lib/end-session.d.ts.map +1 -0
  59. package/dist/lib/end-session.js +4 -11
  60. package/dist/lib/field.d.ts +2 -1
  61. package/dist/lib/field.d.ts.map +1 -0
  62. package/dist/lib/field.js +11 -21
  63. package/dist/lib/index.d.ts +1 -0
  64. package/dist/lib/index.d.ts.map +1 -0
  65. package/dist/lib/index.js +13 -65
  66. package/dist/lib/logger.d.ts +25 -2
  67. package/dist/lib/logger.d.ts.map +1 -0
  68. package/dist/lib/logger.js +18 -9
  69. package/dist/lib/mount.d.ts +1 -0
  70. package/dist/lib/mount.d.ts.map +1 -0
  71. package/dist/lib/mount.js +10 -16
  72. package/dist/lib/nunjucks-filters.d.ts +7 -3
  73. package/dist/lib/nunjucks-filters.d.ts.map +1 -0
  74. package/dist/lib/nunjucks-filters.js +58 -71
  75. package/dist/lib/nunjucks.d.ts +4 -4
  76. package/dist/lib/nunjucks.d.ts.map +1 -0
  77. package/dist/lib/nunjucks.js +14 -26
  78. package/dist/lib/utils.d.ts +4 -2
  79. package/dist/lib/utils.d.ts.map +1 -0
  80. package/dist/lib/utils.js +14 -28
  81. package/dist/lib/validators/dateObject.d.ts +1 -0
  82. package/dist/lib/validators/dateObject.d.ts.map +1 -0
  83. package/dist/lib/validators/dateObject.js +14 -21
  84. package/dist/lib/validators/email.d.ts +1 -0
  85. package/dist/lib/validators/email.d.ts.map +1 -0
  86. package/dist/lib/validators/email.js +8 -15
  87. package/dist/lib/validators/inArray.d.ts +1 -0
  88. package/dist/lib/validators/inArray.d.ts.map +1 -0
  89. package/dist/lib/validators/inArray.js +8 -15
  90. package/dist/lib/validators/index.d.ts +1 -0
  91. package/dist/lib/validators/index.d.ts.map +1 -0
  92. package/dist/lib/validators/index.js +21 -27
  93. package/dist/lib/validators/nino.d.ts +1 -0
  94. package/dist/lib/validators/nino.d.ts.map +1 -0
  95. package/dist/lib/validators/nino.js +6 -13
  96. package/dist/lib/validators/postalAddressObject.d.ts +1 -0
  97. package/dist/lib/validators/postalAddressObject.d.ts.map +1 -0
  98. package/dist/lib/validators/postalAddressObject.js +12 -19
  99. package/dist/lib/validators/range.d.ts +1 -0
  100. package/dist/lib/validators/range.d.ts.map +1 -0
  101. package/dist/lib/validators/range.js +6 -13
  102. package/dist/lib/validators/regex.d.ts +1 -0
  103. package/dist/lib/validators/regex.d.ts.map +1 -0
  104. package/dist/lib/validators/regex.js +6 -13
  105. package/dist/lib/validators/required.d.ts +1 -0
  106. package/dist/lib/validators/required.d.ts.map +1 -0
  107. package/dist/lib/validators/required.js +9 -17
  108. package/dist/lib/validators/strlen.d.ts +1 -0
  109. package/dist/lib/validators/strlen.d.ts.map +1 -0
  110. package/dist/lib/validators/strlen.js +6 -13
  111. package/dist/lib/validators/wordCount.d.ts +1 -0
  112. package/dist/lib/validators/wordCount.d.ts.map +1 -0
  113. package/dist/lib/validators/wordCount.js +6 -13
  114. package/dist/lib/waypoint-url.d.ts +3 -2
  115. package/dist/lib/waypoint-url.d.ts.map +1 -0
  116. package/dist/lib/waypoint-url.js +12 -19
  117. package/dist/middleware/body-parser.d.ts +1 -0
  118. package/dist/middleware/body-parser.d.ts.map +1 -0
  119. package/dist/middleware/body-parser.js +4 -9
  120. package/dist/middleware/csrf.d.ts +1 -0
  121. package/dist/middleware/csrf.d.ts.map +1 -0
  122. package/dist/middleware/csrf.js +4 -8
  123. package/dist/middleware/data.d.ts +2 -3
  124. package/dist/middleware/data.d.ts.map +1 -0
  125. package/dist/middleware/data.js +23 -25
  126. package/dist/middleware/gather-fields.d.ts +3 -1
  127. package/dist/middleware/gather-fields.d.ts.map +1 -0
  128. package/dist/middleware/gather-fields.js +13 -14
  129. package/dist/middleware/i18n.d.ts +1 -0
  130. package/dist/middleware/i18n.d.ts.map +1 -0
  131. package/dist/middleware/i18n.js +26 -31
  132. package/dist/middleware/post.d.ts +4 -2
  133. package/dist/middleware/post.d.ts.map +1 -0
  134. package/dist/middleware/post.js +6 -11
  135. package/dist/middleware/pre.d.ts +2 -1
  136. package/dist/middleware/pre.d.ts.map +1 -0
  137. package/dist/middleware/pre.js +6 -12
  138. package/dist/middleware/progress-journey.d.ts +5 -3
  139. package/dist/middleware/progress-journey.d.ts.map +1 -0
  140. package/dist/middleware/progress-journey.js +20 -18
  141. package/dist/middleware/sanitise-fields.d.ts +5 -3
  142. package/dist/middleware/sanitise-fields.d.ts.map +1 -0
  143. package/dist/middleware/sanitise-fields.js +25 -17
  144. package/dist/middleware/serve-first-waypoint.d.ts +1 -0
  145. package/dist/middleware/serve-first-waypoint.d.ts.map +1 -0
  146. package/dist/middleware/serve-first-waypoint.js +3 -6
  147. package/dist/middleware/session.d.ts +15 -0
  148. package/dist/middleware/session.d.ts.map +1 -0
  149. package/dist/middleware/session.js +53 -57
  150. package/dist/middleware/skip-waypoint.d.ts +3 -2
  151. package/dist/middleware/skip-waypoint.d.ts.map +1 -0
  152. package/dist/middleware/skip-waypoint.js +15 -14
  153. package/dist/middleware/steer-journey.d.ts +2 -1
  154. package/dist/middleware/steer-journey.d.ts.map +1 -0
  155. package/dist/middleware/steer-journey.js +7 -13
  156. package/dist/middleware/strip-proxy-path.d.ts +3 -2
  157. package/dist/middleware/strip-proxy-path.d.ts.map +1 -0
  158. package/dist/middleware/strip-proxy-path.js +24 -24
  159. package/dist/middleware/validate-fields.d.ts +7 -4
  160. package/dist/middleware/validate-fields.d.ts.map +1 -0
  161. package/dist/middleware/validate-fields.js +22 -11
  162. package/dist/routes/ancillary.d.ts +1 -0
  163. package/dist/routes/ancillary.d.ts.map +1 -0
  164. package/dist/routes/ancillary.js +3 -10
  165. package/dist/routes/journey.d.ts +7 -2
  166. package/dist/routes/journey.d.ts.map +1 -0
  167. package/dist/routes/journey.js +56 -55
  168. package/dist/routes/static.d.ts +1 -0
  169. package/dist/routes/static.d.ts.map +1 -0
  170. package/dist/routes/static.js +15 -23
  171. package/package.json +30 -36
  172. package/src/casa.js +63 -31
  173. package/src/core-plugins/edit-snapshot/src/post-steer-hook.js +1 -0
  174. package/src/core-plugins/edit-snapshot/src/pre-steer-hook.js +2 -1
  175. package/src/core-plugins/edit-snapshot/src/utils.js +29 -1
  176. package/src/lib/JourneyContext.js +31 -28
  177. package/src/lib/MutableRouter.js +52 -38
  178. package/src/lib/NullObject.js +4 -0
  179. package/src/lib/Plan.js +41 -55
  180. package/src/lib/ValidationError.js +2 -4
  181. package/src/lib/ValidatorFactory.js +3 -5
  182. package/src/lib/configuration-ingestor.js +18 -38
  183. package/src/lib/configure.js +7 -10
  184. package/src/lib/end-session.js +1 -1
  185. package/src/lib/field.js +7 -12
  186. package/src/lib/logger.js +16 -0
  187. package/src/lib/mount.js +1 -1
  188. package/src/lib/nunjucks-filters.js +51 -61
  189. package/src/lib/nunjucks.js +5 -13
  190. package/src/lib/utils.js +2 -1
  191. package/src/lib/validators/dateObject.js +3 -4
  192. package/src/lib/validators/postalAddressObject.js +6 -7
  193. package/src/lib/validators/required.js +1 -3
  194. package/src/lib/waypoint-url.js +15 -19
  195. package/src/middleware/csrf.js +1 -1
  196. package/src/middleware/data.js +13 -13
  197. package/src/middleware/gather-fields.js +8 -2
  198. package/src/middleware/i18n.js +9 -6
  199. package/src/middleware/post.js +3 -1
  200. package/src/middleware/pre.js +1 -1
  201. package/src/middleware/progress-journey.js +8 -0
  202. package/src/middleware/sanitise-fields.js +17 -2
  203. package/src/middleware/session.js +53 -12
  204. package/src/middleware/skip-waypoint.js +8 -1
  205. package/src/middleware/steer-journey.js +1 -1
  206. package/src/middleware/strip-proxy-path.js +21 -16
  207. package/src/middleware/validate-fields.js +19 -0
  208. package/src/routes/journey.js +18 -8
  209. package/src/routes/static.js +5 -4
  210. package/views/casa/layouts/journey.njk +1 -1
  211. package/views/casa/layouts/main.njk +11 -21
  212. package/dist/casa.js.map +0 -1
  213. package/dist/core-plugins/edit-snapshot/src/configure.js.map +0 -1
  214. package/dist/core-plugins/edit-snapshot/src/index.js.map +0 -1
  215. package/dist/core-plugins/edit-snapshot/src/post-steer-hook.js.map +0 -1
  216. package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.js.map +0 -1
  217. package/dist/core-plugins/edit-snapshot/src/utils.js.map +0 -1
  218. package/dist/core-plugins/index.js.map +0 -1
  219. package/dist/lib/CasaTemplateLoader.js.map +0 -1
  220. package/dist/lib/JourneyContext.js.map +0 -1
  221. package/dist/lib/MutableRouter.js.map +0 -1
  222. package/dist/lib/Plan.js.map +0 -1
  223. package/dist/lib/ValidationError.js.map +0 -1
  224. package/dist/lib/ValidatorFactory.js.map +0 -1
  225. package/dist/lib/configuration-ingestor.js.map +0 -1
  226. package/dist/lib/configure.js.map +0 -1
  227. package/dist/lib/constants.js.map +0 -1
  228. package/dist/lib/context-id-generators.js.map +0 -1
  229. package/dist/lib/dirname.cjs +0 -1
  230. package/dist/lib/dirname.d.cts +0 -2
  231. package/dist/lib/end-session.js.map +0 -1
  232. package/dist/lib/field.js.map +0 -1
  233. package/dist/lib/index.js.map +0 -1
  234. package/dist/lib/logger.js.map +0 -1
  235. package/dist/lib/mount.js.map +0 -1
  236. package/dist/lib/nunjucks-filters.js.map +0 -1
  237. package/dist/lib/nunjucks.js.map +0 -1
  238. package/dist/lib/utils.js.map +0 -1
  239. package/dist/lib/validators/dateObject.js.map +0 -1
  240. package/dist/lib/validators/email.js.map +0 -1
  241. package/dist/lib/validators/inArray.js.map +0 -1
  242. package/dist/lib/validators/index.js.map +0 -1
  243. package/dist/lib/validators/nino.js.map +0 -1
  244. package/dist/lib/validators/postalAddressObject.js.map +0 -1
  245. package/dist/lib/validators/range.js.map +0 -1
  246. package/dist/lib/validators/regex.js.map +0 -1
  247. package/dist/lib/validators/required.js.map +0 -1
  248. package/dist/lib/validators/strlen.js.map +0 -1
  249. package/dist/lib/validators/wordCount.js.map +0 -1
  250. package/dist/lib/waypoint-url.js.map +0 -1
  251. package/dist/middleware/body-parser.js.map +0 -1
  252. package/dist/middleware/csrf.js.map +0 -1
  253. package/dist/middleware/data.js.map +0 -1
  254. package/dist/middleware/dirname.cjs +0 -1
  255. package/dist/middleware/dirname.d.cts +0 -2
  256. package/dist/middleware/gather-fields.js.map +0 -1
  257. package/dist/middleware/i18n.js.map +0 -1
  258. package/dist/middleware/post.js.map +0 -1
  259. package/dist/middleware/pre.js.map +0 -1
  260. package/dist/middleware/progress-journey.js.map +0 -1
  261. package/dist/middleware/sanitise-fields.js.map +0 -1
  262. package/dist/middleware/serve-first-waypoint.js.map +0 -1
  263. package/dist/middleware/session.js.map +0 -1
  264. package/dist/middleware/skip-waypoint.js.map +0 -1
  265. package/dist/middleware/steer-journey.js.map +0 -1
  266. package/dist/middleware/strip-proxy-path.js.map +0 -1
  267. package/dist/middleware/validate-fields.js.map +0 -1
  268. package/dist/mjs/esm-wrapper.js +0 -20
  269. package/dist/mjs/package.json +0 -3
  270. package/dist/package.json +0 -3
  271. package/dist/routes/ancillary.js.map +0 -1
  272. package/dist/routes/dirname.cjs +0 -1
  273. package/dist/routes/dirname.d.cts +0 -2
  274. package/dist/routes/journey.js.map +0 -1
  275. package/dist/routes/static.js.map +0 -1
  276. package/src/lib/dirname.cjs +0 -1
  277. package/src/middleware/dirname.cjs +0 -1
  278. package/src/routes/dirname.cjs +0 -1
@@ -1,82 +1,72 @@
1
- import merge from "deepmerge";
1
+ import deepmerge from "@fastify/deepmerge";
2
2
  import { DateTime } from "luxon";
3
3
  import nunjucks from "nunjucks";
4
+ import isPlainObject from "is-plain-obj";
5
+ import NullObject from "./NullObject.js";
4
6
 
5
- const { all: deepmergeAll } = merge;
6
-
7
- // Arrays will be merged such that elements at the same index will be merged
8
- // into each other
9
- // ref: https://www.npmjs.com/package/deepmerge
10
-
11
- const combineMerge = (target, source, options) => {
12
- const destination = target.slice();
13
-
14
- for (let index = 0; index < source.length; index++) {
15
- // ESLint disabled as `index` is only an integer
16
- /* eslint-disable security/detect-object-injection */
17
- const item = source[index];
18
-
19
- if (typeof destination[index] === "undefined") {
20
- destination[index] = options.cloneUnlessOtherwiseSpecified(item, options);
21
- } else if (options.isMergeableObject(item)) {
22
- destination[index] = merge(target[index], item, options);
23
- } else if (target.indexOf(item) === -1) {
24
- destination.push(item);
25
- }
26
- /* eslint-enable security/detect-object-injection */
27
- }
28
- return destination;
29
- };
30
-
31
- // Allows objects to be deepmerged and retain their type, without becoming [object Object]
32
- // ref: https://github.com/jonschlinkert/is-plain-object/blob/master/is-plain-object.js
33
-
34
- /**
35
- * @param {any} o Value to test
36
- * @returns {boolean} True if an object
37
- */
38
- function isObject(o) {
39
- return Object.prototype.toString.call(o) === "[object Object]";
40
- }
7
+ const merge = deepmerge({
8
+ all: true,
9
+ onlyDefinedProperties: true,
10
+ isMergeableObject: isMergable,
11
+ mergeArray(options) {
12
+ const deepmerge = options.deepmerge;
13
+ const clone = options.clone;
14
+ return function (target, source) {
15
+ let i = 0;
16
+ const tl = target.length;
17
+ const sl = source.length;
18
+ const il = Math.max(tl, sl);
19
+ const result = new Array(il);
20
+ /* eslint-disable security/detect-object-injection */
21
+ for (i = 0; i < il; ++i) {
22
+ const targetItem = target[i];
23
+ const sourceItem = source[i];
24
+ if (i < sl) {
25
+ result[i] = deepmerge(targetItem, sourceItem);
26
+ } else {
27
+ result[i] = clone(targetItem);
28
+ }
29
+ }
30
+ /* eslint-enable security/detect-object-injection */
31
+ return result;
32
+ };
33
+ },
34
+ });
41
35
 
42
36
  /**
43
- * @param {any} o Value to test
44
- * @returns {boolean} True if a plain object or array
37
+ * Returns true if object is mergable (plain object or Array)
38
+ *
39
+ * @param {object} object Input object
40
+ * @returns {boolean} Is mergable
45
41
  */
46
- function isPlainObjectOrArray(o) {
47
- if (Array.isArray(o)) {
48
- return true;
49
- }
50
- if (isObject(o) === false) {
51
- return false;
52
- }
53
- const ctor = o.constructor;
54
- if (ctor === undefined) {
55
- return true;
56
- }
57
- const prot = ctor.prototype;
58
- if (isObject(prot) === false) {
59
- return false;
60
- }
61
- return Object.hasOwn(prot, "isPrototypeOf");
42
+ function isMergable(object) {
43
+ return Array.isArray(object) || isPlainObject(object);
62
44
  }
63
45
 
64
46
  /**
65
- * @param {...any} objects Objects to merge
47
+ * Merge multiple objects into one object
48
+ *
49
+ * @memberof NunjucksFilters
50
+ * @param {...(object | Array)} objects Objects to merge
66
51
  * @returns {object} Merged object
67
52
  */
68
53
  function mergeObjects(...objects) {
69
- return deepmergeAll([Object.create(null), ...objects], {
70
- arrayMerge: combineMerge,
71
- isMergeableObject: isPlainObjectOrArray,
72
- });
54
+ const merged = merge(new NullObject(), ...objects);
55
+ if (merged === null) {
56
+ for (const object of objects) {
57
+ if (!isMergable(object)) {
58
+ throw new TypeError(`Cannot convert ${typeof object} to object`);
59
+ }
60
+ }
61
+ }
62
+ return merged;
73
63
  }
74
64
 
75
65
  /**
76
66
  * Determine whether a value exists in a list.
77
67
  *
78
68
  * @memberof NunjucksFilters
79
- * @param {any[]} source List of items to search
69
+ * @param {Array} source List of items to search
80
70
  * @param {any} search Item to search within the `source`
81
71
  * @returns {boolean} True if the search item was found
82
72
  */
@@ -1,7 +1,5 @@
1
- import { readFileSync } from "node:fs";
2
- import { resolve } from "node:path";
3
1
  import { Environment } from "nunjucks";
4
- import dirname from "./dirname.cjs";
2
+ import packageInfo from "../../package.json" with { type: "json" };
5
3
  import CasaTemplateLoader from "./CasaTemplateLoader.js";
6
4
  import {
7
5
  mergeObjects,
@@ -20,11 +18,11 @@ import {
20
18
  * Create a Nunjucks environment.
21
19
  *
22
20
  * @param {NunjucksOptions} options Nunjucks options
23
- * @param {boolean} govukRebrand GovukRebrand flag
24
- * @returns {Environment} Nunjucks Environment instance
21
+ * @returns {import("../casa.js").CasaNunjucksEnvironment} Nunjucks Environment
22
+ * instance
25
23
  * @access private
26
24
  */
27
- export default function nunjucksConfig({ views = [], govukRebrand }) {
25
+ export default function nunjucksConfig({ views = [] }) {
28
26
  // Prepare a single Nunjucks environment for all responses to use. Note that
29
27
  // we cannot prepare response-specific global functions/filters if we use a
30
28
  // single environment, but the performance gains of doing so are significant.
@@ -45,13 +43,7 @@ export default function nunjucksConfig({ views = [], govukRebrand }) {
45
43
 
46
44
  // Globals
47
45
  // These can't be modified once set. But they can be overridden by res.locals.
48
- env.addGlobal(
49
- "casaVersion",
50
- /* eslint-disable-next-line security/detect-non-literal-fs-filename */
51
- JSON.parse(readFileSync(resolve(dirname, "../../package.json"))).version,
52
- );
53
-
54
- env.addGlobal("govukRebrand", govukRebrand);
46
+ env.addGlobal("casaVersion", packageInfo.version);
55
47
  env.addGlobal("mergeObjects", mergeObjects);
56
48
  env.addGlobal("includes", includes);
57
49
  env.addGlobal("formatDateObject", formatDateObject);
package/src/lib/utils.js CHANGED
@@ -44,7 +44,8 @@ export function isStringable(value) {
44
44
  * @param {string} hookName Hook name (including scope prefix)
45
45
  * @param {string} path URL path to match (relative to mountUrl)
46
46
  * @param {Hook[]} hooks Hooks to be applied at the page level
47
- * @returns {Function[]} An array of middleware that should be applied
47
+ * @returns {import("express").RequestHandler[]} An array of middleware that
48
+ * should be applied
48
49
  * @access private
49
50
  */
50
51
  export function resolveMiddlewareHooks(hookName, path, hooks = []) {
@@ -1,11 +1,10 @@
1
+ import isPlainObject from "is-plain-obj";
1
2
  import { DateTime } from "luxon";
2
- import lodash from "lodash";
3
+ import NullObject from "../NullObject.js";
3
4
  import ValidationError from "../ValidationError.js";
4
5
  import ValidatorFactory from "../ValidatorFactory.js";
5
6
  import { stringifyInput, stripWhitespace } from "../utils.js";
6
7
 
7
- const { isPlainObject } = lodash;
8
-
9
8
  /**
10
9
  * @typedef {import("../../casa").ErrorMessageConfig} ErrorMessageConfig
11
10
  * @access private
@@ -172,7 +171,7 @@ export default class DateObject extends ValidatorFactory {
172
171
  mm: stripWhitespace(stringifyInput(value.mm)),
173
172
  yyyy: stripWhitespace(stringifyInput(value.yyyy)),
174
173
  }
175
- : Object.create(null);
174
+ : new NullObject();
176
175
  }
177
176
  return undefined;
178
177
  }
@@ -1,10 +1,9 @@
1
- import lodash from "lodash";
1
+ import isPlainObject from "is-plain-obj";
2
+ import NullObject from "../NullObject.js";
2
3
  import ValidationError from "../ValidationError.js";
3
4
  import ValidatorFactory from "../ValidatorFactory.js";
4
5
  import { stringifyInput } from "../utils.js";
5
6
 
6
- const { isPlainObject } = lodash; // CommonjS
7
-
8
7
  /**
9
8
  * @typedef {import("../../casa").ErrorMessageConfig} ErrorMessageConfig
10
9
  * @access private
@@ -91,7 +90,7 @@ export default class PostalAddressObject extends ValidatorFactory {
91
90
  : err;
92
91
 
93
92
  // Work out required/optional parts based on config
94
- const reqF = Object.create(null);
93
+ const reqF = new NullObject();
95
94
  const reqC = cfg.requiredFields;
96
95
  reqF.address1 = reqC.indexOf("address1") > -1;
97
96
  reqF.address2 = reqC.indexOf("address2") > -1;
@@ -140,7 +139,7 @@ export default class PostalAddressObject extends ValidatorFactory {
140
139
  if (condMissingOrRegexMismatch || condExceedStrlen) {
141
140
  valid = false;
142
141
  errorMsgs.push(
143
- Object.assign(Object.create(null), objectifyError(attr[3]), {
142
+ Object.assign(new NullObject(), objectifyError(attr[3]), {
144
143
  fieldKeySuffix: `[${k}]`,
145
144
  }),
146
145
  );
@@ -163,7 +162,7 @@ export default class PostalAddressObject extends ValidatorFactory {
163
162
  sanitise(value) {
164
163
  // Only objects are supported
165
164
  if (!isPlainObject(value)) {
166
- return Object.create(null);
165
+ return new NullObject();
167
166
  }
168
167
 
169
168
  // Prune unrecognised attributes, and coerce to Strings
@@ -179,6 +178,6 @@ export default class PostalAddressObject extends ValidatorFactory {
179
178
  .filter(([k]) => validKeys.includes(k))
180
179
  .map(([k, v]) => [k, stringifyInput(v)]),
181
180
  );
182
- return Object.assign(Object.create(null), pruned);
181
+ return Object.assign(new NullObject(), pruned);
183
182
  }
184
183
  }
@@ -1,10 +1,8 @@
1
- import lodash from "lodash";
1
+ import isPlainObject from "is-plain-obj";
2
2
  import { isEmpty, isStringable, stringifyInput } from "../utils.js";
3
3
  import ValidatorFactory from "../ValidatorFactory.js";
4
4
  import ValidationError from "../ValidationError.js";
5
5
 
6
- const { isPlainObject } = lodash; // CommonJS
7
-
8
6
  /**
9
7
  * @typedef {import("../../casa").ErrorMessageConfig} ErrorMessageConfig
10
8
  * @access private
@@ -1,3 +1,5 @@
1
+ import NullObject from "./NullObject.js";
2
+
1
3
  /**
2
4
  * @typedef {import("./index").JourneyContext} JourneyContext
3
5
  * @access private
@@ -14,7 +16,7 @@ const reUrlProtocolExtract = /^url:\/\/(.+)$/i;
14
16
  * @access private
15
17
  */
16
18
  const sanitiseWaypoint = (w) =>
17
- w.replace(/[^/a-z0-9_-]/gi, "").replace(/\/+/g, "/");
19
+ w.replace(/[^/a-z0-9_-]+/gi, "").replace(/\/+/g, "/");
18
20
 
19
21
  /**
20
22
  * Sanitise a waypoint string, with allowed URL parameters: contextid =
@@ -25,32 +27,26 @@ const sanitiseWaypoint = (w) =>
25
27
  * @access private
26
28
  */
27
29
  const sanitiseWaypointWithAllowedParams = (w) => {
28
- // Extract URL params
29
- const parts = w.split("?");
30
- if (parts.length !== 2) {
30
+ const queryIndex = w.indexOf("?");
31
+ if (queryIndex === -1 || queryIndex !== w.lastIndexOf("?")) {
31
32
  return sanitiseWaypoint(w);
32
33
  }
33
- const [waypoint, rawParams] = parts;
34
- const urlSearchParams = new URLSearchParams(rawParams);
35
-
36
- // Strip all but those parameters allowed
37
- const validatedUrlSearchParams = new URLSearchParams();
38
- for (const pk of ["contextid"]) {
39
- if (urlSearchParams.has(pk)) {
40
- validatedUrlSearchParams.set(pk, urlSearchParams.get(pk));
41
- }
34
+
35
+ const waypoint = w.substring(0, queryIndex);
36
+ const urlSearchParams = new URLSearchParams(w.substring(queryIndex + 1));
37
+
38
+ let query = "";
39
+ if (urlSearchParams.has("contextid")) {
40
+ query += `?contextid=${encodeURIComponent(urlSearchParams.get("contextid"))}`;
42
41
  }
43
42
 
44
- return `${sanitiseWaypoint(waypoint)}?${validatedUrlSearchParams.toString()}`.replace(
45
- /\?$/,
46
- "",
47
- );
43
+ return `${sanitiseWaypoint(waypoint)}${query}`;
48
44
  };
49
45
 
50
46
  /**
51
47
  * Generate a URL pointing at a particular waypoint.
52
48
  *
53
- * @memberof module:@dwp/govuk-casa
49
+ * @memberof module:"@dwp/govuk-casa"
54
50
  * @example
55
51
  * // generates: /path/details?edit&editorigin=%2Fsomewhere%2Felse
56
52
  * waypointUrl({
@@ -80,7 +76,7 @@ export default function waypointUrl(
80
76
  editOrigin,
81
77
  skipTo,
82
78
  routeName = "next",
83
- } = Object.create(null),
79
+ } = new NullObject(),
84
80
  ) {
85
81
  const url = new URL("https://placeholder.test");
86
82
 
@@ -16,7 +16,7 @@ import { csrfSync } from "csrf-sync";
16
16
  */
17
17
  export default function csrfMiddleware() {
18
18
  const { csrfSynchronisedProtection } = csrfSync({
19
- getTokenFromRequest: (req) => req.body._csrf,
19
+ getTokenFromRequest: (req) => req.body?._csrf,
20
20
  });
21
21
  return [
22
22
  csrfSynchronisedProtection,
@@ -22,8 +22,14 @@ import waypointUrl from "../lib/waypoint-url.js";
22
22
  * @access private
23
23
  */
24
24
 
25
+ /**
26
+ * Get edit orgin
27
+ *
28
+ * @param {import("express").Request} req Express request
29
+ * @returns {string} Currently set edit origin
30
+ */
25
31
  const editOrigin = (req) => {
26
- if (Object.hasOwn(req.query, "editorigin")) {
32
+ if (req.query && Object.hasOwn(req.query, "editorigin")) {
27
33
  return waypointUrl({ waypoint: req.query.editorigin });
28
34
  }
29
35
  if (req.body && Object.hasOwn(req.body, "editorigin")) {
@@ -43,16 +49,9 @@ const editOrigin = (req) => {
43
49
  * @param {Plan} opts.plan CASA Plan
44
50
  * @param {ContextEventHandler[]} opts.events Event handlers
45
51
  * @param {ContextIdGenerator} opts.contextIdGenerator Content ID generator
46
- * @param {boolean} ops.govukRebrand Govuk rebrand feature flag
47
- * @param opts.govukRebrand
48
52
  * @returns {RequestHandler[]} Middleware functions
49
53
  */
50
- export default function dataMiddleware({
51
- plan,
52
- events,
53
- contextIdGenerator,
54
- govukRebrand,
55
- }) {
54
+ export default function dataMiddleware({ plan, events, contextIdGenerator }) {
56
55
  return [
57
56
  (req, res, next) => {
58
57
  /* ------------------------------------------------ Request decorations */
@@ -73,9 +72,11 @@ export default function dataMiddleware({
73
72
 
74
73
  // Edit mode
75
74
  editMode:
76
- (Object.hasOwn(req.query, "edit") &&
75
+ (req.query &&
76
+ Object.hasOwn(req.query, "edit") &&
77
77
  Object.hasOwn(req.query, "editorigin")) ||
78
- (Object.hasOwn(req.body, "edit") &&
78
+ (req.body &&
79
+ Object.hasOwn(req.body, "edit") &&
79
80
  Object.hasOwn(req.body, "editorigin")),
80
81
  editOrigin: editOrigin(req),
81
82
  };
@@ -131,8 +132,7 @@ export default function dataMiddleware({
131
132
  // htmlLang = req.language is provided by i18n-http-middleware
132
133
  // assetPath = used for linking to static assets in the govuk-frontend module
133
134
  res.locals.htmlLang = req.language;
134
- const rebrandAssetPath = govukRebrand ? "/rebrand" : "";
135
- res.locals.assetPath = `${staticMountUrl}govuk/assets${rebrandAssetPath}`;
135
+ res.locals.assetPath = `${staticMountUrl}govuk/assets`;
136
136
 
137
137
  // Function for building URLs. This will be curried with the `mountUrl`,
138
138
  // `journeyContext`, `edit` and `editOrigin` for convenience. This means
@@ -4,6 +4,7 @@
4
4
  // - Remove validation date of JourneyContext so it can re-evaluted
5
5
 
6
6
  import JourneyContext from "../lib/JourneyContext.js";
7
+ import NullObject from "../lib/NullObject.js";
7
8
  import { REQUEST_PHASE_GATHER } from "../lib/constants.js";
8
9
 
9
10
  /**
@@ -11,6 +12,11 @@ import { REQUEST_PHASE_GATHER } from "../lib/constants.js";
11
12
  * @access private
12
13
  */
13
14
 
15
+ /**
16
+ * @typedef {import("express").RequestHandler} RequestHandler
17
+ * @access private
18
+ */
19
+
14
20
  /**
15
21
  * Gather the field data from `req.body` into the current JourneyContext
16
22
  *
@@ -21,7 +27,7 @@ import { REQUEST_PHASE_GATHER } from "../lib/constants.js";
21
27
  * @param {object} obj Options
22
28
  * @param {string} obj.waypoint Waypoint
23
29
  * @param {PageField[]} [obj.fields] Fields. Default is `[]`
24
- * @returns {Array} Array of middleware
30
+ * @returns {RequestHandler[]} Array of middleware
25
31
  */
26
32
  export default ({ waypoint, fields = [] }) => [
27
33
  (req, res, next) => {
@@ -35,7 +41,7 @@ export default ({ waypoint, fields = [] }) => [
35
41
  // Ignore data for any non-persistent fields
36
42
  // ESLint disabled as `fields`, `i` and `name` are dev-controlled
37
43
  /* eslint-disable security/detect-object-injection */
38
- const persistentBody = Object.create(null);
44
+ const persistentBody = new NullObject();
39
45
  for (let i = 0, l = fields.length; i < l; i++) {
40
46
  if (
41
47
  fields[i].meta.persist &&
@@ -2,9 +2,10 @@ import { createInstance } from "i18next";
2
2
  import { LanguageDetector, handle } from "i18next-http-middleware";
3
3
  import { resolve, basename } from "node:path";
4
4
  import { existsSync, readFileSync, readdirSync } from "node:fs";
5
- import deepmerge from "deepmerge";
5
+ import deepmerge from "@fastify/deepmerge";
6
6
  import { load as jsYamlLoad } from "js-yaml";
7
7
  import logger from "../lib/logger.js";
8
+ import NullObject from "../lib/NullObject.js";
8
9
 
9
10
  /**
10
11
  * @typedef {import("express").RequestHandler} RequestHandler
@@ -13,6 +14,8 @@ import logger from "../lib/logger.js";
13
14
 
14
15
  const log = logger("middleware:i18n");
15
16
 
17
+ const merge = deepmerge({ onlyDefinedProperties: true });
18
+
16
19
  const loadJson = (file) => {
17
20
  // Strip out newlines (this is a legacy feature which we're keeping for
18
21
  // backwards compatibility).
@@ -35,13 +38,13 @@ const extract = (file) => {
35
38
  };
36
39
 
37
40
  const loadResources = (languages, directories) => {
38
- const store = Object.create(null);
41
+ const store = new NullObject();
39
42
 
40
43
  for (const language of languages) {
41
44
  // ESLint disabled as `store`, `language` and `ns` are all dev-controlled,
42
45
  // and this function is only called once, at boot-time.
43
46
  /* eslint-disable security/detect-object-injection */
44
- store[language] = Object.create(null);
47
+ store[language] = new NullObject();
45
48
 
46
49
  for (const basedir of directories) {
47
50
  const dir = resolve(basedir, language);
@@ -56,10 +59,10 @@ const loadResources = (languages, directories) => {
56
59
  const { ns, data } = extract(resolve(dir, file));
57
60
 
58
61
  if (store[language][ns] === undefined) {
59
- store[language][ns] = Object.create(null);
62
+ store[language][ns] = new NullObject();
60
63
  }
61
64
 
62
- store[language][ns] = deepmerge(store[language][ns], data);
65
+ store[language][ns] = merge(store[language][ns], data);
63
66
  }
64
67
  }
65
68
  /* eslint-enable security/detect-object-injection */
@@ -115,7 +118,7 @@ export default function i18nMiddleware({
115
118
  if (!req.session.language) {
116
119
  req.session.language = languages[0];
117
120
  }
118
- if (req?.query.lang && languages.includes(req.query.lang)) {
121
+ if (req.query?.lang && languages.includes(req.query.lang)) {
119
122
  req.session.language = String(req.query.lang);
120
123
  }
121
124
  next();
@@ -3,12 +3,14 @@ import logger from "../lib/logger.js";
3
3
 
4
4
  /**
5
5
  * @typedef {import("express").RequestHandler} RequestHandler
6
+ *
7
+ * @typedef {import("express").ErrorRequestHandler} ErrorRequestHandler
6
8
  * @access private
7
9
  */
8
10
 
9
11
  const log = logger("middleware:post");
10
12
 
11
- /** @returns {RequestHandler[]} Middleware functions */
13
+ /** @returns {[RequestHandler, ErrorRequestHandler]} Middleware functions */
12
14
  export default function postMiddleware() {
13
15
  return [
14
16
  (req, res) => {
@@ -33,7 +33,7 @@ function casaCspNonce(req, res) {
33
33
  * @param {object} opts Options
34
34
  * @param {HelmetConfigurator} opts.helmetConfigurator Function to customise
35
35
  * Helmet configuration
36
- * @returns {Function[]} List of middleware
36
+ * @returns {import("express").RequestHandler[]} List of middleware
37
37
  */
38
38
  export default ({ helmetConfigurator = (config) => config } = {}) => [
39
39
  // Only allow certain request methods
@@ -25,6 +25,14 @@ const saveAndRedirect = (session, journeyContext, url, res, next) => {
25
25
  });
26
26
  };
27
27
 
28
+ /**
29
+ * Get "progress journey" middleware for a given waypoint
30
+ *
31
+ * @param {object} opts Options
32
+ * @param {string} opts.waypoint Waypoint
33
+ * @param {Plan} opts.plan CASA Plan
34
+ * @returns {import("express").RequestHandler[]} Middleware functions
35
+ */
28
36
  export default ({ waypoint, plan }) => [
29
37
  (req, res, next) => {
30
38
  // Determine the next available waypoint after the current one
@@ -4,7 +4,16 @@
4
4
 
5
5
  import fieldFactory from "../lib/field.js";
6
6
  import JourneyContext from "../lib/JourneyContext.js";
7
+ import NullObject from "../lib/NullObject.js";
7
8
 
9
+ /**
10
+ * Get "sanitise fields" middleware for a given waypoint
11
+ *
12
+ * @param {object} opts Options
13
+ * @param {string} opts.waypoint Waypoint
14
+ * @param {PageField[]} opts.fields Fields to sanitise
15
+ * @returns {import("express").RequestHandler[]} Middleware functions
16
+ */
8
17
  export default ({ waypoint, fields = [] }) => {
9
18
  // Add some common, transient fields to ensure they survive beyond this sanitisation process
10
19
  fields.push(
@@ -35,9 +44,10 @@ export default ({ waypoint, fields = [] }) => {
35
44
  // those that do not have an entry in `fields`)
36
45
  // EsLint disabled as `fields`, `i` & `name` are only controlled by dev
37
46
  /* eslint-disable security/detect-object-injection */
38
- const prunedBody = Object.create(null);
47
+ const prunedBody = new NullObject();
39
48
  for (let i = 0, l = fields.length; i < l; i++) {
40
49
  if (
50
+ req.body &&
41
51
  Object.hasOwn(req.body, fields[i].name) &&
42
52
  req.body[fields[i].name] !== undefined
43
53
  ) {
@@ -54,7 +64,7 @@ export default ({ waypoint, fields = [] }) => {
54
64
 
55
65
  // Second, prune any fields that do not pass the validation conditional,
56
66
  // and process those that do.
57
- const sanitisedBody = Object.create(null);
67
+ const sanitisedBody = new NullObject();
58
68
  for (let i = 0, l = fields.length; i < l; i++) {
59
69
  const field =
60
70
  fields[i]; /* eslint-disable-line security/detect-object-injection */
@@ -78,3 +88,8 @@ export default ({ waypoint, fields = [] }) => {
78
88
  },
79
89
  ];
80
90
  };
91
+
92
+ /**
93
+ * @typedef {import("../casa").PageField} PageField
94
+ * @access private
95
+ */