@defra/forms-engine-plugin 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.server/server/forms/components.json +8 -0
  2. package/.server/server/plugins/engine/components/ComponentBase.js +2 -2
  3. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  4. package/.server/server/plugins/engine/configureEnginePlugin.js +2 -1
  5. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  6. package/.server/server/plugins/engine/index.d.ts +2 -2
  7. package/.server/server/plugins/engine/index.js +5 -3
  8. package/.server/server/plugins/engine/index.js.map +1 -1
  9. package/.server/server/plugins/engine/options.js +5 -1
  10. package/.server/server/plugins/engine/options.js.map +1 -1
  11. package/.server/server/plugins/engine/options.test.js +3 -1
  12. package/.server/server/plugins/engine/options.test.js.map +1 -1
  13. package/.server/server/plugins/engine/types.d.ts +1 -0
  14. package/.server/server/plugins/engine/types.js.map +1 -1
  15. package/.server/server/plugins/engine/vision.js +1 -1
  16. package/.server/server/plugins/engine/vision.js.map +1 -1
  17. package/.server/server/plugins/nunjucks/filters/index.d.ts +0 -1
  18. package/.server/server/plugins/nunjucks/filters/index.js +0 -1
  19. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
  20. package/package.json +2 -2
  21. package/src/server/forms/components.json +8 -0
  22. package/src/server/index.test.ts +73 -0
  23. package/src/server/plugins/engine/components/ComponentBase.ts +2 -2
  24. package/src/server/plugins/engine/configureEnginePlugin.ts +2 -1
  25. package/src/server/plugins/engine/index.ts +9 -4
  26. package/src/server/plugins/engine/options.js +9 -1
  27. package/src/server/plugins/engine/options.test.js +3 -1
  28. package/src/server/plugins/engine/types.ts +1 -0
  29. package/src/server/plugins/engine/vision.ts +1 -1
  30. package/src/server/plugins/nunjucks/filters/index.js +0 -1
@@ -112,6 +112,14 @@
112
112
  "content": "Content",
113
113
  "options": {},
114
114
  "schema": {}
115
+ },
116
+ {
117
+ "type": "Markdown",
118
+ "name": "markdown",
119
+ "title": "Title",
120
+ "content": "### This is a H3 in markdown\n\n[An internal link](http://localhost:3009/fictional-page)\n\n[An external link](https://defra.gov.uk/fictional-page)",
121
+ "options": {},
122
+ "schema": {}
115
123
  }
116
124
  ]
117
125
  }
@@ -1,4 +1,4 @@
1
- import { isConditionalType } from '@defra/forms-model';
1
+ import { isConditionalRevealType } from '@defra/forms-model';
2
2
  import joi from 'joi';
