@dwp/govuk-casa 8.2.4 → 8.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [8.2.7](https://github.com/dwp/govuk-casa/compare/8.2.6...8.2.7) (2022-06-15)
6
+
7
+ ### [8.2.6](https://github.com/dwp/govuk-casa/compare/8.2.5...8.2.6) (2022-06-06)
8
+
9
+ ### [8.2.5](https://github.com/dwp/govuk-casa/compare/8.2.3...8.2.5) (2022-06-01)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * serve statics correctly from deeply nested apps ([0fd00a5](https://github.com/dwp/govuk-casa/commit/0fd00a5f10de28706afe87a993072d3184e3c7aa))
15
+
5
16
  ### [8.2.3](https://github.com/dwp/govuk-casa/compare/8.2.2...8.2.3) (2022-05-23)
6
17
 
7
18
  ### [8.2.2](https://github.com/dwp/govuk-casa/compare/8.2.1...8.2.2) (2022-05-23)
package/dist/casa.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type PageField = import('./lib/field').PageField;
2
- export type ContextEventHandler = (journeyContext: JourneyContext, previousContext: JourneyContext) => void;
2
+ export type ContextEventHandler = (opts: object, journeyContext: JourneyContext, previousContext: JourneyContext, session: object) => void;
3
3
  export type ContextEvent = {
4
4
  /**
5
5
  * Waypoint to watch for changes
@@ -108,14 +108,24 @@ export type IPlugin = {
108
108
  /**
109
109
  * Modify the app config
110
110
  */
111
- configure?: Function | undefined;
111
+ configure?: PluginConfigureFunction | undefined;
112
112
  /**
113
113
  * Modify post-configuration artifacts
114
114
  */
115
- bootstrap?: Function | undefined;
115
+ bootstrap?: PluginBootstrapFunction | undefined;
116
116
  };
117
- export type PluginConfigureFunction = (config: object) => any;
117
+ export type PluginConfigureFunction = (config: ConfigurationOptions) => any;
118
+ export type PluginBootstrapFunction = (config: ConfigureResult) => any;
118
119
  export type HelmetConfigurator = (config: object) => object;
120
+ /**
121
+ * Mounting function.
122
+ *
123
+ * This will mount all of the routes and middleware in the correct order on
124
+ * the given ExpressJS app.
125
+ *
126
+ * Once this is called, you will not be able to modify any of the routers as
127
+ * they will be "sealed".
128
+ */
119
129
  export type Mounter = (app: import('express').Express, opts: object, route?: string | undefined) => import('express').Express;
120
130
  export type MutableRouter = import('./lib/index').MutableRouter;
121
131
  /**
@@ -153,11 +163,15 @@ export type ConfigurationOptions = {
153
163
  /**
154
164
  * CASA Plan
155
165
  */
156
- plan: Plan;
166
+ plan?: Plan | undefined;
157
167
  /**
158
168
  * Handlers for JourneyContext events
159
169
  */
160
170
  events?: ContextEvent[] | undefined;
171
+ /**
172
+ * Helmet configuration manipulator function
173
+ */
174
+ helmetConfigurator?: HelmetConfigurator | undefined;
161
175
  };
162
176
  /**
163
177
  * Result of a call to configure() function
@@ -211,6 +225,10 @@ export type ConfigureResult = {
211
225
  * Function used to mount all CASA artifacts onto an ExpressJS app
212
226
  */
213
227
  mount: Mounter;
228
+ /**
229
+ * Ingested config supplied to `configure()`
230
+ */
231
+ config: ConfigurationOptions;
214
232
  };
215
233
  /**
216
234
  * Configuration for generating a ValidationError.
package/dist/casa.js CHANGED
@@ -55,8 +55,10 @@ exports.nunjucksFilters = nunjucksFilters;
55
55
  */
