From 76d75a40821dc924b4da7b703f356ea6e01f7e2d Mon Sep 17 00:00:00 2001 From: Tobias Lobitz Date: Wed, 25 Sep 2024 14:23:55 +0200 Subject: [PATCH 01/22] docs(readme): Add more roadmap items --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index bcdbff9..ddb7be6 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,9 @@ Check out the [deployment documentation](https://hub.nuxt.com/docs/getting-start - [ ] Add rate limiting via [NuxtSecurity](https://nuxt-security.vercel.app/documentation/middleware/rate-limiter) - [ ] Add Double-Opt-in - [ ] Add more basic components + - [ ] Faq-Area + - [ ] Pricing-Area - [ ] Add ability of customisation via config +- [ ] Add light mode From 4e16bc8467c6a95958d4932d0f97aa79b27a8503 Mon Sep 17 00:00:00 2001 From: Tobias Lobitz Date: Wed, 25 Sep 2024 15:53:35 +0200 Subject: [PATCH 02/22] wip: Add email template --- .env.example | 2 + app.vue | 3 +- emails/Verify.vue | 66 ++ nuxt.config.ts | 33 +- package-lock.json | 1108 +++++++++++++++++++++++++++++- package.json | 4 + server/api/join-waitlist.post.ts | 15 + 7 files changed, 1211 insertions(+), 20 deletions(-) create mode 100644 .env.example create mode 100644 emails/Verify.vue diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4fef032 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +NUXT_RESEND_API_KEY="" +NUXT_APP_URL="http://localhost:3000" diff --git a/app.vue b/app.vue index bfa2efd..4c9b714 100644 --- a/app.vue +++ b/app.vue @@ -69,7 +69,8 @@ const currentYear = computed(() => new Date().getFullYear()) in minutes

- Save time using the #NuxtHubLanding boilerplate, to verify your ideas. Deploy it for + Save time using the #NuxtHubLanding boilerplate, to verify your + ideas. Deploy it for free on NuxtHub, collect leads for your next big thing.

diff --git a/emails/Verify.vue b/emails/Verify.vue new file mode 100644 index 0000000..a8300bc --- /dev/null +++ b/emails/Verify.vue @@ -0,0 +1,66 @@ + + + diff --git a/nuxt.config.ts b/nuxt.config.ts index 917e3d1..f76aecf 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,12 +1,33 @@ +import vue from '@vitejs/plugin-vue' + // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ - compatibilityDate: '2024-04-03', - devtools: { enabled: true }, - modules: ['@nuxthub/core', '@nuxtjs/tailwindcss'], + compatibilityDate: '2024-04-03', + devtools: {enabled: true}, + modules: [ + '@nuxthub/core', + '@nuxtjs/tailwindcss', + 'nuxt-resend', + ], + + components: { + dirs: [ + '~/components', + { + path: '~/emails', + extensions: ['vue'], + }, + ], + }, + hub: { + database: true + }, - hub: { - database: true - } + nitro: { + rollupConfig: { + plugins: [vue()] + }, + }, }) diff --git a/package-lock.json b/package-lock.json index b865b2a..365cf15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "dependencies": { "@nuxthub/core": "^0.7.12", "@nuxtjs/tailwindcss": "^6.12.1", + "@vitejs/plugin-vue": "^5.1.4", + "@vue-email/components": "^0.0.19", + "@vue-email/render": "^0.0.9", "drizzle-orm": "^0.33.0", "h3-zod": "^0.5.3", "nuxt": "^3.13.0", @@ -18,6 +21,7 @@ }, "devDependencies": { "drizzle-kit": "^0.24.2", + "nuxt-resend": "^0.0.2", "wrangler": "^3.76.0" } }, @@ -2448,6 +2452,11 @@ "unctx": "^2.3.1" } }, + "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/@parcel/watcher": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", @@ -2758,6 +2767,21 @@ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==" }, + "node_modules/@react-email/render": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-0.0.9.tgz", + "integrity": "sha512-nrim7wiACnaXsGtL7GF6jp3Qmml8J6vAjAH88jkC8lIbfNZaCyuPQHANjyYIXlvQeAbsWADQJFZgOHUqFqjh/A==", + "dev": true, + "dependencies": { + "html-to-text": "9.0.5", + "pretty": "2.0.0", + "react": "18.2.0", + "react-dom": "18.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@rollup/plugin-alias": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz", @@ -3130,6 +3154,64 @@ "win32" ] }, + "node_modules/@selderee/plugin-htmlparser2": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", + "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "dependencies": { + "domhandler": "^5.0.3", + "selderee": "^0.11.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/@shikijs/core": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.18.0.tgz", + "integrity": "sha512-VK4BNVCd2leY62Nm2JjyxtRLkyrZT/tv104O81eyaCjHq4Adceq2uJVFJJAIof6lT1mBwZrEo2qT/T+grv3MQQ==", + "dependencies": { + "@shikijs/engine-javascript": "1.18.0", + "@shikijs/engine-oniguruma": "1.18.0", + "@shikijs/types": "1.18.0", + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.3" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.18.0.tgz", + "integrity": "sha512-qoP/aO/ATNwYAUw1YMdaip/YVEstMZEgrwhePm83Ll9OeQPuxDZd48szZR8oSQNQBT8m8UlWxZv8EA3lFuyI5A==", + "dependencies": { + "@shikijs/types": "1.18.0", + "@shikijs/vscode-textmate": "^9.2.2", + "oniguruma-to-js": "0.4.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.18.0.tgz", + "integrity": "sha512-B9u0ZKI/cud+TcmF8Chyh+R4V5qQVvyDOqXC2l2a4x73PBSBc6sZ0JRAX3eqyJswqir6ktwApUUGBYePdKnMJg==", + "dependencies": { + "@shikijs/types": "1.18.0", + "@shikijs/vscode-textmate": "^9.2.2" + } + }, + "node_modules/@shikijs/types": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.18.0.tgz", + "integrity": "sha512-O9N36UEaGGrxv1yUrN2nye7gDLG5Uq0/c1LyfmxsvzNPqlHzWo9DI0A4+fhW2y3bGKuQu/fwS7EPdKJJCowcVA==", + "dependencies": { + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.2.2.tgz", + "integrity": "sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==" + }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", @@ -3154,6 +3236,14 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-proxy": { "version": "1.17.15", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", @@ -3162,6 +3252,14 @@ "@types/node": "*" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/node": { "version": "22.5.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", @@ -3184,6 +3282,16 @@ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, "node_modules/@unhead/dom": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.6.tgz", @@ -3365,6 +3473,219 @@ "vue": "^3.0.0" } }, + "node_modules/@vue-email/body": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/body/-/body-0.0.3.tgz", + "integrity": "sha512-JxZb1upJTnt6tLKszqHfrCWTpioWuG7eZXneUmCBr0YVt6gI781dSAgUQASTBprENnO+M2sNsarszoo7jGZNOQ==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/button": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/button/-/button-0.0.3.tgz", + "integrity": "sha512-Su1iKBpfzF03SqLhxxIGDm2ZgpnXh+yD8kvLQ9+V0COepXOSdVQD5Ovif14uKGweafXSMkyQSi5lqmnVIace3A==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/code-block": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/code-block/-/code-block-0.0.3.tgz", + "integrity": "sha512-NuOAY3dhWCwnywQ9lguLWqh+2BDKr1UM7AkV/ejDJwfX/GLZnlN9EEJRYOVhFnGwXOcJnS27NmvjZHaRweZfWA==", + "dependencies": { + "shiki": "^1.7.0" + }, + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/code-inline": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/code-inline/-/code-inline-0.0.3.tgz", + "integrity": "sha512-FN9WPQBwWWumpPIAN/++oly9/hpUfioVokb7nfM/U0hEOS3mGsVjYPZNiX/oGOAHrAF83qOqgIFcd9ipVMtefg==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/column": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/column/-/column-0.0.3.tgz", + "integrity": "sha512-L1tYu3ZqoqcSNa5GK8wEKQKuvUBlsWeSA3RqKlkLrc+dR+uitcyCcbYsoE/N2tgvzZ1poykE/S6VIcrqH/zL9A==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/components": { + "version": "0.0.19", + "resolved": "https://registry.npmjs.org/@vue-email/components/-/components-0.0.19.tgz", + "integrity": "sha512-ZcYnZMofAOU+TiHDVoeEzrPjUjqSyDb1fsE33kro6tCvIxsIZxoa+PHEnj/DwFHnF0grv/3Dq1UDmWUE8s8QkQ==", + "dependencies": { + "@vue-email/body": "0.0.3", + "@vue-email/button": "0.0.3", + "@vue-email/code-block": "0.0.3", + "@vue-email/code-inline": "0.0.3", + "@vue-email/column": "0.0.3", + "@vue-email/container": "0.0.3", + "@vue-email/font": "0.0.3", + "@vue-email/head": "0.0.3", + "@vue-email/heading": "0.0.3", + "@vue-email/hr": "0.0.3", + "@vue-email/html": "0.0.3", + "@vue-email/img": "0.0.3", + "@vue-email/link": "0.0.3", + "@vue-email/markdown": "0.0.7", + "@vue-email/preview": "0.0.3", + "@vue-email/render": "^0.0.9", + "@vue-email/row": "0.0.3", + "@vue-email/section": "0.0.3", + "@vue-email/style": "0.0.3", + "@vue-email/tailwind": "0.1.9", + "@vue-email/text": "0.0.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "vue": "^3.4.29" + } + }, + "node_modules/@vue-email/container": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/container/-/container-0.0.3.tgz", + "integrity": "sha512-L+9VZ3RbCgyPaV54QCQsOAe9lAvq549JULEmH3QRDgHsIaFE3Yaddc1zMJKdbwH7ugiaRPRFrM2paR57IS6sWw==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/font": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/font/-/font-0.0.3.tgz", + "integrity": "sha512-X9vwUTKjPeFuh06BkYbFVM3rdZggLo7hJ3SHgJ09qBF7e0J7zZx5KLjkeYzKTWfHlM1fx9XtNdpQ35UKyIpwUg==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/head": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/head/-/head-0.0.3.tgz", + "integrity": "sha512-RobskTPhRQ18aHFcwOe1eorGDd3BCotXJy2YfZF9VRHvw1MtV5M/AQ0eqUUgnNuNrxFGobZtxZJMu4+EE2ToUQ==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/heading": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/heading/-/heading-0.0.3.tgz", + "integrity": "sha512-cUxlLDuRHEdLn+CZWehsUuJjWNnDVC1LAK1iKtZl++J9xGE1l4lrxaBFdhIZctH2yoZZLGm8OHvUJiYrke9YIg==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/hr": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/hr/-/hr-0.0.3.tgz", + "integrity": "sha512-DNSi4CrsgRJRstTIB8ILcCqg0rWIIMKZ1fmSVgHoEBfgiV9I4m/6wKv2Rzg1AxkYldy5w1YhwVx0NfBTCHlL7A==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/html": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/html/-/html-0.0.3.tgz", + "integrity": "sha512-kjUBwwWcgNoJ6jEmZW7gkdSXZzy6rcJ0jV4507TZlF6PRsu4FaCpeI6WHnDexdXIfCeAIwZqeAQOJHASHJd2mw==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/img": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/img/-/img-0.0.3.tgz", + "integrity": "sha512-lFDavbzdbOXo2VK8Fq/YPFB/2ZolOaToC/oFR9fQlw2hA9n1TTiEZZK5D7en62FSD9gPG1P7eQ81tOt6/Tc6+A==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/link": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/link/-/link-0.0.3.tgz", + "integrity": "sha512-AvX5wxXACrxqyLaooIDQBsz/8Q6FDSVGDg0Lo1opUZKEW80s68zI+FUhI3kuByVEogzIhfdpq9rc1yGmkUUh8A==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/markdown": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@vue-email/markdown/-/markdown-0.0.7.tgz", + "integrity": "sha512-AxMpxo0A20lFiED7l1nJy6Y+fDYHknYW6u1HZ7F2QbDUihtinwAWU9pBFDX9SSHg9Ihkf3KeitvGf4YHiYlXRA==", + "dependencies": { + "marked": "7.0.4" + }, + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/preview": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/preview/-/preview-0.0.3.tgz", + "integrity": "sha512-e9qLDsP5+f5KZQhiVGjI/d/x5NfLfYusjI9C4bewwXM6bYJo1i5WRRzX6SM7sygKEk0ZbKqnzPqbnSWtABuzDw==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/render": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@vue-email/render/-/render-0.0.9.tgz", + "integrity": "sha512-FShvnLSoys3y+30uIO4ZuBSsXKGaj/VOB+CfNxhgxLmFsLbwKwUZQgJVAmXviuuSC5xpWQCOEPQ8P7oEEYa0WA==", + "dependencies": { + "html-to-text": "9.0.5", + "js-beautify": "^1.15.1", + "vue": "^3.4.27" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@vue-email/row": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/row/-/row-0.0.3.tgz", + "integrity": "sha512-/wnkRMFJl5WTehD3/ryn7taJs42Vk7K1xUH37Q5R5T8tu1lBnAhwWcDvUeqaH/7+N4weELEBjQdKa9F6fqYlcA==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/section": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/section/-/section-0.0.3.tgz", + "integrity": "sha512-DPvDBmf3jPZGw8ZoIgs/lS3yt8H0BXMLniR0aTa6FhBPWJsXXaaQHDPY6h7jfeCgppgAoDRLZqiMnZXfqSTUWw==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/style": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/style/-/style-0.0.3.tgz", + "integrity": "sha512-1t1I1EFJ77N4AC0qb1WYUfSOsFXTUUR2gxnbg88dsXHiaMiBlyokqy50wnn4/FrubUqbOFmseCz+4nSZzGg5bA==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, + "node_modules/@vue-email/tailwind": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@vue-email/tailwind/-/tailwind-0.1.9.tgz", + "integrity": "sha512-3CH5bBAL8DTQfucWeMnM1uwE1vnnqvMvZsPMIdWnw87NQMRfj0dVkBRav7efZOrtowELM6VFHC0B1S3/rK5GPw==", + "peerDependencies": { + "vue": "^3.4.29" + } + }, + "node_modules/@vue-email/text": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@vue-email/text/-/text-0.0.3.tgz", + "integrity": "sha512-xn6MYicnXyFeV72nNTDZylOL7ut60clYUR2bqMDKkW6+XD6qWp6LGAx4b8sE0kADgu7MTq23zbU9kA/cebhk8w==", + "peerDependencies": { + "vue": "^3.4.27" + } + }, "node_modules/@vue-macros/common": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.14.0.tgz", @@ -4216,6 +4537,15 @@ "tslib": "^2.2.0" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -4237,6 +4567,24 @@ "node": ">=0.8.0" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -4422,6 +4770,15 @@ "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -4471,11 +4828,39 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/condense-newlines": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/condense-newlines/-/condense-newlines-0.2.1.tgz", + "integrity": "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-whitespace": "^0.3.0", + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/confbox": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==" }, + "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/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "node_modules/consola": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", @@ -4901,6 +5286,14 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, "node_modules/destr": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", @@ -4928,6 +5321,18 @@ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz", "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -5167,6 +5572,45 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "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/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/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5366,6 +5810,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/externality": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/externality/-/externality-1.0.2.tgz", @@ -5848,6 +6304,40 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", + "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hookable": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", @@ -5864,6 +6354,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/html-to-text": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", + "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", + "dependencies": { + "@selderee/plugin-htmlparser2": "^0.11.0", + "deepmerge": "^4.3.1", + "dom-serializer": "^2.0.0", + "htmlparser2": "^8.0.2", + "selderee": "^0.11.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "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.0.1", + "entities": "^4.4.0" + } + }, "node_modules/http-assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", @@ -6065,6 +6597,12 @@ "node": ">=8" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", @@ -6093,6 +6631,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6228,6 +6775,15 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/is-whitespace": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-whitespace/-/is-whitespace-0.3.0.tgz", + "integrity": "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-wsl": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", @@ -6266,26 +6822,117 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, - "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==", + "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/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "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": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" + }, + "bin": { + "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/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/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": { - "@isaacs/cliui": "^8.0.2" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "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": { - "jiti": "bin/jiti.js" + "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": { @@ -6348,6 +6995,18 @@ "node": ">= 0.6" } }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -6574,6 +7233,14 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/leac": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", + "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/lilconfig": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", @@ -6659,6 +7326,18 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "devOptional": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6718,6 +7397,37 @@ "semver": "bin/semver.js" } }, + "node_modules/marked": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-7.0.4.tgz", + "integrity": "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -6752,6 +7462,90 @@ "node": ">= 0.6" } }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -7834,6 +8628,17 @@ } } }, + "node_modules/nuxt-resend": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/nuxt-resend/-/nuxt-resend-0.0.2.tgz", + "integrity": "sha512-WLNPTEqpiiY+QNa5jDiJPhnJl19SLOahixk9NILRKrge8+cH7TSw4A5kOEQkSP60EcrO063I3BKLqzP1+ByCyw==", + "dev": true, + "dependencies": { + "@nuxt/kit": "^3.8.1", + "defu": "^6.1.3", + "resend": "^2.0.0" + } + }, "node_modules/nuxt/node_modules/@esbuild/aix-ppc64": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", @@ -8352,6 +9157,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/oniguruma-to-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz", + "integrity": "sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==", + "dependencies": { + "regex": "^4.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/only": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", @@ -8468,6 +9284,18 @@ "parse-path": "^7.0.0" } }, + "node_modules/parseley": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", + "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "dependencies": { + "leac": "^0.6.0", + "peberminta": "^0.9.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -8538,6 +9366,14 @@ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==" }, + "node_modules/peberminta": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", + "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -9177,6 +10013,20 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/pretty": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pretty/-/pretty-2.0.0.tgz", + "integrity": "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==", + "dev": true, + "dependencies": { + "condense-newlines": "^0.2.1", + "extend-shallow": "^2.0.1", + "js-beautify": "^1.6.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pretty-bytes": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", @@ -9219,6 +10069,20 @@ "node": ">= 6" } }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/protocols": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", @@ -9278,6 +10142,31 @@ "destr": "^2.0.3" } }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "devOptional": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -9339,6 +10228,11 @@ "node": ">=4" } }, + "node_modules/regex": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.2.tgz", + "integrity": "sha512-kK/AA3A9K6q2js89+VMymcboLOlF5lZRCYJv3gzszXFHBr6kO6qLGzbm+UIugBEV8SMMKCTR59txoY6ctRHYVw==" + }, "node_modules/replace-in-file": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.3.5.tgz", @@ -9467,6 +10361,18 @@ "node": ">=0.10.0" } }, + "node_modules/resend": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resend/-/resend-2.1.0.tgz", + "integrity": "sha512-s6LlaEReTUvlbo6w3Eg1M1TMuwK9OKJ1GVgyptIV8smLPHhFZVqnwBTFPZHID9rcsih72t3iuyrtkQ3IIGwnow==", + "dev": true, + "dependencies": { + "@react-email/render": "0.0.9" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -9797,11 +10703,31 @@ } ] }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/scule": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==" }, + "node_modules/selderee": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", + "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "dependencies": { + "parseley": "^0.12.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/selfsigned": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", @@ -9948,6 +10874,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shiki": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.18.0.tgz", + "integrity": "sha512-8jo7tOXr96h9PBQmOHVrltnETn1honZZY76YA79MHheGQg55jBvbm9dtU+MI5pjC5NJCFuA6rvVTLVeSW5cE4A==", + "dependencies": { + "@shikijs/core": "1.18.0", + "@shikijs/engine-javascript": "1.18.0", + "@shikijs/engine-oniguruma": "1.18.0", + "@shikijs/types": "1.18.0", + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -10041,6 +10980,15 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "dev": true }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/speakingurl": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", @@ -10135,6 +11083,19 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -10665,6 +11626,15 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -10707,6 +11677,20 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/ufo": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", @@ -10817,6 +11801,69 @@ "unplugin": "^1.14.1" } }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -11047,6 +12094,32 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "5.4.7", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", @@ -12620,6 +13693,15 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 1762f9d..7969a95 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,9 @@ "dependencies": { "@nuxthub/core": "^0.7.12", "@nuxtjs/tailwindcss": "^6.12.1", + "@vitejs/plugin-vue": "^5.1.4", + "@vue-email/components": "^0.0.19", + "@vue-email/render": "^0.0.9", "drizzle-orm": "^0.33.0", "h3-zod": "^0.5.3", "nuxt": "^3.13.0", @@ -22,6 +25,7 @@ }, "devDependencies": { "drizzle-kit": "^0.24.2", + "nuxt-resend": "^0.0.2", "wrangler": "^3.76.0" } } diff --git a/server/api/join-waitlist.post.ts b/server/api/join-waitlist.post.ts index 95e50b6..01aa256 100644 --- a/server/api/join-waitlist.post.ts +++ b/server/api/join-waitlist.post.ts @@ -2,8 +2,13 @@ import {useDrizzle} from "~/server/utils/drizzle"; import {consola} from "consola"; import {useValidatedBody, z} from "h3-zod"; +import {render} from "@vue-email/render"; +import Verify from "~/emails/Verify.vue"; + + export default defineEventHandler(async event => { consola.info("User trying to signup for waitlist...") + const {emails} = useResend(); const {email} = await useValidatedBody(event, z.object( { @@ -29,5 +34,15 @@ export default defineEventHandler(async event => { consola.info(`User ${entry.email} is now on waitlist...`) + console.debug("Sending email..") + await emails.send({ + from: "NuxtHubLanding ", + to: 'hallo@tobiaslobitz.de', + subject: "Confirm your email on NuxtHubLanding", + + html: await render(Verify, {appName: 'NuxtHubLanding', link: 'http://localhost:3000'}, {pretty: true}) + }) + + return entry }) From aebaa4fe9aefe4e618f1684485eb7956403a858c Mon Sep 17 00:00:00 2001 From: Tobias Lobitz Date: Wed, 25 Sep 2024 19:15:24 +0200 Subject: [PATCH 03/22] wip: Add verify email flow --- app.vue | 235 +----------------- nuxt.config.ts | 12 +- package-lock.json | 20 +- pages/index.vue | 235 ++++++++++++++++++ pages/verify.vue | 29 +++ server/api/join-waitlist.post.ts | 17 +- server/api/verify.patch.ts | 40 +-- .../migrations/0002_curious_brood.sql | 1 + .../migrations/meta/0002_snapshot.json | 69 +++++ server/database/migrations/meta/_journal.json | 7 + server/database/schema.ts | 1 + utlis/helpers.ts | 20 ++ 12 files changed, 416 insertions(+), 270 deletions(-) create mode 100644 server/database/migrations/0002_curious_brood.sql create mode 100644 server/database/migrations/meta/0002_snapshot.json diff --git a/app.vue b/app.vue index 7182c35..824e24d 100644 --- a/app.vue +++ b/app.vue @@ -1,18 +1,8 @@ diff --git a/nuxt.config.ts b/nuxt.config.ts index b8e3838..d31e970 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -11,21 +11,11 @@ export default defineNuxtConfig({ 'nuxt-resend', ], - components: { - dirs: [ - '~/components', - { - path: '~/emails', - extensions: ['vue'], - }, - ], - }, - routeRules: { '/api/join-waitlist': { security: { rateLimiter: { - tokensPerInterval: 1 + tokensPerInterval: 3 } } } diff --git a/package-lock.json b/package-lock.json index e822e8b..3967837 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8650,6 +8650,16 @@ } } }, + "node_modules/nuxt-csurf": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/nuxt-csurf/-/nuxt-csurf-1.6.2.tgz", + "integrity": "sha512-GksV+0DrnEYzQGAPb/BzLgulITqQNi3p5LUOW+zIf2GqdshW7MKoS/EBeVLDBfeT0P0a0OvewzaEGt1/OGVtAQ==", + "dependencies": { + "@nuxt/kit": "^3.12.4", + "defu": "^6.1.4", + "uncsrf": "^1.1.1" + } + }, "node_modules/nuxt-resend": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/nuxt-resend/-/nuxt-resend-0.0.2.tgz", @@ -8661,16 +8671,6 @@ "resend": "^2.0.0" } }, - "node_modules/nuxt-csurf": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/nuxt-csurf/-/nuxt-csurf-1.6.2.tgz", - "integrity": "sha512-GksV+0DrnEYzQGAPb/BzLgulITqQNi3p5LUOW+zIf2GqdshW7MKoS/EBeVLDBfeT0P0a0OvewzaEGt1/OGVtAQ==", - "dependencies": { - "@nuxt/kit": "^3.12.4", - "defu": "^6.1.4", - "uncsrf": "^1.1.1" - } - }, "node_modules/nuxt-security": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/nuxt-security/-/nuxt-security-2.0.0.tgz", diff --git a/pages/index.vue b/pages/index.vue index e69de29..e851110 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -0,0 +1,235 @@ + + + + diff --git a/pages/verify.vue b/pages/verify.vue index e69de29..767dac9 100644 --- a/pages/verify.vue +++ b/pages/verify.vue @@ -0,0 +1,29 @@ + + + diff --git a/server/api/join-waitlist.post.ts b/server/api/join-waitlist.post.ts index 01aa256..1e6ec17 100644 --- a/server/api/join-waitlist.post.ts +++ b/server/api/join-waitlist.post.ts @@ -3,13 +3,16 @@ import {consola} from "consola"; import {useValidatedBody, z} from "h3-zod"; import {render} from "@vue-email/render"; -import Verify from "~/emails/Verify.vue"; +import VerifyTemplate from "~/emails/VerifyEmail.vue"; +import {generateSecureToken} from "~/utlis/helpers"; +import {waitlist} from "~/server/database/schema"; export default defineEventHandler(async event => { consola.info("User trying to signup for waitlist...") const {emails} = useResend(); + const {email} = await useValidatedBody(event, z.object( { email: z.string().email().refine(async (email) => { @@ -26,7 +29,7 @@ export default defineEventHandler(async event => { consola.info("User joining waitlist...") - const entry = await useDrizzle().insert(tables.waitlist).values({ + let entry = await useDrizzle().insert(tables.waitlist).values({ email, createdAt: new Date(), }).returning().get() @@ -35,14 +38,18 @@ export default defineEventHandler(async event => { consola.info(`User ${entry.email} is now on waitlist...`) console.debug("Sending email..") + + await emails.send({ from: "NuxtHubLanding ", to: 'hallo@tobiaslobitz.de', subject: "Confirm your email on NuxtHubLanding", - html: await render(Verify, {appName: 'NuxtHubLanding', link: 'http://localhost:3000'}, {pretty: true}) + html: await render(VerifyTemplate, { + appName: 'NuxtHubLanding', + link: `http://localhost:3000/verify?email=${email}&token=${generateSecureToken(email)}` + }, {pretty: true}) }) - - return entry + setResponseStatus(event, 201) }) diff --git a/server/api/verify.patch.ts b/server/api/verify.patch.ts index 36ce740..7d2c465 100644 --- a/server/api/verify.patch.ts +++ b/server/api/verify.patch.ts @@ -1,37 +1,45 @@ import {useDrizzle} from "~/server/utils/drizzle"; import {consola} from "consola"; import {useValidatedBody, z} from "h3-zod"; - -import {render} from "@vue-email/render"; -import Verify from "~/emails/Verify.vue"; -import {generateSecureToken} from "~/utlis/helpers"; import {waitlist} from "~/server/database/schema"; +import {compareToken} from "~/utlis/helpers"; +import {isNull, and} from "drizzle-orm"; export default defineEventHandler(async event => { consola.info("User trying to verify email ...") - const {email} = await useValidatedBody(event, z.object( + const {email, token} = await useValidatedBody(event, z.object( { token: z.string(), - email: z.string().email().refine(async (email) => { - const alreadyVerified = await useDrizzle().query.waitlist.findFirst({ - where: [ - (waitlist, {eq}) => eq(waitlist.email, email), - (waitlist, {isNotNull}) => isNotNull(waitlist.verifiedAt) - ], - }) - - return !alreadyVerified - }, 'Email already verified...') + email: z.string().email() + .refine(async (email) => { + const alreadyVerified = await useDrizzle().query.waitlist.findFirst({ + where: and( + eq(waitlist.email, email), + isNull(waitlist.verifiedAt), + ), + }) + + return alreadyVerified !== undefined + }, 'Email already verified.') + } )) - consola.info("User verifying email...") + if (!compareToken(email, token)) { + consola.error("token mismatch...") + throw createError({ + statusCode: 403, + message: 'Token mismatch', + }) + } + consola.info("User verifying email...") + const entry = await useDrizzle().update(tables.waitlist).set({ verifiedAt: new Date(), }).where(eq(waitlist.email, email)) diff --git a/server/database/migrations/0002_curious_brood.sql b/server/database/migrations/0002_curious_brood.sql new file mode 100644 index 0000000..4430550 --- /dev/null +++ b/server/database/migrations/0002_curious_brood.sql @@ -0,0 +1 @@ +ALTER TABLE `waitlist` ADD `verified_at` integer; \ No newline at end of file diff --git a/server/database/migrations/meta/0002_snapshot.json b/server/database/migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..347c279 --- /dev/null +++ b/server/database/migrations/meta/0002_snapshot.json @@ -0,0 +1,69 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "8afbf261-a066-440e-93ae-8da6acc5354c", + "prevId": "cb616729-fbff-4573-ad26-2bad022df348", + "tables": { + "waitlist": { + "name": "waitlist", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "verified_at": { + "name": "verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "email_idx": { + "name": "email_idx", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/server/database/migrations/meta/_journal.json b/server/database/migrations/meta/_journal.json index df9826c..3b582ef 100644 --- a/server/database/migrations/meta/_journal.json +++ b/server/database/migrations/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1727187012751, "tag": "0001_illegal_lenny_balinger", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1727279869048, + "tag": "0002_curious_brood", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/database/schema.ts b/server/database/schema.ts index 1fd9f7c..09aeb3e 100644 --- a/server/database/schema.ts +++ b/server/database/schema.ts @@ -3,6 +3,7 @@ import {sqliteTable, text, integer, uniqueIndex} from 'drizzle-orm/sqlite-core' export const waitlist = sqliteTable('waitlist', { id: integer('id').primaryKey({autoIncrement: true}), email: text('email').notNull().unique(), + verifiedAt: integer('verified_at', {mode: 'timestamp'}), createdAt: integer('created_at', {mode: 'timestamp'}).notNull(), }, (table) => ({ emailIdx: uniqueIndex('email_idx').on(table.email) diff --git a/utlis/helpers.ts b/utlis/helpers.ts index e69de29..800fdaa 100644 --- a/utlis/helpers.ts +++ b/utlis/helpers.ts @@ -0,0 +1,20 @@ +import crypto from 'crypto' + + +function generateSecureToken(email: string) { + const hmac = crypto.createHmac('md5', process.env.SECRET_KEY) + + return hmac.update(email).digest('hex') +} + + +function compareToken(email, token) { + + console.log(generateSecureToken(email, token), token) + return generateSecureToken(email, token) === token +} + +export { + compareToken, + generateSecureToken +} From f8f31657986c07161ee4e947cb918846f3786deb Mon Sep 17 00:00:00 2001 From: Tobias Lobitz Date: Wed, 25 Sep 2024 19:15:56 +0200 Subject: [PATCH 04/22] fix: remove token log --- utlis/helpers.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/utlis/helpers.ts b/utlis/helpers.ts index 800fdaa..4a33dc8 100644 --- a/utlis/helpers.ts +++ b/utlis/helpers.ts @@ -9,8 +9,6 @@ function generateSecureToken(email: string) { function compareToken(email, token) { - - console.log(generateSecureToken(email, token), token) return generateSecureToken(email, token) === token } From ac772b9d18b2bcf6cda4abd9055a245718031fe8 Mon Sep 17 00:00:00 2001 From: Tobias Lobitz Date: Thu, 21 Nov 2024 16:08:06 +0100 Subject: [PATCH 05/22] wip: Users needs to verify --- .env.example | 3 ++ README.md | 1 + app.vue | 2 + emails/VerifyEmail.vue | 5 +- modules/nuxtHubLanding/index.ts | 51 +++++++++++++++++++ .../runtime}/join-waitlist.post.ts | 36 ++++++++----- .../nuxtHubLanding/runtime}/verify.patch.ts | 4 +- nuxt.config.ts | 6 +++ package-lock.json | 1 + package.json | 1 + pages/verify.vue | 45 ++++++++++++---- tsconfig.json | 3 ++ types/nuxt.d.ts | 7 +++ 13 files changed, 138 insertions(+), 27 deletions(-) create mode 100644 modules/nuxtHubLanding/index.ts rename {server/api => modules/nuxtHubLanding/runtime}/join-waitlist.post.ts (55%) rename {server/api => modules/nuxtHubLanding/runtime}/verify.patch.ts (97%) create mode 100644 types/nuxt.d.ts diff --git a/.env.example b/.env.example index 4fef032..ec54f23 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,5 @@ +NUXT_APPLICATION_NAME="NuxtHubLanding" +NUXT_HUB_PROJECT_KEY="" NUXT_RESEND_API_KEY="" NUXT_APP_URL="http://localhost:3000" +SECRET_KEY= diff --git a/README.md b/README.md index 4057400..380f844 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ Check out the [deployment documentation](https://hub.nuxt.com/docs/getting-start ## Roadmap - [X] Add rate limiting via [NuxtSecurity](https://nuxt-security.vercel.app/documentation/middleware/rate-limiter) - [ ] Add Double-Opt-in +- [ ] Add [NuxtSeo](https://nuxtseo.com/nuxt-seo/getting-started/what-is-nuxt-seo) module - [ ] Add more basic components - [ ] Faq-Area - [ ] Pricing-Area diff --git a/app.vue b/app.vue index 824e24d..6797b5d 100644 --- a/app.vue +++ b/app.vue @@ -18,6 +18,8 @@ useSeoMeta({ }) const currentYear = computed(() => new Date().getFullYear()) + +console.log(useRuntimeConfig().nuxtHubLanding)