Merge branch 'wt4': Add email sending and documentation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
"@simplewebauthn/server": "^13.2.2",
|
"@simplewebauthn/server": "^13.2.2",
|
||||||
"@simplewebauthn/types": "^12.0.0",
|
"@simplewebauthn/types": "^12.0.0",
|
||||||
"kysely": "^0.28.2",
|
"kysely": "^0.28.2",
|
||||||
|
"postmark": "^4.0.5",
|
||||||
"zxcvbn": "^4.4.2"
|
"zxcvbn": "^4.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -27,3 +27,31 @@ export const getAllowedOrigins = (): string[] => {
|
|||||||
"http://localhost:6828",
|
"http://localhost:6828",
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ===== Email Configuration =====
|
||||||
|
|
||||||
|
/** Email sender address */
|
||||||
|
export const EMAIL_FROM = Bun.env.EMAIL_FROM ?? "noreply@reviq.io";
|
||||||
|
|
||||||
|
/** Base URL for generating email links */
|
||||||
|
export const BASE_URL = Bun.env.BASE_URL ?? "http://localhost:6827";
|
||||||
|
|
||||||
|
/** Dev mode: log emails instead of sending (default: true) */
|
||||||
|
export const EMAIL_DEV_MODE = Bun.env.EMAIL_DEV_MODE !== "false";
|
||||||
|
|
||||||
|
/** Postmark API key (required when EMAIL_DEV_MODE is false) */
|
||||||
|
export const POSTMARK_API_KEY = Bun.env.POSTMARK_API_KEY;
|
||||||
|
|
||||||
|
// ===== Token Expiration Times =====
|
||||||
|
|
||||||
|
/** Email verification token expiry in hours */
|
||||||
|
export const EMAIL_VERIFICATION_EXPIRY_HOURS = 24;
|
||||||
|
|
||||||
|
/** Password reset token expiry in hours */
|
||||||
|
export const PASSWORD_RESET_EXPIRY_HOURS = 1;
|
||||||
|
|
||||||
|
/** Login confirmation token expiry in minutes */
|
||||||
|
export const LOGIN_CONFIRMATION_EXPIRY_MINUTES = 15;
|
||||||
|
|
||||||
|
/** Org invite token expiry in days */
|
||||||
|
export const ORG_INVITE_EXPIRY_DAYS = 7;
|
||||||
|
|||||||
@@ -1,13 +1,345 @@
|
|||||||
/**
|
/**
|
||||||
* Email sending utilities (stubbed for now)
|
* Email sending utilities using Postmark
|
||||||
* Will be implemented in Workstream G with actual Postmark integration
|
* Implements Workstream G: Email Service (Backend)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { OrgRole } from "@reviq/db-schema";
|
||||||
|
import { ServerClient } from "postmark";
|
||||||
|
import {
|
||||||
|
BASE_URL,
|
||||||
|
EMAIL_DEV_MODE,
|
||||||
|
EMAIL_FROM,
|
||||||
|
EMAIL_VERIFICATION_EXPIRY_HOURS,
|
||||||
|
LOGIN_CONFIRMATION_EXPIRY_MINUTES,
|
||||||
|
ORG_INVITE_EXPIRY_DAYS,
|
||||||
|
PASSWORD_RESET_EXPIRY_HOURS,
|
||||||
|
POSTMARK_API_KEY,
|
||||||
|
} from "../constants.js";
|
||||||
|
|
||||||
|
// ===== Types =====
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the base URL for email links
|
* Email send result
|
||||||
* Read at function call time to allow environment variable changes
|
|
||||||
*/
|
*/
|
||||||
const getBaseUrl = (): string => Bun.env.APP_URL ?? "http://localhost:6827";
|
export interface EmailResult {
|
||||||
|
success: boolean;
|
||||||
|
messageId?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Postmark Client =====
|
||||||
|
|
||||||
|
let postmarkClient: ServerClient | null = null;
|
||||||
|
|
||||||
|
const getPostmarkClient = (): ServerClient => {
|
||||||
|
if (!postmarkClient) {
|
||||||
|
if (!POSTMARK_API_KEY) {
|
||||||
|
throw new Error(
|
||||||
|
"POSTMARK_API_KEY is required when EMAIL_DEV_MODE is false",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
postmarkClient = new ServerClient(POSTMARK_API_KEY);
|
||||||
|
}
|
||||||
|
return postmarkClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== URL Helpers =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a URL with query parameters using the URL constructor
|
||||||
|
*/
|
||||||
|
const buildUrl = (path: string, params: Record<string, string>): string => {
|
||||||
|
const url = new URL(path, BASE_URL);
|
||||||
|
for (const [key, value] of Object.entries(params)) {
|
||||||
|
url.searchParams.set(key, value);
|
||||||
|
}
|
||||||
|
return url.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== HTML Escaping =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape HTML special characters to prevent XSS
|
||||||
|
*/
|
||||||
|
const escapeHtml = (unsafe: string): string =>
|
||||||
|
unsafe
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
|
||||||
|
// ===== Core Email Function =====
|
||||||
|
|
||||||
|
interface SendEmailParams {
|
||||||
|
to: string;
|
||||||
|
subject: string;
|
||||||
|
htmlBody: string;
|
||||||
|
textBody: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an email via Postmark (or log in dev mode)
|
||||||
|
*/
|
||||||
|
const sendEmail = async (params: SendEmailParams): Promise<EmailResult> => {
|
||||||
|
const { to, subject, htmlBody, textBody } = params;
|
||||||
|
|
||||||
|
// Dev mode: log instead of sending
|
||||||
|
if (EMAIL_DEV_MODE) {
|
||||||
|
console.log("=== DEV MODE EMAIL ===");
|
||||||
|
console.log(`To: ${to}`);
|
||||||
|
console.log(`Subject: ${subject}`);
|
||||||
|
console.log(`Body:\n${textBody}`);
|
||||||
|
console.log("======================");
|
||||||
|
return { success: true, messageId: "dev-mode" };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = getPostmarkClient();
|
||||||
|
const result = await client.sendEmail({
|
||||||
|
From: EMAIL_FROM,
|
||||||
|
To: to,
|
||||||
|
Subject: subject,
|
||||||
|
HtmlBody: htmlBody,
|
||||||
|
TextBody: textBody,
|
||||||
|
});
|
||||||
|
return { success: true, messageId: result.MessageID };
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
console.error(`Failed to send email to ${to}:`, message);
|
||||||
|
return { success: false, error: message };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== Template Helpers =====
|
||||||
|
|
||||||
|
const formatExpiryHours = (hours: number): string => {
|
||||||
|
if (hours === 1) {
|
||||||
|
return "1 hour";
|
||||||
|
}
|
||||||
|
return `${hours} hours`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatExpiryMinutes = (minutes: number): string => {
|
||||||
|
if (minutes === 1) {
|
||||||
|
return "1 minute";
|
||||||
|
}
|
||||||
|
return `${minutes} minutes`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatExpiryDays = (days: number): string => {
|
||||||
|
if (days === 1) {
|
||||||
|
return "1 day";
|
||||||
|
}
|
||||||
|
return `${days} days`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatRoleDisplay = (role: OrgRole): string => {
|
||||||
|
switch (role) {
|
||||||
|
case "owner":
|
||||||
|
return "Owner";
|
||||||
|
case "admin":
|
||||||
|
return "Admin";
|
||||||
|
case "member":
|
||||||
|
return "Member";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the correct article (a/an) for a role
|
||||||
|
*/
|
||||||
|
const getArticleForRole = (role: OrgRole): string => {
|
||||||
|
return role === "owner" || role === "admin" ? "an" : "a";
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== Email Templates =====
|
||||||
|
|
||||||
|
// Common styles
|
||||||
|
const emailStyles = `font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5;`;
|
||||||
|
const containerStyles =
|
||||||
|
"max-width: 600px; margin: 0 auto; background: white; border-radius: 8px; padding: 40px;";
|
||||||
|
const headingStyles = "margin: 0 0 24px; font-size: 24px; color: #1a1a1a;";
|
||||||
|
const paragraphStyles =
|
||||||
|
"margin: 0 0 24px; font-size: 16px; color: #4a4a4a; line-height: 1.5;";
|
||||||
|
const buttonStyles =
|
||||||
|
"display: inline-block; background-color: #0066cc; color: white; padding: 12px 24px; border-radius: 6px; text-decoration: none; font-weight: 500;";
|
||||||
|
const footerStyles = "margin: 24px 0 0; font-size: 14px; color: #6a6a6a;";
|
||||||
|
|
||||||
|
// Verification Email
|
||||||
|
const buildVerificationEmailHtml = (
|
||||||
|
verifyUrl: string,
|
||||||
|
expiresIn: string,
|
||||||
|
): string => `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
</head>
|
||||||
|
<body style="${emailStyles}">
|
||||||
|
<div style="${containerStyles}">
|
||||||
|
<h1 style="${headingStyles}">Verify your email</h1>
|
||||||
|
<p style="${paragraphStyles}">Please verify your email address by clicking the button below:</p>
|
||||||
|
<a href="${verifyUrl}" style="${buttonStyles}">Verify Email</a>
|
||||||
|
<p style="${footerStyles}">This link expires in ${expiresIn}.</p>
|
||||||
|
<p style="${footerStyles}">If you didn't create an account, you can safely ignore this email.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const buildVerificationEmailText = (
|
||||||
|
verifyUrl: string,
|
||||||
|
expiresIn: string,
|
||||||
|
): string =>
|
||||||
|
`Verify your email
|
||||||
|
|
||||||
|
Please verify your email address by clicking the link below:
|
||||||
|
|
||||||
|
${verifyUrl}
|
||||||
|
|
||||||
|
This link expires in ${expiresIn}.
|
||||||
|
|
||||||
|
If you didn't create an account, you can safely ignore this email.
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Password Reset Email
|
||||||
|
const buildPasswordResetEmailHtml = (
|
||||||
|
resetUrl: string,
|
||||||
|
expiresIn: string,
|
||||||
|
): string => `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
</head>
|
||||||
|
<body style="${emailStyles}">
|
||||||
|
<div style="${containerStyles}">
|
||||||
|
<h1 style="${headingStyles}">Reset your password</h1>
|
||||||
|
<p style="${paragraphStyles}">We received a request to reset your password. Click the button below to choose a new password:</p>
|
||||||
|
<a href="${resetUrl}" style="${buttonStyles}">Reset Password</a>
|
||||||
|
<p style="${footerStyles}">This link expires in ${expiresIn}.</p>
|
||||||
|
<p style="${footerStyles}">If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const buildPasswordResetEmailText = (
|
||||||
|
resetUrl: string,
|
||||||
|
expiresIn: string,
|
||||||
|
): string =>
|
||||||
|
`Reset your password
|
||||||
|
|
||||||
|
We received a request to reset your password. Click the link below to choose a new password:
|
||||||
|
|
||||||
|
${resetUrl}
|
||||||
|
|
||||||
|
This link expires in ${expiresIn}.
|
||||||
|
|
||||||
|
If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Login Confirmation Email
|
||||||
|
const buildLoginConfirmationEmailHtml = (
|
||||||
|
confirmUrl: string,
|
||||||
|
expiresIn: string,
|
||||||
|
): string => `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
</head>
|
||||||
|
<body style="${emailStyles}">
|
||||||
|
<div style="${containerStyles}">
|
||||||
|
<h1 style="${headingStyles}">Confirm your login</h1>
|
||||||
|
<p style="${paragraphStyles}">Someone is trying to sign in to your account. If this was you, click the button below to confirm:</p>
|
||||||
|
<a href="${confirmUrl}" style="${buttonStyles}">Confirm Login</a>
|
||||||
|
<p style="${footerStyles}">This link expires in ${expiresIn}.</p>
|
||||||
|
<p style="${footerStyles}">If you didn't try to sign in, you can safely ignore this email. Someone may have entered your email address by mistake.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const buildLoginConfirmationEmailText = (
|
||||||
|
confirmUrl: string,
|
||||||
|
expiresIn: string,
|
||||||
|
): string =>
|
||||||
|
`Confirm your login
|
||||||
|
|
||||||
|
Someone is trying to sign in to your account. If this was you, click the link below to confirm:
|
||||||
|
|
||||||
|
${confirmUrl}
|
||||||
|
|
||||||
|
This link expires in ${expiresIn}.
|
||||||
|
|
||||||
|
If you didn't try to sign in, you can safely ignore this email. Someone may have entered your email address by mistake.
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Org Invite Email
|
||||||
|
const buildOrgInviteEmailHtml = (
|
||||||
|
email: string,
|
||||||
|
orgName: string,
|
||||||
|
inviterName: string,
|
||||||
|
role: OrgRole,
|
||||||
|
inviteUrl: string,
|
||||||
|
expiresIn: string,
|
||||||
|
): string => {
|
||||||
|
const safeOrgName = escapeHtml(orgName);
|
||||||
|
const safeInviterName = escapeHtml(inviterName);
|
||||||
|
const safeEmail = escapeHtml(email);
|
||||||
|
const roleDisplay = formatRoleDisplay(role);
|
||||||
|
const article = getArticleForRole(role);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
</head>
|
||||||
|
<body style="${emailStyles}">
|
||||||
|
<div style="${containerStyles}">
|
||||||
|
<h1 style="${headingStyles}">You've been invited to join ${safeOrgName}</h1>
|
||||||
|
<p style="${paragraphStyles}">${safeInviterName} has invited you to join <strong>${safeOrgName}</strong> as ${article} <strong>${roleDisplay}</strong>.</p>
|
||||||
|
<a href="${inviteUrl}" style="${buttonStyles}">Accept Invitation</a>
|
||||||
|
<p style="${footerStyles}">This invitation expires in ${expiresIn}.</p>
|
||||||
|
<p style="${footerStyles}">This invitation was sent to ${safeEmail}. If you weren't expecting this invitation, you can safely ignore this email.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildOrgInviteEmailText = (
|
||||||
|
email: string,
|
||||||
|
orgName: string,
|
||||||
|
inviterName: string,
|
||||||
|
role: OrgRole,
|
||||||
|
inviteUrl: string,
|
||||||
|
expiresIn: string,
|
||||||
|
): string => {
|
||||||
|
const roleDisplay = formatRoleDisplay(role);
|
||||||
|
const article = getArticleForRole(role);
|
||||||
|
|
||||||
|
return `You've been invited to join ${orgName}
|
||||||
|
|
||||||
|
${inviterName} has invited you to join ${orgName} as ${article} ${roleDisplay}.
|
||||||
|
|
||||||
|
Click the link below to accept the invitation:
|
||||||
|
|
||||||
|
${inviteUrl}
|
||||||
|
|
||||||
|
This invitation expires in ${expiresIn}.
|
||||||
|
|
||||||
|
This invitation was sent to ${email}. If you weren't expecting this invitation, you can safely ignore this email.
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== Email Helpers =====
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send verification email to user
|
* Send verification email to user
|
||||||
@@ -15,10 +347,16 @@ const getBaseUrl = (): string => Bun.env.APP_URL ?? "http://localhost:6827";
|
|||||||
export async function sendVerificationEmail(
|
export async function sendVerificationEmail(
|
||||||
email: string,
|
email: string,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<void> {
|
): Promise<EmailResult> {
|
||||||
const url = `${getBaseUrl()}/auth/verify?token=${token}`;
|
const url = buildUrl("/auth/verify", { token });
|
||||||
console.log(`[EMAIL STUB] Verification email to ${email}`);
|
const expiresIn = formatExpiryHours(EMAIL_VERIFICATION_EXPIRY_HOURS);
|
||||||
console.log(`[EMAIL STUB] Verify link: ${url}`);
|
|
||||||
|
return sendEmail({
|
||||||
|
to: email,
|
||||||
|
subject: "Verify your email address",
|
||||||
|
htmlBody: buildVerificationEmailHtml(url, expiresIn),
|
||||||
|
textBody: buildVerificationEmailText(url, expiresIn),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,10 +365,16 @@ export async function sendVerificationEmail(
|
|||||||
export async function sendLoginConfirmationEmail(
|
export async function sendLoginConfirmationEmail(
|
||||||
email: string,
|
email: string,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<void> {
|
): Promise<EmailResult> {
|
||||||
const url = `${getBaseUrl()}/auth/confirm?token=${token}`;
|
const url = buildUrl("/auth/confirm", { token });
|
||||||
console.log(`[EMAIL STUB] Login confirmation to ${email}`);
|
const expiresIn = formatExpiryMinutes(LOGIN_CONFIRMATION_EXPIRY_MINUTES);
|
||||||
console.log(`[EMAIL STUB] Confirm link: ${url}`);
|
|
||||||
|
return sendEmail({
|
||||||
|
to: email,
|
||||||
|
subject: "Confirm your login",
|
||||||
|
htmlBody: buildLoginConfirmationEmailHtml(url, expiresIn),
|
||||||
|
textBody: buildLoginConfirmationEmailText(url, expiresIn),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,8 +383,49 @@ export async function sendLoginConfirmationEmail(
|
|||||||
export async function sendPasswordResetEmail(
|
export async function sendPasswordResetEmail(
|
||||||
email: string,
|
email: string,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<void> {
|
): Promise<EmailResult> {
|
||||||
const url = `${getBaseUrl()}/auth/reset-password?token=${token}`;
|
const url = buildUrl("/auth/reset-password", { token });
|
||||||
console.log(`[EMAIL STUB] Password reset to ${email}`);
|
const expiresIn = formatExpiryHours(PASSWORD_RESET_EXPIRY_HOURS);
|
||||||
console.log(`[EMAIL STUB] Reset link: ${url}`);
|
|
||||||
|
return sendEmail({
|
||||||
|
to: email,
|
||||||
|
subject: "Reset your password",
|
||||||
|
htmlBody: buildPasswordResetEmailHtml(url, expiresIn),
|
||||||
|
textBody: buildPasswordResetEmailText(url, expiresIn),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send org invite email
|
||||||
|
*/
|
||||||
|
export async function sendOrgInviteEmail(
|
||||||
|
email: string,
|
||||||
|
token: string,
|
||||||
|
orgName: string,
|
||||||
|
inviterName: string,
|
||||||
|
role: OrgRole,
|
||||||
|
): Promise<EmailResult> {
|
||||||
|
const url = buildUrl("/invite/accept", { token });
|
||||||
|
const expiresIn = formatExpiryDays(ORG_INVITE_EXPIRY_DAYS);
|
||||||
|
|
||||||
|
return sendEmail({
|
||||||
|
to: email,
|
||||||
|
subject: `You've been invited to join ${orgName}`,
|
||||||
|
htmlBody: buildOrgInviteEmailHtml(
|
||||||
|
email,
|
||||||
|
orgName,
|
||||||
|
inviterName,
|
||||||
|
role,
|
||||||
|
url,
|
||||||
|
expiresIn,
|
||||||
|
),
|
||||||
|
textBody: buildOrgInviteEmailText(
|
||||||
|
email,
|
||||||
|
orgName,
|
||||||
|
inviterName,
|
||||||
|
role,
|
||||||
|
url,
|
||||||
|
expiresIn,
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
45
bun.lock
45
bun.lock
@@ -23,6 +23,7 @@
|
|||||||
"@simplewebauthn/server": "^13.2.2",
|
"@simplewebauthn/server": "^13.2.2",
|
||||||
"@simplewebauthn/types": "^12.0.0",
|
"@simplewebauthn/types": "^12.0.0",
|
||||||
"kysely": "^0.28.2",
|
"kysely": "^0.28.2",
|
||||||
|
"postmark": "^4.0.5",
|
||||||
"zxcvbn": "^4.4.2",
|
"zxcvbn": "^4.4.2",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -495,6 +496,10 @@
|
|||||||
|
|
||||||
"asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="],
|
"asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="],
|
||||||
|
|
||||||
|
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||||
|
|
||||||
|
"axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="],
|
||||||
|
|
||||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||||
|
|
||||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
@@ -507,6 +512,8 @@
|
|||||||
|
|
||||||
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
||||||
|
|
||||||
|
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||||
|
|
||||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||||
|
|
||||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
@@ -519,6 +526,8 @@
|
|||||||
|
|
||||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||||
|
|
||||||
|
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||||
|
|
||||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||||
|
|
||||||
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
|
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
|
||||||
@@ -533,6 +542,8 @@
|
|||||||
|
|
||||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||||
|
|
||||||
|
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||||
|
|
||||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||||
|
|
||||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
@@ -545,12 +556,22 @@
|
|||||||
|
|
||||||
"dotenv-expand": ["dotenv-expand@12.0.3", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA=="],
|
"dotenv-expand": ["dotenv-expand@12.0.3", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA=="],
|
||||||
|
|
||||||
|
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||||
|
|
||||||
"enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
|
"enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
|
||||||
|
|
||||||
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
|
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
|
||||||
|
|
||||||
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
|
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
|
||||||
|
|
||||||
|
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||||
|
|
||||||
|
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||||
|
|
||||||
|
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||||
|
|
||||||
|
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
|
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
|
||||||
|
|
||||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||||
@@ -597,12 +618,20 @@
|
|||||||
|
|
||||||
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
||||||
|
|
||||||
|
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
|
||||||
|
|
||||||
|
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
|
||||||
|
|
||||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||||
|
|
||||||
|
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||||
|
|
||||||
|
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||||
|
|
||||||
"git-diff": ["git-diff@2.0.6", "", { "dependencies": { "chalk": "^2.3.2", "diff": "^3.5.0", "loglevel": "^1.6.1", "shelljs": "^0.8.1", "shelljs.exec": "^1.1.7" } }, "sha512-/Iu4prUrydE3Pb3lCBMbcSNIf81tgGt0W1ZwknnyF62t3tHmtiJTRj0f+1ZIhp3+Rh0ktz1pJVoa7ZXUCskivA=="],
|
"git-diff": ["git-diff@2.0.6", "", { "dependencies": { "chalk": "^2.3.2", "diff": "^3.5.0", "loglevel": "^1.6.1", "shelljs": "^0.8.1", "shelljs.exec": "^1.1.7" } }, "sha512-/Iu4prUrydE3Pb3lCBMbcSNIf81tgGt0W1ZwknnyF62t3tHmtiJTRj0f+1ZIhp3+Rh0ktz1pJVoa7ZXUCskivA=="],
|
||||||
|
|
||||||
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||||
@@ -611,10 +640,16 @@
|
|||||||
|
|
||||||
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
|
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
|
||||||
|
|
||||||
|
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||||
|
|
||||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||||
|
|
||||||
|
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||||
|
|
||||||
|
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||||
|
|
||||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||||
|
|
||||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||||
@@ -709,8 +744,14 @@
|
|||||||
|
|
||||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
|
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||||
|
|
||||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||||
|
|
||||||
|
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||||
|
|
||||||
|
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||||
|
|
||||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||||
@@ -781,8 +822,12 @@
|
|||||||
|
|
||||||
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
|
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
|
||||||
|
|
||||||
|
"postmark": ["postmark@4.0.5", "", { "dependencies": { "axios": "^1.7.4" } }, "sha512-nerZdd3TwOH4CgGboZnlUM/q7oZk0EqpZgJL+Y3Nup8kHeaukxouQ6JcFF3EJEijc4QbuNv1TefGhboAKtf/SQ=="],
|
||||||
|
|
||||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||||
|
|
||||||
|
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
"pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="],
|
"pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="],
|
||||||
|
|||||||
@@ -2260,12 +2260,12 @@ _Depends on: D1 (auth middleware)_
|
|||||||
_Depends on: C2_
|
_Depends on: C2_
|
||||||
_Can run parallel to D, E, F_
|
_Can run parallel to D, E, F_
|
||||||
|
|
||||||
- [ ] **G1**: Set up Postmark client with env config
|
- [x] **G1**: Set up Postmark client with env config
|
||||||
- [ ] **G2**: Create email templates (verification, password reset, login confirmation, org invite)
|
- [x] **G2**: Create email templates (verification, password reset, login confirmation, org invite)
|
||||||
- [ ] **G3**: Implement `sendVerificationEmail()` helper
|
- [x] **G3**: Implement `sendVerificationEmail()` helper
|
||||||
- [ ] **G4**: Implement `sendPasswordResetEmail()` helper
|
- [x] **G4**: Implement `sendPasswordResetEmail()` helper
|
||||||
- [ ] **G5**: Implement `sendLoginConfirmationEmail()` helper
|
- [x] **G5**: Implement `sendLoginConfirmationEmail()` helper
|
||||||
- [ ] **G6**: Implement `sendOrgInviteEmail()` helper
|
- [x] **G6**: Implement `sendOrgInviteEmail()` helper
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user