@defra/forms-engine-plugin 0.1.26 → 0.1.28

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 (95) hide show
  1. package/.public/assets/images/govuk-crest.svg +1 -1
  2. package/.public/assets/rebrand/images/favicon.ico +0 -0
  3. package/.public/assets/rebrand/images/favicon.svg +1 -0
  4. package/.public/assets/rebrand/images/govuk-crest.svg +1 -0
  5. package/.public/assets/rebrand/images/govuk-icon-180.png +0 -0
  6. package/.public/assets/rebrand/images/govuk-icon-192.png +0 -0
  7. package/.public/assets/rebrand/images/govuk-icon-512.png +0 -0
  8. package/.public/assets/rebrand/images/govuk-icon-mask.svg +1 -0
  9. package/.public/assets/rebrand/images/govuk-opengraph-image.png +0 -0
  10. package/.public/assets/rebrand/manifest.json +39 -0
  11. package/.public/assets-manifest.json +9 -0
  12. package/.public/javascripts/application.min.js +1 -1
  13. package/.public/javascripts/application.min.js.LICENSE.txt +5 -1
  14. package/.public/javascripts/application.min.js.map +1 -1
  15. package/.public/javascripts/shared.min.js +1 -1
  16. package/.public/javascripts/shared.min.js.LICENSE.txt +5 -1
  17. package/.public/javascripts/shared.min.js.map +1 -1
  18. package/.public/stylesheets/application.min.css +2 -2
  19. package/.public/stylesheets/application.min.css.map +1 -1
  20. package/.server/index.js +6 -2
  21. package/.server/index.js.map +1 -1
  22. package/.server/server/common/helpers/logging/request-tracing.js +1 -1
  23. package/.server/server/common/helpers/logging/request-tracing.js.map +1 -1
  24. package/.server/server/common/helpers/redis-client.js +5 -3
  25. package/.server/server/common/helpers/redis-client.js.map +1 -1
  26. package/.server/server/constants.d.ts +0 -1
  27. package/.server/server/constants.js +0 -1
  28. package/.server/server/constants.js.map +1 -1
  29. package/.server/server/index.js +3 -1
  30. package/.server/server/index.js.map +1 -1
  31. package/.server/server/plugins/engine/components/DatePartsField.d.ts +1 -6
  32. package/.server/server/plugins/engine/components/DatePartsField.js +2 -1
  33. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
  34. package/.server/server/plugins/engine/components/MonthYearField.d.ts +1 -5
  35. package/.server/server/plugins/engine/components/MonthYearField.js +3 -2
  36. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
  37. package/.server/server/plugins/engine/components/YesNoField.js +2 -1
  38. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  39. package/.server/server/plugins/engine/components/types.d.ts +9 -0
  40. package/.server/server/plugins/engine/components/types.js.map +1 -1
  41. package/.server/server/plugins/engine/date-helper.d.ts +12 -0
  42. package/.server/server/plugins/engine/date-helper.js +21 -0
  43. package/.server/server/plugins/engine/date-helper.js.map +1 -0
  44. package/.server/server/plugins/engine/helpers.js +4 -3
  45. package/.server/server/plugins/engine/helpers.js.map +1 -1
  46. package/.server/server/plugins/engine/index.js +3 -2
  47. package/.server/server/plugins/engine/index.js.map +1 -1
  48. package/.server/server/plugins/engine/models/FormModel.d.ts +13 -6
  49. package/.server/server/plugins/engine/models/FormModel.js +51 -18
  50. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  51. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  52. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +3 -2
  53. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  54. package/.server/server/plugins/engine/plugin.js +6 -5
  55. package/.server/server/plugins/engine/plugin.js.map +1 -1
  56. package/.server/server/plugins/engine/services/notifyService.js +3 -1
  57. package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
  58. package/.server/server/plugins/engine/views/components/tag-env/template.njk +5 -24
  59. package/.server/server/plugins/engine/views/components/tag-env/template.test.js +19 -53
  60. package/.server/server/plugins/engine/views/components/tag-env/template.test.js.map +1 -1
  61. package/.server/server/plugins/errorPages.js +1 -1
  62. package/.server/server/plugins/errorPages.js.map +1 -1
  63. package/.server/server/plugins/nunjucks/context.js +1 -1
  64. package/.server/server/plugins/nunjucks/context.js.map +1 -1
  65. package/.server/server/plugins/nunjucks/environment.d.ts +1 -0
  66. package/.server/server/plugins/nunjucks/environment.js +4 -0
  67. package/.server/server/plugins/nunjucks/environment.js.map +1 -1
  68. package/package.json +18 -18
  69. package/src/index.ts +7 -2
  70. package/src/server/common/helpers/logging/request-tracing.js +1 -1
  71. package/src/server/common/helpers/redis-client.js +5 -3
  72. package/src/server/constants.js +0 -1
  73. package/src/server/index.ts +5 -2
  74. package/src/server/plugins/engine/components/DatePartsField.test.ts +17 -0
  75. package/src/server/plugins/engine/components/DatePartsField.ts +7 -8
  76. package/src/server/plugins/engine/components/MonthYearField.test.ts +15 -0
  77. package/src/server/plugins/engine/components/MonthYearField.ts +12 -8
  78. package/src/server/plugins/engine/components/YesNoField.ts +16 -2
  79. package/src/server/plugins/engine/components/types.ts +11 -0
  80. package/src/server/plugins/engine/date-helper.test.ts +47 -0
  81. package/src/server/plugins/engine/date-helper.ts +32 -0
  82. package/src/server/plugins/engine/helpers.ts +9 -2
  83. package/src/server/plugins/engine/index.ts +4 -2
  84. package/src/server/plugins/engine/models/FormModel.test.ts +163 -1
  85. package/src/server/plugins/engine/models/FormModel.ts +90 -23
  86. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +4 -2
  87. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +2 -1
  88. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +6 -2
  89. package/src/server/plugins/engine/plugin.ts +11 -7
  90. package/src/server/plugins/engine/services/notifyService.ts +6 -2
  91. package/src/server/plugins/engine/views/components/tag-env/template.njk +5 -24
  92. package/src/server/plugins/engine/views/components/tag-env/template.test.js +18 -56
  93. package/src/server/plugins/errorPages.ts +5 -1
  94. package/src/server/plugins/nunjucks/context.js +3 -1
  95. package/src/server/plugins/nunjucks/environment.js +6 -0
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","config","createLogger","checkFormStatus","encodeUrl","safeGenerateCrumb","logger","webpackManifest","context","request","params","response","isPreview","isPreviewMode","state","formState","isResponseOK","isBoom","statusCode","OK","pluginStorage","server","plugins","consumerViewContext","Error","baseLayoutPath","viewContext","ctx","crumb","currentPath","path","url","search","previewMode","undefined","slug","devtoolContext","_request","manifestPath","get","JSON","parse","error","cdpEnvironment","designerUrl","feedbackLink","phaseTag","serviceName","serviceVersion","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 { config } from '~/src/config/index.js'\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n checkFormStatus,\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 async function context(request) {\n const { params, response } = request ?? {}\n\n const { isPreview: isPreviewMode, state: formState } = checkFormStatus(params)\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\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 (typeof pluginStorage.viewContext === 'function') {\n consumerViewContext = await 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 crumb: safeGenerateCrumb(request),\n currentPath: `${request.path}${request.url.search}`,\n previewMode: isPreviewMode ? formState : 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 * @param {FormRequest | FormRequestPayload | null} _request\n * @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}\n */\nexport function devtoolContext(_request) {\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 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 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
+ {"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","config","createLogger","checkFormStatus","encodeUrl","safeGenerateCrumb","logger","webpackManifest","context","request","params","response","isPreview","isPreviewMode","state","formState","isResponseOK","isBoom","statusCode","OK","pluginStorage","server","plugins","consumerViewContext","Error","baseLayoutPath","viewContext","ctx","crumb","currentPath","path","url","search","previewMode","undefined","slug","devtoolContext","_request","manifestPath","get","JSON","parse","info","cdpEnvironment","designerUrl","feedbackLink","phaseTag","serviceName","serviceVersion","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 { config } from '~/src/config/index.js'\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n checkFormStatus,\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 async function context(request) {\n const { params, response } = request ?? {}\n\n const { isPreview: isPreviewMode, state: formState } = checkFormStatus(params)\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\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 (typeof pluginStorage.viewContext === 'function') {\n consumerViewContext = await 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 crumb: safeGenerateCrumb(request),\n currentPath: `${request.path}${request.url.search}`,\n previewMode: isPreviewMode ? formState : 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 * @param {FormRequest | FormRequestPayload | null} _request\n * @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}\n */\nexport function devtoolContext(_request) {\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.info(\n `[webpackManifestMissing] Webpack ${basename(manifestPath)} not found - running without asset manifest`\n )\n }\n }\n\n return {\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 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":[]}
@@ -13,6 +13,7 @@ export function checkComponentTemplates(this: NunjucksContext, component: Compon
13
13
  * @param {string} template
14
14
  */
15
15
  export function evaluate(this: NunjucksContext, template: string): string;
16
+ export function govukRebrand(): boolean;
16
17
  export const paths: string[];
17
18
  export const environment: nunjucks.Environment;
18
19
  import type { FormSubmissionError } from '~/src/server/plugins/engine/types.js';
@@ -86,6 +86,10 @@ export function evaluate(template) {
86
86
  return context ? evaluateTemplate(template, context) : template;
87
87
  }
88
88
  environment.addGlobal('evaluate', evaluate);
89
+ export function govukRebrand() {
90
+ return true;
91
+ }
92
+ environment.addGlobal('govukRebrand', govukRebrand());
89
93
 
90
94
  /**
91
95
  * @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'
@@ -1 +1 @@
1
- {"version":3,"file":"environment.js","names":["dirname","join","ComponentType","nunjucks","resolvePkg","config","evaluateTemplate","filters","govukFrontendPath","sync","paths","get","environment","configure","trimBlocks","lstripBlocks","watch","noCache","name","nunjucksFilter","Object","entries","addFilter","checkErrorTemplates","errors","context","ctx","forEach","error","text","addGlobal","checkComponentTemplates","component","isFormComponent","model","fieldset","legend","label","errorMessage","message","type","Html","content","evaluate","template"],"sources":["../../../../src/server/plugins/nunjucks/environment.js"],"sourcesContent":["import { dirname, join } from 'node:path'\n\nimport { ComponentType } from '@defra/forms-model'\nimport nunjucks from 'nunjucks'\nimport resolvePkg from 'resolve'\n\nimport { config } from '~/src/config/index.js'\nimport { evaluateTemplate } from '~/src/server/plugins/engine/helpers.js'\nimport * as filters from '~/src/server/plugins/nunjucks/filters/index.js'\n\nconst govukFrontendPath = dirname(\n resolvePkg.sync('govuk-frontend/package.json')\n)\n\nexport const paths = [\n join(config.get('appDir'), 'plugins/engine/views'),\n join(config.get('appDir'), 'views')\n]\n\nexport const environment = nunjucks.configure(\n [...paths, join(govukFrontendPath, 'dist')],\n {\n trimBlocks: true,\n lstripBlocks: true,\n watch: config.get('isDevelopment'),\n noCache: config.get('isDevelopment')\n }\n)\n\nfor (const [name, nunjucksFilter] of Object.entries(filters)) {\n environment.addFilter(name, nunjucksFilter)\n}\n\n/**\n * @this {NunjucksContext}\n * @param {FormSubmissionError[]} errors\n */\nexport function checkErrorTemplates(errors) {\n const { context } = this.ctx\n\n if (!context) {\n return errors\n }\n\n errors.forEach((error) => {\n error.text = evaluateTemplate(error.text, context)\n })\n\n return errors\n}\n\nenvironment.addGlobal('checkErrorTemplates', checkErrorTemplates)\n\n/**\n * @this {NunjucksContext}\n * @param {ComponentViewModel} component\n */\nexport function checkComponentTemplates(component) {\n const { context } = this.ctx\n\n if (!context) {\n return component\n }\n\n if (component.isFormComponent) {\n // Evaluate label/legend text\n if (component.model.fieldset?.legend?.text) {\n const legend = component.model.fieldset.legend\n\n legend.text = evaluateTemplate(legend.text, context)\n } else if (component.model.label?.text) {\n const label = component.model.label\n\n label.text = evaluateTemplate(label.text, context)\n } else {\n // No template evaluation needed for other component types\n }\n\n // Evaluate error message\n if (component.model.errorMessage?.text) {\n const message = component.model.errorMessage\n\n message.text = evaluateTemplate(message.text, context)\n }\n } else if (component.type === ComponentType.Html) {\n const content = component.model.content\n\n if (typeof content === 'string') {\n component.model.content = evaluateTemplate(content, context)\n }\n } else {\n // No template evaluation needed for other component types\n }\n\n return component\n}\n\nenvironment.addGlobal('checkComponentTemplates', checkComponentTemplates)\n\n/**\n * @this {NunjucksContext}\n * @param {string} template\n */\nexport function evaluate(template) {\n const { context } = this.ctx\n\n return context ? evaluateTemplate(template, context) : template\n}\n\nenvironment.addGlobal('evaluate', evaluate)\n\n/**\n * @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'\n * @import { FormSubmissionError } from '~/src/server/plugins/engine/types.js'\n * @import { ComponentViewModel } from '~/src/server/plugins/engine/components/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"environment.js","names":["dirname","join","ComponentType","nunjucks","resolvePkg","config","evaluateTemplate","filters","govukFrontendPath","sync","paths","get","environment","configure","trimBlocks","lstripBlocks","watch","noCache","name","nunjucksFilter","Object","entries","addFilter","checkErrorTemplates","errors","context","ctx","forEach","error","text","addGlobal","checkComponentTemplates","component","isFormComponent","model","fieldset","legend","label","errorMessage","message","type","Html","content","evaluate","template","govukRebrand"],"sources":["../../../../src/server/plugins/nunjucks/environment.js"],"sourcesContent":["import { dirname, join } from 'node:path'\n\nimport { ComponentType } from '@defra/forms-model'\nimport nunjucks from 'nunjucks'\nimport resolvePkg from 'resolve'\n\nimport { config } from '~/src/config/index.js'\nimport { evaluateTemplate } from '~/src/server/plugins/engine/helpers.js'\nimport * as filters from '~/src/server/plugins/nunjucks/filters/index.js'\n\nconst govukFrontendPath = dirname(\n resolvePkg.sync('govuk-frontend/package.json')\n)\n\nexport const paths = [\n join(config.get('appDir'), 'plugins/engine/views'),\n join(config.get('appDir'), 'views')\n]\n\nexport const environment = nunjucks.configure(\n [...paths, join(govukFrontendPath, 'dist')],\n {\n trimBlocks: true,\n lstripBlocks: true,\n watch: config.get('isDevelopment'),\n noCache: config.get('isDevelopment')\n }\n)\n\nfor (const [name, nunjucksFilter] of Object.entries(filters)) {\n environment.addFilter(name, nunjucksFilter)\n}\n\n/**\n * @this {NunjucksContext}\n * @param {FormSubmissionError[]} errors\n */\nexport function checkErrorTemplates(errors) {\n const { context } = this.ctx\n\n if (!context) {\n return errors\n }\n\n errors.forEach((error) => {\n error.text = evaluateTemplate(error.text, context)\n })\n\n return errors\n}\n\nenvironment.addGlobal('checkErrorTemplates', checkErrorTemplates)\n\n/**\n * @this {NunjucksContext}\n * @param {ComponentViewModel} component\n */\nexport function checkComponentTemplates(component) {\n const { context } = this.ctx\n\n if (!context) {\n return component\n }\n\n if (component.isFormComponent) {\n // Evaluate label/legend text\n if (component.model.fieldset?.legend?.text) {\n const legend = component.model.fieldset.legend\n\n legend.text = evaluateTemplate(legend.text, context)\n } else if (component.model.label?.text) {\n const label = component.model.label\n\n label.text = evaluateTemplate(label.text, context)\n } else {\n // No template evaluation needed for other component types\n }\n\n // Evaluate error message\n if (component.model.errorMessage?.text) {\n const message = component.model.errorMessage\n\n message.text = evaluateTemplate(message.text, context)\n }\n } else if (component.type === ComponentType.Html) {\n const content = component.model.content\n\n if (typeof content === 'string') {\n component.model.content = evaluateTemplate(content, context)\n }\n } else {\n // No template evaluation needed for other component types\n }\n\n return component\n}\n\nenvironment.addGlobal('checkComponentTemplates', checkComponentTemplates)\n\n/**\n * @this {NunjucksContext}\n * @param {string} template\n */\nexport function evaluate(template) {\n const { context } = this.ctx\n\n return context ? evaluateTemplate(template, context) : template\n}\n\nenvironment.addGlobal('evaluate', evaluate)\n\nexport function govukRebrand() {\n return true\n}\n\nenvironment.addGlobal('govukRebrand', govukRebrand())\n\n/**\n * @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'\n * @import { FormSubmissionError } from '~/src/server/plugins/engine/types.js'\n * @import { ComponentViewModel } from '~/src/server/plugins/engine/components/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -63,7 +63,7 @@
63
63
  },
64
64
  "license": "SEE LICENSE IN LICENSE",
65
65
  "dependencies": {
66
- "@defra/forms-model": "^3.0.441",
66
+ "@defra/forms-model": "^3.0.497",
67
67
  "@defra/hapi-tracing": "^1.0.0",
68
68
  "@elastic/ecs-pino-format": "^1.5.0",
69
69
  "@hapi/boom": "^10.0.1",
@@ -87,22 +87,22 @@
87
87
  "btoa": "^1.2.1",
88
88
  "convict": "^6.2.4",
89
89
  "date-fns": "^4.1.0",
90
- "dotenv": "^16.4.7",
90
+ "dotenv": "^16.5.0",
91
91
  "expr-eval": "^2.0.2",
92
- "govuk-frontend": "^5.7.1",
92
+ "govuk-frontend": "^5.10.2",
93
93
  "hapi-pino": "^12.1.0",
94
94
  "hapi-pulse": "^3.0.1",
95
95
  "highlight.js": "^11.11.1",
96
96
  "http-status-codes": "^2.3.0",
97
- "humanize-duration": "^3.32.1",
98
- "ioredis": "^5.6.0",
97
+ "humanize-duration": "^3.33.0",
98
+ "ioredis": "^5.6.1",
99
99
  "joi": "^17.13.3",
100
- "liquidjs": "^10.21.0",
100
+ "liquidjs": "^10.21.1",
101
101
  "lodash": "^4.17.21",
102
102
  "marked": "^15.0.7",
103
103
  "nunjucks": "^3.2.3",
104
104
  "outdent": "^0.8.0",
105
- "pino": "^9.6.0",
105
+ "pino": "^9.7.0",
106
106
  "pino-pretty": "^13.0.0",
107
107
  "proxy-agent": "^6.5.0",
108
108
  "resolve": "^1.22.10",
@@ -120,18 +120,18 @@
120
120
  "@types/btoa": "^1.2.5",
121
121
  "@types/convict": "^6.1.6",
122
122
  "@types/eslint": "^9.6.1",
123
- "@types/govuk-frontend": "^5.7.0",
123
+ "@types/govuk-frontend": "^5.9.0",
124
124
  "@types/hapi": "^18.0.14",
125
125
  "@types/hapi__catbox-memory": "^4.1.8",
126
126
  "@types/hapi__cookie": "^12.0.5",
127
127
  "@types/hapi__crumb": "^7.3.7",
128
128
  "@types/hapi__yar": "^10.1.6",
129
129
  "@types/hoek": "^4.1.7",
130
- "@types/jest": "^29.5.14",
130
+ "@types/jest": "^30.0.0",
131
131
  "@types/jsdom": "^21.1.7",
132
- "@types/lodash": "^4.17.13",
133
- "@types/mysql": "^2.15.26",
134
- "@types/node": "^22.10.2",
132
+ "@types/lodash": "^4.17.17",
133
+ "@types/mysql": "^2.15.27",
134
+ "@types/node": "^24.0.2",
135
135
  "@types/nunjucks": "^3.2.6",
136
136
  "@types/resolve": "^1.20.6",
137
137
  "@types/url-parse": "^1.4.11",
@@ -145,7 +145,7 @@
145
145
  "babel-plugin-replace-import-extension": "^1.1.4",
146
146
  "babel-plugin-transform-import-meta": "^2.3.2",
147
147
  "clean-webpack-plugin": "^4.0.0",
148
- "concurrently": "^9.1.2",
148
+ "concurrently": "^9.2.0",
149
149
  "cookie": "^1.0.2",
150
150
  "copy-webpack-plugin": "^12.0.2",
151
151
  "core-js": "^3.39.0",
@@ -161,11 +161,11 @@
161
161
  "eslint-plugin-jsdoc": "^50.6.1",
162
162
  "eslint-plugin-n": "^16.6.2",
163
163
  "eslint-plugin-promise": "^6.6.0",
164
- "global-jsdom": "^25.0.0",
164
+ "global-jsdom": "^26.0.0",
165
165
  "husky": "^9.1.7",
166
- "jest": "^29.7.0",
166
+ "jest": "^30.0.2",
167
167
  "jest-extended": "^4.0.2",
168
- "jsdom": "^25.0.1",
168
+ "jsdom": "^26.1.0",
169
169
  "lint-staged": "^15.3.0",
170
170
  "postcss": "^8.4.49",
171
171
  "postcss-load-config": "^6.0.1",
@@ -178,7 +178,7 @@
178
178
  "stylelint": "^16.12.0",
179
179
  "stylelint-config-gds": "^2.0.0",
180
180
  "terser-webpack-plugin": "^5.3.11",
181
- "tsx": "^4.19.3",
181
+ "tsx": "^4.20.3",
182
182
  "typescript": "^5.7.2",
183
183
  "webpack": "^5.97.1",
184
184
  "webpack-assets-manifest": "^6.0.2",
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { getErrorMessage } from '@defra/forms-model'
2
+
1
3
  import { config } from '~/src/config/index.js'
2
4
  import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
3
5
  import { createServer } from '~/src/server/index.js'
@@ -5,8 +7,9 @@ import { createServer } from '~/src/server/index.js'
5
7
  const logger = createLogger()
6
8
 
7
9
  process.on('unhandledRejection', (error) => {
10
+ const err = getErrorMessage(error)
8
11
  logger.info('Unhandled rejection')
9
- logger.error(error)
12
+ logger.error(err, `[unhandledRejection] Unhandled promise rejection: ${err}`)
10
13
  throw error
11
14
  })
12
15
 
@@ -26,6 +29,8 @@ async function startServer() {
26
29
  }
27
30
 
28
31
  startServer().catch((error: unknown) => {
32
+ const err = getErrorMessage(error)
29
33
  logger.info('Server failed to start :(')
30
- logger.error(error)
34
+ logger.error(err, `[serverStartup] Server failed to start: ${err}`)
35
+ throw error
31
36
  })
@@ -6,5 +6,5 @@ import { config } from '~/src/config/index.js'
6
6
 
7
7
  export const requestTracing = {
8
8
  plugin: tracing,
9
- options: { tracingHeader: config.get('tracing').header }
9
+ options: { tracingHeader: config.get('tracing.header') }
10
10
  }
@@ -1,3 +1,4 @@
1
+ import { getErrorMessage } from '@defra/forms-model'
1
2
  import { Cluster, Redis } from 'ioredis'
2
3
 
3
4
  import { config } from '~/src/config/index.js'
@@ -53,17 +54,18 @@ export function buildRedisClient() {
53
54
  }
54
55
 
55
56
  redisClient.on('connect', () => {
56
- logger.info('Connected to Redis server')
57
+ logger.info('[redisConnected] Connected to Redis server')
57
58
  })
58
59
 
59
60
  redisClient.on('close', () => {
60
61
  logger.warn(
61
- 'Redis connection closed attempting reconnect with default behavior'
62
+ '[redisDisconnected] Redis connection closed attempting reconnect with default behavior'
62
63
  )
63
64
  })
64
65
 
65
66
  redisClient.on('error', (error) => {
66
- logger.error(error, `Redis connection error ${error}.`)
67
+ const err = getErrorMessage(error)
68
+ logger.error(err, `[redisConnectionError] Redis connection error - ${err}`)
67
69
  })
68
70
 
69
71
  return redisClient
@@ -1,3 +1,2 @@
1
1
  export const PREVIEW_PATH_PREFIX = '/preview'
2
- export const ERROR_PREVIEW_PATH_PREFIX = '/error-preview'
3
2
  export const FORM_PREFIX = ''
@@ -89,7 +89,6 @@ export async function createServer(routeConfig?: RouteConfig) {
89
89
  await server.register(inert)
90
90
  await server.register(Scooter)
91
91
  await server.register(pluginCrumb)
92
-
93
92
  await server.register(pluginEngine)
94
93
 
95
94
  server.ext('onPreResponse', (request: Request, h: ResponseToolkit) => {
@@ -126,7 +125,11 @@ export async function createServer(routeConfig?: RouteConfig) {
126
125
  })
127
126
 
128
127
  await server.register(pluginErrorPages)
129
- await server.register(blipp)
128
+
129
+ if (config.get('cdpEnvironment') === 'local') {
130
+ await server.register(blipp)
131
+ }
132
+
130
133
  await server.register(requestTracing)
131
134
 
132
135
  return server
@@ -242,9 +242,26 @@ describe('DatePartsField', () => {
242
242
  })
243
243
  )
244
244
 
245
+ // Check a non-4-digit year shows as an error
246
+ const state4 = getFormState({
247
+ day: 1,
248
+ month: 2,
249
+ year: 20
250
+ })
251
+ const result4 = field.getContextValueFromState(state4)
252
+
253
+ const state5 = getFormState({
254
+ day: 1,
255
+ month: 2,
256
+ year: 2000
257
+ })
258
+ const result5 = field.getContextValueFromState(state5)
259
+
245
260
  expect(result1.errors).toBeTruthy()
246
261
  expect(result2.errors).toBeTruthy()
247
262
  expect(result3.errors).toBeTruthy()
263
+ expect(result4).toBeNull()
264
+ expect(result5).toBe('2000-02-01')
248
265
  })
249
266
  })
250
267
 
@@ -9,7 +9,11 @@ import {
9
9
  isFormValue
10
10
  } from '~/src/server/plugins/engine/components/FormComponent.js'
11
11
  import { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'
12
- import { type DateInputItem } from '~/src/server/plugins/engine/components/types.js'
12
+ import {
13
+ type DateInputItem,
14
+ type DatePartsState
15
+ } from '~/src/server/plugins/engine/components/types.js'
16
+ import { parseStrictDate } from '~/src/server/plugins/engine/date-helper.js'
13
17
  import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
14
18
  import {
15
19
  type ErrorMessageTemplateList,
@@ -121,7 +125,8 @@ export class DatePartsField extends FormComponent {
121
125
  if (
122
126
  !value ||
123
127
  !isValid(
124
- parse(
128
+ parseStrictDate(
129
+ value,
125
130
  `${value.year}-${value.month}-${value.day}`,
126
131
  'yyyy-MM-dd',
127
132
  new Date()
@@ -232,12 +237,6 @@ export class DatePartsField extends FormComponent {
232
237
  }
233
238
  }
234
239
 
235
- export interface DatePartsState extends Record<string, number> {
236
- day: number
237
- month: number
238
- year: number
239
- }
240
-
241
240
  export function getValidatorDate(component: DatePartsField) {
242
241
  const validator: CustomValidator = (payload: FormPayload, helpers) => {
243
242
  const { collection, name, options } = component
@@ -222,9 +222,24 @@ describe('MonthYearField', () => {
222
222
  })
223
223
  )
224
224
 
225
+ // Check a non-4-digit year shows as an error
226
+ const state4 = getFormState({
227
+ month: 2,
228
+ year: 20
229
+ })
230
+ const result4 = field.getContextValueFromState(state4)
231
+
232
+ const state5 = getFormState({
233
+ month: 5,
234
+ year: 2000
235
+ })
236
+ const result5 = field.getContextValueFromState(state5)
237
+
225
238
  expect(result1.errors).toBeTruthy()
226
239
  expect(result2.errors).toBeTruthy()
227
240
  expect(result3.errors).toBeTruthy()
241
+ expect(result4).toBeNull()
242
+ expect(result5).toBe('2000-05')
228
243
  })
229
244
  })
230
245
 
@@ -1,5 +1,5 @@
1
1
  import { ComponentType, type MonthYearFieldComponent } from '@defra/forms-model'
2
- import { format, isValid, parse } from 'date-fns'
2
+ import { format, isValid } from 'date-fns'
3
3
  import {
4
4
  type Context,
5
5
  type CustomValidator,
@@ -14,7 +14,11 @@ import {
14
14
  isFormValue
15
15
  } from '~/src/server/plugins/engine/components/FormComponent.js'
16
16
  import { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'
17
- import { type DateInputItem } from '~/src/server/plugins/engine/components/types.js'
17
+ import {
18
+ type DateInputItem,
19
+ type MonthYearState
20
+ } from '~/src/server/plugins/engine/components/types.js'
21
+ import { parseStrictDate } from '~/src/server/plugins/engine/date-helper.js'
18
22
  import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
19
23
  import {
20
24
  type ErrorMessageTemplateList,
@@ -119,7 +123,12 @@ export class MonthYearField extends FormComponent {
119
123
  if (
120
124
  !value ||
121
125
  !isValid(
122
- parse(`${value.year}-${value.month}-01`, 'yyyy-MM-dd', new Date())
126
+ parseStrictDate(
127
+ value,
128
+ `${value.year}-${value.month}-01`,
129
+ 'yyyy-MM-dd',
130
+ new Date()
131
+ )
123
132
  )
124
133
  ) {
125
134
  return null
@@ -223,11 +232,6 @@ export class MonthYearField extends FormComponent {
223
232
  }
224
233
  }
225
234
 
226
- export interface MonthYearState extends Record<string, number> {
227
- month: number
228
- year: number
229
- }
230
-
231
235
  export function getValidatorMonthYear(component: MonthYearField) {
232
236
  const validator: CustomValidator = (payload: FormPayload, helpers) => {
233
237
  const { collection, name, options } = component
@@ -1,4 +1,9 @@
1
- import { type YesNoFieldComponent } from '@defra/forms-model'
1
+ import {
2
+ SchemaVersion,
3
+ yesNoListId,
4
+ yesNoListName,
5
+ type YesNoFieldComponent
6
+ } from '@defra/forms-model'
2
7
 
3
8
  import { SelectionControlField } from '~/src/server/plugins/engine/components/SelectionControlField.js'
4
9
  import { addClassOptionIfNone } from '~/src/server/plugins/engine/components/helpers.js'
@@ -17,7 +22,16 @@ export class YesNoField extends SelectionControlField {
17
22
  def: YesNoFieldComponent,
18
23
  props: ConstructorParameters<typeof SelectionControlField>[1]
19
24
  ) {
20
- super({ ...def, list: '__yesNo' }, props)
25
+ super(
26
+ {
27
+ ...def,
28
+ list:
29
+ props.model.schemaVersion === SchemaVersion.V1
30
+ ? yesNoListName
31
+ : yesNoListId
32
+ },
33
+ props
34
+ )
21
35
 
22
36
  const { options } = def
23
37
  let { formSchema } = this
@@ -115,3 +115,14 @@ export interface ComponentViewModel {
115
115
  isFormComponent: boolean
116
116
  model: ViewModel
117
117
  }
118
+
119
+ export interface DatePartsState extends Record<string, number> {
120
+ day: number
121
+ month: number
122
+ year: number
123
+ }
124
+
125
+ export interface MonthYearState extends Record<string, number> {
126
+ month: number
127
+ year: number
128
+ }
@@ -0,0 +1,47 @@
1
+ import { startOfToday } from 'date-fns'
2
+
3
+ import {
4
+ parseStrictDate,
5
+ todayAsDateOnly
6
+ } from '~/src/server/plugins/engine/date-helper.js'
7
+
8
+ describe('todayAsDateOnly()', () => {
9
+ test('should return today with no time element', () => {
10
+ expect(todayAsDateOnly()).toEqual(startOfToday())
11
+ })
12
+ })
13
+
14
+ describe('parseStrictDate()', () => {
15
+ test('should parse valid date', () => {
16
+ const dateObj = {
17
+ year: 2025,
18
+ month: 5,
19
+ day: 21
20
+ }
21
+ expect(
22
+ parseStrictDate(dateObj, '2025-05-21', 'yyyy-MM-dd', new Date())
23
+ ).toEqual(new Date(2025, 4, 21))
24
+ })
25
+
26
+ test('should fail to parse invalid date with 4-digit year', () => {
27
+ const dateObj = {
28
+ year: 2025,
29
+ month: 15,
30
+ day: 21
31
+ }
32
+ expect(
33
+ parseStrictDate(dateObj, '2025-15-21', 'yyyy-MM-dd', new Date())
34
+ ).toBeNaN()
35
+ })
36
+
37
+ test('should fail to parse valid date that has non-4-digit year', () => {
38
+ const dateObj = {
39
+ year: 25,
40
+ month: 5,
41
+ day: 21
42
+ }
43
+ expect(
44
+ parseStrictDate(dateObj, '25-15-21', 'yyyy-MM-dd', new Date())
45
+ ).toBeNaN()
46
+ })
47
+ })
@@ -0,0 +1,32 @@
1
+ import { parse, startOfToday } from 'date-fns'
2
+
3
+ import {
4
+ type DatePartsState,
5
+ type MonthYearState
6
+ } from '~/src/server/plugins/engine/components/types.js'
7
+
8
+ /**
9
+ * This function is just a wrapper for startOfToday() but, since it's in a separate file, allows
10
+ * the function to be easily mocked for unit testing.
11
+ * @returns {Date}
12
+ */
13
+ export function todayAsDateOnly() {
14
+ return startOfToday()
15
+ }
16
+
17
+ /**
18
+ * Wrapper for date-fns parse() method to ensure the year is 4-digits. It seems parse() allows a non-4-digit year
19
+ * despite the format mask enforcing it.
20
+ */
21
+ export function parseStrictDate(
22
+ dateObj: DatePartsState | MonthYearState,
23
+ dateStr: string,
24
+ formatStr: string,
25
+ referenceDate: Date
26
+ ) {
27
+ if (!dateObj.year || dateObj.year < 1000 || dateObj.year > 9999) {
28
+ return NaN
29
+ }
30
+
31
+ return parse(dateStr, formatStr, referenceDate)
32
+ }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  ControllerPath,
3
3
  Engine,
4
+ getErrorMessage,
4
5
  hasComponents,
5
6
  isFormType,
6
7
  type ComponentDef,
@@ -148,7 +149,11 @@ export function encodeUrl(link?: string) {
148
149
  try {
149
150
  return new URL(link).toString() // escape the search params without breaking the ? and & reserved characters in rfc2368
150
151
  } catch (err) {
151
- logger.error(err, `Failed to encode ${link}`)
152
+ const errMsg = getErrorMessage(err)
153
+ logger.error(
154
+ errMsg,
155
+ `[urlEncodingFailed] Failed to encode URL: ${link} - ${errMsg}`
156
+ )
152
157
  throw err
153
158
  }
154
159
  }
@@ -404,7 +409,9 @@ export function setPageTitles(def: FormDefinition) {
404
409
  if (!page.title) {
405
410
  const formNameMsg = def.name ? ` in form '${def.name}'` : ''
406
411
 
407
- logger.warn(`Page '${page.path}' has no title${formNameMsg}`)
412
+ logger.info(
413
+ `[pageTitleMissing] Page '${page.path}' has no title${formNameMsg}`
414
+ )
408
415
  }
409
416
  }
410
417
  })
@@ -6,7 +6,8 @@ import { type FilterFunction } from '~/src/server/plugins/engine/types.js'
6
6
  import {
7
7
  checkComponentTemplates,
8
8
  checkErrorTemplates,
9
- evaluate
9
+ evaluate,
10
+ govukRebrand
10
11
  } from '~/src/server/plugins/nunjucks/environment.js'
11
12
  import * as filters from '~/src/server/plugins/nunjucks/filters/index.js'
12
13
 
@@ -16,7 +17,8 @@ export { context } from '~/src/server/plugins/nunjucks/context.js'
16
17
  const globals = {
17
18
  checkComponentTemplates,
18
19
  checkErrorTemplates,
19
- evaluate
20
+ evaluate,
21
+ govukRebrand
20
22
  }
21
23
 
22
24
  export const VIEW_PATH = 'src/server/plugins/engine/views'