@defra/forms-engine-plugin 0.1.10 → 0.1.11
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/.public/stylesheets/application.min.css +1 -1
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/stylesheets/application.scss +10 -0
- package/.server/config/index.js +3 -14
- package/.server/config/index.js.map +1 -1
- package/.server/server/devserver/dxt-devtool-baselayout.html +71 -0
- package/.server/server/forms/register-as-a-unicorn-breeder.json +393 -0
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +251 -0
- package/.server/server/index.js +11 -16
- package/.server/server/index.js.map +1 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js +16 -2
- package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
- package/.server/server/plugins/engine/plugin.js +27 -16
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/services/formsService.js +15 -29
- package/.server/server/plugins/engine/services/formsService.js.map +1 -1
- package/.server/server/plugins/engine/services/localFormsService.js +52 -0
- package/.server/server/plugins/engine/services/localFormsService.js.map +1 -0
- package/.server/server/plugins/engine/views/confirmation.html +1 -1
- package/.server/server/plugins/engine/views/file-upload.html +1 -1
- package/.server/server/plugins/engine/views/index.html +1 -1
- package/.server/server/plugins/engine/views/item-delete.html +1 -1
- package/.server/server/plugins/engine/views/repeat-list-summary.html +1 -1
- package/.server/server/plugins/engine/views/summary.html +1 -1
- package/.server/server/plugins/errorPages.js +4 -26
- package/.server/server/plugins/errorPages.js.map +1 -1
- package/.server/server/plugins/nunjucks/context.js +37 -28
- package/.server/server/plugins/nunjucks/context.js.map +1 -1
- package/.server/server/plugins/nunjucks/context.test.js +23 -28
- package/.server/server/plugins/nunjucks/context.test.js.map +1 -1
- package/.server/server/plugins/nunjucks/types.js +3 -4
- package/.server/server/plugins/nunjucks/types.js.map +1 -1
- package/.server/server/routes/index.js +0 -1
- package/.server/server/routes/index.js.map +1 -1
- package/.server/typings/hapi/index.d.js.map +1 -1
- package/package.json +2 -1
- package/src/client/stylesheets/application.scss +10 -0
- package/src/config/index.ts +4 -17
- package/src/server/devserver/dxt-devtool-baselayout.html +71 -0
- package/src/server/forms/register-as-a-unicorn-breeder.json +393 -0
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +251 -0
- package/src/server/index.test.ts +4 -37
- package/src/server/index.ts +13 -16
- package/src/server/plugins/engine/configureEnginePlugin.ts +19 -1
- package/src/server/plugins/engine/plugin.ts +43 -17
- package/src/server/plugins/engine/services/formsService.js +17 -35
- package/src/server/plugins/engine/services/localFormsService.js +49 -0
- package/src/server/plugins/engine/views/confirmation.html +1 -1
- package/src/server/plugins/engine/views/file-upload.html +1 -1
- package/src/server/plugins/engine/views/index.html +1 -1
- package/src/server/plugins/engine/views/item-delete.html +1 -1
- package/src/server/plugins/engine/views/repeat-list-summary.html +1 -1
- package/src/server/plugins/engine/views/summary.html +1 -1
- package/src/server/plugins/errorPages.ts +4 -26
- package/src/server/plugins/nunjucks/context.js +41 -31
- package/src/server/plugins/nunjucks/context.test.js +24 -27
- package/src/server/plugins/nunjucks/types.js +3 -4
- package/src/server/routes/index.ts +0 -1
- package/src/typings/hapi/index.d.ts +3 -9
- package/.server/common/cookies.js +0 -55
- package/.server/common/cookies.js.map +0 -1
- package/.server/common/cookies.test.js +0 -15
- package/.server/common/cookies.test.js.map +0 -1
- package/.server/common/types.js +0 -6
- package/.server/common/types.js.map +0 -1
- package/.server/server/forms/README.md +0 -10
- package/.server/server/forms/report-a-terrorist.json +0 -270
- package/.server/server/forms/runner-components-test.json +0 -365
- package/.server/server/forms/test.json +0 -581
- package/.server/server/forms/test.yaml +0 -363
- package/.server/server/plugins/blankie.js +0 -29
- package/.server/server/plugins/blankie.js.map +0 -1
- package/.server/server/plugins/engine/services/formsService.test.js +0 -71
- package/.server/server/plugins/engine/services/formsService.test.js.map +0 -1
- package/.server/server/plugins/engine/views/layout.html +0 -199
- package/.server/server/plugins/router.js +0 -169
- package/.server/server/plugins/router.js.map +0 -1
- package/.server/server/routes/health.js +0 -15
- package/.server/server/routes/health.js.map +0 -1
- package/.server/server/routes/health.test.js +0 -32
- package/.server/server/routes/health.test.js.map +0 -1
- package/.server/server/utils/file-form-service.test.js +0 -52
- package/.server/server/utils/file-form-service.test.js.map +0 -1
- package/.server/server/views/404.html +0 -16
- package/.server/server/views/500.html +0 -19
- package/.server/server/views/help/accessibility-statement.html +0 -58
- package/.server/server/views/help/cookie-preferences.html +0 -57
- package/.server/server/views/help/cookies.html +0 -71
- package/.server/server/views/help/get-support.html +0 -37
- package/.server/server/views/help/privacy-notice.html +0 -68
- package/.server/server/views/help/terms-and-conditions.html +0 -83
- package/src/common/cookies.js +0 -58
- package/src/common/cookies.test.js +0 -23
- package/src/common/types.js +0 -5
- package/src/server/forms/README.md +0 -10
- package/src/server/forms/report-a-terrorist.json +0 -270
- package/src/server/forms/runner-components-test.json +0 -365
- package/src/server/forms/test.json +0 -581
- package/src/server/forms/test.yaml +0 -363
- package/src/server/plugins/blankie.test.ts +0 -73
- package/src/server/plugins/blankie.ts +0 -48
- package/src/server/plugins/engine/services/formsService.test.js +0 -90
- package/src/server/plugins/engine/views/layout.html +0 -199
- package/src/server/plugins/router.ts +0 -201
- package/src/server/routes/health.js +0 -13
- package/src/server/routes/health.test.js +0 -35
- package/src/server/routes/index.test.ts +0 -125
- package/src/server/utils/file-form-service.test.js +0 -79
- package/src/server/views/404.html +0 -16
- package/src/server/views/500.html +0 -19
- package/src/server/views/help/accessibility-statement.html +0 -58
- package/src/server/views/help/cookie-preferences.html +0 -57
- package/src/server/views/help/cookies.html +0 -71
- package/src/server/views/help/get-support.html +0 -37
- package/src/server/views/help/privacy-notice.html +0 -68
- package/src/server/views/help/terms-and-conditions.html +0 -83
package/src/server/index.ts
CHANGED
|
@@ -15,14 +15,13 @@ import { config } from '~/src/config/index.js'
|
|
|
15
15
|
import { requestLogger } from '~/src/server/common/helpers/logging/request-logger.js'
|
|
16
16
|
import { requestTracing } from '~/src/server/common/helpers/logging/request-tracing.js'
|
|
17
17
|
import { buildRedisClient } from '~/src/server/common/helpers/redis-client.js'
|
|
18
|
-
import { configureBlankiePlugin } from '~/src/server/plugins/blankie.js'
|
|
19
18
|
import { configureCrumbPlugin } from '~/src/server/plugins/crumb.js'
|
|
20
|
-
import { configureEnginePlugin } from '~/src/server/plugins/engine/
|
|
19
|
+
import { configureEnginePlugin } from '~/src/server/plugins/engine/configureEnginePlugin.js'
|
|
21
20
|
import pluginErrorPages from '~/src/server/plugins/errorPages.js'
|
|
22
21
|
import { plugin as pluginViews } from '~/src/server/plugins/nunjucks/index.js'
|
|
23
22
|
import pluginPulse from '~/src/server/plugins/pulse.js'
|
|
24
|
-
import pluginRouter from '~/src/server/plugins/router.js'
|
|
25
23
|
import pluginSession from '~/src/server/plugins/session.js'
|
|
24
|
+
import { publicRoutes } from '~/src/server/routes/index.js'
|
|
26
25
|
import { prepareSecureContext } from '~/src/server/secure-context.js'
|
|
27
26
|
import { type RouteConfig } from '~/src/server/types.js'
|
|
28
27
|
|
|
@@ -82,15 +81,13 @@ export async function createServer(routeConfig?: RouteConfig) {
|
|
|
82
81
|
prepareSecureContext(server)
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
const pluginEngine = await configureEnginePlugin(routeConfig)
|
|
86
84
|
const pluginCrumb = configureCrumbPlugin(routeConfig)
|
|
87
|
-
const
|
|
85
|
+
const pluginEngine = await configureEnginePlugin(routeConfig)
|
|
88
86
|
|
|
89
87
|
await server.register(pluginSession)
|
|
90
88
|
await server.register(pluginPulse)
|
|
91
89
|
await server.register(inert)
|
|
92
90
|
await server.register(Scooter)
|
|
93
|
-
await server.register(pluginBlankie)
|
|
94
91
|
await server.register(pluginCrumb)
|
|
95
92
|
|
|
96
93
|
server.ext('onPreResponse', (request: Request, h: ResponseToolkit) => {
|
|
@@ -117,19 +114,19 @@ export async function createServer(routeConfig?: RouteConfig) {
|
|
|
117
114
|
|
|
118
115
|
await server.register(pluginViews)
|
|
119
116
|
await server.register(pluginEngine)
|
|
120
|
-
|
|
117
|
+
|
|
118
|
+
await server.register({
|
|
119
|
+
plugin: {
|
|
120
|
+
name: 'router',
|
|
121
|
+
register: (server) => {
|
|
122
|
+
server.route(publicRoutes)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
121
127
|
await server.register(pluginErrorPages)
|
|
122
128
|
await server.register(blipp)
|
|
123
129
|
await server.register(requestTracing)
|
|
124
130
|
|
|
125
|
-
server.state('cookieConsent', {
|
|
126
|
-
ttl: 365 * 24 * 60 * 60 * 1000, // 1 year in ms
|
|
127
|
-
clearInvalid: true,
|
|
128
|
-
isHttpOnly: false,
|
|
129
|
-
isSecure: config.get('isProduction'),
|
|
130
|
-
path: '/',
|
|
131
|
-
encoding: 'none' // handle this inside the application so we can share frontend/backend cookie modification
|
|
132
|
-
})
|
|
133
|
-
|
|
134
131
|
return server
|
|
135
132
|
}
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
plugin,
|
|
9
9
|
type PluginOptions
|
|
10
10
|
} from '~/src/server/plugins/engine/plugin.js'
|
|
11
|
+
import { findPackageRoot } from '~/src/server/plugins/engine/plugin.js'
|
|
12
|
+
import * as defaultServices from '~/src/server/plugins/engine/services/index.js'
|
|
13
|
+
import { formsService } from '~/src/server/plugins/engine/services/localFormsService.js'
|
|
14
|
+
import { devtoolContext } from '~/src/server/plugins/nunjucks/context.js'
|
|
11
15
|
import { type RouteConfig } from '~/src/server/types.js'
|
|
12
16
|
|
|
13
17
|
export const configureEnginePlugin = async ({
|
|
@@ -27,7 +31,21 @@ export const configureEnginePlugin = async ({
|
|
|
27
31
|
|
|
28
32
|
return {
|
|
29
33
|
plugin,
|
|
30
|
-
options: {
|
|
34
|
+
options: {
|
|
35
|
+
model,
|
|
36
|
+
services: services ?? {
|
|
37
|
+
// services for testing, else use the disk loader option for running this service locally
|
|
38
|
+
...defaultServices,
|
|
39
|
+
formsService: await formsService()
|
|
40
|
+
},
|
|
41
|
+
controllers,
|
|
42
|
+
cacheName: 'session',
|
|
43
|
+
nunjucks: {
|
|
44
|
+
baseLayoutPath: 'dxt-devtool-baselayout.html',
|
|
45
|
+
paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner
|
|
46
|
+
},
|
|
47
|
+
viewContext: devtoolContext
|
|
48
|
+
}
|
|
31
49
|
}
|
|
32
50
|
}
|
|
33
51
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { existsSync } from 'fs'
|
|
2
|
+
import { dirname, join } from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
|
|
1
5
|
import { hasFormComponents, slugSchema } from '@defra/forms-model'
|
|
2
6
|
import Boom from '@hapi/boom'
|
|
3
7
|
import {
|
|
@@ -11,6 +15,7 @@ import vision from '@hapi/vision'
|
|
|
11
15
|
import { isEqual } from 'date-fns'
|
|
12
16
|
import Joi from 'joi'
|
|
13
17
|
import nunjucks, { type Environment } from 'nunjucks'
|
|
18
|
+
import resolvePkg from 'resolve'
|
|
14
19
|
|
|
15
20
|
import { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'
|
|
16
21
|
import {
|
|
@@ -25,7 +30,6 @@ import {
|
|
|
25
30
|
redirectPath
|
|
26
31
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
27
32
|
import {
|
|
28
|
-
PLUGIN_PATH,
|
|
29
33
|
VIEW_PATH,
|
|
30
34
|
context,
|
|
31
35
|
prepareNunjucksEnvironment
|
|
@@ -65,6 +69,20 @@ import * as httpService from '~/src/server/services/httpService.js'
|
|
|
65
69
|
import { CacheService } from '~/src/server/services/index.js'
|
|
66
70
|
import { type Services } from '~/src/server/types.js'
|
|
67
71
|
|
|
72
|
+
export function findPackageRoot() {
|
|
73
|
+
const currentFileName = fileURLToPath(import.meta.url)
|
|
74
|
+
const currentDirectoryName = dirname(currentFileName)
|
|
75
|
+
|
|
76
|
+
let dir = currentDirectoryName
|
|
77
|
+
while (dir !== '/') {
|
|
78
|
+
if (existsSync(join(dir, 'package.json'))) {
|
|
79
|
+
return dir
|
|
80
|
+
}
|
|
81
|
+
dir = dirname(dir)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throw new Error('package.json not found in parent directories')
|
|
85
|
+
}
|
|
68
86
|
export interface PluginOptions {
|
|
69
87
|
model?: FormModel
|
|
70
88
|
services?: Services
|
|
@@ -73,6 +91,13 @@ export interface PluginOptions {
|
|
|
73
91
|
viewPaths?: string[]
|
|
74
92
|
filters?: Record<string, FilterFunction>
|
|
75
93
|
pluginPath?: string
|
|
94
|
+
nunjucks: {
|
|
95
|
+
baseLayoutPath: string
|
|
96
|
+
paths: string[]
|
|
97
|
+
}
|
|
98
|
+
viewContext: (
|
|
99
|
+
request: FormRequest | FormRequestPayload | null
|
|
100
|
+
) => Record<string, unknown>
|
|
76
101
|
}
|
|
77
102
|
|
|
78
103
|
export const plugin = {
|
|
@@ -85,23 +110,25 @@ export const plugin = {
|
|
|
85
110
|
services = defaultServices,
|
|
86
111
|
controllers,
|
|
87
112
|
cacheName,
|
|
88
|
-
viewPaths,
|
|
89
113
|
filters,
|
|
90
|
-
|
|
114
|
+
nunjucks: nunjucksOptions,
|
|
115
|
+
viewContext
|
|
91
116
|
} = options
|
|
92
117
|
const { formsService } = services
|
|
93
118
|
const cacheService = new CacheService(server, cacheName)
|
|
94
119
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
120
|
+
const packageRoot = findPackageRoot()
|
|
121
|
+
const govukFrontendPath = dirname(
|
|
122
|
+
resolvePkg.sync('govuk-frontend/package.json')
|
|
123
|
+
)
|
|
99
124
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
const viewPathResolved = join(packageRoot, VIEW_PATH)
|
|
126
|
+
|
|
127
|
+
const paths = [
|
|
128
|
+
...nunjucksOptions.paths,
|
|
129
|
+
viewPathResolved,
|
|
130
|
+
join(govukFrontendPath, 'dist')
|
|
131
|
+
]
|
|
105
132
|
|
|
106
133
|
await server.register({
|
|
107
134
|
plugin: vision,
|
|
@@ -127,10 +154,7 @@ export const plugin = {
|
|
|
127
154
|
) => {
|
|
128
155
|
// Nunjucks also needs an additional path configuration
|
|
129
156
|
// to use the templates and macros from `govuk-frontend`
|
|
130
|
-
const environment = nunjucks.configure(
|
|
131
|
-
...path,
|
|
132
|
-
'node_modules/govuk-frontend/dist'
|
|
133
|
-
])
|
|
157
|
+
const environment = nunjucks.configure(paths)
|
|
134
158
|
|
|
135
159
|
// Applies custom filters and globals for nunjucks
|
|
136
160
|
// that are required by the `forms-engine-plugin`
|
|
@@ -142,12 +166,14 @@ export const plugin = {
|
|
|
142
166
|
}
|
|
143
167
|
}
|
|
144
168
|
},
|
|
145
|
-
path,
|
|
169
|
+
path: paths,
|
|
146
170
|
// Provides global context used with all templates
|
|
147
171
|
context
|
|
148
172
|
}
|
|
149
173
|
})
|
|
150
174
|
|
|
175
|
+
server.expose('baseLayoutPath', nunjucksOptions.baseLayoutPath)
|
|
176
|
+
server.expose('viewContext', viewContext)
|
|
151
177
|
server.expose('cacheService', cacheService)
|
|
152
178
|
|
|
153
179
|
server.app.model = model
|
|
@@ -1,46 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { FormStatus } from '~/src/server/routes/types.js'
|
|
5
|
-
import { getJson } from '~/src/server/services/httpService.js'
|
|
1
|
+
const error = Error(
|
|
2
|
+
'Not implemented. Consider setting up a form loader or providing a service implementation.'
|
|
3
|
+
)
|
|
6
4
|
|
|
5
|
+
// eslint-disable-next-line jsdoc/require-returns-check
|
|
7
6
|
/**
|
|
8
|
-
*
|
|
9
|
-
* @param {string}
|
|
7
|
+
* Dummy function to get form metadata.
|
|
8
|
+
* @param {string} _slug - the slug of the form
|
|
9
|
+
* @returns {Promise<FormMetadata>}
|
|
10
10
|
*/
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const { payload: metadata } = await getJsonByType(
|
|
15
|
-
`${config.get('managerUrl')}/forms/slug/${slug}`
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
// Run it through the schema to coerce dates
|
|
19
|
-
const result = formMetadataSchema.validate(metadata)
|
|
20
|
-
|
|
21
|
-
if (result.error) {
|
|
22
|
-
throw result.error
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return result.value
|
|
11
|
+
export function getFormMetadata(_slug) {
|
|
12
|
+
throw error
|
|
26
13
|
}
|
|
27
14
|
|
|
15
|
+
// eslint-disable-next-line jsdoc/require-returns-check
|
|
28
16
|
/**
|
|
29
|
-
*
|
|
30
|
-
* @param {string}
|
|
31
|
-
* @param {FormStatus}
|
|
17
|
+
* Dummy function to get form metadata.
|
|
18
|
+
* @param {string} _id - the id of the form
|
|
19
|
+
* @param {FormStatus} _state - the state of the form
|
|
20
|
+
* @returns {Promise<FormDefinition | undefined>}
|
|
32
21
|
*/
|
|
33
|
-
export
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const suffix = state === FormStatus.Draft ? `/${state}` : ''
|
|
37
|
-
const { payload: definition } = await getJsonByType(
|
|
38
|
-
`${config.get('managerUrl')}/forms/${id}/definition${suffix}`
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
return definition
|
|
22
|
+
export function getFormDefinition(_id, _state) {
|
|
23
|
+
throw error
|
|
42
24
|
}
|
|
43
25
|
|
|
44
26
|
/**
|
|
45
|
-
* @import { FormDefinition, FormMetadata } from '@defra/forms-model'
|
|
27
|
+
* @import { FormStatus, FormDefinition, FormMetadata } from '@defra/forms-model'
|
|
46
28
|
*/
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { config } from '~/src/config/index.js'
|
|
2
|
+
import { FileFormService } from '~/src/server/utils/file-form-service.js'
|
|
3
|
+
|
|
4
|
+
// Create shared form metadata
|
|
5
|
+
const now = new Date()
|
|
6
|
+
const user = { id: 'user', displayName: 'Username' }
|
|
7
|
+
const author = {
|
|
8
|
+
createdAt: now,
|
|
9
|
+
createdBy: user,
|
|
10
|
+
updatedAt: now,
|
|
11
|
+
updatedBy: user
|
|
12
|
+
}
|
|
13
|
+
const metadata = {
|
|
14
|
+
organisation: 'Defra',
|
|
15
|
+
teamName: 'Team name',
|
|
16
|
+
teamEmail: 'team@defra.gov.uk',
|
|
17
|
+
submissionGuidance: "Thanks for your submission, we'll be in touch",
|
|
18
|
+
notificationEmail: config.get('submissionEmailAddress'),
|
|
19
|
+
...author,
|
|
20
|
+
live: author
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Return an function rather than the service directly. This is to prevent consumer applications
|
|
25
|
+
* blowing up as they won't have these files on disk. We can defer the execution until when it's
|
|
26
|
+
* needed, i.e. the createServer function of the devtool.
|
|
27
|
+
*/
|
|
28
|
+
export const formsService = async () => {
|
|
29
|
+
// Instantiate the file loader form service
|
|
30
|
+
const loader = new FileFormService()
|
|
31
|
+
|
|
32
|
+
// Add a Json form
|
|
33
|
+
await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.json', {
|
|
34
|
+
...metadata,
|
|
35
|
+
id: '95e92559-968d-44ae-8666-2b1ad3dffd31',
|
|
36
|
+
title: 'Register as a unicorn breeder',
|
|
37
|
+
slug: 'register-as-a-unicorn-breeder'
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Add a Yaml form
|
|
41
|
+
await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.yaml', {
|
|
42
|
+
...metadata,
|
|
43
|
+
id: '641aeafd-13dd-40fa-9186-001703800efb',
|
|
44
|
+
title: 'Register as a unicorn breeder (yaml)',
|
|
45
|
+
slug: 'register-as-a-unicorn-breeder-yaml' // if we needed to validate any JSON logic, make it available for convenience
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return loader.toFormsService()
|
|
49
|
+
}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
{% block bodyEnd %}
|
|
36
36
|
{{ super() }}
|
|
37
37
|
<script type="module" nonce="{{ cspNonce }}">
|
|
38
|
-
import { initFileUpload } from '{{
|
|
38
|
+
import { initFileUpload } from '{{ getDxtAssetPath("file-upload.js") }}';
|
|
39
39
|
if (document.readyState === 'loading') {
|
|
40
40
|
document.addEventListener('DOMContentLoaded', initFileUpload);
|
|
41
41
|
} else {
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
type ResponseToolkit,
|
|
4
4
|
type ServerRegisterPluginObject
|
|
5
5
|
} from '@hapi/hapi'
|
|
6
|
-
import { StatusCodes } from 'http-status-codes'
|
|
7
6
|
|
|
8
7
|
/*
|
|
9
8
|
* Add an `onPreResponse` listener to return error pages
|
|
@@ -20,36 +19,15 @@ export default {
|
|
|
20
19
|
// processing the request
|
|
21
20
|
const statusCode = response.output.statusCode
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
// and use it to set the correct service name
|
|
25
|
-
// and start page path. In the event of a error
|
|
26
|
-
// happening inside a "form" level request, the header
|
|
27
|
-
// then displays the contextual form text and href
|
|
28
|
-
const model = request.app.model
|
|
29
|
-
const viewModel = model
|
|
30
|
-
? {
|
|
31
|
-
name: model.name,
|
|
32
|
-
serviceUrl: `/${model.basePath}`
|
|
33
|
-
}
|
|
34
|
-
: undefined
|
|
35
|
-
|
|
36
|
-
// In the event of 404
|
|
37
|
-
// return the `404` view
|
|
38
|
-
if (statusCode === StatusCodes.NOT_FOUND.valueOf()) {
|
|
39
|
-
return h.view('404', viewModel).code(statusCode)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
request.log('error', {
|
|
22
|
+
const error = {
|
|
43
23
|
statusCode,
|
|
44
|
-
data: response.data,
|
|
45
24
|
message: response.message,
|
|
46
25
|
stack: response.stack
|
|
47
|
-
}
|
|
26
|
+
}
|
|
48
27
|
|
|
49
|
-
request.
|
|
28
|
+
request.log('error', error)
|
|
50
29
|
|
|
51
|
-
|
|
52
|
-
return h.view('500', viewModel).code(statusCode)
|
|
30
|
+
return h.response(error).code(statusCode)
|
|
53
31
|
}
|
|
54
32
|
return h.continue
|
|
55
33
|
})
|
|
@@ -5,7 +5,6 @@ import Boom from '@hapi/boom'
|
|
|
5
5
|
import { StatusCodes } from 'http-status-codes'
|
|
6
6
|
|
|
7
7
|
import pkg from '~/package.json' with { type: 'json' }
|
|
8
|
-
import { parseCookieConsent } from '~/src/common/cookies.js'
|
|
9
8
|
import { config } from '~/src/config/index.js'
|
|
10
9
|
import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
|
|
11
10
|
import { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'
|
|
@@ -23,20 +22,8 @@ let webpackManifest
|
|
|
23
22
|
* @param {FormRequest | FormRequestPayload | null} request
|
|
24
23
|
*/
|
|
25
24
|
export function context(request) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
if (!webpackManifest) {
|
|
29
|
-
try {
|
|
30
|
-
// eslint-disable-next-line -- Allow JSON type 'any'
|
|
31
|
-
webpackManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))
|
|
32
|
-
} catch {
|
|
33
|
-
logger.error(`Webpack ${basename(manifestPath)} not found`)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
25
|
+
const { params, path, response } = request ?? {}
|
|
36
26
|
|
|
37
|
-
const { params, path, query = {}, response, state } = request ?? {}
|
|
38
|
-
|
|
39
|
-
const isForceAccess = 'force' in query
|
|
40
27
|
const isPreviewMode = path?.startsWith(PREVIEW_PATH_PREFIX)
|
|
41
28
|
|
|
42
29
|
// Only add the slug in to the context if the response is OK.
|
|
@@ -44,45 +31,68 @@ export function context(request) {
|
|
|
44
31
|
const isResponseOK =
|
|
45
32
|
!Boom.isBoom(response) && response?.statusCode === StatusCodes.OK
|
|
46
33
|
|
|
34
|
+
const pluginStorage = request?.server.plugins['forms-engine-plugin']
|
|
35
|
+
let consumerViewContext = {}
|
|
36
|
+
|
|
37
|
+
if (!pluginStorage) {
|
|
38
|
+
throw Error('context called before plugin registered')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!pluginStorage.baseLayoutPath) {
|
|
42
|
+
throw Error('Missing baseLayoutPath in plugin.options.nunjucks')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if ('viewContext' in pluginStorage) {
|
|
46
|
+
consumerViewContext = pluginStorage.viewContext(request)
|
|
47
|
+
}
|
|
48
|
+
|
|
47
49
|
/** @type {ViewContext} */
|
|
48
50
|
const ctx = {
|
|
51
|
+
// take consumers props first so we can override it
|
|
52
|
+
...consumerViewContext,
|
|
53
|
+
baseLayoutPath: pluginStorage.baseLayoutPath,
|
|
49
54
|
appVersion: pkg.version,
|
|
50
|
-
assetPath: '/assets',
|
|
51
55
|
config: {
|
|
52
56
|
cdpEnvironment: config.get('cdpEnvironment'),
|
|
53
57
|
designerUrl: config.get('designerUrl'),
|
|
54
58
|
feedbackLink: encodeUrl(config.get('feedbackLink')),
|
|
55
59
|
phaseTag: config.get('phaseTag'),
|
|
56
|
-
serviceBannerText: config.get('serviceBannerText'),
|
|
57
60
|
serviceName: config.get('serviceName'),
|
|
58
61
|
serviceVersion: config.get('serviceVersion')
|
|
59
62
|
},
|
|
60
63
|
crumb: safeGenerateCrumb(request),
|
|
61
|
-
|
|
62
|
-
currentPath: request ? `${request.path}${request.url.search}` : undefined,
|
|
64
|
+
currentPath: `${request.path}${request.url.search}`,
|
|
63
65
|
previewMode: isPreviewMode ? params?.state : undefined,
|
|
64
|
-
slug: isResponseOK ? params?.slug : undefined
|
|
65
|
-
|
|
66
|
-
getAssetPath: (asset = '') => {
|
|
67
|
-
return `/${webpackManifest?.[asset] ?? asset}`
|
|
68
|
-
}
|
|
66
|
+
slug: isResponseOK ? params?.slug : undefined
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
return ctx
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Returns the context for the devtool. Consumers won't have access to this.
|
|
74
|
+
*/
|
|
75
|
+
export function devtoolContext() {
|
|
76
|
+
const manifestPath = join(config.get('publicDir'), 'assets-manifest.json')
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
if (!webpackManifest) {
|
|
79
|
+
try {
|
|
80
|
+
// eslint-disable-next-line -- Allow JSON type 'any'
|
|
81
|
+
webpackManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))
|
|
82
|
+
} catch {
|
|
83
|
+
logger.error(`Webpack ${basename(manifestPath)} not found`)
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
return
|
|
87
|
+
return {
|
|
88
|
+
assetPath: '/assets',
|
|
89
|
+
getDxtAssetPath: (asset = '') => {
|
|
90
|
+
return `/${webpackManifest?.[asset] ?? asset}`
|
|
91
|
+
}
|
|
92
|
+
}
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
/**
|
|
85
|
-
* @import { CookieConsent } from '~/src/common/types.js'
|
|
86
96
|
* @import { ViewContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
87
97
|
* @import { FormRequest, FormRequestPayload } from '~/src/server/routes/types.js'
|
|
88
98
|
*/
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import { tmpdir } from 'node:os'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
context,
|
|
5
|
+
devtoolContext
|
|
6
|
+
} from '~/src/server/plugins/nunjucks/context.js'
|
|
6
7
|
|
|
7
8
|
describe('Nunjucks context', () => {
|
|
8
9
|
beforeEach(() => jest.resetModules())
|
|
9
10
|
|
|
10
11
|
describe('Asset path', () => {
|
|
11
12
|
it("should include 'assetPath' for GOV.UK Frontend icons", () => {
|
|
12
|
-
const { assetPath } =
|
|
13
|
+
const { assetPath } = devtoolContext()
|
|
13
14
|
expect(assetPath).toBe('/assets')
|
|
14
15
|
})
|
|
15
16
|
})
|
|
16
17
|
|
|
17
18
|
describe('Asset helper', () => {
|
|
18
19
|
it("should locate 'assets-manifest.json' assets", () => {
|
|
19
|
-
const {
|
|
20
|
+
const { getDxtAssetPath } = devtoolContext()
|
|
20
21
|
|
|
21
|
-
expect(
|
|
22
|
+
expect(getDxtAssetPath('example.scss')).toBe(
|
|
22
23
|
'/stylesheets/example.xxxxxxx.min.css'
|
|
23
24
|
)
|
|
24
25
|
|
|
25
|
-
expect(
|
|
26
|
+
expect(getDxtAssetPath('example.mjs')).toBe(
|
|
26
27
|
'/javascripts/example.xxxxxxx.min.js'
|
|
27
28
|
)
|
|
28
29
|
})
|
|
@@ -32,43 +33,33 @@ describe('Nunjucks context', () => {
|
|
|
32
33
|
const { config } = await import('~/src/config/index.js')
|
|
33
34
|
|
|
34
35
|
// Import when isolated to avoid cache
|
|
35
|
-
const {
|
|
36
|
+
const { devtoolContext } = await import(
|
|
36
37
|
'~/src/server/plugins/nunjucks/context.js'
|
|
37
38
|
)
|
|
38
39
|
|
|
39
40
|
// Update config for missing manifest
|
|
40
41
|
config.set('publicDir', tmpdir())
|
|
41
|
-
const {
|
|
42
|
+
const { getDxtAssetPath } = devtoolContext()
|
|
42
43
|
|
|
43
44
|
// Uses original paths when missing
|
|
44
|
-
expect(
|
|
45
|
-
expect(
|
|
45
|
+
expect(getDxtAssetPath('example.scss')).toBe('/example.scss')
|
|
46
|
+
expect(getDxtAssetPath('example.mjs')).toBe('/example.mjs')
|
|
46
47
|
})
|
|
47
48
|
})
|
|
48
49
|
|
|
49
50
|
it('should return path to unknown assets', () => {
|
|
50
|
-
const {
|
|
51
|
+
const { getDxtAssetPath } = devtoolContext()
|
|
51
52
|
|
|
52
|
-
expect(
|
|
53
|
-
expect(
|
|
54
|
-
expect(
|
|
53
|
+
expect(getDxtAssetPath()).toBe('/')
|
|
54
|
+
expect(getDxtAssetPath('example.jpg')).toBe('/example.jpg')
|
|
55
|
+
expect(getDxtAssetPath('example.gif')).toBe('/example.gif')
|
|
55
56
|
})
|
|
56
57
|
})
|
|
57
58
|
|
|
58
59
|
describe('Config', () => {
|
|
59
60
|
it('should include environment, phase tag and service info', () => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
expect(ctx.config).toEqual(
|
|
63
|
-
expect.objectContaining({
|
|
64
|
-
cdpEnvironment: config.get('cdpEnvironment'),
|
|
65
|
-
feedbackLink: encodeUrl(config.get('feedbackLink')),
|
|
66
|
-
googleAnalyticsTrackingId: config.get('googleAnalyticsTrackingId'),
|
|
67
|
-
phaseTag: config.get('phaseTag'),
|
|
68
|
-
serviceBannerText: config.get('serviceBannerText'),
|
|
69
|
-
serviceName: config.get('serviceName'),
|
|
70
|
-
serviceVersion: config.get('serviceVersion')
|
|
71
|
-
})
|
|
61
|
+
expect(() => context(null)).toThrow(
|
|
62
|
+
'context called before plugin registered'
|
|
72
63
|
)
|
|
73
64
|
})
|
|
74
65
|
})
|
|
@@ -83,6 +74,9 @@ describe('Nunjucks context', () => {
|
|
|
83
74
|
plugins: {
|
|
84
75
|
crumb: {
|
|
85
76
|
generate: jest.fn()
|
|
77
|
+
},
|
|
78
|
+
'forms-engine-plugin': {
|
|
79
|
+
baseLayoutPath: 'randomValue'
|
|
86
80
|
}
|
|
87
81
|
}
|
|
88
82
|
},
|
|
@@ -113,6 +107,9 @@ describe('Nunjucks context', () => {
|
|
|
113
107
|
plugins: {
|
|
114
108
|
crumb: {
|
|
115
109
|
generate: jest.fn().mockReturnValue(mockCrumb)
|
|
110
|
+
},
|
|
111
|
+
'forms-engine-plugin': {
|
|
112
|
+
baseLayoutPath: 'randomValue'
|
|
116
113
|
}
|
|
117
114
|
}
|
|
118
115
|
},
|