@defra/forms-engine-plugin 1.0.4 → 1.0.6

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 (45) 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.d.ts +1 -1
  5. package/.server/server/plugins/engine/configureEnginePlugin.js +5 -2
  6. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  7. package/.server/server/plugins/engine/index.d.ts +2 -2
  8. package/.server/server/plugins/engine/index.js +5 -3
  9. package/.server/server/plugins/engine/index.js.map +1 -1
  10. package/.server/server/plugins/engine/options.js +6 -1
  11. package/.server/server/plugins/engine/options.js.map +1 -1
  12. package/.server/server/plugins/engine/options.test.js +3 -1
  13. package/.server/server/plugins/engine/options.test.js.map +1 -1
  14. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +2 -2
  15. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  16. package/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +1 -1
  17. package/.server/server/plugins/engine/routes/index.js +7 -1
  18. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  19. package/.server/server/plugins/engine/types.d.ts +7 -4
  20. package/.server/server/plugins/engine/types.js.map +1 -1
  21. package/.server/server/plugins/engine/vision.js +1 -1
  22. package/.server/server/plugins/engine/vision.js.map +1 -1
  23. package/.server/server/plugins/nunjucks/filters/index.d.ts +0 -1
  24. package/.server/server/plugins/nunjucks/filters/index.js +0 -1
  25. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
  26. package/.server/server/schemas/index.d.ts +2 -2
  27. package/.server/server/schemas/index.js.map +1 -1
  28. package/.server/server/types.d.ts +2 -1
  29. package/.server/server/types.js.map +1 -1
  30. package/package.json +3 -2
  31. package/src/server/forms/components.json +8 -0
  32. package/src/server/index.test.ts +73 -0
  33. package/src/server/plugins/engine/components/ComponentBase.ts +2 -2
  34. package/src/server/plugins/engine/configureEnginePlugin.ts +5 -2
  35. package/src/server/plugins/engine/index.ts +9 -4
  36. package/src/server/plugins/engine/options.js +10 -1
  37. package/src/server/plugins/engine/options.test.js +3 -1
  38. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +2 -2
  39. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +3 -3
  40. package/src/server/plugins/engine/routes/index.ts +6 -1
  41. package/src/server/plugins/engine/types.ts +14 -2
  42. package/src/server/plugins/engine/vision.ts +1 -1
  43. package/src/server/plugins/nunjucks/filters/index.js +0 -1
  44. package/src/server/schemas/index.ts +2 -2
  45. package/src/server/types.ts +5 -1
@@ -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 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 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 FormParams {\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 = FormParams & 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 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}\n"],"mappings":"AA6BA;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":[]}
@@ -1,5 +1,5 @@
1
1
  import Joi from 'joi';
2
- import { type FormParams } from '~/src/server/plugins/engine/types.js';
2
+ import { type FormPayloadParams } from '~/src/server/plugins/engine/types.js';
3
3
  import { FormAction, FormStatus } from '~/src/server/routes/types.js';
4
4
  export declare const stateSchema: Joi.StringSchema<FormStatus>;
5
5
  export declare const actionSchema: Joi.StringSchema<FormAction>;
@@ -7,4 +7,4 @@ export declare const pathSchema: Joi.StringSchema<string>;
7
7
  export declare const itemIdSchema: Joi.StringSchema<string>;
8
8
  export declare const crumbSchema: Joi.StringSchema<string>;
9
9
  export declare const confirmSchema: Joi.BooleanSchema<boolean>;
