@dwp/govuk-casa 8.12.0 → 8.14.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 (68) hide show
  1. package/dist/casa.d.ts +7 -1
  2. package/dist/casa.js +1 -0
  3. package/dist/casa.js.map +1 -1
  4. package/dist/lib/CasaTemplateLoader.js.map +1 -1
  5. package/dist/lib/JourneyContext.d.ts +1 -1
  6. package/dist/lib/JourneyContext.js +6 -6
  7. package/dist/lib/JourneyContext.js.map +1 -1
  8. package/dist/lib/MutableRouter.js.map +1 -1
  9. package/dist/lib/Plan.js.map +1 -1
  10. package/dist/lib/ValidationError.js.map +1 -1
  11. package/dist/lib/ValidatorFactory.js.map +1 -1
  12. package/dist/lib/configuration-ingestor.d.ts +10 -1
  13. package/dist/lib/configuration-ingestor.js +25 -1
  14. package/dist/lib/configuration-ingestor.js.map +1 -1
  15. package/dist/lib/configure.js +4 -1
  16. package/dist/lib/configure.js.map +1 -1
  17. package/dist/lib/constants.d.ts +2 -0
  18. package/dist/lib/constants.js +3 -1
  19. package/dist/lib/constants.js.map +1 -1
  20. package/dist/lib/context-id-generators.js.map +1 -1
  21. package/dist/lib/end-session.js.map +1 -1
  22. package/dist/lib/field.js +1 -1
  23. package/dist/lib/field.js.map +1 -1
  24. package/dist/lib/mount.js.map +1 -1
  25. package/dist/lib/nunjucks-filters.js.map +1 -1
  26. package/dist/lib/nunjucks.js +1 -0
  27. package/dist/lib/nunjucks.js.map +1 -1
  28. package/dist/lib/utils.js.map +1 -1
  29. package/dist/lib/validators/dateObject.js.map +1 -1
  30. package/dist/lib/validators/email.js.map +1 -1
  31. package/dist/lib/validators/inArray.js.map +1 -1
  32. package/dist/lib/validators/nino.js.map +1 -1
  33. package/dist/lib/validators/postalAddressObject.js.map +1 -1
  34. package/dist/lib/validators/range.js.map +1 -1
  35. package/dist/lib/validators/regex.js.map +1 -1
  36. package/dist/lib/validators/required.js.map +1 -1
  37. package/dist/lib/validators/strlen.js.map +1 -1
  38. package/dist/lib/validators/wordCount.js.map +1 -1
  39. package/dist/lib/waypoint-url.js.map +1 -1
  40. package/dist/middleware/body-parser.js.map +1 -1
  41. package/dist/middleware/data.js.map +1 -1
  42. package/dist/middleware/gather-fields.js.map +1 -1
  43. package/dist/middleware/i18n.js +7 -3
  44. package/dist/middleware/i18n.js.map +1 -1
  45. package/dist/middleware/post.js.map +1 -1
  46. package/dist/middleware/pre.js.map +1 -1
  47. package/dist/middleware/progress-journey.js.map +1 -1
  48. package/dist/middleware/sanitise-fields.js.map +1 -1
  49. package/dist/middleware/session.js.map +1 -1
  50. package/dist/middleware/skip-waypoint.js.map +1 -1
  51. package/dist/middleware/steer-journey.js.map +1 -1
  52. package/dist/middleware/strip-proxy-path.js.map +1 -1
  53. package/dist/middleware/validate-fields.js.map +1 -1
  54. package/dist/routes/journey.d.ts +1 -1
  55. package/dist/routes/journey.js +35 -10
  56. package/dist/routes/journey.js.map +1 -1
  57. package/dist/routes/static.js +2 -0
  58. package/dist/routes/static.js.map +1 -1
  59. package/package.json +32 -30
  60. package/src/casa.js +1 -0
  61. package/src/lib/JourneyContext.js +6 -6
  62. package/src/lib/configuration-ingestor.js +26 -0
  63. package/src/lib/configure.js +5 -0
  64. package/src/lib/constants.js +2 -0
  65. package/src/lib/nunjucks.js +1 -0
  66. package/src/middleware/i18n.js +6 -2
  67. package/src/routes/journey.js +36 -9
  68. package/src/routes/static.js +2 -0
