🧑‍💻 Use mailpit as dev/test smtp server (#1486)

This commit is contained in:
Luke Vella 2025-01-10 14:27:08 +00:00 committed by GitHub
parent b00b685bbd
commit 285860ec9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 124 additions and 242 deletions

View file

@ -69,8 +69,7 @@ jobs:
- uses: ./.github/actions/yarn-install
- name: Install system dependencies
run: |
sudo apt-get update
run: sudo apt-get update
- name: Install playwright dependencies
run: yarn playwright install --with-deps chromium

View file

@ -5,4 +5,4 @@ SECRET_PASSWORD=abcdef1234567890abcdef1234567890
DATABASE_URL=postgres://postgres:postgres@localhost:5450/rallly
SUPPORT_EMAIL=support@rallly.co
SMTP_HOST=localhost
SMTP_PORT=4025
SMTP_PORT=1025

View file

@ -97,7 +97,6 @@
"cross-env": "^7.0.3",
"i18next-scanner": "^4.2.0",
"i18next-scanner-typescript": "^1.1.1",
"smtp-tester": "^2.1.0",
"vitest": "^2.1.1",
"wait-on": "^6.0.1"
}

View file

@ -1,33 +1,24 @@
import { expect, test } from "@playwright/test";
import { prisma } from "@rallly/database";
import { load } from "cheerio";
import smtpTester from "smtp-tester";
import { captureEmailHTML } from "./mailpit/mailpit";
const testUserEmail = "test@example.com";
let mailServer: smtpTester.MailServer;
/**
* Get the 6-digit code from the email
* @returns 6-digit code
*/
const getCode = async () => {
const { email } = await mailServer.captureOne(testUserEmail, {
wait: 5000,
});
const html = await captureEmailHTML(testUserEmail);
if (!email.html) {
throw new Error("Email doesn't contain HTML");
}
const $ = load(email.html);
const $ = load(html);
return $("#code").text().trim();
};
test.describe.serial(() => {
test.beforeAll(() => {
mailServer = smtpTester.init(4025);
});
test.afterAll(async () => {
try {
await prisma.user.deleteMany({
@ -38,8 +29,6 @@ test.describe.serial(() => {
} catch {
// User doesn't exist
}
mailServer.stop(() => {});
});
test.describe("new user", () => {
@ -110,15 +99,9 @@ test.describe.serial(() => {
await page.getByRole("button", { name: "Login with Email" }).click();
const { email } = await mailServer.captureOne(testUserEmail, {
wait: 5000,
});
const html = await captureEmailHTML(testUserEmail);
if (!email.html) {
throw new Error("Email doesn't contain HTML");
}
const $ = load(email.html);
const $ = load(html);
const magicLink = $("#magicLink").attr("href");

View file

@ -1,20 +1,15 @@
import type { Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import type { MailServer } from "smtp-tester";
import smtpTester from "smtp-tester";
import { NewPollPage } from "tests/new-poll-page";
import { deleteAllMessages } from "./mailpit/mailpit";
test.describe.serial(() => {
let page: Page;
let mailServer: MailServer;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
mailServer = smtpTester.init(4025);
});
test.afterAll(async () => {
mailServer.stop(() => {});
await deleteAllMessages(); // Clean the mailbox before tests
});
test("create a new poll", async () => {

View file

@ -1,18 +1,14 @@
import type { Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import type { MailServer } from "smtp-tester";
import smtpTester from "smtp-tester";
import type { EditOptionsPage } from "tests/edit-options-page";
import { NewPollPage } from "tests/new-poll-page";
test.describe("edit options", () => {
let page: Page;
let editOptionsPage: EditOptionsPage;
let mailServer: MailServer;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
mailServer = smtpTester.init(4025);
const newPollPage = new NewPollPage(page);
await newPollPage.goto();
const pollPage = await newPollPage.createPollAndCloseDialog();
@ -20,10 +16,6 @@ test.describe("edit options", () => {
editOptionsPage = await pollPage.editOptions();
});
test.afterAll(async () => {
mailServer.stop(() => {});
});
test("should show warning when deleting options with votes in them", async () => {
editOptionsPage.switchToSpecifyTimes();

View file

@ -0,0 +1,56 @@
import type { MailpitListMessagesResponse, MailpitMessage } from "./types";
const MAILPIT_API_URL = "http://localhost:8025/api";
export async function getMessages(): Promise<MailpitListMessagesResponse> {
const response = await fetch(`${MAILPIT_API_URL}/v1/messages`);
const data = (await response.json()) as MailpitListMessagesResponse;
return data;
}
export async function getMessage(id: string): Promise<MailpitMessage> {
const response = await fetch(`${MAILPIT_API_URL}/v1/message/${id}`);
const data = (await response.json()) as MailpitMessage;
return data;
}
export async function deleteAllMessages(): Promise<void> {
await fetch(`${MAILPIT_API_URL}/v1/messages`, {
method: "DELETE",
});
}
export async function captureOne(
to: string,
options: { wait?: number } = {},
): Promise<{ email: MailpitMessage }> {
const startTime = Date.now();
const timeout = options.wait ?? 5000;
while (Date.now() - startTime < timeout) {
const { messages } = await getMessages();
const message = messages.find(
(msg) =>
new Date(msg.Created) > new Date(startTime) &&
msg.To.some((recipient) => recipient.Address === to),
);
if (message) {
const fullMessage = await getMessage(message.ID);
return { email: fullMessage };
}
// Wait a bit before trying again
await new Promise((resolve) => setTimeout(resolve, 100));
}
throw new Error(`No email received for ${to} within ${timeout}ms`);
}
export async function captureEmailHTML(to: string): Promise<string> {
const { email } = await captureOne(to);
if (!email.HTML) {
throw new Error("Email doesn't contain HTML");
}
return email.HTML;
}

View file

@ -0,0 +1,46 @@
export interface MailpitAttachment {
ContentID: string;
ContentType: string;
FileName: string;
PartID: string;
Size: number;
}
export interface MailpitEmailAddress {
Address: string;
Name: string;
}
export interface MailpitMessageSummary {
Attachments: number;
Bcc: MailpitEmailAddress[];
Cc: MailpitEmailAddress[];
Created: string;
From: MailpitEmailAddress;
ID: string;
MessageID: string;
Read: boolean;
ReplyTo: MailpitEmailAddress[];
Size: number;
Snippet: string;
Subject: string;
Tags: string[];
To: MailpitEmailAddress[];
}
export interface MailpitMessage extends MailpitMessageSummary {
HTML: string;
Text: string;
Inline: MailpitAttachment[];
ReturnPath: string;
Date: string;
}
export interface MailpitListMessagesResponse {
messages: MailpitMessageSummary[];
messages_count: number;
start: number;
tags: string[];
total: number;
unread: number;
}

View file

@ -16,7 +16,7 @@ export class NewPollPage {
async createPoll() {
const page = this.page;
await page.type('[placeholder="Monthly Meetup"]', "Monthly Meetup");
await page.fill('[placeholder="Monthly Meetup"]', "Monthly Meetup");
// click on label to focus on input
await page.click('text="Location"');
await page.keyboard.type("Joe's Coffee Shop");

View file

@ -1,10 +1,9 @@
import type { Page, Request } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { load } from "cheerio";
import type { MailServer } from "smtp-tester";
import smtpTester from "smtp-tester";
import type { PollPage } from "tests/poll-page";
import { captureOne } from "./mailpit/mailpit";
import { NewPollPage } from "./new-poll-page";
test.describe(() => {
@ -13,9 +12,7 @@ test.describe(() => {
let touchRequest: Promise<Request>;
let editSubmissionUrl: string;
let mailServer: MailServer;
test.beforeAll(async ({ browser }) => {
mailServer = smtpTester.init(4025);
page = await browser.newPage();
touchRequest = page.waitForRequest(
(request) =>
@ -27,10 +24,6 @@ test.describe(() => {
pollPage = await newPollPage.createPollAndCloseDialog();
});
test.afterAll(async () => {
mailServer.stop(() => {});
});
test("should call touch endpoint", async () => {
// make sure call to touch RPC is made
expect(await touchRequest).not.toBeNull();
@ -54,17 +47,15 @@ test.describe(() => {
await invitePage.addParticipant("Anne", "test@example.com");
await expect(page.locator("text='Anne'")).toBeVisible();
const { email } = await mailServer.captureOne("test@example.com", {
const { email } = await captureOne("test@example.com", {
wait: 5000,
});
expect(email.headers.subject).toBe(
"Thanks for responding to Monthly Meetup",
);
await expect(page.locator("text='Anne'")).toBeVisible();
const $ = load(email.html as string);
expect(email.Subject).toBe("Thanks for responding to Monthly Meetup");
const $ = load(email.HTML);
const href = $("#editSubmissionUrl").attr("href");
if (!href) {

View file

@ -45,8 +45,8 @@ services:
MINIO_ROOT_PASSWORD: minio123
volumes:
- s3-data:/data
mailhog:
image: mailhog/mailhog
mailpit:
image: axllent/mailpit
ports:
- "8025:8025"
- "1025:1025"

181
yarn.lock
View file

@ -4732,14 +4732,6 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz#5f1b518ec5fa54437c0b7c4a821546c64fed6922"
integrity sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==
"@selderee/plugin-htmlparser2@^0.10.0":
version "0.10.0"
resolved "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.10.0.tgz"
integrity sha512-gW69MEamZ4wk1OsOq1nG1jcyhXIQcnrsX5JwixVw/9xaiav8TCyjESAruu1Rz9yyInhgBXxkNwMeygKnN2uxNA==
dependencies:
domhandler "^5.0.3"
selderee "^0.10.0"
"@selderee/plugin-htmlparser2@^0.11.0":
version "0.11.0"
resolved "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz"
@ -6633,14 +6625,6 @@
resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz"
integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==
"@types/mailparser@^3.4.0":
version "3.4.4"
resolved "https://registry.yarnpkg.com/@types/mailparser/-/mailparser-3.4.4.tgz#0bd71e205573b9dd9a445e10a8b8cb0e45420998"
integrity sha512-C6Znp2QVS25JqtuPyxj38Qh+QoFcLycdxsvcc6IZCGekhaMBzbdTXzwGzhGoYb3TfKu8IRCNV0sV1o3Od97cEQ==
dependencies:
"@types/node" "*"
iconv-lite "^0.6.3"
"@types/mdast@^4.0.0":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
@ -6704,13 +6688,6 @@
resolved "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz"
integrity sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==
"@types/nodemailer@*":
version "6.4.10"
resolved "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.10.tgz"
integrity sha512-oPW/IdhkU3FyZc1dzeqmS+MBjrjZNiiINnrEOrWALzccJlP5xTlbkNr2YnTnnyj9Eqm5ofjRoASEbrCYpA7BrA==
dependencies:
"@types/node" "*"
"@types/nodemailer@^6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.14.tgz#5c81a5e856db7f8ede80013e6dbad7c5fb2283e2"
@ -6847,14 +6824,6 @@
resolved "https://registry.npmjs.org/@types/smoothscroll-polyfill/-/smoothscroll-polyfill-0.3.1.tgz"
integrity sha512-+KkHw4y+EyeCtVXET7woHUhIbfWFCflc0E0mZnSV+ZdjPQeHt/9KPEuT7gSW/kFQ8O3EG30PLO++YhChDt8+Ag==
"@types/smtp-server@^3.5.7":
version "3.5.10"
resolved "https://registry.yarnpkg.com/@types/smtp-server/-/smtp-server-3.5.10.tgz#06d0338aea519469529847a12b0903678fdd6bea"
integrity sha512-i3Jx7sJ2qF52vjaOf3HguulXlWRFf6BSfsRLsIdmytDyVGv7KkhSs+gR9BXJnJWg1Ljkh/56Fh1Xqwa6u6X7zw==
dependencies:
"@types/node" "*"
"@types/nodemailer" "*"
"@types/unist@*", "@types/unist@^2.0.0":
version "2.0.6"
resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz"
@ -7718,11 +7687,6 @@ balanced-match@^1.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base32.js@0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz"
integrity sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
@ -8740,11 +8704,6 @@ emoji-regex@^9.2.2:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
encoding-japanese@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz"
integrity sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz"
@ -10074,11 +10033,6 @@ hast-util-whitespace@^3.0.0:
dependencies:
"@types/hast" "^3.0.0"
he@1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
@ -10098,17 +10052,6 @@ html-parse-stringify@^3.0.1:
dependencies:
void-elements "3.1.0"
html-to-text@9.0.3:
version "9.0.3"
resolved "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.3.tgz"
integrity sha512-hxDF1kVCF2uw4VUJ3vr2doc91pXf2D5ngKcNviSitNkhP9OMOaJkDrFIFL6RMvko7NisWTEiqGpQ9LAxcVok1w==
dependencies:
"@selderee/plugin-htmlparser2" "^0.10.0"
deepmerge "^4.2.2"
dom-serializer "^2.0.0"
htmlparser2 "^8.0.1"
selderee "^0.10.0"
html-to-text@9.0.5:
version "9.0.5"
resolved "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz"
@ -10235,13 +10178,6 @@ iconv-lite@0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@0.6.3, iconv-lite@^0.6.3:
version "0.6.3"
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
ics@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/ics/-/ics-3.1.0.tgz"
@ -10375,11 +10311,6 @@ invariant@^2.2.4:
dependencies:
loose-envify "^1.0.0"
ipv6-normalize@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/ipv6-normalize/-/ipv6-normalize-1.0.1.tgz"
integrity sha512-Bm6H79i01DjgGTCWjUuCjJ6QDo1HB96PT/xCYuyJUP9WFbVDrLSbG4EZCvOCun2rNswZb0c3e4Jt/ws795esHA==
iron-session@^6.3.1:
version "6.3.1"
resolved "https://registry.npmjs.org/iron-session/-/iron-session-6.3.1.tgz"
@ -10976,26 +10907,6 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
libbase64@1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/libbase64/-/libbase64-1.2.1.tgz"
integrity sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew==
libmime@5.2.0:
version "5.2.0"
resolved "https://registry.npmjs.org/libmime/-/libmime-5.2.0.tgz"
integrity sha512-X2U5Wx0YmK0rXFbk67ASMeqYIkZ6E5vY7pNWRKtnNzqjvdYYG8xtPDpCnuUEnPU9vlgNev+JoSrcaKSUaNvfsw==
dependencies:
encoding-japanese "2.0.0"
iconv-lite "0.6.3"
libbase64 "1.2.1"
libqp "2.0.1"
libqp@2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/libqp/-/libqp-2.0.1.tgz"
integrity sha512-Ka0eC5LkF3IPNQHJmYBWljJsw0UvM6j+QdKRbWyCdTmYwvIDE6a7bCm0UkTAL/K+3KXK5qXT/ClcInU01OpdLg==
lilconfig@^2.0.5, lilconfig@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz"
@ -11006,13 +10917,6 @@ lines-and-columns@^1.1.6:
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
linkify-it@4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz"
integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==
dependencies:
uc.micro "^1.0.1"
linkify-react@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/linkify-react/-/linkify-react-4.1.3.tgz#461d348b4bdab3fcd0452ae1b5bbc22536395b97"
@ -11169,30 +11073,6 @@ magic-string@^0.30.3:
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15"
mailparser@^3.5.0:
version "3.6.3"
resolved "https://registry.npmjs.org/mailparser/-/mailparser-3.6.3.tgz"
integrity sha512-Yi6poKSsZsmjEcUexv3H4w4+TIeyN9u3+TCdC43VK7fe4rUOGDJ3wL4kMhNLiTOScCA1Rpzldv1hcf6g1MLtZQ==
dependencies:
encoding-japanese "2.0.0"
he "1.2.0"
html-to-text "9.0.3"
iconv-lite "0.6.3"
libmime "5.2.0"
linkify-it "4.0.1"
mailsplit "5.4.0"
nodemailer "6.8.0"
tlds "1.236.0"
mailsplit@5.4.0:
version "5.4.0"
resolved "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.0.tgz"
integrity sha512-wnYxX5D5qymGIPYLwnp6h8n1+6P6vz/MJn5AzGjZ8pwICWssL+CCQjWBIToOVHASmATot4ktvlLo6CyLfOXWYA==
dependencies:
libbase64 "1.2.1"
libmime "5.2.0"
libqp "2.0.1"
map-obj@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
@ -11959,16 +11839,6 @@ node-releases@^2.0.8:
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz"
integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==
nodemailer@6.7.3:
version "6.7.3"
resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.3.tgz"
integrity sha512-KUdDsspqx89sD4UUyUKzdlUOper3hRkDVkrKh/89G+d9WKsU5ox51NWS4tB1XR5dPUdR4SP0E3molyEfOvSa3g==
nodemailer@6.8.0:
version "6.8.0"
resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz"
integrity sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ==
nodemailer@^6.9.9:
version "6.9.9"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.9.tgz#4549bfbf710cc6addec5064dd0f19874d24248d9"
@ -12315,14 +12185,6 @@ parse5@^7.0.0:
dependencies:
entities "^4.4.0"
parseley@^0.11.0:
version "0.11.0"
resolved "https://registry.npmjs.org/parseley/-/parseley-0.11.0.tgz"
integrity sha512-VfcwXlBWgTF+unPcr7yu3HSSA6QUdDaDnrHcytVfj5Z8azAyKBDrYnSIfeSxlrEayndNcLmrXzg+Vxbo6DWRXQ==
dependencies:
leac "^0.6.0"
peberminta "^0.8.0"
parseley@^0.12.0:
version "0.12.1"
resolved "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz"
@ -12399,11 +12261,6 @@ pathval@^2.0.0:
resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25"
integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==
peberminta@^0.8.0:
version "0.8.0"
resolved "https://registry.npmjs.org/peberminta/-/peberminta-0.8.0.tgz"
integrity sha512-YYEs+eauIjDH5nUEGi18EohWE0nV2QbGTqmxQcqgZ/0g+laPCQmuIqq7EBLVi9uim9zMgfJv0QBZEnQ3uHw/Tw==
peberminta@^0.9.0:
version "0.9.0"
resolved "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz"
@ -13453,7 +13310,7 @@ safe-regex-test@^1.0.0:
get-intrinsic "^1.1.3"
is-regex "^1.1.4"
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@ -13487,13 +13344,6 @@ section-matter@^1.0.0:
extend-shallow "^2.0.1"
kind-of "^6.0.0"
selderee@^0.10.0:
version "0.10.0"
resolved "https://registry.npmjs.org/selderee/-/selderee-0.10.0.tgz"
integrity sha512-DEL/RW/f4qLw/NrVg97xKaEBC8IpzIG2fvxnzCp3Z4yk4jQ3MXom+Imav9wApjxX2dfS3eW7x0DXafJr85i39A==
dependencies:
parseley "^0.11.0"
selderee@^0.11.0:
version "0.11.0"
resolved "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz"
@ -13642,25 +13492,6 @@ smoothscroll-polyfill@^0.4.4:
resolved "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz"
integrity sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==
smtp-server@^3.11.0:
version "3.11.0"
resolved "https://registry.npmjs.org/smtp-server/-/smtp-server-3.11.0.tgz"
integrity sha512-j/W6mEKeMNKuiM9oCAAjm87agPEN1O3IU4cFLT4ZOCyyq3UXN7HiIXF+q7izxJcYSar15B/JaSxcijoPCR8Tag==
dependencies:
base32.js "0.1.0"
ipv6-normalize "1.0.1"
nodemailer "6.7.3"
smtp-tester@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/smtp-tester/-/smtp-tester-2.1.0.tgz#ed67ef933767cacb8defcbf0683f29d75335a1ca"
integrity sha512-HfOBdHkdwoBO+Qb06H8ShVEc08nnvDbtYWzdT5iQMIeInBdLKu17XOhuaC79uM24zPApRfN3JAg627TIy3y/Ww==
dependencies:
"@types/mailparser" "^3.4.0"
"@types/smtp-server" "^3.5.7"
mailparser "^3.5.0"
smtp-server "^3.11.0"
socket.io-adapter@~2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12"
@ -14320,11 +14151,6 @@ tinyspy@^3.0.0:
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a"
integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==
tlds@1.236.0:
version "1.236.0"
resolved "https://registry.npmjs.org/tlds/-/tlds-1.236.0.tgz"
integrity sha512-oP2PZ3KeGlgpHgsEfrtva3/K9kzsJUNliQSbCfrJ7JMCWFoCdtG+9YMq/g2AnADQ1v5tVlbtvKJZ4KLpy/P6MA==
to-absolute-glob@^2.0.0:
version "2.0.2"
resolved "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz"
@ -14587,11 +14413,6 @@ typescript@^5.2.2:
resolved "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz"
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
uc.micro@^1.0.1:
version "1.0.6"
resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz"
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
unbox-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz"