diff --git a/interfaces/IBF-dashboard/src/assets/i18n/en.json b/interfaces/IBF-dashboard/src/assets/i18n/en.json index 86ff0dd42..d4bf6861c 100644 --- a/interfaces/IBF-dashboard/src/assets/i18n/en.json +++ b/interfaces/IBF-dashboard/src/assets/i18n/en.json @@ -173,7 +173,7 @@ }, "alertLevel": { "expected": "expected on", - "expected-trigger": "expected to reach threshold on", + "expected-trigger": "expected to trigger on", "exposed-areas": "Exposed {{adminAreaLabelPlural}}:", "no-data": "No population data available" } diff --git a/services/API-service/package-lock.json b/services/API-service/package-lock.json index bc79f9198..e23f7b3af 100644 --- a/services/API-service/package-lock.json +++ b/services/API-service/package-lock.json @@ -24,6 +24,7 @@ "jsonwebtoken": "^8.1.1", "juice": "^10.0.0", "mailchimp-api-v3": "^1.15.0", + "mjml": "^4.15.3", "mysql": "^2.15.0", "passport": "^0.4.1", "passport-jwt": "^4.0.0", @@ -719,6 +720,95 @@ "node": ">=10" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@jest/console": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", @@ -1168,15 +1258,6 @@ "node": ">=6" } }, - "node_modules/@jest/reporters/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@jest/reporters/node_modules/supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -1203,15 +1284,6 @@ "node": ">= 6" } }, - "node_modules/@jest/source-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@jest/test-result": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", @@ -1451,15 +1523,6 @@ "node": ">=6" } }, - "node_modules/@jest/transform/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@jest/transform/node_modules/to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -2105,6 +2168,20 @@ "node": ">=8" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -3065,7 +3142,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -3460,7 +3536,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } @@ -3622,7 +3697,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -3806,6 +3880,15 @@ "node": ">=6" } }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -3952,7 +4035,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dev": true, "dependencies": { "anymatch": "~3.1.1", "braces": "~3.0.2", @@ -3973,7 +4055,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -4034,6 +4115,17 @@ "validator": "^13.7.0" } }, + "node_modules/clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, "node_modules/cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -4280,6 +4372,15 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "node_modules/configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -4448,7 +4549,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -4462,7 +4562,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -4474,7 +4573,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -4483,7 +4581,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -4761,6 +4858,11 @@ "node": ">=0.10.0" } }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -4898,6 +5000,11 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -4915,6 +5022,64 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/editorconfig/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5074,16 +5239,6 @@ "source-map": "~0.6.1" } }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint": { "version": "8.47.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz", @@ -6062,7 +6217,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -6155,6 +6309,32 @@ "node": ">=0.10.0" } }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -6242,7 +6422,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, "optional": true, "os": [ "darwin" @@ -6560,6 +6739,14 @@ "node": ">=8" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -6589,6 +6776,31 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-minifier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", + "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", + "dependencies": { + "camel-case": "^3.0.0", + "clean-css": "^4.2.1", + "commander": "^2.19.0", + "he": "^1.2.0", + "param-case": "^2.1.1", + "relateurl": "^0.2.7", + "uglify-js": "^3.5.1" + }, + "bin": { + "html-minifier": "cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/html-minifier/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/htmlparser2": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", @@ -6791,8 +7003,7 @@ "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/invariant": { "version": "2.2.4", @@ -6845,7 +7056,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -6958,7 +7168,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6984,7 +7193,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -7027,7 +7235,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -7119,8 +7326,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "node_modules/isobject": { "version": "3.0.1", @@ -7235,15 +7441,6 @@ "rimraf": "bin.js" } }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-reports": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", @@ -7264,6 +7461,20 @@ "node": ">=6" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.8.7", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", @@ -9351,15 +9562,6 @@ "node": ">=6" } }, - "node_modules/jest-util/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/jest-validate": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz", @@ -9705,47 +9907,146 @@ "decamelize": "^1.2.0" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/js-beautify": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", "dependencies": { - "argparse": "^2.0.1" + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" }, "bin": { - "js-yaml": "bin/js-yaml.js" + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "node_modules/js-beautify/node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, - "node_modules/jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "dev": true, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", + "balanced-match": "^1.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/js-beautify/node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "node_modules/jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "dependencies": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", "pn": "^1.1.0", "request": "^2.87.0", "request-promise-native": "^1.0.5", @@ -10104,6 +10405,11 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + }, "node_modules/lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -10373,6 +10679,447 @@ "node": ">=0.10.0" } }, + "node_modules/mjml": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.15.3.tgz", + "integrity": "sha512-bW2WpJxm6HS+S3Yu6tq1DUPFoTxU9sPviUSmnL7Ua+oVO3WA5ILFWqvujUlz+oeuM+HCwEyMiP5xvKNPENVjYA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "mjml-cli": "4.15.3", + "mjml-core": "4.15.3", + "mjml-migrate": "4.15.3", + "mjml-preset-core": "4.15.3", + "mjml-validator": "4.15.3" + }, + "bin": { + "mjml": "bin/mjml" + } + }, + "node_modules/mjml-accordion": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.15.3.tgz", + "integrity": "sha512-LPNVSj1LyUVYT9G1gWwSw3GSuDzDsQCu0tPB2uDsq4VesYNnU6v3iLCQidMiR6azmIt13OEozG700ygAUuA6Ng==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-body": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.15.3.tgz", + "integrity": "sha512-7pfUOVPtmb0wC+oUOn4xBsAw4eT5DyD6xqaxj/kssu6RrFXOXgJaVnDPAI9AzIvXJ/5as9QrqRGYAddehwWpHQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-button": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.15.3.tgz", + "integrity": "sha512-79qwn9AgdGjJR1vLnrcm2rq2AsAZkKC5JPwffTMG+Nja6zGYpTDZFZ56ekHWr/r1b5WxkukcPj2PdevUug8c+Q==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-carousel": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.15.3.tgz", + "integrity": "sha512-3ju6I4l7uUhPRrJfN3yK9AMsfHvrYbRkcJ1GRphFHzUj37B2J6qJOQUpzA547Y4aeh69TSb7HFVf1t12ejQxVw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-cli": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.15.3.tgz", + "integrity": "sha512-+V2TDw3tXUVEptFvLSerz125C2ogYl8klIBRY1m5BHd4JvGVf3yhx8N3PngByCzA6PGcv/eydGQN+wy34SHf0Q==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "chokidar": "^3.0.0", + "glob": "^10.3.10", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "lodash": "^4.17.21", + "minimatch": "^9.0.3", + "mjml-core": "4.15.3", + "mjml-migrate": "4.15.3", + "mjml-parser-xml": "4.15.3", + "mjml-validator": "4.15.3", + "yargs": "^17.7.2" + }, + "bin": { + "mjml-cli": "bin/mjml" + } + }, + "node_modules/mjml-cli/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mjml-cli/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mjml-cli/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mjml-cli/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mjml-column": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.15.3.tgz", + "integrity": "sha512-hYdEFdJGHPbZJSEysykrevEbB07yhJGSwfDZEYDSbhQQFjV2tXrEgYcFD5EneMaowjb55e3divSJxU4c5q4Qgw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-core": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.15.3.tgz", + "integrity": "sha512-Dmwk+2cgSD9L9GmTbEUNd8QxkTZtW9P7FN/ROZW/fGZD6Hq6/4TB0zEspg2Ow9eYjZXO2ofOJ3PaQEEShKV0kQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "cheerio": "1.0.0-rc.12", + "detect-node": "^2.0.4", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "juice": "^10.0.0", + "lodash": "^4.17.21", + "mjml-migrate": "4.15.3", + "mjml-parser-xml": "4.15.3", + "mjml-validator": "4.15.3" + } + }, + "node_modules/mjml-divider": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.15.3.tgz", + "integrity": "sha512-vh27LQ9FG/01y0b9ntfqm+GT5AjJnDSDY9hilss2ixIUh0FemvfGRfsGVeV5UBVPBKK7Ffhvfqc7Rciob9Spzw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-group": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.15.3.tgz", + "integrity": "sha512-HSu/rKnGZVKFq3ciT46vi1EOy+9mkB0HewO4+P6dP/Y0UerWkN6S3UK11Cxsj0cAp0vFwkPDCdOeEzRdpFEkzA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.15.3.tgz", + "integrity": "sha512-o3mRuuP/MB5fZycjD3KH/uXsnaPl7Oo8GtdbJTKtH1+O/3pz8GzGMkscTKa97l03DAG2EhGrzzLcU2A6eshwFw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-attributes": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.15.3.tgz", + "integrity": "sha512-2ISo0r5ZKwkrvJgDou9xVPxxtXMaETe2AsAA02L89LnbB2KC0N5myNsHV0sEysTw9+CfCmgjAb0GAI5QGpxKkQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-breakpoint": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.15.3.tgz", + "integrity": "sha512-Eo56FA5C2v6ucmWQL/JBJ2z641pLOom4k0wP6CMZI2utfyiJ+e2Uuinj1KTrgDcEvW4EtU9HrfAqLK9UosLZlg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-font": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.15.3.tgz", + "integrity": "sha512-CzV2aDPpiNIIgGPHNcBhgyedKY4SX3BJoTwOobSwZVIlEA6TAWB4Z9WwFUmQqZOgo1AkkiTHPZQvGcEhFFXH6g==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-html-attributes": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.15.3.tgz", + "integrity": "sha512-MDNDPMBOgXUZYdxhosyrA2kudiGO8aogT0/cODyi2Ed9o/1S7W+je11JUYskQbncqhWKGxNyaP4VWa+6+vUC/g==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-preview": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.15.3.tgz", + "integrity": "sha512-J2PxCefUVeFwsAExhrKo4lwxDevc5aKj888HBl/wN4EuWOoOg06iOGCxz4Omd8dqyFsrqvbBuPqRzQ+VycGmaA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-style": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.15.3.tgz", + "integrity": "sha512-9J+JuH+mKrQU65CaJ4KZegACUgNIlYmWQYx3VOBR/tyz+8kDYX7xBhKJCjQ1I4wj2Tvga3bykd89Oc2kFZ5WOw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-title": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.15.3.tgz", + "integrity": "sha512-IM59xRtsxID4DubQ0iLmoCGXguEe+9BFG4z6y2xQDrscIa4QY3KlfqgKGT69ojW+AVbXXJPEVqrAi4/eCsLItQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-hero": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.15.3.tgz", + "integrity": "sha512-9cLAPuc69yiuzNrMZIN58j+HMK1UWPaq2i3/Fg2ZpimfcGFKRcPGCbEVh0v+Pb6/J0+kf8yIO0leH20opu3AyQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-image": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.15.3.tgz", + "integrity": "sha512-g1OhSdofIytE9qaOGdTPmRIp7JsCtgO0zbsn1Fk6wQh2gEL55Z40j/VoghslWAWTgT2OHFdBKnMvWtN6U5+d2Q==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-migrate": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.15.3.tgz", + "integrity": "sha512-sr/+35RdxZroNQVegjpfRHJ5hda9XCgaS4mK2FGO+Mb1IUevKfeEPII3F/cHDpNwFeYH3kAgyqQ22ClhGLWNBA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "js-beautify": "^1.6.14", + "lodash": "^4.17.21", + "mjml-core": "4.15.3", + "mjml-parser-xml": "4.15.3", + "yargs": "^17.7.2" + }, + "bin": { + "migrate": "lib/cli.js" + } + }, + "node_modules/mjml-navbar": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.15.3.tgz", + "integrity": "sha512-VsKH/Jdlf8Yu3y7GpzQV5n7JMdpqvZvTSpF6UQXL0PWOm7k6+LX+sCZimOfpHJ+wCaaybpxokjWZ71mxOoCWoA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-parser-xml": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.15.3.tgz", + "integrity": "sha512-Tz0UX8/JVYICLjT+U8J1f/TFxIYVYjzZHeh4/Oyta0pLpRLeZlxEd71f3u3kdnulCKMP4i37pFRDmyLXAlEuLw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "detect-node": "2.1.0", + "htmlparser2": "^9.1.0", + "lodash": "^4.17.15" + } + }, + "node_modules/mjml-parser-xml/node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/mjml-preset-core": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.15.3.tgz", + "integrity": "sha512-1zZS8P4O0KweWUqNS655+oNnVMPQ1Rq1GaZq5S9JfwT1Vh/m516lSmiTW9oko6gGHytt5s6Yj6oOeu5Zm8FoLw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "mjml-accordion": "4.15.3", + "mjml-body": "4.15.3", + "mjml-button": "4.15.3", + "mjml-carousel": "4.15.3", + "mjml-column": "4.15.3", + "mjml-divider": "4.15.3", + "mjml-group": "4.15.3", + "mjml-head": "4.15.3", + "mjml-head-attributes": "4.15.3", + "mjml-head-breakpoint": "4.15.3", + "mjml-head-font": "4.15.3", + "mjml-head-html-attributes": "4.15.3", + "mjml-head-preview": "4.15.3", + "mjml-head-style": "4.15.3", + "mjml-head-title": "4.15.3", + "mjml-hero": "4.15.3", + "mjml-image": "4.15.3", + "mjml-navbar": "4.15.3", + "mjml-raw": "4.15.3", + "mjml-section": "4.15.3", + "mjml-social": "4.15.3", + "mjml-spacer": "4.15.3", + "mjml-table": "4.15.3", + "mjml-text": "4.15.3", + "mjml-wrapper": "4.15.3" + } + }, + "node_modules/mjml-raw": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.15.3.tgz", + "integrity": "sha512-IGyHheOYyRchBLiAEgw3UM11kFNmBSMupu2BDdejC6ZiDhEAdG+tyERlsCwDPYtXanvFpGWULIu3XlsUPc+RZw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-section": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.15.3.tgz", + "integrity": "sha512-JfVPRXH++Hd933gmQfG8JXXCBCR6fIzC3DwiYycvanL/aW1cEQ2EnebUfQkt5QzlYjOkJEH+JpccAsq3ln6FZQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-social": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.15.3.tgz", + "integrity": "sha512-7sD5FXrESOxpT9Z4Oh36bS6u/geuUrMP1aCg2sjyAwbPcF1aWa2k9OcatQfpRf6pJEhUZ18y6/WBBXmMVmSzXg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-spacer": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.15.3.tgz", + "integrity": "sha512-3B7Qj+17EgDdAtZ3NAdMyOwLTX1jfmJuY7gjyhS2HtcZAmppW+cxqHUBwCKfvSRgTQiccmEvtNxaQK+tfyrZqA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-table": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.15.3.tgz", + "integrity": "sha512-FLx7DcRKTdKdcOCbMyBaeudeHaHpwPveRrBm6WyQe3LXx6FfdmOh59i71/16LFQMgBOD3N4/UJkzxLzlTJzMqQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-text": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.15.3.tgz", + "integrity": "sha512-+C0hxCmw9kg0XzT6vhE5mFkK6y225nC8UEQcN94K0fBCjPKkM+HqZMwGX205fzdGRi+Bxa55b/VhrIVwdv+8vw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-validator": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-validator/-/mjml-validator-4.15.3.tgz", + "integrity": "sha512-Xb72KdqRwjv/qM2rJpV22syyP2N3cRQ9VVDrN6u2FSzLq02buFNxmSPJ7CKhat3PrUNdVHU75KZwOf/tz4UEhA==", + "dependencies": { + "@babel/runtime": "^7.23.9" + } + }, + "node_modules/mjml-wrapper": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.15.3.tgz", + "integrity": "sha512-ditsCijeHJrmBmObtJmQ18ddLxv5oPyMTdPU8Di8APOnD2zPk7Z4UAuJSl7HXB45oFiivr3MJf4koFzMUSZ6Gg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3", + "mjml-section": "4.15.3" + } + }, "node_modules/mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -10512,6 +11259,14 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dependencies": { + "lower-case": "^1.1.1" + } + }, "node_modules/node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", @@ -10684,7 +11439,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10959,11 +11713,24 @@ "node": ">=8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, "node_modules/packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "dependencies": { + "no-case": "^2.2.0" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11062,7 +11829,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -11073,6 +11839,34 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/path-to-regexp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", @@ -11207,7 +12001,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -11399,6 +12192,11 @@ "node": ">= 6" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -11607,7 +12405,6 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -11674,6 +12471,14 @@ "node": ">=8" } }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -12524,6 +13329,14 @@ "node": ">=0.10.0" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -12547,15 +13360,6 @@ "source-map": "^0.6.0" } }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-url": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", @@ -12798,6 +13602,20 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trimend": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", @@ -12829,6 +13647,18 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -13165,7 +13995,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -13657,19 +14486,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/typeorm/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/typeorm/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -13789,31 +14605,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" }, - "node_modules/typeorm/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/typeorm/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -13826,6 +14617,17 @@ "node": ">=4.2.0" } }, + "node_modules/uglify-js": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.0.tgz", + "integrity": "sha512-wNKHUY2hYYkf6oSFfhwwiHo4WCHzHmzcXsqXYTN9ja3iApYIFbb2U6ics9hBcYLHcYGQoAlwnZlTrf3oF+BL/Q==", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/uid": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", @@ -14053,6 +14855,11 @@ "node": ">=8" } }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -14427,6 +15234,53 @@ "node": ">=10" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -14529,18 +15383,17 @@ "dev": true }, "node_modules/yargs": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.0.tgz", - "integrity": "sha512-gbtedDPfBgG40iLbaRXhqYJycUYqFVZQLIxl1cG5Ez/xZL/47TetSYzPSIixkWa36GKHr9D/o/oSG1vHXF4zTw==", - "dev": true, + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { "node": ">=12" @@ -14554,6 +15407,27 @@ "node": ">=10" } }, + "node_modules/yargs/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -15067,6 +15941,64 @@ } } }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, "@jest/console": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", @@ -15455,12 +16387,6 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -15481,14 +16407,6 @@ "callsites": "^3.0.0", "graceful-fs": "^4.1.15", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "@jest/test-result": { @@ -15699,12 +16617,6 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -16094,6 +17006,17 @@ } } }, + "@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -16790,7 +17713,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -17114,8 +18036,7 @@ "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "bindings": { "version": "1.5.0", @@ -17248,7 +18169,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -17389,6 +18309,15 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -17490,7 +18419,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dev": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", @@ -17506,7 +18434,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -17562,6 +18489,14 @@ "validator": "^13.7.0" } }, + "clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "requires": { + "source-map": "~0.6.0" + } + }, "cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -17759,6 +18694,15 @@ } } }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -17890,7 +18834,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -17901,7 +18844,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -17909,14 +18851,12 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -18129,6 +19069,11 @@ "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", "dev": true }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -18227,6 +19172,11 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -18244,6 +19194,45 @@ "safe-buffer": "^5.0.1" } }, + "editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "requires": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==" + }, + "minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" + } + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -18362,15 +19351,6 @@ "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } } }, "eslint": { @@ -19119,7 +20099,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -19183,6 +20162,22 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + } + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -19252,7 +20247,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, "optional": true }, "function-bind": { @@ -19503,6 +20497,11 @@ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "dev": true }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, "highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -19529,6 +20528,27 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "html-minifier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", + "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", + "requires": { + "camel-case": "^3.0.0", + "clean-css": "^4.2.1", + "commander": "^2.19.0", + "he": "^1.2.0", + "param-case": "^2.1.1", + "relateurl": "^0.2.7", + "uglify-js": "^3.5.1" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + } + } + }, "htmlparser2": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", @@ -19685,8 +20705,7 @@ "ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "invariant": { "version": "2.2.4", @@ -19732,7 +20751,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -19821,8 +20839,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -19839,7 +20856,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -19869,8 +20885,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-obj": { "version": "2.0.0", @@ -19938,8 +20953,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -20031,12 +21045,6 @@ "requires": { "glob": "^7.1.3" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -20054,6 +21062,15 @@ "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==" }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "jake": { "version": "10.8.7", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", @@ -21969,12 +22986,6 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -22119,6 +23130,72 @@ } } }, + "js-beautify": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", + "requires": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" + }, + "dependencies": { + "abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==" + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + }, + "nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "requires": { + "abbrev": "^2.0.0" + } + } + } + }, + "js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -22466,6 +23543,11 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -22589,98 +23671,512 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mjml": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.15.3.tgz", + "integrity": "sha512-bW2WpJxm6HS+S3Yu6tq1DUPFoTxU9sPviUSmnL7Ua+oVO3WA5ILFWqvujUlz+oeuM+HCwEyMiP5xvKNPENVjYA==", + "requires": { + "@babel/runtime": "^7.23.9", + "mjml-cli": "4.15.3", + "mjml-core": "4.15.3", + "mjml-migrate": "4.15.3", + "mjml-preset-core": "4.15.3", + "mjml-validator": "4.15.3" + } + }, + "mjml-accordion": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.15.3.tgz", + "integrity": "sha512-LPNVSj1LyUVYT9G1gWwSw3GSuDzDsQCu0tPB2uDsq4VesYNnU6v3iLCQidMiR6azmIt13OEozG700ygAUuA6Ng==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-body": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.15.3.tgz", + "integrity": "sha512-7pfUOVPtmb0wC+oUOn4xBsAw4eT5DyD6xqaxj/kssu6RrFXOXgJaVnDPAI9AzIvXJ/5as9QrqRGYAddehwWpHQ==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-button": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.15.3.tgz", + "integrity": "sha512-79qwn9AgdGjJR1vLnrcm2rq2AsAZkKC5JPwffTMG+Nja6zGYpTDZFZ56ekHWr/r1b5WxkukcPj2PdevUug8c+Q==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-carousel": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.15.3.tgz", + "integrity": "sha512-3ju6I4l7uUhPRrJfN3yK9AMsfHvrYbRkcJ1GRphFHzUj37B2J6qJOQUpzA547Y4aeh69TSb7HFVf1t12ejQxVw==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-cli": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.15.3.tgz", + "integrity": "sha512-+V2TDw3tXUVEptFvLSerz125C2ogYl8klIBRY1m5BHd4JvGVf3yhx8N3PngByCzA6PGcv/eydGQN+wy34SHf0Q==", + "requires": { + "@babel/runtime": "^7.23.9", + "chokidar": "^3.0.0", + "glob": "^10.3.10", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "lodash": "^4.17.21", + "minimatch": "^9.0.3", + "mjml-core": "4.15.3", + "mjml-migrate": "4.15.3", + "mjml-parser-xml": "4.15.3", + "mjml-validator": "4.15.3", + "yargs": "^17.7.2" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + } + } + }, + "mjml-column": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.15.3.tgz", + "integrity": "sha512-hYdEFdJGHPbZJSEysykrevEbB07yhJGSwfDZEYDSbhQQFjV2tXrEgYcFD5EneMaowjb55e3divSJxU4c5q4Qgw==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-core": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.15.3.tgz", + "integrity": "sha512-Dmwk+2cgSD9L9GmTbEUNd8QxkTZtW9P7FN/ROZW/fGZD6Hq6/4TB0zEspg2Ow9eYjZXO2ofOJ3PaQEEShKV0kQ==", + "requires": { + "@babel/runtime": "^7.23.9", + "cheerio": "1.0.0-rc.12", + "detect-node": "^2.0.4", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "juice": "^10.0.0", + "lodash": "^4.17.21", + "mjml-migrate": "4.15.3", + "mjml-parser-xml": "4.15.3", + "mjml-validator": "4.15.3" + } + }, + "mjml-divider": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.15.3.tgz", + "integrity": "sha512-vh27LQ9FG/01y0b9ntfqm+GT5AjJnDSDY9hilss2ixIUh0FemvfGRfsGVeV5UBVPBKK7Ffhvfqc7Rciob9Spzw==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-group": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.15.3.tgz", + "integrity": "sha512-HSu/rKnGZVKFq3ciT46vi1EOy+9mkB0HewO4+P6dP/Y0UerWkN6S3UK11Cxsj0cAp0vFwkPDCdOeEzRdpFEkzA==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-head": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.15.3.tgz", + "integrity": "sha512-o3mRuuP/MB5fZycjD3KH/uXsnaPl7Oo8GtdbJTKtH1+O/3pz8GzGMkscTKa97l03DAG2EhGrzzLcU2A6eshwFw==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-head-attributes": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.15.3.tgz", + "integrity": "sha512-2ISo0r5ZKwkrvJgDou9xVPxxtXMaETe2AsAA02L89LnbB2KC0N5myNsHV0sEysTw9+CfCmgjAb0GAI5QGpxKkQ==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-head-breakpoint": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.15.3.tgz", + "integrity": "sha512-Eo56FA5C2v6ucmWQL/JBJ2z641pLOom4k0wP6CMZI2utfyiJ+e2Uuinj1KTrgDcEvW4EtU9HrfAqLK9UosLZlg==", "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "mjml-head-font": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.15.3.tgz", + "integrity": "sha512-CzV2aDPpiNIIgGPHNcBhgyedKY4SX3BJoTwOobSwZVIlEA6TAWB4Z9WwFUmQqZOgo1AkkiTHPZQvGcEhFFXH6g==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + "mjml-head-html-attributes": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.15.3.tgz", + "integrity": "sha512-MDNDPMBOgXUZYdxhosyrA2kudiGO8aogT0/cODyi2Ed9o/1S7W+je11JUYskQbncqhWKGxNyaP4VWa+6+vUC/g==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "mjml-head-preview": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.15.3.tgz", + "integrity": "sha512-J2PxCefUVeFwsAExhrKo4lwxDevc5aKj888HBl/wN4EuWOoOg06iOGCxz4Omd8dqyFsrqvbBuPqRzQ+VycGmaA==", "requires": { - "mime-db": "1.52.0" + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" } }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true + "mjml-head-style": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.15.3.tgz", + "integrity": "sha512-9J+JuH+mKrQU65CaJ4KZegACUgNIlYmWQYx3VOBR/tyz+8kDYX7xBhKJCjQ1I4wj2Tvga3bykd89Oc2kFZ5WOw==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "mjml-head-title": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.15.3.tgz", + "integrity": "sha512-IM59xRtsxID4DubQ0iLmoCGXguEe+9BFG4z6y2xQDrscIa4QY3KlfqgKGT69ojW+AVbXXJPEVqrAi4/eCsLItQ==", "requires": { - "brace-expansion": "^1.1.7" + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "mjml-hero": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.15.3.tgz", + "integrity": "sha512-9cLAPuc69yiuzNrMZIN58j+HMK1UWPaq2i3/Fg2ZpimfcGFKRcPGCbEVh0v+Pb6/J0+kf8yIO0leH20opu3AyQ==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "mjml-image": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.15.3.tgz", + "integrity": "sha512-g1OhSdofIytE9qaOGdTPmRIp7JsCtgO0zbsn1Fk6wQh2gEL55Z40j/VoghslWAWTgT2OHFdBKnMvWtN6U5+d2Q==", "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - }, - "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" } }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "mjml-migrate": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.15.3.tgz", + "integrity": "sha512-sr/+35RdxZroNQVegjpfRHJ5hda9XCgaS4mK2FGO+Mb1IUevKfeEPII3F/cHDpNwFeYH3kAgyqQ22ClhGLWNBA==", "requires": { - "minipass": "^2.9.0" + "@babel/runtime": "^7.23.9", + "js-beautify": "^1.6.14", + "lodash": "^4.17.21", + "mjml-core": "4.15.3", + "mjml-parser-xml": "4.15.3", + "yargs": "^17.7.2" } }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, + "mjml-navbar": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.15.3.tgz", + "integrity": "sha512-VsKH/Jdlf8Yu3y7GpzQV5n7JMdpqvZvTSpF6UQXL0PWOm7k6+LX+sCZimOfpHJ+wCaaybpxokjWZ71mxOoCWoA==", "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-parser-xml": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.15.3.tgz", + "integrity": "sha512-Tz0UX8/JVYICLjT+U8J1f/TFxIYVYjzZHeh4/Oyta0pLpRLeZlxEd71f3u3kdnulCKMP4i37pFRDmyLXAlEuLw==", + "requires": { + "@babel/runtime": "^7.23.9", + "detect-node": "2.1.0", + "htmlparser2": "^9.1.0", + "lodash": "^4.17.15" }, "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, + "htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", "requires": { - "is-plain-object": "^2.0.4" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" } } } }, + "mjml-preset-core": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.15.3.tgz", + "integrity": "sha512-1zZS8P4O0KweWUqNS655+oNnVMPQ1Rq1GaZq5S9JfwT1Vh/m516lSmiTW9oko6gGHytt5s6Yj6oOeu5Zm8FoLw==", + "requires": { + "@babel/runtime": "^7.23.9", + "mjml-accordion": "4.15.3", + "mjml-body": "4.15.3", + "mjml-button": "4.15.3", + "mjml-carousel": "4.15.3", + "mjml-column": "4.15.3", + "mjml-divider": "4.15.3", + "mjml-group": "4.15.3", + "mjml-head": "4.15.3", + "mjml-head-attributes": "4.15.3", + "mjml-head-breakpoint": "4.15.3", + "mjml-head-font": "4.15.3", + "mjml-head-html-attributes": "4.15.3", + "mjml-head-preview": "4.15.3", + "mjml-head-style": "4.15.3", + "mjml-head-title": "4.15.3", + "mjml-hero": "4.15.3", + "mjml-image": "4.15.3", + "mjml-navbar": "4.15.3", + "mjml-raw": "4.15.3", + "mjml-section": "4.15.3", + "mjml-social": "4.15.3", + "mjml-spacer": "4.15.3", + "mjml-table": "4.15.3", + "mjml-text": "4.15.3", + "mjml-wrapper": "4.15.3" + } + }, + "mjml-raw": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.15.3.tgz", + "integrity": "sha512-IGyHheOYyRchBLiAEgw3UM11kFNmBSMupu2BDdejC6ZiDhEAdG+tyERlsCwDPYtXanvFpGWULIu3XlsUPc+RZw==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-section": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.15.3.tgz", + "integrity": "sha512-JfVPRXH++Hd933gmQfG8JXXCBCR6fIzC3DwiYycvanL/aW1cEQ2EnebUfQkt5QzlYjOkJEH+JpccAsq3ln6FZQ==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-social": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.15.3.tgz", + "integrity": "sha512-7sD5FXrESOxpT9Z4Oh36bS6u/geuUrMP1aCg2sjyAwbPcF1aWa2k9OcatQfpRf6pJEhUZ18y6/WBBXmMVmSzXg==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-spacer": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.15.3.tgz", + "integrity": "sha512-3B7Qj+17EgDdAtZ3NAdMyOwLTX1jfmJuY7gjyhS2HtcZAmppW+cxqHUBwCKfvSRgTQiccmEvtNxaQK+tfyrZqA==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-table": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.15.3.tgz", + "integrity": "sha512-FLx7DcRKTdKdcOCbMyBaeudeHaHpwPveRrBm6WyQe3LXx6FfdmOh59i71/16LFQMgBOD3N4/UJkzxLzlTJzMqQ==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-text": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.15.3.tgz", + "integrity": "sha512-+C0hxCmw9kg0XzT6vhE5mFkK6y225nC8UEQcN94K0fBCjPKkM+HqZMwGX205fzdGRi+Bxa55b/VhrIVwdv+8vw==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "mjml-validator": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-validator/-/mjml-validator-4.15.3.tgz", + "integrity": "sha512-Xb72KdqRwjv/qM2rJpV22syyP2N3cRQ9VVDrN6u2FSzLq02buFNxmSPJ7CKhat3PrUNdVHU75KZwOf/tz4UEhA==", + "requires": { + "@babel/runtime": "^7.23.9" + } + }, + "mjml-wrapper": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.15.3.tgz", + "integrity": "sha512-ditsCijeHJrmBmObtJmQ18ddLxv5oPyMTdPU8Di8APOnD2zPk7Z4UAuJSl7HXB45oFiivr3MJf4koFzMUSZ6Gg==", + "requires": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3", + "mjml-section": "4.15.3" + } + }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -22807,6 +24303,14 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "requires": { + "lower-case": "^1.1.1" + } + }, "node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", @@ -22954,8 +24458,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-url": { "version": "4.5.1", @@ -23163,11 +24666,24 @@ "semver": "^6.2.0" } }, + "package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, "packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "requires": { + "no-case": "^2.2.0" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -23246,8 +24762,7 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-parse": { "version": "1.0.7", @@ -23255,6 +24770,27 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + } + } + }, "path-to-regexp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", @@ -23363,8 +24899,7 @@ "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pify": { "version": "3.0.0", @@ -23501,6 +25036,11 @@ "sisteransi": "^1.0.5" } }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -23658,7 +25198,6 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -23710,6 +25249,11 @@ "rc": "^1.2.8" } }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==" + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -24406,6 +25950,11 @@ } } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -24427,14 +25976,6 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "source-map-url": { @@ -24636,6 +26177,16 @@ "strip-ansi": "^6.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "string.prototype.trimend": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", @@ -24664,6 +26215,14 @@ "ansi-regex": "^5.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -24937,7 +26496,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -25235,16 +26793,6 @@ "supports-color": "^7.1.0" } }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -25321,25 +26869,6 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" } } }, @@ -25348,6 +26877,11 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" }, + "uglify-js": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.0.tgz", + "integrity": "sha512-wNKHUY2hYYkf6oSFfhwwiHo4WCHzHmzcXsqXYTN9ja3iApYIFbb2U6ics9hBcYLHcYGQoAlwnZlTrf3oF+BL/Q==" + }, "uid": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", @@ -25519,6 +27053,11 @@ } } }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==" + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -25835,6 +27374,39 @@ } } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -25895,18 +27467,34 @@ "dev": true }, "yargs": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.0.tgz", - "integrity": "sha512-gbtedDPfBgG40iLbaRXhqYJycUYqFVZQLIxl1cG5Ez/xZL/47TetSYzPSIixkWa36GKHr9D/o/oSG1vHXF4zTw==", - "dev": true, + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "requires": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } } }, "yargs-parser": { diff --git a/services/API-service/package.json b/services/API-service/package.json index d3e42e71d..f34e4c5ea 100644 --- a/services/API-service/package.json +++ b/services/API-service/package.json @@ -44,6 +44,7 @@ "jsonwebtoken": "^8.1.1", "juice": "^10.0.0", "mailchimp-api-v3": "^1.15.0", + "mjml": "^4.15.3", "mysql": "^2.15.0", "passport": "^0.4.1", "passport-jwt": "^4.0.0", diff --git a/services/API-service/src/api/notification/email/email-template.service.ts b/services/API-service/src/api/notification/email/email-template.service.ts deleted file mode 100644 index b53547902..000000000 --- a/services/API-service/src/api/notification/email/email-template.service.ts +++ /dev/null @@ -1,530 +0,0 @@ -import * as fs from 'fs'; -import { Injectable } from '@nestjs/common'; - -import * as ejs from 'ejs'; -import * as juice from 'juice'; - -import { - EapAlertClassKeyEnum, - EventSummaryCountry, - TriggeredArea, -} from '../../../shared/data.model'; -import { HelperService } from '../../../shared/helper.service'; -import { LeadTime } from '../../admin-area-dynamic-data/enum/lead-time.enum'; -import { CountryTimeZoneMapping } from '../../country/country-time-zone-mapping'; -import { CountryEntity } from '../../country/country.entity'; -import { DisasterType } from '../../disaster/disaster-type.enum'; -import { ContentEventEmail } from '../dto/content-trigger-email.dto'; -import { - NotificationDataPerEventDto, - TriggerStatusLabelEnum, -} from '../dto/notification-date-per-event.dto'; - -const emailFolder = './src/api/notification/email'; -const emailTemplateFolder = `${emailFolder}/html`; -const emailIconFolder = `${emailFolder}/icons`; -const emailLogoFolder = `${emailFolder}/logos`; - -@Injectable() -export class EmailTemplateService { - public constructor(private readonly helperService: HelperService) {} - - public async createHtmlForTriggerEmail( - emailContent: ContentEventEmail, - date: Date, - ): Promise { - const replaceKeyValues = this.createReplaceKeyValuesTrigger( - emailContent, - date, - ); - return this.formatEmail(replaceKeyValues); - } - - public async createHtmlForTriggerFinishedEmail( - country: CountryEntity, - disasterType: DisasterType, - finishedEvents: EventSummaryCountry[], - disasterTypeLabel: string, - _date: Date, // I am not sure if this is needed and for what it was used before - ): Promise { - const replaceKeyValues = this.createReplaceKeyValuesTriggerFinished( - country, - disasterType, - finishedEvents, - disasterTypeLabel, - ); - return await this.formatEmail(replaceKeyValues); - } - - private createReplaceKeyValuesTrigger( - emailContent: ContentEventEmail, - _date: Date, - ): Record { - const country = emailContent.country; - const disasterType = emailContent.disasterType; - - const keyValueReplaceObject = { - emailBody: this.readHtmlFile('trigger-notification.html'), - headerEventOverview: this.getHeaderEventStarted(emailContent), - notificationActions: this.getNotificationActionsHtml( - country, - emailContent.linkEapSop, - ), - tablesStacked: this.getTablesForEvents(emailContent), - eventListBody: this.getEventListBody(emailContent), - imgLogo: country.notificationInfo.logo[disasterType], - triggerStatement: country.notificationInfo.triggerStatement[disasterType], - mapImagePart: this.getMapImageHtml(emailContent), - linkDashboard: process.env.DASHBOARD_URL, - socialMediaLink: country.notificationInfo.linkSocialMediaUrl, - socialMediaType: country.notificationInfo.linkSocialMediaType, - disasterType: emailContent.disasterType, - disasterTypeLabel: emailContent.disasterTypeLabel, - footer: this.getFooterHtml(country.countryName), - }; - return keyValueReplaceObject; - } - - private createReplaceKeyValuesTriggerFinished( - country: CountryEntity, - disasterType: DisasterType, - events: EventSummaryCountry[], - disasterTypeLabel: string, - ): Record { - const keyValueReplaceObject = { - emailBody: this.readHtmlFile('trigger-finished.html'), - headerEventOverview: '', - eventOverview: this.getEventsFinishedOverview( - country, - events, - disasterTypeLabel, - ), - imgLogo: country.notificationInfo.logo[disasterType], - linkDashboard: process.env.DASHBOARD_URL, - socialMediaPart: this.getSocialMediaHtml(country), - socialMediaLink: country.notificationInfo.linkSocialMediaUrl, - socialMediaType: country.notificationInfo.linkSocialMediaType, - disasterType: disasterType, - disasterTypeLabel: disasterTypeLabel, - footer: this.getFooterHtml(country.countryName), - }; - return keyValueReplaceObject; - } - - private getEventsFinishedOverview( - country: CountryEntity, - events: EventSummaryCountry[], - disasterTypeLabel: string, - ): string { - const template = this.readHtmlFile('event-finished.html'); - return events - .map((event) => - ejs.render(template, { - disasterTypeLabel, - eventName: event.eventName, - issuedDate: this.dateObjectToDateTimeString( - new Date(event.startDate), - country.countryCodeISO3, - ), - timeZone: this.getTimezoneDisplay(country.countryCodeISO3), - }), - ) - .join(''); - } - - private getHeaderEventStarted(emailContent: ContentEventEmail): string { - let headerEventOverview = this.readHtmlFile('header.html'); - headerEventOverview = ejs.render(headerEventOverview, { - sentOnDate: this.getCurrentDateTimeString( - emailContent.country.countryCodeISO3, - ), - disasterTypeLabel: emailContent.disasterTypeLabel, - nrOfEvents: emailContent.dataPerEvent.length, - timeZone: this.getTimezoneDisplay(emailContent.country.countryCodeISO3), - }); - return headerEventOverview; - } - - private getTimezoneDisplay = (countryCodeISO3: string) => { - return CountryTimeZoneMapping[countryCodeISO3].split('_').join(' '); - }; - - private getNotificationActionsHtml( - country: CountryEntity, - linkEapSop: string, - ): string { - const socialMediaLinkHtml = this.getSocialMediaHtml(country); - - let html = this.readHtmlFile('notification-actions.html'); - const data = { - linkDashboard: process.env.DASHBOARD_URL, - linkEapSop: linkEapSop, - socialMediaPart: socialMediaLinkHtml, - }; - html = ejs.render(html, data); - return html; - } - - private getSocialMediaHtml(country: CountryEntity) { - return country.notificationInfo.linkSocialMediaType - ? this.readHtmlFile('social-media-link.html') - : ''; - } - - private getMapImageHtml(emailContent: ContentEventEmail) { - return emailContent.dataPerEvent - .filter((event) => event.mapImage) - .map((event) => { - const eventHtmlTemplate = this.readHtmlFile('map-image.html'); - const replacements = { - mapImgSrc: this.getMapImgSrc( - emailContent.country.countryCodeISO3, - emailContent.disasterType, - event.eventName, - ), - mapImgDescription: this.getMapImageDescription( - emailContent.disasterType, - ), - eventName: event.eventName ? `(for ${event.eventName})` : '', - }; - return ejs.render(eventHtmlTemplate, replacements); - }) - .join(''); - } - - private getMapImgSrc( - countryCodeISO3: string, - disasterType: DisasterType, - eventName: string, - ) { - return `${ - process.env.NG_API_URL - }/event/event-map-image/${countryCodeISO3}/${disasterType}/${ - eventName || 'no-name' - }`; - } - - private getMapImageDescription(disasterType: DisasterType): string { - const descriptions = { - [DisasterType.Floods]: - 'The triggered areas are outlined in purple. The potential flood extent is shown in red.
', - }; - - return descriptions[disasterType] || ''; - } - - private async formatEmail( - emailKeyValueReplaceObject: Record, - ): Promise { - // TODO REFACTOR: Apply styles in a separate file also for the base.html - const template = this.readHtmlFile('base.html'); - const styles = this.readHtmlFile('styles.ejs'); - const templateWithStyle = styles + template; - - let emailHtml = templateWithStyle; - let previousHtml = null; - - // This loop is needed to handle nested EJS tags. It repeatedly renders the template - // until there are no more EJS tags left to render. This is necessary because EJS - // doesn't render nested tags in one pass. - while (emailHtml !== previousHtml) { - previousHtml = emailHtml; - emailHtml = ejs.render(previousHtml, emailKeyValueReplaceObject); - } - // Inline the CSS - const inlinedHtml = await new Promise((resolve, reject) => { - juice.juiceResources(emailHtml, { webResources: {} }, (err, html) => { - if (err) { - console.error('Error inlining CSS: ', err); - reject(err); - } else { - resolve(html); - } - }); - }); - - return inlinedHtml as string; - } - - private getTablesForEvents(emailContent: ContentEventEmail): string { - const adminAreaLabelsParent = - emailContent.country.adminRegionLabels[ - String(Math.max(1, emailContent.defaultAdminLevel - 1)) - ]; - return emailContent.dataPerEvent - .map((event) => { - const data = { - disasterTypeLabel: emailContent.disasterTypeLabel, - triggerStatusLabel: event.triggerStatusLabel, - eventName: event.eventName, - defaultAdminAreaLabelSingular: - emailContent.defaultAdminAreaLabel.singular, - defaultAdminAreaLabelPlural: - emailContent.defaultAdminAreaLabel.plural.toLocaleLowerCase(), - defaultAdminAreaLabelParent: adminAreaLabelsParent.singular, - indicatorLabel: emailContent.indicatorMetadata.label, - triangleIcon: this.getTriangleIcon( - event.eapAlertClass?.key, - event.triggerStatusLabel, - ), - tableRows: this.getTablesRows(event), - isIndicatorAvailable: this.isIndicatorAvailable(event.triggeredAreas), - color: this.getIbfHexColor( - event.eapAlertClass?.color, - event.triggerStatusLabel, - ), - severityLabel: this.getEventSeverityLabel(event.eapAlertClass?.key), - }; - - const templateFileName = 'table-event.html'; - const template = this.readHtmlFile(templateFileName); - - const result = ejs.render(template, data); - return result; - }) - .join(''); - } - - private getEventSeverityLabel( - eapAlertClassKey: EapAlertClassKeyEnum, - ): string { - const severityLabels = { - [EapAlertClassKeyEnum.med]: 'Medium', - [EapAlertClassKeyEnum.min]: 'Low', - }; - - return severityLabels[eapAlertClassKey] || ''; - } - - private isIndicatorAvailable(areas: TriggeredArea[]) { - return areas.some((area) => area.actionsValue); - } - - private getTablesRows(event: NotificationDataPerEventDto) { - return event.triggeredAreas - .filter((area) => area.actionsValue) - .map((area) => { - const areaTemplate = this.readHtmlFile('table-row.html'); - const areaData = { - affectedOfIndicator: this.helperService.toCompactNumber( - area.actionsValue, - ), - adminBoundary: area.displayName ? area.displayName : area.name, - higherAdminBoundary: area.nameParent, - }; - - return ejs.render(areaTemplate, areaData); - }) - .join(''); - } - - private getEventListBody(emailContent: ContentEventEmail): string { - return emailContent.dataPerEvent - .map((event) => { - const data = { - // Event details - eventName: event.eventName, - disasterTypeLabel: emailContent.disasterTypeLabel, - triggerStatusLabel: event.triggerStatusLabel, - issuedDate: this.dateObjectToDateTimeString( - event.issuedDate, - emailContent.country.countryCodeISO3, - ), - timeZone: this.getTimezoneDisplay( - emailContent.country.countryCodeISO3, - ), - - // Lead time details - firstLeadTimeString: event.firstLeadTimeString, - firstTriggerLeadTimeString: event.firstTriggerLeadTimeString, - firstLeadTimeFromNow: this.getTimeFromNow(event.firstLeadTime), - firstTriggerLeadTimeFromNow: this.getTimeFromNow( - event.firstTriggerLeadTime, - ), - - // Area details - nrOfTriggeredAreas: event.nrOfTriggeredAreas, - defaultAdminAreaLabel: - emailContent.defaultAdminAreaLabel.plural.toLocaleLowerCase(), - - // Indicator details - indicatorLabel: emailContent.indicatorMetadata.label, - totalAffectedOfIndicator: event.totalAffectedOfIndicator, - indicatorUnit: emailContent.indicatorMetadata.unit, - totalAffected: this.getTotalAffectedHtml( - event, - emailContent.indicatorMetadata.label.toLowerCase(), - ), - - // EAP details - triangleIcon: this.getTriangleIcon( - event.eapAlertClass?.key, - event.triggerStatusLabel, - ), - leadTime: event.firstLeadTime.replace('-', ' '), - disasterIssuedLabel: this.getDisasterIssuedLabel( - event.eapAlertClass?.label, - event.triggerStatusLabel, - ), - color: this.getIbfHexColor( - event.eapAlertClass?.color, - event.triggerStatusLabel, - ), - advisory: this.getAdvisoryHtml( - event.triggerStatusLabel, - emailContent.linkEapSop, - ), - }; - - const templateFileName = 'body-event.html'; - const template = this.readHtmlFile(templateFileName); - return ejs.render(template, data); - }) - .join(''); - } - - private getTimeFromNow(leadTime: LeadTime) { - if (!leadTime) return ''; - - return [LeadTime.day0, LeadTime.month0, LeadTime.hour0].includes(leadTime) - ? 'ongoing' - : `${leadTime.replace('-', ' ')}s from now`; - } - - private getDisasterIssuedLabel( - eapLabel: string, - triggerStatusLabel: TriggerStatusLabelEnum, - ) { - return eapLabel || triggerStatusLabel; - } - - private getAdvisoryHtml( - triggerStatusLabel: TriggerStatusLabelEnum, - eapLink: string, - ) { - const fileName = - triggerStatusLabel === TriggerStatusLabelEnum.Trigger - ? 'advisory-trigger.html' - : 'advisory-warning.html'; - const advisoryHtml = this.readHtmlFile(fileName); - return ejs.render(advisoryHtml, { eapLink }); - } - - private getTotalAffectedHtml( - event: NotificationDataPerEventDto, - indicatorUnit: string, - ): string { - const fileName = event.totalAffectedOfIndicator - ? 'body-total-affected-available.html' - : 'body-total-affected-unavailable.html'; - const htmlTemplate = this.readHtmlFile(fileName); - return ejs.render(htmlTemplate, { - totalAffectedOfIndicator: this.helperService.toCompactNumber( - event.totalAffectedOfIndicator, - ), - indicatorUnit: indicatorUnit, - }); - } - - private getIbfHexColor( - color: string, - triggerStatusLabel: TriggerStatusLabelEnum, - ): string { - const ibfOrange = '#aa6009'; - const ibfYellow = '#665606'; - const ibfRed = '#8a0f32'; - - // Color defined in the EAP Alert Class. This is only used for flood events - // For other events, the color is defined in the disaster settings - // So we decide it based on the trigger status label - - if (color) { - // TODO: Define in a place where FrontEnd and Backend can share this - switch (color) { - case 'ibf-orange': - return ibfOrange; - case 'fiveten-yellow-500': - return ibfYellow; - default: - return ibfRed; - } - } - return triggerStatusLabel === TriggerStatusLabelEnum.Trigger - ? ibfRed - : ibfOrange; - } - - private getFooterHtml(countryName: string): string { - const footerHtml = this.readHtmlFile('footer.html'); - const ibfLogo = this.getLogoImageAsDataURL(); - return ejs.render(footerHtml, { - ibfLogo: ibfLogo, - countryName: countryName, - }); - } - - private getCurrentDateTimeString(countryCodeISO3: string): string { - const date = new Date(); - return this.dateObjectToDateTimeString(date, countryCodeISO3); - } - - private dateObjectToDateTimeString( - date: Date, - countryCodeISO3: string, - ): string { - const timeZone = CountryTimeZoneMapping[countryCodeISO3]; - const options: Intl.DateTimeFormatOptions = { - weekday: 'long', - day: '2-digit', - month: 'long', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', - timeZone: timeZone, - }; - return date.toLocaleString('default', options); - } - - private getTriangleIcon( - eapAlertClassKey: EapAlertClassKeyEnum, - triggerStatusLabel: TriggerStatusLabelEnum, - ) { - const fileNameMap = { - [EapAlertClassKeyEnum.med]: 'warning-medium.png', - [EapAlertClassKeyEnum.min]: 'warning-low.png', - [EapAlertClassKeyEnum.max]: 'trigger.png', - default: 'trigger.png', - }; - - let fileName = eapAlertClassKey - ? fileNameMap[eapAlertClassKey] - : fileNameMap.default; - if ( - !eapAlertClassKey && - triggerStatusLabel !== TriggerStatusLabelEnum.Trigger - ) { - fileName = 'warning-medium.png'; - } - - const filePath = `${emailIconFolder}/${fileName}`; - return this.getPngImageAsDataURL(filePath); - } - - private getLogoImageAsDataURL() { - const filePath = `${emailLogoFolder}/logo-IBF.png`; - return this.getPngImageAsDataURL(filePath); - } - - private getPngImageAsDataURL(relativePath: string) { - const imageBuffer = fs.readFileSync(relativePath); - const imageDataURL = `data:image/png;base64,${imageBuffer.toString( - 'base64', - )}`; - - return imageDataURL; - } - - private readHtmlFile(fileName: string): string { - return fs.readFileSync(`${emailTemplateFolder}/${fileName}`, 'utf8'); - } -} diff --git a/services/API-service/src/api/notification/email/email.service.ts b/services/API-service/src/api/notification/email/email.service.ts index f89f2de9b..01bcb609f 100644 --- a/services/API-service/src/api/notification/email/email.service.ts +++ b/services/API-service/src/api/notification/email/email.service.ts @@ -1,3 +1,4 @@ +// import * as fs from 'fs'; import { Injectable } from '@nestjs/common'; import Mailchimp from 'mailchimp-api-v3'; @@ -6,7 +7,7 @@ import { EventSummaryCountry } from '../../../shared/data.model'; import { DisasterType } from '../../disaster/disaster-type.enum'; import { CountryEntity } from './../../country/country.entity'; import { NotificationContentService } from './../notification-content/notification-content.service'; -import { EmailTemplateService } from './email-template.service'; +import { MjmlService } from './mjml.service'; @Injectable() export class EmailService { @@ -17,7 +18,7 @@ export class EmailService { public constructor( private readonly notificationContentService: NotificationContentService, - private readonly emailTemplateService: EmailTemplateService, + private readonly mjmlService: MjmlService, ) {} private async getSegmentId( @@ -47,18 +48,22 @@ export class EmailService { date?: Date, ): Promise { date = date ? new Date(date) : new Date(); - const emailContent = await this.notificationContentService.getContentTriggerNotification( country, disasterType, activeEvents, ); - const emailHtml = await this.emailTemplateService.createHtmlForTriggerEmail( + let emailHtml = ''; + + emailHtml += this.mjmlService.getTriggerEmailHtmlOutput({ emailContent, date, - ); + }); + if (isApiTest) { + // NOTE: use this to test the email output instead of using Mailchimp + // fs.writeFileSync(`email.html`, emailHtml); return emailHtml; } const emailSubject = `IBF ${emailContent.disasterTypeLabel} alert`; @@ -79,15 +84,19 @@ export class EmailService { ): Promise { const disasterTypeLabel = await this.notificationContentService.getDisasterTypeLabel(disasterType); - const emailHtml = - await this.emailTemplateService.createHtmlForTriggerFinishedEmail( + + const emailContent = + await this.notificationContentService.getContentTriggerNotification( country, disasterType, finishedEvents, - disasterTypeLabel, - date ? new Date(date) : new Date(), ); + const emailHtml = this.mjmlService.getEventFinishedEmailHtmlOutput({ + emailContent, + date, + }); + if (isApiTest) { return emailHtml; } diff --git a/services/API-service/src/api/notification/email/html/advisory-trigger.html b/services/API-service/src/api/notification/email/html/advisory-trigger.html deleted file mode 100644 index d9e896dda..000000000 --- a/services/API-service/src/api/notification/email/html/advisory-trigger.html +++ /dev/null @@ -1,7 +0,0 @@ -Activate - - Early Action Protocol - diff --git a/services/API-service/src/api/notification/email/html/advisory-warning.html b/services/API-service/src/api/notification/email/html/advisory-warning.html deleted file mode 100644 index 90204931d..000000000 --- a/services/API-service/src/api/notification/email/html/advisory-warning.html +++ /dev/null @@ -1 +0,0 @@ -Inform all potentially exposed districts diff --git a/services/API-service/src/api/notification/email/html/base.html b/services/API-service/src/api/notification/email/html/base.html deleted file mode 100644 index a250575d6..000000000 --- a/services/API-service/src/api/notification/email/html/base.html +++ /dev/null @@ -1,1005 +0,0 @@ - - - - - - - - - *|MC:SUBJECT|* - - - -
- - - - -
- - - - - - <%- emailBody %> - - - -
- - - - - -
- - - - - - -
- - - - - - - <%- headerEventOverview %> - - -
- -
-
-
- - -
- - - - - -
- - - - - - -
- - - - - - - - -
- Click here to unsubscribe from IBF - alerts. - *|LIST:ADDRESSLINE|*
- *|IF:REWARDS|* *|HTML:REWARDS|* - *|END:IF|* -
- - -
-
- - -
- - -
-
- - diff --git a/services/API-service/src/api/notification/email/html/body-event.html b/services/API-service/src/api/notification/email/html/body-event.html deleted file mode 100644 index f45b90f16..000000000 --- a/services/API-service/src/api/notification/email/html/body-event.html +++ /dev/null @@ -1,51 +0,0 @@ -
- - triangleIcon - <%= disasterTypeLabel %>: <%= eventName %> - - - <% if (firstTriggerLeadTimeString) { %> - - <%= disasterTypeLabel %> expected to start on <%= firstLeadTimeString %>, - <%= firstLeadTimeFromNow %>. - - - <%= disasterIssuedLabel %>: - - expected to reach threshold on <%= firstTriggerLeadTimeString %>, <%= - firstTriggerLeadTimeFromNow %>. - - - <% } else { %> - <%= disasterIssuedLabel %>: - - expected on <%= firstLeadTimeString %>, <%= firstLeadTimeFromNow %>. - - - <% } %> - - Expected exposed <%= defaultAdminAreaLabel %>: - - - <%= nrOfTriggeredAreas %> (see list below) - - - <%= indicatorLabel %>: - <%- totalAffected %> - Advisory: - - <%- advisory %> - - -
- - This <%= triggerStatusLabel %> was issued by IBF on <%= issuedDate %> (<%= - timeZone %>) - - -
-
diff --git a/services/API-service/src/api/notification/email/html/body-total-affected-available.html b/services/API-service/src/api/notification/email/html/body-total-affected-available.html deleted file mode 100644 index 433087791..000000000 --- a/services/API-service/src/api/notification/email/html/body-total-affected-available.html +++ /dev/null @@ -1,4 +0,0 @@ - - approximately <%= totalAffectedOfIndicator %> <%= indicatorUnit %> - - diff --git a/services/API-service/src/api/notification/email/html/body-total-affected-unavailable.html b/services/API-service/src/api/notification/email/html/body-total-affected-unavailable.html deleted file mode 100644 index bf0f8ddae..000000000 --- a/services/API-service/src/api/notification/email/html/body-total-affected-unavailable.html +++ /dev/null @@ -1 +0,0 @@ -The <%= indicatorUnit %> information is unavailable
diff --git a/services/API-service/src/api/notification/email/html/event-finished.html b/services/API-service/src/api/notification/email/html/event-finished.html deleted file mode 100644 index 7fd67f322..000000000 --- a/services/API-service/src/api/notification/email/html/event-finished.html +++ /dev/null @@ -1,15 +0,0 @@ -
- - <%= disasterTypeLabel %>: <%= eventName %> is now below trigger threshold - - - - The events actions will continue to show on the IBF portal and can still be - managed. - - - - This warning was issued by the IBF portal on <%= issuedDate %> (<%= timeZone - %>) - -
diff --git a/services/API-service/src/api/notification/email/html/footer.html b/services/API-service/src/api/notification/email/html/footer.html deleted file mode 100644 index a1cb1582c..000000000 --- a/services/API-service/src/api/notification/email/html/footer.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - -
- - -
- Impact-Based Forecasting Portal (IBF) was co-developed by Netherlands - Red Cross 510 the together with the <%= countryName %> Red Cross - National Society. Contact us at - ibf-support@510.global. -
-
diff --git a/services/API-service/src/api/notification/email/html/header.html b/services/API-service/src/api/notification/email/html/header.html deleted file mode 100644 index d8a9585b9..000000000 --- a/services/API-service/src/api/notification/email/html/header.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - -
-

- <%= nrOfEvents %> <%= disasterTypeLabel %> alerts -

- - IBF alert sent on <%= sentOnDate %> (<%= timeZone %>) - -
- - diff --git a/services/API-service/src/api/notification/email/html/map-image.html b/services/API-service/src/api/notification/email/html/map-image.html deleted file mode 100644 index 4af2b1715..000000000 --- a/services/API-service/src/api/notification/email/html/map-image.html +++ /dev/null @@ -1,9 +0,0 @@ -
-Map of the triggered area <%- eventName %> -
-<%- mapImgDescription %> -click 'Download pictures' if it does not show diff --git a/services/API-service/src/api/notification/email/html/notification-actions.html b/services/API-service/src/api/notification/email/html/notification-actions.html deleted file mode 100644 index 8bf39c940..000000000 --- a/services/API-service/src/api/notification/email/html/notification-actions.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - <%- socialMediaPart %> - - - - - - -
- Open IBF -   - Find more information about the potentially exposed areas, view the map - and manage anticipatory actions. -
- About Trigger -   - Read about the trigger methodology and the anticipatory actions. -
diff --git a/services/API-service/src/api/notification/email/html/social-media-link.html b/services/API-service/src/api/notification/email/html/social-media-link.html deleted file mode 100644 index b59a45a2d..000000000 --- a/services/API-service/src/api/notification/email/html/social-media-link.html +++ /dev/null @@ -1,14 +0,0 @@ - - - Join <%- socialMediaType %> - -   - - Communicate with relevant others about the trigger. - - diff --git a/services/API-service/src/api/notification/email/html/styles.ejs b/services/API-service/src/api/notification/email/html/styles.ejs deleted file mode 100644 index cf94f066b..000000000 --- a/services/API-service/src/api/notification/email/html/styles.ejs +++ /dev/null @@ -1,152 +0,0 @@ - diff --git a/services/API-service/src/api/notification/email/html/table-event.html b/services/API-service/src/api/notification/email/html/table-event.html deleted file mode 100644 index 31102d687..000000000 --- a/services/API-service/src/api/notification/email/html/table-event.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - -
-
-
- Warning icon - <%= severityLabel %> <%= triggerStatusLabel %> <%= disasterTypeLabel - %>: <%= eventName %> -
- <% if (isIndicatorAvailable) { %> -
- Expected exposed <%= defaultAdminAreaLabelPlural %> in order of <%= - indicatorLabel.toLowerCase() %>: -
-
- - - - - - <%- tableRows %> -
- <%= indicatorLabel %> - - <%= defaultAdminAreaLabelSingular %> <% if - (defaultAdminAreaLabelParent != defaultAdminAreaLabelSingular) { - %>(<%= defaultAdminAreaLabelParent %>)<% } %> -
-
- <% } else { %> -
- <%= indicatorLabel %> information is unavailable. -
- <% } %> -
-
-
diff --git a/services/API-service/src/api/notification/email/html/table-row.html b/services/API-service/src/api/notification/email/html/table-row.html deleted file mode 100644 index 2579e03d5..000000000 --- a/services/API-service/src/api/notification/email/html/table-row.html +++ /dev/null @@ -1,9 +0,0 @@ - - - <%= affectedOfIndicator %> - - - <%= adminBoundary %> <% if (higherAdminBoundary) { %>(<%= - higherAdminBoundary %>)<% } %> - - diff --git a/services/API-service/src/api/notification/email/html/trigger-finished.html b/services/API-service/src/api/notification/email/html/trigger-finished.html deleted file mode 100644 index fbd5ff3ea..000000000 --- a/services/API-service/src/api/notification/email/html/trigger-finished.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - -
- - - - - - -
- - - - - - -
- - - - - - - - -
-
-
-
-
Dear Reader,
-

