@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","pkg","type","
|
|
1
|
+
{"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","pkg","type","config","createLogger","PREVIEW_PATH_PREFIX","encodeUrl","safeGenerateCrumb","logger","webpackManifest","context","request","params","path","response","isPreviewMode","startsWith","isResponseOK","isBoom","statusCode","OK","pluginStorage","server","plugins","consumerViewContext","Error","baseLayoutPath","viewContext","ctx","appVersion","version","cdpEnvironment","get","designerUrl","feedbackLink","phaseTag","serviceName","serviceVersion","crumb","currentPath","url","search","previewMode","state","undefined","slug","devtoolContext","manifestPath","JSON","parse","error","assetPath","getDxtAssetPath","asset"],"sources":["../../../../src/server/plugins/nunjucks/context.js"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport Boom from '@hapi/boom'\nimport { StatusCodes } from 'http-status-codes'\n\nimport pkg from '~/package.json' with { type: 'json' }\nimport { config } from '~/src/config/index.js'\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'\nimport {\n encodeUrl,\n safeGenerateCrumb\n} from '~/src/server/plugins/engine/helpers.js'\n\nconst logger = createLogger()\n\n/** @type {Record<string, string> | undefined} */\nlet webpackManifest\n\n/**\n * @param {FormRequest | FormRequestPayload | null} request\n */\nexport function context(request) {\n const { params, path, response } = request ?? {}\n\n const isPreviewMode = path?.startsWith(PREVIEW_PATH_PREFIX)\n\n // Only add the slug in to the context if the response is OK.\n // Footer meta links are not rendered when the slug is missing.\n const isResponseOK =\n !Boom.isBoom(response) && response?.statusCode === StatusCodes.OK\n\n const pluginStorage = request?.server.plugins['forms-engine-plugin']\n let consumerViewContext = {}\n\n if (!pluginStorage) {\n throw Error('context called before plugin registered')\n }\n\n if (!pluginStorage.baseLayoutPath) {\n throw Error('Missing baseLayoutPath in plugin.options.nunjucks')\n }\n\n if ('viewContext' in pluginStorage) {\n consumerViewContext = pluginStorage.viewContext(request)\n }\n\n /** @type {ViewContext} */\n const ctx = {\n // take consumers props first so we can override it\n ...consumerViewContext,\n baseLayoutPath: pluginStorage.baseLayoutPath,\n appVersion: pkg.version,\n config: {\n cdpEnvironment: config.get('cdpEnvironment'),\n designerUrl: config.get('designerUrl'),\n feedbackLink: encodeUrl(config.get('feedbackLink')),\n phaseTag: config.get('phaseTag'),\n serviceName: config.get('serviceName'),\n serviceVersion: config.get('serviceVersion')\n },\n crumb: safeGenerateCrumb(request),\n currentPath: `${request.path}${request.url.search}`,\n previewMode: isPreviewMode ? params?.state : undefined,\n slug: isResponseOK ? params?.slug : undefined\n }\n\n return ctx\n}\n\n/**\n * Returns the context for the devtool. Consumers won't have access to this.\n */\nexport function devtoolContext() {\n const manifestPath = join(config.get('publicDir'), 'assets-manifest.json')\n\n if (!webpackManifest) {\n try {\n // eslint-disable-next-line -- Allow JSON type 'any'\n webpackManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))\n } catch {\n logger.error(`Webpack ${basename(manifestPath)} not found`)\n }\n }\n\n return {\n assetPath: '/assets',\n getDxtAssetPath: (asset = '') => {\n return `/${webpackManifest?.[asset] ?? asset}`\n }\n }\n}\n\n/**\n * @import { ViewContext } from '~/src/server/plugins/nunjucks/types.js'\n * @import { FormRequest, FormRequestPayload } from '~/src/server/routes/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import { tmpdir } from 'node:os';
|
|
2
|
-
import {
|
|
3
|
-
import { encodeUrl } from "../engine/helpers.js";
|
|
4
|
-
import { context } from "./context.js";
|
|
2
|
+
import { context, devtoolContext } from "./context.js";
|
|
5
3
|
describe('Nunjucks context', () => {
|
|
6
4
|
beforeEach(() => jest.resetModules());
|
|
7
5
|
describe('Asset path', () => {
|
|
8
6
|
it("should include 'assetPath' for GOV.UK Frontend icons", () => {
|
|
9
7
|
const {
|
|
10
8
|
assetPath
|
|
11
|
-
} =
|
|
9
|
+
} = devtoolContext();
|
|
12
10
|
expect(assetPath).toBe('/assets');
|
|
13
11
|
});
|
|
14
12
|
});
|
|
15
13
|
describe('Asset helper', () => {
|
|
16
14
|
it("should locate 'assets-manifest.json' assets", () => {
|
|
17
15
|
const {
|
|
18
|
-
|
|
19
|
-
} =
|
|
20
|
-
expect(
|
|
21
|
-
expect(
|
|
16
|
+
getDxtAssetPath
|
|
17
|
+
} = devtoolContext();
|
|
18
|
+
expect(getDxtAssetPath('example.scss')).toBe('/stylesheets/example.xxxxxxx.min.css');
|
|
19
|
+
expect(getDxtAssetPath('example.mjs')).toBe('/javascripts/example.xxxxxxx.min.js');
|
|
22
20
|
});
|
|
23
21
|
it("should return path when 'assets-manifest.json' is missing", async () => {
|
|
24
22
|
await jest.isolateModulesAsync(async () => {
|
|
@@ -28,41 +26,32 @@ describe('Nunjucks context', () => {
|
|
|
28
26
|
|
|
29
27
|
// Import when isolated to avoid cache
|
|
30
28
|
const {
|
|
31
|
-
|
|
29
|
+
devtoolContext
|
|
32
30
|
} = await import("./context.js");
|
|
33
31
|
|
|
34
32
|
// Update config for missing manifest
|
|
35
33
|
config.set('publicDir', tmpdir());
|
|
36
34
|
const {
|
|
37
|
-
|
|
38
|
-
} =
|
|
35
|
+
getDxtAssetPath
|
|
36
|
+
} = devtoolContext();
|
|
39
37
|
|
|
40
38
|
// Uses original paths when missing
|
|
41
|
-
expect(
|
|
42
|
-
expect(
|
|
39
|
+
expect(getDxtAssetPath('example.scss')).toBe('/example.scss');
|
|
40
|
+
expect(getDxtAssetPath('example.mjs')).toBe('/example.mjs');
|
|
43
41
|
});
|
|
44
42
|
});
|
|
45
43
|
it('should return path to unknown assets', () => {
|
|
46
44
|
const {
|
|
47
|
-
|
|
48
|
-
} =
|
|
49
|
-
expect(
|
|
50
|
-
expect(
|
|
51
|
-
expect(
|
|
45
|
+
getDxtAssetPath
|
|
46
|
+
} = devtoolContext();
|
|
47
|
+
expect(getDxtAssetPath()).toBe('/');
|
|
48
|
+
expect(getDxtAssetPath('example.jpg')).toBe('/example.jpg');
|
|
49
|
+
expect(getDxtAssetPath('example.gif')).toBe('/example.gif');
|
|
52
50
|
});
|
|
53
51
|
});
|
|
54
52
|
describe('Config', () => {
|
|
55
53
|
it('should include environment, phase tag and service info', () => {
|
|
56
|
-
|
|
57
|
-
expect(ctx.config).toEqual(expect.objectContaining({
|
|
58
|
-
cdpEnvironment: config.get('cdpEnvironment'),
|
|
59
|
-
feedbackLink: encodeUrl(config.get('feedbackLink')),
|
|
60
|
-
googleAnalyticsTrackingId: config.get('googleAnalyticsTrackingId'),
|
|
61
|
-
phaseTag: config.get('phaseTag'),
|
|
62
|
-
serviceBannerText: config.get('serviceBannerText'),
|
|
63
|
-
serviceName: config.get('serviceName'),
|
|
64
|
-
serviceVersion: config.get('serviceVersion')
|
|
65
|
-
}));
|
|
54
|
+
expect(() => context(null)).toThrow('context called before plugin registered');
|
|
66
55
|
});
|
|
67
56
|
});
|
|
68
57
|
describe('Crumb', () => {
|
|
@@ -75,6 +64,9 @@ describe('Nunjucks context', () => {
|
|
|
75
64
|
plugins: {
|
|
76
65
|
crumb: {
|
|
77
66
|
generate: jest.fn()
|
|
67
|
+
},
|
|
68
|
+
'forms-engine-plugin': {
|
|
69
|
+
baseLayoutPath: 'randomValue'
|
|
78
70
|
}
|
|
79
71
|
}
|
|
80
72
|
},
|
|
@@ -104,6 +96,9 @@ describe('Nunjucks context', () => {
|
|
|
104
96
|
plugins: {
|
|
105
97
|
crumb: {
|
|
106
98
|
generate: jest.fn().mockReturnValue(mockCrumb)
|
|
99
|
+
},
|
|
100
|
+
'forms-engine-plugin': {
|
|
101
|
+
baseLayoutPath: 'randomValue'
|
|
107
102
|
}
|
|
108
103
|
}
|
|
109
104
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.test.js","names":["tmpdir","
|
|
1
|
+
{"version":3,"file":"context.test.js","names":["tmpdir","context","devtoolContext","describe","beforeEach","jest","resetModules","it","assetPath","expect","toBe","getDxtAssetPath","isolateModulesAsync","config","set","toThrow","malformedRequest","server","plugins","crumb","generate","fn","baseLayoutPath","route","settings","path","url","search","toBeUndefined","not","toHaveBeenCalled","mockCrumb","validRequest","mockReturnValue","state","toHaveBeenCalledWith"],"sources":["../../../../src/server/plugins/nunjucks/context.test.js"],"sourcesContent":["import { tmpdir } from 'node:os'\n\nimport {\n context,\n devtoolContext\n} from '~/src/server/plugins/nunjucks/context.js'\n\ndescribe('Nunjucks context', () => {\n beforeEach(() => jest.resetModules())\n\n describe('Asset path', () => {\n it(\"should include 'assetPath' for GOV.UK Frontend icons\", () => {\n const { assetPath } = devtoolContext()\n expect(assetPath).toBe('/assets')\n })\n })\n\n describe('Asset helper', () => {\n it(\"should locate 'assets-manifest.json' assets\", () => {\n const { getDxtAssetPath } = devtoolContext()\n\n expect(getDxtAssetPath('example.scss')).toBe(\n '/stylesheets/example.xxxxxxx.min.css'\n )\n\n expect(getDxtAssetPath('example.mjs')).toBe(\n '/javascripts/example.xxxxxxx.min.js'\n )\n })\n\n it(\"should return path when 'assets-manifest.json' is missing\", async () => {\n await jest.isolateModulesAsync(async () => {\n const { config } = await import('~/src/config/index.js')\n\n // Import when isolated to avoid cache\n const { devtoolContext } = await import(\n '~/src/server/plugins/nunjucks/context.js'\n )\n\n // Update config for missing manifest\n config.set('publicDir', tmpdir())\n const { getDxtAssetPath } = devtoolContext()\n\n // Uses original paths when missing\n expect(getDxtAssetPath('example.scss')).toBe('/example.scss')\n expect(getDxtAssetPath('example.mjs')).toBe('/example.mjs')\n })\n })\n\n it('should return path to unknown assets', () => {\n const { getDxtAssetPath } = devtoolContext()\n\n expect(getDxtAssetPath()).toBe('/')\n expect(getDxtAssetPath('example.jpg')).toBe('/example.jpg')\n expect(getDxtAssetPath('example.gif')).toBe('/example.gif')\n })\n })\n\n describe('Config', () => {\n it('should include environment, phase tag and service info', () => {\n expect(() => context(null)).toThrow(\n 'context called before plugin registered'\n )\n })\n })\n\n describe('Crumb', () => {\n it('should handle malformed requests with missing state', () => {\n // While state should always exist in a valid Hapi request (it holds cookies),\n // we've seen malformed requests in production where it's missing\n const malformedRequest = /** @type {FormRequest} */ (\n /** @type {unknown} */ ({\n server: {\n plugins: {\n crumb: {\n generate: jest.fn()\n },\n 'forms-engine-plugin': {\n baseLayoutPath: 'randomValue'\n }\n }\n },\n plugins: {},\n route: {\n settings: {\n plugins: {}\n }\n },\n path: '/test',\n url: { search: '' }\n // state intentionally omitted to test real malformed requests\n })\n )\n\n const { crumb } = context(malformedRequest)\n expect(crumb).toBeUndefined()\n expect(\n malformedRequest.server.plugins.crumb.generate\n ).not.toHaveBeenCalled()\n })\n\n it('should generate crumb when state exists', () => {\n const mockCrumb = 'generated-crumb-value'\n const validRequest = /** @type {FormRequest} */ (\n /** @type {unknown} */ ({\n server: {\n plugins: {\n crumb: {\n generate: jest.fn().mockReturnValue(mockCrumb)\n },\n 'forms-engine-plugin': {\n baseLayoutPath: 'randomValue'\n }\n }\n },\n plugins: {},\n route: {\n settings: {\n plugins: {}\n }\n },\n path: '/test',\n url: { search: '' },\n state: {}\n })\n )\n\n const { crumb } = context(validRequest)\n expect(crumb).toBe(mockCrumb)\n expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith(\n validRequest\n )\n })\n })\n})\n\n/**\n * @import { FormRequest } from '~/src/server/routes/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -12,16 +12,15 @@
|
|
|
12
12
|
/**
|
|
13
13
|
* @typedef {object} ViewContext - Nunjucks view context
|
|
14
14
|
* @property {string} appVersion - Application version
|
|
15
|
-
* @property {string}
|
|
15
|
+
* @property {string} [baseLayoutPath] - Base layout path
|
|
16
16
|
* @property {Partial<Config>} config - Application config properties
|
|
17
|
-
* @property {CookieConsent} [cookieConsent] - Cookie consent preferences
|
|
18
17
|
* @property {string} [crumb] - Cross-Site Request Forgery (CSRF) token
|
|
19
18
|
* @property {string} [cspNonce] - Content Security Policy (CSP) nonce
|
|
20
19
|
* @property {string} [currentPath] - Current path
|
|
21
20
|
* @property {string} [previewMode] - Preview mode
|
|
22
21
|
* @property {string} [slug] - Form slug
|
|
23
|
-
* @property {(asset?: string) => string} getAssetPath - Asset path resolver
|
|
24
22
|
* @property {FormContext} [context] - the current form context
|
|
23
|
+
* @property {PluginOptions['viewContext']} [injectedViewContext] - the current form context
|
|
25
24
|
*/
|
|
26
25
|
|
|
27
26
|
/**
|
|
@@ -34,8 +33,8 @@
|
|
|
34
33
|
*/
|
|
35
34
|
|
|
36
35
|
/**
|
|
37
|
-
* @import { CookieConsent } from '~/src/common/types.js'
|
|
38
36
|
* @import { config } from '~/src/config/index.js'
|
|
39
37
|
* @import { FormContext } from '~/src/server/plugins/engine/types.js'
|
|
38
|
+
* @import { PluginOptions } from '~/src/server/plugins/engine/plugin.js'
|
|
40
39
|
*/
|
|
41
40
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/server/plugins/nunjucks/types.js"],"sourcesContent":["/**\n * @typedef {object} MacroOptions\n * @property {string} [callBlock] - Nunjucks call block content\n * @property {object} [params] - Nunjucks macro params\n */\n\n/**\n * @typedef {object} RenderOptions\n * @property {object} [context] - Nunjucks render context\n */\n\n/**\n * @typedef {object} ViewContext - Nunjucks view context\n * @property {string} appVersion - Application version\n * @property {string}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/server/plugins/nunjucks/types.js"],"sourcesContent":["/**\n * @typedef {object} MacroOptions\n * @property {string} [callBlock] - Nunjucks call block content\n * @property {object} [params] - Nunjucks macro params\n */\n\n/**\n * @typedef {object} RenderOptions\n * @property {object} [context] - Nunjucks render context\n */\n\n/**\n * @typedef {object} ViewContext - Nunjucks view context\n * @property {string} appVersion - Application version\n * @property {string} [baseLayoutPath] - Base layout path\n * @property {Partial<Config>} config - Application config properties\n * @property {string} [crumb] - Cross-Site Request Forgery (CSRF) token\n * @property {string} [cspNonce] - Content Security Policy (CSP) nonce\n * @property {string} [currentPath] - Current path\n * @property {string} [previewMode] - Preview mode\n * @property {string} [slug] - Form slug\n * @property {FormContext} [context] - the current form context\n * @property {PluginOptions['viewContext']} [injectedViewContext] - the current form context\n */\n\n/**\n * @typedef {ReturnType<typeof config['getProperties']>} Config - Application config properties\n */\n\n/**\n * @typedef NunjucksContext\n * @property {ViewContext} ctx - the current nunjucks view context\n */\n\n/**\n * @import { config } from '~/src/config/index.js'\n * @import { FormContext } from '~/src/server/plugins/engine/types.js'\n * @import { PluginOptions } from '~/src/server/plugins/engine/plugin.js'\n */\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["default","publicRoutes"
|
|
1
|
+
{"version":3,"file":"index.js","names":["default","publicRoutes"],"sources":["../../../src/server/routes/index.ts"],"sourcesContent":["export { default as publicRoutes } from '~/src/server/routes/public.js'\n"],"mappings":"AAAA,SAASA,OAAO,IAAIC,YAAY","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: Request | FormRequest | FormRequestPayload) => string\n }\n 'forms-engine-plugin': {\n
|
|
1
|
+
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type context } from '~/src/server/plugins/engine/nunjucks.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: Request | FormRequest | FormRequestPayload) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext: context\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module '@hapi/scooter' {\n declare const hapiScooter: {\n plugin: Plugin\n }\n\n export = hapiScooter\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"./file-form-service.js": "./.server/server/utils/file-form-service.js",
|
|
25
25
|
"./controllers/*": "./.server/server/plugins/engine/pageControllers/*",
|
|
26
26
|
"./services/*": "./.server/server/plugins/engine/services/*",
|
|
27
|
+
"./engine/*": "./.server/server/plugins/engine/*",
|
|
27
28
|
"./package.json": "./package.json"
|
|
28
29
|
},
|
|
29
30
|
"scripts": {
|
|
@@ -12,3 +12,13 @@
|
|
|
12
12
|
.autocomplete__option {
|
|
13
13
|
@include govuk-typography-common;
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
// An example of some user-supplied styling
|
|
17
|
+
// Not great practice but it illustrates the point
|
|
18
|
+
.govuk-header {
|
|
19
|
+
background: #008531;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.govuk-header__container {
|
|
23
|
+
border-bottom: 10px solid #003d16;
|
|
24
|
+
}
|
package/src/config/index.ts
CHANGED
|
@@ -179,12 +179,6 @@ export const config = convict({
|
|
|
179
179
|
/**
|
|
180
180
|
* API integrations
|
|
181
181
|
*/
|
|
182
|
-
managerUrl: {
|
|
183
|
-
format: String,
|
|
184
|
-
default: 'http://localhost:3001',
|
|
185
|
-
env: 'MANAGER_URL'
|
|
186
|
-
} as SchemaObj<string>,
|
|
187
|
-
|
|
188
182
|
designerUrl: {
|
|
189
183
|
format: String,
|
|
190
184
|
default: 'http://localhost:3000',
|
|
@@ -253,19 +247,12 @@ export const config = convict({
|
|
|
253
247
|
env: 'STAGING_PREFIX'
|
|
254
248
|
},
|
|
255
249
|
|
|
256
|
-
|
|
257
|
-
doc: '
|
|
258
|
-
format: String,
|
|
259
|
-
default: '',
|
|
260
|
-
env: 'SERVICE_BANNER_TEXT'
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
googleAnalyticsTrackingId: {
|
|
264
|
-
doc: 'Google analytics tracking ID to be used when a user has opted in to additional cookies',
|
|
250
|
+
submissionEmailAddress: {
|
|
251
|
+
doc: 'Email address to send the form to (local devtool only)',
|
|
265
252
|
format: String,
|
|
266
253
|
default: '',
|
|
267
|
-
env: '
|
|
268
|
-
}
|
|
254
|
+
env: 'SUBMISSION_EMAIL_ADDRESS'
|
|
255
|
+
} as SchemaObj<string>
|
|
269
256
|
})
|
|
270
257
|
|
|
271
258
|
config.validate({ allowed: 'strict' })
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{% extends "govuk/template.njk" %}
|
|
2
|
+
|
|
3
|
+
{% from "govuk/components/back-link/macro.njk" import govukBackLink -%}
|
|
4
|
+
{% from "govuk/components/footer/macro.njk" import govukFooter -%}
|
|
5
|
+
{% from "govuk/components/phase-banner/macro.njk" import govukPhaseBanner -%}
|
|
6
|
+
{% from "govuk/components/skip-link/macro.njk" import govukSkipLink -%}
|
|
7
|
+
{% from "govuk/macros/attributes.njk" import govukAttributes -%}
|
|
8
|
+
{% from "components/service-banner/macro.njk" import appServiceBanner -%}
|
|
9
|
+
{% from "components/tag-env/macro.njk" import appTagEnv -%}
|
|
10
|
+
{% from "govuk/components/cookie-banner/macro.njk" import govukCookieBanner -%}
|
|
11
|
+
{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner -%}
|
|
12
|
+
|
|
13
|
+
{% set productName %}
|
|
14
|
+
{{ appTagEnv({ env: "devtool" }) }}
|
|
15
|
+
{% endset %}
|
|
16
|
+
|
|
17
|
+
{% block head %}
|
|
18
|
+
<link rel="preload" as="font" href="{{ assetPath }}/fonts/bold-b542beb274-v2.woff2" type="font/woff2" crossorigin="anonymous">
|
|
19
|
+
<link rel="preload" as="font" href="{{ assetPath }}/fonts/light-94a07e06a1-v2.woff2" type="font/woff2" crossorigin="anonymous">
|
|
20
|
+
<link rel="stylesheet" href="{{ getDxtAssetPath("stylesheets/application.scss") }}">
|
|
21
|
+
{% endblock %}
|
|
22
|
+
|
|
23
|
+
{% block pageTitle -%}
|
|
24
|
+
{{ "Error: " if errors | length }}{{ pageTitle | evaluate }} - {{ name if name else config.serviceName }} - GOV.UK
|
|
25
|
+
{%- endblock %}
|
|
26
|
+
|
|
27
|
+
{% block skipLink %}
|
|
28
|
+
{{ govukSkipLink({
|
|
29
|
+
href: '#main-content',
|
|
30
|
+
text: 'Skip to main content'
|
|
31
|
+
}) }}
|
|
32
|
+
{% endblock %}
|
|
33
|
+
|
|
34
|
+
{% block header %}
|
|
35
|
+
{{ govukHeader({
|
|
36
|
+
homepageUrl: currentPath if context.isForceAccess else "https://defra.github.io/forms-engine-plugin/",
|
|
37
|
+
containerClasses: "govuk-width-container",
|
|
38
|
+
productName: productName | safe | trim,
|
|
39
|
+
serviceName: "Digital Express Toolkit",
|
|
40
|
+
serviceUrl: currentPath if context.isForceAccess else serviceUrl
|
|
41
|
+
}) }}
|
|
42
|
+
{% endblock %}
|
|
43
|
+
|
|
44
|
+
{% block beforeContent %}
|
|
45
|
+
{% if backLink %}
|
|
46
|
+
{{ govukBackLink(backLink) }}
|
|
47
|
+
{% endif %}
|
|
48
|
+
{% endblock %}
|
|
49
|
+
|
|
50
|
+
{% block content %}
|
|
51
|
+
<h1 class="govuk-heading-l">Default page template</h1>
|
|
52
|
+
{% endblock %}
|
|
53
|
+
|
|
54
|
+
{% block bodyEnd %}
|
|
55
|
+
<script type="module" nonce="{{ cspNonce }}" src="{{ getDxtAssetPath("application.js") }}"></script>
|
|
56
|
+
{% endblock %}
|
|
57
|
+
|
|
58
|
+
{% block footer %}
|
|
59
|
+
{% set meta = {
|
|
60
|
+
items: [
|
|
61
|
+
{
|
|
62
|
+
href: 'https://defra.github.io/forms-engine-plugin/',
|
|
63
|
+
text: 'DXT documentation'
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
} if slug %}
|
|
67
|
+
|
|
68
|
+
{% if not context.isForceAccess %}
|
|
69
|
+
{{ govukFooter({ meta: meta }) }}
|
|
70
|
+
{% endif %}
|
|
71
|
+
{% endblock %}
|