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
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
This commit is contained in:
@@ -1,13 +1,25 @@
|
||||
name: Monthly Analytics Mailer
|
||||
name: Analytics Mailer
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 8 1 * *' # Run at 08:00 on the 1st of every month
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
# 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 Monthly Reports
|
||||
name: 📊 Send Analytics Reports
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
@@ -40,5 +52,18 @@ jobs:
|
||||
- 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
|
||||
run: pnpm --filter @mintel/analytics-mailer run start -- --interval=${{ steps.set-interval.outputs.interval }}
|
||||
|
||||
@@ -1,14 +1,34 @@
|
||||
[
|
||||
{
|
||||
"websiteId": "d773ea10-a3b3-4ccf-9024-987e14c4d669",
|
||||
"domain": "e-tib.com",
|
||||
"clientEmail": "marc@mintel.me",
|
||||
"clientName": "E-TIB GmbH"
|
||||
},
|
||||
{
|
||||
"websiteId": "59a7db94-0100-4c7e-98ef-99f45b17f9c3",
|
||||
"domain": "klz-cables.com",
|
||||
"clientEmail": "marc@mintel.me",
|
||||
"clientName": "KLZ"
|
||||
"clientName": "KLZ Cables",
|
||||
"greeting": "Hallo Herr Künzel,",
|
||||
"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 Herr Bartenbach,",
|
||||
"interval": "monthly"
|
||||
},
|
||||
{
|
||||
"websiteId": "d773ea10-a3b3-4ccf-9024-987e14c4d669",
|
||||
"domain": "e-tib.com",
|
||||
"clientEmail": "marc@mintel.me",
|
||||
"clientName": "E-TIB GmbH",
|
||||
"greeting": "Sehr geehrter Herr Mintel,",
|
||||
"interval": "monthly"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
"devDependencies": {
|
||||
"@mintel/eslint-config": "workspace:*",
|
||||
"@mintel/tsconfig": "workspace:*",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/node": "^22.19.10",
|
||||
"tsup": "^8.3.5",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.2"
|
||||
"typescript": "^5.7.2",
|
||||
"vitest": "^4.1.10"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import axios from "axios";
|
||||
import { render } from "@mintel/mail";
|
||||
import { MonthlyAnalyticsTemplate } from "@mintel/mail";
|
||||
import { AnalyticsTemplate } from "@mintel/mail";
|
||||
import { getLast7Days, getThisMonth, getThisYear } from "./utils/time-calculator";
|
||||
import Mailgun from "mailgun.js";
|
||||
import formData from "form-data";
|
||||
import * as dotenv from "dotenv";
|
||||
@@ -24,6 +25,8 @@ interface ConfigClient {
|
||||
domain: string;
|
||||
clientEmail: string;
|
||||
clientName: string;
|
||||
greeting?: string;
|
||||
interval?: "weekly" | "monthly";
|
||||
}
|
||||
|
||||
async function getUmamiToken(): Promise<string> {
|
||||
@@ -40,27 +43,67 @@ async function getUmamiToken(): Promise<string> {
|
||||
return res.data.token;
|
||||
}
|
||||
|
||||
async function fetchUmamiData(api: any, websiteId: string, startAt: number, endAt: number) {
|
||||
const [statsRes, pagesRes, refRes] = await Promise.all([
|
||||
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: "url", limit: 5, startAt, endAt } }),
|
||||
api.get(`/websites/${websiteId}/metrics`, { params: { type: "referrer", limit: 3, 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 }));
|
||||
const topReferrers = (refRes.data || []).map((r: any) => ({ url: r.x, visitors: r.y }));
|
||||
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 {
|
||||
totalVisitors: stats.visitors?.value || 0,
|
||||
totalPageviews: stats.pageviews?.value || 0,
|
||||
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");
|
||||
@@ -70,47 +113,39 @@ async function main() {
|
||||
// Setup Umami API
|
||||
const token = await getUmamiToken();
|
||||
const headers = UMAMI_API_KEY ? { "x-umami-api-key": token } : { Authorization: `Bearer ${token}` };
|
||||
const api = axios.create({ baseURL: `${UMAMI_BASE_URL}/api`, headers });
|
||||
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
||||
const api = axios.create({ baseURL: `${UMAMI_BASE_URL}/api`, headers, httpsAgent });
|
||||
|
||||
// Setup Mailgun
|
||||
const mailgun = new Mailgun(formData);
|
||||
const mg = mailgun.client({ username: "api", key: MAILGUN_API_KEY!, url: MAILGUN_URL });
|
||||
const mg = mailgun.client({ username: "api", key: MAILGUN_API_KEY || "dummy", url: MAILGUN_URL });
|
||||
|
||||
// Time range calculation (previous month)
|
||||
const now = new Date();
|
||||
// Ensure we get the correct previous month relative to current local time, but use UTC boundary or local? Umami uses ms timestamps.
|
||||
// Standard way: First day of previous month, last day of previous month.
|
||||
const startOfPrevMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const endOfPrevMonth = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
|
||||
|
||||
const startAt = startOfPrevMonth.getTime();
|
||||
const endAt = endOfPrevMonth.getTime();
|
||||
const timeRanges = [getLast7Days(), getThisMonth(), getThisYear()];
|
||||
|
||||
const monthName = startOfPrevMonth.toLocaleString("de-DE", { month: "long" });
|
||||
const year = startOfPrevMonth.getFullYear().toString();
|
||||
|
||||
console.log(`Generating reports for ${monthName} ${year}...`);
|
||||
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 data = await fetchUmamiData(api, client.websiteId, startAt, endAt);
|
||||
const periods = await Promise.all(
|
||||
timeRanges.map((tr) => fetchUmamiData(api, client.websiteId, tr.startAt, tr.endAt, tr.label))
|
||||
);
|
||||
|
||||
const html = await render(MonthlyAnalyticsTemplate({
|
||||
clientName: client.clientName,
|
||||
const html = await render(AnalyticsTemplate({
|
||||
greeting: client.greeting || `Hallo ${client.clientName},`,
|
||||
domain: client.domain,
|
||||
month: monthName,
|
||||
year,
|
||||
totalVisitors: data.totalVisitors,
|
||||
totalPageviews: data.totalPageviews,
|
||||
topPages: data.topPages,
|
||||
topReferrers: data.topReferrers,
|
||||
periods,
|
||||
}));
|
||||
|
||||
const subject = `Ihr Website-Report für ${monthName} ${year} - ${client.domain}`;
|
||||
const subject = `Performance-Report - ${client.domain}`;
|
||||
|
||||
if (isDryRun) {
|
||||
console.log(`[DRY-RUN] Would send email to ${client.clientEmail} for ${client.domain}`);
|
||||
|
||||
19
apps/analytics-mailer/src/utils/time-calculator.ts
Normal file
19
apps/analytics-mailer/src/utils/time-calculator.ts
Normal 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 };
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -25,4 +25,4 @@ export * from "./templates/ConfirmationMessage";
|
||||
export * from "./templates/FollowUpTemplate";
|
||||
export * from "./templates/ProjectEstimateTemplate";
|
||||
export * from "./templates/SiteAuditTemplate";
|
||||
export * from "./templates/MonthlyAnalyticsTemplate";
|
||||
export * from "./templates/AnalyticsTemplate";
|
||||
|
||||
197
packages/mail/src/layouts/GatekeeperLayout.tsx
Normal file
197
packages/mail/src/layouts/GatekeeperLayout.tsx
Normal 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}>
|
||||
© {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",
|
||||
};
|
||||
318
packages/mail/src/templates/AnalyticsTemplate.tsx
Normal file
318
packages/mail/src/templates/AnalyticsTemplate.tsx
Normal file
File diff suppressed because one or more lines are too long
@@ -1,205 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { Heading, Section, Text, Row, Column } from "@react-email/components";
|
||||
import { MintelLayout } from "../layouts/MintelLayout";
|
||||
|
||||
export interface MonthlyAnalyticsTemplateProps {
|
||||
clientName: string;
|
||||
domain: string;
|
||||
month: string;
|
||||
year: string;
|
||||
totalVisitors: number;
|
||||
totalPageviews: number;
|
||||
topPages: { url: string; views: number }[];
|
||||
topReferrers: { url: string; visitors: number }[];
|
||||
}
|
||||
|
||||
export const MonthlyAnalyticsTemplate = ({
|
||||
clientName,
|
||||
domain,
|
||||
month,
|
||||
year,
|
||||
totalVisitors,
|
||||
totalPageviews,
|
||||
topPages,
|
||||
topReferrers,
|
||||
}: MonthlyAnalyticsTemplateProps) => {
|
||||
const preview = `Ihr Website-Report für ${month} ${year}: ${domain}`;
|
||||
|
||||
return (
|
||||
<MintelLayout preview={preview}>
|
||||
<Heading style={h1}>Monatsreport: {month} {year}</Heading>
|
||||
<Text style={intro}>
|
||||
Hallo {clientName},<br /><br />
|
||||
hier ist der monatliche Performance-Report für Ihre Website ({domain}).
|
||||
Die Daten zeigen, wie viele Menschen Ihre Seite besucht haben und welche Inhalte am beliebtesten waren.
|
||||
</Text>
|
||||
|
||||
<Section style={metricsContainer}>
|
||||
<Row>
|
||||
<Column style={metricColumn}>
|
||||
<Text style={metricLabel}>BESUCHER</Text>
|
||||
<Text style={metricValue}>{totalVisitors.toLocaleString('de-DE')}</Text>
|
||||
</Column>
|
||||
<Column style={metricColumn}>
|
||||
<Text style={metricLabel}>SEITENAUFRUFE</Text>
|
||||
<Text style={metricValue}>{totalPageviews.toLocaleString('de-DE')}</Text>
|
||||
</Column>
|
||||
</Row>
|
||||
</Section>
|
||||
|
||||
<Section style={auditContainer}>
|
||||
<Heading as="h2" style={h2}>Top Seiten</Heading>
|
||||
{topPages.map((page, i) => (
|
||||
<Row key={i} style={highlightRow}>
|
||||
<Column style={bulletCol}>
|
||||
<div style={bullet} />
|
||||
</Column>
|
||||
<Column>
|
||||
<Text style={highlightText}>{page.url}</Text>
|
||||
</Column>
|
||||
<Column align="right">
|
||||
<Text style={highlightNumber}>{page.views.toLocaleString('de-DE')} Aufrufe</Text>
|
||||
</Column>
|
||||
</Row>
|
||||
))}
|
||||
</Section>
|
||||
|
||||
<Section style={auditContainer}>
|
||||
<Heading as="h2" style={h2}>Top Besucherquellen</Heading>
|
||||
{topReferrers.length > 0 ? topReferrers.map((ref, i) => (
|
||||
<Row key={i} style={highlightRow}>
|
||||
<Column style={bulletCol}>
|
||||
<div style={bullet} />
|
||||
</Column>
|
||||
<Column>
|
||||
<Text style={highlightText}>{ref.url || "Direktaufruf"}</Text>
|
||||
</Column>
|
||||
<Column align="right">
|
||||
<Text style={highlightNumber}>{ref.visitors.toLocaleString('de-DE')} Besucher</Text>
|
||||
</Column>
|
||||
</Row>
|
||||
)) : (
|
||||
<Text style={highlightText}>Keine externen Quellen verzeichnet.</Text>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
<Text style={bodyText}>
|
||||
Haben Sie Fragen zu diesen Zahlen oder möchten Sie die Reichweite Ihrer Seite weiter ausbauen?
|
||||
Antworten Sie einfach auf diese E-Mail.
|
||||
</Text>
|
||||
|
||||
<Text style={footerText}>
|
||||
Beste Grüße,<br />
|
||||
<strong>Marc Mintel</strong><br />
|
||||
Digitaler Architekt
|
||||
</Text>
|
||||
</MintelLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default MonthlyAnalyticsTemplate;
|
||||
|
||||
const h1 = {
|
||||
fontSize: "28px",
|
||||
fontWeight: "900",
|
||||
margin: "0 0 24px",
|
||||
color: "#ffffff",
|
||||
letterSpacing: "-0.04em",
|
||||
};
|
||||
|
||||
const h2 = {
|
||||
fontSize: "14px",
|
||||
fontWeight: "900",
|
||||
textTransform: "uppercase" as const,
|
||||
color: "#444444",
|
||||
margin: "0 0 16px",
|
||||
letterSpacing: "0.1em",
|
||||
};
|
||||
|
||||
const intro = {
|
||||
fontSize: "16px",
|
||||
lineHeight: "24px",
|
||||
color: "#cccccc",
|
||||
margin: "0 0 32px",
|
||||
};
|
||||
|
||||
const metricsContainer = {
|
||||
backgroundColor: "#151515",
|
||||
padding: "32px",
|
||||
borderRadius: "8px",
|
||||
marginBottom: "32px",
|
||||
border: "1px solid #222222",
|
||||
textAlign: "center" as const,
|
||||
};
|
||||
|
||||
const metricColumn = {
|
||||
width: "50%",
|
||||
};
|
||||
|
||||
const metricLabel = {
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
color: "#888888",
|
||||
letterSpacing: "0.1em",
|
||||
margin: "0 0 8px",
|
||||
};
|
||||
|
||||
const metricValue = {
|
||||
fontSize: "36px",
|
||||
fontWeight: "900",
|
||||
color: "#4CAF50",
|
||||
margin: "0",
|
||||
letterSpacing: "-0.02em",
|
||||
};
|
||||
|
||||
const auditContainer = {
|
||||
backgroundColor: "#151515",
|
||||
padding: "32px",
|
||||
borderRadius: "8px",
|
||||
marginBottom: "32px",
|
||||
border: "1px solid #222222",
|
||||
};
|
||||
|
||||
const highlightRow = {
|
||||
marginBottom: "12px",
|
||||
};
|
||||
|
||||
const bulletCol = {
|
||||
width: "24px",
|
||||
verticalAlign: "top" as const,
|
||||
};
|
||||
|
||||
const bullet = {
|
||||
width: "6px",
|
||||
height: "6px",
|
||||
backgroundColor: "#4CAF50",
|
||||
marginTop: "8px",
|
||||
};
|
||||
|
||||
const highlightText = {
|
||||
fontSize: "15px",
|
||||
color: "#ffffff",
|
||||
margin: "0",
|
||||
lineHeight: "22px",
|
||||
wordBreak: "break-all" as const,
|
||||
};
|
||||
|
||||
const highlightNumber = {
|
||||
fontSize: "15px",
|
||||
color: "#888888",
|
||||
margin: "0",
|
||||
whiteSpace: "nowrap" as const,
|
||||
};
|
||||
|
||||
const bodyText = {
|
||||
fontSize: "16px",
|
||||
lineHeight: "24px",
|
||||
color: "#888888",
|
||||
margin: "0 0 32px",
|
||||
};
|
||||
|
||||
const footerText = {
|
||||
fontSize: "14px",
|
||||
color: "#666666",
|
||||
lineHeight: "20px",
|
||||
};
|
||||
182
pnpm-lock.yaml
generated
182
pnpm-lock.yaml
generated
@@ -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
|
||||
@@ -129,7 +129,7 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/tsconfig
|
||||
'@types/node':
|
||||
specifier: ^22.10.2
|
||||
specifier: ^22.19.10
|
||||
version: 22.19.10
|
||||
tsup:
|
||||
specifier: ^8.3.5
|
||||
@@ -140,6 +140,9 @@ importers:
|
||||
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:
|
||||
@@ -272,7 +275,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.0.5
|
||||
version: 3.2.4(@types/debug@4.1.12)(@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)
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@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)
|
||||
|
||||
packages/content-engine:
|
||||
dependencies:
|
||||
@@ -574,7 +577,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.0.4
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@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)
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@4.0.18(vitest@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)
|
||||
|
||||
packages/meme-generator:
|
||||
dependencies:
|
||||
@@ -633,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:
|
||||
@@ -789,7 +792,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^2.0.0
|
||||
version: 2.1.9(@types/node@22.19.10)(@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@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))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||
|
||||
packages/outline-mcp:
|
||||
dependencies:
|
||||
@@ -820,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:
|
||||
@@ -965,7 +968,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.0.5
|
||||
version: 3.2.4(@types/debug@4.1.12)(@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)
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@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)
|
||||
|
||||
packages/serpbear-mcp:
|
||||
dependencies:
|
||||
@@ -1088,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:
|
||||
|
||||
@@ -3827,6 +3830,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:
|
||||
@@ -3860,6 +3866,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==}
|
||||
|
||||
@@ -3869,6 +3886,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==}
|
||||
|
||||
@@ -3878,6 +3898,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==}
|
||||
|
||||
@@ -3887,6 +3910,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==}
|
||||
|
||||
@@ -3896,6 +3922,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:
|
||||
@@ -3910,6 +3939,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'}
|
||||
@@ -7798,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'}
|
||||
@@ -8084,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'}
|
||||
@@ -8560,6 +8599,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'}
|
||||
@@ -11830,6 +11910,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
|
||||
@@ -11870,6 +11959,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
|
||||
@@ -11882,6 +11979,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
|
||||
@@ -11898,6 +11999,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
|
||||
@@ -11916,6 +12022,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
|
||||
@@ -11926,6 +12039,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
|
||||
@@ -11955,6 +12070,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':
|
||||
@@ -16417,6 +16538,8 @@ snapshots:
|
||||
|
||||
std-env@3.10.0: {}
|
||||
|
||||
std-env@4.2.0: {}
|
||||
|
||||
stop-iteration-iterator@1.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -16766,6 +16889,8 @@ snapshots:
|
||||
|
||||
tinyrainbow@3.0.3: {}
|
||||
|
||||
tinyrainbow@3.1.0: {}
|
||||
|
||||
tinyspy@3.0.2: {}
|
||||
|
||||
tinyspy@4.0.4: {}
|
||||
@@ -17227,7 +17352,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))
|
||||
@@ -17265,7 +17390,7 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vitest@2.1.9(@types/node@22.19.10)(@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@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))(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@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0))
|
||||
@@ -17303,7 +17428,7 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@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):
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@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):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
@@ -17348,7 +17473,7 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@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):
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@4.0.18(vitest@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):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
@@ -17434,6 +17559,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
|
||||
|
||||
Reference in New Issue
Block a user