56
56
  /**
57
57
  * @callback ContextEventHandler
58
- * @param {JourneyContext} journeyContext Context including changes
59
- * @param {JourneyContext} previousContext Context prior to changes
58
+ * @param {object} opts Options
59
+ * @param {JourneyContext} opts.journeyContext Context including changes
60
+ * @param {JourneyContext} opts.previousContext Context prior to changes
61
+ * @param {object} opts.session Request session object
60
62
  * @returns {void}
61
63
  */
62
64
  /**
@@ -99,12 +101,16 @@ exports.nunjucksFilters = nunjucksFilters;
99
101
  */
100
102
  /**
101
103
  * @typedef {object} IPlugin Plugin interface
102
- * @property {Function} [configure] Modify the app config
103
- * @property {Function} [bootstrap] Modify post-configuration artifacts
104
+ * @property {PluginConfigureFunction} [configure] Modify the app config
105
+ * @property {PluginBootstrapFunction} [bootstrap] Modify post-configuration artifacts
104
106
  */
105
107
  /**
106
108
  * @callback PluginConfigureFunction
107
- * @param {object} config Options
109
+ * @param {ConfigurationOptions} config Options
110
+ */
111
+ /**
112
+ * @callback PluginBootstrapFunction
113
+ * @param {ConfigureResult} config Options
108
114
  */
109
115
  /**
110
116
  * @callback HelmetConfigurator
@@ -112,10 +118,18 @@ exports.nunjucksFilters = nunjucksFilters;
112
118
  * @returns {object} The modified configuration object
113
119
  */
114
120
  /**
121
+ * Mounting function.
122
+ *
123
+ * This will mount all of the routes and middleware in the correct order on
124
+ * the given ExpressJS app.
125
+ *
126
+ * Once this is called, you will not be able to modify any of the routers as
127
+ * they will be "sealed".
128
+ *
115
129
  * @callback Mounter
116
130
  * @param {import('express').Express} app Express application
117
131
  * @param {object} opts Mounting options
118
- * @param {string} [opts.route=/] Optional route to attach all middleware/routers too
132
+ * @param {string} [opts.route='/'] Optional route to attach all middleware/routers too
119
133
  * @returns {import('express').Express} The prepared ExpressJS app instance
120
134
  */
121
135
  /**
@@ -132,8 +146,9 @@ exports.nunjucksFilters = nunjucksFilters;
132
146
  * @property {GlobalHook[]} [hooks=[]] Hooks to apply
133
147
  * @property {IPlugin[]} [plugins=[]] Plugins
134
148
  * @property {I18nOptions[]} [i18n] I18n configuration
135
- * @property {Plan} plan CASA Plan
149
+ * @property {Plan} [plan] CASA Plan
136
150
  * @property {ContextEvent[]} [events=[]] Handlers for JourneyContext events
151
+ * @property {HelmetConfigurator} [helmetConfigurator] Helmet configuration manipulator function
137
152
  */
138
153
  /**
139
154
  * @typedef {object} ConfigureResult Result of a call to configure() function
@@ -149,6 +164,7 @@ exports.nunjucksFilters = nunjucksFilters;
149
164
  * @property {import('express').RequestHandler[]} i18nMiddleware I18n preparation middleware
150
165
  * @property {import('express').RequestHandler} bodyParserMiddleware Body parsing middleware
151
166
  * @property {Mounter} mount Function used to mount all CASA artifacts onto an ExpressJS app
167
+ * @property {ConfigurationOptions} config Ingested config supplied to `configure()`
152
168
  */
153
169
  /**
154
170
  * Configuration for generating a ValidationError.
@@ -466,10 +466,19 @@ class JourneyContext {
466
466
  * @returns {void}
467
467
  */