- - <%- eventOverview %> <%- notificationActions %> - <%- footer %> -
-
-
- - -
-
- - - - diff --git a/services/API-service/src/api/notification/email/html/trigger-notification.html b/services/API-service/src/api/notification/email/html/trigger-notification.html deleted file mode 100644 index c1a052490..000000000 --- a/services/API-service/src/api/notification/email/html/trigger-notification.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - -
- - - - - - -
- - - - - - -
- - - - - - - - -
-
-
-
-
Dear Reader,
-
- <%- eventListBody %> <%- notificationActions %> -
- Trigger Statement: - <%- triggerStatement %> -
- <%- mapImagePart %> -
-
-
- All numbers are approximate and meant to be used - as guidance. -
-
- <%- tablesStacked %> <%- footer %> -
-
-
- - -
-
- - - - diff --git a/services/API-service/src/api/notification/email/logos/email-logo-ETH.png b/services/API-service/src/api/notification/email/logos/email-logo-ETH.png index 48a1689dc..637b0e069 100644 Binary files a/services/API-service/src/api/notification/email/logos/email-logo-ETH.png and b/services/API-service/src/api/notification/email/logos/email-logo-ETH.png differ diff --git a/services/API-service/src/api/notification/email/logos/email-logo-KEN.png b/services/API-service/src/api/notification/email/logos/email-logo-KEN.png index a303e2f24..bd327826b 100644 Binary files a/services/API-service/src/api/notification/email/logos/email-logo-KEN.png and b/services/API-service/src/api/notification/email/logos/email-logo-KEN.png differ diff --git a/services/API-service/src/api/notification/email/logos/email-logo-MWI-flash_floods.png b/services/API-service/src/api/notification/email/logos/email-logo-MWI-flash_floods.png new file mode 100644 index 000000000..113156d70 Binary files /dev/null and b/services/API-service/src/api/notification/email/logos/email-logo-MWI-flash_floods.png differ diff --git a/services/API-service/src/api/notification/email/logos/email-logo-MWI-floods.png b/services/API-service/src/api/notification/email/logos/email-logo-MWI-floods.png new file mode 100644 index 000000000..249e531aa Binary files /dev/null and b/services/API-service/src/api/notification/email/logos/email-logo-MWI-floods.png differ diff --git a/services/API-service/src/api/notification/email/logos/email-logo-MWI.png b/services/API-service/src/api/notification/email/logos/email-logo-MWI.png deleted file mode 100644 index 290b59656..000000000 Binary files a/services/API-service/src/api/notification/email/logos/email-logo-MWI.png and /dev/null differ diff --git a/services/API-service/src/api/notification/email/logos/email-logo-PHL.jpg b/services/API-service/src/api/notification/email/logos/email-logo-PHL.jpg deleted file mode 100644 index 15a606ff2..000000000 Binary files a/services/API-service/src/api/notification/email/logos/email-logo-PHL.jpg and /dev/null differ diff --git a/services/API-service/src/api/notification/email/logos/email-logo-PHL.png b/services/API-service/src/api/notification/email/logos/email-logo-PHL.png new file mode 100644 index 000000000..148f909f0 Binary files /dev/null and b/services/API-service/src/api/notification/email/logos/email-logo-PHL.png differ diff --git a/services/API-service/src/api/notification/email/logos/email-logo-SSD.png b/services/API-service/src/api/notification/email/logos/email-logo-SSD.png index 78c3a4ea5..7ff06a3f3 100644 Binary files a/services/API-service/src/api/notification/email/logos/email-logo-SSD.png and b/services/API-service/src/api/notification/email/logos/email-logo-SSD.png differ diff --git a/services/API-service/src/api/notification/email/logos/email-logo-UGA.png b/services/API-service/src/api/notification/email/logos/email-logo-UGA.png index 115ce8526..212536b60 100644 Binary files a/services/API-service/src/api/notification/email/logos/email-logo-UGA.png and b/services/API-service/src/api/notification/email/logos/email-logo-UGA.png differ diff --git a/services/API-service/src/api/notification/email/logos/email-logo-ZMB.PNG b/services/API-service/src/api/notification/email/logos/email-logo-ZMB.PNG index 7577662df..c9aa268d7 100644 Binary files a/services/API-service/src/api/notification/email/logos/email-logo-ZMB.PNG and b/services/API-service/src/api/notification/email/logos/email-logo-ZMB.PNG differ diff --git a/services/API-service/src/api/notification/email/logos/email-logo-ZWE.png b/services/API-service/src/api/notification/email/logos/email-logo-ZWE.png index 430858f1f..b253fdf3f 100644 Binary files a/services/API-service/src/api/notification/email/logos/email-logo-ZWE.png and b/services/API-service/src/api/notification/email/logos/email-logo-ZWE.png differ diff --git a/services/API-service/src/api/notification/email/mjml.service.ts b/services/API-service/src/api/notification/email/mjml.service.ts new file mode 100644 index 000000000..b00719976 --- /dev/null +++ b/services/API-service/src/api/notification/email/mjml.service.ts @@ -0,0 +1,192 @@ +import { Injectable } from '@nestjs/common'; + +import mjml2html from 'mjml'; + +import { HelperService } from '../../../shared/helper.service'; +import { ContentEventEmail } from '../dto/content-trigger-email.dto'; +import { + BODY_WIDTH, + EMAIL_HEAD, + getFormattedDate, + getSectionElement, + getTextElement, + getTimezoneDisplay, +} from '../helpers/mjml.helper'; +import { getMjmlEventListBody } from './mjml/body-event'; +import { + getMjmlAdminAreaDisclaimer, + getMjmlAdminAreaTableList, +} from './mjml/event-admin-area-table'; +import { getMjmlFinishedEvents } from './mjml/event-finished'; +import { getIbfFooter, getMailchimpFooter } from './mjml/footer'; +import { getMjmlHeader } from './mjml/header'; +import { getMjmlMapImages } from './mjml/map-image'; +import { getMjmlNotificationAction } from './mjml/notification-actions'; +import { getMjmlTriggerStatement } from './mjml/trigger-statement'; + +@Injectable() +export class MjmlService { + public constructor(private readonly helperService: HelperService) {} + + private mailOpening = getSectionElement({ + childrenEls: [getTextElement({ content: 'Dear reader,' })], + attributes: { padding: '16px 8px 8px' }, + }); + + private header = ({ + emailContent, + date, + }: { + emailContent: ContentEventEmail; + date: Date; + }) => + getMjmlHeader({ + disasterTypeLabel: emailContent.disasterTypeLabel, + nrOfEvents: emailContent.dataPerEvent.length, + sentOnDate: getFormattedDate({ date }), + logosSrc: + emailContent.country.notificationInfo.logo[emailContent.disasterType], + }); + + private footer = ({ countryName }: { countryName: string }) => [ + getIbfFooter({ countryName }), + getMailchimpFooter(), + ]; + + private notificationAction = ({ + linkDashboard, + linkEapSop, + socialMediaLink, + socialMediaType, + }: { + linkDashboard: string; + linkEapSop: string; + socialMediaLink: string; + socialMediaType: string; + }) => + getMjmlNotificationAction({ + linkDashboard, + linkEapSop, + socialMediaLink, + socialMediaType, + }); + + public getTriggerEmailHtmlOutput({ + emailContent, + date, + }: { + emailContent: ContentEventEmail; + date: Date; + }): string { + const children = []; + + children.push(this.header({ emailContent, date })); + + children.push(this.mailOpening); + + children.push( + ...getMjmlEventListBody(emailContent, this.helperService.toCompactNumber), + ); + + children.push( + this.notificationAction({ + linkDashboard: process.env.DASHBOARD_URL, + linkEapSop: emailContent.linkEapSop, + socialMediaLink: + emailContent.country.notificationInfo.linkSocialMediaUrl ?? '', + socialMediaType: + emailContent.country.notificationInfo.linkSocialMediaType ?? '', + }), + ); + + children.push( + getMjmlTriggerStatement({ + triggerStatement: + emailContent.country.notificationInfo.triggerStatement[ + emailContent.disasterType + ], + }), + ); + + children.push(...getMjmlMapImages(emailContent)); + + children.push( + getMjmlAdminAreaDisclaimer(), + ...getMjmlAdminAreaTableList( + emailContent, + this.helperService.toCompactNumber, + ), + ); + + children.push( + ...this.footer({ countryName: emailContent.country.countryName }), + ); + + const emailObject = { + tagName: 'mjml', + attributes: {}, + children: [ + EMAIL_HEAD, + { + tagName: 'mj-body', + children, + attributes: { width: BODY_WIDTH, padding: '0 20px' }, + }, + ], + }; + + return mjml2html(emailObject).html; + } + + public getEventFinishedEmailHtmlOutput({ + emailContent, + date, + }: { + emailContent: ContentEventEmail; + date: Date; + }): string { + const children = []; + + children.push(this.header({ emailContent, date })); + + children.push(this.mailOpening); + + children.push( + ...getMjmlFinishedEvents({ + disasterType: emailContent.disasterTypeLabel, + dataPerEvent: emailContent.dataPerEvent, + timezone: getTimezoneDisplay(emailContent.country.countryCodeISO3), + }), + ); + + children.push( + this.notificationAction({ + linkDashboard: process.env.DASHBOARD_URL, + linkEapSop: emailContent.linkEapSop, + socialMediaLink: + emailContent.country.notificationInfo.linkSocialMediaUrl ?? '', + socialMediaType: + emailContent.country.notificationInfo.linkSocialMediaType ?? '', + }), + ); + + children.push( + ...this.footer({ countryName: emailContent.country.countryName }), + ); + + const emailObject = { + tagName: 'mjml', + attributes: {}, + children: [ + EMAIL_HEAD, + { + tagName: 'mj-body', + children, + attributes: { width: BODY_WIDTH, padding: '0 20px' }, + }, + ], + }; + + return mjml2html(emailObject).html; + } +} diff --git a/services/API-service/src/api/notification/email/mjml/body-event.ts b/services/API-service/src/api/notification/email/mjml/body-event.ts new file mode 100644 index 000000000..3eef33b95 --- /dev/null +++ b/services/API-service/src/api/notification/email/mjml/body-event.ts @@ -0,0 +1,167 @@ +import { ContentEventEmail } from '../../dto/content-trigger-email.dto'; +import { TriggerStatusLabelEnum } from '../../dto/notification-date-per-event.dto'; +import { + dateObjectToDateTimeString, + getDisasterIssuedLabel, + getIbfHexColor, + getInlineImage, + getSectionElement, + getTextElement, + getTimeFromNow, + getTimezoneDisplay, + getTotalAffected, + getTriangleIcon, +} from '../../helpers/mjml.helper'; + +const getMjmlBodyEvent = ({ + color, + defaultAdminAreaLabel, + disasterIssuedLabel, + disasterTypeLabel, + eventName, + firstLeadTimeFromNow, + firstLeadTimeString, + firstTriggerLeadTimeFromNow, + firstTriggerLeadTimeString, + indicatorLabel, + issuedDate, + nrOfTriggeredAreas, + timeZone, + totalAffected, + triangleIcon, + eapLink, + triggerStatusLabel, + toCompactNumber, +}: { + color: string; + defaultAdminAreaLabel: string; + disasterIssuedLabel: string; + disasterTypeLabel: string; + eventName: string; + firstLeadTimeFromNow: string; + firstLeadTimeString: string; + firstTriggerLeadTimeFromNow: string; + firstTriggerLeadTimeString: string; + indicatorLabel: string; + issuedDate: string; + nrOfTriggeredAreas: number; + timeZone: string; + totalAffected: number; + triangleIcon: string; + eapLink: string; + triggerStatusLabel: string; + toCompactNumber: (value: number) => string; +}): object => { + const icon = getInlineImage({ src: triangleIcon, size: 16 }); + + const eventNameElement = getTextElement({ + attributes: { color }, + content: `${icon} ${disasterTypeLabel}: ${eventName}`, + }); + + const contentContent = []; + + contentContent.push( + firstTriggerLeadTimeString + ? `${disasterTypeLabel}: Expected to start on ${firstLeadTimeString}, ${firstLeadTimeFromNow}.` + : `${disasterIssuedLabel}: Expected on ${firstLeadTimeString}, ${firstLeadTimeFromNow}.`, + ); + + if (firstTriggerLeadTimeString) { + contentContent.push( + `${disasterIssuedLabel}: Expected to trigger on ${firstTriggerLeadTimeString}, ${firstTriggerLeadTimeFromNow}.`, + ); + } + + contentContent.push( + `Expected exposed ${defaultAdminAreaLabel}: ${nrOfTriggeredAreas} (see list below)`, + ), + contentContent.push( + `${indicatorLabel}: ${ + totalAffected + ? `Approximately ${toCompactNumber( + totalAffected, + )} ${indicatorLabel.toLowerCase()}` + : 'Information is unavailable' + }`, + ); + + contentContent.push( + triggerStatusLabel === TriggerStatusLabelEnum.Trigger + ? `Advisory: Activate Early Action Protocol` + : `Advisory: Inform all potentialy exposed ${defaultAdminAreaLabel}`, + ); + + const contentElement = getTextElement({ + content: contentContent.join('
'), + }); + + const closingElement = getTextElement({ + content: `This ${triggerStatusLabel} was issued by IBF on ${issuedDate} (${timeZone})`, + attributes: { + 'padding-top': '8px', + 'font-size': '14px', + }, + }); + + return getSectionElement({ + childrenEls: [eventNameElement, contentElement, closingElement], + attributes: { padding: '8px' }, + }); +}; + +export const getMjmlEventListBody = ( + emailContent: ContentEventEmail, + toCompactNumber: (value: number) => string, +): object[] => { + const eventList = []; + + for (const event of emailContent.dataPerEvent) { + eventList.push( + getMjmlBodyEvent({ + eventName: event.eventName, + + disasterTypeLabel: emailContent.disasterTypeLabel, + triggerStatusLabel: event.triggerStatusLabel, + issuedDate: dateObjectToDateTimeString( + event.issuedDate, + emailContent.country.countryCodeISO3, + ), + timeZone: getTimezoneDisplay(emailContent.country.countryCodeISO3), + + // Lead time details + firstLeadTimeString: event.firstLeadTimeString, + firstTriggerLeadTimeString: event.firstTriggerLeadTimeString, + firstLeadTimeFromNow: getTimeFromNow(event.firstLeadTime), + firstTriggerLeadTimeFromNow: getTimeFromNow(event.firstTriggerLeadTime), + + // Area details + nrOfTriggeredAreas: event.nrOfTriggeredAreas, + defaultAdminAreaLabel: + emailContent.defaultAdminAreaLabel.plural.toLocaleLowerCase(), + + // Indicator details + indicatorLabel: emailContent.indicatorMetadata.label, + totalAffected: getTotalAffected(event), + toCompactNumber: toCompactNumber, + + // EAP details + triangleIcon: getTriangleIcon( + event.eapAlertClass?.key, + event.triggerStatusLabel, + ), + eapLink: emailContent.linkEapSop, + + disasterIssuedLabel: getDisasterIssuedLabel( + event.eapAlertClass?.label, + event.triggerStatusLabel, + ), + color: getIbfHexColor( + event.eapAlertClass?.color, + event.triggerStatusLabel, + ), + }), + ); + } + return eventList; +}; diff --git a/services/API-service/src/api/notification/email/mjml/event-admin-area-table.ts b/services/API-service/src/api/notification/email/mjml/event-admin-area-table.ts new file mode 100644 index 000000000..a697ac020 --- /dev/null +++ b/services/API-service/src/api/notification/email/mjml/event-admin-area-table.ts @@ -0,0 +1,145 @@ +import { IndicatorMetadataEntity } from '../../../metadata/indicator-metadata.entity'; +import { AdminAreaLabel } from '../../dto/admin-area-notification-info.dto'; +import { ContentEventEmail } from '../../dto/content-trigger-email.dto'; +import { NotificationDataPerEventDto } from '../../dto/notification-date-per-event.dto'; +import { + COLOR_WHITE, + getAdminAreaTable, + getEventSeverityLabel, + getIbfHexColor, + getInlineImage, + getSectionElement, + getTextElement, + getTriangleIcon, +} from '../../helpers/mjml.helper'; + +const getMjmlEventAdminAreaTable = ({ + disasterTypeLabel, + color, + defaultAdminAreaLabel, + defaultAdminAreaParentLabel, + indicatorMetadata, + event, + triangleIcon, + toCompactNumber, +}: { + disasterTypeLabel: string; + color: string; + defaultAdminAreaLabel: AdminAreaLabel; + defaultAdminAreaParentLabel: AdminAreaLabel; + indicatorMetadata: IndicatorMetadataEntity; + event: NotificationDataPerEventDto; + triangleIcon: string; + toCompactNumber: (value: number) => string; +}) => { + const icon = getInlineImage({ src: triangleIcon, size: 16 }); + + const severityLabel = getEventSeverityLabel(event.eapAlertClass?.key); + + const titleElement = getTextElement({ + content: `${icon} ${severityLabel} ${event.triggerStatusLabel} ${disasterTypeLabel}: ${event.eventName}`, + attributes: { + color, + 'container-background-color': COLOR_WHITE, + align: 'center', + padding: '8px 24px', + }, + }); + + const isIndicatorAvailable = event.triggeredAreas.some( + ({ actionsValue }) => actionsValue, + ); + + const subtitleContent = isIndicatorAvailable + ? `Expected exposed ${defaultAdminAreaLabel.plural.toLowerCase()} in order of ${indicatorMetadata.label.toLowerCase()}:` + : `${indicatorMetadata.label} information is unavailable.`; + + const subtitleElement = getTextElement({ + content: subtitleContent, + attributes: { + 'container-background-color': COLOR_WHITE, + align: 'center', + padding: isIndicatorAvailable ? '0' : '0 0 8px', + 'font-size': '14px', + }, + }); + + const adminAreaList = event.triggeredAreas + .filter(({ actionsValue }) => actionsValue) + .map((triggeredArea) => { + return { + exposed: toCompactNumber(triggeredArea.actionsValue), + name: `${triggeredArea.name} ${ + triggeredArea.nameParent ? `(${triggeredArea.nameParent})` : '' + }`, + }; + }); + + const adminAreaTable = getAdminAreaTable({ + adminAreaList, + adminAreaLabel: defaultAdminAreaLabel.singular, + adminAreaParentLabel: defaultAdminAreaParentLabel.singular, + indicatorLabel: indicatorMetadata.label, + }); + + const childrenEls = [titleElement, subtitleElement]; + + if (isIndicatorAvailable) { + childrenEls.push(adminAreaTable); + } + + return getSectionElement({ + childrenEls, + attributes: { padding: '8px' }, + }); +}; + +export const getMjmlAdminAreaDisclaimer = (): object => { + return getSectionElement({ + childrenEls: [ + getTextElement({ + content: + 'All numbers are approximate and meant to be used as guidance.', + attributes: { + align: 'center', + 'font-size': '14px', + }, + }), + ], + attributes: { padding: '8px' }, + }); +}; + +export const getMjmlAdminAreaTableList = ( + emailContent: ContentEventEmail, + toCompactNumber: (value: number) => string, +): object[] => { + const adminAreaTableList = []; + + const adminAreaParentLabel = + emailContent.country.adminRegionLabels[ + String(Math.max(1, emailContent.defaultAdminLevel - 1)) + ]; + + for (const event of emailContent.dataPerEvent) { + adminAreaTableList.push( + getMjmlEventAdminAreaTable({ + disasterTypeLabel: emailContent.disasterTypeLabel, + color: getIbfHexColor( + event.eapAlertClass?.color, + event.triggerStatusLabel, + ), + defaultAdminAreaLabel: emailContent.defaultAdminAreaLabel, + defaultAdminAreaParentLabel: adminAreaParentLabel, + indicatorMetadata: emailContent.indicatorMetadata, + event, + triangleIcon: getTriangleIcon( + event.eapAlertClass?.key, + event.triggerStatusLabel, + ), + toCompactNumber, + }), + ); + } + return adminAreaTableList; +}; diff --git a/services/API-service/src/api/notification/email/mjml/event-finished.ts b/services/API-service/src/api/notification/email/mjml/event-finished.ts new file mode 100644 index 000000000..e428f7995 --- /dev/null +++ b/services/API-service/src/api/notification/email/mjml/event-finished.ts @@ -0,0 +1,63 @@ +import { NotificationDataPerEventDto } from '../../dto/notification-date-per-event.dto'; +import { + getFormattedDate, + getSectionElement, + getTextElement, +} from '../../helpers/mjml.helper'; + +const getMjmlFinishedEvent = ({ + disasterTypeLabel, + eventName, + issuedDate, + timezone, +}: { + disasterTypeLabel: string; + eventName: string; + issuedDate: string; + timezone: string; +}): object => { + const belowThresholdText = `${disasterTypeLabel}: ${eventName} is now below threshold`; + const portalText = + 'The events actions will continue to show on the IBF portal and can still be managed.'; + + const eventEndedElement = getTextElement({ + content: `${belowThresholdText}
${portalText}`, + }); + + const pleaseNoteElement = getTextElement({ + content: + 'Please note: The event will close when the forecast stays below threshold for 7 days in a row and will no longer show in the IBF portal', + }); + + const warningIssuedDate = getTextElement({ + content: `This warning was issued by the IBF portal on ${issuedDate} (${timezone})`, + }); + + return getSectionElement({ + childrenEls: [eventEndedElement, pleaseNoteElement, warningIssuedDate], + attributes: { padding: '8px' }, + }); +}; + +export const getMjmlFinishedEvents = ({ + disasterType, + dataPerEvent, + timezone, +}: { + disasterType: string; + dataPerEvent: NotificationDataPerEventDto[]; + timezone: string; +}): object[] => { + const finishedEvents = []; + for (const event of dataPerEvent) { + finishedEvents.push( + getMjmlFinishedEvent({ + disasterTypeLabel: disasterType, + eventName: event.eventName, + issuedDate: getFormattedDate({ date: event.issuedDate }), + timezone: timezone, + }), + ); + } + return finishedEvents; +}; diff --git a/services/API-service/src/api/notification/email/mjml/footer.ts b/services/API-service/src/api/notification/email/mjml/footer.ts new file mode 100644 index 000000000..f275cb909 --- /dev/null +++ b/services/API-service/src/api/notification/email/mjml/footer.ts @@ -0,0 +1,81 @@ +import { + COLOR_BROWN, + COLOR_WHITE, + getImageElement, + getLogoImageAsDataURL, + getSectionElement, + getTextElement, +} from '../../helpers/mjml.helper'; + +export const getIbfFooter = ({ + countryName, +}: { + countryName: string; +}): object => { + const logoElement = getImageElement({ + src: getLogoImageAsDataURL(), + attributes: { width: '45px', height: '45px' }, + }); + + const textElement = getTextElement({ + content: `Impact-Based Forecasting Portal (IBF) was co-developed by Netherlands + Red Cross 510 the together with the ${countryName} Red Cross + National Society. For questions contact us at ibf-support@510.global`, + attributes: { 'padding-top': '8px', 'font-size': '12px' }, + }); + + const twoColumnSectionElement = { + tagName: 'mj-section', + children: [ + { + tagName: 'mj-column', + children: [logoElement], + attributes: { width: '20%' }, + }, + { + tagName: 'mj-column', + children: [textElement], + attributes: { width: '80%' }, + }, + ], + attributes: { padding: '0 8px' }, + }; + + return getSectionElement({ + childrenEls: [twoColumnSectionElement], + }); +}; + +export const getMailchimpFooter = (): object => { + const mailchimpFooter = getTextElement({ + content: ` +

+ + Click here to unsubscribe from IBF alerts + +   + *|LIST:ADDRESSLINE|* +

+

*|IF:REWARDS|* *|HTML:REWARDS|* *|END:IF|*

+ `, + attributes: { + color: COLOR_WHITE, + align: 'center', + 'font-size': '12px', + }, + }); + + const mailchimpColumn = { + tagName: 'mj-column', + children: [mailchimpFooter], + attributes: { + padding: '0 8px', + width: '100%', + }, + }; + + return getSectionElement({ + childrenEls: [mailchimpColumn], + attributes: { padding: '0', 'background-color': COLOR_BROWN }, + }); +}; diff --git a/services/API-service/src/api/notification/email/mjml/header.ts b/services/API-service/src/api/notification/email/mjml/header.ts new file mode 100644 index 000000000..64398b7fc --- /dev/null +++ b/services/API-service/src/api/notification/email/mjml/header.ts @@ -0,0 +1,47 @@ +import { + COLOR_PRIMARY, + COLOR_WHITE, + getSectionElement, + getTextElement, +} from '../../helpers/mjml.helper'; + +export const getMjmlHeader = ({ + disasterTypeLabel, + nrOfEvents, + sentOnDate, + logosSrc, +}: { + disasterTypeLabel: string; + nrOfEvents: number; + sentOnDate: string; + logosSrc: string; +}): object => { + const logosElement = { + tagName: 'mj-raw', + content: ``, + }; + + const titleElement = getTextElement({ + content: `${nrOfEvents} ${disasterTypeLabel} alerts`, + attributes: { + color: COLOR_WHITE, + 'font-size': '28px', + 'font-weight': 'bold', + 'padding-top': '14px', + align: 'center', + }, + }); + + const subtitleElement = getTextElement({ + content: `IBF alert sent on ${sentOnDate}`, + attributes: { + color: COLOR_WHITE, + align: 'center', + }, + }); + + return getSectionElement({ + childrenEls: [logosElement, titleElement, subtitleElement], + attributes: { 'background-color': COLOR_PRIMARY }, + }); +}; diff --git a/services/API-service/src/api/notification/email/mjml/map-image.ts b/services/API-service/src/api/notification/email/mjml/map-image.ts new file mode 100644 index 000000000..aa756888a --- /dev/null +++ b/services/API-service/src/api/notification/email/mjml/map-image.ts @@ -0,0 +1,55 @@ +import { ContentEventEmail } from '../../dto/content-trigger-email.dto'; +import { + getImageElement, + getMapImageDescription, + getMapImgSrc, + getSectionElement, + getTextElement, +} from '../../helpers/mjml.helper'; + +const getMjmlMapImage = ({ + eventName, + mapImgDescription, + src, +}: { + eventName: string; + mapImgDescription: string; + src: string; +}): object => { + const titleElement = getTextElement({ + content: `Map of the triggered area ${eventName}`, + }); + + const descriptionElement = getTextElement({ + content: `${mapImgDescription}`, + }); + + const mapImageElement = getImageElement({ + src, + attributes: { width: '600px' }, + }); + + return getSectionElement({ + childrenEls: [titleElement, descriptionElement, mapImageElement], + }); +}; + +export const getMjmlMapImages = (emailContent: ContentEventEmail): object[] => { + const mapImages = []; + for (const event of emailContent.dataPerEvent.filter( + (event) => event.mapImage, + )) { + mapImages.push( + getMjmlMapImage({ + src: getMapImgSrc( + emailContent.country.countryCodeISO3, + emailContent.disasterType, + event.eventName, + ), + mapImgDescription: getMapImageDescription(emailContent.disasterType), + eventName: event.eventName ? `(for ${event.eventName})` : '', + }), + ); + } + return mapImages; +}; diff --git a/services/API-service/src/api/notification/email/mjml/notification-actions.ts b/services/API-service/src/api/notification/email/mjml/notification-actions.ts new file mode 100644 index 000000000..e2195bff9 --- /dev/null +++ b/services/API-service/src/api/notification/email/mjml/notification-actions.ts @@ -0,0 +1,43 @@ +import { + getNotificationActionsSection, + getSectionElement, +} from '../../helpers/mjml.helper'; + +export const getMjmlNotificationAction = ({ + linkDashboard, + linkEapSop, + socialMediaLink, + socialMediaType, +}: { + linkDashboard: string; + linkEapSop: string; + socialMediaLink: string; + socialMediaType: string; +}): object => { + const ibfPortalRow = getNotificationActionsSection({ + buttonText: 'Open IBF', + buttonLink: linkDashboard, + description: + 'Find more information about the potentially exposed areas, view the map and manage anticipatory actions.', + primary: true, + }); + + const socialMediaRow = getNotificationActionsSection({ + buttonText: `Join ${socialMediaType}`, + buttonLink: socialMediaLink, + description: 'Communicate with relevant others about the trigger.', + primary: false, + }); + + const aboutTriggerRow = getNotificationActionsSection({ + buttonText: `About Trigger`, + buttonLink: linkEapSop, + description: + 'Read about the trigger methodology and the anticipatory actions.', + primary: false, + }); + + return getSectionElement({ + childrenEls: [ibfPortalRow, socialMediaRow, aboutTriggerRow], + }); +}; diff --git a/services/API-service/src/api/notification/email/mjml/trigger-statement.ts b/services/API-service/src/api/notification/email/mjml/trigger-statement.ts new file mode 100644 index 000000000..2c9b53ab9 --- /dev/null +++ b/services/API-service/src/api/notification/email/mjml/trigger-statement.ts @@ -0,0 +1,13 @@ +import { getSectionElement, getTextElement } from '../../helpers/mjml.helper'; + +export const getMjmlTriggerStatement = ({ + triggerStatement, +}: { + triggerStatement: string; +}): object => { + const textElement = getTextElement({ + content: `Trigger Statement: ${triggerStatement}`, + }); + + return getSectionElement({ childrenEls: [textElement] }); +}; diff --git a/services/API-service/src/api/notification/helpers/mjml.helper.ts b/services/API-service/src/api/notification/helpers/mjml.helper.ts new file mode 100644 index 000000000..3a7a630f1 --- /dev/null +++ b/services/API-service/src/api/notification/helpers/mjml.helper.ts @@ -0,0 +1,401 @@ +import * as fs from 'fs'; + +import { format } from 'date-fns'; + +import { EapAlertClassKeyEnum } from '../../../shared/data.model'; +import { LeadTime } from '../../admin-area-dynamic-data/enum/lead-time.enum'; +import { CountryTimeZoneMapping } from '../../country/country-time-zone-mapping'; +import { DisasterType } from '../../disaster/disaster-type.enum'; +import { + NotificationDataPerEventDto, + TriggerStatusLabelEnum, +} from '../dto/notification-date-per-event.dto'; + +interface AdminArea { + exposed?: string; + name: string; +} + +export const BODY_WIDTH = '768px'; +export const COLOR_PRIMARY = '#4f22d7'; +export const COLOR_TERTIARY = '#cfbfff'; +export const COLOR_WHITE = '#ffffff'; +export const COLOR_GREY = '#f4f5f8'; +export const COLOR_BROWN = '#241C15'; +const COLOR_WARNING_ORANGE = '#aa6009'; +const COLOR_WARNING_YELLOW = '#665606'; +const COLOR_TRIGGER_RED = '#8a0f32'; + +const emailFolder = './src/api/notification/email'; +const emailIconFolder = `${emailFolder}/icons`; +const emailLogoFolder = `${emailFolder}/logos`; + +const HEAD_ATTRIBUTES = { + tagName: 'mj-attributes', + children: [ + { + tagName: 'mj-all', + attributes: { 'font-family': 'Arial, sans-serif' }, + }, + { + tagName: 'mj-text', + attributes: { 'font-size': '16px' }, + }, + ], +}; + +export const EMAIL_HEAD = { + tagName: 'mj-head', + children: [ + HEAD_ATTRIBUTES, + { + tagName: 'mj-breakpoint', + attributes: { width: '520px' }, + }, + ], +}; + +export const getSectionElement = ({ + childrenEls, + backgroundColor = COLOR_GREY, + attributes, +}: { + childrenEls: object[]; + backgroundColor?: string; + attributes?: object; +}) => { + return { + tagName: 'mj-section', + children: [ + { + tagName: 'mj-column', + children: childrenEls, + }, + ], + attributes: { + 'full-width': 'full-width', + padding: '16px', + 'background-color': backgroundColor, + ...attributes, + }, + }; +}; + +export const getTextElement = ({ + content, + attributes, +}: { + content: string; + attributes?: object; +}): { tagName: string; content: string; attributes?: object } => { + return { + tagName: 'mj-text', + content, + attributes: { + 'line-height': '1.5', + padding: '0', + ...attributes, + }, + }; +}; + +export const getNotificationActionsSection = ({ + buttonText, + buttonLink, + description, + primary, +}: { + buttonText: string; + buttonLink: string; + description: string; + primary: boolean; +}): object => { + return { + tagName: 'mj-section', + attributes: { + padding: '0', + 'text-align': 'left', + }, + children: [ + { + tagName: 'mj-column', + attributes: { + 'vertical-align': 'middle', + padding: '8px 0 8px 0', + width: '224px', + }, + children: [ + { + tagName: 'mj-raw', + content: ``, + }, + { + tagName: 'mj-button', + attributes: { + href: buttonLink, + width: '200px', + height: '24px', + 'border-radius': '24px', + padding: '0', + 'font-size': '16px', + 'background-color': primary ? COLOR_PRIMARY : COLOR_WHITE, + color: primary ? COLOR_WHITE : COLOR_PRIMARY, + border: `1px solid ${primary ? COLOR_PRIMARY : COLOR_TERTIARY}`, + }, + content: buttonText, + }, + { + tagName: 'mj-raw', + content: '', + }, + ], + }, + { + tagName: 'mj-column', + attributes: { + width: '512px', + 'vertical-align': 'middle', + padding: '4px', + }, + children: [ + getTextElement({ + content: description, + }), + ], + }, + ], + }; +}; + +export const getAdminAreaTable = ({ + indicatorLabel, + adminAreaLabel, + adminAreaParentLabel, + adminAreaList, +}: { + indicatorLabel: string; + adminAreaLabel: string; + adminAreaParentLabel: string; + adminAreaList: AdminArea[]; +}) => { + const header = ` + + ${indicatorLabel} + ${adminAreaLabel} ${ + adminAreaLabel === adminAreaParentLabel ? '' : `(${adminAreaParentLabel})` + } + + `; + + const row = (adminArea: AdminArea) => ` + + ${adminArea.exposed} + ${adminArea.name} + + `; + const rows = adminAreaList.map((adminArea) => row(adminArea)).join(''); + + const tbody = `${header}${rows}`; + + return { + tagName: 'mj-table', + content: tbody, + attributes: { + 'container-background-color': COLOR_WHITE, + cellpadding: '4', + 'table-layout': 'fixed', + }, + }; +}; + +export const getInlineImage = ({ + src, + size, +}: { + src: string; + size: number; +}): string => + ``; + +export const getImageElement = ({ + src, + attributes, +}: { + src: string; + attributes?: object; +}): object => { + return { + tagName: 'mj-image', + attributes: { src, ...attributes }, + }; +}; + +export const getMapImgSrc = ( + countryCodeISO3: string, + disasterType: DisasterType, + eventName: string, +): string => { + return `${ + process.env.NG_API_URL + }/event/event-map-image/${countryCodeISO3}/${disasterType}/${ + eventName || 'no-name' + }`; +}; + +export const getMapImageDescription = (disasterType: DisasterType): string => { + const descriptions = { + [DisasterType.Floods]: + 'The triggered areas are outlined in purple. The potential flood extent is shown in red.
', + }; + + return descriptions[disasterType] || ''; +}; + +export const dateObjectToDateTimeString = ( + date: Date, + countryCodeISO3: string, +): string => { + const timeZone = CountryTimeZoneMapping[countryCodeISO3]; + const options: Intl.DateTimeFormatOptions = { + weekday: 'long', + day: '2-digit', + month: 'long', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + timeZone: timeZone, + }; + return date.toLocaleString('default', options); +}; + +export const getTimezoneDisplay = (countryCodeISO3: string) => { + return CountryTimeZoneMapping[countryCodeISO3].split('_').join(' '); +}; + +export const getTimeFromNow = (leadTime: LeadTime) => { + if (!leadTime) return ''; + + const leadTimeQuantity = parseInt(leadTime.split('-')[0]); + + return [LeadTime.day0, LeadTime.month0, LeadTime.hour0].includes(leadTime) + ? 'ongoing' + : `${leadTime.replace('-', ' ')}${ + leadTimeQuantity === 1 ? '' : 's' + } from now`; +}; + +export const getTotalAffected = ( + event: NotificationDataPerEventDto, +): number | null => { + return event.totalAffectedOfIndicator ?? null; +}; + +export const getTriangleIcon = ( + eapAlertClassKey: EapAlertClassKeyEnum, + triggerStatusLabel: TriggerStatusLabelEnum, +) => { + const fileNameMap = { + [EapAlertClassKeyEnum.med]: 'warning-medium.png', + [EapAlertClassKeyEnum.min]: 'warning-low.png', + [EapAlertClassKeyEnum.max]: 'trigger.png', + default: 'trigger.png', + }; + + let fileName = eapAlertClassKey + ? fileNameMap[eapAlertClassKey] + : fileNameMap.default; + if ( + !eapAlertClassKey && + triggerStatusLabel !== TriggerStatusLabelEnum.Trigger + ) { + fileName = 'warning-medium.png'; + } + const filePath = `${emailIconFolder}/${fileName}`; + return getPngImageAsDataURL(filePath); +}; + +export const getEventSeverityLabel = ( + eapAlertClassKey: EapAlertClassKeyEnum, +) => { + const severityLabels = { + [EapAlertClassKeyEnum.med]: 'Medium', + [EapAlertClassKeyEnum.min]: 'Low', + }; + + return severityLabels[eapAlertClassKey] || ''; +}; + +export const getPngImageAsDataURL = (relativePath: string) => { + const imageBuffer = fs.readFileSync(relativePath); + const imageDataURL = `data:image/png;base64,${imageBuffer.toString( + 'base64', + )}`; + + return imageDataURL; +}; + +export const getDisasterIssuedLabel = ( + eapLabel: string, + triggerStatusLabel: TriggerStatusLabelEnum, +) => { + return eapLabel || triggerStatusLabel; +}; + +export const getIbfHexColor = ( + color: string, + triggerStatusLabel: TriggerStatusLabelEnum, +): string => { + // Color defined in the EAP Alert Class. This is only used for flood events + // For other events, the color is defined in the disaster settings + // So we decide it based on the trigger status label + + if (color) { + // TODO: Define in a place where FrontEnd and Backend can share this + switch (color) { + case 'ibf-orange': + return COLOR_WARNING_ORANGE; + case 'fiveten-yellow-500': + return COLOR_WARNING_YELLOW; + default: + return COLOR_TRIGGER_RED; + } + } + return triggerStatusLabel === TriggerStatusLabelEnum.Trigger + ? COLOR_TRIGGER_RED + : COLOR_WARNING_ORANGE; +}; + +export const getLogoImageAsDataURL = () => { + const filePath = `${emailLogoFolder}/logo-IBF.png`; + return getPngImageAsDataURL(filePath); +}; + +export const getFormattedDate = ({ + date, + countryCodeISO3, +}: { + date: Date; + countryCodeISO3?: string; +}): string => { + return `${format(date, 'EEEE, dd MMMM')}${ + countryCodeISO3 + ? CountryTimeZoneMapping[countryCodeISO3].split('_').join(' ') + : '' + }`; +}; diff --git a/services/API-service/src/api/notification/notification-content/notification-content.service.ts b/services/API-service/src/api/notification/notification-content/notification-content.service.ts index 4191228f8..24f592bac 100644 --- a/services/API-service/src/api/notification/notification-content/notification-content.service.ts +++ b/services/API-service/src/api/notification/notification-content/notification-content.service.ts @@ -297,7 +297,7 @@ export class NotificationContentService { return new Date(getNewDate[unit]).toLocaleDateString('default', { ...dayOption, - month: 'short', + month: 'long', year: 'numeric', }); } diff --git a/services/API-service/src/api/notification/notification.module.ts b/services/API-service/src/api/notification/notification.module.ts index 3aacc55aa..75b774d84 100644 --- a/services/API-service/src/api/notification/notification.module.ts +++ b/services/API-service/src/api/notification/notification.module.ts @@ -7,8 +7,8 @@ import { TyphoonTrackModule } from '../typhoon-track/typhoon-track.module'; import { UserModule } from '../user/user.module'; import { AdminAreaDynamicDataModule } from './../admin-area-dynamic-data/admin-area-dynamic-data.module'; import { EventModule } from './../event/event.module'; -import { EmailTemplateService } from './email/email-template.service'; import { EmailService } from './email/email.service'; +import { MjmlService } from './email/mjml.service'; import { NotificationInfoEntity } from './notifcation-info.entity'; import { NotificationContentModule } from './notification-content/notification-content.module'; import { NotificationController } from './notification.controller'; @@ -26,11 +26,6 @@ import { WhatsappModule } from './whatsapp/whatsapp.module'; TyphoonTrackModule, ], controllers: [NotificationController], - providers: [ - NotificationService, - EmailService, - EmailTemplateService, - HelperService, - ], + providers: [NotificationService, EmailService, MjmlService, HelperService], }) export class NotificationModule {} diff --git a/services/API-service/src/scripts/json/notification-info.json b/services/API-service/src/scripts/json/notification-info.json index b8346fd2b..a5a94e277 100644 --- a/services/API-service/src/scripts/json/notification-info.json +++ b/services/API-service/src/scripts/json/notification-info.json @@ -2,9 +2,9 @@ { "countryCodeISO3": "UGA", "logo": { - "drought": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/c44cb471-0595-454d-aad6-d1050553315f.png", - "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/c44cb471-0595-454d-aad6-d1050553315f.png", - "heavy-rain": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/c44cb471-0595-454d-aad6-d1050553315f.png" + "drought": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/79e11aea-b32e-38b3-8cdf-f07c7c98e58c.png", + "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/79e11aea-b32e-38b3-8cdf-f07c7c98e58c.png", + "heavy-rain": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/79e11aea-b32e-38b3-8cdf-f07c7c98e58c.png" }, "triggerStatement": { "floods": "URCS will activate this EAP when GloFAS issues a forecast of at least 60% probability (based on the different ensemble runs) of a 5-year return period flood occurring in flood prone districts, which will be anticipated to affect more than 1,000hh. The EAP will be triggered with a lead time of 7 days and a FAR of not more than 0.5.", @@ -34,9 +34,9 @@ { "countryCodeISO3": "PHL", "logo": { - "dengue": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/af6240bf-bdd3-9982-9ca5-44f7e5a71f01.jpg", - "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/af6240bf-bdd3-9982-9ca5-44f7e5a71f01.jpg", - "typhoon": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/af6240bf-bdd3-9982-9ca5-44f7e5a71f01.jpg" + "dengue": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/9b078528-da7b-9a8c-6f6a-686a5a705e0a.png", + "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/9b078528-da7b-9a8c-6f6a-686a5a705e0a.png", + "typhoon": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/9b078528-da7b-9a8c-6f6a-686a5a705e0a.png" }, "triggerStatement": { "floods": "The Flood Early Action Protocol will be triggered 3 days before the flood when the forecast of the GloFAS is predicting with more than 70% probability the occurrence of a 1 in 5 years flood (at least), in any of the 4 targeted river basins. Then the corresponding flood extent will be overlayed on the rice crop map to estimate the flood impact in the concerned river basin, allowing the selection of the municipalities where to focus the anticipatory actions (where for instance the predicted impact is more than 50% of crops possibly affected, or between 30-50%)", @@ -51,7 +51,7 @@ { "countryCodeISO3": "ZMB", "logo": { - "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/35e3a47b-65d0-9b15-ad8f-ac19300ef5b6.png" + "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/70b81676-d8bd-cd07-afb1-4e7d8da9581b.png" }, "triggerStatement": { "floods": "The trigger is activated if the daily issued GLOFAS forecast reports a water discharge that exceeds the threshold corresponding to a 10y return period flood in one or more GLOFAS stations. The EAP will be triggered with a lead time of 7 days." @@ -64,8 +64,8 @@ { "countryCodeISO3": "KEN", "logo": { - "drought": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/905748b3-7aaf-4b5e-b5b9-516ad6f4105a.png", - "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/905748b3-7aaf-4b5e-b5b9-516ad6f4105a.png" + "drought": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/69a16c94-f1a8-ff36-89d2-84f7336d5883.png", + "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/69a16c94-f1a8-ff36-89d2-84f7336d5883.png" }, "triggerStatement": { "floods": "This EAP will be triggered when GloFAS issues a forecast where a trigger level will be exceeded and the probability of exceedance is at least the probability indicated. Three trigger levels were defined for this EAP:
  • Stations in Nzoia Basin a 5-year return period discharge (5.3m level/469.8 m3/s) with 85 % probability of exceedance
  • Stations in Athi Basin a 5 year return period discharge period discharge (469.236 m3/s) with 85% probability of exceedance
  • Stations in Tana Basin a 5-year return period discharge with (5.1m/ 1191.6 m3/s) 85% probability of exceedance
", @@ -79,9 +79,9 @@ { "countryCodeISO3": "ETH", "logo": { - "drought": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/eedbd97e-52c1-4a16-8155-9b607ad05ad2.png", - "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/eedbd97e-52c1-4a16-8155-9b607ad05ad2.png", - "malaria": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/eedbd97e-52c1-4a16-8155-9b607ad05ad2.png" + "drought": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/08e2626c-fd79-9026-1324-280899b3f436.png", + "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/08e2626c-fd79-9026-1324-280899b3f436.png", + "malaria": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/08e2626c-fd79-9026-1324-280899b3f436.png" }, "triggerStatement": { "floods": "The ERCS will act based on the developed trigger table when the Global Floods awareness system forecast indicates a level of river discharge greater or equal to a level of 10 years return period with a probability of at least 75% forecast uncertainty within 7 days lead time period.", @@ -109,7 +109,7 @@ { "countryCodeISO3": "ZWE", "logo": { - "drought": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/ff56e5b1-f3be-1d68-be49-7898ba16be81.png" + "drought": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/71256230-3d58-35b8-b48a-4497c85ce32c.png" }, "triggerStatement": { "drought": "When the ENSO-based September forecast of the 510 Impact-based Forecast model anticipates a once-in-six year drought event, a first set of early actions addressing crop losses and livestock deaths is activated. In March, FEWSNET forecast of IPC 4 and above will trigger further actions devoted to reducing the impacts of livestock deaths and food insecurity." @@ -122,8 +122,8 @@ { "countryCodeISO3": "MWI", "logo": { - "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/575cc45e-7050-4b2d-d8bc-d1f278e42a66.png", - "flash-floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/ee9473a2-cac5-aec2-a2ee-cee872837098.jpeg" + "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/31600ce7-b5e8-992f-8f53-e58f1b5dc955.png", + "flash-floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/d628fa5d-f4fa-bc51-9977-d6b464ff003b.png" }, "triggerStatement": { "floods": "An administrative area is triggered based on two parameters from the 6-days GloFAS forecast on a daily basis: the return period of the forecasted flood and the probability of occurrence. The trigger will activate when GloFAS issues a forecast of at least 60% probability of occurrence of a 5 year return period flood within the next 6 days. The GloFAS flood forecast triggers except in the Traditional Areas where the False Alarm Ratio (FAR) exceeds the predetermined maximum value which is 0.5.", @@ -152,7 +152,7 @@ { "countryCodeISO3": "SSD", "logo": { - "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/093cd804-0be0-21f3-c712-f210dd0f7c05.png" + "floods": "https://mcusercontent.com/e71f3b134823403aa6fe0c6dc/images/b6d46eb1-2ae5-e8ea-8497-0180d0399a92.png" }, "triggerStatement": { "floods": "Triggered areas (payams) are those in which the trigger threshold has been reached. The threshold is defined by two parameters from the 7-days GloFAS forecast: the return period of the forecasted flood and the probability of occurrence, these are updated on a daily basis. The trigger is issued when GloFAS forecasts an occurrence with a probability of at least 60% of a 5 year return period flood in the next 7 days. The GloFAS will not trigger in areas where the False Alarm Ratio (FAR) > 0.35." diff --git a/services/API-service/test/email/dengue/test-dengue-scenario.helper.ts b/services/API-service/test/email/dengue/test-dengue-scenario.helper.ts index dd8e1818b..e7e233689 100644 --- a/services/API-service/test/email/dengue/test-dengue-scenario.helper.ts +++ b/services/API-service/test/email/dengue/test-dengue-scenario.helper.ts @@ -45,9 +45,9 @@ export async function testDengueScenario( const dom = new JSDOM(response.body.activeEvents.email); const document = dom.window.document; - // Get all span elements with apiTest="eventName" and their lower case text content + // Get all span elements with data-testid="event-name" and their lower case text content const eventNamesInEmail = Array.from( - document.querySelectorAll('span[apiTest="eventName"]'), + document.querySelectorAll('[data-testid="event-name"]'), (el) => (el as Element).textContent.toLowerCase(), ).map((el) => el.trim()); diff --git a/services/API-service/test/email/drought/email-uga-drought.test.ts b/services/API-service/test/email/drought/email-uga-drought.test.ts index 773768de7..90cb52c10 100644 --- a/services/API-service/test/email/drought/email-uga-drought.test.ts +++ b/services/API-service/test/email/drought/email-uga-drought.test.ts @@ -55,9 +55,9 @@ describe('Should send an email for uga drought', () => { const dom = new JSDOM(response.body.activeEvents.email); const document = dom.window.document; - // Get all span elements with apiTest="eventName" and their lower case text content + // Get all span elements with data-testid="event-name" and their lower case text content const eventNamesInEmail = Array.from( - document.querySelectorAll('span[apiTest="eventName"]'), + document.querySelectorAll('[data-testid="event-name"]'), (el) => (el as Element).textContent.toLowerCase(), ); diff --git a/services/API-service/test/email/flash-flood/test-flash-flood-scenario.helper.ts b/services/API-service/test/email/flash-flood/test-flash-flood-scenario.helper.ts index 2c65bd569..e5e707de2 100644 --- a/services/API-service/test/email/flash-flood/test-flash-flood-scenario.helper.ts +++ b/services/API-service/test/email/flash-flood/test-flash-flood-scenario.helper.ts @@ -45,9 +45,9 @@ export async function testFlashFloodScenario( const dom = new JSDOM(response.body.activeEvents.email); const document = dom.window.document; - // Get all span elements with apiTest="eventName" and their lower case text content + // Get all span elements with data-testid="event-name" and their lower case text content const eventNamesInEmail = Array.from( - document.querySelectorAll('span[apiTest="eventName"]'), + document.querySelectorAll('[data-testid="event-name"]'), (el) => (el as Element).textContent.toLowerCase(), ).map((el) => el.trim()); diff --git a/services/API-service/test/email/floods/test-flood-scenario.helper.ts b/services/API-service/test/email/floods/test-flood-scenario.helper.ts index a8c524ebe..138678057 100644 --- a/services/API-service/test/email/floods/test-flood-scenario.helper.ts +++ b/services/API-service/test/email/floods/test-flood-scenario.helper.ts @@ -50,9 +50,9 @@ export async function testFloodScenario( const dom = new JSDOM(response.body.activeEvents.email); const document = dom.window.document; - // Get all span elements with apiTest="eventName" and their lower case text content + // Get all span elements with data-testid="event-name" and their lower case text content const eventNamesInEmail = Array.from( - document.querySelectorAll('span[apiTest="eventName"]'), + document.querySelectorAll('[data-testid="event-name"]'), (el) => (el as Element).textContent.toLowerCase(), ); diff --git a/services/API-service/test/email/malaria/test-malaria-scenario.helper.ts b/services/API-service/test/email/malaria/test-malaria-scenario.helper.ts index 0b5b2390e..1231e22d5 100644 --- a/services/API-service/test/email/malaria/test-malaria-scenario.helper.ts +++ b/services/API-service/test/email/malaria/test-malaria-scenario.helper.ts @@ -45,9 +45,9 @@ export async function testMalariaScenario( const dom = new JSDOM(response.body.activeEvents.email); const document = dom.window.document; - // Get all span elements with apiTest="eventName" and their lower case text content + // Get all span elements with data-testid="event-name" and their lower case text content const eventNamesInEmail = Array.from( - document.querySelectorAll('span[apiTest="eventName"]'), + document.querySelectorAll('[data-testid="event-name"]'), (el) => (el as Element).textContent.toLowerCase(), ).map((el) => el.trim()); diff --git a/services/API-service/test/email/typhoon/test-typhoon-scenario.helper.ts b/services/API-service/test/email/typhoon/test-typhoon-scenario.helper.ts index 19df160b4..524e4f767 100644 --- a/services/API-service/test/email/typhoon/test-typhoon-scenario.helper.ts +++ b/services/API-service/test/email/typhoon/test-typhoon-scenario.helper.ts @@ -41,9 +41,9 @@ export async function testTyphoonScenario( const dom = new JSDOM(response.body.activeEvents.email); const document = dom.window.document; - // Get all span elements with apiTest="eventName" and their lower case text content + // Get all span elements with data-testid="event-name" and their lower case text content const eventNamesInEmail = Array.from( - document.querySelectorAll('span[apiTest="eventName"]'), + document.querySelectorAll('[data-testid="event-name"]'), (el) => (el as Element).textContent.toLowerCase(), );