From ae45410b82623e004a3eb911ab471d069e8077a0 Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Tue, 14 Oct 2025 16:30:33 +0200 Subject: [PATCH] Use html in emails Signed-off-by: Sacha Al Himdani --- .gitignore | 1 + .goreleaser.yaml | 2 +- GNUmakefile | 10 +- package-lock.json | 1424 ++++++++++++++++- packages/emails/emails.go | 167 ++ packages/emails/package.json | 22 + packages/emails/scripts/build.ts | 74 + packages/emails/src/ConfirmEmail.tsx | 25 + packages/emails/src/DocumentExport.tsx | 25 + packages/emails/src/DocumentSigning.tsx | 36 + packages/emails/src/FrameworkExport.tsx | 25 + packages/emails/src/Invitation.tsx | 25 + packages/emails/src/PasswordReset.tsx | 25 + packages/emails/src/TrustCenterAccess.tsx | 25 + .../emails/src/components/EmailLayout.tsx | 149 ++ packages/emails/src/components/ProboLogo.tsx | 34 + packages/emails/templates/confirm-email.txt | 11 + packages/emails/templates/document-export.txt | 11 + .../emails/templates/document-signing.txt | 19 + .../emails/templates/framework-export.txt | 11 + .../emails/templates/invitation.txt | 9 +- packages/emails/templates/password-reset.txt | 11 + .../emails/templates/trust-center-access.txt | 11 + packages/emails/tsconfig.json | 12 + pkg/auth/emails/password_reset.txt.tmpl | 12 - pkg/auth/emails/signup_confirmation.txt.tmpl | 12 - pkg/auth/service.go | 50 +- pkg/authz/service.go | 56 +- pkg/coredata/email.go | 14 +- pkg/coredata/migrations/20251014T132711Z.sql | 1 + pkg/mailer/mailer.go | 4 + pkg/probo/document_service.go | 54 +- pkg/probo/framework_service.go | 22 +- pkg/probo/trust_center_access_service.go | 30 +- 34 files changed, 2211 insertions(+), 208 deletions(-) create mode 100644 packages/emails/emails.go create mode 100644 packages/emails/package.json create mode 100644 packages/emails/scripts/build.ts create mode 100644 packages/emails/src/ConfirmEmail.tsx create mode 100644 packages/emails/src/DocumentExport.tsx create mode 100644 packages/emails/src/DocumentSigning.tsx create mode 100644 packages/emails/src/FrameworkExport.tsx create mode 100644 packages/emails/src/Invitation.tsx create mode 100644 packages/emails/src/PasswordReset.tsx create mode 100644 packages/emails/src/TrustCenterAccess.tsx create mode 100644 packages/emails/src/components/EmailLayout.tsx create mode 100644 packages/emails/src/components/ProboLogo.tsx create mode 100644 packages/emails/templates/confirm-email.txt create mode 100644 packages/emails/templates/document-export.txt create mode 100644 packages/emails/templates/document-signing.txt create mode 100644 packages/emails/templates/framework-export.txt rename pkg/authz/emails/invitation.txt.tmpl => packages/emails/templates/invitation.txt (51%) create mode 100644 packages/emails/templates/password-reset.txt create mode 100644 packages/emails/templates/trust-center-access.txt create mode 100644 packages/emails/tsconfig.json delete mode 100644 pkg/auth/emails/password_reset.txt.tmpl delete mode 100644 pkg/auth/emails/signup_confirmation.txt.tmpl create mode 100644 pkg/coredata/migrations/20251014T132711Z.sql diff --git a/.gitignore b/.gitignore index d6fbce578..3477fef13 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules/ .turbo .vscode dist/ +.react-email/ sbom.json sbom-docker.json *_sbom.json diff --git a/.goreleaser.yaml b/.goreleaser.yaml index f9636d6f4..3dca27b98 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -5,7 +5,7 @@ project_name: probod before: hooks: - - make @probo/console @probo/trust + - make @probo/emails @probo/console @probo/trust builds: - id: probod diff --git a/GNUmakefile b/GNUmakefile index 168435cd8..7b5df79df 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -42,7 +42,7 @@ all: build lint: vet # npm-lint .PHONY: vet -vet: +vet: @probo/emails $(GO_VET) ./... .PHONY: npm-lint @@ -72,7 +72,7 @@ test-bench: TEST_FLAGS+=-bench=. test-bench: test ## Run benchmark tests .PHONY: build -build: @probo/console @probo/trust bin/probod +build: @probo/emails @probo/console @probo/trust bin/probod .PHONY: sbom-docker sbom-docker: docker-build @@ -114,9 +114,14 @@ bin/probod: pkg/server/api/console/v1/schema/schema.go \ pkg/server/api/trust/v1/schema/schema.go \ pkg/server/api/trust/v1/types/types.go \ pkg/server/api/trust/v1/v1_resolver.go \ + @probo/emails \ vet $(GO_BUILD) -o $(PROBOD_BIN) $(PROBOD_SRC) +.PHONY: @probo/emails +@probo/emails: + $(NPM) --workspace $@ run build + .PHONY: @probo/console @probo/console: NODE_ENV=production @probo/console: @@ -159,6 +164,7 @@ clean: ## Clean the project (node_modules and build artifacts) $(RM) -rf bin/* $(RM) -rf node_modules $(RM) -rf apps/{console,trust}/{dist,node_modules} + $(RM) -rf packages/emails/{dist,node_modules} $(RM) -rf sbom-docker.json sbom.json $(RM) -rf coverage.out coverage.html diff --git a/package-lock.json b/package-lock.json index 0d60de96f..a80cbaf69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -174,7 +174,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -230,7 +229,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.0", @@ -264,7 +262,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -316,7 +313,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -326,7 +322,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -360,7 +355,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.0" @@ -417,7 +411,6 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -432,7 +425,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -451,7 +443,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -492,6 +483,16 @@ "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", @@ -1222,11 +1223,31 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -1594,6 +1615,10 @@ "resolved": "apps/console", "link": true }, + "node_modules/@probo/emails": { + "resolved": "packages/emails", + "link": true + }, "node_modules/@probo/esbuild-plugin-postcss": { "resolved": "packages/esbuild-plugin-postcss", "link": true @@ -2402,6 +2427,286 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@react-email/body": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.1.0.tgz", + "integrity": "sha512-o1bcSAmDYNNHECbkeyceCVPGmVsYvT+O3sSO/Ct7apKUu3JphTi31hu+0Nwqr/pgV5QFqdoT5vdS3SW5DJFHgQ==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/button": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@react-email/button/-/button-0.2.0.tgz", + "integrity": "sha512-8i+v6cMxr2emz4ihCrRiYJPp2/sdYsNNsBzXStlcA+/B9Umpm5Jj3WJKYpgTPM+aeyiqlG/MMI1AucnBm4f1oQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/code-block": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.1.0.tgz", + "integrity": "sha512-jSpHFsgqnQXxDIssE4gvmdtFncaFQz5D6e22BnVjcCPk/udK+0A9jRwGFEG8JD2si9ZXBmU4WsuqQEczuZn4ww==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/code-inline": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@react-email/code-inline/-/code-inline-0.0.5.tgz", + "integrity": "sha512-MmAsOzdJpzsnY2cZoPHFPk6uDO/Ncpb4Kh1hAt9UZc1xOW3fIzpe1Pi9y9p6wwUmpaeeDalJxAxH6/fnTquinA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/column": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@react-email/column/-/column-0.0.13.tgz", + "integrity": "sha512-Lqq17l7ShzJG/d3b1w/+lVO+gp2FM05ZUo/nW0rjxB8xBICXOVv6PqjDnn3FXKssvhO5qAV20lHM6S+spRhEwQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/components": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.5.6.tgz", + "integrity": "sha512-3o9ellDaF3bBcVMWeos9HI0iUIT1zGygPRcn9WSfI5JREORiN6ViEJIvz5SKWEn1KPNZtw/iaW8ct7PpVyhomg==", + "license": "MIT", + "dependencies": { + "@react-email/body": "0.1.0", + "@react-email/button": "0.2.0", + "@react-email/code-block": "0.1.0", + "@react-email/code-inline": "0.0.5", + "@react-email/column": "0.0.13", + "@react-email/container": "0.0.15", + "@react-email/font": "0.0.9", + "@react-email/head": "0.0.12", + "@react-email/heading": "0.0.15", + "@react-email/hr": "0.0.11", + "@react-email/html": "0.0.11", + "@react-email/img": "0.0.11", + "@react-email/link": "0.0.12", + "@react-email/markdown": "0.0.15", + "@react-email/preview": "0.0.13", + "@react-email/render": "1.3.2", + "@react-email/row": "0.0.12", + "@react-email/section": "0.0.16", + "@react-email/tailwind": "1.2.2", + "@react-email/text": "0.1.5" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/container": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.15.tgz", + "integrity": "sha512-Qo2IQo0ru2kZq47REmHW3iXjAQaKu4tpeq/M8m1zHIVwKduL2vYOBQWbC2oDnMtWPmkBjej6XxgtZByxM6cCFg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/font": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@react-email/font/-/font-0.0.9.tgz", + "integrity": "sha512-4zjq23oT9APXkerqeslPH3OZWuh5X4crHK6nx82mVHV2SrLba8+8dPEnWbaACWTNjOCbcLIzaC9unk7Wq2MIXw==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/head": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@react-email/head/-/head-0.0.12.tgz", + "integrity": "sha512-X2Ii6dDFMF+D4niNwMAHbTkeCjlYYnMsd7edXOsi0JByxt9wNyZ9EnhFiBoQdqkE+SMDcu8TlNNttMrf5sJeMA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/heading": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@react-email/heading/-/heading-0.0.15.tgz", + "integrity": "sha512-xF2GqsvBrp/HbRHWEfOgSfRFX+Q8I5KBEIG5+Lv3Vb2R/NYr0s8A5JhHHGf2pWBMJdbP4B2WHgj/VUrhy8dkIg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/hr": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@react-email/hr/-/hr-0.0.11.tgz", + "integrity": "sha512-S1gZHVhwOsd1Iad5IFhpfICwNPMGPJidG/Uysy1AwmspyoAP5a4Iw3OWEpINFdgh9MHladbxcLKO2AJO+cA9Lw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/html": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@react-email/html/-/html-0.0.11.tgz", + "integrity": "sha512-qJhbOQy5VW5qzU74AimjAR9FRFQfrMa7dn4gkEXKMB/S9xZN8e1yC1uA9C15jkXI/PzmJ0muDIWmFwatm5/+VA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/img": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@react-email/img/-/img-0.0.11.tgz", + "integrity": "sha512-aGc8Y6U5C3igoMaqAJKsCpkbm1XjguQ09Acd+YcTKwjnC2+0w3yGUJkjWB2vTx4tN8dCqQCXO8FmdJpMfOA9EQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/link": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@react-email/link/-/link-0.0.12.tgz", + "integrity": "sha512-vF+xxQk2fGS1CN7UPQDbzvcBGfffr+GjTPNiWM38fhBfsLv6A/YUfaqxWlmL7zLzVmo0K2cvvV9wxlSyNba1aQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/markdown": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@react-email/markdown/-/markdown-0.0.15.tgz", + "integrity": "sha512-UQA9pVm5sbflgtg3EX3FquUP4aMBzmLReLbGJ6DZQZnAskBF36aI56cRykDq1o+1jT+CKIK1CducPYziaXliag==", + "license": "MIT", + "dependencies": { + "md-to-react-email": "^5.0.5" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/preview": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@react-email/preview/-/preview-0.0.13.tgz", + "integrity": "sha512-F7j9FJ0JN/A4d7yr+aw28p4uX7VLWs7hTHtLo7WRyw4G+Lit6Zucq4UWKRxJC8lpsUdzVmG7aBJnKOT+urqs/w==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/render": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.3.2.tgz", + "integrity": "sha512-oq8/BD/I/YspeuBjjdLJG6xaf9tsPYk+VWu8/mX9xWbRN0t0ExKSVm9sEBL6RsCpndQA2jbY2VgPEreIrzUgqw==", + "license": "MIT", + "dependencies": { + "html-to-text": "^9.0.5", + "prettier": "^3.5.3", + "react-promise-suspense": "^0.3.4" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/row": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@react-email/row/-/row-0.0.12.tgz", + "integrity": "sha512-HkCdnEjvK3o+n0y0tZKXYhIXUNPDx+2vq1dJTmqappVHXS5tXS6W5JOPZr5j+eoZ8gY3PShI2LWj5rWF7ZEtIQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/section": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@react-email/section/-/section-0.0.16.tgz", + "integrity": "sha512-FjqF9xQ8FoeUZYKSdt8sMIKvoT9XF8BrzhT3xiFKdEMwYNbsDflcjfErJe3jb7Wj/es/lKTbV5QR1dnLzGpL3w==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/tailwind": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-1.2.2.tgz", + "integrity": "sha512-heO9Khaqxm6Ulm6p7HQ9h01oiiLRrZuuEQuYds/O7Iyp3c58sMVHZGIxiRXO/kSs857NZQycpjewEVKF3jhNTw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, + "node_modules/@react-email/text": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.5.tgz", + "integrity": "sha512-o5PNHFSE085VMXayxH+SJ1LSOtGsTv+RpNKnTiJDrJUwoBu77G3PlKOsZZQHCNyD28WsQpl9v2WcJLbQudqwPg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.19", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", @@ -2692,6 +2997,25 @@ "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==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "selderee": "^0.11.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@standard-schema/utils": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", @@ -3697,6 +4021,15 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -3777,7 +4110,6 @@ "version": "22.16.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.0.tgz", "integrity": "sha512-B2egV9wALML1JCpv3VQoQ+yesQKAmNMBIAY7OteVrikcOcAkWm+dGL6qpeCktPjAv6N1JLnhbNiqS35UpFyBsQ==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -4513,6 +4845,19 @@ "node": ">=14.0.0" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -4557,7 +4902,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4567,7 +4911,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4856,6 +5199,15 @@ "dev": true, "license": "MIT" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/better-opn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", @@ -5143,6 +5495,21 @@ "node": ">= 16" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -5176,6 +5543,42 @@ } } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -5189,7 +5592,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5202,7 +5604,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/comma-separated-tokens": { @@ -5215,6 +5616,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5222,6 +5632,21 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5238,6 +5663,19 @@ "node": ">=18" } }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", @@ -5267,7 +5705,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5304,6 +5741,18 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/debounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.0.0.tgz", + "integrity": "sha512-xRetU6gL1VJbs85Mc4FoEGSjQxzpdxRyFhe3lmWFyy2EzydIcD4xzUvRJMD+NPDfMwKNhxa3PvsIOU32luIWeA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -5351,6 +5800,15 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -5436,6 +5894,73 @@ "dev": true, "license": "MIT" }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5455,7 +5980,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/electron-to-chromium": { @@ -5469,9 +5993,84 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { "version": "5.18.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", @@ -5860,6 +6459,12 @@ "node": ">=12.0.0" } }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -6068,7 +6673,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -6129,6 +6733,18 @@ "node": ">=6.9.0" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -6177,6 +6793,19 @@ "node": ">= 0.4" } }, + "node_modules/get-tsconfig": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz", + "integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -6502,6 +7131,22 @@ "dev": true, "license": "MIT" }, + "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==", + "license": "MIT", + "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-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", @@ -6522,6 +7167,37 @@ "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" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6715,7 +7391,6 @@ "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==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6763,6 +7438,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6820,6 +7507,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -6837,7 +7536,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -6953,7 +7651,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -7001,7 +7698,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -7037,12 +7733,20 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "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==", + "license": "MIT", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7342,6 +8046,22 @@ "dev": true, "license": "MIT" }, + "node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -7476,6 +8196,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/marked": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-7.0.4.tgz", + "integrity": "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7486,6 +8218,18 @@ "node": ">= 0.4" } }, + "node_modules/md-to-react-email": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/md-to-react-email/-/md-to-react-email-5.0.5.tgz", + "integrity": "sha512-OvAXqwq57uOk+WZqFFNCMZz8yDp8BD3WazW1wAKHUrPbbdr89K9DWS6JXY09vd9xNdPNeurI8DU/X4flcfaD8A==", + "license": "MIT", + "dependencies": { + "marked": "7.0.4" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0" + } + }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", @@ -8395,6 +9139,39 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -8422,7 +9199,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8511,6 +9287,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -8538,6 +9323,15 @@ "dev": true, "license": "MIT" }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/normalize-range": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", @@ -8554,6 +9348,25 @@ "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", "license": "MIT" }, + "node_modules/nypm": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.0.tgz", + "integrity": "sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==", + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "pathe": "^2.0.3", + "pkg-types": "^2.0.0", + "tinyexec": "^0.3.2" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8563,6 +9376,21 @@ "node": ">=0.10.0" } }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -8599,6 +9427,92 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/ora/node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -8635,7 +9549,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -8712,6 +9625,19 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parseley": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", + "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "license": "MIT", + "dependencies": { + "leac": "^0.6.0", + "peberminta": "^0.9.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8726,7 +9652,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8777,7 +9702,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/pathval": { @@ -8802,6 +9726,15 @@ "@napi-rs/canvas": "^0.1.71" } }, + "node_modules/peberminta": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", + "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", + "license": "MIT", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -8820,6 +9753,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/playwright": { "version": "1.53.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz", @@ -8938,7 +9882,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", - "dev": true, "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -8978,6 +9921,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -9001,7 +9953,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, "license": "MIT", "dependencies": { "kleur": "^3.0.3", @@ -9155,6 +10106,135 @@ "react": ">= 16.8 || 18.0.0" } }, + "node_modules/react-email": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/react-email/-/react-email-4.3.0.tgz", + "integrity": "sha512-XFHCSfhdlO7k5q2TYGwC0HsVh5Yn13YaOdahuJEUEOfOJKHEpSP4PKg7R/RiKFoK9cDvzunhY+58pXxz0vE2zA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/traverse": "^7.27.0", + "chokidar": "^4.0.3", + "commander": "^13.0.0", + "debounce": "^2.0.0", + "esbuild": "^0.25.0", + "glob": "^11.0.0", + "jiti": "2.4.2", + "log-symbols": "^7.0.0", + "mime-types": "^3.0.0", + "normalize-path": "^3.0.0", + "nypm": "0.6.0", + "ora": "^8.0.0", + "prompts": "2.4.2", + "socket.io": "^4.8.1", + "tsconfig-paths": "4.2.0" + }, + "bin": { + "email": "dist/index.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/react-email/node_modules/glob": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/react-email/node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/react-email/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/react-email/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/react-email/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/react-email/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/react-email/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/react-error-boundary": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.0.0.tgz", @@ -9261,6 +10341,21 @@ } } }, + "node_modules/react-promise-suspense": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz", + "integrity": "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^2.0.1" + } + }, + "node_modules/react-promise-suspense/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -9378,6 +10473,19 @@ } } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/recast": { "version": "0.23.11", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", @@ -9555,6 +10663,32 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -9653,6 +10787,18 @@ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", "license": "MIT" }, + "node_modules/selderee": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", + "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "license": "MIT", + "dependencies": { + "parseley": "^0.12.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -9697,7 +10843,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -9710,7 +10855,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9727,7 +10871,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -9755,9 +10898,121 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, "license": "MIT" }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9808,6 +11063,18 @@ "dev": true, "license": "MIT" }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/storybook": { "version": "8.6.14", "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.14.tgz", @@ -9839,7 +11106,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -9858,7 +11124,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -9873,14 +11138,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -9907,7 +11170,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -9924,7 +11186,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -9937,7 +11198,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -9950,7 +11210,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -10174,7 +11433,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, "license": "MIT" }, "node_modules/tinyglobby": { @@ -10299,7 +11557,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, "license": "MIT", "dependencies": { "json5": "^2.2.2", @@ -10316,6 +11573,41 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/turbo": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.5.4.tgz", @@ -10527,7 +11819,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, "license": "MIT" }, "node_modules/unified": { @@ -10796,6 +12087,15 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -11136,7 +12436,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -11201,7 +12500,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -11220,7 +12518,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -11238,14 +12535,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11260,7 +12555,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11273,7 +12567,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -11324,6 +12617,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "3.25.71", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.71.tgz", @@ -11372,6 +12677,21 @@ "url": "https://github.com/sponsors/wooorm" } }, + "packages/emails": { + "name": "@probo/emails", + "version": "0.1.0", + "dependencies": { + "@react-email/components": "^0.5.6", + "react": "^19.0.0", + "react-email": "^4.3.0" + }, + "devDependencies": { + "@types/node": "^22.10.7", + "@types/react": "^19.0.6", + "tsx": "^4.19.2", + "typescript": "^5.7.3" + } + }, "packages/esbuild-plugin-postcss": { "name": "@probo/esbuild-plugin-postcss", "version": "0.0.1", diff --git a/packages/emails/emails.go b/packages/emails/emails.go new file mode 100644 index 000000000..94f963674 --- /dev/null +++ b/packages/emails/emails.go @@ -0,0 +1,167 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package emails + +import ( + "bytes" + "embed" + "fmt" + htmltemplate "html/template" + texttemplate "text/template" +) + +//go:embed dist +var Templates embed.FS + +const ( + subjectConfirmEmail = "Confirm your email address" + subjectPasswordReset = "Reset your password" + subjectInvitation = "Invitation to join %s on Probo" + subjectDocumentSigning = "Action Required – Please review and sign %s compliance documents" + subjectDocumentExport = "Your document export is ready" + subjectFrameworkExport = "Your framework export is ready" + subjectTrustCenterAccess = "Trust Center Access Invitation - %s" +) + +var ( + confirmEmailHTMLTemplate = htmltemplate.Must(htmltemplate.ParseFS(Templates, "dist/confirm-email.html.tmpl")) + confirmEmailTextTemplate = texttemplate.Must(texttemplate.ParseFS(Templates, "dist/confirm-email.txt.tmpl")) + passwordResetHTMLTemplate = htmltemplate.Must(htmltemplate.ParseFS(Templates, "dist/password-reset.html.tmpl")) + passwordResetTextTemplate = texttemplate.Must(texttemplate.ParseFS(Templates, "dist/password-reset.txt.tmpl")) + invitationHTMLTemplate = htmltemplate.Must(htmltemplate.ParseFS(Templates, "dist/invitation.html.tmpl")) + invitationTextTemplate = texttemplate.Must(texttemplate.ParseFS(Templates, "dist/invitation.txt.tmpl")) + documentSigningHTMLTemplate = htmltemplate.Must(htmltemplate.ParseFS(Templates, "dist/document-signing.html.tmpl")) + documentSigningTextTemplate = texttemplate.Must(texttemplate.ParseFS(Templates, "dist/document-signing.txt.tmpl")) + documentExportHTMLTemplate = htmltemplate.Must(htmltemplate.ParseFS(Templates, "dist/document-export.html.tmpl")) + documentExportTextTemplate = texttemplate.Must(texttemplate.ParseFS(Templates, "dist/document-export.txt.tmpl")) + frameworkExportHTMLTemplate = htmltemplate.Must(htmltemplate.ParseFS(Templates, "dist/framework-export.html.tmpl")) + frameworkExportTextTemplate = texttemplate.Must(texttemplate.ParseFS(Templates, "dist/framework-export.txt.tmpl")) + trustCenterAccessHTMLTemplate = htmltemplate.Must(htmltemplate.ParseFS(Templates, "dist/trust-center-access.html.tmpl")) + trustCenterAccessTextTemplate = texttemplate.Must(texttemplate.ParseFS(Templates, "dist/trust-center-access.txt.tmpl")) +) + +func RenderConfirmEmail(fullName, confirmationUrl string) (subject string, textBody string, htmlBody *string, err error) { + data := struct { + FullName string + ConfirmationUrl string + }{ + FullName: fullName, + ConfirmationUrl: confirmationUrl, + } + + textBody, htmlBody, err = renderEmail(confirmEmailTextTemplate, confirmEmailHTMLTemplate, data) + return subjectConfirmEmail, textBody, htmlBody, err +} + +func RenderPasswordReset(fullName, resetUrl string) (subject string, textBody string, htmlBody *string, err error) { + data := struct { + FullName string + ResetUrl string + }{ + FullName: fullName, + ResetUrl: resetUrl, + } + + textBody, htmlBody, err = renderEmail(passwordResetTextTemplate, passwordResetHTMLTemplate, data) + return subjectPasswordReset, textBody, htmlBody, err +} + +func RenderInvitation(fullName, organizationName, invitationUrl string) (subject string, textBody string, htmlBody *string, err error) { + data := struct { + FullName string + OrganizationName string + InvitationUrl string + }{ + FullName: fullName, + OrganizationName: organizationName, + InvitationUrl: invitationUrl, + } + + textBody, htmlBody, err = renderEmail(invitationTextTemplate, invitationHTMLTemplate, data) + return fmt.Sprintf(subjectInvitation, organizationName), textBody, htmlBody, err +} + +func RenderDocumentSigning(fullName, organizationName, signingUrl string) (subject string, textBody string, htmlBody *string, err error) { + data := struct { + FullName string + OrganizationName string + SigningUrl string + }{ + FullName: fullName, + OrganizationName: organizationName, + SigningUrl: signingUrl, + } + + textBody, htmlBody, err = renderEmail(documentSigningTextTemplate, documentSigningHTMLTemplate, data) + return fmt.Sprintf(subjectDocumentSigning, organizationName), textBody, htmlBody, err +} + +func RenderDocumentExport(fullName, downloadUrl string) (subject string, textBody string, htmlBody *string, err error) { + data := struct { + FullName string + DownloadUrl string + }{ + FullName: fullName, + DownloadUrl: downloadUrl, + } + + textBody, htmlBody, err = renderEmail(documentExportTextTemplate, documentExportHTMLTemplate, data) + return subjectDocumentExport, textBody, htmlBody, err +} + +func RenderFrameworkExport(fullName, downloadUrl string) (subject string, textBody string, htmlBody *string, err error) { + data := struct { + FullName string + DownloadUrl string + }{ + FullName: fullName, + DownloadUrl: downloadUrl, + } + + textBody, htmlBody, err = renderEmail(frameworkExportTextTemplate, frameworkExportHTMLTemplate, data) + return subjectFrameworkExport, textBody, htmlBody, err +} + +func RenderTrustCenterAccess(fullName, organizationName, accessUrl string) (subject string, textBody string, htmlBody *string, err error) { + data := struct { + FullName string + OrganizationName string + AccessUrl string + }{ + FullName: fullName, + OrganizationName: organizationName, + AccessUrl: accessUrl, + } + + textBody, htmlBody, err = renderEmail(trustCenterAccessTextTemplate, trustCenterAccessHTMLTemplate, data) + return fmt.Sprintf(subjectTrustCenterAccess, organizationName), textBody, htmlBody, err +} + +func renderEmail(textTemplate *texttemplate.Template, htmlTemplate *htmltemplate.Template, data any) (textBody string, htmlBody *string, err error) { + var textBuf bytes.Buffer + if err := textTemplate.Execute(&textBuf, data); err != nil { + return "", nil, fmt.Errorf("failed to execute text template: %w", err) + } + textBody = textBuf.String() + + var htmlBuf bytes.Buffer + if err := htmlTemplate.Execute(&htmlBuf, data); err != nil { + return "", nil, fmt.Errorf("failed to execute html template: %w", err) + } + htmlBodyStr := htmlBuf.String() + htmlBody = &htmlBodyStr + + return textBody, htmlBody, nil +} diff --git a/packages/emails/package.json b/packages/emails/package.json new file mode 100644 index 000000000..5312b6a04 --- /dev/null +++ b/packages/emails/package.json @@ -0,0 +1,22 @@ +{ + "name": "@probo/emails", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "email dev --dir src --port 3003", + "build": "tsx scripts/build.ts", + "export": "email export" + }, + "dependencies": { + "@react-email/components": "^0.5.6", + "react": "^19.0.0", + "react-email": "^4.3.0" + }, + "devDependencies": { + "@types/node": "^22.10.7", + "@types/react": "^19.0.6", + "tsx": "^4.19.2", + "typescript": "^5.7.3" + } +} diff --git a/packages/emails/scripts/build.ts b/packages/emails/scripts/build.ts new file mode 100644 index 000000000..5c2bc278c --- /dev/null +++ b/packages/emails/scripts/build.ts @@ -0,0 +1,74 @@ +import { render } from '@react-email/components'; +import { copyFile, mkdir, writeFile } from 'fs/promises'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import * as React from 'react'; + +import ConfirmEmail from '../src/ConfirmEmail'; +import DocumentExport from '../src/DocumentExport'; +import DocumentSigning from '../src/DocumentSigning'; +import FrameworkExport from '../src/FrameworkExport'; +import Invitation from '../src/Invitation'; +import PasswordReset from '../src/PasswordReset'; +import TrustCenterAccess from '../src/TrustCenterAccess'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +type TemplateConfig = { + name: string; + render: () => React.ReactElement; +}; + +const templates: TemplateConfig[] = [ + { + name: 'confirm-email', + render: () => ConfirmEmail() + }, + { + name: 'password-reset', + render: () => PasswordReset() + }, + { + name: 'invitation', + render: () => Invitation() + }, + { + name: 'document-signing', + render: () => DocumentSigning() + }, + { + name: 'document-export', + render: () => DocumentExport() + }, + { + name: 'framework-export', + render: () => FrameworkExport() + }, + { + name: 'trust-center-access', + render: () => TrustCenterAccess() + }, +]; + +async function build() { + const outputDir = join(__dirname, '..', 'dist'); + const templatesDir = join(__dirname, '..', 'templates'); + await mkdir(outputDir, { recursive: true }); + + for (const template of templates) { + const html = await render(template.render(), { pretty: true }); + + const htmlPath = join(outputDir, `${template.name}.html.tmpl`); + const txtSrcPath = join(templatesDir, `${template.name}.txt`); + const txtDstPath = join(outputDir, `${template.name}.txt.tmpl`); + + await writeFile(htmlPath, html); + await copyFile(txtSrcPath, txtDstPath); + } +} + +build().catch((err) => { + console.error('Failed to build email templates:', err); + process.exit(1); +}); diff --git a/packages/emails/src/ConfirmEmail.tsx b/packages/emails/src/ConfirmEmail.tsx new file mode 100644 index 000000000..58a97871f --- /dev/null +++ b/packages/emails/src/ConfirmEmail.tsx @@ -0,0 +1,25 @@ +import { Button, Section, Text } from '@react-email/components'; +import * as React from 'react'; +import EmailLayout, { bodyText, button, buttonContainer, footerText } from './components/EmailLayout'; + +export const ConfirmEmail = () => { + return ( + + + Thanks for joining Probo! Please confirm your email address by clicking the button below: + + +
+ +
+ + + If you did not sign up for Probo, please ignore this email. + +
+ ); +}; + +export default ConfirmEmail; diff --git a/packages/emails/src/DocumentExport.tsx b/packages/emails/src/DocumentExport.tsx new file mode 100644 index 000000000..88106cd3f --- /dev/null +++ b/packages/emails/src/DocumentExport.tsx @@ -0,0 +1,25 @@ +import { Button, Section, Text } from '@react-email/components'; +import * as React from 'react'; +import EmailLayout, { bodyText, button, buttonContainer, footerText } from './components/EmailLayout'; + +export const DocumentExport = () => { + return ( + + + Your document export has been completed successfully. Click the button below to download it: + + +
+ +
+ + + This link will expire in 24 hours. + +
+ ); +}; + +export default DocumentExport; diff --git a/packages/emails/src/DocumentSigning.tsx b/packages/emails/src/DocumentSigning.tsx new file mode 100644 index 000000000..d3d82afa4 --- /dev/null +++ b/packages/emails/src/DocumentSigning.tsx @@ -0,0 +1,36 @@ +import { Button, Section, Text } from '@react-email/components'; +import * as React from 'react'; +import EmailLayout, { bodyText, button, buttonContainer, footerText } from './components/EmailLayout'; + +export const DocumentSigning = () => { + return ( + + + You're receiving this message because your company, {'{{.OrganizationName}}'}, has shared a new compliance document that requires your review and signature. + + + To stay compliant with company policies, please take a moment to review and sign the document by clicking the button below: + + +
+ +
+ + + If you have any questions, please contact your security team. + + + + This process is managed securely by Probo, acting as the compliance partner on behalf of {'{{.OrganizationName}}'}. + Thank you for your prompt attention to this matter. + + + Best regards, + +
+ ); +}; + +export default DocumentSigning; diff --git a/packages/emails/src/FrameworkExport.tsx b/packages/emails/src/FrameworkExport.tsx new file mode 100644 index 000000000..8d6547978 --- /dev/null +++ b/packages/emails/src/FrameworkExport.tsx @@ -0,0 +1,25 @@ +import { Button, Section, Text } from '@react-email/components'; +import * as React from 'react'; +import EmailLayout, { bodyText, button, buttonContainer, footerText } from './components/EmailLayout'; + +export const FrameworkExport = () => { + return ( + + + Your framework export has been completed successfully. Click the button below to download it: + + +
+ +
+ + + This link will expire in 24 hours. + +
+ ); +}; + +export default FrameworkExport; diff --git a/packages/emails/src/Invitation.tsx b/packages/emails/src/Invitation.tsx new file mode 100644 index 000000000..f62737683 --- /dev/null +++ b/packages/emails/src/Invitation.tsx @@ -0,0 +1,25 @@ +import { Button, Section, Text } from '@react-email/components'; +import * as React from 'react'; +import EmailLayout, { bodyText, button, buttonContainer, footerText } from './components/EmailLayout'; + +export const Invitation = () => { + return ( + + + You have been invited to join organization {'{{.OrganizationName}}'} on Probo. Click the button below to accept the invitation: + + +
+ +
+ + + If you don't want to accept the invitation, you can ignore this email. + +
+ ); +}; + +export default Invitation; diff --git a/packages/emails/src/PasswordReset.tsx b/packages/emails/src/PasswordReset.tsx new file mode 100644 index 000000000..416e0ca60 --- /dev/null +++ b/packages/emails/src/PasswordReset.tsx @@ -0,0 +1,25 @@ +import { Button, Section, Text } from '@react-email/components'; +import * as React from 'react'; +import EmailLayout, { bodyText, button, buttonContainer, footerText } from './components/EmailLayout'; + +export const PasswordReset = () => { + return ( + + + You have requested a password reset for your Probo account. Click the button below to reset your password: + + +
+ +
+ + + If you did not request this password reset, please ignore this email. + +
+ ); +}; + +export default PasswordReset; diff --git a/packages/emails/src/TrustCenterAccess.tsx b/packages/emails/src/TrustCenterAccess.tsx new file mode 100644 index 000000000..fc14764ef --- /dev/null +++ b/packages/emails/src/TrustCenterAccess.tsx @@ -0,0 +1,25 @@ +import { Button, Section, Text } from '@react-email/components'; +import * as React from 'react'; +import EmailLayout, { bodyText, button, buttonContainer, footerText } from './components/EmailLayout'; + +export const TrustCenterAccess = () => { + return ( + + + You have been granted access to {'{{.OrganizationName}}'}'s Trust Center! Click the button below to access it: + + +
+ +
+ + + This link will expire in 7 days. + +
+ ); +}; + +export default TrustCenterAccess; diff --git a/packages/emails/src/components/EmailLayout.tsx b/packages/emails/src/components/EmailLayout.tsx new file mode 100644 index 000000000..38226df02 --- /dev/null +++ b/packages/emails/src/components/EmailLayout.tsx @@ -0,0 +1,149 @@ +import { + Body, + Button, + Container, + Head, + Heading, + Html, + Link, + Preview, + Section, + Text, +} from '@react-email/components'; +import * as React from 'react'; +import { ProboLogo } from './ProboLogo'; + +interface EmailLayoutProps { + subject: string; + children: React.ReactNode; + organizationName?: string; +} + +export const EmailLayout = ({ + subject, + children, + organizationName, +}: EmailLayoutProps) => { + return ( + + + + + {subject} + + + +
+
+ + + +
+ + Hi {'{{.FullName}}'}, + + {children} +
+ +
+ + Probo Inc, 490 Post St, STE 640, San Francisco, CA, 94102, US + +
+
+ + + ); +}; + +export default EmailLayout; + +const main: React.CSSProperties = { + margin: '0', + padding: '0', + fontFamily: + "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif", + backgroundColor: '#fffffb', + WebkitTextSizeAdjust: '100%', +}; + +const container: React.CSSProperties = { + maxWidth: '600px', + width: '100%', + backgroundColor: '#ffffff', + borderRadius: '8px', + boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)', + margin: '40px auto', +}; + +export const headerSection: React.CSSProperties = { + padding: '40px 40px 30px 40px', + textAlign: 'center', + backgroundColor: '#101e1c', + borderRadius: '8px 8px 0 0', +}; + +export const h1: React.CSSProperties = { + margin: '0', + color: '#e6ff03', + fontSize: '24px', + fontWeight: '600', + lineHeight: '1.3', +}; + +const content: React.CSSProperties = { + padding: '40px', +}; + +const text: React.CSSProperties = { + margin: '0 0 20px 0', + color: '#141e12', + fontSize: '16px', + lineHeight: '24px', +}; + +export const buttonContainer: React.CSSProperties = { + padding: '10px 0 30px 0', +}; + +export const button: React.CSSProperties = { + display: 'inline-block', + padding: '14px 28px', + backgroundColor: '#101e1c', + color: '#ffffff', + textDecoration: 'none', + borderRadius: '8px', + fontWeight: '600', + fontSize: '16px', + lineHeight: '20px', +}; + +export const footerText: React.CSSProperties = { + margin: '0', + color: '#6b716a', + fontSize: '14px', + lineHeight: '20px', +}; + +const footerSection: React.CSSProperties = { + padding: '30px 40px', + borderTop: '1px solid #ecefec', +}; + +const footerAddress: React.CSSProperties = { + margin: '10px 0 0 0', + color: '#6b716a', + fontSize: '12px', + lineHeight: '18px', +}; + +const logoSection: React.CSSProperties = { + marginBottom: '30px', +}; + +export const bodyText: React.CSSProperties = { + margin: '0 0 30px 0', + color: '#141e12', + fontSize: '16px', + lineHeight: '24px', +}; diff --git a/packages/emails/src/components/ProboLogo.tsx b/packages/emails/src/components/ProboLogo.tsx new file mode 100644 index 000000000..be689e0df --- /dev/null +++ b/packages/emails/src/components/ProboLogo.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; + +export function ProboLogo() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/emails/templates/confirm-email.txt b/packages/emails/templates/confirm-email.txt new file mode 100644 index 000000000..22f406bf4 --- /dev/null +++ b/packages/emails/templates/confirm-email.txt @@ -0,0 +1,11 @@ +Probo + +Hi {{.FullName}}, + +Thanks for joining Probo! Please confirm your email address by clicking the link below: + +{{.ConfirmationUrl}} + +If you did not sign up for Probo, please ignore this email. + +Probo Inc, 490 Post St, STE 640, San Francisco, CA, 94102, US diff --git a/packages/emails/templates/document-export.txt b/packages/emails/templates/document-export.txt new file mode 100644 index 000000000..76ec9b5ca --- /dev/null +++ b/packages/emails/templates/document-export.txt @@ -0,0 +1,11 @@ +Probo + +Hi {{.FullName}}, + +Your document export has been completed successfully. Click the link below to download it: + +{{.DownloadUrl}} + +This link will expire in 24 hours. + +Probo Inc, 490 Post St, STE 640, San Francisco, CA, 94102, US diff --git a/packages/emails/templates/document-signing.txt b/packages/emails/templates/document-signing.txt new file mode 100644 index 000000000..8145aa013 --- /dev/null +++ b/packages/emails/templates/document-signing.txt @@ -0,0 +1,19 @@ +Probo + +Hi {{.FullName}}, + +You're receiving this message because your company, {{.OrganizationName}}, has shared a new compliance document that requires your review and signature. + +To stay compliant with company policies, please take a moment to review and sign the document by clicking the link below: + +{{.SigningUrl}} + +If you have any questions, please contact your security team. + +This process is managed securely by Probo, acting as the compliance partner on behalf of {{.OrganizationName}}. + +Thank you for your prompt attention to this matter. + +Best regards, + +Probo Inc, 490 Post St, STE 640, San Francisco, CA, 94102, US diff --git a/packages/emails/templates/framework-export.txt b/packages/emails/templates/framework-export.txt new file mode 100644 index 000000000..7ae729502 --- /dev/null +++ b/packages/emails/templates/framework-export.txt @@ -0,0 +1,11 @@ +Probo + +Hi {{.FullName}}, + +Your framework export has been completed successfully. Click the link below to download it: + +{{.DownloadUrl}} + +This link will expire in 24 hours. + +Probo Inc, 490 Post St, STE 640, San Francisco, CA, 94102, US diff --git a/pkg/authz/emails/invitation.txt.tmpl b/packages/emails/templates/invitation.txt similarity index 51% rename from pkg/authz/emails/invitation.txt.tmpl rename to packages/emails/templates/invitation.txt index 414c7d358..79b2c06d5 100644 --- a/pkg/authz/emails/invitation.txt.tmpl +++ b/packages/emails/templates/invitation.txt @@ -1,10 +1,11 @@ +Probo + Hi {{.FullName}}, -You have been invited to join organization {{.OrganizationName}}. Please click the link below to accept the invitation: +You have been invited to join organization {{.OrganizationName}} on Probo. Click the link below to accept the invitation: -{{.InvitationURL}} +{{.InvitationUrl}} If you don't want to accept the invitation, you can ignore this email. -Thanks, -Probo Team +Probo Inc, 490 Post St, STE 640, San Francisco, CA, 94102, US diff --git a/packages/emails/templates/password-reset.txt b/packages/emails/templates/password-reset.txt new file mode 100644 index 000000000..b8aebde12 --- /dev/null +++ b/packages/emails/templates/password-reset.txt @@ -0,0 +1,11 @@ +Probo + +Hi {{.FullName}}, + +You have requested a password reset for your Probo account. Click the link below to reset your password: + +{{.ResetUrl}} + +If you did not request this password reset, please ignore this email. + +Probo Inc, 490 Post St, STE 640, San Francisco, CA, 94102, US diff --git a/packages/emails/templates/trust-center-access.txt b/packages/emails/templates/trust-center-access.txt new file mode 100644 index 000000000..0a1f6527e --- /dev/null +++ b/packages/emails/templates/trust-center-access.txt @@ -0,0 +1,11 @@ +Probo + +Hi {{.FullName}}, + +You have been granted access to {{.OrganizationName}}'s Trust Center! Click the link below to access it: + +{{.AccessUrl}} + +This link will expire in 7 days. + +Probo Inc, 490 Post St, STE 640, San Francisco, CA, 94102, US diff --git a/packages/emails/tsconfig.json b/packages/emails/tsconfig.json new file mode 100644 index 000000000..fcfc2e8a9 --- /dev/null +++ b/packages/emails/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@probo/tsconfig/base.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react", + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pkg/auth/emails/password_reset.txt.tmpl b/pkg/auth/emails/password_reset.txt.tmpl deleted file mode 100644 index aa995cdfa..000000000 --- a/pkg/auth/emails/password_reset.txt.tmpl +++ /dev/null @@ -1,12 +0,0 @@ -Hi {{.FullName}}, - -You have requested a password reset for your Probo account. - -Please click the link below to reset your password: - -{{.ResetURL}} - -If you did not request this password reset, please ignore this email. - -Thanks, -Probo Team diff --git a/pkg/auth/emails/signup_confirmation.txt.tmpl b/pkg/auth/emails/signup_confirmation.txt.tmpl deleted file mode 100644 index 513e0f5ce..000000000 --- a/pkg/auth/emails/signup_confirmation.txt.tmpl +++ /dev/null @@ -1,12 +0,0 @@ -Hi {{.FullName}}, - -Thanks for joining Probo! - -Please confirm your email address by clicking the link below: - -{{.ConfirmationURL}} - -If you did not sign up for Probo, please ignore this email. - -Thanks, -Probo Team diff --git a/pkg/auth/service.go b/pkg/auth/service.go index adc585b27..b88bfe99e 100644 --- a/pkg/auth/service.go +++ b/pkg/auth/service.go @@ -15,16 +15,14 @@ package auth import ( - "bytes" "context" - _ "embed" "errors" "fmt" - "html/template" "net/mail" "net/url" "time" + "github.com/getprobo/probo/packages/emails" "github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/crypto/passwdhash" "github.com/getprobo/probo/pkg/gid" @@ -100,18 +98,6 @@ const ( TokenTypePasswordReset = "password_reset" ) -var ( - //go:embed emails/signup_confirmation.txt.tmpl - signupEmailTemplateData string - signupEmailTemplate = template.Must(template.New("signup").Parse(signupEmailTemplateData)) - signupEmailSubject = "Confirm your email address" - - //go:embed emails/password_reset.txt.tmpl - passwordResetEmailTemplateData string - passwordResetEmailTemplate = template.Must(template.New("password_reset").Parse(passwordResetEmailTemplateData)) - passwordResetEmailSubject = "Reset your password" -) - func (e ErrInvalidCredentials) Error() string { return e.message } @@ -204,23 +190,20 @@ func (s Service) ForgetPassword( return fmt.Errorf("cannot load user: %w", err) } - body := bytes.NewBuffer(nil) - err = passwordResetEmailTemplate.Execute( - body, - map[string]string{ - "FullName": user.FullName, - "ResetURL": resetPasswordUrl.String(), - }, + subject, textBody, htmlBody, err := emails.RenderPasswordReset( + user.FullName, + resetPasswordUrl.String(), ) if err != nil { - return fmt.Errorf("cannot execute password reset template: %w", err) + return fmt.Errorf("cannot render password reset email: %w", err) } passwordResetEmail := coredata.NewEmail( user.FullName, email, - passwordResetEmailSubject, - body.String(), + subject, + textBody, + htmlBody, ) if err := passwordResetEmail.Insert(ctx, conn); err != nil { return fmt.Errorf("cannot insert email: %w", err) @@ -308,23 +291,20 @@ func (s Service) SignUp( }.Encode(), } - body := bytes.NewBuffer(nil) - err = signupEmailTemplate.Execute( - body, - map[string]string{ - "FullName": user.FullName, - "ConfirmationURL": confirmationUrl.String(), - }, + subject, textBody, htmlBody, err := emails.RenderConfirmEmail( + user.FullName, + confirmationUrl.String(), ) if err != nil { - return fmt.Errorf("cannot execute signup template: %w", err) + return fmt.Errorf("cannot render confirmation email: %w", err) } confirmationEmail := coredata.NewEmail( user.FullName, user.EmailAddress, - signupEmailSubject, - body.String(), + subject, + textBody, + htmlBody, ) if err := confirmationEmail.Insert(ctx, tx); err != nil { diff --git a/pkg/authz/service.go b/pkg/authz/service.go index d057f9269..658aa1995 100644 --- a/pkg/authz/service.go +++ b/pkg/authz/service.go @@ -15,15 +15,13 @@ package authz import ( - "bytes" "context" - _ "embed" "errors" "fmt" "net/url" - "text/template" "time" + "github.com/getprobo/probo/packages/emails" "github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/page" @@ -64,14 +62,6 @@ const ( TokenTypeOrganizationInvitation = "organization_invitation" ) -var ( - //go:embed emails/invitation.txt.tmpl - invitationEmailBodyData string - - invitationEmailBodyTemplate = template.Must(template.New("invitation").Parse(invitationEmailBodyData)) - invitationEmailSubject = "Invitation to join organization" -) - func NewService( ctx context.Context, pgClient *pg.Client, @@ -681,21 +671,15 @@ func (s *TenantAuthzService) InviteUserToOrganization( CreatedAt: now, } - body := bytes.NewBuffer(nil) var err error + var invitationURL string + var recipientName string + if userExists { - err = invitationEmailBodyTemplate.Execute( - body, - map[string]string{ - "FullName": user.FullName, - "OrganizationName": organization.Name, - "InvitationURL": fmt.Sprintf("https://%s/", s.hostname), - }, - ) - if err != nil { - return fmt.Errorf("failed to execute template: %w", err) - } + recipientName = user.FullName + invitationURL = fmt.Sprintf("https://%s/", s.hostname) } else { + recipientName = fullName invitationData := coredata.InvitationData{ InvitationID: invitationID, OrganizationID: organizationID, @@ -714,24 +698,24 @@ func (s *TenantAuthzService) InviteUserToOrganization( return fmt.Errorf("failed to generate invitation token: %w", err) } - err = invitationEmailBodyTemplate.Execute( - body, - map[string]string{ - "FullName": fullName, - "OrganizationName": organization.Name, - "InvitationURL": fmt.Sprintf("https://%s/auth/signup-from-invitation?token=%s&fullName=%s", s.hostname, invitationToken, url.QueryEscape(fullName)), - }, - ) - if err != nil { - return fmt.Errorf("failed to execute template: %w", err) - } + invitationURL = fmt.Sprintf("https://%s/auth/signup-from-invitation?token=%s&fullName=%s", s.hostname, invitationToken, url.QueryEscape(fullName)) + } + + subject, textBody, htmlBody, err := emails.RenderInvitation( + recipientName, + organization.Name, + invitationURL, + ) + if err != nil { + return fmt.Errorf("failed to render invitation email: %w", err) } email := coredata.NewEmail( fullName, emailAddress, - invitationEmailSubject, - body.String(), + subject, + textBody, + htmlBody, ) if err := email.Insert(ctx, tx); err != nil { diff --git a/pkg/coredata/email.go b/pkg/coredata/email.go index 0181a233f..5b9c4704b 100644 --- a/pkg/coredata/email.go +++ b/pkg/coredata/email.go @@ -32,6 +32,7 @@ type ( RecipientName string `db:"recipient_name"` Subject string `db:"subject"` TextBody string `db:"text_body"` + HtmlBody *string `db:"html_body"` CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` SentAt *time.Time `db:"sent_at"` @@ -46,7 +47,8 @@ func NewEmail( recipientName string, recipientEmail string, subject string, - body string, + textBody string, + htmlBody *string, ) *Email { now := time.Now() return &Email{ @@ -54,7 +56,8 @@ func NewEmail( RecipientName: recipientName, RecipientEmail: recipientEmail, Subject: subject, - TextBody: body, + TextBody: textBody, + HtmlBody: htmlBody, CreatedAt: now, UpdatedAt: now, } @@ -65,8 +68,8 @@ func (e *Email) Insert( conn pg.Conn, ) error { q := ` -INSERT INTO emails (id, recipient_email, recipient_name, subject, text_body, created_at, updated_at) -VALUES (@id, @recipient_email, @recipient_name, @subject, @text_body, @created_at, @updated_at) +INSERT INTO emails (id, recipient_email, recipient_name, subject, text_body, html_body, created_at, updated_at) +VALUES (@id, @recipient_email, @recipient_name, @subject, @text_body, @html_body, @created_at, @updated_at) ` args := pgx.StrictNamedArgs{ @@ -75,6 +78,7 @@ VALUES (@id, @recipient_email, @recipient_name, @subject, @text_body, @created_a "recipient_name": e.RecipientName, "subject": e.Subject, "text_body": e.TextBody, + "html_body": e.HtmlBody, "created_at": e.CreatedAt, "updated_at": e.UpdatedAt, } @@ -88,7 +92,7 @@ func (e *Email) LoadNextUnsentForUpdate( conn pg.Conn, ) error { q := ` -SELECT id, recipient_email, recipient_name, subject, text_body, created_at, updated_at, sent_at +SELECT id, recipient_email, recipient_name, subject, text_body, html_body, created_at, updated_at, sent_at FROM emails WHERE sent_at IS NULL ORDER BY created_at ASC diff --git a/pkg/coredata/migrations/20251014T132711Z.sql b/pkg/coredata/migrations/20251014T132711Z.sql new file mode 100644 index 000000000..a41fbd997 --- /dev/null +++ b/pkg/coredata/migrations/20251014T132711Z.sql @@ -0,0 +1 @@ +ALTER TABLE emails ADD COLUMN html_body TEXT; diff --git a/pkg/mailer/mailer.go b/pkg/mailer/mailer.go index 358a6bcee..50f19742f 100644 --- a/pkg/mailer/mailer.go +++ b/pkg/mailer/mailer.go @@ -149,6 +149,10 @@ func (m *Mailer) batchSendEmails(ctx context.Context) error { To(email.RecipientName, email.RecipientEmail). Text([]byte(email.TextBody)) + if email.HtmlBody != nil { + mail = mail.HTML([]byte(*email.HtmlBody)) + } + envelope, err := mail.Build() if err != nil { return fmt.Errorf("cannot build email: %w", err) diff --git a/pkg/probo/document_service.go b/pkg/probo/document_service.go index 2f7dfb325..a8b93059e 100644 --- a/pkg/probo/document_service.go +++ b/pkg/probo/document_service.go @@ -15,6 +15,7 @@ import ( "unicode" "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/getprobo/probo/packages/emails" "github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/docgen" "github.com/getprobo/probo/pkg/gid" @@ -79,14 +80,6 @@ const ( TokenTypeSigningRequest = "signing_request" documentExportEmailExpiresIn = 24 * time.Hour - documentExportEmailSubject = "Your document export is ready" - documentExportEmailBody = ` -Your document export has been completed successfully. - -You can download the export using the link below: -[1] %s - -This link will expire in 24 hours.` maxFilenameLength = 200 ) @@ -433,11 +426,12 @@ func (s *DocumentService) SendSigningNotifications( return fmt.Errorf("cannot load people: %w", err) } + organization := &coredata.Organization{} + if err := organization.LoadByID(ctx, tx, s.svc.scope, organizationID); err != nil { + return fmt.Errorf("cannot load organization: %w", err) + } + for _, people := range peoples { - now := time.Now() - - emailID := gid.New(s.svc.scope.GetTenantID(), coredata.EmailEntityType) - token, err := statelesstoken.NewToken( s.svc.tokenSecret, TokenTypeSigningRequest, @@ -460,16 +454,23 @@ func (s *DocumentService) SendSigningNotifications( }.Encode(), } - email := &coredata.Email{ - ID: emailID, - RecipientEmail: people.PrimaryEmailAddress, - RecipientName: people.FullName, - Subject: "Probo - Documents Signing Request", - TextBody: fmt.Sprintf("Hi,\nYou have documents awaiting your signature. Please follow this link to sign them: %s", signRequestURL.String()), - CreatedAt: now, - UpdatedAt: now, + subject, textBody, htmlBody, err := emails.RenderDocumentSigning( + people.FullName, + organization.Name, + signRequestURL.String(), + ) + if err != nil { + return fmt.Errorf("cannot render signing request email: %w", err) } + email := coredata.NewEmail( + people.FullName, + people.PrimaryEmailAddress, + subject, + textBody, + htmlBody, + ) + if err := email.Insert(ctx, tx); err != nil { return fmt.Errorf("cannot insert email: %w", err) } @@ -1568,11 +1569,20 @@ func (s *DocumentService) SendExportEmail( return fmt.Errorf("cannot generate download URL: %w", err) } + subject, textBody, htmlBody, err := emails.RenderDocumentExport( + recipientName, + downloadURL, + ) + if err != nil { + return fmt.Errorf("cannot render document export email: %w", err) + } + email := coredata.NewEmail( recipientName, recipientEmail, - documentExportEmailSubject, - fmt.Sprintf(documentExportEmailBody, downloadURL), + subject, + textBody, + htmlBody, ) if err := email.Insert(ctx, tx); err != nil { diff --git a/pkg/probo/framework_service.go b/pkg/probo/framework_service.go index da708c334..d97086e74 100644 --- a/pkg/probo/framework_service.go +++ b/pkg/probo/framework_service.go @@ -25,6 +25,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/getprobo/probo/packages/emails" "github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/html2pdf" @@ -39,14 +40,6 @@ import ( const ( maxStateOfApplicabilityLimit = 10_000 frameworkExportEmailExpiresIn = 24 * time.Hour - frameworkExportEmailSubject = "Your framework export is ready" - frameworkExportEmailBody = ` -Your framework export has been completed successfully. - -You can download the export using the link below: -[1] %s - -This link will expire in 24 hours.` ) type ( @@ -715,11 +708,20 @@ func (s FrameworkService) SendExportEmail( return fmt.Errorf("cannot generate download URL: %w", err) } + subject, textBody, htmlBody, err := emails.RenderFrameworkExport( + recipientName, + downloadURL, + ) + if err != nil { + return fmt.Errorf("cannot render framework export email: %w", err) + } + email := coredata.NewEmail( recipientName, recipientEmail, - frameworkExportEmailSubject, - fmt.Sprintf(frameworkExportEmailBody, downloadURL), + subject, + textBody, + htmlBody, ) if err := email.Insert(ctx, tx); err != nil { diff --git a/pkg/probo/trust_center_access_service.go b/pkg/probo/trust_center_access_service.go index 3668c4467..ac8ed1c99 100644 --- a/pkg/probo/trust_center_access_service.go +++ b/pkg/probo/trust_center_access_service.go @@ -21,6 +21,7 @@ import ( "net/url" "time" + "github.com/getprobo/probo/packages/emails" "github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/page" @@ -57,21 +58,6 @@ type ( } ) -const ( - trustCenterAccessEmailSubject = "Trust Center Access Invitation - %s" - trustCenterAccessEmailTemplate = ` - You have been granted access to %s's Trust Center! - - Click the link below to access it: - - [1] %s - - This link will expire in 7 days. - - If the link above doesn't work, copy and paste the entire URL into your browser. - ` -) - func (s TrustCenterAccessService) ListForTrustCenterID( ctx context.Context, trustCenterID gid.GID, @@ -421,11 +407,21 @@ func (s TrustCenterAccessService) sendTrustCenterAccessEmail( companyName string, accessURL string, ) error { + subject, textBody, htmlBody, err := emails.RenderTrustCenterAccess( + name, + companyName, + accessURL, + ) + if err != nil { + return fmt.Errorf("cannot render trust center access email: %w", err) + } + accessEmail := coredata.NewEmail( name, email, - fmt.Sprintf(trustCenterAccessEmailSubject, companyName), - fmt.Sprintf(trustCenterAccessEmailTemplate, companyName, accessURL), + subject, + textBody, + htmlBody, ) if err := accessEmail.Insert(ctx, tx); err != nil {