3
3
  export class ComponentBase {
4
4
  page;
@@ -46,7 +46,7 @@ export class ComponentBase {
46
46
  if ('classes' in options) {
47
47
  viewModel.classes = options.classes;
48
48
  }
49
- if ('condition' in options && isConditionalType(type)) {
49
+ if ('condition' in options && isConditionalRevealType(type)) {
50
50
  viewModel.condition = options.condition;
51
51
  }
52
52
  return viewModel;
@@ -1 +1 @@
1
- {"version":3,"file":"ComponentBase.js","names":["isConditionalType","joi","ComponentBase","page","parent","collection","type","name","title","schema","options","isFormComponent","model","formSchema","string","stateSchema","constructor","def","props","viewModel","attributes","autocomplete","classes","condition"],"sources":["../../../../../src/server/plugins/engine/components/ComponentBase.ts"],"sourcesContent":["import { isConditionalType, type ComponentDef } from '@defra/forms-model'\nimport joi, {\n type ArraySchema,\n type BooleanSchema,\n type DateSchema,\n type NumberSchema,\n type ObjectSchema,\n type StringSchema\n} from 'joi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport { type ViewModel } from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\n\nexport class ComponentBase {\n page?: PageControllerClass\n parent: Component | undefined\n collection: ComponentCollection | undefined\n\n type: ComponentDef['type']\n name: ComponentDef['name']\n title: ComponentDef['title']\n schema?: Extract<ComponentDef, { schema: object }>['schema']\n options?: Extract<ComponentDef, { options: object }>['options']\n\n isFormComponent = false\n model: FormModel\n\n /** joi schemas based on a component defined in the form JSON. This validates a user's answer and is generated from {@link ComponentDef} */\n formSchema: ComponentSchema = joi.string()\n stateSchema: ComponentSchema = joi.string()\n\n constructor(\n def: ComponentDef,\n props: {\n page?: PageControllerClass\n parent?: Component\n model: FormModel\n }\n ) {\n this.type = def.type\n this.name = def.name\n this.title = def.title\n\n if ('schema' in def) {\n this.schema = def.schema\n }\n\n if ('options' in def) {\n this.options = def.options\n }\n\n this.page = props.page\n this.parent = props.parent\n this.model = props.model\n }\n\n get viewModel() {\n const { options, type } = this\n\n const viewModel: ViewModel = {\n attributes: {}\n }\n\n if (!options) {\n return viewModel\n }\n\n if ('autocomplete' in options) {\n viewModel.attributes.autocomplete = options.autocomplete\n }\n\n if ('classes' in options) {\n viewModel.classes = options.classes\n }\n\n if ('condition' in options && isConditionalType(type)) {\n viewModel.condition = options.condition\n }\n\n return viewModel\n }\n}\n\nexport type ComponentSchema =\n | ArraySchema<string>\n | ArraySchema<number>\n | ArraySchema<boolean>\n | ArraySchema<object>\n | BooleanSchema<string>\n | DateSchema\n | NumberSchema<string>\n | NumberSchema\n | ObjectSchema\n | StringSchema\n"],"mappings":"AAAA,SAASA,iBAAiB,QAA2B,oBAAoB;AACzE,OAAOC,GAAG,MAOH,KAAK;AAQZ,OAAO,MAAMC,aAAa,CAAC;EACzBC,IAAI;EACJC,MAAM;EACNC,UAAU;EAEVC,IAAI;EACJC,IAAI;EACJC,KAAK;EACLC,MAAM;EACNC,OAAO;EAEPC,eAAe,GAAG,KAAK;EACvBC,KAAK;;EAEL;EACAC,UAAU,GAAoBZ,GAAG,CAACa,MAAM,CAAC,CAAC;EAC1CC,WAAW,GAAoBd,GAAG,CAACa,MAAM,CAAC,CAAC;EAE3CE,WAAWA,CACTC,GAAiB,EACjBC,KAIC,EACD;IACA,IAAI,CAACZ,IAAI,GAAGW,GAAG,CAACX,IAAI;IACpB,IAAI,CAACC,IAAI,GAAGU,GAAG,CAACV,IAAI;IACpB,IAAI,CAACC,KAAK,GAAGS,GAAG,CAACT,KAAK;IAEtB,IAAI,QAAQ,IAAIS,GAAG,EAAE;MACnB,IAAI,CAACR,MAAM,GAAGQ,GAAG,CAACR,MAAM;IAC1B;IAEA,IAAI,SAAS,IAAIQ,GAAG,EAAE;MACpB,IAAI,CAACP,OAAO,GAAGO,GAAG,CAACP,OAAO;IAC5B;IAEA,IAAI,CAACP,IAAI,GAAGe,KAAK,CAACf,IAAI;IACtB,IAAI,CAACC,MAAM,GAAGc,KAAK,CAACd,MAAM;IAC1B,IAAI,CAACQ,KAAK,GAAGM,KAAK,CAACN,KAAK;EAC1B;EAEA,IAAIO,SAASA,CAAA,EAAG;IACd,MAAM;MAAET,OAAO;MAAEJ;IAAK,CAAC,GAAG,IAAI;IAE9B,MAAMa,SAAoB,GAAG;MAC3BC,UAAU,EAAE,CAAC;IACf,CAAC;IAED,IAAI,CAACV,OAAO,EAAE;MACZ,OAAOS,SAAS;IAClB;IAEA,IAAI,cAAc,IAAIT,OAAO,EAAE;MAC7BS,SAAS,CAACC,UAAU,CAACC,YAAY,GAAGX,OAAO,CAACW,YAAY;IAC1D;IAEA,IAAI,SAAS,IAAIX,OAAO,EAAE;MACxBS,SAAS,CAACG,OAAO,GAAGZ,OAAO,CAACY,OAAO;IACrC;IAEA,IAAI,WAAW,IAAIZ,OAAO,IAAIV,iBAAiB,CAACM,IAAI,CAAC,EAAE;MACrDa,SAAS,CAACI,SAAS,GAAGb,OAAO,CAACa,SAAS;IACzC;IAEA,OAAOJ,SAAS;EAClB;AACF","ignoreList":[]}
1
+ {"version":3,"file":"ComponentBase.js","names":["isConditionalRevealType","joi","ComponentBase","page","parent","collection","type","name","title","schema","options","isFormComponent","model","formSchema","string","stateSchema","constructor","def","props","viewModel","attributes","autocomplete","classes","condition"],"sources":["../../../../../src/server/plugins/engine/components/ComponentBase.ts"],"sourcesContent":["import { isConditionalRevealType, type ComponentDef } from '@defra/forms-model'\nimport joi, {\n type ArraySchema,\n type BooleanSchema,\n type DateSchema,\n type NumberSchema,\n type ObjectSchema,\n type StringSchema\n} from 'joi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport { type ViewModel } from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\n\nexport class ComponentBase {\n page?: PageControllerClass\n parent: Component | undefined\n collection: ComponentCollection | undefined\n\n type: ComponentDef['type']\n name: ComponentDef['name']\n title: ComponentDef['title']\n schema?: Extract<ComponentDef, { schema: object }>['schema']\n options?: Extract<ComponentDef, { options: object }>['options']\n\n isFormComponent = false\n model: FormModel\n\n /** joi schemas based on a component defined in the form JSON. This validates a user's answer and is generated from {@link ComponentDef} */\n formSchema: ComponentSchema = joi.string()\n stateSchema: ComponentSchema = joi.string()\n\n constructor(\n def: ComponentDef,\n props: {\n page?: PageControllerClass\n parent?: Component\n model: FormModel\n }\n ) {\n this.type = def.type\n this.name = def.name\n this.title = def.title\n\n if ('schema' in def) {\n this.schema = def.schema\n }\n\n if ('options' in def) {\n this.options = def.options\n }\n\n this.page = props.page\n this.parent = props.parent\n this.model = props.model\n }\n\n get viewModel() {\n const { options, type } = this\n\n const viewModel: ViewModel = {\n attributes: {}\n }\n\n if (!options) {\n return viewModel\n }\n\n if ('autocomplete' in options) {\n viewModel.attributes.autocomplete = options.autocomplete\n }\n\n if ('classes' in options) {\n viewModel.classes = options.classes\n }\n\n if ('condition' in options && isConditionalRevealType(type)) {\n viewModel.condition = options.condition\n }\n\n return viewModel\n }\n}\n\nexport type ComponentSchema =\n | ArraySchema<string>\n | ArraySchema<number>\n | ArraySchema<boolean>\n | ArraySchema<object>\n | BooleanSchema<string>\n | DateSchema\n | NumberSchema<string>\n | NumberSchema\n | ObjectSchema\n | StringSchema\n"],"mappings":"AAAA,SAASA,uBAAuB,QAA2B,oBAAoB;AAC/E,OAAOC,GAAG,MAOH,KAAK;AAQZ,OAAO,MAAMC,aAAa,CAAC;EACzBC,IAAI;EACJC,MAAM;EACNC,UAAU;EAEVC,IAAI;EACJC,IAAI;EACJC,KAAK;EACLC,MAAM;EACNC,OAAO;EAEPC,eAAe,GAAG,KAAK;EACvBC,KAAK;;EAEL;EACAC,UAAU,GAAoBZ,GAAG,CAACa,MAAM,CAAC,CAAC;EAC1CC,WAAW,GAAoBd,GAAG,CAACa,MAAM,CAAC,CAAC;EAE3CE,WAAWA,CACTC,GAAiB,EACjBC,KAIC,EACD;IACA,IAAI,CAACZ,IAAI,GAAGW,GAAG,CAACX,IAAI;IACpB,IAAI,CAACC,IAAI,GAAGU,GAAG,CAACV,IAAI;IACpB,IAAI,CAACC,KAAK,GAAGS,GAAG,CAACT,KAAK;IAEtB,IAAI,QAAQ,IAAIS,GAAG,EAAE;MACnB,IAAI,CAACR,MAAM,GAAGQ,GAAG,CAACR,MAAM;IAC1B;IAEA,IAAI,SAAS,IAAIQ,GAAG,EAAE;MACpB,IAAI,CAACP,OAAO,GAAGO,GAAG,CAACP,OAAO;IAC5B;IAEA,IAAI,CAACP,IAAI,GAAGe,KAAK,CAACf,IAAI;IACtB,IAAI,CAACC,MAAM,GAAGc,KAAK,CAACd,MAAM;IAC1B,IAAI,CAACQ,KAAK,GAAGM,KAAK,CAACN,KAAK;EAC1B;EAEA,IAAIO,SAASA,CAAA,EAAG;IACd,MAAM;MAAET,OAAO;MAAEJ;IAAK,CAAC,GAAG,IAAI;IAE9B,MAAMa,SAAoB,GAAG;MAC3BC,UAAU,EAAE,CAAC;IACf,CAAC;IAED,IAAI,CAACV,OAAO,EAAE;MACZ,OAAOS,SAAS;IAClB;IAEA,IAAI,cAAc,IAAIT,OAAO,EAAE;MAC7BS,SAAS,CAACC,UAAU,CAACC,YAAY,GAAGX,OAAO,CAACW,YAAY;IAC1D;IAEA,IAAI,SAAS,IAAIX,OAAO,EAAE;MACxBS,SAAS,CAACG,OAAO,GAAGZ,OAAO,CAACY,OAAO;IACrC;IAEA,IAAI,WAAW,IAAIZ,OAAO,IAAIV,uBAAuB,CAACM,IAAI,CAAC,EAAE;MAC3Da,SAAS,CAACI,SAAS,GAAGb,OAAO,CAACa,SAAS;IACzC;IAEA,OAAOJ,SAAS;EAClB;AACF","ignoreList":[]}
@@ -42,7 +42,8 @@ export const configureEnginePlugin = async ({
42
42
  },
43
43
  viewContext: devtoolContext,
44
44
  preparePageEventRequestOptions,
45
- onRequest
45
+ onRequest,
46
+ baseUrl: 'http://localhost:3009' // always runs locally
46
47
  }
47
48
  };
48
49
  };
@@ -1 +1 @@
1
- {"version":3,"file":"configureEnginePlugin.js","names":["join","parse","FORM_PREFIX","FormModel","plugin","defaultServices","formsService","findPackageRoot","devtoolContext","configureEnginePlugin","formFileName","formFilePath","services","controllers","preparePageEventRequestOptions","onRequest","model","definition","getForm","name","initialBasePath","basePath","options","cacheName","nunjucks","baseLayoutPath","paths","viewContext","importPath","ext","attributes","type","formImport","with","default"],"sources":["../../../../src/server/plugins/engine/configureEnginePlugin.ts"],"sourcesContent":["import { join, parse } from 'node:path'\n\nimport { type FormDefinition } from '@defra/forms-model'\n\nimport { FORM_PREFIX } from '~/src/server/constants.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport { formsService } from '~/src/server/plugins/engine/services/localFormsService.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { findPackageRoot } from '~/src/server/plugins/engine/vision.js'\nimport { devtoolContext } from '~/src/server/plugins/nunjucks/context.js'\nimport { type RouteConfig } from '~/src/server/types.js'\n\nexport const configureEnginePlugin = async ({\n formFileName,\n formFilePath,\n services,\n controllers,\n preparePageEventRequestOptions,\n onRequest\n}: RouteConfig = {}): Promise<{\n plugin: typeof plugin\n options: PluginOptions\n}> => {\n let model: FormModel | undefined\n\n if (formFileName && formFilePath) {\n const definition = await getForm(join(formFilePath, formFileName))\n const { name } = parse(formFileName)\n\n const initialBasePath = `${FORM_PREFIX}${name}`\n\n model = new FormModel(\n definition,\n { basePath: initialBasePath },\n services,\n controllers\n )\n }\n\n return {\n plugin,\n options: {\n model,\n services: services ?? {\n // services for testing, else use the disk loader option for running this service locally\n ...defaultServices,\n formsService: await formsService()\n },\n controllers,\n cacheName: 'session',\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: devtoolContext,\n preparePageEventRequestOptions,\n onRequest\n }\n }\n}\n\nexport async function getForm(importPath: string) {\n const { ext } = parse(importPath)\n\n const attributes: ImportAttributes = {\n type: ext === '.json' ? 'json' : 'module'\n }\n\n const formImport = import(importPath, { with: attributes }) as Promise<{\n default: FormDefinition\n }>\n\n const { default: definition } = await formImport\n return definition\n}\n"],"mappings":"AAAA,SAASA,IAAI,EAAEC,KAAK,QAAQ,WAAW;AAIvC,SAASC,WAAW;AACpB,SAASC,SAAS;AAClB,SAASC,MAAM;AACf,OAAO,KAAKC,eAAe;AAC3B,SAASC,YAAY;AAErB,SAASC,eAAe;AACxB,SAASC,cAAc;AAGvB,OAAO,MAAMC,qBAAqB,GAAG,MAAAA,CAAO;EAC1CC,YAAY;EACZC,YAAY;EACZC,QAAQ;EACRC,WAAW;EACXC,8BAA8B;EAC9BC;AACW,CAAC,GAAG,CAAC,CAAC,KAGb;EACJ,IAAIC,KAA4B;EAEhC,IAAIN,YAAY,IAAIC,YAAY,EAAE;IAChC,MAAMM,UAAU,GAAG,MAAMC,OAAO,CAAClB,IAAI,CAACW,YAAY,EAAED,YAAY,CAAC,CAAC;IAClE,MAAM;MAAES;IAAK,CAAC,GAAGlB,KAAK,CAACS,YAAY,CAAC;IAEpC,MAAMU,eAAe,GAAG,GAAGlB,WAAW,GAAGiB,IAAI,EAAE;IAE/CH,KAAK,GAAG,IAAIb,SAAS,CACnBc,UAAU,EACV;MAAEI,QAAQ,EAAED;IAAgB,CAAC,EAC7BR,QAAQ,EACRC,WACF,CAAC;EACH;EAEA,OAAO;IACLT,MAAM;IACNkB,OAAO,EAAE;MACPN,KAAK;MACLJ,QAAQ,EAAEA,QAAQ,IAAI;QACpB;QACA,GAAGP,eAAe;QAClBC,YAAY,EAAE,MAAMA,YAAY,CAAC;MACnC,CAAC;MACDO,WAAW;MACXU,SAAS,EAAE,SAAS;MACpBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC1B,IAAI,CAACO,eAAe,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC;MAC3D,CAAC;MACDoB,WAAW,EAAEnB,cAAc;MAC3BM,8BAA8B;MAC9BC;IACF;EACF,CAAC;AACH,CAAC;AAED,OAAO,eAAeG,OAAOA,CAACU,UAAkB,EAAE;EAChD,MAAM;IAAEC;EAAI,CAAC,GAAG5B,KAAK,CAAC2B,UAAU,CAAC;EAEjC,MAAME,UAA4B,GAAG;IACnCC,IAAI,EAAEF,GAAG,KAAK,OAAO,GAAG,MAAM,GAAG;EACnC,CAAC;EAED,MAAMG,UAAU,GAAG,MAAM,CAACJ,UAAU,EAAE;IAAEK,IAAI,EAAEH;EAAW,CAAC,CAExD;EAEF,MAAM;IAAEI,OAAO,EAAEjB;EAAW,CAAC,GAAG,MAAMe,UAAU;EAChD,OAAOf,UAAU;AACnB","ignoreList":[]}
1
+ {"version":3,"file":"configureEnginePlugin.js","names":["join","parse","FORM_PREFIX","FormModel","plugin","defaultServices","formsService","findPackageRoot","devtoolContext","configureEnginePlugin","formFileName","formFilePath","services","controllers","preparePageEventRequestOptions","onRequest","model","definition","getForm","name","initialBasePath","basePath","options","cacheName","nunjucks","baseLayoutPath","paths","viewContext","baseUrl","importPath","ext","attributes","type","formImport","with","default"],"sources":["../../../../src/server/plugins/engine/configureEnginePlugin.ts"],"sourcesContent":["import { join, parse } from 'node:path'\n\nimport { type FormDefinition } from '@defra/forms-model'\n\nimport { FORM_PREFIX } from '~/src/server/constants.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport { formsService } from '~/src/server/plugins/engine/services/localFormsService.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { findPackageRoot } from '~/src/server/plugins/engine/vision.js'\nimport { devtoolContext } from '~/src/server/plugins/nunjucks/context.js'\nimport { type RouteConfig } from '~/src/server/types.js'\n\nexport const configureEnginePlugin = async ({\n formFileName,\n formFilePath,\n services,\n controllers,\n preparePageEventRequestOptions,\n onRequest\n}: RouteConfig = {}): Promise<{\n plugin: typeof plugin\n options: PluginOptions\n}> => {\n let model: FormModel | undefined\n\n if (formFileName && formFilePath) {\n const definition = await getForm(join(formFilePath, formFileName))\n const { name } = parse(formFileName)\n\n const initialBasePath = `${FORM_PREFIX}${name}`\n\n model = new FormModel(\n definition,\n { basePath: initialBasePath },\n services,\n controllers\n )\n }\n\n return {\n plugin,\n options: {\n model,\n services: services ?? {\n // services for testing, else use the disk loader option for running this service locally\n ...defaultServices,\n formsService: await formsService()\n },\n controllers,\n cacheName: 'session',\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: devtoolContext,\n preparePageEventRequestOptions,\n onRequest,\n baseUrl: 'http://localhost:3009' // always runs locally\n }\n }\n}\n\nexport async function getForm(importPath: string) {\n const { ext } = parse(importPath)\n\n const attributes: ImportAttributes = {\n type: ext === '.json' ? 'json' : 'module'\n }\n\n const formImport = import(importPath, { with: attributes }) as Promise<{\n default: FormDefinition\n }>\n\n const { default: definition } = await formImport\n return definition\n}\n"],"mappings":"AAAA,SAASA,IAAI,EAAEC,KAAK,QAAQ,WAAW;AAIvC,SAASC,WAAW;AACpB,SAASC,SAAS;AAClB,SAASC,MAAM;AACf,OAAO,KAAKC,eAAe;AAC3B,SAASC,YAAY;AAErB,SAASC,eAAe;AACxB,SAASC,cAAc;AAGvB,OAAO,MAAMC,qBAAqB,GAAG,MAAAA,CAAO;EAC1CC,YAAY;EACZC,YAAY;EACZC,QAAQ;EACRC,WAAW;EACXC,8BAA8B;EAC9BC;AACW,CAAC,GAAG,CAAC,CAAC,KAGb;EACJ,IAAIC,KAA4B;EAEhC,IAAIN,YAAY,IAAIC,YAAY,EAAE;IAChC,MAAMM,UAAU,GAAG,MAAMC,OAAO,CAAClB,IAAI,CAACW,YAAY,EAAED,YAAY,CAAC,CAAC;IAClE,MAAM;MAAES;IAAK,CAAC,GAAGlB,KAAK,CAACS,YAAY,CAAC;IAEpC,MAAMU,eAAe,GAAG,GAAGlB,WAAW,GAAGiB,IAAI,EAAE;IAE/CH,KAAK,GAAG,IAAIb,SAAS,CACnBc,UAAU,EACV;MAAEI,QAAQ,EAAED;IAAgB,CAAC,EAC7BR,QAAQ,EACRC,WACF,CAAC;EACH;EAEA,OAAO;IACLT,MAAM;IACNkB,OAAO,EAAE;MACPN,KAAK;MACLJ,QAAQ,EAAEA,QAAQ,IAAI;QACpB;QACA,GAAGP,eAAe;QAClBC,YAAY,EAAE,MAAMA,YAAY,CAAC;MACnC,CAAC;MACDO,WAAW;MACXU,SAAS,EAAE,SAAS;MACpBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC1B,IAAI,CAACO,eAAe,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC;MAC3D,CAAC;MACDoB,WAAW,EAAEnB,cAAc;MAC3BM,8BAA8B;MAC9BC,SAAS;MACTa,OAAO,EAAE,uBAAuB,CAAC;IACnC;EACF,CAAC;AACH,CAAC;AAED,OAAO,eAAeV,OAAOA,CAACW,UAAkB,EAAE;EAChD,MAAM;IAAEC;EAAI,CAAC,GAAG7B,KAAK,CAAC4B,UAAU,CAAC;EAEjC,MAAME,UAA4B,GAAG;IACnCC,IAAI,EAAEF,GAAG,KAAK,OAAO,GAAG,MAAM,GAAG;EACnC,CAAC;EAED,MAAMG,UAAU,GAAG,MAAM,CAACJ,UAAU,EAAE;IAAEK,IAAI,EAAEH;EAAW,CAAC,CAExD;EAEF,MAAM;IAAEI,OAAO,EAAElB;EAAW,CAAC,GAAG,MAAMgB,UAAU;EAChD,OAAOhB,UAAU;AACnB","ignoreList":[]}
@@ -1,9 +1,9 @@
1
1
  import { type Environment } from 'nunjucks';
2
2
  import { plugin } from '~/src/server/plugins/engine/plugin.js';
3
- import { type FilterFunction } from '~/src/server/plugins/engine/types.js';
3
+ import { type PluginOptions } from '~/src/server/plugins/engine/types.js';
4
4
  export { getPageHref } from '~/src/server/plugins/engine/helpers.js';
5
5
  export { context } from '~/src/server/plugins/nunjucks/context.js';
6
6
  export declare const VIEW_PATH = "src/server/plugins/engine/views";
7
7
  export declare const PLUGIN_PATH = "node_modules/@defra/forms-engine-plugin";
8
- export declare const prepareNunjucksEnvironment: (env: Environment, additionalFilters?: Record<string, FilterFunction>) => void;
8
+ export declare const prepareNunjucksEnvironment: (env: Environment, pluginOptions: PluginOptions) => void;
9
9
  export default plugin;
@@ -1,3 +1,4 @@
1
+ import { markdownToHtml } from '@defra/forms-model';
1
2
  import { engine } from "./helpers.js";
2
3
  import { plugin } from "./plugin.js";
3
4
  import { checkComponentTemplates, checkErrorTemplates, evaluate, govukRebrand } from "../nunjucks/environment.js";
@@ -12,17 +13,18 @@ const globals = {
12
13
  };
13
14
  export const VIEW_PATH = 'src/server/plugins/engine/views';
14
15
  export const PLUGIN_PATH = 'node_modules/@defra/forms-engine-plugin';
15
- export const prepareNunjucksEnvironment = function (env, additionalFilters) {
16
+ export const prepareNunjucksEnvironment = function (env, pluginOptions) {
16
17
  for (const [name, nunjucksFilter] of Object.entries(filters)) {
17
18
  env.addFilter(name, nunjucksFilter);
18
19
  }
20
+ env.addFilter('markdown', text => markdownToHtml(text, pluginOptions.baseUrl));
19
21
  for (const [name, nunjucksGlobal] of Object.entries(globals)) {
20
22
  env.addGlobal(name, nunjucksGlobal);
21
23
  }
22
24
 
23
25
  // Apply any additional filters to both the liquid and nunjucks engines
24
- if (additionalFilters) {
25
- for (const [name, filter] of Object.entries(additionalFilters)) {
26
+ if (pluginOptions.filters) {
27
+ for (const [name, filter] of Object.entries(pluginOptions.filters)) {
26
28
  env.addFilter(name, filter);
27
29
  engine.registerFilter(name, filter);
28
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["engine","plugin","checkComponentTemplates","checkErrorTemplates","evaluate","govukRebrand","filters","getPageHref","context","globals","VIEW_PATH","PLUGIN_PATH","prepareNunjucksEnvironment","env","additionalFilters","name","nunjucksFilter","Object","entries","addFilter","nunjucksGlobal","addGlobal","filter","registerFilter"],"sources":["../../../../src/server/plugins/engine/index.ts"],"sourcesContent":["import { type Environment } from 'nunjucks'\n\nimport { engine } from '~/src/server/plugins/engine/helpers.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport { type FilterFunction } from '~/src/server/plugins/engine/types.js'\nimport {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n} from '~/src/server/plugins/nunjucks/environment.js'\nimport * as filters from '~/src/server/plugins/nunjucks/filters/index.js'\n\nexport { getPageHref } from '~/src/server/plugins/engine/helpers.js'\nexport { context } from '~/src/server/plugins/nunjucks/context.js'\n\nconst globals = {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n}\n\nexport const VIEW_PATH = 'src/server/plugins/engine/views'\nexport const PLUGIN_PATH = 'node_modules/@defra/forms-engine-plugin'\n\nexport const prepareNunjucksEnvironment = function (\n env: Environment,\n additionalFilters?: Record<string, FilterFunction>\n) {\n for (const [name, nunjucksFilter] of Object.entries(filters)) {\n env.addFilter(name, nunjucksFilter)\n }\n\n for (const [name, nunjucksGlobal] of Object.entries(globals)) {\n env.addGlobal(name, nunjucksGlobal)\n }\n\n // Apply any additional filters to both the liquid and nunjucks engines\n if (additionalFilters) {\n for (const [name, filter] of Object.entries(additionalFilters)) {\n env.addFilter(name, filter)\n engine.registerFilter(name, filter)\n }\n }\n}\n\nexport default plugin\n"],"mappings":"AAEA,SAASA,MAAM;AACf,SAASC,MAAM;AAEf,SACEC,uBAAuB,EACvBC,mBAAmB,EACnBC,QAAQ,EACRC,YAAY;AAEd,OAAO,KAAKC,OAAO;AAEnB,SAASC,WAAW;AACpB,SAASC,OAAO;AAEhB,MAAMC,OAAO,GAAG;EACdP,uBAAuB;EACvBC,mBAAmB;EACnBC,QAAQ;EACRC;AACF,CAAC;AAED,OAAO,MAAMK,SAAS,GAAG,iCAAiC;AAC1D,OAAO,MAAMC,WAAW,GAAG,yCAAyC;AAEpE,OAAO,MAAMC,0BAA0B,GAAG,SAAAA,CACxCC,GAAgB,EAChBC,iBAAkD,EAClD;EACA,KAAK,MAAM,CAACC,IAAI,EAAEC,cAAc,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACZ,OAAO,CAAC,EAAE;IAC5DO,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAEC,cAAc,CAAC;EACrC;EAEA,KAAK,MAAM,CAACD,IAAI,EAAEK,cAAc,CAAC,IAAIH,MAAM,CAACC,OAAO,CAACT,OAAO,CAAC,EAAE;IAC5DI,GAAG,CAACQ,SAAS,CAACN,IAAI,EAAEK,cAAc,CAAC;EACrC;;EAEA;EACA,IAAIN,iBAAiB,EAAE;IACrB,KAAK,MAAM,CAACC,IAAI,EAAEO,MAAM,CAAC,IAAIL,MAAM,CAACC,OAAO,CAACJ,iBAAiB,CAAC,EAAE;MAC9DD,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAEO,MAAM,CAAC;MAC3BtB,MAAM,CAACuB,cAAc,CAACR,IAAI,EAAEO,MAAM,CAAC;IACrC;EACF;AACF,CAAC;AAED,eAAerB,MAAM","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["markdownToHtml","engine","plugin","checkComponentTemplates","checkErrorTemplates","evaluate","govukRebrand","filters","getPageHref","context","globals","VIEW_PATH","PLUGIN_PATH","prepareNunjucksEnvironment","env","pluginOptions","name","nunjucksFilter","Object","entries","addFilter","text","baseUrl","nunjucksGlobal","addGlobal","filter","registerFilter"],"sources":["../../../../src/server/plugins/engine/index.ts"],"sourcesContent":["import { markdownToHtml } from '@defra/forms-model'\nimport { type Environment } from 'nunjucks'\n\nimport { engine } from '~/src/server/plugins/engine/helpers.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n} from '~/src/server/plugins/nunjucks/environment.js'\nimport * as filters from '~/src/server/plugins/nunjucks/filters/index.js'\n\nexport { getPageHref } from '~/src/server/plugins/engine/helpers.js'\nexport { context } from '~/src/server/plugins/nunjucks/context.js'\n\nconst globals = {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n}\n\nexport const VIEW_PATH = 'src/server/plugins/engine/views'\nexport const PLUGIN_PATH = 'node_modules/@defra/forms-engine-plugin'\n\nexport const prepareNunjucksEnvironment = function (\n env: Environment,\n pluginOptions: PluginOptions\n) {\n for (const [name, nunjucksFilter] of Object.entries(filters)) {\n env.addFilter(name, nunjucksFilter)\n }\n\n env.addFilter('markdown', (text: string) =>\n markdownToHtml(text, pluginOptions.baseUrl)\n )\n\n for (const [name, nunjucksGlobal] of Object.entries(globals)) {\n env.addGlobal(name, nunjucksGlobal)\n }\n\n // Apply any additional filters to both the liquid and nunjucks engines\n if (pluginOptions.filters) {\n for (const [name, filter] of Object.entries(pluginOptions.filters)) {\n env.addFilter(name, filter)\n engine.registerFilter(name, filter)\n }\n }\n}\n\nexport default plugin\n"],"mappings":"AAAA,SAASA,cAAc,QAAQ,oBAAoB;AAGnD,SAASC,MAAM;AACf,SAASC,MAAM;AAEf,SACEC,uBAAuB,EACvBC,mBAAmB,EACnBC,QAAQ,EACRC,YAAY;AAEd,OAAO,KAAKC,OAAO;AAEnB,SAASC,WAAW;AACpB,SAASC,OAAO;AAEhB,MAAMC,OAAO,GAAG;EACdP,uBAAuB;EACvBC,mBAAmB;EACnBC,QAAQ;EACRC;AACF,CAAC;AAED,OAAO,MAAMK,SAAS,GAAG,iCAAiC;AAC1D,OAAO,MAAMC,WAAW,GAAG,yCAAyC;AAEpE,OAAO,MAAMC,0BAA0B,GAAG,SAAAA,CACxCC,GAAgB,EAChBC,aAA4B,EAC5B;EACA,KAAK,MAAM,CAACC,IAAI,EAAEC,cAAc,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACZ,OAAO,CAAC,EAAE;IAC5DO,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAEC,cAAc,CAAC;EACrC;EAEAH,GAAG,CAACM,SAAS,CAAC,UAAU,EAAGC,IAAY,IACrCrB,cAAc,CAACqB,IAAI,EAAEN,aAAa,CAACO,OAAO,CAC5C,CAAC;EAED,KAAK,MAAM,CAACN,IAAI,EAAEO,cAAc,CAAC,IAAIL,MAAM,CAACC,OAAO,CAACT,OAAO,CAAC,EAAE;IAC5DI,GAAG,CAACU,SAAS,CAACR,IAAI,EAAEO,cAAc,CAAC;EACrC;;EAEA;EACA,IAAIR,aAAa,CAACR,OAAO,EAAE;IACzB,KAAK,MAAM,CAACS,IAAI,EAAES,MAAM,CAAC,IAAIP,MAAM,CAACC,OAAO,CAACJ,aAAa,CAACR,OAAO,CAAC,EAAE;MAClEO,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAES,MAAM,CAAC;MAC3BxB,MAAM,CAACyB,cAAc,CAACV,IAAI,EAAES,MAAM,CAAC;IACrC;EACF;AACF,CAAC;AAED,eAAevB,MAAM","ignoreList":[]}
@@ -1,4 +1,6 @@
1
1
  import Joi from 'joi';
2
+ import { createLogger } from "../../common/helpers/logging/logger.js";
3
+ const logger = createLogger();
2
4
  const pluginRegistrationOptionsSchema = Joi.object({
3
5
  model: Joi.object().optional(),
4
6
  services: Joi.object().optional(),
@@ -12,7 +14,8 @@ const pluginRegistrationOptionsSchema = Joi.object({
12
14
  }).required(),
13
15
  viewContext: Joi.function().required(),
14
16
  preparePageEventRequestOptions: Joi.function().optional(),
15
- onRequest: Joi.function().optional()
17
+ onRequest: Joi.function().optional(),
18
+ baseUrl: Joi.string().uri().required()
16
19
  });
17
20
 
18
21
  /**
@@ -25,6 +28,7 @@ export function validatePluginOptions(options) {
25
28
  abortEarly: false
26
29
  });
27
30
  if (result.error) {
31
+ logger.error(`Missing required properties in plugin options: ${result.error.message}`);
28
32
  throw new Error('Invalid plugin options', result.error);
29
33
  }
30
34
 
@@ -1 +1 @@
1
- {"version":3,"file":"options.js","names":["Joi","pluginRegistrationOptionsSchema","object","model","optional","services","controllers","pattern","string","any","cacheName","filters","pluginPath","nunjucks","baseLayoutPath","required","paths","array","items","viewContext","function","preparePageEventRequestOptions","onRequest","validatePluginOptions","options","result","validate","abortEarly","error","Error","value"],"sources":["../../../../src/server/plugins/engine/options.js"],"sourcesContent":["import Joi from 'joi'\n\nconst pluginRegistrationOptionsSchema = Joi.object({\n model: Joi.object().optional(),\n services: Joi.object().optional(),\n controllers: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n cacheName: Joi.string().optional(),\n filters: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n pluginPath: Joi.string().optional(),\n nunjucks: Joi.object({\n baseLayoutPath: Joi.string().required(),\n paths: Joi.array().items(Joi.string()).required()\n }).required(),\n viewContext: Joi.function().required(),\n preparePageEventRequestOptions: Joi.function().optional(),\n onRequest: Joi.function().optional()\n})\n\n/**\n * Validates the plugin options against the schema and returns the validated value.\n * @param {PluginOptions} options\n * @returns {PluginOptions}\n */\nexport function validatePluginOptions(options) {\n const result = pluginRegistrationOptionsSchema.validate(options, {\n abortEarly: false\n })\n\n if (result.error) {\n throw new Error('Invalid plugin options', result.error)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return result.value\n}\n\n/**\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,OAAOA,GAAG,MAAM,KAAK;AAErB,MAAMC,+BAA+B,GAAGD,GAAG,CAACE,MAAM,CAAC;EACjDC,KAAK,EAAEH,GAAG,CAACE,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EAC9BC,QAAQ,EAAEL,GAAG,CAACE,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EACjCE,WAAW,EAAEN,GAAG,CAACE,MAAM,CAAC,CAAC,CAACK,OAAO,CAACP,GAAG,CAACQ,MAAM,CAAC,CAAC,EAAER,GAAG,CAACS,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACrEM,SAAS,EAAEV,GAAG,CAACQ,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EAClCO,OAAO,EAAEX,GAAG,CAACE,MAAM,CAAC,CAAC,CAACK,OAAO,CAACP,GAAG,CAACQ,MAAM,CAAC,CAAC,EAAER,GAAG,CAACS,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjEQ,UAAU,EAAEZ,GAAG,CAACQ,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EACnCS,QAAQ,EAAEb,GAAG,CAACE,MAAM,CAAC;IACnBY,cAAc,EAAEd,GAAG,CAACQ,MAAM,CAAC,CAAC,CAACO,QAAQ,CAAC,CAAC;IACvCC,KAAK,EAAEhB,GAAG,CAACiB,KAAK,CAAC,CAAC,CAACC,KAAK,CAAClB,GAAG,CAACQ,MAAM,CAAC,CAAC,CAAC,CAACO,QAAQ,CAAC;EAClD,CAAC,CAAC,CAACA,QAAQ,CAAC,CAAC;EACbI,WAAW,EAAEnB,GAAG,CAACoB,QAAQ,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACtCM,8BAA8B,EAAErB,GAAG,CAACoB,QAAQ,CAAC,CAAC,CAAChB,QAAQ,CAAC,CAAC;EACzDkB,SAAS,EAAEtB,GAAG,CAACoB,QAAQ,CAAC,CAAC,CAAChB,QAAQ,CAAC;AACrC,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA,OAAO,SAASmB,qBAAqBA,CAACC,OAAO,EAAE;EAC7C,MAAMC,MAAM,GAAGxB,+BAA+B,CAACyB,QAAQ,CAACF,OAAO,EAAE;IAC/DG,UAAU,EAAE;EACd,CAAC,CAAC;EAEF,IAAIF,MAAM,CAACG,KAAK,EAAE;IAChB,MAAM,IAAIC,KAAK,CAAC,wBAAwB,EAAEJ,MAAM,CAACG,KAAK,CAAC;EACzD;;EAEA;EACA,OAAOH,MAAM,CAACK,KAAK;AACrB;;AAEA;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"file":"options.js","names":["Joi","createLogger","logger","pluginRegistrationOptionsSchema","object","model","optional","services","controllers","pattern","string","any","cacheName","filters","pluginPath","nunjucks","baseLayoutPath","required","paths","array","items","viewContext","function","preparePageEventRequestOptions","onRequest","baseUrl","uri","validatePluginOptions","options","result","validate","abortEarly","error","message","Error","value"],"sources":["../../../../src/server/plugins/engine/options.js"],"sourcesContent":["import Joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\n\nconst logger = createLogger()\n\nconst pluginRegistrationOptionsSchema = Joi.object({\n model: Joi.object().optional(),\n services: Joi.object().optional(),\n controllers: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n cacheName: Joi.string().optional(),\n filters: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n pluginPath: Joi.string().optional(),\n nunjucks: Joi.object({\n baseLayoutPath: Joi.string().required(),\n paths: Joi.array().items(Joi.string()).required()\n }).required(),\n viewContext: Joi.function().required(),\n preparePageEventRequestOptions: Joi.function().optional(),\n onRequest: Joi.function().optional(),\n baseUrl: Joi.string().uri().required()\n})\n\n/**\n * Validates the plugin options against the schema and returns the validated value.\n * @param {PluginOptions} options\n * @returns {PluginOptions}\n */\nexport function validatePluginOptions(options) {\n const result = pluginRegistrationOptionsSchema.validate(options, {\n abortEarly: false\n })\n\n if (result.error) {\n logger.error(\n `Missing required properties in plugin options: ${result.error.message}`\n )\n throw new Error('Invalid plugin options', result.error)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return result.value\n}\n\n/**\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,OAAOA,GAAG,MAAM,KAAK;AAErB,SAASC,YAAY;AAErB,MAAMC,MAAM,GAAGD,YAAY,CAAC,CAAC;AAE7B,MAAME,+BAA+B,GAAGH,GAAG,CAACI,MAAM,CAAC;EACjDC,KAAK,EAAEL,GAAG,CAACI,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EAC9BC,QAAQ,EAAEP,GAAG,CAACI,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EACjCE,WAAW,EAAER,GAAG,CAACI,MAAM,CAAC,CAAC,CAACK,OAAO,CAACT,GAAG,CAACU,MAAM,CAAC,CAAC,EAAEV,GAAG,CAACW,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACrEM,SAAS,EAAEZ,GAAG,CAACU,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EAClCO,OAAO,EAAEb,GAAG,CAACI,MAAM,CAAC,CAAC,CAACK,OAAO,CAACT,GAAG,CAACU,MAAM,CAAC,CAAC,EAAEV,GAAG,CAACW,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjEQ,UAAU,EAAEd,GAAG,CAACU,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EACnCS,QAAQ,EAAEf,GAAG,CAACI,MAAM,CAAC;IACnBY,cAAc,EAAEhB,GAAG,CAACU,MAAM,CAAC,CAAC,CAACO,QAAQ,CAAC,CAAC;IACvCC,KAAK,EAAElB,GAAG,CAACmB,KAAK,CAAC,CAAC,CAACC,KAAK,CAACpB,GAAG,CAACU,MAAM,CAAC,CAAC,CAAC,CAACO,QAAQ,CAAC;EAClD,CAAC,CAAC,CAACA,QAAQ,CAAC,CAAC;EACbI,WAAW,EAAErB,GAAG,CAACsB,QAAQ,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACtCM,8BAA8B,EAAEvB,GAAG,CAACsB,QAAQ,CAAC,CAAC,CAAChB,QAAQ,CAAC,CAAC;EACzDkB,SAAS,EAAExB,GAAG,CAACsB,QAAQ,CAAC,CAAC,CAAChB,QAAQ,CAAC,CAAC;EACpCmB,OAAO,EAAEzB,GAAG,CAACU,MAAM,CAAC,CAAC,CAACgB,GAAG,CAAC,CAAC,CAACT,QAAQ,CAAC;AACvC,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA,OAAO,SAASU,qBAAqBA,CAACC,OAAO,EAAE;EAC7C,MAAMC,MAAM,GAAG1B,+BAA+B,CAAC2B,QAAQ,CAACF,OAAO,EAAE;IAC/DG,UAAU,EAAE;EACd,CAAC,CAAC;EAEF,IAAIF,MAAM,CAACG,KAAK,EAAE;IAChB9B,MAAM,CAAC8B,KAAK,CACV,kDAAkDH,MAAM,CAACG,KAAK,CAACC,OAAO,EACxE,CAAC;IACD,MAAM,IAAIC,KAAK,CAAC,wBAAwB,EAAEL,MAAM,CAACG,KAAK,CAAC;EACzD;;EAEA;EACA,OAAOH,MAAM,CAACM,KAAK;AACrB;;AAEA;AACA;AACA","ignoreList":[]}
@@ -10,7 +10,8 @@ describe('validatePluginOptions', () => {
10
10
  return {
11
11
  hello: 'world'
12
12
  };
13
- }
13
+ },
14
+ baseUrl: 'http://localhost:3009'
14
15
  };
15
16
  expect(validatePluginOptions(validOptions)).toEqual(validOptions);
16
17
  });
@@ -19,6 +20,7 @@ describe('validatePluginOptions', () => {
19
20
  * tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test
20
21
  */
21
22
  it('fails if a required attribute is missing', () => {
23
+ // viewContext is missing
22
24
  const invalidOptions = {
23
25
  nunjucks: {
24
26
  baseLayoutPath: 'dxt-devtool-baselayout.html',
@@ -1 +1 @@
1
- {"version":3,"file":"options.test.js","names":["validatePluginOptions","describe","it","validOptions","nunjucks","baseLayoutPath","paths","viewContext","hello","expect","toEqual","invalidOptions","toThrow"],"sources":["../../../../src/server/plugins/engine/options.test.js"],"sourcesContent":["import { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\n\ndescribe('validatePluginOptions', () => {\n it('returns the validated value for valid options', () => {\n const validOptions = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: () => {\n return { hello: 'world' }\n }\n }\n\n expect(validatePluginOptions(validOptions)).toEqual(validOptions)\n })\n\n /**\n * tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test\n */\n it('fails if a required attribute is missing', () => {\n const invalidOptions = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n }\n }\n\n // @ts-expect-error -- add a test for JS users\n expect(() => validatePluginOptions(invalidOptions)).toThrow(\n 'Invalid plugin options'\n )\n })\n})\n"],"mappings":"AAAA,SAASA,qBAAqB;AAE9BC,QAAQ,CAAC,uBAAuB,EAAE,MAAM;EACtCC,EAAE,CAAC,+CAA+C,EAAE,MAAM;IACxD,MAAMC,YAAY,GAAG;MACnBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC,CAAC;MACDC,WAAW,EAAEA,CAAA,KAAM;QACjB,OAAO;UAAEC,KAAK,EAAE;QAAQ,CAAC;MAC3B;IACF,CAAC;IAEDC,MAAM,CAACT,qBAAqB,CAACG,YAAY,CAAC,CAAC,CAACO,OAAO,CAACP,YAAY,CAAC;EACnE,CAAC,CAAC;;EAEF;AACF;AACA;EACED,EAAE,CAAC,0CAA0C,EAAE,MAAM;IACnD,MAAMS,cAAc,GAAG;MACrBP,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC;IACF,CAAC;;IAED;IACAG,MAAM,CAAC,MAAMT,qBAAqB,CAACW,cAAc,CAAC,CAAC,CAACC,OAAO,CACzD,wBACF,CAAC;EACH,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"options.test.js","names":["validatePluginOptions","describe","it","validOptions","nunjucks","baseLayoutPath","paths","viewContext","hello","baseUrl","expect","toEqual","invalidOptions","toThrow"],"sources":["../../../../src/server/plugins/engine/options.test.js"],"sourcesContent":["import { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\n\ndescribe('validatePluginOptions', () => {\n it('returns the validated value for valid options', () => {\n const validOptions = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: () => {\n return { hello: 'world' }\n },\n baseUrl: 'http://localhost:3009'\n }\n\n expect(validatePluginOptions(validOptions)).toEqual(validOptions)\n })\n\n /**\n * tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test\n */\n it('fails if a required attribute is missing', () => {\n // viewContext is missing\n const invalidOptions = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n }\n }\n\n // @ts-expect-error -- add a test for JS users\n expect(() => validatePluginOptions(invalidOptions)).toThrow(\n 'Invalid plugin options'\n )\n })\n})\n"],"mappings":"AAAA,SAASA,qBAAqB;AAE9BC,QAAQ,CAAC,uBAAuB,EAAE,MAAM;EACtCC,EAAE,CAAC,+CAA+C,EAAE,MAAM;IACxD,MAAMC,YAAY,GAAG;MACnBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC,CAAC;MACDC,WAAW,EAAEA,CAAA,KAAM;QACjB,OAAO;UAAEC,KAAK,EAAE;QAAQ,CAAC;MAC3B,CAAC;MACDC,OAAO,EAAE;IACX,CAAC;IAEDC,MAAM,CAACV,qBAAqB,CAACG,YAAY,CAAC,CAAC,CAACQ,OAAO,CAACR,YAAY,CAAC;EACnE,CAAC,CAAC;;EAEF;AACF;AACA;EACED,EAAE,CAAC,0CAA0C,EAAE,MAAM;IACnD;IACA,MAAMU,cAAc,GAAG;MACrBR,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC;IACF,CAAC;;IAED;IACAI,MAAM,CAAC,MAAMV,qBAAqB,CAACY,cAAc,CAAC,CAAC,CAACC,OAAO,CACzD,wBACF,CAAC;EACH,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
@@ -276,4 +276,5 @@ export interface PluginOptions {
276
276
  viewContext: PluginProperties['forms-engine-plugin']['viewContext'];
277
277
  preparePageEventRequestOptions?: PreparePageEventRequestOptions;
278
278
  onRequest?: OnRequestCallback;
279
+ baseUrl: string;
279
280
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["UploadStatus","FileStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { type PluginProperties, type Request } from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<FormRequest, 'app' | 'method' | 'params' | 'path' | 'query' | 'url'>\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport enum UploadStatus {\n initiated = 'initiated',\n pending = 'pending',\n ready = 'ready'\n}\n\nexport enum FileStatus {\n complete = 'complete',\n rejected = 'rejected',\n pending = 'pending'\n}\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: FormRequest | FormRequestPayload,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n filters?: Record<string, FilterFunction>\n keyGenerator?: (request: Request | FormRequest | FormRequestPayload) => string\n sessionHydrator?: (\n request: Request | FormRequest | FormRequestPayload\n ) => Promise<FormSubmissionState>\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n}\n"],"mappings":"AAgCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAkGA,WAAYA,YAAY,0BAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA;AAMxB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":["UploadStatus","FileStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { type PluginProperties, type Request } from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<FormRequest, 'app' | 'method' | 'params' | 'path' | 'query' | 'url'>\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport enum UploadStatus {\n initiated = 'initiated',\n pending = 'pending',\n ready = 'ready'\n}\n\nexport enum FileStatus {\n complete = 'complete',\n rejected = 'rejected',\n pending = 'pending'\n}\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: FormRequest | FormRequestPayload,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n filters?: Record<string, FilterFunction>\n keyGenerator?: (request: Request | FormRequest | FormRequestPayload) => string\n sessionHydrator?: (\n request: Request | FormRequest | FormRequestPayload\n ) => Promise<FormSubmissionState>\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n"],"mappings":"AAgCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAkGA,WAAYA,YAAY,0BAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA;AAMxB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA","ignoreList":[]}
@@ -28,7 +28,7 @@ export async function registerVision(server, pluginOptions) {
28
28
 
29
29
  // Applies custom filters and globals for nunjucks
30
30
  // that are required by the `forms-engine-plugin`
31
- prepareNunjucksEnvironment(environment, pluginOptions.filters);
31
+ prepareNunjucksEnvironment(environment, pluginOptions);
32
32
  options.compileOptions.environment = environment;
33
33
  next();
34
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vision.js","names":["existsSync","dirname","join","fileURLToPath","vision","nunjucks","resolvePkg","VIEW_PATH","context","prepareNunjucksEnvironment","registerVision","server","pluginOptions","packageRoot","findPackageRoot","govukFrontendPath","sync","viewPathResolved","paths","register","plugin","options","engines","html","compile","path","compileOptions","template","environment","render","prepare","next","configure","filters","currentFileName","import","meta","url","currentDirectoryName","dir","Error"],"sources":["../../../../src/server/plugins/engine/vision.ts"],"sourcesContent":["import { existsSync } from 'fs'\nimport { dirname, join } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport { type Server } from '@hapi/hapi'\nimport vision from '@hapi/vision'\nimport nunjucks, { type Environment } from 'nunjucks'\nimport resolvePkg from 'resolve'\n\nimport {\n VIEW_PATH,\n context,\n prepareNunjucksEnvironment\n} from '~/src/server/plugins/engine/index.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\n\nexport async function registerVision(\n server: Server,\n pluginOptions: PluginOptions\n) {\n const packageRoot = findPackageRoot()\n const govukFrontendPath = dirname(\n resolvePkg.sync('govuk-frontend/package.json')\n )\n\n const viewPathResolved = join(packageRoot, VIEW_PATH)\n\n const paths = [\n ...pluginOptions.nunjucks.paths,\n viewPathResolved,\n join(govukFrontendPath, 'dist')\n ]\n\n await server.register({\n plugin: vision,\n options: {\n engines: {\n html: {\n compile: (\n path: string,\n compileOptions: { environment: Environment }\n ) => {\n const template = nunjucks.compile(path, compileOptions.environment)\n\n return (context: object | undefined) => {\n return template.render(context)\n }\n },\n prepare: (\n options: EngineConfigurationObject,\n next: (err?: Error) => void\n ) => {\n // Nunjucks also needs an additional path configuration\n // to use the templates and macros from `govuk-frontend`\n const environment = nunjucks.configure(paths)\n\n // Applies custom filters and globals for nunjucks\n // that are required by the `forms-engine-plugin`\n prepareNunjucksEnvironment(environment, pluginOptions.filters)\n\n options.compileOptions.environment = environment\n\n next()\n }\n }\n },\n path: paths,\n // Provides global context used with all templates\n context\n }\n })\n}\n\ninterface CompileOptions {\n environment: Environment\n}\n\nexport interface EngineConfigurationObject {\n compileOptions: CompileOptions\n}\n\nexport function findPackageRoot() {\n const currentFileName = fileURLToPath(import.meta.url)\n const currentDirectoryName = dirname(currentFileName)\n\n let dir = currentDirectoryName\n while (dir !== '/') {\n if (existsSync(join(dir, 'package.json'))) {\n return dir\n }\n dir = dirname(dir)\n }\n\n throw new Error('package.json not found in parent directories')\n}\n"],"mappings":"AAAA,SAASA,UAAU,QAAQ,IAAI;AAC/B,SAASC,OAAO,EAAEC,IAAI,QAAQ,MAAM;AACpC,SAASC,aAAa,QAAQ,KAAK;AAGnC,OAAOC,MAAM,MAAM,cAAc;AACjC,OAAOC,QAAQ,MAA4B,UAAU;AACrD,OAAOC,UAAU,MAAM,SAAS;AAEhC,SACEC,SAAS,EACTC,OAAO,EACPC,0BAA0B;AAI5B,OAAO,eAAeC,cAAcA,CAClCC,MAAc,EACdC,aAA4B,EAC5B;EACA,MAAMC,WAAW,GAAGC,eAAe,CAAC,CAAC;EACrC,MAAMC,iBAAiB,GAAGd,OAAO,CAC/BK,UAAU,CAACU,IAAI,CAAC,6BAA6B,CAC/C,CAAC;EAED,MAAMC,gBAAgB,GAAGf,IAAI,CAACW,WAAW,EAAEN,SAAS,CAAC;EAErD,MAAMW,KAAK,GAAG,CACZ,GAAGN,aAAa,CAACP,QAAQ,CAACa,KAAK,EAC/BD,gBAAgB,EAChBf,IAAI,CAACa,iBAAiB,EAAE,MAAM,CAAC,CAChC;EAED,MAAMJ,MAAM,CAACQ,QAAQ,CAAC;IACpBC,MAAM,EAAEhB,MAAM;IACdiB,OAAO,EAAE;MACPC,OAAO,EAAE;QACPC,IAAI,EAAE;UACJC,OAAO,EAAEA,CACPC,IAAY,EACZC,cAA4C,KACzC;YACH,MAAMC,QAAQ,GAAGtB,QAAQ,CAACmB,OAAO,CAACC,IAAI,EAAEC,cAAc,CAACE,WAAW,CAAC;YAEnE,OAAQpB,OAA2B,IAAK;cACtC,OAAOmB,QAAQ,CAACE,MAAM,CAACrB,OAAO,CAAC;YACjC,CAAC;UACH,CAAC;UACDsB,OAAO,EAAEA,CACPT,OAAkC,EAClCU,IAA2B,KACxB;YACH;YACA;YACA,MAAMH,WAAW,GAAGvB,QAAQ,CAAC2B,SAAS,CAACd,KAAK,CAAC;;YAE7C;YACA;YACAT,0BAA0B,CAACmB,WAAW,EAAEhB,aAAa,CAACqB,OAAO,CAAC;YAE9DZ,OAAO,CAACK,cAAc,CAACE,WAAW,GAAGA,WAAW;YAEhDG,IAAI,CAAC,CAAC;UACR;QACF;MACF,CAAC;MACDN,IAAI,EAAEP,KAAK;MACX;MACAV;IACF;EACF,CAAC,CAAC;AACJ;AAUA,OAAO,SAASM,eAAeA,CAAA,EAAG;EAChC,MAAMoB,eAAe,GAAG/B,aAAa,CAACgC,MAAM,CAACC,IAAI,CAACC,GAAG,CAAC;EACtD,MAAMC,oBAAoB,GAAGrC,OAAO,CAACiC,eAAe,CAAC;EAErD,IAAIK,GAAG,GAAGD,oBAAoB;EAC9B,OAAOC,GAAG,KAAK,GAAG,EAAE;IAClB,IAAIvC,UAAU,CAACE,IAAI,CAACqC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE;MACzC,OAAOA,GAAG;IACZ;IACAA,GAAG,GAAGtC,OAAO,CAACsC,GAAG,CAAC;EACpB;EAEA,MAAM,IAAIC,KAAK,CAAC,8CAA8C,CAAC;AACjE","ignoreList":[]}
1
+ {"version":3,"file":"vision.js","names":["existsSync","dirname","join","fileURLToPath","vision","nunjucks","resolvePkg","VIEW_PATH","context","prepareNunjucksEnvironment","registerVision","server","pluginOptions","packageRoot","findPackageRoot","govukFrontendPath","sync","viewPathResolved","paths","register","plugin","options","engines","html","compile","path","compileOptions","template","environment","render","prepare","next","configure","currentFileName","import","meta","url","currentDirectoryName","dir","Error"],"sources":["../../../../src/server/plugins/engine/vision.ts"],"sourcesContent":["import { existsSync } from 'fs'\nimport { dirname, join } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport { type Server } from '@hapi/hapi'\nimport vision from '@hapi/vision'\nimport nunjucks, { type Environment } from 'nunjucks'\nimport resolvePkg from 'resolve'\n\nimport {\n VIEW_PATH,\n context,\n prepareNunjucksEnvironment\n} from '~/src/server/plugins/engine/index.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\n\nexport async function registerVision(\n server: Server,\n pluginOptions: PluginOptions\n) {\n const packageRoot = findPackageRoot()\n const govukFrontendPath = dirname(\n resolvePkg.sync('govuk-frontend/package.json')\n )\n\n const viewPathResolved = join(packageRoot, VIEW_PATH)\n\n const paths = [\n ...pluginOptions.nunjucks.paths,\n viewPathResolved,\n join(govukFrontendPath, 'dist')\n ]\n\n await server.register({\n plugin: vision,\n options: {\n engines: {\n html: {\n compile: (\n path: string,\n compileOptions: { environment: Environment }\n ) => {\n const template = nunjucks.compile(path, compileOptions.environment)\n\n return (context: object | undefined) => {\n return template.render(context)\n }\n },\n prepare: (\n options: EngineConfigurationObject,\n next: (err?: Error) => void\n ) => {\n // Nunjucks also needs an additional path configuration\n // to use the templates and macros from `govuk-frontend`\n const environment = nunjucks.configure(paths)\n\n // Applies custom filters and globals for nunjucks\n // that are required by the `forms-engine-plugin`\n prepareNunjucksEnvironment(environment, pluginOptions)\n\n options.compileOptions.environment = environment\n\n next()\n }\n }\n },\n path: paths,\n // Provides global context used with all templates\n context\n }\n })\n}\n\ninterface CompileOptions {\n environment: Environment\n}\n\nexport interface EngineConfigurationObject {\n compileOptions: CompileOptions\n}\n\nexport function findPackageRoot() {\n const currentFileName = fileURLToPath(import.meta.url)\n const currentDirectoryName = dirname(currentFileName)\n\n let dir = currentDirectoryName\n while (dir !== '/') {\n if (existsSync(join(dir, 'package.json'))) {\n return dir\n }\n dir = dirname(dir)\n }\n\n throw new Error('package.json not found in parent directories')\n}\n"],"mappings":"AAAA,SAASA,UAAU,QAAQ,IAAI;AAC/B,SAASC,OAAO,EAAEC,IAAI,QAAQ,MAAM;AACpC,SAASC,aAAa,QAAQ,KAAK;AAGnC,OAAOC,MAAM,MAAM,cAAc;AACjC,OAAOC,QAAQ,MAA4B,UAAU;AACrD,OAAOC,UAAU,MAAM,SAAS;AAEhC,SACEC,SAAS,EACTC,OAAO,EACPC,0BAA0B;AAI5B,OAAO,eAAeC,cAAcA,CAClCC,MAAc,EACdC,aAA4B,EAC5B;EACA,MAAMC,WAAW,GAAGC,eAAe,CAAC,CAAC;EACrC,MAAMC,iBAAiB,GAAGd,OAAO,CAC/BK,UAAU,CAACU,IAAI,CAAC,6BAA6B,CAC/C,CAAC;EAED,MAAMC,gBAAgB,GAAGf,IAAI,CAACW,WAAW,EAAEN,SAAS,CAAC;EAErD,MAAMW,KAAK,GAAG,CACZ,GAAGN,aAAa,CAACP,QAAQ,CAACa,KAAK,EAC/BD,gBAAgB,EAChBf,IAAI,CAACa,iBAAiB,EAAE,MAAM,CAAC,CAChC;EAED,MAAMJ,MAAM,CAACQ,QAAQ,CAAC;IACpBC,MAAM,EAAEhB,MAAM;IACdiB,OAAO,EAAE;MACPC,OAAO,EAAE;QACPC,IAAI,EAAE;UACJC,OAAO,EAAEA,CACPC,IAAY,EACZC,cAA4C,KACzC;YACH,MAAMC,QAAQ,GAAGtB,QAAQ,CAACmB,OAAO,CAACC,IAAI,EAAEC,cAAc,CAACE,WAAW,CAAC;YAEnE,OAAQpB,OAA2B,IAAK;cACtC,OAAOmB,QAAQ,CAACE,MAAM,CAACrB,OAAO,CAAC;YACjC,CAAC;UACH,CAAC;UACDsB,OAAO,EAAEA,CACPT,OAAkC,EAClCU,IAA2B,KACxB;YACH;YACA;YACA,MAAMH,WAAW,GAAGvB,QAAQ,CAAC2B,SAAS,CAACd,KAAK,CAAC;;YAE7C;YACA;YACAT,0BAA0B,CAACmB,WAAW,EAAEhB,aAAa,CAAC;YAEtDS,OAAO,CAACK,cAAc,CAACE,WAAW,GAAGA,WAAW;YAEhDG,IAAI,CAAC,CAAC;UACR;QACF;MACF,CAAC;MACDN,IAAI,EAAEP,KAAK;MACX;MACAV;IACF;EACF,CAAC,CAAC;AACJ;AAUA,OAAO,SAASM,eAAeA,CAAA,EAAG;EAChC,MAAMmB,eAAe,GAAG9B,aAAa,CAAC+B,MAAM,CAACC,IAAI,CAACC,GAAG,CAAC;EACtD,MAAMC,oBAAoB,GAAGpC,OAAO,CAACgC,eAAe,CAAC;EAErD,IAAIK,GAAG,GAAGD,oBAAoB;EAC9B,OAAOC,GAAG,KAAK,GAAG,EAAE;IAClB,IAAItC,UAAU,CAACE,IAAI,CAACoC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE;MACzC,OAAOA,GAAG;IACZ;IACAA,GAAG,GAAGrC,OAAO,CAACqC,GAAG,CAAC;EACpB;EAEA,MAAM,IAAIC,KAAK,CAAC,8CAA8C,CAAC;AACjE","ignoreList":[]}
@@ -1,4 +1,3 @@
1
- export { markdownToHtml as markdown } from "@defra/forms-model";
2
1
  export { highlight } from "~/src/server/plugins/nunjucks/filters/highlight.js";
3
2
  export { inspect } from "~/src/server/plugins/nunjucks/filters/inspect.js";
4
3
  export { evaluate } from "~/src/server/plugins/nunjucks/filters/evaluate.js";
@@ -1,4 +1,3 @@
1
- export { markdownToHtml as markdown } from '@defra/forms-model';
2
1
  export { highlight } from "./highlight.js";
3
2
  export { inspect } from "./inspect.js";
4
3
  export { evaluate } from "./evaluate.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["markdownToHtml","markdown","highlight","inspect","evaluate","answer","href","field","page"],"sources":["../../../../../src/server/plugins/nunjucks/filters/index.js"],"sourcesContent":["export { markdownToHtml as markdown } from '@defra/forms-model'\nexport { highlight } from '~/src/server/plugins/nunjucks/filters/highlight.js'\nexport { inspect } from '~/src/server/plugins/nunjucks/filters/inspect.js'\nexport { evaluate } from '~/src/server/plugins/nunjucks/filters/evaluate.js'\nexport { answer } from '~/src/server/plugins/nunjucks/filters/answer.js'\nexport { href } from '~/src/server/plugins/nunjucks/filters/href.js'\nexport { field } from '~/src/server/plugins/nunjucks/filters/field.js'\nexport { page } from '~/src/server/plugins/nunjucks/filters/page.js'\n"],"mappings":"AAAA,SAASA,cAAc,IAAIC,QAAQ,QAAQ,oBAAoB;AAC/D,SAASC,SAAS;AAClB,SAASC,OAAO;AAChB,SAASC,QAAQ;AACjB,SAASC,MAAM;AACf,SAASC,IAAI;AACb,SAASC,KAAK;AACd,SAASC,IAAI","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["highlight","inspect","evaluate","answer","href","field","page"],"sources":["../../../../../src/server/plugins/nunjucks/filters/index.js"],"sourcesContent":["export { highlight } from '~/src/server/plugins/nunjucks/filters/highlight.js'\nexport { inspect } from '~/src/server/plugins/nunjucks/filters/inspect.js'\nexport { evaluate } from '~/src/server/plugins/nunjucks/filters/evaluate.js'\nexport { answer } from '~/src/server/plugins/nunjucks/filters/answer.js'\nexport { href } from '~/src/server/plugins/nunjucks/filters/href.js'\nexport { field } from '~/src/server/plugins/nunjucks/filters/field.js'\nexport { page } from '~/src/server/plugins/nunjucks/filters/page.js'\n"],"mappings":"AAAA,SAASA,SAAS;AAClB,SAASC,OAAO;AAChB,SAASC,QAAQ;AACjB,SAASC,MAAM;AACf,SAASC,IAAI;AACb,SAASC,KAAK;AACd,SAASC,IAAI","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
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.497",
66
+ "@defra/forms-model": "^3.0.505",
67
67
  "@defra/hapi-tracing": "^1.0.0",
68
68
  "@elastic/ecs-pino-format": "^1.5.0",
69
69
  "@hapi/boom": "^10.0.1",
@@ -112,6 +112,14 @@
112
112
  "content": "Content",
113
113
  "options": {},
114
114
  "schema": {}
115
+ },
116
+ {
117
+ "type": "Markdown",
118
+ "name": "markdown",
119
+ "title": "Title",
120
+ "content": "### This is a H3 in markdown\n\n[An internal link](http://localhost:3009/fictional-page)\n\n[An external link](https://defra.gov.uk/fictional-page)",
121
+ "options": {},
122
+ "schema": {}
115
123
  }
116
124
  ]
117
125
  }
@@ -1,8 +1,10 @@
1
1
  import { type Server } from '@hapi/hapi'
2
2
  import { StatusCodes } from 'http-status-codes'
3
+ import { type Environment } from 'nunjucks'
3
4
 
4
5
  import { FORM_PREFIX } from '~/src/server/constants.js'
5
6
  import { createServer } from '~/src/server/index.js'
7
+ import { prepareNunjucksEnvironment } from '~/src/server/plugins/engine/index.js'
6
8
  import {
7
9
  getFormDefinition,
8
10
  getFormMetadata
@@ -12,6 +14,7 @@ import { getUploadStatus } from '~/src/server/plugins/engine/services/uploadServ
12
14
  import {
13
15
  FileStatus,
14
16
  UploadStatus,
17
+ type PluginOptions,
15
18
  type UploadStatusResponse
16
19
  } from '~/src/server/plugins/engine/types.js'
17
20
  import { FormStatus } from '~/src/server/routes/types.js'
@@ -552,3 +555,73 @@ describe('Upload status route', () => {
552
555
  expect(res.statusCode).toBe(StatusCodes.BAD_REQUEST)
553
556
  })
554
557
  })
558
+
559
+ describe('prepareEnvironment', () => {
560
+ const mockEnv = {
561
+ addFilter: jest.fn(),
562
+ addGlobal: jest.fn()
563
+ } as unknown as Environment
564
+
565
+ const mockPluginOptions: PluginOptions = {
566
+ baseUrl: 'http://localhost',
567
+ nunjucks: {
568
+ baseLayoutPath: '',
569
+ paths: []
570
+ },
571
+ viewContext: undefined
572
+ }
573
+
574
+ beforeEach(() => {
575
+ jest.clearAllMocks()
576
+ })
577
+
578
+ const expectedBaseFilters = [
579
+ 'highlight',
580
+ 'inspect',
581
+ 'evaluate',
582
+ 'answer',
583
+ 'href',
584
+ 'field',
585
+ 'page',
586
+ 'markdown'
587
+ ]
588
+
589
+ test('registers base filters', () => {
590
+ prepareNunjucksEnvironment(mockEnv, mockPluginOptions)
591
+
592
+ expect(mockEnv.addFilter).toHaveBeenCalledTimes(expectedBaseFilters.length)
593
+ expectedBaseFilters.forEach((name) => {
594
+ expect(mockEnv.addFilter).toHaveBeenCalledWith(name, expect.any(Function))
595
+ })
596
+ })
597
+
598
+ test('registers additional filters', () => {
599
+ prepareNunjucksEnvironment(mockEnv, {
600
+ ...mockPluginOptions,
601
+ filters: {
602
+ customFilter: (value) => value
603
+ }
604
+ })
605
+
606
+ expect(mockEnv.addFilter).toHaveBeenCalledWith(
607
+ 'customFilter',
608
+ expect.any(Function)
609
+ )
610
+ })
611
+
612
+ test('registers all globals', () => {
613
+ const expectedGlobals = [
614
+ 'checkComponentTemplates',
615
+ 'checkErrorTemplates',
616
+ 'evaluate',
617
+ 'govukRebrand'
618
+ ]
619
+
620
+ prepareNunjucksEnvironment(mockEnv, mockPluginOptions)
621
+
622
+ expect(mockEnv.addGlobal).toHaveBeenCalledTimes(expectedGlobals.length)
623
+ expectedGlobals.forEach((name) => {
624
+ expect(mockEnv.addGlobal).toHaveBeenCalledWith(name, expect.any(Function))
625
+ })
626
+ })
627
+ })
@@ -1,4 +1,4 @@
1
- import { isConditionalType, type ComponentDef } from '@defra/forms-model'
1
+ import { isConditionalRevealType, type ComponentDef } from '@defra/forms-model'
2
2
  import joi, {
3
3
  type ArraySchema,
4
4
  type BooleanSchema,
@@ -76,7 +76,7 @@ export class ComponentBase {
76
76
  viewModel.classes = options.classes
77
77
  }
78
78
 
79
- if ('condition' in options && isConditionalType(type)) {
79
+ if ('condition' in options && isConditionalRevealType(type)) {
80
80
  viewModel.condition = options.condition
81
81
  }
82
82
 
@@ -56,7 +56,8 @@ export const configureEnginePlugin = async ({
56
56
  },
57
57
  viewContext: devtoolContext,
58
58
  preparePageEventRequestOptions,
59
- onRequest
59
+ onRequest,
60
+ baseUrl: 'http://localhost:3009' // always runs locally
60
61
  }
61
62
  }
62
63
  }
@@ -1,8 +1,9 @@
1
+ import { markdownToHtml } from '@defra/forms-model'
1
2
  import { type Environment } from 'nunjucks'
2
3
 
3
4
  import { engine } from '~/src/server/plugins/engine/helpers.js'
4
5
  import { plugin } from '~/src/server/plugins/engine/plugin.js'
5
- import { type FilterFunction } from '~/src/server/plugins/engine/types.js'
6
+ import { type PluginOptions } from '~/src/server/plugins/engine/types.js'
6
7
  import {
7
8
  checkComponentTemplates,
8
9
  checkErrorTemplates,
@@ -26,19 +27,23 @@ export const PLUGIN_PATH = 'node_modules/@defra/forms-engine-plugin'
26
27
 
27
28
  export const prepareNunjucksEnvironment = function (
28
29
  env: Environment,
29
- additionalFilters?: Record<string, FilterFunction>
30
+ pluginOptions: PluginOptions
30
31
  ) {
31
32
  for (const [name, nunjucksFilter] of Object.entries(filters)) {
32
33
  env.addFilter(name, nunjucksFilter)
33
34
  }
34
35
 
36
+ env.addFilter('markdown', (text: string) =>
37
+ markdownToHtml(text, pluginOptions.baseUrl)
38
+ )
39
+
35
40
  for (const [name, nunjucksGlobal] of Object.entries(globals)) {
36
41
  env.addGlobal(name, nunjucksGlobal)
37
42
  }
38
43
 
39
44
  // Apply any additional filters to both the liquid and nunjucks engines
40
- if (additionalFilters) {
41
- for (const [name, filter] of Object.entries(additionalFilters)) {
45
+ if (pluginOptions.filters) {
46
+ for (const [name, filter] of Object.entries(pluginOptions.filters)) {
42
47
  env.addFilter(name, filter)
43
48
  engine.registerFilter(name, filter)
44
49
  }
@@ -1,5 +1,9 @@
1
1
  import Joi from 'joi'
2
2
 
3
+ import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
4
+
5
+ const logger = createLogger()
6
+
3
7
  const pluginRegistrationOptionsSchema = Joi.object({
4
8
  model: Joi.object().optional(),
5
9
  services: Joi.object().optional(),
@@ -13,7 +17,8 @@ const pluginRegistrationOptionsSchema = Joi.object({
13
17
  }).required(),
14
18
  viewContext: Joi.function().required(),
15
19
  preparePageEventRequestOptions: Joi.function().optional(),
16
- onRequest: Joi.function().optional()
20
+ onRequest: Joi.function().optional(),
21
+ baseUrl: Joi.string().uri().required()
17
22
  })
18
23
 
19
24
  /**
@@ -27,6 +32,9 @@ export function validatePluginOptions(options) {
27
32
  })
28
33
 
29
34
  if (result.error) {
35
+ logger.error(
36
+ `Missing required properties in plugin options: ${result.error.message}`
37
+ )
30
38
  throw new Error('Invalid plugin options', result.error)
31
39
  }
32
40
 
@@ -9,7 +9,8 @@ describe('validatePluginOptions', () => {
9
9
  },
10
10
  viewContext: () => {
11
11
  return { hello: 'world' }
12
- }
12
+ },
13
+ baseUrl: 'http://localhost:3009'
13
14
  }
14
15
 
15
16
  expect(validatePluginOptions(validOptions)).toEqual(validOptions)
@@ -19,6 +20,7 @@ describe('validatePluginOptions', () => {
19
20
  * tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test
20
21
  */
21
22
  it('fails if a required attribute is missing', () => {
23
+ // viewContext is missing
22
24
  const invalidOptions = {
23
25
  nunjucks: {
24
26
  baseLayoutPath: 'dxt-devtool-baselayout.html',
@@ -370,4 +370,5 @@ export interface PluginOptions {
370
370
  viewContext: PluginProperties['forms-engine-plugin']['viewContext']
371
371
  preparePageEventRequestOptions?: PreparePageEventRequestOptions
372
372
  onRequest?: OnRequestCallback
373
+ baseUrl: string // base URL of the application, protocol and hostname e.g. "https://myapp.com"
373
374
  }
@@ -56,7 +56,7 @@ export async function registerVision(
56
56
 
57
57
  // Applies custom filters and globals for nunjucks
58
58
  // that are required by the `forms-engine-plugin`
59
- prepareNunjucksEnvironment(environment, pluginOptions.filters)
59
+ prepareNunjucksEnvironment(environment, pluginOptions)
60
60
 
61
61
  options.compileOptions.environment = environment
62
62
 
@@ -1,4 +1,3 @@
1
- export { markdownToHtml as markdown } from '@defra/forms-model'
2
1
  export { highlight } from '~/src/server/plugins/nunjucks/filters/highlight.js'
3
2
  export { inspect } from '~/src/server/plugins/nunjucks/filters/inspect.js'
4
3
  export { evaluate } from '~/src/server/plugins/nunjucks/filters/evaluate.js'