@@ -53,8 +53,10 @@ function staticRouter({ maxAge = 3600000, } = {}) {
53
53
  // The CASA CSS source contains the placeholder `~~~CASA_MOUNT_URL~~~` which
54
54
  // must be replaced with the dynamic `mountUrl` to ensure govuk-frontend
55
55
  // assets are served from the correct location.
56
+ /* eslint-disable security/detect-non-literal-fs-filename */
56
57
  const casaCss = (0, fs_1.readFileSync)((0, path_1.resolve)(dirname_cjs_1.default, '../../dist/assets/css/casa.css'), { encoding: 'utf8' });
57
58
  const casaCssIe8 = (0, fs_1.readFileSync)((0, path_1.resolve)(dirname_cjs_1.default, '../../dist/assets/css/casa-ie8.css'), { encoding: 'utf8' });
59
+ /* eslint-enable security/detect-non-literal-fs-filename */
58
60
  // The static middleware will only server GET/HEAD requests, so we can mount
59
61
  // the middleware using `use()` rather than resorting to `get()`
60
62
  const govukFrontendDirectory = (0, path_1.resolve)((0, module_1.createRequire)(dirname_cjs_1.default).resolve('govuk-frontend'), '../../');
@@ -1 +1 @@
1
- {"version":3,"file":"static.js","sourceRoot":"","sources":["../../src/routes/static.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"static.js","sourceRoot":"","sources":["../../src/routes/static.js"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dwp/govuk-casa",
3
- "version": "8.12.0",
3
+ "version": "8.14.0",
4
4
  "description": "A framework for building GOVUK Collect-And-Submit-Applications",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,8 +20,8 @@
20
20
  "views/**/*"
21
21
  ],
22
22
  "engines": {
23
- "node": "^16.0.0 || ^18.0.0 || ^20.0.0",
24
- "npm": "^8.0.0 || ^9.0.0"
23
+ "node": "^18.0.0 || ^20.0.0",
24
+ "npm": "^8.0.0 || ^9.0.0 || ^10.0.0"
25
25
  },
26
26
  "scripts": {
27
27
  "pipeline": "npm run coverage && npm run lint",
@@ -57,46 +57,48 @@
57
57
  "express": "4.18.2",
58
58
  "express-session": "1.17.3",
59
59
  "govuk-frontend": "4.7.0",
60
- "helmet": "7.0.0",
61
- "i18next": "23.2.10",
62
- "i18next-http-middleware": "3.3.2",
60
+ "helmet": "7.1.0",
61
+ "i18next": "23.7.6",
62
+ "i18next-http-middleware": "3.5.0",
63
63
  "js-yaml": "4.1.0",
64
64
  "lodash": "4.17.21",
65
- "luxon": "3.3.0",
65
+ "luxon": "3.4.4",
66
66
  "nunjucks": "3.2.4",
67
67
  "path-to-regexp": "6.2.1",
68
- "validator": "13.9.0"
68
+ "validator": "13.11.0"
69
69
  },
70
70
  "devDependencies": {
71
- "@babel/core": "7.22.8",
72
- "@babel/eslint-parser": "7.22.7",
73
- "@babel/preset-env": "7.22.7",
74
- "@ckeditor/jsdoc-plugins": "38.1.1",
75
- "@commitlint/config-conventional": "17.6.6",
76
- "@dwp/casa-spiderplan": "3.1.2",
77
- "@dwp/casa-spiderplan-a11y-plugin": "0.1.13",
78
- "@dwp/casa-spiderplan-zap-plugin": "0.1.9",
79
- "@dwp/eslint-config-base": "6.1.1",
80
- "@types/express": "4.17.17",
81
- "@types/node": "20.4.1",
82
- "@types/nunjucks": "3.2.3",
83
- "c8": "8.0.0",
84
- "chai": "4.3.7",
71
+ "@babel/core": "7.23.3",
72
+ "@babel/eslint-parser": "7.23.3",
73
+ "@babel/preset-env": "7.23.3",
74
+ "@ckeditor/jsdoc-plugins": "39.2.1",
75
+ "@commitlint/config-conventional": "18.4.3",
76
+ "@dwp/casa-spiderplan": "3.1.3",
77
+ "@dwp/casa-spiderplan-a11y-plugin": "0.1.14",
78
+ "@dwp/casa-spiderplan-zap-plugin": "0.1.10",
79
+ "@dwp/eslint-config-base": "7.0.0",
80
+ "@types/express": "4.17.21",
81
+ "@types/node": "20.9.3",
82
+ "@types/nunjucks": "3.2.6",
83
+ "c8": "8.0.1",
84
+ "chai": "4.3.10",
85
85
  "cheerio": "1.0.0-rc.12",
86
- "commitlint": "17.6.6",
87
- "docdash": "2.0.1",
88
- "eslint": "8.44.0",
86
+ "commitlint": "18.4.3",
87
+ "docdash": "2.0.2",
88
+ "eslint": "8.54.0",
89
+ "eslint-plugin-import": "2.29.0",
90
+ "eslint-plugin-jsdoc": "46.9.0",
89
91
  "eslint-plugin-no-unsafe-regex": "1.0.0",
90
92
  "eslint-plugin-security": "1.7.1",
91
- "eslint-plugin-sonarjs": "0.19.0",
92
- "fast-check": "3.11.0",
93
+ "eslint-plugin-sonarjs": "0.23.0",
94
+ "fast-check": "3.14.0",
93
95
  "jsdoc": "4.0.2",
94
96
  "jsdoc-tsimport-plugin": "1.0.5",
95
97
  "mocha": "10.2.0",
96
- "sass": "1.63.6",
97
- "sinon": "15.2.0",
98
+ "sass": "1.69.5",
99
+ "sinon": "17.0.1",
98
100
  "sinon-chai": "3.7.0",
99
101
  "supertest": "6.3.3",
100
- "typescript": "5.1.6"
102
+ "typescript": "5.3.2"
101
103
  }
102
104
  }
package/src/casa.js CHANGED
@@ -165,6 +165,7 @@ export {
165
165
  * @property {number} [formMaxParams=25] Max number of form parameters to ingest
166
166
  * @property {number|string} [formMaxBytes="50KB"] Max total form payload size to ingest
167
167
  * @property {ContextIdGenerator} [contextIdGenerator] Custom context ID generator
168
+ * @property {symbol|Function} [errorVisibility] option to keep page errors active on GET request
168
169
  */
169
170
 
170
171
  /**
@@ -1,4 +1,3 @@
1
- /* eslint-disable import/no-cycle */
2
1
  /**
3
2
  * Represents the state of a user's journey through the Plan. It contains
4
3
  * information about:
@@ -14,7 +13,7 @@ import { notProto } from './utils.js';
14
13
  import { uuid as uuidGenerator } from './context-id-generators.js';
15
14
 
16
15
  const {
17
- cloneDeep, isPlainObject, isObject, has, isEqual,
16
+ isPlainObject, isObject, has, isEqual,
18
17
  } = lodash; // CommonJS
19
18
 
20
19
  const log = logger('lib:journey-context');
@@ -127,10 +126,10 @@ export default class JourneyContext {
127
126
  */
128
127
  toObject() {
129
128
  return Object.assign(Object.create(null), {
130
- data: cloneDeep(this.#data),
131
- validation: cloneDeep(this.#validation),
132
- nav: cloneDeep(this.#nav),
133
- identity: cloneDeep(this.#identity),
129
+ data: structuredClone(this.#data),
130
+ validation: structuredClone(this.#validation),
131
+ nav: structuredClone(this.#nav),
132
+ identity: structuredClone(this.#identity),
134
133
  });
135
134
  }
136
135
 
@@ -257,6 +256,7 @@ export default class JourneyContext {
257
256
  * @returns {JourneyContext} Chain.
258
257
  */
259
258
  removeValidationStateForPage(pageId) {
259
+ /* eslint-disable-next-line no-unused-vars */
260
260
  const { [pageId]: dummy, ...remaining } = this.#validation;
261
261
  this.#validation = { ...remaining };
262
262
  return this;
@@ -10,6 +10,7 @@ import {
10
10
  validateView,
11
11
  } from './utils.js';
12
12
  import * as contextIdGenerators from './context-id-generators.js';
13
+ import { CONFIG_ERROR_VISIBILITY_ALWAYS, CONFIG_ERROR_VISIBILITY_ONSUBMIT } from './constants.js';
13
14
 
14
15
  /**
15
16
  * @access private
@@ -256,6 +257,25 @@ export function validateSessionCookiePath(cookiePath, defaultPath = '/') {
256
257
  * @returns {boolean} cookie path
257
258
  * @throws {TypeError} When invalid arguments are provided
258
259
  */
260
+
261
+ /**
262
+ * Validates errorVisibility.
263
+ *
264
+ * @access private
265
+ * @param {string} errorVisibility sets visibility flag for page validation error
266
+ * @throws {SyntaxError} For invalid errorVisibility flag.
267
+ * @returns {symbol | Function} flag for error visibility.
268
+ */
269
+ export function validateErrorVisibility(errorVisibility = CONFIG_ERROR_VISIBILITY_ONSUBMIT) {
270
+ if (errorVisibility === undefined) {
271
+ return undefined;
272
+ }
273
+ if (errorVisibility === CONFIG_ERROR_VISIBILITY_ALWAYS || errorVisibility === CONFIG_ERROR_VISIBILITY_ONSUBMIT || typeof errorVisibility === 'function') {
274
+ return errorVisibility;
275
+ }
276
+ throw new TypeError('errorVisibility must be casa constant CONFIG_ERROR_VISIBILITY_ALWAYS | CONFIG_ERROR_VISIBILITY_ONSUBMIT or function');
277
+ }
278
+
259
279
  export function validateSessionCookieSameSite(cookieSameSite, defaultFlag) {
260
280
  const validValues = [true, false, 'Strict', 'Lax', 'None'];
261
281
 
@@ -322,6 +342,9 @@ const validatePage = (page, index) => {
322
342
  if (page.hooks !== undefined) {
323
343
  validatePageHooks(page.hooks);
324
344
  }
345
+ if (page.errorVisibility !== undefined) {
346
+ validateErrorVisibility(page.errorVisibility)
347
+ }
325
348
  } catch (err) {
326
349
  err.message = `Page at index ${index} is invalid: ${err.message}`;
327
350
  throw err;
@@ -467,6 +490,9 @@ export default function ingest(config = {}) {
467
490
  // URL that will prefix all URLs in the browser address bar
468
491
  mountUrl: validateMountUrl(config.mountUrl),
469
492
 
493
+ // flag to make validation error visible on get requests
494
+ errorVisibility: validateErrorVisibility(config.errorVisibility),
495
+
470
496
  // Session
471
497
  session: validateSessionObject(config.session, (session) => ({
472
498
  name: validateSessionName(session.name),
@@ -22,6 +22,8 @@ import dataMiddlewareFactory from '../middleware/data.js';
22
22
  import bodyParserMiddlewareFactory from '../middleware/body-parser.js';
23
23
  import csrfMiddlewareFactory from '../middleware/csrf.js';
24
24
 
25
+ import { CONFIG_ERROR_VISIBILITY_ONSUBMIT } from './constants.js';
26
+
25
27
  /**
26
28
  * @access private
27
29
  * @typedef {import('../casa').ConfigurationOptions} ConfigurationOptions
@@ -55,6 +57,7 @@ export default function configure(config = {}) {
55
57
  const ingestedConfig = configurationIngestor(config);
56
58
  const {
57
59
  mountUrl,
60
+ errorVisibility = CONFIG_ERROR_VISIBILITY_ONSUBMIT,
58
61
  views = [],
59
62
  session = {
60
63
  secret: 'secret',
@@ -145,11 +148,13 @@ export default function configure(config = {}) {
145
148
  });
146
149
 
147
150
  // Setup waypoint router, which includes routes for every defined waypoint
151
+ const globalErrorVisibility = errorVisibility
148
152
  const journeyRouter = journeyRoutes({
149
153
  globalHooks: hooks,
150
154
  pages,
151
155
  plan,
152
156
  csrfMiddleware,
157
+ globalErrorVisibility,
153
158
  });
154
159
 
155
160
  // Create the mounting function
@@ -7,3 +7,5 @@ export const REQUEST_PHASE_GATHER = Symbol('gather');
7
7
  export const REQUEST_PHASE_VALIDATE = Symbol('validate');
8
8
  export const REQUEST_PHASE_REDIRECT = Symbol('redirect');
9
9
  export const REQUEST_PHASE_RENDER = Symbol('render');
10
+ export const CONFIG_ERROR_VISIBILITY_ONSUBMIT = Symbol('onsubmit');
11
+ export const CONFIG_ERROR_VISIBILITY_ALWAYS = Symbol('always');
@@ -42,6 +42,7 @@ export default function nunjucksConfig({
42
42
 
43
43
  // Globals
44
44
  // These can't be modified once set. But they can be overridden by res.locals.
45
+ /* eslint-disable-next-line security/detect-non-literal-fs-filename */
45
46
  env.addGlobal('casaVersion', JSON.parse(readFileSync(resolve(dirname, '../../package.json'))).version);
46
47
 
47
48
  env.addGlobal('mergeObjects', mergeObjects);
@@ -1,4 +1,4 @@
1
- import i18next from 'i18next';
1
+ import { createInstance } from 'i18next';
2
2
  import { LanguageDetector, handle } from 'i18next-http-middleware';
3
3
  import { resolve, basename } from 'path';
4
4
  import { existsSync, readFileSync, readdirSync } from 'fs';
@@ -11,10 +11,12 @@ const log = logger('middleware:i18n');
11
11
  const loadJson = (file) => {
12
12
  // Strip out newlines (this is a legacy feature which we're keeping for
13
13
  // backwards compatibility).
14
+ /* eslint-disable-next-line security/detect-non-literal-fs-filename */
14
15
  const json = readFileSync(file, 'utf8');
15
16
  return JSON.parse(json.replace(/[\r\n]/g, ''));
16
17
  }
17
18
 
19
+ /* eslint-disable-next-line security/detect-non-literal-fs-filename */
18
20
  const loadYaml = (file) => yaml.load(readFileSync(file, 'utf8'))
19
21
 
20
22
  const extract = (file) => {
@@ -38,11 +40,13 @@ const loadResources = (languages, directories) => {
38
40
 
39
41
  directories.forEach((basedir) => {
40
42
  const dir = resolve(basedir, language);
43
+ /* eslint-disable-next-line security/detect-non-literal-fs-filename */
41
44
  if (!existsSync(dir)) {
42
45
  return;
43
46
  }
44
47
 
45
48
  log.info('Loading %s language from %s ...', language, dir);
49
+ /* eslint-disable-next-line security/detect-non-literal-fs-filename */
46
50
  readdirSync(dir).forEach((file) => {
47
51
  const { ns, data } = extract(resolve(dir, file));
48
52
 
@@ -67,7 +71,7 @@ export default function i18nMiddleware({
67
71
  const resources = loadResources(languages, directories);
68
72
 
69
73
  // Configure i18next
70
- const i18nInstance = i18next.createInstance();
74
+ const i18nInstance = createInstance();
71
75
  i18nInstance
72
76
  .use(LanguageDetector)
73
77
  .init({
@@ -9,6 +9,7 @@ import progressJourneyMiddlewareFactory from '../middleware/progress-journey.js'
9
9
  import waypointUrl from '../lib/waypoint-url.js';
10
10
  import logger from '../lib/logger.js';
11
11
  import { resolveMiddlewareHooks } from '../lib/utils.js';
12
+ import { CONFIG_ERROR_VISIBILITY_ALWAYS } from '../lib/constants.js';
12
13
 
13
14
  const log = logger('routes:journey');
14
15
 
@@ -53,6 +54,26 @@ const renderMiddlewareFactory = (view, contextFactory) => [
53
54
  },
54
55
  ];
55
56
 
57
+ /**
58
+ * generate page validation error
59
+ *
60
+ * @param {object} errors object of page validation error
61
+ * @param {object} req casa request object
62
+ * @returns {object[]} array of error objects
63
+ */
64
+ const generateGovukErrors = (errors, req) => Object.values(errors || {}).map(([error]) => ({
65
+ text: req.t(error.summary, error.variables),
66
+ href: error.fieldHref,
67
+ }))
68
+ /**
69
+ * handle errorVisibility flag and function and return boolean
70
+ *
71
+ * @param {symbol | Function} errorVisibility errorVisibility config option
72
+ * @param {object} req casa request object
73
+ * @returns {boolean} true if errorVisibility is "always" or function condition true
74
+ */
75
+ const resolveErrorVisibility = (req, errorVisibility) => (typeof errorVisibility === 'function' ? errorVisibility({ req }) : errorVisibility === CONFIG_ERROR_VISIBILITY_ALWAYS)
76
+
56
77
  /**
57
78
  * Create an instance of the router for all waypoints visited during a Journey
58
79
  * through the Plan.
@@ -66,6 +87,7 @@ export default function journeyRouter({
66
87
  pages,
67
88
  plan,
68
89
  csrfMiddleware,
90
+ globalErrorVisibility,
69
91
  }) {
70
92
  // Router
71
93
  const router = new MutableRouter();
@@ -116,7 +138,7 @@ export default function journeyRouter({
116
138
  ];
117
139
 
118
140
  pages.forEach((page) => {
119
- const { waypoint, view, hooks: pageHooks = [], fields } = page;
141
+ const { waypoint, view, hooks: pageHooks = [], fields, errorVisibility } = page;
120
142
  const waypointPath = `/${waypoint}`;
121
143
 
122
144
  let commonWaypointMiddleware = [
@@ -145,10 +167,18 @@ export default function journeyRouter({
145
167
  ...resolveMiddlewareHooks('journey.poststeer', waypointPath, [...globalHooks, ...pageHooks]),
146
168
 
147
169
  ...resolveMiddlewareHooks('journey.prerender', waypointPath, [...globalHooks, ...pageHooks]),
148
- renderMiddlewareFactory(view, (req) => ({
149
- formUrl: waypointUrl({ mountUrl: `${req.baseUrl}/`, waypoint }),
150
- formData: req.casa.journeyContext.getDataForPage(waypoint),
151
- })),
170
+ renderMiddlewareFactory(view, (req) => {
171
+ const displayErrors = resolveErrorVisibility(req, globalErrorVisibility) || resolveErrorVisibility(req, errorVisibility);
172
+ const errors = displayErrors && (req.casa.journeyContext.getValidationErrorsForPageByField(waypoint) ?? Object.create(null));
173
+ const govukErrors = displayErrors && generateGovukErrors(errors, req);
174
+
175
+ return ({
176
+ formUrl: waypointUrl({ mountUrl: `${req.baseUrl}/`, waypoint }),
177
+ formData: req.casa.journeyContext.getDataForPage(waypoint),
178
+ formErrors: (Object.keys(errors).length && displayErrors) ? errors : null,
179
+ formErrorsGovukArray: (govukErrors.length && displayErrors) ? govukErrors : null,
180
+ })
181
+ }),
152
182
  );
153
183
 
154
184
  router.post(
@@ -193,10 +223,7 @@ export default function journeyRouter({
193
223
  // first one is shown.
194
224
  // Disabling security/detect-object-injection rule because both `errors`
195
225
  // and the `k` property are known entities
196
- const govukErrors = Object.keys(errors).map((k) => ({
197
- text: req.t(errors[k][0].summary, errors[k][0].variables), /* eslint-disable-line security/detect-object-injection */
198
- href: errors[k][0].fieldHref, /* eslint-disable-line security/detect-object-injection */
199
- }));
226
+ const govukErrors = generateGovukErrors(errors, req)
200
227
 
201
228
  return {
202
229
  formUrl: waypointUrl({ mountUrl: `${req.baseUrl}/`, waypoint }),
@@ -58,8 +58,10 @@ export default function staticRouter({
58
58
  // The CASA CSS source contains the placeholder `~~~CASA_MOUNT_URL~~~` which
59
59
  // must be replaced with the dynamic `mountUrl` to ensure govuk-frontend
60
60
  // assets are served from the correct location.
61
+ /* eslint-disable security/detect-non-literal-fs-filename */
61
62
  const casaCss = readFileSync(resolve(dirname, '../../dist/assets/css/casa.css'), { encoding: 'utf8' });
62
63
  const casaCssIe8 = readFileSync(resolve(dirname, '../../dist/assets/css/casa-ie8.css'), { encoding: 'utf8' });
64
+ /* eslint-enable security/detect-non-literal-fs-filename */
63
65
 
64
66
  // The static middleware will only server GET/HEAD requests, so we can mount
65
67
  // the middleware using `use()` rather than resorting to `get()`