Compare commits

...

11 Commits

Author SHA1 Message Date
9a92874011 fix(analytics-mailer): send emails individually to avoid exposing other recipients in CC/To field
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧹 Lint (push) Failing after 1m25s
Monorepo Pipeline / 🧪 Test (push) Successful in 43s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m8s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
🏥 Server Maintenance / 🧹 Prune & Clean (push) Failing after 7s
2026-07-21 12:01:20 +02:00
a54c3a784b fix(analytics-mailer): correct greeting for MB Grid Solutions
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 3s
Monorepo Pipeline / 🧪 Test (push) Has been cancelled
Monorepo Pipeline / 🏗️ Build (push) Has been cancelled
Monorepo Pipeline / 🚀 Release (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been cancelled
Monorepo Pipeline / 🧹 Lint (push) Has been cancelled
2026-07-21 12:01:07 +02:00
7b975ed88f fix(analytics-mailer): correct greetings for E-TIB and KLZ Cables
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 3s
Monorepo Pipeline / 🧪 Test (push) Has been cancelled
Monorepo Pipeline / 🏗️ Build (push) Has been cancelled
Monorepo Pipeline / 🚀 Release (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been cancelled
Monorepo Pipeline / 🧹 Lint (push) Has been cancelled
2026-07-21 11:59:44 +02:00
1a5a60304f style(mail): reduce margin of evaluation badges
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 3s
Monorepo Pipeline / 🧪 Test (push) Has been cancelled
Monorepo Pipeline / 🏗️ Build (push) Has been cancelled
Monorepo Pipeline / 🚀 Release (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been cancelled
Monorepo Pipeline / 🧹 Lint (push) Has been cancelled
2026-07-21 11:59:14 +02:00
48374528ad chore(analytics-mailer): ignore dry-run html previews
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧹 Lint (push) Failing after 1m23s
Monorepo Pipeline / 🏗️ Build (push) Has been cancelled
Monorepo Pipeline / 🚀 Release (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been cancelled
Monorepo Pipeline / 🧪 Test (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been cancelled
2026-07-21 11:57:25 +02:00
541da2db20 feat(mail): add client-safe context evaluations to analytics template
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧹 Lint (push) Failing after 1m19s
Monorepo Pipeline / 🧪 Test (push) Successful in 40s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m7s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
2026-07-21 11:46:11 +02:00
39bb862fd6 feat(analytics-mailer): add gotify error logging
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧹 Lint (push) Failing after 1m21s
Monorepo Pipeline / 🧪 Test (push) Successful in 40s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m6s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
2026-07-21 11:38:19 +02:00
09a85000bb chore(analytics-mailer): update client email recipients
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧹 Lint (push) Failing after 1m25s
Monorepo Pipeline / 🧪 Test (push) Successful in 40s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m7s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
2026-07-21 11:32:23 +02:00
b6e8628970 feat(analytics-mailer): replace mailgun with stalwart smtp
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 3s
Monorepo Pipeline / 🧹 Lint (push) Failing after 1m23s
Monorepo Pipeline / 🧪 Test (push) Successful in 40s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m7s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
2026-07-21 10:46:51 +02:00
53ad9f23d8 feat(analytics-mailer): Redesign email to Umami format with 3 periods, German translation, and inline glossary
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 3s
Monorepo Pipeline / 🧹 Lint (push) Failing after 1m24s
Monorepo Pipeline / 🧪 Test (push) Successful in 41s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m4s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
🏥 Server Maintenance / 🧹 Prune & Clean (push) Failing after 7s
2026-07-20 14:17:07 +02:00
7a9dd06fc6 feat: setup analytics-mailer with gitea cron pipeline for e-tib and klz
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 3s
Monorepo Pipeline / 🧹 Lint (push) Failing after 1m42s
Monorepo Pipeline / 🧪 Test (push) Successful in 56s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m35s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
Monthly Analytics Mailer / 📊 Send Monthly Reports (push) Failing after 59s
🏥 Server Maintenance / 🧹 Prune & Clean (push) Failing after 7s
2026-06-21 20:52:31 +02:00
12 changed files with 1123 additions and 8 deletions

View File

@@ -0,0 +1,73 @@
name: Analytics Mailer
on:
schedule:
# Run weekly on Monday at 08:00 UTC
- cron: '0 8 * * 1'
# Run monthly on the 1st day of the month at 08:30 UTC
- cron: '30 8 1 * *'
workflow_dispatch:
inputs:
interval:
description: 'Interval to run (weekly or monthly)'
required: true
default: 'weekly'
type: choice
options:
- weekly
- monthly
jobs:
send-reports:
name: 📊 Send Analytics Reports
runs-on: docker
container:
image: catthehacker/ubuntu:act-latest
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
UMAMI_BASE_URL: ${{ secrets.UMAMI_BASE_URL }}
UMAMI_USERNAME: ${{ secrets.UMAMI_USERNAME }}
UMAMI_PASSWORD: ${{ secrets.UMAMI_PASSWORD }}
SMTP_HOST: ${{ secrets.SMTP_HOST }}
SMTP_PORT: ${{ secrets.SMTP_PORT }}
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASS: ${{ secrets.SMTP_PASS }}
SMTP_SENDER: ${{ secrets.SMTP_SENDER }}
GOTIFY_URL: ${{ secrets.GOTIFY_URL }}
GOTIFY_TOKEN: ${{ secrets.GOTIFY_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node_version: 20
- name: Enable pnpm
run: corepack enable && corepack prepare pnpm@10.2.0 --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts --no-color
- name: Build mail templates
run: pnpm --filter @mintel/mail build
- name: Build analytics mailer
run: pnpm --filter @mintel/analytics-mailer build
- name: Determine Interval
id: set-interval
run: |
if [ "${{ github.event_name }}" = "schedule" ]; then
if [ "${{ github.event.schedule }}" = "0 8 * * 1" ]; then
echo "interval=weekly" >> $GITHUB_OUTPUT
else
echo "interval=monthly" >> $GITHUB_OUTPUT
fi
else
echo "interval=${{ github.event.inputs.interval }}" >> $GITHUB_OUTPUT
fi
- name: Run Analytics Mailer
run: pnpm --filter @mintel/analytics-mailer run start -- --interval=${{ steps.set-interval.outputs.interval }}

1
apps/analytics-mailer/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dry-run-*.html

View File

@@ -0,0 +1,34 @@
[
{
"websiteId": "59a7db94-0100-4c7e-98ef-99f45b17f9c3",
"domain": "klz-cables.com",
"clientEmail": "klaus.mintel@klz-cables.com, michael.bodemer@klz-cables.com, marc@mintel.me",
"clientName": "KLZ Cables",
"greeting": "Hallo Klaus, hallo Michael,",
"interval": "monthly"
},
{
"websiteId": "b9035279-72f1-49bc-909d-b073d184c388",
"domain": "mintel.me",
"clientEmail": "marc@mintel.me",
"clientName": "Mintel.me",
"greeting": "Hallo Team Mintel,",
"interval": "monthly"
},
{
"websiteId": "caa402bc-b2d2-4349-a254-84c0dd2a4460",
"domain": "mb-grid-solutions.com",
"clientEmail": "marc@mintel.me",
"clientName": "MB Grid Solutions",
"greeting": "Hallo Marc,",
"interval": "monthly"
},
{
"websiteId": "d773ea10-a3b3-4ccf-9024-987e14c4d669",
"domain": "e-tib.com",
"clientEmail": "d.joseph@e-tib.com, marc@mintel.me",
"clientName": "E-TIB GmbH",
"greeting": "Hallo Danny,",
"interval": "monthly"
}
]

View File

@@ -0,0 +1,27 @@
{
"name": "@mintel/analytics-mailer",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsup src/index.ts --format esm --clean",
"start": "node dist/index.js",
"dev": "tsx src/index.ts"
},
"dependencies": {
"@mintel/mail": "workspace:*",
"axios": "^1.7.9",
"dotenv": "^16.4.7",
"nodemailer": "^9.0.3"
},
"devDependencies": {
"@mintel/eslint-config": "workspace:*",
"@mintel/tsconfig": "workspace:*",
"@types/node": "^22.19.10",
"@types/nodemailer": "^8.0.1",
"tsup": "^8.3.5",
"tsx": "^4.19.2",
"typescript": "^5.7.2",
"vitest": "^4.1.10"
}
}

View File

@@ -0,0 +1,205 @@
import fs from "fs/promises";
import path from "path";
import axios from "axios";
import { render } from "@mintel/mail";
import { AnalyticsTemplate } from "@mintel/mail";
import { getLast7Days, getThisMonth, getThisYear } from "./utils/time-calculator";
import nodemailer from "nodemailer";
import * as dotenv from "dotenv";
dotenv.config();
const UMAMI_BASE_URL = process.env.UMAMI_BASE_URL || "https://umami.infra.mintel.me";
const UMAMI_USERNAME = process.env.UMAMI_USERNAME;
const UMAMI_PASSWORD = process.env.UMAMI_PASSWORD;
const UMAMI_API_KEY = process.env.UMAMI_API_KEY;
const SMTP_HOST = process.env.SMTP_HOST || "mail.infra.mintel.me";
const SMTP_PORT = parseInt(process.env.SMTP_PORT || "587", 10);
const SMTP_USER = process.env.SMTP_USER;
const SMTP_PASS = process.env.SMTP_PASS;
const SMTP_SENDER = process.env.SMTP_SENDER || "reports@mintel.me";
const GOTIFY_URL = process.env.GOTIFY_URL;
const GOTIFY_TOKEN = process.env.GOTIFY_TOKEN;
interface ConfigClient {
websiteId: string;
domain: string;
clientEmail: string;
clientName: string;
greeting?: string;
interval?: "weekly" | "monthly";
}
async function getUmamiToken(): Promise<string> {
if (UMAMI_API_KEY) {
return UMAMI_API_KEY;
}
if (!UMAMI_USERNAME || !UMAMI_PASSWORD) {
throw new Error("Missing UMAMI_USERNAME and UMAMI_PASSWORD or UMAMI_API_KEY");
}
const res = await axios.post(`${UMAMI_BASE_URL}/api/auth/login`, {
username: UMAMI_USERNAME,
password: UMAMI_PASSWORD,
});
return res.data.token;
}
import https from "https";
async function fetchUmamiData(api: any, websiteId: string, startAt: number, endAt: number, label: string) {
let unit = "day";
if (label === "Dieses Jahr") unit = "month";
const [statsRes, pagesRes, refRes, viewsRes] = await Promise.all([
api.get(`/websites/${websiteId}/stats`, { params: { startAt, endAt } }),
api.get(`/websites/${websiteId}/metrics`, { params: { type: "path", limit: 5, startAt, endAt } }),
api.get(`/websites/${websiteId}/metrics`, { params: { type: "referrer", limit: 5, startAt, endAt } }),
api.get(`/websites/${websiteId}/pageviews`, { params: { startAt, endAt, unit, timezone: "Europe/Berlin" } }).catch(() => ({ data: { pageviews: [] } })),
]);
const stats = statsRes.data;
const topPages = (pagesRes.data || []).map((p: any) => ({ url: p.x, views: p.y || 0 }));
const topReferrers = (refRes.data || []).map((r: any) => ({ url: r.x, visitors: r.y || 0 }));
const visitors = typeof stats.visitors === "object" ? stats.visitors?.value || 0 : stats.visitors || 0;
const visits = typeof stats.visits === "object" ? stats.visits?.value || 0 : stats.visits || 0;
const views = typeof stats.pageviews === "object" ? stats.pageviews?.value || 0 : stats.pageviews || 0;
const bounces = typeof stats.bounces === "object" ? stats.bounces?.value || 0 : stats.bounces || 0;
const totaltime = typeof stats.totaltime === "object" ? stats.totaltime?.value || 0 : stats.totaltime || 0;
const bounceRate = visits > 0 ? Math.round((bounces / visits) * 100) : 0;
let visitDuration = "0s";
if (visits > 0) {
const avgSeconds = Math.round(totaltime / visits);
if (avgSeconds < 60) {
visitDuration = `${avgSeconds}s`;
} else {
visitDuration = `${Math.floor(avgSeconds / 60)}m ${avgSeconds % 60}s`;
}
}
const chartData = (viewsRes.data?.pageviews || []).map((pv: any) => ({
label: pv.x,
value: pv.y || 0
}));
return {
label,
visitors,
visits,
views,
bounceRate,
visitDuration,
topPages,
topReferrers,
chartData,
};
}
async function main() {
const isDryRun = process.argv.includes("--dry-run");
const intervalArg = process.argv.find(a => a.startsWith("--interval="));
const runInterval = intervalArg ? intervalArg.split("=")[1] : "monthly";
if (runInterval !== "weekly" && runInterval !== "monthly") {
throw new Error("Invalid interval. Use --interval=weekly or --interval=monthly");
}
// Load config
const configPath = path.resolve(process.cwd(), "config.json");
const configRaw = await fs.readFile(configPath, "utf-8");
const clients: ConfigClient[] = JSON.parse(configRaw);
// Setup Umami API
const token = await getUmamiToken();
const headers = UMAMI_API_KEY ? { "x-umami-api-key": token } : { Authorization: `Bearer ${token}` };
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
const api = axios.create({ baseURL: `${UMAMI_BASE_URL}/api`, headers, httpsAgent });
// Setup SMTP Transporter (Nodemailer)
const transporter = nodemailer.createTransport({
host: SMTP_HOST,
port: SMTP_PORT,
secure: SMTP_PORT === 465,
auth: SMTP_USER && SMTP_PASS ? {
user: SMTP_USER,
pass: SMTP_PASS,
} : undefined,
});
const timeRanges = [getLast7Days(), getThisMonth(), getThisYear()];
console.log(`Generating reports...`);
for (const client of clients) {
if (client.websiteId === "replace-with-umami-website-id") continue;
const clientInterval = client.interval || "monthly";
if (clientInterval !== runInterval) {
console.log(`Skipping ${client.domain} (runs ${clientInterval}, currently executing ${runInterval})`);
continue;
}
console.log(`Processing ${client.domain} (${client.clientName})...`);
try {
const periods = await Promise.all(
timeRanges.map((tr) => fetchUmamiData(api, client.websiteId, tr.startAt, tr.endAt, tr.label))
);
const html = await render(AnalyticsTemplate({
greeting: client.greeting || `Hallo ${client.clientName},`,
domain: client.domain,
periods,
}));
const subject = `Performance-Report - ${client.domain}`;
if (isDryRun) {
console.log(`[DRY-RUN] Would send email to ${client.clientEmail} for ${client.domain}`);
const debugPath = path.resolve(process.cwd(), `dry-run-${client.domain}.html`);
await fs.writeFile(debugPath, html);
console.log(`[DRY-RUN] Saved HTML preview to ${debugPath}`);
} else {
if (!SMTP_HOST) {
throw new Error("SMTP_HOST is missing for live run.");
}
const recipients = client.clientEmail.split(',').map(e => e.trim()).filter(e => e.length > 0);
for (const recipient of recipients) {
await transporter.sendMail({
from: SMTP_SENDER,
to: recipient,
subject,
html,
});
console.log(`Successfully sent to ${recipient}`);
}
}
} catch (e: any) {
const errorMsg = e.response?.data || e.message;
console.error(`Failed to process client ${client.domain}:`, errorMsg);
if (GOTIFY_URL && GOTIFY_TOKEN) {
try {
await axios.post(`${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}`, {
title: `❌ Analytics Mailer Error (${client.domain})`,
message: `Failed to generate or send report for ${client.domain}.\n\nError:\n${errorMsg}`,
priority: 8
});
} catch (gotifyErr: any) {
console.error("Failed to push to Gotify:", gotifyErr.message);
}
}
}
}
console.log("Done.");
}
main().catch((err) => {
console.error("Fatal error:", err);
process.exit(1);
});

View File

@@ -0,0 +1,19 @@
export function getLast7Days() {
const endAt = Date.now();
const startAt = endAt - 7 * 24 * 60 * 60 * 1000;
return { label: "Letzte 7 Tage", startAt, endAt };
}
export function getThisMonth() {
const now = new Date();
const startAt = new Date(now.getFullYear(), now.getMonth(), 1).getTime();
const endAt = Date.now();
return { label: "Dieser Monat", startAt, endAt };
}
export function getThisYear() {
const now = new Date();
const startAt = new Date(now.getFullYear(), 0, 1).getTime();
const endAt = Date.now();
return { label: "Dieses Jahr", startAt, endAt };
}

View File

@@ -0,0 +1,10 @@
{
"extends": "@mintel/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"module": "NodeNext",
"moduleResolution": "NodeNext"
},
"include": ["src/**/*"]
}

View File

@@ -31,7 +31,7 @@
"@next/eslint-plugin-next": "16.1.6",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/node": "^20.17.16",
"@types/node": "^20.19.33",
"@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2",

View File

@@ -25,3 +25,4 @@ export * from "./templates/ConfirmationMessage";
export * from "./templates/FollowUpTemplate";
export * from "./templates/ProjectEstimateTemplate";
export * from "./templates/SiteAuditTemplate";
export * from "./templates/AnalyticsTemplate";

View File

@@ -0,0 +1,197 @@
import {
Body,
Container,
Head,
Html,
Preview,
Section,
Text,
Img,
} from "@react-email/components";
import * as React from "react";
export interface GatekeeperLayoutProps {
preview: string;
projectName?: string;
subTitle?: string;
children: React.ReactNode;
}
export const GatekeeperLayout = ({ preview, projectName = "MINTEL", subTitle = "INFRASTRUCTURE", children }: GatekeeperLayoutProps) => {
return (
<Html>
<Head />
<Preview>{preview}</Preview>
<Body style={main}>
<Container style={container}>
{/* Top Icon Box */}
<Section style={iconBoxSection}>
<div style={iconBox}>
<Img
src="https://mintel.me/favicon-32x32.png"
width={28}
height={28}
alt="Mintel"
style={iconImg}
/>
</div>
</Section>
{/* Title Area */}
<Section style={titleSection}>
<Text style={gatekeeperTitle}>
{projectName} <span style={{ color: "rgba(0,0,0,0.3)" }}>GATEKEEPER</span>
</Text>
<table align="center" width="100%" border={0} cellPadding={0} cellSpacing={0} role="presentation">
<tbody>
<tr>
<td width="30%" align="right"><div style={lineGradientRight} /></td>
<td width="40%" align="center">
<Text style={gatekeeperSub}>
{subTitle}
</Text>
</td>
<td width="30%" align="left"><div style={lineGradientLeft} /></td>
</tr>
</tbody>
</table>
</Section>
{/* Main Content Area */}
<Section style={contentSection}>
{children}
</Section>
{/* Footer Area */}
<Section style={footerSection}>
<div style={lineGradientCenter} />
<div style={{ marginTop: "24px" }}>
<Img
src="https://mintel.me/favicon-32x32.png"
width={24}
height={24}
alt={projectName}
style={{ opacity: 0.3, margin: "0 auto", display: "block" }}
/>
</div>
<Text style={footerText}>
&copy; {new Date().getFullYear()} MINTEL
</Text>
</Section>
</Container>
</Body>
</Html>
);
};
const main = {
backgroundColor: "#f5f5f7",
color: "#111111",
fontFamily:
'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
WebkitFontSmoothing: "antialiased" as const,
};
const container = {
margin: "0 auto",
padding: "40px 20px 80px",
maxWidth: "420px",
};
const iconBoxSection = {
textAlign: "center" as const,
marginBottom: "40px",
};
const iconBox = {
width: "56px",
height: "56px",
backgroundColor: "#ffffff",
borderRadius: "16px",
margin: "0 auto",
display: "flex",
alignItems: "center",
justifyContent: "center",
border: "1px solid rgba(0,0,0,0.06)",
boxShadow: "0 10px 15px -3px rgba(0,0,0,0.06), 0 4px 6px -2px rgba(0,0,0,0.03)",
};
const iconImg = {
margin: "14px auto",
display: "block",
opacity: 0.8,
filter: "grayscale(100%) brightness(0)",
};
const titleSection = {
textAlign: "center" as const,
marginBottom: "32px",
};
const gatekeeperTitle = {
fontSize: "11px",
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif',
fontWeight: "bold",
textTransform: "uppercase" as const,
letterSpacing: "0.5em",
color: "rgba(0,0,0,0.8)",
margin: "0 0 12px 0",
};
const gatekeeperSub = {
fontSize: "8px",
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif',
textTransform: "uppercase" as const,
letterSpacing: "0.35em",
fontWeight: 600,
color: "rgba(0,0,0,0.3)",
margin: "0",
};
const lineGradientRight = {
height: "1px",
width: "32px",
backgroundColor: "rgba(0,0,0,0.1)",
marginLeft: "auto",
};
const lineGradientLeft = {
height: "1px",
width: "32px",
backgroundColor: "rgba(0,0,0,0.1)",
marginRight: "auto",
};
const contentSection = {
backgroundColor: "#ffffff",
padding: "32px",
borderRadius: "24px",
border: "1px solid rgba(0,0,0,0.06)",
boxShadow: "0 4px 6px -1px rgba(0,0,0,0.03)",
marginBottom: "32px",
};
const footerSection = {
textAlign: "center" as const,
paddingTop: "24px",
};
const lineGradientCenter = {
height: "1px",
width: "64px",
backgroundColor: "rgba(0,0,0,0.1)",
margin: "0 auto",
};
const footerText = {
fontSize: "7px",
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif',
fontWeight: 600,
color: "rgba(0,0,0,0.25)",
textTransform: "uppercase" as const,
letterSpacing: "0.5em",
marginTop: "20px",
};

File diff suppressed because one or more lines are too long

220
pnpm-lock.yaml generated
View File

@@ -53,7 +53,7 @@ importers:
specifier: ^16.3.2
version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@types/node':
specifier: ^20.17.16
specifier: ^20.19.33
version: 20.19.33
'@types/react':
specifier: ^19.2.10
@@ -104,6 +104,46 @@ importers:
specifier: ^4.0.18
version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jiti@2.6.1)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
apps/analytics-mailer:
dependencies:
'@mintel/mail':
specifier: workspace:*
version: link:../../packages/mail
axios:
specifier: ^1.7.9
version: 1.13.5
dotenv:
specifier: ^16.4.7
version: 16.6.1
nodemailer:
specifier: ^9.0.3
version: 9.0.3
devDependencies:
'@mintel/eslint-config':
specifier: workspace:*
version: link:../../packages/eslint-config
'@mintel/tsconfig':
specifier: workspace:*
version: link:../../packages/tsconfig
'@types/node':
specifier: ^22.19.10
version: 22.19.10
'@types/nodemailer':
specifier: ^8.0.1
version: 8.0.1
tsup:
specifier: ^8.3.5
version: 8.5.1(@swc/core@1.15.11(@swc/helpers@0.5.18))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
tsx:
specifier: ^4.19.2
version: 4.21.0
typescript:
specifier: ^5.7.2
version: 5.9.3
vitest:
specifier: ^4.1.10
version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(vite@7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
apps/sample-website:
dependencies:
'@mintel/next-observability':
@@ -596,7 +636,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^2.1.3
version: 2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
version: 2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
packages/next-config:
dependencies:
@@ -783,7 +823,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^2.1.3
version: 2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
version: 2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
packages/page-audit:
dependencies:
@@ -1051,7 +1091,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^2.1.3
version: 2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
version: 2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
packages:
@@ -3559,6 +3599,9 @@ packages:
'@types/node@22.19.10':
resolution: {integrity: sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw==}
'@types/nodemailer@8.0.1':
resolution: {integrity: sha512-PxpaInm8V1JQDd4j0ds5HfvWQk8JupS1C0Picb96QJsrrRDjBH+DlK7L4ZdNSqNULhiZRQHc40nLVShaGxXAMw==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -3790,6 +3833,9 @@ packages:
'@vitest/expect@4.0.18':
resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==}
'@vitest/expect@4.1.10':
resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==}
'@vitest/mocker@2.1.9':
resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==}
peerDependencies:
@@ -3823,6 +3869,17 @@ packages:
vite:
optional: true
'@vitest/mocker@4.1.10':
resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@2.1.9':
resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==}
@@ -3832,6 +3889,9 @@ packages:
'@vitest/pretty-format@4.0.18':
resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==}
'@vitest/pretty-format@4.1.10':
resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==}
'@vitest/runner@2.1.9':
resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==}
@@ -3841,6 +3901,9 @@ packages:
'@vitest/runner@4.0.18':
resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==}
'@vitest/runner@4.1.10':
resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==}
'@vitest/snapshot@2.1.9':
resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==}
@@ -3850,6 +3913,9 @@ packages:
'@vitest/snapshot@4.0.18':
resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==}
'@vitest/snapshot@4.1.10':
resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==}
'@vitest/spy@2.1.9':
resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==}
@@ -3859,6 +3925,9 @@ packages:
'@vitest/spy@4.0.18':
resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==}
'@vitest/spy@4.1.10':
resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==}
'@vitest/ui@4.0.18':
resolution: {integrity: sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==}
peerDependencies:
@@ -3873,6 +3942,9 @@ packages:
'@vitest/utils@4.0.18':
resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
'@vitest/utils@4.1.10':
resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==}
'@vladfrangu/async_event_emitter@2.4.7':
resolution: {integrity: sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
@@ -6701,6 +6773,10 @@ packages:
node-releases@2.0.27:
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
nodemailer@9.0.3:
resolution: {integrity: sha512-n+YP+NKwR5zRWa60k3GiQ6Q3B4KXCoAw40dAKeCtYn020iNN74aWK2liXIC3ZEATeGql7we3tE3t8QwhY0eskw==}
engines: {node: '>=6.0.0'}
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
@@ -7754,6 +7830,9 @@ packages:
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
std-env@4.2.0:
resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==}
stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
@@ -8040,6 +8119,10 @@ packages:
resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
engines: {node: '>=14.0.0'}
tinyrainbow@3.1.0:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
tinyspy@3.0.2:
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
engines: {node: '>=14.0.0'}
@@ -8513,6 +8596,47 @@ packages:
jsdom:
optional: true
vitest@4.1.10:
resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 4.1.10
'@vitest/browser-preview': 4.1.10
'@vitest/browser-webdriverio': 4.1.10
'@vitest/coverage-istanbul': 4.1.10
'@vitest/coverage-v8': 4.1.10
'@vitest/ui': 4.1.10
happy-dom: '*'
jsdom: '*'
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@opentelemetry/api':
optional: true
'@types/node':
optional: true
'@vitest/browser-playwright':
optional: true
'@vitest/browser-preview':
optional: true
'@vitest/browser-webdriverio':
optional: true
'@vitest/coverage-istanbul':
optional: true
'@vitest/coverage-v8':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
vizion@2.2.1:
resolution: {integrity: sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==}
engines: {node: '>=4.0'}
@@ -11528,6 +11652,10 @@ snapshots:
dependencies:
undici-types: 6.21.0
'@types/nodemailer@8.0.1':
dependencies:
'@types/node': 20.19.33
'@types/parse-json@4.0.2': {}
'@types/pg-pool@2.0.7':
@@ -11783,6 +11911,15 @@ snapshots:
chai: 6.2.2
tinyrainbow: 3.0.3
'@vitest/expect@4.1.10':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
'@vitest/spy': 4.1.10
'@vitest/utils': 4.1.10
chai: 6.2.2
tinyrainbow: 3.1.0
'@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.33)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0))':
dependencies:
'@vitest/spy': 2.1.9
@@ -11823,6 +11960,14 @@ snapshots:
optionalDependencies:
vite: 7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
'@vitest/mocker@4.1.10(vite@7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@vitest/spy': 4.1.10
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
'@vitest/pretty-format@2.1.9':
dependencies:
tinyrainbow: 1.2.0
@@ -11835,6 +11980,10 @@ snapshots:
dependencies:
tinyrainbow: 3.0.3
'@vitest/pretty-format@4.1.10':
dependencies:
tinyrainbow: 3.1.0
'@vitest/runner@2.1.9':
dependencies:
'@vitest/utils': 2.1.9
@@ -11851,6 +12000,11 @@ snapshots:
'@vitest/utils': 4.0.18
pathe: 2.0.3
'@vitest/runner@4.1.10':
dependencies:
'@vitest/utils': 4.1.10
pathe: 2.0.3
'@vitest/snapshot@2.1.9':
dependencies:
'@vitest/pretty-format': 2.1.9
@@ -11869,6 +12023,13 @@ snapshots:
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/snapshot@4.1.10':
dependencies:
'@vitest/pretty-format': 4.1.10
'@vitest/utils': 4.1.10
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@2.1.9':
dependencies:
tinyspy: 3.0.2
@@ -11879,6 +12040,8 @@ snapshots:
'@vitest/spy@4.0.18': {}
'@vitest/spy@4.1.10': {}
'@vitest/ui@4.0.18(vitest@4.0.18)':
dependencies:
'@vitest/utils': 4.0.18
@@ -11887,7 +12050,7 @@ snapshots:
pathe: 2.0.3
sirv: 3.0.2
tinyglobby: 0.2.15
tinyrainbow: 3.0.3
tinyrainbow: 3.1.0
vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jiti@2.6.1)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
optional: true
@@ -11908,6 +12071,12 @@ snapshots:
'@vitest/pretty-format': 4.0.18
tinyrainbow: 3.0.3
'@vitest/utils@4.1.10':
dependencies:
'@vitest/pretty-format': 4.1.10
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
'@vladfrangu/async_event_emitter@2.4.7': {}
'@webassemblyjs/ast@1.14.1':
@@ -13973,7 +14142,7 @@ snapshots:
happy-dom@20.5.3:
dependencies:
'@types/node': 20.19.33
'@types/node': 22.19.10
'@types/whatwg-mimetype': 3.0.2
'@types/ws': 8.18.1
entities: 6.0.1
@@ -15019,6 +15188,8 @@ snapshots:
node-releases@2.0.27: {}
nodemailer@9.0.3: {}
normalize-path@3.0.0: {}
normalize-svg-path@1.1.0:
@@ -16360,6 +16531,8 @@ snapshots:
std-env@3.10.0: {}
std-env@4.2.0: {}
stop-iteration-iterator@1.1.0:
dependencies:
es-errors: 1.3.0
@@ -16709,6 +16882,8 @@ snapshots:
tinyrainbow@3.0.3: {}
tinyrainbow@3.1.0: {}
tinyspy@3.0.2: {}
tinyspy@4.0.4: {}
@@ -17168,7 +17343,7 @@ snapshots:
tsx: 4.21.0
yaml: 2.8.2
vitest@2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
vitest@2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
dependencies:
'@vitest/expect': 2.1.9
'@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.33)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0))
@@ -17375,6 +17550,37 @@ snapshots:
- tsx
- yaml
vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(vite@7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
'@vitest/expect': 4.1.10
'@vitest/mocker': 4.1.10(vite@7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
'@vitest/pretty-format': 4.1.10
'@vitest/runner': 4.1.10
'@vitest/snapshot': 4.1.10
'@vitest/spy': 4.1.10
'@vitest/utils': 4.1.10
es-module-lexer: 2.0.0
expect-type: 1.3.0
magic-string: 0.30.21
obug: 2.1.1
pathe: 2.0.3
picomatch: 4.0.3
std-env: 4.2.0
tinybench: 2.9.0
tinyexec: 1.0.2
tinyglobby: 0.2.15
tinyrainbow: 3.1.0
vite: 7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
why-is-node-running: 2.3.0
optionalDependencies:
'@opentelemetry/api': 1.9.0
'@types/node': 22.19.10
'@vitest/ui': 4.0.18(vitest@4.0.18)
happy-dom: 20.5.3
jsdom: 27.4.0(canvas@3.2.1)
transitivePeerDependencies:
- msw
vizion@2.2.1:
dependencies:
async: 2.6.4