468
468
  static initContextStore(session) {
469
+ // For existing sessions that were created prior to `journeyContextList`
470
+ // being remodelled as an array, we need to convert the "legacy" structure
471
+ // into an equivalent array.
472
+ if (isPlainObject(session === null || session === void 0 ? void 0 : session.journeyContextList)) {
473
+ log.trace('Session context list already initialised as an object (legacy structure). Will convert from object to array.');
474
+ /* eslint-disable-next-line no-param-reassign */
475
+ session.journeyContextList = Object.entries(session.journeyContextList);
476
+ }
477
+ // Initialise new context list in the session
469
478
  if (!has(session, 'journeyContextList')) {
470
479
  log.trace('Initialising session with a default journey context list');
471
480
  /* eslint-disable-next-line no-param-reassign */
472
- session.journeyContextList = Object.create(null);
481
+ session.journeyContextList = [];
473
482
  const defaultContext = new JourneyContext();
474
483
  defaultContext.identity.id = JourneyContext.DEFAULT_CONTEXT_ID;
475
484
  JourneyContext.putContext(session, defaultContext);
@@ -515,10 +524,11 @@ class JourneyContext {
515
524
  * @returns {JourneyContext} The discovered JourneyContext instance
516
525
  */
517
526
  static getContextById(session, id) {
518
- if (has(session === null || session === void 0 ? void 0 : session.journeyContextList, id)) {
527
+ const list = new Map(session === null || session === void 0 ? void 0 : session.journeyContextList);
528
+ if (list.has(id)) {
519
529
  // ESLint disabled as `id` has been verified as an "own" property
520
530
  /* eslint-disable-next-line security/detect-object-injection */
521
- return JourneyContext.fromObject(session.journeyContextList[id]);
531
+ return JourneyContext.fromObject(list.get(id));
522
532
  }
523
533
  return undefined;
524
534
  }
@@ -531,7 +541,8 @@ class JourneyContext {
531
541
  */
532
542
  static getContextByName(session, name) {
533
543
  if (session) {
534
- const context = Object.values(session.journeyContextList).find((c) => (c.identity.name === name));
544
+ const list = new Map(session === null || session === void 0 ? void 0 : session.journeyContextList);
545
+ const context = [...list.values()].find((c) => (c.identity.name === name));
535
546
  if (context) {
536
547
  return JourneyContext.fromObject(context);
537
548
  }
@@ -547,7 +558,8 @@ class JourneyContext {
547
558
  */
548
559
  static getContextsByTag(session, tag) {
549
560
  if (session) {
550
- return Object.values(session.journeyContextList).filter((c) => { var _a; return ((_a = c.identity.tags) === null || _a === void 0 ? void 0 : _a.includes(tag)); }).map((c) => (JourneyContext.fromObject(c)));
561
+ const list = new Map(session === null || session === void 0 ? void 0 : session.journeyContextList);
562
+ return [...list.values()].filter((c) => { var _a; return ((_a = c.identity.tags) === null || _a === void 0 ? void 0 : _a.includes(tag)); }).map((c) => (JourneyContext.fromObject(c)));
551
563
  }
552
564
  return undefined;
553
565
  }
@@ -559,7 +571,7 @@ class JourneyContext {
559
571
  */
560
572
  static getContexts(session) {
561
573
  if (has(session, 'journeyContextList')) {
562
- return Object.values(session.journeyContextList).map((contextObj) => (JourneyContext.fromObject(contextObj)));
574
+ return session.journeyContextList.map(([, contextObj]) => (JourneyContext.fromObject(contextObj)));
563
575
  }
564
576
  return [];
565
577
  }
@@ -594,8 +606,10 @@ class JourneyContext {
594
606
  event: 'context-change',
595
607
  session,
596
608
  });
609
+ const list = new Map(session.journeyContextList);
610
+ list.set(context.identity.id, context.toObject());
597
611
  /* eslint-disable-next-line no-param-reassign */
598
- session.journeyContextList[context.identity.id] = context.toObject();
612
+ session.journeyContextList = [...list.entries()];
599
613
  }
600
614
  /**
601
615
  * Remove a context from the session store.
@@ -617,10 +631,10 @@ class JourneyContext {
617
631
  * @returns {void}
618
632
  */
619
633
  static removeContextById(session, id) {
620
- if (session && has(session.journeyContextList, id)) {
621
- // ESLint disabled as `id` has been verified as an "own" property
622
- /* eslint-disable-next-line security/detect-object-injection, no-param-reassign */
623
- delete session.journeyContextList[id];
634
+ var _a;
635
+ const index = ((_a = session === null || session === void 0 ? void 0 : session.journeyContextList) !== null && _a !== void 0 ? _a : []).findIndex(([contextId]) => contextId === id);
636
+ if (index > -1) {
637
+ session.journeyContextList.splice(index, 1);
624
638
  }
625
639
  }
626
640
  /**
@@ -4,7 +4,7 @@
4
4
  */
5
5
  /**
6
6
  * @access private
7
- * @typedef {import('../casa').ConfigurationOptions} ConfigureResult
7
+ * @typedef {import('../casa').ConfigureResult} ConfigureResult
8
8
  */
9
9
  /**
10
10
  * @access private
@@ -19,5 +19,5 @@
19
19
  */
20
20
  export default function configure(config?: ConfigurationOptions): ConfigureResult;
21
21
  export type ConfigurationOptions = import('../casa').ConfigurationOptions;
22
- export type ConfigureResult = import('../casa').ConfigurationOptions;
22
+ export type ConfigureResult = import('../casa').ConfigureResult;
23
23
  export type Mounter = import('../casa').Mounter;
@@ -3,20 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const express_1 = require("express");
7
6
  const express_session_1 = require("express-session");
8
7
  const path_1 = require("path");
9
8
  const module_1 = require("module");
10
9
  const cookie_parser_1 = __importDefault(require("cookie-parser"));
11
- const path_to_regexp_1 = require("path-to-regexp");
12
10
  const dirname_cjs_1 = __importDefault(require("./dirname.cjs"));
13
- const utils_js_1 = require("./utils.js");
14
11
  const configuration_ingestor_js_1 = __importDefault(require("./configuration-ingestor.js"));
15
12
  const nunjucks_js_1 = __importDefault(require("./nunjucks.js"));
13
+ const mount_js_1 = __importDefault(require("./mount.js"));
16
14
  const static_js_1 = __importDefault(require("../routes/static.js"));
17
15
  const ancillary_js_1 = __importDefault(require("../routes/ancillary.js"));
18
16
  const journey_js_1 = __importDefault(require("../routes/journey.js"));
19
- const strip_proxy_path_js_1 = __importDefault(require("../middleware/strip-proxy-path.js"));
20
17
  const pre_js_1 = __importDefault(require("../middleware/pre.js"));
21
18
  const post_js_1 = __importDefault(require("../middleware/post.js"));
22
19
  const session_js_1 = __importDefault(require("../middleware/session.js"));
@@ -30,7 +27,7 @@ const csrf_js_1 = __importDefault(require("../middleware/csrf.js"));
30
27
  */
31
28
  /**
32
29
  * @access private
33
- * @typedef {import('../casa').ConfigurationOptions} ConfigureResult
30
+ * @typedef {import('../casa').ConfigureResult} ConfigureResult
34
31
  */
35
32
  /**
36
33
  * @access private
@@ -51,6 +48,7 @@ function configure(config = {}) {
51
48
  plugin.configure(config);
52
49
  });
53
50
  // Extract config
51
+ const ingestedConfig = (0, configuration_ingestor_js_1.default)(config);
54
52
  const { mountUrl, views = [], session = {
55
53
  secret: 'secret',
56
54
  name: 'casasession',
@@ -62,7 +60,7 @@ function configure(config = {}) {
62
60
  }, pages = [], plan = null, hooks = [], plugins = [], events = [], i18n = {
63
61
  dirs: [],
64
62
  locales: ['en', 'cy'],
65
- }, helmetConfigurator = undefined, } = (0, configuration_ingestor_js_1.default)(config);
63
+ }, helmetConfigurator = undefined, } = ingestedConfig;
66
64
  // Prepare all page hooks so they are prefixed with the `journey.` scope.
67
65
  pages.forEach((page) => {
68
66
  var _a;
@@ -124,73 +122,21 @@ function configure(config = {}) {
124
122
  plan,
125
123
  csrfMiddleware,
126
124
  });
127
- // Mount function
128
- // This will mount all of these routes and middleware in the correct order on
129
- // the given ExpressJS app.
130
- // Once this is called, you will not be able to modify any of the routers as
131
- // they will be "sealed".
132
- /**
133
- * Mounting function.
134
- *
135
- * @type {Mounter} mount
136
- */
137
- const mount = (app, { route = '/' } = {}) => {
138
- nunjucksEnv.express(app);
139
- app.set('view engine', 'njk');
140
- // If a `mountUrl` has been defined, then we're potentially in "proxy mode",
141
- // in which we strip the proxy path prefix from the incoming request URLs.
142
- if (mountUrl) {
143
- app.use((0, strip_proxy_path_js_1.default)({ mountUrl }));
144
- }
145
- // Attach a handler to redirect requests for `/` to the first waypoint in
146
- // the plan
147
- if (plan) {
148
- const re = (0, path_to_regexp_1.pathToRegexp)(`${route}`.replace(/\/+/g, '/'));
149
- app.use(re, (req, res) => {
150
- const reqUrl = new URL(req.url, 'https://placeholder.test/');
151
- const reqPath = (0, utils_js_1.validateUrlPath)(`${req.baseUrl}${reqUrl.pathname}${plan.getWaypoints()[0]}`);
152
- let reqParams = reqUrl.searchParams.toString();
153
- reqParams = reqParams ? `?${reqParams}` : '';
154
- res.redirect(302, `${reqPath}${reqParams}`);
155
- });
156
- }
157
- // Capture the mount path of this CASA app, before any parameterised path
158
- // segments exert influence over `req.baseUrl` in the `router` further below.
159
- // This can later be used by middleware that wants to use the
160
- // "unparameterised" version of the request's `baseUrl`, such as the static
161
- // router's middleware.
162
- app.use((req, res, next) => {
163
- req.unparameterisedBaseUrl = req.baseUrl;
164
- next();
165
- });
166
- // Serve static assets from the `app` rather than the `router`. The router
167
- // may contain paramaterised path segments which would mean serving static
168
- // assets over a dynamic URL each time, thus causing lots of cache misses on
169
- // the browser.
170
- const sealedStaticRouter = staticRouter.seal();
171
- app.use(preMiddleware);
172
- app.use(sealedStaticRouter);
173
- const router = (0, express_1.Router)({
174
- // Required so that any parameters in the URL are propagated to middleware
175
- mergeParams: true,
176
- });
177
- router.use(preMiddleware);
178
- // !!! DEPRECATE in v9 !!! For performance reasons, static assets will
179
- // always be handled via the `app` middleware rather than `router`.
180
- // Anywhere `mountUrl` is used in templates to service static assets must be
181
- // changed to use `staticMountUrl`.
182
- // TASK: remove this line below
183
- router.use(sealedStaticRouter);
184
- router.use(sessionMiddleware);
185
- router.use(i18nMiddleware);
186
- router.use(bodyParserMiddleware);
187
- router.use(dataMiddleware);
188
- router.use(ancillaryRouter.seal());
189
- router.use(journeyRouter.seal());
190
- router.use(postMiddleware);
191
- app.use(route, router);
192
- return app;
193
- };
125
+ // Create the mounting function
126
+ const mount = (0, mount_js_1.default)({
127
+ nunjucksEnv,
128
+ mountUrl,
129
+ plan,
130
+ staticRouter,
131
+ ancillaryRouter,
132
+ journeyRouter,
133
+ preMiddleware,
134
+ sessionMiddleware,
135
+ i18nMiddleware,
136
+ bodyParserMiddleware,
137
+ dataMiddleware,
138
+ postMiddleware,
139
+ });
194
140
  // Prepare configuration result
195
141
  const configOutput = {
196
142
  // Nunjucks environment, so it can be attached to other ExpressJS instances
@@ -214,6 +160,8 @@ function configure(config = {}) {
214
160
  dataMiddleware,
215
161
  // Mount function
216
162
  mount,
163
+ // Ingested config
164
+ config: ingestedConfig,
217
165
  };
218
166
  // Bootstrap all plugins
219
167
  plugins.filter((p) => p.bootstrap).forEach((plugin) => plugin === null || plugin === void 0 ? void 0 : plugin.bootstrap(configOutput));
@@ -0,0 +1,20 @@
1
+ declare function _default({ nunjucksEnv, mountUrl, plan, staticRouter, ancillaryRouter, journeyRouter, preMiddleware, sessionMiddleware, i18nMiddleware, bodyParserMiddleware, dataMiddleware, postMiddleware, }: {
2
+ nunjucksEnv: NunjucksEnvironment;
3
+ mountUrl?: string | undefined;
4
+ plan?: import("./Plan.js").default | undefined;
5
+ staticRouter: MutableRouter;
6
+ ancillaryRouter: MutableRouter;
7
+ journeyRouter: MutableRouter;
8
+ preMiddleware: ExpressRequestHandler[];
9
+ sessionMiddleware: ExpressRequestHandler[];
10
+ i18nMiddleware: ExpressRequestHandler[];
11
+ bodyParserMiddleware: ExpressRequestHandler[];
12
+ dataMiddleware: ExpressRequestHandler[];
13
+ postMiddleware: ExpressRequestHandler[];
14
+ }): Mounter;
15
+ export default _default;
16
+ export type NunjucksEnvironment = import('nunjucks').Environment;
17
+ export type ExpressRequestHandler = import('express').RequestHandler;
18
+ export type Mounter = import('../casa').Mounter;
19
+ export type Plan = import('../casa').Plan;
20
+ export type MutableRouter = import('../casa').MutableRouter;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const express_1 = require("express");
7
+ const path_to_regexp_1 = require("path-to-regexp");
8
+ const strip_proxy_path_js_1 = __importDefault(require("../middleware/strip-proxy-path.js"));
9
+ const serve_first_waypoint_js_1 = __importDefault(require("../middleware/serve-first-waypoint.js"));
10
+ /**
11
+ * @access private
12
+ * @typedef {import('nunjucks').Environment} NunjucksEnvironment
13
+ */
14
+ /**
15
+ * @access private
16
+ * @typedef {import('express').RequestHandler} ExpressRequestHandler
17
+ */
18
+ /**
19
+ * @access private
20
+ * @typedef {import('../casa').Mounter} Mounter
21
+ */
22
+ /**
23
+ * @access private
24
+ * @typedef {import('../casa').Plan} Plan
25
+ */
26
+ /**
27
+ * @access private
28
+ * @typedef {import('../casa').MutableRouter} MutableRouter
29
+ */
30
+ /**
31
+ * Mounting function factory.
32
+ *
33
+ * @param {Object} args Arguments
34
+ * @param {NunjucksEnvironment} args.nunjucksEnv Pre-configured Nunmjucks environment
35
+ * @param {string} [args.mountUrl] Mount URL
36
+ * @param {Plan} [args.plan] CASA Plan
37
+ * @param {MutableRouter} args.staticRouter Router for all static assets
38
+ * @param {MutableRouter} args.ancillaryRouter Router for all ancillary routes
39
+ * @param {MutableRouter} args.journeyRouter Router for all waypoints
40
+ * @param {ExpressRequestHandler[]} args.preMiddleware Middleware
41
+ * @param {ExpressRequestHandler[]} args.sessionMiddleware Middleware
42
+ * @param {ExpressRequestHandler[]} args.i18nMiddleware Middleware
43
+ * @param {ExpressRequestHandler[]} args.bodyParserMiddleware Middleware
44
+ * @param {ExpressRequestHandler[]} args.dataMiddleware Middleware
45
+ * @param {ExpressRequestHandler[]} args.postMiddleware Middleware
46
+ * @returns {Mounter} mount
47
+ */
48
+ exports.default = ({ nunjucksEnv, mountUrl, plan, staticRouter, ancillaryRouter, journeyRouter, preMiddleware, sessionMiddleware, i18nMiddleware, bodyParserMiddleware, dataMiddleware, postMiddleware, }) => (app, { route = '/', serveFirstWaypoint = false, } = {}) => {
49
+ nunjucksEnv.express(app);
50
+ app.set('view engine', 'njk');
51
+ // If a `mountUrl` has been defined, then we're potentially in "proxy mode",
52
+ // in which we strip the proxy path prefix from the incoming request URLs.
53
+ if (mountUrl) {
54
+ app.use((0, strip_proxy_path_js_1.default)({ mountUrl }));
55
+ }
56
+ // Attach a handler to redirect requests for `/` to the first waypoint in
57
+ // the plan
58
+ if (serveFirstWaypoint && plan) {
59
+ const re = (0, path_to_regexp_1.pathToRegexp)(`${route}`.replace(/\/+/g, '/'));
60
+ app.use(re, (0, serve_first_waypoint_js_1.default)({ plan }));
61
+ }
62
+ // Capture the mount path of this CASA app, before any parameterised path
63
+ // segments exert influence over `req.baseUrl` in the `router` further below.
64
+ // This can later be used by middleware that wants to use the
65
+ // "unparameterised" version of the request's `baseUrl`, such as the static
66
+ // router's middleware.
67
+ app.use((req, res, next) => {
68
+ req.unparameterisedBaseUrl = req.baseUrl;
69
+ next();
70
+ });
71
+ // Serve static assets from the `app` rather than the `router`. The router
72
+ // may contain paramaterised path segments which would mean serving static
73
+ // assets over a dynamic URL each time, thus causing lots of cache misses on
74
+ // the browser.
75
+ const sealedStaticRouter = staticRouter.seal();
76
+ app.use(preMiddleware);
77
+ app.use(sealedStaticRouter);
78
+ const router = (0, express_1.Router)({
79
+ // Required so that any parameters in the URL are propagated to middleware
80
+ mergeParams: true,
81
+ });
82
+ router.use(preMiddleware);
83
+ // !!! DEPRECATE in v9 !!! For performance reasons, static assets will
84
+ // always be handled via the `app` middleware rather than `router`.
85
+ // Anywhere `mountUrl` is used in templates to service static assets must be
86
+ // changed to use `staticMountUrl`.
87
+ // TASK: remove this line below
88
+ router.use(sealedStaticRouter);
89
+ router.use(sessionMiddleware);
90
+ router.use(i18nMiddleware);
91
+ router.use(bodyParserMiddleware);
92
+ router.use(dataMiddleware);
93
+ router.use(ancillaryRouter.seal());
94
+ router.use(journeyRouter.seal());
95
+ router.use(postMiddleware);
96
+ app.use(route, router);
97
+ return app;
98
+ };
package/dist/lib/utils.js CHANGED
@@ -148,7 +148,7 @@ function validateHookName(hookName) {
148
148
  if (!hookName.length) {
149
149
  throw new SyntaxError('Hook name must not be empty');
150
150
  }
151
- if (!hookName.match(/^([a-z]+\.|)[a-z]+$/i)) {
151
+ if (!hookName.match(/^([a-z_]+\.|)[a-z_]+$/i)) {
152
152
  throw new SyntaxError('Hook name must match either <scope>.<hookname> or <hookname> formats');
153
153
  }
154
154
  }
@@ -0,0 +1,4 @@
1
+ declare function _default({ plan, }: Plan): ExpressRequestHandler[];
2
+ export default _default;
3
+ export type ExpressRequestHandler = import('express').RequestHandler;
4
+ export type Plan = import('../casa').Plan;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_js_1 = require("../lib/utils.js");
4
+ /**
5
+ * @access private
6
+ * @typedef {import('express').RequestHandler} ExpressRequestHandler
7
+ */
8
+ /**
9
+ * @access private
10
+ * @typedef {import('../casa').Plan} Plan
11
+ */
12
+ /**
13
+ * Redirect the user to the first Plan waypoint when they request the root /
14
+ * path.
15
+ *
16
+ * @param {Plan} plan CASA Plan
17
+ * @returns {ExpressRequestHandler[]} Array of middleware
18
+ */
19
+ exports.default = ({ plan, }) => [(req, res) => {
20
+ const reqUrl = new URL(req.url, 'https://placeholder.test/');
21
+ const reqPath = (0, utils_js_1.validateUrlPath)(`${req.baseUrl}${reqUrl.pathname}${plan.getWaypoints()[0]}`);
22
+ let reqParams = reqUrl.searchParams.toString();
23
+ reqParams = reqParams ? `?${reqParams}` : '';
24
+ res.redirect(302, `${reqPath}${reqParams}`);
25
+ }];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dwp/govuk-casa",
3
- "version": "8.2.4",
3
+ "version": "8.2.7",
4
4
  "description": "A framework for building GOVUK Collect-And-Submit-Applications",
5
5
  "repository": {
6
6
  "type": "git",
@@ -51,8 +51,8 @@
51
51
  "govuk-frontend": "4.0.1",
52
52
  "graphlib": "2.1.8",
53
53
  "helmet": "5.1.0",
54
- "i18next": "21.8.4",
55
- "i18next-http-middleware": "3.2.0",
54
+ "i18next": "21.8.8",
55
+ "i18next-http-middleware": "3.2.1",
56
56
  "js-yaml": "4.1.0",
57
57
  "lodash": "4.17.21",
58
58
  "luxon": "2.4.0",
@@ -62,38 +62,38 @@
62
62
  "validator": "13.7.0"
63
63
  },
64
64
  "devDependencies": {
65
- "@babel/core": "7.18.0",
66
- "@babel/eslint-parser": "7.17.0",
67
- "@babel/preset-env": "7.18.0",
68
- "@commitlint/config-conventional": "17.0.0",
69
- "@ckeditor/jsdoc-plugins": "30.1.0",
65
+ "@babel/core": "7.18.2",
66
+ "@babel/eslint-parser": "7.18.2",
67
+ "@babel/preset-env": "7.18.2",
68
+ "@ckeditor/jsdoc-plugins": "30.2.0",
69
+ "@commitlint/config-conventional": "17.0.2",
70
70
  "@dwp/casa-spiderplan": "2.4.0",
71
71
  "@dwp/casa-spiderplan-a11y-plugin": "0.1.4",
72
72
  "@dwp/casa-spiderplan-zap-plugin": "0.1.1",
73
73
  "@dwp/eslint-config-base": "6.0.0",
74
74
  "@types/express": "4.17.13",
75
- "@types/node": "17.0.35",
75
+ "@types/node": "17.0.40",
76
76
  "@types/nunjucks": "3.2.1",
77
77
  "babel-eslint": "10.1.0",
78
78
  "c8": "7.11.3",
79
79
  "chai": "4.3.6",
80
80
  "cheerio": "1.0.0-rc.11",
81
- "commitlint": "17.0.0",
82
- "eslint": "8.16.0",
81
+ "commitlint": "17.0.2",
83
82
  "docdash": "1.2.0",
84
- "eslint-plugin-security": "1.5.0",
85
- "fast-check": "2.25.0",
86
- "husky": "8.0.1",
87
- "mocha": "10.0.0",
88
- "sass": "1.52.1",
83
+ "eslint": "8.17.0",
89
84
  "eslint-plugin-no-unsafe-regex": "1.0.0",
85
+ "eslint-plugin-security": "1.5.0",
90
86
  "eslint-plugin-sonarjs": "0.13.0",
87
+ "fast-check": "3.0.0",
88
+ "husky": "8.0.1",
91
89
  "jsdoc": "3.6.10",
92
90
  "jsdoc-tsimport-plugin": "1.0.5",
91
+ "mocha": "10.0.0",
92
+ "sass": "1.52.2",
93
93
  "sinon": "14.0.0",
94
94
  "sinon-chai": "3.7.0",
95
95
  "standard-version": "9.5.0",
96
96
  "supertest": "6.2.3",
97
- "typescript": "4.6.4"
97
+ "typescript": "4.7.3"
98
98
  }
99
99
  }