10
- export declare const paramsSchema: Joi.ObjectSchema<FormParams>;
10
+ export declare const paramsSchema: Joi.ObjectSchema<FormPayloadParams>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Joi","FormAction","FormStatus","stateSchema","string","valid","Draft","Live","required","actionSchema","Continue","Validate","Delete","AddAnother","Send","default","optional","pathSchema","itemIdSchema","uuid","crumbSchema","allow","confirmSchema","boolean","empty","paramsSchema","object","keys","action","confirm","crumb","itemId"],"sources":["../../../src/server/schemas/index.ts"],"sourcesContent":["import Joi from 'joi'\n\nimport { type FormParams } from '~/src/server/plugins/engine/types.js'\nimport { FormAction, FormStatus } from '~/src/server/routes/types.js'\n\nexport const stateSchema = Joi.string<FormStatus>()\n .valid(FormStatus.Draft, FormStatus.Live)\n .required()\n\nexport const actionSchema = Joi.string<FormAction>()\n .valid(\n FormAction.Continue,\n FormAction.Validate,\n FormAction.Delete,\n FormAction.AddAnother,\n FormAction.Send\n )\n .default(FormAction.Validate)\n .optional()\n\nexport const pathSchema = Joi.string().required()\nexport const itemIdSchema = Joi.string().uuid().required()\nexport const crumbSchema = Joi.string().optional().allow('')\nexport const confirmSchema = Joi.boolean().empty(false)\n\nexport const paramsSchema = Joi.object<FormParams>()\n .keys({\n action: actionSchema,\n confirm: confirmSchema,\n crumb: crumbSchema,\n itemId: itemIdSchema.optional()\n })\n .default({})\n .optional()\n"],"mappings":"AAAA,OAAOA,GAAG,MAAM,KAAK;AAGrB,SAASC,UAAU,EAAEC,UAAU;AAE/B,OAAO,MAAMC,WAAW,GAAGH,GAAG,CAACI,MAAM,CAAa,CAAC,CAChDC,KAAK,CAACH,UAAU,CAACI,KAAK,EAAEJ,UAAU,CAACK,IAAI,CAAC,CACxCC,QAAQ,CAAC,CAAC;AAEb,OAAO,MAAMC,YAAY,GAAGT,GAAG,CAACI,MAAM,CAAa,CAAC,CACjDC,KAAK,CACJJ,UAAU,CAACS,QAAQ,EACnBT,UAAU,CAACU,QAAQ,EACnBV,UAAU,CAACW,MAAM,EACjBX,UAAU,CAACY,UAAU,EACrBZ,UAAU,CAACa,IACb,CAAC,CACAC,OAAO,CAACd,UAAU,CAACU,QAAQ,CAAC,CAC5BK,QAAQ,CAAC,CAAC;AAEb,OAAO,MAAMC,UAAU,GAAGjB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACI,QAAQ,CAAC,CAAC;AACjD,OAAO,MAAMU,YAAY,GAAGlB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACe,IAAI,CAAC,CAAC,CAACX,QAAQ,CAAC,CAAC;AAC1D,OAAO,MAAMY,WAAW,GAAGpB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACY,QAAQ,CAAC,CAAC,CAACK,KAAK,CAAC,EAAE,CAAC;AAC5D,OAAO,MAAMC,aAAa,GAAGtB,GAAG,CAACuB,OAAO,CAAC,CAAC,CAACC,KAAK,CAAC,KAAK,CAAC;AAEvD,OAAO,MAAMC,YAAY,GAAGzB,GAAG,CAAC0B,MAAM,CAAa,CAAC,CACjDC,IAAI,CAAC;EACJC,MAAM,EAAEnB,YAAY;EACpBoB,OAAO,EAAEP,aAAa;EACtBQ,KAAK,EAAEV,WAAW;EAClBW,MAAM,EAAEb,YAAY,CAACF,QAAQ,CAAC;AAChC,CAAC,CAAC,CACDD,OAAO,CAAC,CAAC,CAAC,CAAC,CACXC,QAAQ,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["Joi","FormAction","FormStatus","stateSchema","string","valid","Draft","Live","required","actionSchema","Continue","Validate","Delete","AddAnother","Send","default","optional","pathSchema","itemIdSchema","uuid","crumbSchema","allow","confirmSchema","boolean","empty","paramsSchema","object","keys","action","confirm","crumb","itemId"],"sources":["../../../src/server/schemas/index.ts"],"sourcesContent":["import Joi from 'joi'\n\nimport { type FormPayloadParams } from '~/src/server/plugins/engine/types.js'\nimport { FormAction, FormStatus } from '~/src/server/routes/types.js'\n\nexport const stateSchema = Joi.string<FormStatus>()\n .valid(FormStatus.Draft, FormStatus.Live)\n .required()\n\nexport const actionSchema = Joi.string<FormAction>()\n .valid(\n FormAction.Continue,\n FormAction.Validate,\n FormAction.Delete,\n FormAction.AddAnother,\n FormAction.Send\n )\n .default(FormAction.Validate)\n .optional()\n\nexport const pathSchema = Joi.string().required()\nexport const itemIdSchema = Joi.string().uuid().required()\nexport const crumbSchema = Joi.string().optional().allow('')\nexport const confirmSchema = Joi.boolean().empty(false)\n\nexport const paramsSchema = Joi.object<FormPayloadParams>()\n .keys({\n action: actionSchema,\n confirm: confirmSchema,\n crumb: crumbSchema,\n itemId: itemIdSchema.optional()\n })\n .default({})\n .optional()\n"],"mappings":"AAAA,OAAOA,GAAG,MAAM,KAAK;AAGrB,SAASC,UAAU,EAAEC,UAAU;AAE/B,OAAO,MAAMC,WAAW,GAAGH,GAAG,CAACI,MAAM,CAAa,CAAC,CAChDC,KAAK,CAACH,UAAU,CAACI,KAAK,EAAEJ,UAAU,CAACK,IAAI,CAAC,CACxCC,QAAQ,CAAC,CAAC;AAEb,OAAO,MAAMC,YAAY,GAAGT,GAAG,CAACI,MAAM,CAAa,CAAC,CACjDC,KAAK,CACJJ,UAAU,CAACS,QAAQ,EACnBT,UAAU,CAACU,QAAQ,EACnBV,UAAU,CAACW,MAAM,EACjBX,UAAU,CAACY,UAAU,EACrBZ,UAAU,CAACa,IACb,CAAC,CACAC,OAAO,CAACd,UAAU,CAACU,QAAQ,CAAC,CAC5BK,QAAQ,CAAC,CAAC;AAEb,OAAO,MAAMC,UAAU,GAAGjB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACI,QAAQ,CAAC,CAAC;AACjD,OAAO,MAAMU,YAAY,GAAGlB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACe,IAAI,CAAC,CAAC,CAACX,QAAQ,CAAC,CAAC;AAC1D,OAAO,MAAMY,WAAW,GAAGpB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACY,QAAQ,CAAC,CAAC,CAACK,KAAK,CAAC,EAAE,CAAC;AAC5D,OAAO,MAAMC,aAAa,GAAGtB,GAAG,CAACuB,OAAO,CAAC,CAAC,CAACC,KAAK,CAAC,KAAK,CAAC;AAEvD,OAAO,MAAMC,YAAY,GAAGzB,GAAG,CAAC0B,MAAM,CAAoB,CAAC,CACxDC,IAAI,CAAC;EACJC,MAAM,EAAEnB,YAAY;EACpBoB,OAAO,EAAEP,aAAa;EACtBQ,KAAK,EAAEV,WAAW;EAClBW,MAAM,EAAEb,YAAY,CAACF,QAAQ,CAAC;AAChC,CAAC,CAAC,CACDD,OAAO,CAAC,CAAC,CAAC,CAAC,CACXC,QAAQ,CAAC,CAAC","ignoreList":[]}
@@ -2,7 +2,7 @@ import { type FormDefinition, type FormMetadata, type SubmitPayload, type Submit
2
2
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
3
3
  import { type DetailItem } from '~/src/server/plugins/engine/models/types.js';
4
4
  import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js';
5
- import { type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js';
5
+ import { type OnRequestCallback, type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js';
6
6
  import { type FormRequestPayload, type FormStatus } from '~/src/server/routes/types.js';
7
7
  export interface FormsService {
8
8
  getFormMetadata: (slug: string) => Promise<FormMetadata>;
@@ -27,6 +27,7 @@ export interface RouteConfig {
27
27
  services?: Services;
28
28
  controllers?: Record<string, typeof PageController>;
29
29
  preparePageEventRequestOptions?: PreparePageEventRequestOptions;
30
+ onRequest?: OnRequestCallback;
30
31
  }
31
32
  export interface OutputService {
32
33
  submit: (request: FormRequestPayload, model: FormModel, emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n}\n\nexport interface OutputService {\n submit: (\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type OnRequestCallback,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n}\n\nexport interface OutputService {\n submit: (\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
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",
@@ -114,6 +114,7 @@
114
114
  "@babel/plugin-syntax-import-attributes": "^7.27.1",
115
115
  "@babel/preset-env": "^7.28.0",
116
116
  "@babel/preset-typescript": "^7.27.1",
117
+ "@hapi/basic": "^7.0.2",
117
118
  "@testing-library/dom": "^10.4.0",
118
119
  "@testing-library/jest-dom": "^6.6.3",
119
120
  "@types/atob": "^2.1.4",
@@ -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
 
@@ -17,7 +17,8 @@ export const configureEnginePlugin = async ({
17
17
  formFilePath,
18
18
  services,
19
19
  controllers,
20
- preparePageEventRequestOptions
20
+ preparePageEventRequestOptions,
21
+ onRequest
21
22
  }: RouteConfig = {}): Promise<{
22
23
  plugin: typeof plugin
23
24
  options: PluginOptions
@@ -54,7 +55,9 @@ export const configureEnginePlugin = async ({
54
55
  paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner
55
56
  },
56
57
  viewContext: devtoolContext,
57
- preparePageEventRequestOptions
58
+ preparePageEventRequestOptions,
59
+ onRequest,
60
+ baseUrl: 'http://localhost:3009' // always runs locally
58
61
  }
59
62
  }
60
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(),
@@ -12,7 +16,9 @@ const pluginRegistrationOptionsSchema = Joi.object({
12
16
  paths: Joi.array().items(Joi.string()).required()
13
17
  }).required(),
14
18
  viewContext: Joi.function().required(),
15
- preparePageEventRequestOptions: Joi.function().optional()
19
+ preparePageEventRequestOptions: Joi.function().optional(),
20
+ onRequest: Joi.function().optional(),
21
+ baseUrl: Joi.string().uri().required()
16
22
  })
17
23
 
18
24
  /**
@@ -26,6 +32,9 @@ export function validatePluginOptions(options) {
26
32
  })
27
33
 
28
34
  if (result.error) {
35
+ logger.error(
36
+ `Missing required properties in plugin options: ${result.error.message}`
37
+ )
29
38
  throw new Error('Invalid plugin options', result.error)
30
39
  }
31
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',
@@ -22,7 +22,7 @@ import {
22
22
  type FeaturedFormPageViewModel,
23
23
  type FormContext,
24
24
  type FormContextRequest,
25
- type FormParams,
25
+ type FormPayloadParams,
26
26
  type FormSubmissionState,
27
27
  type UploadStatusFileResponse,
28
28
  type UploadStatusResponse
@@ -1063,7 +1063,7 @@ describe('FileUploadPageController', () => {
1063
1063
 
1064
1064
  jest
1065
1065
  .spyOn(controller, 'getFormParams')
1066
- .mockReturnValue({ confirm: false } as unknown as FormParams)
1066
+ .mockReturnValue({ confirm: false } as unknown as FormPayloadParams)
1067
1067
 
1068
1068
  const proceedSpy = jest
1069
1069
  .spyOn(controller, 'proceed')
@@ -26,8 +26,8 @@ import {
26
26
  type FormContext,
27
27
  type FormContextRequest,
28
28
  type FormPageViewModel,
29
- type FormParams,
30
29
  type FormPayload,
30
+ type FormPayloadParams,
31
31
  type FormState,
32
32
  type FormStateValue,
33
33
  type FormSubmissionState
@@ -268,7 +268,7 @@ export class QuestionPageController extends PageController {
268
268
  /**
269
269
  * Gets form params (from payload) for this page only
270
270
  */
271
- getFormParams(request?: FormContextRequest): FormParams {
271
+ getFormParams(request?: FormContextRequest): FormPayloadParams {
272
272
  const { payload } = request ?? {}
273
273
 
274
274
  const result = paramsSchema.validate(payload, {
@@ -276,7 +276,7 @@ export class QuestionPageController extends PageController {
276
276
  stripUnknown: true
277
277
  })
278
278
 
279
- return result.value as FormParams
279
+ return result.value as FormPayloadParams
280
280
  }
281
281
 
282
282
  getStateFromValidForm(
@@ -88,7 +88,7 @@ export function makeLoadFormPreHandler(server: Server, options: PluginOptions) {
88
88
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong
89
89
  const prefix = server.realm.modifiers.route.prefix ?? ''
90
90
 
91
- const { services = defaultServices, controllers } = options
91
+ const { services = defaultServices, controllers, onRequest } = options
92
92
 
93
93
  const { formsService } = services
94
94
 
@@ -166,6 +166,11 @@ export function makeLoadFormPreHandler(server: Server, options: PluginOptions) {
166
166
  server.app.models.set(key, item)
167
167
  }
168
168
 
169
+ // Call the onRequest callback if it has been supplied
170
+ if (onRequest) {
171
+ onRequest(request, params, item.model.def, metadata)
172
+ }
173
+
169
174
  // Assign the model to the request data
170
175
  // for use in the downstream handler
171
176
  request.app.model = item.model
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  type ComponentDef,
3
3
  type Event,
4
+ type FormDefinition,
5
+ type FormMetadata,
4
6
  type Item,
5
7
  type List,
6
8
  type Page
@@ -21,6 +23,7 @@ import { type PageControllerClass } from '~/src/server/plugins/engine/pageContro
21
23
  import { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'
22
24
  import {
23
25
  type FormAction,
26
+ type FormParams,
24
27
  type FormRequest,
25
28
  type FormRequestPayload
26
29
  } from '~/src/server/routes/types.js'
@@ -72,7 +75,7 @@ export interface FormSubmissionError
72
75
  text: string // e.g: 'Date field must be a real date'
73
76
  }
74
77
 
75
- export interface FormParams {
78
+ export interface FormPayloadParams {
76
79
  action?: FormAction
77
80
  confirm?: true
78
81
  crumb?: string
@@ -83,7 +86,7 @@ export interface FormParams {
83
86
  * Form POST for question pages
84
87
  * (after Joi has converted value types)
85
88
  */
86
- export type FormPayload = FormParams & Partial<Record<string, FormValue>>
89
+ export type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>
87
90
 
88
91
  export type FormValue =
89
92
  | Item['value']
@@ -342,6 +345,13 @@ export type PreparePageEventRequestOptions = (
342
345
  context: FormContext
343
346
  ) => void
344
347
 
348
+ export type OnRequestCallback = (
349
+ request: FormRequest | FormRequestPayload,
350
+ params: FormParams,
351
+ definition: FormDefinition,
352
+ metadata: FormMetadata
353
+ ) => void
354
+
345
355
  export interface PluginOptions {
346
356
  model?: FormModel
347
357
  services?: Services
@@ -359,4 +369,6 @@ export interface PluginOptions {
359
369
  }
360
370
  viewContext: PluginProperties['forms-engine-plugin']['viewContext']
361
371
  preparePageEventRequestOptions?: PreparePageEventRequestOptions
372
+ onRequest?: OnRequestCallback
373
+ baseUrl: string // base URL of the application, protocol and hostname e.g. "https://myapp.com"
362
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'
@@ -1,6 +1,6 @@
1
1
  import Joi from 'joi'
2
2
 
3
- import { type FormParams } from '~/src/server/plugins/engine/types.js'
3
+ import { type FormPayloadParams } from '~/src/server/plugins/engine/types.js'
4
4
  import { FormAction, FormStatus } from '~/src/server/routes/types.js'
5
5
 
6
6
  export const stateSchema = Joi.string<FormStatus>()
@@ -23,7 +23,7 @@ export const itemIdSchema = Joi.string().uuid().required()
23
23
  export const crumbSchema = Joi.string().optional().allow('')
24
24
  export const confirmSchema = Joi.boolean().empty(false)
25
25
 
26
- export const paramsSchema = Joi.object<FormParams>()
26
+ export const paramsSchema = Joi.object<FormPayloadParams>()
27
27
  .keys({
28
28
  action: actionSchema,
29
29
  confirm: confirmSchema,
@@ -8,7 +8,10 @@ import {
8
8
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
9
9
  import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
10
10
  import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
11
- import { type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js'
11
+ import {
12
+ type OnRequestCallback,
13
+ type PreparePageEventRequestOptions
14
+ } from '~/src/server/plugins/engine/types.js'
12
15
  import {
13
16
  type FormRequestPayload,
14
17
  type FormStatus
@@ -43,6 +46,7 @@ export interface RouteConfig {
43
46
  services?: Services
44
47
  controllers?: Record<string, typeof PageController>
45
48
  preparePageEventRequestOptions?: PreparePageEventRequestOptions
49
+ onRequest?: OnRequestCallback
46
50
  }
47
51
 
48
52
  export interface OutputService {