diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 22fbe13..0ff5ab6 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -3,7 +3,8 @@
"allow": [
"Bash(grep:*)",
"Bash(npm run build:*)",
- "mcp__ide__getDiagnostics"
+ "mcp__ide__getDiagnostics",
+ "Bash(gh pr checks:*)"
],
"deny": [],
"ask": []
diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index 2d215be..59111c4 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -415,7 +415,27 @@
"errorLoad": "Failed to load beta invites. Please try again.",
"confirmDelete": "Are you sure you want to delete this beta invite?",
"confirmApprove": "Are you sure you want to approve this beta invite? The user will be able to sign up.",
- "mustBeLoggedIn": "You must be logged in to approve beta invites."
+ "mustBeLoggedIn": "You must be logged in to approve beta invites.",
+ "marketingEmail": {
+ "title": "Send Marketing Email",
+ "toggleButton": "đ§ Send Marketing Email",
+ "subjectLabel": "Email Subject",
+ "subjectPlaceholder": "Enter email subject...",
+ "htmlLabel": "Email Content (HTML)",
+ "htmlPlaceholder": "Enter email HTML content...",
+ "recipientFilterLabel": "Send To",
+ "recipientFilterAll": "All Beta Users",
+ "recipientFilterApproved": "All Approved Users",
+ "recipientFilterApprovedNotUsed": "Approved (Not Signed Up Yet)",
+ "recipientFilterUsed": "Users Who Signed Up",
+ "recipientFilterPending": "Pending Approval",
+ "sendButton": "Send Email",
+ "sending": "Sending...",
+ "successSent": "Email sent successfully to {count} recipients!",
+ "errorSend": "Failed to send email. Please try again.",
+ "confirmSend": "Are you sure you want to send this email to {count} recipients?",
+ "noRecipientsError": "No recipients found for the selected filter."
+ }
}
},
"vatternrundan": {
diff --git a/frontend/messages/sv.json b/frontend/messages/sv.json
index 355444d..0aef288 100644
--- a/frontend/messages/sv.json
+++ b/frontend/messages/sv.json
@@ -414,7 +414,27 @@
"errorLoad": "Kunde inte ladda beta-inbjudningar. Försök igen.",
"confirmDelete": "Ăr du sĂ€ker pĂ„ att du vill ta bort denna beta-inbjudan?",
"confirmApprove": "Ăr du sĂ€ker pĂ„ att du vill godkĂ€nna denna beta-inbjudan? AnvĂ€ndaren kommer att kunna registrera sig.",
- "mustBeLoggedIn": "Du mÄste vara inloggad för att godkÀnna beta-inbjudningar."
+ "mustBeLoggedIn": "Du mÄste vara inloggad för att godkÀnna beta-inbjudningar.",
+ "marketingEmail": {
+ "title": "Skicka marknadsförings-e-post",
+ "toggleButton": "đ§ Skicka marknadsförings-e-post",
+ "subjectLabel": "E-postÀmne",
+ "subjectPlaceholder": "Ange e-postÀmne...",
+ "htmlLabel": "E-postinnehÄll (HTML)",
+ "htmlPlaceholder": "Ange HTML-innehÄll för e-post...",
+ "recipientFilterLabel": "Skicka till",
+ "recipientFilterAll": "Alla beta-anvÀndare",
+ "recipientFilterApproved": "Alla godkÀnda anvÀndare",
+ "recipientFilterApprovedNotUsed": "GodkÀnda (inte registrerat sig Àn)",
+ "recipientFilterUsed": "AnvÀndare som registrerat sig",
+ "recipientFilterPending": "VÀntar pÄ godkÀnnande",
+ "sendButton": "Skicka e-post",
+ "sending": "Skickar...",
+ "successSent": "E-post skickades till {count} mottagare!",
+ "errorSend": "Kunde inte skicka e-post. Försök igen.",
+ "confirmSend": "Ăr du sĂ€ker pĂ„ att du vill skicka detta e-postmeddelande till {count} mottagare?",
+ "noRecipientsError": "Inga mottagare hittades för det valda filtret."
+ }
}
},
"vatternrundan": {
diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts
index 9edff1c..c4b7818 100644
--- a/frontend/next-env.d.ts
+++ b/frontend/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/types/routes.d.ts";
+import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/frontend/package.json b/frontend/package.json
index 58f0f66..4f887b2 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,7 +12,9 @@
"dependencies": {
"@heroicons/react": "^2.2.0",
"@marsidev/react-turnstile": "^1.3.1",
+ "@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.39.0",
+ "isomorphic-dompurify": "^2.35.0",
"leaflet": "^1.9.4",
"next": "^16.0.5",
"next-intl": "^4.5.6",
diff --git a/frontend/src/app/admin/beta-invites/page.tsx b/frontend/src/app/admin/beta-invites/page.tsx
index e366e95..70b0da8 100644
--- a/frontend/src/app/admin/beta-invites/page.tsx
+++ b/frontend/src/app/admin/beta-invites/page.tsx
@@ -11,6 +11,7 @@ import {
} from '@/lib/admin';
import { useAuth } from '@/components/AuthProvider';
import { formatDateTime } from '@/lib/dateFormat';
+import { supabase } from '@/lib/supabase';
export default function BetaInvitesPage() {
const { user } = useAuth();
@@ -27,6 +28,13 @@ export default function BetaInvitesPage() {
const [invitedBy, setInvitedBy] = useState('');
const [notes, setNotes] = useState('');
+ // Marketing email state
+ const [showMarketingForm, setShowMarketingForm] = useState(false);
+ const [emailSubject, setEmailSubject] = useState('');
+ const [emailHtml, setEmailHtml] = useState('');
+ const [recipientFilter, setRecipientFilter] = useState('all');
+ const [sendingEmail, setSendingEmail] = useState(false);
+
useEffect(() => {
loadInvites();
}, []);
@@ -102,6 +110,94 @@ export default function BetaInvitesPage() {
}
}
+ async function handleSendMarketingEmail(e: React.FormEvent) {
+ e.preventDefault();
+
+ if (!user?.id) {
+ setError(t('mustBeLoggedIn'));
+ return;
+ }
+
+ if (!emailSubject || !emailHtml) {
+ setError(t('marketingEmail.errorSend'));
+ return;
+ }
+
+ // Calculate recipient count for confirmation
+ let recipientCount = 0;
+ switch (recipientFilter) {
+ case 'all':
+ recipientCount = invites.length;
+ break;
+ case 'approved':
+ recipientCount = invites.filter((i) => i.approved).length;
+ break;
+ case 'approved_not_used':
+ recipientCount = invites.filter((i) => i.approved && !i.used).length;
+ break;
+ case 'used':
+ recipientCount = invites.filter((i) => i.used).length;
+ break;
+ case 'pending':
+ recipientCount = invites.filter((i) => !i.approved && !i.used).length;
+ break;
+ }
+
+ if (recipientCount === 0) {
+ setError(t('marketingEmail.noRecipientsError'));
+ return;
+ }
+
+ const confirmMessage = t('marketingEmail.confirmSend', { count: recipientCount });
+ if (!confirm(confirmMessage)) {
+ return;
+ }
+
+ setSendingEmail(true);
+ setError(null);
+ setSuccess(null);
+
+ try {
+ // Get the current session token
+ const { data: { session } } = await supabase.auth.getSession();
+ if (!session) {
+ setError('You must be logged in to send marketing emails');
+ setSendingEmail(false);
+ return;
+ }
+
+ const response = await fetch('/api/send-marketing-email', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${session.access_token}`,
+ },
+ body: JSON.stringify({
+ subject: emailSubject,
+ html: emailHtml,
+ recipientFilter,
+ }),
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ throw new Error(data.error || t('marketingEmail.errorSend'));
+ }
+
+ setSuccess(t('marketingEmail.successSent', { count: data.recipientCount }));
+ setEmailSubject('');
+ setEmailHtml('');
+ setRecipientFilter('all');
+ setShowMarketingForm(false);
+ } catch (err: any) {
+ setError(err.message || t('marketingEmail.errorSend'));
+ console.error('Error sending marketing email:', err);
+ } finally {
+ setSendingEmail(false);
+ }
+ }
+
return (
@@ -111,12 +207,26 @@ export default function BetaInvitesPage() {
{t('subtitle')}
-
+
+
+
+
{/* Success/Error Messages */}
@@ -243,6 +353,102 @@ export default function BetaInvitesPage() {
)}
+ {/* Marketing Email Form */}
+ {showMarketingForm && (
+
+
+ {t('marketingEmail.title')}
+
+
+
+ )}
+
{/* Invites Table */}
{loading ? (
diff --git a/frontend/src/app/api/send-marketing-email/route.ts b/frontend/src/app/api/send-marketing-email/route.ts
new file mode 100644
index 0000000..b369cdd
--- /dev/null
+++ b/frontend/src/app/api/send-marketing-email/route.ts
@@ -0,0 +1,178 @@
+import { Resend } from 'resend';
+import { NextRequest, NextResponse } from 'next/server';
+import { createClient } from '@supabase/supabase-js';
+import { defaultLocale, Locale, locales } from '@/i18n/config';
+import DOMPurify from 'isomorphic-dompurify';
+
+const resend = new Resend(process.env.RESEND_API_KEY);
+
+export async function POST(request: NextRequest) {
+ try {
+ const { subject, html, recipientFilter } = await request.json();
+
+ // Validate input
+ if (!subject || !html) {
+ return NextResponse.json(
+ { error: 'Subject and HTML content are required' },
+ { status: 400 }
+ );
+ }
+
+ // Get auth token from request headers
+ const authHeader = request.headers.get('authorization');
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
+ console.error('â No authorization header found');
+ return NextResponse.json({ error: 'Unauthorized', debug: 'Missing auth token' }, { status: 401 });
+ }
+
+ const token = authHeader.replace('Bearer ', '');
+
+ // Create Supabase client and verify the token
+ const supabase = createClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+ );
+
+ // Get authenticated user using the token
+ const { data: { user }, error: authError } = await supabase.auth.getUser(token);
+
+ if (authError) {
+ console.error('â Auth error:', authError);
+ return NextResponse.json({ error: 'Unauthorized', debug: 'Invalid token' }, { status: 401 });
+ }
+
+ if (!user) {
+ console.error('â No user found with token');
+ return NextResponse.json({ error: 'Unauthorized', debug: 'No user found' }, { status: 401 });
+ }
+
+ console.log('â
User authenticated:', user.id);
+
+ // Verify the authenticated user is an admin using service role
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
+ const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
+ const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey);
+
+ const { data: adminData, error: adminError } = await supabaseAdmin
+ .from('admin_users')
+ .select('user_id')
+ .eq('user_id', user.id)
+ .single();
+
+ if (adminError) {
+ console.error('â Admin check error:', adminError);
+ return NextResponse.json({ error: 'Forbidden', debug: 'Admin check failed' }, { status: 403 });
+ }
+
+ if (!adminData) {
+ console.error('â User is not an admin:', user.id);
+ return NextResponse.json({ error: 'Forbidden', debug: 'Not an admin' }, { status: 403 });
+ }
+
+ console.log('â
Admin verified:', user.id);
+
+ // Sanitize HTML content to prevent XSS
+ const sanitizedHtml = DOMPurify.sanitize(html, {
+ ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'a', 'img', 'div', 'span', 'table', 'tr', 'td', 'th', 'thead', 'tbody'],
+ ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'style', 'target'],
+ });
+
+ // Get locale preference and app name
+ const locale = defaultLocale;
+ const validLocale = locales.includes(locale) ? locale : defaultLocale;
+ const messages = (await import(`../../../../messages/${validLocale}.json`)).default;
+ const appName = messages.common.appName;
+
+ const fromEmail = process.env.EMAIL_FROM || 'onboarding@resend.dev';
+ const resendApiKey = process.env.RESEND_API_KEY;
+
+ if (!resendApiKey) {
+ console.error('â RESEND_API_KEY is not configured!');
+ return NextResponse.json(
+ { error: 'Email service not configured' },
+ { status: 500 }
+ );
+ }
+
+ // Fetch beta invites based on filter using admin client
+ let query = supabaseAdmin.from('beta_invites').select('email');
+
+ switch (recipientFilter) {
+ case 'all':
+ // No filter - get all
+ break;
+ case 'approved':
+ query = query.eq('approved', true);
+ break;
+ case 'approved_not_used':
+ query = query.eq('approved', true).eq('used', false);
+ break;
+ case 'used':
+ query = query.eq('used', true);
+ break;
+ case 'pending':
+ query = query.eq('approved', false).eq('used', false);
+ break;
+ default:
+ return NextResponse.json(
+ { error: 'Invalid recipient filter' },
+ { status: 400 }
+ );
+ }
+
+ const { data: invites, error: invitesError } = await query;
+
+ if (invitesError) {
+ console.error('â Error fetching invites:', invitesError);
+ return NextResponse.json(
+ { error: 'Failed to fetch recipients' },
+ { status: 500 }
+ );
+ }
+
+ if (!invites || invites.length === 0) {
+ console.log('â ïž No recipients found for filter:', recipientFilter);
+ return NextResponse.json(
+ { error: 'No recipients found matching the selected filter' },
+ { status: 400 }
+ );
+ }
+
+ const recipients = invites.map((invite) => invite.email);
+ console.log(`đ§ Sending marketing email to ${recipients.length} recipients by admin ${user.id}`);
+
+ // Send emails using BCC to respect privacy
+ const emailResult = await resend.emails.send({
+ from: `${appName} <${fromEmail}>`,
+ to: fromEmail, // Send to self
+ bcc: recipients, // Use BCC for privacy
+ subject: subject,
+ html: sanitizedHtml, // Use sanitized HTML
+ });
+
+ if (emailResult.error) {
+ console.error('â Error sending marketing email:', emailResult.error);
+ return NextResponse.json(
+ { error: 'Failed to send email' },
+ { status: 500 }
+ );
+ }
+
+ console.log(`â
Marketing email sent successfully to ${recipients.length} recipients`, {
+ emailId: emailResult.data?.id,
+ adminUserId: user.id,
+ });
+
+ return NextResponse.json({
+ success: true,
+ message: `Email sent to ${recipients.length} recipients`,
+ recipientCount: recipients.length,
+ });
+ } catch (error) {
+ console.error('â Error in send-marketing-email:', error);
+ return NextResponse.json(
+ { error: 'Internal server error' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index cc0521d..0966189 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -54,7 +54,9 @@
"dependencies": {
"@heroicons/react": "^2.2.0",
"@marsidev/react-turnstile": "^1.3.1",
+ "@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.39.0",
+ "isomorphic-dompurify": "^2.35.0",
"leaflet": "^1.9.4",
"next": "^16.0.5",
"next-intl": "^4.5.6",
@@ -241,6 +243,12 @@
"node": ">= 4"
}
},
+ "node_modules/@acemir/cssom": {
+ "version": "0.9.30",
+ "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.30.tgz",
+ "integrity": "sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==",
+ "license": "MIT"
+ },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -254,6 +262,56 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz",
+ "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-color-parser": "^3.1.0",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "lru-cache": "^11.2.4"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+ "version": "11.2.4",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
+ "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "6.7.6",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz",
+ "integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==",
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/nwsapi": "^2.3.9",
+ "bidi-js": "^1.0.3",
+ "css-tree": "^3.1.0",
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.2.4"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
+ "version": "11.2.4",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
+ "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@asamuzakjp/nwsapi": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
+ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
+ "license": "MIT"
+ },
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -524,6 +582,135 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.0.22",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.22.tgz",
+ "integrity": "sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@emnapi/core": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
@@ -1166,6 +1353,23 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@exodus/bytes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.8.0.tgz",
+ "integrity": "sha512-8JPn18Bcp8Uo1T82gR8lh2guEOa5KKU/IEKvvdp0sgmi7coPBWf1Doi1EXsGZb2ehc8ym/StJCjffYV+ne7sXQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@exodus/crypto": "^1.0.0-rc.4"
+ },
+ "peerDependenciesMeta": {
+ "@exodus/crypto": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@formatjs/ecma402-abstract": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz",
@@ -2418,6 +2622,31 @@
"node": ">=20.0.0"
}
},
+ "node_modules/@supabase/ssr": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.8.0.tgz",
+ "integrity": "sha512-/PKk8kNFSs8QvvJ2vOww1mF5/c5W8y42duYtXvkOSe+yZKRgTTZywYG2l41pjhNomqESZCpZtXuWmYjFRMV+dw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.2"
+ },
+ "peerDependencies": {
+ "@supabase/supabase-js": "^2.76.1"
+ }
+ },
+ "node_modules/@supabase/ssr/node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/@supabase/storage-js": {
"version": "2.89.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.89.0.tgz",
@@ -2854,6 +3083,13 @@
"@types/node": "*"
}
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@@ -3768,6 +4004,15 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -4142,6 +4387,15 @@
"baseline-browser-mapping": "dist/cli.js"
}
},
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -4576,6 +4830,19 @@
"node": ">= 8"
}
},
+ "node_modules/css-tree": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.12.2",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -4589,6 +4856,30 @@
"node": ">=4"
}
},
+ "node_modules/cssstyle": {
+ "version": "5.3.6",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.6.tgz",
+ "integrity": "sha512-legscpSpgSAeGEe0TNcai97DKt9Vd9AsAdOL7Uoetb52Ar/8eJm3LIa39qpv8wWzLFlNG4vVvppQM+teaMPj3A==",
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^4.1.1",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.21",
+ "css-tree": "^3.1.0",
+ "lru-cache": "^11.2.4"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/cssstyle/node_modules/lru-cache": {
+ "version": "11.2.4",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
+ "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -4603,6 +4894,19 @@
"dev": true,
"license": "BSD-2-Clause"
},
+ "node_modules/data-urls": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz",
+ "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/data-view-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -4678,7 +4982,6 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -4812,6 +5115,15 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dompurify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz",
+ "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
@@ -4867,6 +5179,18 @@
"node": ">= 0.8"
}
},
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/es-abstract": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
@@ -6363,6 +6687,18 @@
"hermes-estree": "0.25.1"
}
},
+ "node_modules/html-encoding-sniffer": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
+ "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@exodus/bytes": "^1.6.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
@@ -6383,6 +6719,32 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/iceberg-js": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
@@ -6782,6 +7144,12 @@
"node": ">=8"
}
},
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "license": "MIT"
+ },
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -6941,6 +7309,19 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/isomorphic-dompurify": {
+ "version": "2.35.0",
+ "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.35.0.tgz",
+ "integrity": "sha512-a9+LQqylQCU8f1zmsYmg2tfrbdY2YS/Hc+xntcq/mDI2MY3Q108nq8K23BWDIg6YGC5JsUMC15fj2ZMqCzt/+A==",
+ "license": "MIT",
+ "dependencies": {
+ "dompurify": "^3.3.1",
+ "jsdom": "^27.4.0"
+ },
+ "engines": {
+ "node": ">=20.19.5"
+ }
+ },
"node_modules/iterator.prototype": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
@@ -6988,6 +7369,45 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/jsdom": {
+ "version": "27.4.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz",
+ "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@acemir/cssom": "^0.9.28",
+ "@asamuzakjp/dom-selector": "^6.7.6",
+ "@exodus/bytes": "^1.6.0",
+ "cssstyle": "^5.3.4",
+ "data-urls": "^6.0.0",
+ "decimal.js": "^10.6.0",
+ "html-encoding-sniffer": "^6.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "parse5": "^8.0.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^6.0.0",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^8.0.0",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.1.0",
+ "ws": "^8.18.3",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -7182,6 +7602,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/mdn-data": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "license": "CC0-1.0"
+ },
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -7789,6 +8215,18 @@
"node": ">=6"
}
},
+ "node_modules/parse5": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
+ "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -8123,7 +8561,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -8318,6 +8755,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -8535,6 +8981,18 @@
"dev": true,
"license": "BlueOak-1.0.0"
},
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@@ -9150,6 +9608,12 @@
"undici-types": "~6.21.0"
}
},
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "license": "MIT"
+ },
"node_modules/tailwindcss": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
@@ -9296,6 +9760,24 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/tldts": {
+ "version": "7.0.19",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz",
+ "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==",
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^7.0.19"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "7.0.19",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz",
+ "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==",
+ "license": "MIT"
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -9317,6 +9799,30 @@
"node": ">=0.6"
}
},
+ "node_modules/tough-cookie": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
+ "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^7.0.5"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
@@ -9780,6 +10286,49 @@
"node": ">= 0.8"
}
},
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
+ "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
+ "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -9941,6 +10490,15 @@
}
}
},
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/xml2js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
@@ -9965,6 +10523,12 @@
"node": ">=4.0"
}
},
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "license": "MIT"
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",