@gov-cy/govcy-express-services 1.0.0-alpha.15 → 1.0.0-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  ![License](https://img.shields.io/github/license/gov-cy/govcy-express-services)
4
4
  [![Unit test](https://github.com/gov-cy/govcy-express-services/actions/workflows/unit-test.yml/badge.svg)](https://github.com/gov-cy/govcy-express-services/actions/workflows/unit-test.yml)
5
5
  [![tag-and-publish-on-version-change](https://github.com/gov-cy/govcy-express-services/actions/workflows/tag-and-publish-on-version-change.yml/badge.svg)](https://github.com/gov-cy/govcy-express-services/actions/workflows/tag-and-publish-on-version-change.yml)
6
+ ![coverage](coverage-badges.svg)
6
7
 
7
8
  > ⚠️ **Warning:**
8
9
  > This package is **under active development** and is not a finished product. It is intended for testing, acceptance, integration, and browser testing purposes only.
@@ -29,15 +30,15 @@ The APIs used for submission, temporary save and file uploads are not part of th
29
30
  - [✅ Best Practices](#-best-practices)
30
31
  - [📦 Full installation guide](#-full-installation-guide)
31
32
  - [🛠️ Usage](#%EF%B8%8F-usage)
32
- - [🧩 Dynamic services rendering](#-dynamic-services-rendering)
33
+ - [🧩 Dynamic services](#-dynamic-services)
33
34
  - [🛡️ Site eligibility checks](#%EF%B8%8F-site-eligibility-checks)
34
35
  - [📤 Site submissions](#-site-submissions)
35
36
  - [✅ Input validations](#-input-validations)
36
- - [ Conditional logic](#-conditional-logic)
37
+ - [🔀 Conditional logic](#-conditional-logic)
37
38
  - [💾 Temporary save feature](#-temporary-save-feature)
38
39
  - [🗃️ Files uploads feature](#%EF%B8%8F-files-uploads-feature)
39
40
  - [🛣️ Routes](#%EF%B8%8F-routes)
40
- - [👨‍💻 Enviromental variables](#-enviromental-variables)
41
+ - [👨‍💻 Environment variables](#-environment-variables)
41
42
  - [🔒 Security note](#-security-note)
42
43
  - [❓ Troubleshooting / FAQ](#-troubleshooting--faq)
43
44
  - [🙏 Credits](#-credits)
@@ -141,7 +142,7 @@ The CY Login tokens are used to also connect with the various APIs through [cyCo
141
142
 
142
143
  The CY Login settings are configured in the `secrets/.env` file.
143
144
 
144
- ### 🧩 Dynamic Services Rendering
145
+ ### 🧩 Dynamic Services
145
146
  Services are rendered dynamically using JSON templates stored in the `/data` folder. All the service configuration, pages, routes, and logic is stored in the JSON files. The service will load `data/:siteId.json` to get the form data when a user visits `/:siteId/:pageUrl`. Checkout the [express-service-shema.json](express-service-shema.json) and the example JSON structure of the **[test.json](data/test.json)** file for more details.
146
147
 
147
148
  Here is an example JSON config:
@@ -1174,14 +1175,14 @@ Accept: text/plain
1174
1175
  Content-Type: application/json
1175
1176
 
1176
1177
  {
1177
- "submission_username": "username",
1178
- "submission_email": "email@example.com",
1179
- "submission_data": "{\"index\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}}",
1180
- "submission_data_version": "1",
1181
- "print_friendly_data": "[{\"pageUrl\":\"index\",\"pageTitle\":{\"el\":\"Επιλογή Εγγάφου\",\"en\":\"Document selection\",\"tr\":\"\"},\"fields\":[{\"id\":\"certificate_select\",\"name\":\"certificate_select\",\"label\":{\"el\":\"Τι έγγραφα επιθυμείτε να εκδώσετε;\",\"en\":\"What documents do you wish to issue?\"},\"value\":[\"birth\",\"permanent_residence\"],\"valueLabel\":[{\"el\":\"Πιστοποιητικό γέννησης​\",\"en\":\"Birth certificate\",\"tr\":\"\"},{\"el\":\"Βεβαίωση μόνιμης διαμονής​\",\"en\":\"Certificate of permanent residence\",\"tr\":\"\"}]}]}]",
1182
- "renderer_data": "{\"element\":\"summaryList\",\"params\":{\"items\":[{\"key\":{\"el\":\"Επιλογή Εγγάφου\",\"en\":\"Document selection\",\"tr\":\"\"},\"value\":[{\"element\":\"summaryList\",\"params\":{\"items\":[{\"key\":{\"el\":\"Τι έγγραφα επιθυμείτε να εκδώσετε;\",\"en\":\"What documents do you wish to issue?\"},\"value\":[{\"element\":\"textElement\",\"params\":{\"text\":{\"en\":\"Birth certificate, Certificate of permanent residence\",\"el\":\"Birth certificate, Certificate of permanent residence\",\"tr\":\"Birth certificate, Certificate of permanent residence\"},\"type\":\"span\"}}]}]}}]}]}}",
1183
- "renderer_version": "1.14.3",
1184
- "design_systems_version": "3.2.0",
1178
+ "submissionUsername": "username",
1179
+ "submissionEmail": "email@example.com",
1180
+ "submissionData": "{\"index\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}}",
1181
+ "submissionDataVersion": "1",
1182
+ "printFriendlyData": "[{\"pageUrl\":\"index\",\"pageTitle\":{\"el\":\"Επιλογή Εγγάφου\",\"en\":\"Document selection\",\"tr\":\"\"},\"fields\":[{\"id\":\"certificate_select\",\"name\":\"certificate_select\",\"label\":{\"el\":\"Τι έγγραφα επιθυμείτε να εκδώσετε;\",\"en\":\"What documents do you wish to issue?\"},\"value\":[\"birth\",\"permanent_residence\"],\"valueLabel\":[{\"el\":\"Πιστοποιητικό γέννησης​\",\"en\":\"Birth certificate\",\"tr\":\"\"},{\"el\":\"Βεβαίωση μόνιμης διαμονής​\",\"en\":\"Certificate of permanent residence\",\"tr\":\"\"}]}]}]",
1183
+ "rendererData": "{\"element\":\"summaryList\",\"params\":{\"items\":[{\"key\":{\"el\":\"Επιλογή Εγγάφου\",\"en\":\"Document selection\",\"tr\":\"\"},\"value\":[{\"element\":\"summaryList\",\"params\":{\"items\":[{\"key\":{\"el\":\"Τι έγγραφα επιθυμείτε να εκδώσετε;\",\"en\":\"What documents do you wish to issue?\"},\"value\":[{\"element\":\"textElement\",\"params\":{\"text\":{\"en\":\"Birth certificate, Certificate of permanent residence\",\"el\":\"Birth certificate, Certificate of permanent residence\",\"tr\":\"Birth certificate, Certificate of permanent residence\"},\"type\":\"span\"}}]}]}}]}]}}",
1184
+ "rendererVersion": "1.14.3",
1185
+ "designSystemsVersion": "3.2.0",
1185
1186
  "service": "{\"id\":\"test\",\"title\":{\"el\":\"Υπηρεσία τεστ\",\"en\":\"Test service\",\"tr\":\"\"}}"
1186
1187
  }
1187
1188
  ```
@@ -1232,15 +1233,15 @@ HTTP/1.1 200 OK
1232
1233
  The data is collected from the form elements and the data layer and are sent via the submission API in the following format:
1233
1234
 
1234
1235
  ```json
1235
- "submissionData": { // Site level successful submission data
1236
- "submission_username" : "", // User's username
1237
- "submission_email" : "", // User's email
1238
- "submission_data": "{}", // Raw data as submitted by the user in each page
1239
- "submission_data_version": "",// The submission data version
1240
- "print_friendly_data": "[]", // Print friendly data
1241
- "renderer_data" :"{}", // Renderer data of the summary list
1242
- "renderer_version": "", // The renderer version
1243
- "design_systems_version": "", // The design systems version
1236
+ {
1237
+ "submissionUsername" : "", // User's username
1238
+ "submissionEmail" : "", // User's email
1239
+ "submissionData": "{}", // Raw data as submitted by the user in each page
1240
+ "submissionDataVersion": "",// The submission data version
1241
+ "printFriendlyData": "[]", // Print friendly data
1242
+ "rendererData" :"{}", // Renderer data of the summary list
1243
+ "rendererVersion": "", // The renderer version
1244
+ "designSystemsVersion": "", // The design systems version
1244
1245
  "service": "{}" // Service info
1245
1246
  }
1246
1247
  ```
@@ -1251,22 +1252,22 @@ The data is collected from the form elements and the data layer and are sent via
1251
1252
 
1252
1253
  > ℹ️ **Note:**
1253
1254
  >
1254
- > When sent to the API, the fields `submission_data`, `renderer_data`, `print_friendly_data`, and `service` are stringified using `JSON.stringify()`.
1255
+ > When sent to the API, the fields `submissionData`, `rendererData`, `printFriendlyData`, and `service` are stringified using `JSON.stringify()`.
1255
1256
  >
1256
1257
  > The sample below shows the structure **before** stringification for clarity.
1257
1258
 
1258
1259
  ```json
1259
1260
  {
1260
- "submission_username": "username", // User's username
1261
- "submission_email": "email@example.com", // User's email
1262
- "submission_data_version": "0.1", // Submission data version
1263
- "submission_data": { // Submission raw data. Object, will be stringified
1261
+ "submissionUsername": "username", // User's username
1262
+ "submissionEmail": "email@example.com", // User's email
1263
+ "submissionDataVersion": "0.1", // Submission data version
1264
+ "submissionData": { // Submission raw data. Object, will be stringified
1264
1265
  "index": { // Page level
1265
1266
  "id_select": ["id", "arc"], // field level. Could be string or array
1266
1267
  "id_number": "654654",
1267
1268
  "arc_number": "",
1268
1269
  "aka": "232323",
1269
- "evidenceAttachments": // File attachments contains an object with `fileId` and `sha256`
1270
+ "evidenceAttachment": // File attachments contains an object with `fileId` and `sha256`
1270
1271
  {
1271
1272
  "fileId": "1234567891234567890",
1272
1273
  "sha256": "123456789012345678901234567890123456789012345678901234567890123456"
@@ -1292,8 +1293,8 @@ The data is collected from the form elements and the data layer and are sent via
1292
1293
  "reason": "24324dssf"
1293
1294
  }
1294
1295
  },
1295
- "submission_data_version": "1", // Submission data version
1296
- "renderer_data": { // Summary list renderer data ready for rendering . Object, will be stringified
1296
+ "submissionDataVersion": "1", // Submission data version
1297
+ "rendererData": { // Summary list renderer data ready for rendering . Object, will be stringified
1297
1298
  "element": "summaryList",
1298
1299
  "params": {
1299
1300
  "items": [
@@ -1500,7 +1501,7 @@ The data is collected from the form elements and the data layer and are sent via
1500
1501
  ]
1501
1502
  }
1502
1503
  },
1503
- "print_friendly_data": [ // Print friendly data. Object, will be stringified
1504
+ "printFriendlyData": [ // Print friendly data. Object, will be stringified
1504
1505
  {
1505
1506
  "pageUrl": "index", // Page URL
1506
1507
  "pageTitle": { // Page title
@@ -1663,8 +1664,8 @@ The data is collected from the form elements and the data layer and are sent via
1663
1664
  ]
1664
1665
  }
1665
1666
  ],
1666
- "renderer_version": "1.14.1", // Renderer version
1667
- "design_systems_version": "3.1.0", // Design systems version
1667
+ "rendererVersion": "1.14.1", // Renderer version
1668
+ "designSystemsVersion": "3.1.0", // Design systems version
1668
1669
  "service": { // Service metadata. Object, will be stringified
1669
1670
  "id": "takeover",
1670
1671
  "title": {
@@ -1744,7 +1745,7 @@ Example:
1744
1745
  ]
1745
1746
  ```
1746
1747
 
1747
- ### Conditional logic
1748
+ ### 🔀 Conditional logic
1748
1749
 
1749
1750
  The project supports conditional logic on pages. Conditional logic is evaluated using a custom `govcyExpressions.mjs` module, which executes expressions in a safe and scoped context using `new Function`. Only safe data access through the `dataLayer` is allowed. The system uses expressions and session data from the service's [data layer](NOTES.md#data-layer) to decide if a page will be shown or not.
1750
1751
 
@@ -2014,7 +2015,7 @@ TEST_SUBMISSION_API_SERVICE_ID=123
2014
2015
  3. If not found, call the PUT endpoint to create a new temporary record.
2015
2016
  - **On every form POST**, after successful validation:
2016
2017
  - The `submissionPutAPIEndpoint` will fire-and-forget a `PUT` request to update the saved submission with the latest form data.
2017
- - The payload includes all required submission fields with `submission_data` JSON-stringified.
2018
+ - The payload includes all required submission fields with `submissionData` JSON-stringified.
2018
2019
 
2019
2020
  #### `submissionGetAPIEndpoint` `GET` API Request and Response
2020
2021
  This API is used to retrieve the saved submission data.
@@ -2115,7 +2116,7 @@ Accept: text/plain
2115
2116
  Content-Type: application/json
2116
2117
 
2117
2118
  {
2118
- "submission_data" : "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}"
2119
+ "submissionData" : "{\"index\":{\"formData\":{\"certificate_select\":[\"birth\",\"permanent_residence\"]}},\"data-entry-radios\":{\"formData\":{\"mobile_select\":\"other\",\"mobileTxt\":\"+35799484967\"}}}"
2119
2120
  }
2120
2121
  ```
2121
2122
 
@@ -2487,7 +2488,7 @@ The project uses express.js to serve the following routes:
2487
2488
  #### API routes:
2488
2489
  - **`/apis/:siteId/:pageUrl/upload`**: Uploads a file. Used from the client side JS.
2489
2490
 
2490
- ### 👨‍💻 Enviromental variables
2491
+ ### 👨‍💻 Environment variables
2491
2492
  The environment variables are defined in:
2492
2493
  - **Secret environment variables**: These are secret variables and MUSR NOT be saved in version control. The are saved locally in the `secrets/.env` file and they control the server configuration, authentication, integrations, and development behavior. These variables vary depending on the environment and are defined through the deployment prosses for `staging` and `production`.
2493
2494
  - **Non secret environment variables**: These are non secret enviromentat variables and can be saved in version control. These are stored in the root folder of the project:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gov-cy/govcy-express-services",
3
- "version": "1.0.0-alpha.15",
3
+ "version": "1.0.0-alpha.17",
4
4
  "description": "An Express-based system that dynamically renders services using @gov-cy/govcy-frontend-renderer and posts data to a submission API.",
5
5
  "author": "DMRID - DSF Team",
6
6
  "license": "MIT",
@@ -45,12 +45,13 @@
45
45
  "test:package": "mocha --recursive tests/package/**/*.test.mjs",
46
46
  "test:functional": "mocha --timeout 30000 --recursive tests/functional/**/*.test.mjs",
47
47
  "test:watch": "mocha --watch --timeout 60000 tests/**/*.test.mjs",
48
- "coverage": "c8 --reporter=html --reporter=text --reporter=lcov --reporter=cobertura npm test",
49
- "coverage:report": "c8 report"
48
+ "coverage": "c8 --reporter=html --reporter=text --reporter=lcov --reporter=json-summary npm test",
49
+ "coverage:report": "c8 report",
50
+ "coverage:badge": "coverage-badges --output ./coverage-badges.svg"
50
51
  },
51
52
  "dependencies": {
52
53
  "@gov-cy/dsf-email-templates": "^2.1.0",
53
- "@gov-cy/govcy-frontend-renderer": "^1.22.0",
54
+ "@gov-cy/govcy-frontend-renderer": "^1.22.1",
54
55
  "axios": "^1.9.0",
55
56
  "cookie-parser": "^1.4.7",
56
57
  "dotenv": "^16.3.1",
@@ -65,6 +66,7 @@
65
66
  "c8": "^10.1.3",
66
67
  "chai": "^5.2.0",
67
68
  "chai-http": "^5.1.1",
69
+ "coverage-badges-cli": "^2.2.0",
68
70
  "mocha": "^11.1.0",
69
71
  "mochawesome": "^7.1.3",
70
72
  "nodemon": "^3.0.2",
@@ -2,7 +2,7 @@ import * as client from 'openid-client';
2
2
  import { getEnvVariable } from '../utils/govcyEnvVariables.mjs';
3
3
  import { logger } from "../utils/govcyLogger.mjs";
4
4
 
5
-
5
+ /* c8 ignore start */
6
6
  // OpenID Configuration
7
7
  const issuerUrl = getEnvVariable('CYLOGIN_ISSUER_URL');
8
8
  const clientId = getEnvVariable('CYLOGIN_CLIENT_ID');
@@ -131,3 +131,4 @@ export function getLogoutUrl(id_token_hint = '') {
131
131
 
132
132
  // Export config if needed elsewhere
133
133
  export { config };
134
+ /* c8 ignore end */
@@ -10,6 +10,7 @@ import { handleMiddlewareError } from "../utils/govcyUtils.mjs";
10
10
  import { errorResponse } from "../utils/govcyApiResponse.mjs";
11
11
  import { isApiRequest } from '../utils/govcyApiDetection.mjs';
12
12
 
13
+ /* c8 ignore start */
13
14
  /**
14
15
  * Middleware to check if the user is authenticated. If not, redirect to the login page.
15
16
  *
@@ -136,4 +137,5 @@ export function handleLogout() {
136
137
  res.redirect(logoutUrl);
137
138
  });
138
139
  };
139
- }
140
+ }
141
+ /* c8 ignore end */
@@ -6,6 +6,7 @@ import { logger } from "../utils/govcyLogger.mjs";
6
6
  * Middleware function to render PDFs using the GovCy Frontend Renderer.
7
7
  * This function takes the processed page data and template, and generates the final PDF response.
8
8
  */
9
+ /* c8 ignore start */
9
10
  export function govcyPDFRender() {
10
11
  return async (req, res) => {
11
12
  try {
@@ -29,4 +30,5 @@ export function govcyPDFRender() {
29
30
  res.status(500).send('Unable to generate PDF at this time.');
30
31
  }
31
32
  };
32
- }
33
+ }
34
+ /* c8 ignore end */
@@ -118,7 +118,7 @@ export function govcyReviewPostHandler() {
118
118
 
119
119
  //-- Send email to user
120
120
  // Generate the email body
121
- let emailBody = generateSubmitEmail(service, submissionData.print_friendly_data, referenceNo, req);
121
+ let emailBody = generateSubmitEmail(service, submissionData.printFriendlyData, referenceNo, req);
122
122
  logger.debug("Email generated:", emailBody);
123
123
  // Send the email
124
124
  sendEmail(service.site.title[service.site.lang],emailBody,[dataLayer.getUser(req.session).email], "eMail").catch(err => {
@@ -78,7 +78,7 @@ export function govcySuccessPageHandler(isPDF = false) {
78
78
  }
79
79
  }
80
80
 
81
- let summaryList = submissionData.renderer_data;
81
+ let summaryList = submissionData.rendererData;
82
82
 
83
83
  let mainElements = [];
84
84
  // Add elements to the main section
@@ -135,9 +135,9 @@ function _uploadFileEventHandler(event) {
135
135
  "tr": "The selected file must be a JPG, JPEG, PNG or PDF"
136
136
  },
137
137
  "uploadFailed409": {
138
- "el": "Το επιλεγμένο αρχείο πρέπει να είναι μικρότερο από 5MB",
139
- "en": "The selected file must be smaller than 5MB",
140
- "tr": "The selected file must be smaller than 5MB"
138
+ "el": "Το επιλεγμένο αρχείο πρέπει να είναι μικρότερο από 4MB",
139
+ "en": "The selected file must be smaller than 4MB",
140
+ "tr": "The selected file must be smaller than 4MB"
141
141
  }
142
142
  };
143
143
 
@@ -4,5 +4,5 @@
4
4
  export const ALLOWED_FORM_ELEMENTS = ["textInput", "textArea", "select", "radios", "checkboxes", "datePicker", "dateInput","fileInput","fileView"];
5
5
  export const ALLOWED_FILE_MIME_TYPES = ['application/pdf', 'image/jpeg', 'image/png'];
6
6
  export const ALLOWED_FILE_EXTENSIONS = ['pdf', 'jpg', 'jpeg', 'png'];
7
- export const ALLOWED_FILE_SIZE_MB = 5; // Maximum file size in MB
7
+ export const ALLOWED_FILE_SIZE_MB = 4; // Maximum file size in MB
8
8
  export const ALLOWED_MULTER_FILE_SIZE_MB = 10; // Maximum file size in MB
@@ -32,7 +32,7 @@ export function prepareSubmissionData(req, siteId, service) {
32
32
  // }
33
33
  // }
34
34
 
35
- // ----- consistent data model for submission_data (CONFIG-BASED)
35
+ // ----- consistent data model for submissionData (CONFIG-BASED)
36
36
  const submissionData = {};
37
37
 
38
38
  // Loop through every page in the service definition
@@ -110,7 +110,7 @@ export function prepareSubmissionData(req, siteId, service) {
110
110
  }
111
111
  }
112
112
  logger.debug("Submission Data prepared:", submissionData);
113
- // ----- END config-based stable submission_data block
113
+ // ----- END config-based stable submissionData block
114
114
 
115
115
  // Get the print-friendly data from the session store
116
116
  const printFriendlyData = preparePrintFriendlyData(req, siteId, service);
@@ -119,14 +119,14 @@ export function prepareSubmissionData(req, siteId, service) {
119
119
  const reviewSummaryList = generateReviewSummary(printFriendlyData, req, siteId, false);
120
120
  // Prepare the submission data object
121
121
  return {
122
- submission_username: dataLayer.getUser(req.session).name,
123
- submission_email: dataLayer.getUser(req.session).email,
124
- submission_data: submissionData, // Raw data as submitted by the user in each page
125
- submission_data_version: service.site?.submission_data_version || "", // The submission data version
126
- print_friendly_data: printFriendlyData, // Print-friendly data
127
- renderer_data: reviewSummaryList, // Renderer data of the summary list
128
- renderer_version: service.site?.renderer_version || "", // The renderer version
129
- design_systems_version: service.site?.design_systems_version || "", // The design systems version
122
+ submissionUsername: dataLayer.getUser(req.session).name,
123
+ submissionEmail: dataLayer.getUser(req.session).email,
124
+ submissionData: submissionData, // Raw data as submitted by the user in each page
125
+ submissionDataVersion: service.site?.submission_data_version || "", // The submission data version
126
+ printFriendlyData: printFriendlyData, // Print-friendly data
127
+ rendererData: reviewSummaryList, // Renderer data of the summary list
128
+ rendererVersion: service.site?.renderer_version || "", // The renderer version
129
+ designSystemsVersion: service.site?.design_systems_version || "", // The design systems version
130
130
  service: { // Service info
131
131
  id: service.site.id, // Service ID
132
132
  title: service.site.title // Service title multilingual object
@@ -145,14 +145,14 @@ export function prepareSubmissionData(req, siteId, service) {
145
145
  export function prepareSubmissionDataAPI(data) {
146
146
 
147
147
  return {
148
- submission_username: String(data.submission_username ?? ""),
149
- submission_email: String(data.submission_email ?? ""),
150
- submission_data: JSON.stringify(data.submission_data ?? {}),
151
- submission_data_version: String(data.submission_data_version ?? ""),
152
- print_friendly_data: JSON.stringify(data.print_friendly_data ?? []),
153
- renderer_data: JSON.stringify(data.renderer_data ?? {}),
154
- renderer_version: String(data.renderer_version ?? ""),
155
- design_systems_version: String(data.design_systems_version ?? ""),
148
+ submissionUsername: String(data.submissionUsername ?? ""),
149
+ submissionEmail: String(data.submissionEmail ?? ""),
150
+ submissionData: JSON.stringify(data.submissionData ?? {}),
151
+ submissionDataVersion: String(data.submissionDataVersion ?? ""),
152
+ printFriendlyData: JSON.stringify(data.printFriendlyData ?? []),
153
+ rendererData: JSON.stringify(data.rendererData ?? {}),
154
+ rendererVersion: String(data.rendererVersion ?? ""),
155
+ designSystemsVersion: String(data.designSystemsVersion ?? ""),
156
156
  service: JSON.stringify(data.service ?? {})
157
157
  };
158
158
  }