diff --git a/package-lock.json b/package-lock.json index 64ff2a0..c663068 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1373,7 +1373,6 @@ "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz", "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -2659,6 +2658,21 @@ "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", "dev": true }, + "@types/mjml": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@types/mjml/-/mjml-4.7.0.tgz", + "integrity": "sha512-aWWu8Lxq2SexXGs+lBPRUpN3kFf0sDRo3Y4jz7BQ15cQvMfyZOadgFJsNlHmDqI6D2Qjx0PIK+1f9IMXgq9vTA==", + "dev": true, + "requires": { + "@types/mjml-core": "*" + } + }, + "@types/mjml-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@types/mjml-core/-/mjml-core-4.7.0.tgz", + "integrity": "sha512-sjdnVUQCZMRCXNuIve6OmB/pyoCV0wPbc5UV+VDNE/qTrM5ObJiOGpSVjwh2Kv7AEvPMP3hBh+xB6ERdN/ZPvg==", + "dev": true + }, "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -2670,6 +2684,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.10.tgz", "integrity": "sha512-yV1nWZPlMFpoXyoknm4S56y2nlTAuFYaJuQtYRAOU7xA/FJ9RY0Xm7QOkaYMMmr8ESdHIuUb6oQgR/0+2NqlyA==" }, + "@types/nodemailer": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.0.tgz", + "integrity": "sha512-KY7bFWB0MahRZvVW4CuW83qcCDny59pJJ0MQ5ifvfcjNwPlIT0vW4uARO4u1gtkYnWdhSvURegecY/tzcukJcA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -4149,8 +4172,7 @@ "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" }, "ansi-escapes": { "version": "4.3.1", @@ -4768,8 +4790,7 @@ "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "optional": true + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==" }, "bindings": { "version": "1.5.0", @@ -4860,8 +4881,7 @@ "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, "boxen": { "version": "4.2.0", @@ -5410,7 +5430,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", - "dev": true, "requires": { "no-case": "^2.2.0", "upper-case": "^1.1.1" @@ -5495,11 +5514,67 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + }, + "dependencies": { + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "*" + } + } + } + }, "chokidar": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", - "optional": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", @@ -5568,7 +5643,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", - "dev": true, "requires": { "source-map": "~0.6.0" } @@ -6059,7 +6133,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dev": true, "requires": { "ini": "^1.3.4", "proto-list": "~1.2.1" @@ -7874,8 +7947,7 @@ "detect-node": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", - "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", - "dev": true + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" }, "dezalgo": { "version": "1.0.3", @@ -7990,7 +8062,6 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dev": true, "requires": { "domelementtype": "^2.0.1", "entities": "^2.0.0" @@ -7999,14 +8070,12 @@ "domelementtype": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", - "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==", - "dev": true + "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==" }, "entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "dev": true + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" } } }, @@ -8025,8 +8094,7 @@ "domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" }, "domexception": { "version": "2.0.1", @@ -8049,7 +8117,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, "requires": { "domelementtype": "1" } @@ -8058,7 +8125,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, "requires": { "dom-serializer": "0", "domelementtype": "1" @@ -8170,7 +8236,6 @@ "version": "0.15.3", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", - "dev": true, "requires": { "commander": "^2.19.0", "lru-cache": "^4.1.5", @@ -8181,14 +8246,12 @@ "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==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -8197,8 +8260,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, @@ -8302,8 +8364,7 @@ "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" }, "envify": { "version": "4.1.0", @@ -8437,8 +8498,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-goat": { "version": "2.1.1", @@ -10815,8 +10875,7 @@ "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, "hex-color-regex": { "version": "1.1.0", @@ -11000,7 +11059,6 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, "requires": { "domelementtype": "^1.3.1", "domhandler": "^2.3.0", @@ -11014,7 +11072,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -11574,7 +11631,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==", - "optional": true, "requires": { "binary-extensions": "^2.0.0" } @@ -14398,7 +14454,6 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.13.0.tgz", "integrity": "sha512-/Tbp1OVzZjbwzwJQFIlYLm9eWQ+3aYbBXLSaqb1mEJzhcQAfrqMMQYtjb6io+U6KpD0ID4F+Id3/xcjH3l/sqA==", - "dev": true, "requires": { "config-chain": "^1.1.12", "editorconfig": "^0.15.3", @@ -14410,14 +14465,12 @@ "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, "requires": { "abbrev": "1" } @@ -14591,6 +14644,18 @@ "verror": "1.10.0" } }, + "juice": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/juice/-/juice-7.0.0.tgz", + "integrity": "sha512-AjKQX31KKN+uJs+zaf+GW8mBO/f/0NqSh2moTMyvwBY+4/lXIYTU8D8I2h6BAV3Xnz6GGsbalUyFqbYMe+Vh+Q==", + "requires": { + "cheerio": "^1.0.0-rc.3", + "commander": "^5.1.0", + "mensch": "^0.3.4", + "slick": "^1.12.2", + "web-resource-inliner": "^5.0.0" + } + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -14764,8 +14829,7 @@ "lodash": { "version": "4.17.19", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", - "dev": true + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -14909,8 +14973,7 @@ "lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", - "dev": true + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" }, "lowercase-keys": { "version": "1.0.1", @@ -15101,6 +15164,11 @@ "readable-stream": "^2.0.1" } }, + "mensch": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz", + "integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==" + }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", @@ -15276,8 +15344,7 @@ "mime": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "dev": true + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" }, "mime-db": { "version": "1.44.0", @@ -15436,6 +15503,656 @@ } } }, + "mjml": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.8.1.tgz", + "integrity": "sha512-jdKABiLBA1IJ0qTA5QVVsi/NN5GFtdvC1lxwdxrYV6J7dcJW7Z07j3r/GmJyOc2cznCFpJFKzfYV4Z6OZS4iyw==", + "requires": { + "@babel/runtime": "^7.8.7", + "mjml-accordion": "4.8.1", + "mjml-body": "4.8.1", + "mjml-button": "4.8.1", + "mjml-carousel": "4.8.1", + "mjml-cli": "4.8.1", + "mjml-column": "4.8.1", + "mjml-core": "4.8.1", + "mjml-divider": "4.8.1", + "mjml-group": "4.8.1", + "mjml-head": "4.8.1", + "mjml-head-attributes": "4.8.1", + "mjml-head-breakpoint": "4.8.1", + "mjml-head-font": "4.8.1", + "mjml-head-html-attributes": "4.8.1", + "mjml-head-preview": "4.8.1", + "mjml-head-style": "4.8.1", + "mjml-head-title": "4.8.1", + "mjml-hero": "4.8.1", + "mjml-image": "4.8.1", + "mjml-migrate": "4.8.1", + "mjml-navbar": "4.8.1", + "mjml-raw": "4.8.1", + "mjml-section": "4.8.1", + "mjml-social": "4.8.1", + "mjml-spacer": "4.8.1", + "mjml-table": "4.8.1", + "mjml-text": "4.8.1", + "mjml-validator": "4.8.1", + "mjml-wrapper": "4.8.1" + } + }, + "mjml-accordion": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.8.1.tgz", + "integrity": "sha512-VviMSaWlHkiTt0bVSdaIeDQjkApAjJR2whEhVvQmEjpu5gJdUS2Po6dQHrd/0ub8gCVMzVq62UKJdPM0fTBlMw==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-body": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.8.1.tgz", + "integrity": "sha512-L8DjOveb1GsxSAOye7CyeTsqBQ13DBQCuRVz9dXF1pjycawgK3eBvMHhlXAkCcuNlRUuH0/jI04f2whkYTYOZg==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-button": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.8.1.tgz", + "integrity": "sha512-qiPtRQkC1/4QjHPTA8NvpVgK8jRIevEo2pgxw8gDsstkWSnV/TxrzQxQ6iWaWI7gGD3ZOQUMvVTpUeuPAJGssw==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-carousel": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.8.1.tgz", + "integrity": "sha512-YlYy6sQuhi1Q889WW9OwQp9qZvb0mR7M/9rnCgRzI5SFd9dICwafEz5hKHTfy4o/d1CWnKF5Gp/ovg/Ci6pm4A==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-cli": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.8.1.tgz", + "integrity": "sha512-Yb/Ykwin4XLpX9uonFW1GK7aMc3CBUDtcbPsVMC/p9g3U1rXi/Bp4MFpya8WoHT9D4N97gGJPEQ7ndCrzMrgNQ==", + "requires": { + "@babel/runtime": "^7.8.7", + "chokidar": "^3.0.0", + "glob": "^7.1.1", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "lodash": "^4.17.15", + "mjml-core": "4.8.1", + "mjml-migrate": "4.8.1", + "mjml-parser-xml": "4.8.1", + "mjml-validator": "4.8.1", + "yargs": "^16.1.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" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "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==" + }, + "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==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "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" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "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==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + } + } + }, + "mjml-column": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.8.1.tgz", + "integrity": "sha512-8UlYNtBgcEylnFAA+il+LEeuP8sf91ml7v9yV9GWVZpdS9o2eQI1LHtmlEZ6+PDVG9CYpmTm1XZ0YWqBsVVtBg==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-core": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.8.1.tgz", + "integrity": "sha512-CXKzgu3BjgtSfNKDeihK75fLOhhimEZK4eU5ZCgVgtD5S3txfyA+IbRPKikcj5V/vF1Zxfipr3P4SMnVAIi1vA==", + "requires": { + "@babel/runtime": "^7.8.7", + "cheerio": "1.0.0-rc.3", + "detect-node": "2.0.4", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "juice": "^7.0.0", + "lodash": "^4.17.15", + "mjml-migrate": "4.8.1", + "mjml-parser-xml": "4.8.1", + "mjml-validator": "4.8.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==" + }, + "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" + } + } + } + }, + "mjml-divider": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.8.1.tgz", + "integrity": "sha512-kaDHd++6qfOp79dm9k/3o4uMLvhSMy01HjWO5+HCYGv9V4ZLHRHmz8MJVz9plpRByRsOV0gw88vXDNiGVqX6Eg==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-group": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.8.1.tgz", + "integrity": "sha512-839DbzEg9GzXgwjXSJZFcx3k18DiklAHCnhpy/fbH5C1IbFU0W5I3V105p2a9jeXVIvrRiDw4pCjoPSJsNu5lQ==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-head": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.8.1.tgz", + "integrity": "sha512-6UeXCXFN/+8ehwE20weE68Kx1kaWySdffqPm2zjvDdOWQPEAO9tx3FnJk3MuR0kZ6vcvuM0KXYLXlC5rJS02Dg==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-head-attributes": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.8.1.tgz", + "integrity": "sha512-PthSJB+znbNfOZ5DHFiuoY9hG97rgmcKzq0Htn6YQuzVFEtvrKXf7cj57M23RkFq2Gcyvn/8VJ98F6kG1Pfq7A==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-head-breakpoint": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.8.1.tgz", + "integrity": "sha512-ABL6L5GKRE3wWY5YOZEHDp5MQN1oV9srTR13Ohe/IC3MHaLv78T98E8iEBYr51RnVlfMkhfGNJF8vn6C8EXIFQ==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-head-font": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.8.1.tgz", + "integrity": "sha512-Gm4QOKQOE87D6pCor1IqBT76PPd0fhtSXWpceacf9oS8QtZtI1VjNuP7TiEk+T5DZo5mM+0zKuhrW7EUPfku3w==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-head-html-attributes": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.8.1.tgz", + "integrity": "sha512-WNZnF0+2Q3aBAYXvthiOHg4URPxUKdTSNmCsrTajqDrP/1A0lin5uLrKc/BGf2MiiBtSvz2kUYbkEBPn7qvSBA==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-head-preview": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.8.1.tgz", + "integrity": "sha512-7u9Y7p9DpmGedKMAzvRPl5u3vazi7AWj1iNV/cHyt6couWlszrLGiG2L9dxr58zSfsxvAyGLH+dVcx/qRsQwZA==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-head-style": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.8.1.tgz", + "integrity": "sha512-QeYHhSPzv3fV5vM1huFiERmLZ/UzJAxqSz/BJiqwZ0qawWLxVG6ku8VTwNQdiWKD4Wnsiijre41rwPttv4OiJQ==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-head-title": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.8.1.tgz", + "integrity": "sha512-Nc49MlGxML4orDL9XKMnaZDHFSivGZnOTls/TH4nkZG+TLMzLf/6+f//Im1x8XiLKhWgETuGff7eFc1PadxVKg==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-hero": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.8.1.tgz", + "integrity": "sha512-LYe1mzNySN0M/ZHx8IMB5+v5MzKVHElujzywEFKnAFycWXPjRTvFUDp+PzWP55rbg5GILu4+pgD8ePZH02DHOQ==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-image": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.8.1.tgz", + "integrity": "sha512-3xHmUhdfoOVFzXkFV0bpq84ZGQgQrJbCVeArpz7DdwjjaEER7cYoleAMPtlgOlEyx1TwAJzClpIRO887MkV/qg==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-migrate": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.8.1.tgz", + "integrity": "sha512-U8s9uzgjsOt9so+oqq9Dd9x/bZfkPva8euzF2RiN09wfUIKfglMblZyarsbaVTyFT3BOFKbJ091YcVBobsCJiQ==", + "requires": { + "@babel/runtime": "^7.8.7", + "js-beautify": "^1.6.14", + "lodash": "^4.17.15", + "mjml-core": "4.8.1", + "mjml-parser-xml": "4.8.1", + "yargs": "^16.1.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" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "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==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "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==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + } + } + }, + "mjml-navbar": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.8.1.tgz", + "integrity": "sha512-6Y3ITpDYz9HytT69A59hk50/y1r7iOkGQL7PP2qltWAjj2eHxUpu0dsPfPvV0a2X6HB9DXOSPJSWdHNHowI/dQ==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-parser-xml": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.8.1.tgz", + "integrity": "sha512-kU9IpBVWfeDad/vuDpBt0okz9yRyvy+H6JRZqrUm+qDkzNQHEChnLl3U47FYj81SMuh0CVTGatCIi05pFC396g==", + "requires": { + "@babel/runtime": "^7.8.7", + "detect-node": "2.0.4", + "htmlparser2": "^4.1.0", + "lodash": "^4.17.15" + }, + "dependencies": { + "dom-serializer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz", + "integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "entities": "^2.0.0" + }, + "dependencies": { + "domhandler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz", + "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==", + "requires": { + "domelementtype": "^2.1.0" + } + } + } + }, + "domelementtype": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", + "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==" + }, + "domhandler": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", + "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz", + "integrity": "sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==", + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0" + }, + "dependencies": { + "domhandler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz", + "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==", + "requires": { + "domelementtype": "^2.1.0" + } + } + } + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + }, + "htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + } + } + }, + "mjml-raw": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.8.1.tgz", + "integrity": "sha512-NIwHVcFDCOuFHB9dY1fLyRSRTuq//FcYPyorJrEt9TTEY16KznjRyJvJohPbF7bcLoYiqBUDFSRtI7qJFTFapg==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-section": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.8.1.tgz", + "integrity": "sha512-cksu5rVBioDjNZyyWBwV2oW+HdUGrESMQSACrJCgeQFTbkJ7GdiCqsGOGTZN4OoM8DvHAizXzOXHZY9GPC4M8g==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-social": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.8.1.tgz", + "integrity": "sha512-YU0eJg0BnqfV1JzHONWabnZ8c1xJATezVXe1z0xMhXpe/lPPWRjqv97Sf06h2NoK8z9F6PvifHBVv6/kU0HU6g==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-spacer": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.8.1.tgz", + "integrity": "sha512-pMvL2YK0Phb7m78cwipWb+OmvP1TT1spsYDtC1qFuK46vkdOpesGS9dhPMG5iApbaKOcdmoIENtsD01agIzjjw==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-table": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.8.1.tgz", + "integrity": "sha512-aXOThuC9d3wcnuigefqPcUTticSKul9+ElFKDiX5SEGmXbr3g5Mp3TaM9218GaXfyGJNFEe4eWfiwqjeRv8Fmw==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-text": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.8.1.tgz", + "integrity": "sha512-wA+/pMEmJqDnSR45w6jon/f2HGnLddcc19DNo77EfW8yw6KVyjvDdRs5y6L0S7Ri265AsgzvG7hNbJ31AwjtJw==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1" + } + }, + "mjml-validator": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-validator/-/mjml-validator-4.8.1.tgz", + "integrity": "sha512-5qykc1kLILqj89Zqij6SsuKHG/jp5mjJfZWFpC8sEKIPVxBKBduz3BNSp5KABue1wW17swaXyMFhCjiFD+QRrg==", + "requires": { + "@babel/runtime": "^7.8.7" + } + }, + "mjml-wrapper": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.8.1.tgz", + "integrity": "sha512-PgOg6sEW/bXOnvMt5L2KuJCdlsOclw+GqH9Cf3gVHgw2P+7OfLIp6p+gByYTwjc4JAoUz0mfcCWdZ9HRMSCcYQ==", + "requires": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.8.1", + "mjml-section": "4.8.1" + } + }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -15579,7 +16296,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", - "dev": true, "requires": { "lower-case": "^1.1.1" } @@ -15604,6 +16320,11 @@ "lodash.toarray": "^4.4.0" } }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", @@ -15908,6 +16629,11 @@ "find-parent-dir": "^0.3.0" } }, + "nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-89ps+SBGpo0D4Bi5ZrxcrCiRFaMmkCt+gItMXQGzEtZVR3uAD3QAQIDoxTWnx3ky0Dwwy/dhFrQ+6NNGXpw/qQ==" + }, "nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", @@ -16631,7 +17357,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, "requires": { "boolbase": "~1.0.0" } @@ -17100,7 +17825,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", - "dev": true, "requires": { "no-case": "^2.2.0" } @@ -18401,8 +19125,7 @@ "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" }, "proxy-addr": { "version": "2.0.6", @@ -18432,8 +19155,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.8.0", @@ -18674,7 +19396,6 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", - "optional": true, "requires": { "picomatch": "^2.2.1" } @@ -18776,8 +19497,7 @@ "regenerator-runtime": { "version": "0.13.7", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" }, "regenerator-transform": { "version": "0.14.5", @@ -18872,8 +19592,7 @@ "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "dev": true + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" }, "remove-trailing-separator": { "version": "1.1.0", @@ -19926,8 +20645,7 @@ "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" }, "signal-exit": { "version": "3.0.3", @@ -19974,6 +20692,11 @@ "is-fullwidth-code-point": "^2.0.0" } }, + "slick": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", + "integrity": "sha1-vQSN23TefRymkV+qSldXCzVQwtc=" + }, "smoothscroll-polyfill": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz", @@ -20180,8 +20903,7 @@ "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 + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { "version": "0.5.3", @@ -22094,9 +22816,7 @@ "uglify-js": { "version": "3.10.2", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.2.tgz", - "integrity": "sha512-GXCYNwqoo0MbLARghYjxVBxDCnU0tLqN7IPLdHHbibCb1NI5zBkU2EPcy/GaVxc0BtTjqyGXJCINe6JMR2Dpow==", - "dev": true, - "optional": true + "integrity": "sha512-GXCYNwqoo0MbLARghYjxVBxDCnU0tLqN7IPLdHHbibCb1NI5zBkU2EPcy/GaVxc0BtTjqyGXJCINe6JMR2Dpow==" }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", @@ -22328,8 +23048,7 @@ "upper-case": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", - "dev": true + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" }, "uri-js": { "version": "4.2.2", @@ -22505,6 +23224,11 @@ } } }, + "valid-data-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-3.0.1.tgz", + "integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -23113,6 +23837,95 @@ "minimalistic-assert": "^1.0.0" } }, + "web-resource-inliner": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-5.0.0.tgz", + "integrity": "sha512-AIihwH+ZmdHfkJm7BjSXiEClVt4zUFqX4YlFAzjL13wLtDuUneSaFvDBTbdYRecs35SiU7iNKbMnN+++wVfb6A==", + "requires": { + "ansi-colors": "^4.1.1", + "escape-goat": "^3.0.0", + "htmlparser2": "^4.0.0", + "mime": "^2.4.6", + "node-fetch": "^2.6.0", + "valid-data-url": "^3.0.0" + }, + "dependencies": { + "dom-serializer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz", + "integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "entities": "^2.0.0" + }, + "dependencies": { + "domhandler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz", + "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==", + "requires": { + "domelementtype": "^2.1.0" + } + } + } + }, + "domelementtype": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", + "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==" + }, + "domhandler": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", + "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz", + "integrity": "sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==", + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0" + }, + "dependencies": { + "domhandler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz", + "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==", + "requires": { + "domelementtype": "^2.1.0" + } + } + } + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + }, + "escape-goat": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz", + "integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==" + }, + "htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + } + } + }, "webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", diff --git a/package.json b/package.json index 4aaa69a..bec33a7 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,9 @@ "jsonwebtoken": "^8.5.1", "lodash.merge": "~4.6.2", "mime-types": "~2.1.27", + "mjml": "^4.8.1", "ms": "^2.1.2", + "nodemailer": "^6.4.17", "nunjucks": "~3.2.2", "parseurl": "~1.3.3", "qs": "~6.9.4", @@ -93,8 +95,10 @@ "@types/lodash": "^4.14.162", "@types/lodash.merge": "^4.6.6", "@types/mime-types": "^2.1.0", + "@types/mjml": "^4.7.0", "@types/ms": "^0.7.31", "@types/node": "^14.11.10", + "@types/nodemailer": "^6.4.0", "@types/parseurl": "^1.3.1", "@types/qs": "^6.9.5", "@types/secure-password": "^3.1.0", diff --git a/src/config/default.ts b/src/config/default.ts index 3ec369b..dd8393a 100644 --- a/src/config/default.ts +++ b/src/config/default.ts @@ -18,6 +18,7 @@ export const defaultConfig: ZenConfig = { template: './template/', service: './service/', entity: './entity/', + email: './email/', public: join(appDir, 'public'), }, web: { @@ -50,6 +51,9 @@ export const defaultConfig: ZenConfig = { trimBlocks: false, lstripBlocks: false, }, + email: { + engine: 'mjml', + }, log: { level: 'info', wrapConsole: false, diff --git a/src/controller/Controller.ts b/src/controller/Controller.ts index 6a84133..d098ec9 100644 --- a/src/controller/Controller.ts +++ b/src/controller/Controller.ts @@ -1,19 +1,9 @@ import type { Environment } from '../template/Environment' import { TemplateResponse } from '../template/TemplateResponse' -/** - * The basic ZenTS Controller class. Your custom Controller should extend this controller - * in order to use the template engine. - */ export abstract class Controller { constructor(protected templateEnvironment: Environment) {} - /** - * Renders a given template. The supplied key argument is either the filename or the path/to/file without the file extension. - * - * @param key The key of the template to render (path/to/file without extension) - * @param context Optional context of the template. This object will be available inside the rendered template. - */ protected async render( key: string, context?: Record, diff --git a/src/controller/ControllerFactory.ts b/src/controller/ControllerFactory.ts index ee60677..dc5c0fc 100644 --- a/src/controller/ControllerFactory.ts +++ b/src/controller/ControllerFactory.ts @@ -3,6 +3,7 @@ import { Environment, Loader } from '../template/' import { AbstractFactory } from '../core/AbstractFactory' import type { DatabaseContainer } from '../database/DatabaseContainer' +import type { EmailFactory } from '../email/EmailFactory' import type { SessionFactory } from '../security/SessionFactory' export class ControllerFactory extends AbstractFactory { @@ -10,6 +11,7 @@ export class ControllerFactory extends AbstractFactory { constructor( protected readonly controllers: Controllers, + emailFactory: EmailFactory, sessionFactory: SessionFactory, securityProviders: SecurityProviders, databaseContainer: DatabaseContainer, @@ -21,6 +23,7 @@ export class ControllerFactory extends AbstractFactory { this.templateEnvironment = new Environment(templateLoader, templateData) this.injector = this.buildInjector({ databaseContainer, + emailFactory, securityProviders, sessionFactory, }) diff --git a/src/core/AbstractFactory.ts b/src/core/AbstractFactory.ts index 8f92c84..a5c7b4a 100644 --- a/src/core/AbstractFactory.ts +++ b/src/core/AbstractFactory.ts @@ -1,4 +1,5 @@ import type { DatabaseContainer } from '../database/DatabaseContainer' +import type { EmailFactory } from '../email/EmailFactory' import { Injector } from '../dependencies/Injector' import { ModuleContext } from '../dependencies/ModuleContext' import type { SecurityProviders } from '../types/types' @@ -13,14 +14,21 @@ export abstract class AbstractFactory { protected buildInjector({ databaseContainer, + emailFactory, securityProviders, sessionFactory, }: { databaseContainer: DatabaseContainer + emailFactory: EmailFactory securityProviders: SecurityProviders sessionFactory: SessionFactory }): Injector { - const context = new ModuleContext(databaseContainer, sessionFactory, securityProviders) + const context = new ModuleContext( + databaseContainer, + emailFactory, + sessionFactory, + securityProviders, + ) const injector = new Injector(context) return injector diff --git a/src/core/AutoLoader.ts b/src/core/AutoLoader.ts index 8255e04..fecdd6d 100644 --- a/src/core/AutoLoader.ts +++ b/src/core/AutoLoader.ts @@ -1,5 +1,6 @@ import type { Controllers, + EmailTemplates, Entities, SecurityProviders, Services, @@ -8,6 +9,7 @@ import type { import { ControllerLoader } from '../controller/ControllerLoader' import { DatabaseContainer } from '../database/DatabaseContainer' +import { EmailTemplateLoader } from '../email/EmailTemplateLoader' import { EntityLoader } from '../database/EntityLoader' import { Registry } from './Registry' import { SecurityProviderLoader } from '../security/SecurityProviderLoader' @@ -22,6 +24,7 @@ export class AutoLoader { controllers, services, templateData, + emailTemplates, entities, connection, redisClient, @@ -29,6 +32,7 @@ export class AutoLoader { this.loadControllers(), this.loadServices(), this.loadTemplateData(), + this.loadEmailTemplates(), this.loadEntities(), createConnection(), createRedisClient(), @@ -41,6 +45,7 @@ export class AutoLoader { controllers, services, templateData, + emailTemplates, databaseContainer, entities, securityProviders, @@ -49,6 +54,15 @@ export class AutoLoader { return registry } + protected loadSecurityProviders( + entities: Entities, + databaseContainer: DatabaseContainer, + ): SecurityProviders { + const securityProviderLoader = new SecurityProviderLoader() + + return securityProviderLoader.load(entities, databaseContainer) + } + protected async loadControllers(): Promise { const controllerLoader = new ControllerLoader() @@ -73,12 +87,9 @@ export class AutoLoader { return await entityLoader.load() } - protected loadSecurityProviders( - entities: Entities, - databaseContainer: DatabaseContainer, - ): SecurityProviders { - const securityProviderLoader = new SecurityProviderLoader() + protected async loadEmailTemplates(): Promise { + const emailTemplateLoader = new EmailTemplateLoader() - return securityProviderLoader.load(entities, databaseContainer) + return await emailTemplateLoader.load() } } diff --git a/src/core/Registry.ts b/src/core/Registry.ts index 084ada0..eb19685 100644 --- a/src/core/Registry.ts +++ b/src/core/Registry.ts @@ -1,6 +1,6 @@ -import { +import type { Controllers, - DB_TYPE, + EmailTemplates, Entities, RegistryFactories, SecurityProviders, @@ -10,7 +10,9 @@ import { import type { Connection } from 'typeorm' import { ControllerFactory } from '../controller/ControllerFactory' +import { DB_TYPE } from '../types' import { DatabaseContainer } from '../database/DatabaseContainer' +import { EmailFactory } from '../email' import type { Redis } from 'ioredis' import { RequestFactory } from '../http/RequestFactory' import { RouterFactory } from '../router/RouterFactory' @@ -24,24 +26,34 @@ export class Registry { protected readonly controllers: Controllers, protected readonly services: Services, templateData: TemplateEngineLoaderResult, + emailTemplates: EmailTemplates, protected readonly databaseContainer: DatabaseContainer, protected readonly entities: Entities, protected readonly securityProviders: SecurityProviders, ) { const sessionFactory = new SessionFactory(securityProviders, databaseContainer) + const emailFactory = new EmailFactory(emailTemplates) this.factories = { router: new RouterFactory(), controller: new ControllerFactory( controllers, + emailFactory, sessionFactory, securityProviders, databaseContainer, templateData, ), request: new RequestFactory(this), - service: new ServiceFactory(services, sessionFactory, securityProviders, databaseContainer), + service: new ServiceFactory( + services, + emailFactory, + sessionFactory, + securityProviders, + databaseContainer, + ), session: sessionFactory, + email: emailFactory, } } diff --git a/src/decorators/email.ts b/src/decorators/email.ts new file mode 100644 index 0000000..812225b --- /dev/null +++ b/src/decorators/email.ts @@ -0,0 +1,6 @@ +import type { Class } from 'type-fest' +import { REFLECT_METADATA } from '../types/enums' + +export function email(target: Class, propertyKey: string, parameterIndex: number): void { + Reflect.defineMetadata(REFLECT_METADATA.EMAIL, parameterIndex, target, propertyKey) +} diff --git a/src/decorators/index.ts b/src/decorators/index.ts index 9a6a5fe..4403d9b 100644 --- a/src/decorators/index.ts +++ b/src/decorators/index.ts @@ -5,3 +5,4 @@ export * from './controller' export * from './redis' export * from './security' export * from './context' +export * from './email' diff --git a/src/dependencies/InjectorAction/EmailAction.ts b/src/dependencies/InjectorAction/EmailAction.ts new file mode 100644 index 0000000..0e7bb00 --- /dev/null +++ b/src/dependencies/InjectorAction/EmailAction.ts @@ -0,0 +1,19 @@ +import type { GenericControllerInstance, InjectorFunctionParameter } from '../../types/interfaces' + +import { AbstractAction } from './AbstractAction' +import { REFLECT_METADATA } from '../../types/enums' + +export class EmailAction extends AbstractAction { + public run(instance: GenericControllerInstance, method: string): InjectorFunctionParameter { + if (!Reflect.hasMetadata(REFLECT_METADATA.EMAIL, instance, method)) { + return + } + + const metadata = Reflect.getMetadata(REFLECT_METADATA.EMAIL, instance, method) as number + + return { + index: metadata, + value: this.injector.context.getEmailFactory(), + } + } +} diff --git a/src/dependencies/ModuleContext.ts b/src/dependencies/ModuleContext.ts index 4f7f222..959bb11 100644 --- a/src/dependencies/ModuleContext.ts +++ b/src/dependencies/ModuleContext.ts @@ -1,6 +1,7 @@ import type { Connection } from 'typeorm' import { DB_TYPE } from '../types' import type { DatabaseContainer } from '../database/DatabaseContainer' +import type { EmailFactory } from '../email/EmailFactory' import type { Redis } from 'ioredis' import type { SecurityProvider } from '../security/SecurityProvider' import type { SecurityProviders } from '../types/types' @@ -9,6 +10,7 @@ import type { SessionFactory } from '../security/SessionFactory' export class ModuleContext { constructor( protected readonly databaseContainer: DatabaseContainer, + protected readonly emailFactory: EmailFactory, protected readonly sessionFactory: SessionFactory, protected readonly securityProviders: SecurityProviders, ) {} @@ -27,4 +29,7 @@ export class ModuleContext { public getSessionFactory(): SessionFactory { return this.sessionFactory } + public getEmailFactory(): EmailFactory { + return this.emailFactory + } } diff --git a/src/email/EmailFactory.ts b/src/email/EmailFactory.ts new file mode 100644 index 0000000..186838a --- /dev/null +++ b/src/email/EmailFactory.ts @@ -0,0 +1,66 @@ +import type { EmailTemplates } from '../types/types' +import type { Transport } from 'nodemailer' +import { config } from '../config/config' +import { createTransport } from 'nodemailer' +import { log } from '../log/logger' +import mjml2html from 'mjml' +import { renderString } from 'nunjucks' + +export class EmailFactory { + protected transporter: Transport | null + + constructor(protected emailTemplates: EmailTemplates) { + if (typeof config.email?.host === 'string') { + this.transporter = (createTransport(config.email) as unknown) as Transport + } else { + this.transporter = null + } + } + public send({ + to = config.email.defaults.to, + cc = config.email.defaults.cc, + bcc = config.email.defaults.bcc, + from = config.email.defaults.from, + topic = config.email.defaults.topic, + template, + payload = {}, + engine = config.email.engine, + }: { + to: string + from?: string + cc?: string + bcc?: string + topic: string + template: string + payload?: Record + engine?: string + }): void { + if (this.transporter === null) { + log.warn( + 'Trying to send an E-Mail without proper email configuration. Please configure at least a email host', + ) + + return + } else if (!this.emailTemplates.has(template)) { + throw new Error(`Email template "${template}" not found!`) + } + + let content = this.emailTemplates.get(template) + + if (engine !== 'plain') { + content = renderString(content, payload) + } + + if (engine === 'mjml') { + const result = mjml2html(content, config.email?.mjml) + + if (result.errors.length) { + log.error(result.errors) + + throw new Error('Failed to render MJML! See error(s) above for more information.') + } + + content = result.html + } + } +} diff --git a/src/email/EmailTemplateLoader.ts b/src/email/EmailTemplateLoader.ts new file mode 100644 index 0000000..1bfc5ed --- /dev/null +++ b/src/email/EmailTemplateLoader.ts @@ -0,0 +1,36 @@ +import type { EmailTemplates } from '../types/types' +import { config } from '../config/config' +import { fs } from '../filesystem/FS' +import { parse } from 'path' +import { promises } from 'fs' + +export class EmailTemplateLoader { + public async load(): Promise { + const emailTemplates = new Map() as EmailTemplates + const emailFilesPath = fs.resolveZenPath('email') + + if (!((await fs.exists(emailFilesPath)) || config.email.engine === 'plain')) { + return emailTemplates + } + + const filePaths = await this.loadFiles(emailFilesPath) + + for (const filePath of filePaths) { + const { name } = parse(filePath) + const content = await promises.readFile(filePath, { + encoding: 'utf-8', + }) + + emailTemplates.set(name, content) + } + + return emailTemplates + } + protected async loadFiles(emailFilesPath: string): Promise { + const fileExtension = config.email.engine === 'mjml' ? '.mjml' : `.${config.template.extension}` + + return (await fs.readDir(emailFilesPath)).filter((filePath: string) => + filePath.endsWith(fileExtension), + ) + } +} diff --git a/src/email/index.ts b/src/email/index.ts new file mode 100644 index 0000000..1b99a67 --- /dev/null +++ b/src/email/index.ts @@ -0,0 +1,2 @@ +export * from './EmailFactory' +export * from './EmailTemplateLoader' diff --git a/src/service/ServiceFactory.ts b/src/service/ServiceFactory.ts index d734bc0..c5239d7 100644 --- a/src/service/ServiceFactory.ts +++ b/src/service/ServiceFactory.ts @@ -2,11 +2,13 @@ import type { SecurityProviders, Services } from '../types/types' import { AbstractFactory } from '../core/AbstractFactory' import type { DatabaseContainer } from '../database/DatabaseContainer' +import type { EmailFactory } from '../email/EmailFactory' import type { SessionFactory } from '../security/SessionFactory' export class ServiceFactory extends AbstractFactory { constructor( protected services: Services, + emailFactory: EmailFactory, sessionFactory: SessionFactory, securityProviders: SecurityProviders, databaseContainer: DatabaseContainer, @@ -14,6 +16,7 @@ export class ServiceFactory extends AbstractFactory { super() this.injector = this.buildInjector({ databaseContainer, + emailFactory, sessionFactory, securityProviders, }) diff --git a/src/types/enums.ts b/src/types/enums.ts index ea412f2..277e36a 100644 --- a/src/types/enums.ts +++ b/src/types/enums.ts @@ -43,6 +43,7 @@ export enum REFLECT_METADATA { DATABASE_EM = 'database:em', DATABASE_REPOSITORY = 'database:repository', DEPENDENCIES = 'dependencies', + EMAIL = 'email', HTTP_METHOD = 'httpMethod', REDIS_CLIENT = 'redisClient', SECURITY_PROVIDER = 'securityProvider', diff --git a/src/types/interfaces.ts b/src/types/interfaces.ts index b8d5ed7..95d3fd5 100644 --- a/src/types/interfaces.ts +++ b/src/types/interfaces.ts @@ -13,6 +13,7 @@ import type { REPOSITORY_TYPE, REQUEST_TYPE, SECURITY_ACTION } from './enums' import type { ConnectionOptions } from 'typeorm' import type { Context as ContextClass } from '../http/Context' import type { ControllerFactory } from '../controller/ControllerFactory' +import type { EmailFactory } from '../email/EmailFactory' import type { RedisOptions } from 'ioredis' import type { Request } from '../http/Request' import type { RequestFactory } from '../http/RequestFactory' @@ -21,6 +22,7 @@ import type { SecurityProvider } from '../security/SecurityProvider' import type { ServiceFactory } from '../service/ServiceFactory' import type { Session as SessionClass } from '../security/Session' import type { SessionFactory } from '../security' +import type { ConnectionOptions as TLSConnectionOptions } from 'tls' // ---- A // ---- B @@ -229,6 +231,7 @@ export interface RegistryFactories { request: RequestFactory service: ServiceFactory session: SessionFactory + email: EmailFactory } export interface RequestConfigController { @@ -383,6 +386,7 @@ export interface ZenConfig { template?: string service?: string entity?: string + email?: string public?: string | boolean } web?: { @@ -465,6 +469,58 @@ export interface ZenConfig { | 'binary' | 'hex' } + email?: { + defaults?: { + to?: string + cc?: string + bcc?: string + from?: string + topic?: string + } + host?: string + port?: number + auth?: any + secure?: boolean + ignoreTLS?: boolean + requireTLS?: boolean + opportunisticTLS?: boolean + name?: string + localAddress?: string + connectionTimeout?: number + greetingTimeout?: number + socketTimeout?: number + transactionLog?: boolean + debug?: boolean + authMethod?: string + tls?: TLSConnectionOptions + url?: string + service?: string + pool?: boolean + maxConnections?: number + maxMessages?: number + rateDelta?: number + rateLimit?: number + engine?: 'mjml' | 'nunjucks' | 'plain' + mjml?: { + fonts?: { + [key: string]: string + } + keepComments?: boolean + minify?: boolean + minifyOptions?: { + collapseWhitespace?: boolean + minifyCSS?: boolean + removeEmptyAttributes?: boolean + } + validationLevel?: 'strict' | 'soft' | 'skip' + filePath?: string + mjmlConfigPath?: string + useMjmlConfigOptions?: boolean + juicePreserveTags?: { [index: string]: { start: string; end: string } } + juiceOptions?: any + preprocessors?: Array<(xml: string) => string> + } + } log?: { level?: LogLevel wrapConsole?: boolean diff --git a/src/types/types.ts b/src/types/types.ts index d292660..d5dc3ab 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -40,6 +40,8 @@ export type DatabaseObjectType = T extends DB_TYPE.ORM ? Connection : Redis export type Entities = Map +export type EmailTemplates = Map + export type ErrorResponseData = JsonObject | JsonArray // ---- F