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
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
This commit is contained in:
44
.gitea/workflows/analytics-mailer.yml
Normal file
44
.gitea/workflows/analytics-mailer.yml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
name: Monthly Analytics Mailer
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 8 1 * *' # Run at 08:00 on the 1st of every month
|
||||||
|
workflow_dispatch: # Allow manual trigger
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
send-reports:
|
||||||
|
name: 📊 Send Monthly 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 }}
|
||||||
|
MAILGUN_API_KEY: ${{ secrets.MAILGUN_API_KEY }}
|
||||||
|
MAILGUN_DOMAIN: ${{ secrets.MAILGUN_DOMAIN }}
|
||||||
|
MAILGUN_SENDER: ${{ secrets.MAILGUN_SENDER }}
|
||||||
|
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: Run Analytics Mailer
|
||||||
|
run: pnpm --filter @mintel/analytics-mailer run start
|
||||||
14
apps/analytics-mailer/config.json
Normal file
14
apps/analytics-mailer/config.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
26
apps/analytics-mailer/package.json
Normal file
26
apps/analytics-mailer/package.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"form-data": "^4.0.1",
|
||||||
|
"mailgun.js": "^10.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@mintel/eslint-config": "workspace:*",
|
||||||
|
"@mintel/tsconfig": "workspace:*",
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"tsup": "^8.3.5",
|
||||||
|
"tsx": "^4.19.2",
|
||||||
|
"typescript": "^5.7.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
143
apps/analytics-mailer/src/index.ts
Normal file
143
apps/analytics-mailer/src/index.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
import axios from "axios";
|
||||||
|
import { render } from "@mintel/mail";
|
||||||
|
import { MonthlyAnalyticsTemplate } from "@mintel/mail";
|
||||||
|
import Mailgun from "mailgun.js";
|
||||||
|
import formData from "form-data";
|
||||||
|
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 MAILGUN_API_KEY = process.env.MAILGUN_API_KEY;
|
||||||
|
const MAILGUN_DOMAIN = process.env.MAILGUN_DOMAIN;
|
||||||
|
const MAILGUN_SENDER = process.env.MAILGUN_SENDER || "reports@mintel.me";
|
||||||
|
const MAILGUN_URL = process.env.MAILGUN_URL || "https://api.eu.mailgun.net";
|
||||||
|
|
||||||
|
interface ConfigClient {
|
||||||
|
websiteId: string;
|
||||||
|
domain: string;
|
||||||
|
clientEmail: string;
|
||||||
|
clientName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchUmamiData(api: any, websiteId: string, startAt: number, endAt: number) {
|
||||||
|
const [statsRes, pagesRes, refRes] = 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 } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
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 }));
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalVisitors: stats.visitors?.value || 0,
|
||||||
|
totalPageviews: stats.pageviews?.value || 0,
|
||||||
|
topPages,
|
||||||
|
topReferrers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const isDryRun = process.argv.includes("--dry-run");
|
||||||
|
|
||||||
|
// 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 api = axios.create({ baseURL: `${UMAMI_BASE_URL}/api`, headers });
|
||||||
|
|
||||||
|
// Setup Mailgun
|
||||||
|
const mailgun = new Mailgun(formData);
|
||||||
|
const mg = mailgun.client({ username: "api", key: MAILGUN_API_KEY!, 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 monthName = startOfPrevMonth.toLocaleString("de-DE", { month: "long" });
|
||||||
|
const year = startOfPrevMonth.getFullYear().toString();
|
||||||
|
|
||||||
|
console.log(`Generating reports for ${monthName} ${year}...`);
|
||||||
|
|
||||||
|
for (const client of clients) {
|
||||||
|
if (client.websiteId === "replace-with-umami-website-id") continue;
|
||||||
|
|
||||||
|
console.log(`Processing ${client.domain} (${client.clientName})...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await fetchUmamiData(api, client.websiteId, startAt, endAt);
|
||||||
|
|
||||||
|
const html = await render(MonthlyAnalyticsTemplate({
|
||||||
|
clientName: client.clientName,
|
||||||
|
domain: client.domain,
|
||||||
|
month: monthName,
|
||||||
|
year,
|
||||||
|
totalVisitors: data.totalVisitors,
|
||||||
|
totalPageviews: data.totalPageviews,
|
||||||
|
topPages: data.topPages,
|
||||||
|
topReferrers: data.topReferrers,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const subject = `Ihr Website-Report für ${monthName} ${year} - ${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 (!MAILGUN_API_KEY) {
|
||||||
|
throw new Error("MAILGUN_API_KEY is missing for live run.");
|
||||||
|
}
|
||||||
|
await mg.messages.create(MAILGUN_DOMAIN!, {
|
||||||
|
from: MAILGUN_SENDER,
|
||||||
|
to: [client.clientEmail],
|
||||||
|
subject,
|
||||||
|
html,
|
||||||
|
});
|
||||||
|
console.log(`Successfully sent to ${client.clientEmail}`);
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(`Failed to process client ${client.domain}:`, e.response?.data || e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Done.");
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("Fatal error:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
10
apps/analytics-mailer/tsconfig.json
Normal file
10
apps/analytics-mailer/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "@mintel/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
||||||
@@ -25,3 +25,4 @@ export * from "./templates/ConfirmationMessage";
|
|||||||
export * from "./templates/FollowUpTemplate";
|
export * from "./templates/FollowUpTemplate";
|
||||||
export * from "./templates/ProjectEstimateTemplate";
|
export * from "./templates/ProjectEstimateTemplate";
|
||||||
export * from "./templates/SiteAuditTemplate";
|
export * from "./templates/SiteAuditTemplate";
|
||||||
|
export * from "./templates/MonthlyAnalyticsTemplate";
|
||||||
|
|||||||
205
packages/mail/src/templates/MonthlyAnalyticsTemplate.tsx
Normal file
205
packages/mail/src/templates/MonthlyAnalyticsTemplate.tsx
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
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",
|
||||||
|
};
|
||||||
73
pnpm-lock.yaml
generated
73
pnpm-lock.yaml
generated
@@ -104,6 +104,43 @@ importers:
|
|||||||
specifier: ^4.0.18
|
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)
|
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
|
||||||
|
form-data:
|
||||||
|
specifier: ^4.0.1
|
||||||
|
version: 4.0.5
|
||||||
|
mailgun.js:
|
||||||
|
specifier: ^10.3.0
|
||||||
|
version: 10.4.0
|
||||||
|
devDependencies:
|
||||||
|
'@mintel/eslint-config':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/eslint-config
|
||||||
|
'@mintel/tsconfig':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/tsconfig
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.10.2
|
||||||
|
version: 22.19.10
|
||||||
|
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
|
||||||
|
|
||||||
apps/sample-website:
|
apps/sample-website:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mintel/next-observability':
|
'@mintel/next-observability':
|
||||||
@@ -235,7 +272,7 @@ importers:
|
|||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
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)
|
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)
|
||||||
|
|
||||||
packages/content-engine:
|
packages/content-engine:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -537,7 +574,7 @@ importers:
|
|||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^3.0.4
|
specifier: ^3.0.4
|
||||||
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)
|
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)
|
||||||
|
|
||||||
packages/meme-generator:
|
packages/meme-generator:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -752,7 +789,7 @@ importers:
|
|||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.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)
|
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)
|
||||||
|
|
||||||
packages/outline-mcp:
|
packages/outline-mcp:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -928,7 +965,7 @@ importers:
|
|||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
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)
|
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)
|
||||||
|
|
||||||
packages/serpbear-mcp:
|
packages/serpbear-mcp:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4234,6 +4271,9 @@ packages:
|
|||||||
bare-url@2.3.2:
|
bare-url@2.3.2:
|
||||||
resolution: {integrity: sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==}
|
resolution: {integrity: sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==}
|
||||||
|
|
||||||
|
base-64@1.0.0:
|
||||||
|
resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==}
|
||||||
|
|
||||||
base64-arraybuffer@1.0.2:
|
base64-arraybuffer@1.0.2:
|
||||||
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
|
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
@@ -6406,6 +6446,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
|
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
mailgun.js@10.4.0:
|
||||||
|
resolution: {integrity: sha512-YrdaZEAJwwjXGBTfZTNQ1LM7tmkdUaz2NpZEu7+zULcG4Wrlhd7cWSNZW0bxT3bP48k5N0mZWz8C2f9gc2+Geg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
map-stream@0.1.0:
|
map-stream@0.1.0:
|
||||||
resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==}
|
resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==}
|
||||||
|
|
||||||
@@ -8285,6 +8329,9 @@ packages:
|
|||||||
uri-js@4.4.1:
|
uri-js@4.4.1:
|
||||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||||
|
|
||||||
|
url-join@4.0.1:
|
||||||
|
resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==}
|
||||||
|
|
||||||
use-context-selector@2.0.0:
|
use-context-selector@2.0.0:
|
||||||
resolution: {integrity: sha512-owfuSmUNd3eNp3J9CdDl0kMgfidV+MkDvHPpvthN5ThqM+ibMccNE0k+Iq7TWC6JPFvGZqanqiGCuQx6DyV24g==}
|
resolution: {integrity: sha512-owfuSmUNd3eNp3J9CdDl0kMgfidV+MkDvHPpvthN5ThqM+ibMccNE0k+Iq7TWC6JPFvGZqanqiGCuQx6DyV24g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -12297,6 +12344,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
bare-path: 3.0.0
|
bare-path: 3.0.0
|
||||||
|
|
||||||
|
base-64@1.0.0: {}
|
||||||
|
|
||||||
base64-arraybuffer@1.0.2: {}
|
base64-arraybuffer@1.0.2: {}
|
||||||
|
|
||||||
base64-js@0.0.8: {}
|
base64-js@0.0.8: {}
|
||||||
@@ -14780,6 +14829,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
mailgun.js@10.4.0:
|
||||||
|
dependencies:
|
||||||
|
axios: 1.13.5
|
||||||
|
base-64: 1.0.0
|
||||||
|
url-join: 4.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
map-stream@0.1.0: {}
|
map-stream@0.1.0: {}
|
||||||
|
|
||||||
marked@14.0.0: {}
|
marked@14.0.0: {}
|
||||||
@@ -16983,6 +17040,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.3.1
|
punycode: 2.3.1
|
||||||
|
|
||||||
|
url-join@4.0.1: {}
|
||||||
|
|
||||||
use-context-selector@2.0.0(react@19.2.4)(scheduler@0.25.0):
|
use-context-selector@2.0.0(react@19.2.4)(scheduler@0.25.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.4
|
react: 19.2.4
|
||||||
@@ -17206,7 +17265,7 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
|
|
||||||
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):
|
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):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 2.1.9
|
'@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))
|
'@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))
|
||||||
@@ -17244,7 +17303,7 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
|
|
||||||
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):
|
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):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/chai': 5.2.3
|
'@types/chai': 5.2.3
|
||||||
'@vitest/expect': 3.2.4
|
'@vitest/expect': 3.2.4
|
||||||
@@ -17289,7 +17348,7 @@ snapshots:
|
|||||||
- tsx
|
- tsx
|
||||||
- yaml
|
- yaml
|
||||||
|
|
||||||
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):
|
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):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/chai': 5.2.3
|
'@types/chai': 5.2.3
|
||||||
'@vitest/expect': 3.2.4
|
'@vitest/expect': 3.2.4
|
||||||
|
|||||||
Reference in New Issue
Block a user