fix(emails): bump @react-email/render to ^2.0.6 to fix empty transactional email bodies (#23323)
## Problem Fixes #23307. Every transactional email (workspace invite, password reset, email verification, etc.) is delivered with an **empty body** — no title, text, or CTA. ## Root cause `twenty-server` pins `@react-email/render` directly at `^1.2.3`: ```jsonc // packages/twenty-server/package.json "@react-email/render": "^1.2.3", ``` In 1.2.3, `render()` reads `renderToReadableStream` **before** the email template's async Suspense boundary (i18n/locale load) has resolved. The result is the Suspense fallback marker instead of the real markup: ```html <!DOCTYPE html ...><!--$!--><template></template><!--/$--> ``` This was fixed upstream in `@react-email/render@2.0.6` (*"await stream.allReady before reading renderToReadableStream output"*). `twenty-emails` already resolves a 2.x render via `react-email@6.5.0`, so the server's direct pin was simply stale — the two were out of sync. ## Fix Bump the direct pin to `^2.0.6` (resolves to `2.1.0`) and regenerate the lockfile. The server's `render()` imports now use the fixed 2.x. > Note: a `1.2.3` entry remains in `yarn.lock` — it is an internal transitive > pin of `@react-email/components@0.5.3`, not the server render path, so it is > expected and harmless. ## Verification Rendering `SendInviteLinkEmail` through the real `render()` (Node 24) now returns full markup (5.7 kB) with no Suspense marker and the resolved invite link + workspace content, instead of the empty fallback. A jest unit test was intentionally not added: `@react-email/render` 2.x uses a dynamic import that jest's CJS runtime rejects ("A dynamic import callback was invoked without --experimental-vm-modules") — which is exactly why the existing email specs mock `render`. The fix was verified with a standalone Node script. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23323?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
committed by
GitHub
parent
44ed0d5498
commit
710d4da4b1
@@ -11,7 +11,6 @@
|
||||
"dependencies": {
|
||||
"@lingui/core": "^5.9.5",
|
||||
"@lingui/react": "^5.9.5",
|
||||
"@react-email/components": "^0.5.3",
|
||||
"twenty-shared": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -22,13 +21,13 @@
|
||||
"@lingui/cli": "^5.9.5",
|
||||
"@lingui/swc-plugin": "^5.11.0",
|
||||
"@lingui/vite-plugin": "^5.9.5",
|
||||
"@react-email/ui": "6.5.0",
|
||||
"@react-email/ui": "^6.9.0",
|
||||
"@tiptap/core": "^3.4.2",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20260116.1",
|
||||
"@vitejs/plugin-react-swc": "^4.3.1",
|
||||
"react-email": "6.5.0",
|
||||
"react-email": "^6.9.0",
|
||||
"tsc-alias": "^1.8.16",
|
||||
"vite-plugin-dts": "^4.5.4"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type JSX } from 'react';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { Container, Html } from '@react-email/components';
|
||||
import { Container, Html } from 'react-email';
|
||||
|
||||
import { BaseHead } from 'src/components/BaseHead';
|
||||
import { Footer } from 'src/components/Footer';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Font, Head } from '@react-email/components';
|
||||
import { Font, Head } from 'react-email';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type JSX } from 'react';
|
||||
import { Button } from '@react-email/components';
|
||||
import { Button } from 'react-email';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type I18n } from '@lingui/core';
|
||||
import { Column, Container, Row } from '@react-email/components';
|
||||
import { Column, Container, Row } from 'react-email';
|
||||
import { Link } from 'src/components/Link';
|
||||
import { ShadowText } from 'src/components/ShadowText';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column, Container, Row } from '@react-email/components';
|
||||
import { Column, Container, Row } from 'react-email';
|
||||
import React, { type JSX } from 'react';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Text } from '@react-email/components';
|
||||
import { Text } from 'react-email';
|
||||
import { type JSX } from 'react';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type JSX } from 'react';
|
||||
import { Link as EmailLink } from '@react-email/components';
|
||||
import { Link as EmailLink } from 'react-email';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Img } from '@react-email/components';
|
||||
import { Img } from 'react-email';
|
||||
|
||||
const logoStyle = {
|
||||
marginBottom: '40px',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type JSX } from 'react';
|
||||
import { Text } from '@react-email/components';
|
||||
import { Text } from 'react-email';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type JSX } from 'react';
|
||||
import { Text } from '@react-email/components';
|
||||
import { Text } from 'react-email';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type JSX } from 'react';
|
||||
import { Heading } from '@react-email/components';
|
||||
import { Heading } from 'react-email';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type JSX } from 'react';
|
||||
import { Heading } from '@react-email/components';
|
||||
import { Heading } from 'react-email';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Trans } from '@lingui/react';
|
||||
import { Img } from '@react-email/components';
|
||||
import { Img } from 'react-email';
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
import { BaseEmail } from 'src/components/BaseEmail';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Trans } from '@lingui/react';
|
||||
import { Img } from '@react-email/components';
|
||||
import { Img } from 'react-email';
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
import { BaseEmail } from 'src/components/BaseEmail';
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export type { JSONContent } from '@tiptap/core';
|
||||
// Consumers must render through this package so there is a single resolved
|
||||
// react-email version: a second, independently pinned render can silently drift
|
||||
// onto an incompatible major and ship empty email bodies.
|
||||
export { render, toPlainText } from 'react-email';
|
||||
export * from './utils/render-email';
|
||||
export * from './emails/billing-subscription-renewing.email';
|
||||
export * from './emails/billing-trial-converting.email';
|
||||
export * from './emails/billing-trial-ending.email';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Body, Head, Html } from '@react-email/components';
|
||||
import { Body, Head, Html } from 'react-email';
|
||||
import { type JSONContent } from '@tiptap/core';
|
||||
import { mappedNodeContent } from 'src/utils/email-renderer/renderers/render-node';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Heading } from '@react-email/components';
|
||||
import { Heading } from 'react-email';
|
||||
import { type JSONContent } from '@tiptap/core';
|
||||
import { type ReactNode } from 'react';
|
||||
import { mappedNodeContent } from 'src/utils/email-renderer/renderers/render-node';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column, Row } from '@react-email/components';
|
||||
import { Column, Row } from 'react-email';
|
||||
import { type JSONContent } from '@tiptap/core';
|
||||
import { type ReactNode } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Text } from '@react-email/components';
|
||||
import { Text } from 'react-email';
|
||||
import { type JSONContent } from '@tiptap/core';
|
||||
import { type ReactNode } from 'react';
|
||||
import { mappedNodeContent } from 'src/utils/email-renderer/renderers/render-node';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { render } from 'react-email';
|
||||
|
||||
// React Email renders the tree inside its own Suspense boundary without an
|
||||
// onError, so anything that throws while rendering a template is swallowed and
|
||||
// the boundary ships as `<!--$!--><template></template><!--/$-->`. The email is
|
||||
// then delivered with a blank body and nothing is logged (#23307). Failing the
|
||||
// send is the lesser evil: a blank email is useless to the recipient and the
|
||||
// thrown error surfaces in the job logs.
|
||||
const ERRORED_SUSPENSE_BOUNDARY_MARKER = '<!--$!-->';
|
||||
|
||||
export class EmailRenderError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'EmailRenderError';
|
||||
}
|
||||
}
|
||||
|
||||
export const renderEmail = async (
|
||||
...renderArgs: Parameters<typeof render>
|
||||
): Promise<string> => {
|
||||
const output = await render(...renderArgs);
|
||||
|
||||
if (output.trim().length === 0) {
|
||||
throw new EmailRenderError('Email template rendered an empty body');
|
||||
}
|
||||
|
||||
if (output.includes(ERRORED_SUSPENSE_BOUNDARY_MARKER)) {
|
||||
throw new EmailRenderError(
|
||||
'Email template threw while rendering, its body would have been empty',
|
||||
);
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
@@ -58,7 +58,6 @@
|
||||
"@nivo/line": "^0.99.0",
|
||||
"@nivo/pie": "^0.99.0",
|
||||
"@nivo/radial-bar": "^0.99.0",
|
||||
"@react-email/components": "^0.5.3",
|
||||
"@react-pdf/renderer": "^4.1.6",
|
||||
"@scalar/api-reference-react": "^0.9.42",
|
||||
"@sentry/react": "^10.51.0",
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { SettingsAccountsEventVisibilitySettingsCard } from '@/settings/accounts
|
||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Section } from '@react-email/components';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { IconUserPlus } from 'twenty-ui/icon';
|
||||
import { H2Title } from 'twenty-ui/typography';
|
||||
import { Card } from 'twenty-ui/surfaces';
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { DATE_TIME_SETTINGS_PREVIEW_DATE } from '@/localization/constants/DateTi
|
||||
import { SettingsAccountsCalendarDisplaySettings } from '@/settings/accounts/components/SettingsAccountsCalendarDisplaySettings';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Section } from '@react-email/components';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { addMinutes, endOfDay, min, startOfDay } from 'date-fns';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { H2Title } from 'twenty-ui/typography';
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFiel
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Section } from '@react-email/components';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useContext, useState } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
"@ptc-org/nestjs-query-core": "^9.4.0",
|
||||
"@ptc-org/nestjs-query-graphql": "patch:@ptc-org/nestjs-query-graphql@npm%3A9.4.0#~/.yarn/patches/@ptc-org-nestjs-query-graphql-npm-9.4.0-8e6f7894e1.patch",
|
||||
"@ptc-org/nestjs-query-typeorm": "patch:@ptc-org/nestjs-query-typeorm@npm%3A9.4.0#~/.yarn/patches/@ptc-org-nestjs-query-typeorm-npm-9.4.0-ca3414967e.patch",
|
||||
"@react-email/render": "^1.2.3",
|
||||
"@react-pdf/renderer": "^4.1.6",
|
||||
"@sentry/nestjs": "^10.59.0",
|
||||
"@sentry/node": "^10.59.0",
|
||||
|
||||
+3
-4
@@ -2,9 +2,8 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { render } from '@react-email/render';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { ServerAdminAccessChangedEmail } from 'twenty-emails';
|
||||
import { ServerAdminAccessChangedEmail, renderEmail } from 'twenty-emails';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
@@ -249,8 +248,8 @@ export class AdminPanelServerAdminService {
|
||||
canImpersonate: targetUser.canImpersonate,
|
||||
locale,
|
||||
});
|
||||
const html = await render(emailTemplate, { pretty: true });
|
||||
const text = await render(emailTemplate, { plainText: true });
|
||||
const html = await renderEmail(emailTemplate, { pretty: true });
|
||||
const text = await renderEmail(emailTemplate, { plainText: true });
|
||||
|
||||
const i18n = this.i18nService.getI18nInstance(locale);
|
||||
const subject = i18n._(msg`Server administrator access changed`);
|
||||
|
||||
+3
-4
@@ -2,8 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { render } from '@react-email/render';
|
||||
import { SendApprovedAccessDomainValidation } from 'twenty-emails';
|
||||
import { SendApprovedAccessDomainValidation, renderEmail } from 'twenty-emails';
|
||||
import { FileFolder, SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
@@ -116,8 +115,8 @@ export class ApprovedAccessDomainService {
|
||||
serverUrl: this.twentyConfigService.get('SERVER_URL'),
|
||||
locale: sender.locale,
|
||||
});
|
||||
const html = await render(emailTemplate);
|
||||
const text = await render(emailTemplate, {
|
||||
const html = await renderEmail(emailTemplate);
|
||||
const text = await renderEmail(emailTemplate, {
|
||||
plainText: true,
|
||||
});
|
||||
|
||||
|
||||
+6
-3
@@ -23,9 +23,12 @@ import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-membe
|
||||
import { FileUrlService } from 'src/engine/core-modules/file/file-url/file-url.service';
|
||||
import { ApprovedAccessDomainService } from './approved-access-domain.service';
|
||||
|
||||
// To avoid dynamic import issues in Jest
|
||||
jest.mock('@react-email/render', () => ({
|
||||
render: jest.fn().mockImplementation(async (template, options) => {
|
||||
// render() resolves through a streaming scheduler that never advances under the
|
||||
// globally enabled fake timers; the real render path is covered by
|
||||
// email-templates-rendering.spec.ts.
|
||||
jest.mock('twenty-emails', () => ({
|
||||
...jest.requireActual('twenty-emails'),
|
||||
renderEmail: jest.fn().mockImplementation(async (_template, options) => {
|
||||
if (options?.plainText) {
|
||||
return 'Plain Text Email';
|
||||
}
|
||||
|
||||
@@ -4,10 +4,9 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import crypto, { randomUUID } from 'node:crypto';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { render } from '@react-email/render';
|
||||
import { addMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
import { PasswordUpdateNotifyEmail } from 'twenty-emails';
|
||||
import { PasswordUpdateNotifyEmail, renderEmail } from 'twenty-emails';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { AppPath, ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
@@ -721,8 +720,8 @@ export class AuthService {
|
||||
locale: firstUserWorkspace.locale,
|
||||
});
|
||||
|
||||
const html = await render(emailTemplate, { pretty: true });
|
||||
const text = await render(emailTemplate, { plainText: true });
|
||||
const html = await renderEmail(emailTemplate, { pretty: true });
|
||||
const text = await renderEmail(emailTemplate, { plainText: true });
|
||||
|
||||
const passwordChangedMsg = msg`Your Password Has Been Successfully Changed`;
|
||||
const i18n = this.i18nService.getI18nInstance(firstUserWorkspace.locale);
|
||||
|
||||
+6
-2
@@ -22,8 +22,12 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
|
||||
import { ResetPasswordService } from './reset-password.service';
|
||||
|
||||
jest.mock('@react-email/render', () => ({
|
||||
render: jest.fn().mockImplementation(async (_, options) => {
|
||||
// render() resolves through a streaming scheduler that never advances under the
|
||||
// globally enabled fake timers; the real render path is covered by
|
||||
// email-templates-rendering.spec.ts.
|
||||
jest.mock('twenty-emails', () => ({
|
||||
...jest.requireActual('twenty-emails'),
|
||||
renderEmail: jest.fn().mockImplementation(async (_template, options) => {
|
||||
if (options?.plainText) {
|
||||
return 'Plain Text Email';
|
||||
}
|
||||
|
||||
+3
-4
@@ -4,10 +4,9 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { render } from '@react-email/render';
|
||||
import { addMilliseconds, differenceInMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
import { PasswordResetLinkEmail } from 'twenty-emails';
|
||||
import { PasswordResetLinkEmail, renderEmail } from 'twenty-emails';
|
||||
import { type APP_LOCALES } from 'twenty-shared/translations';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import {
|
||||
@@ -197,8 +196,8 @@ export class ResetPasswordService {
|
||||
|
||||
const emailTemplate = PasswordResetLinkEmail(emailData);
|
||||
|
||||
const html = await render(emailTemplate, { pretty: true });
|
||||
const text = await render(emailTemplate, { plainText: true });
|
||||
const html = await renderEmail(emailTemplate, { pretty: true });
|
||||
const text = await renderEmail(emailTemplate, { plainText: true });
|
||||
|
||||
const i18n = this.i18nService.getI18nInstance(locale);
|
||||
const subjectTemplate = hasPassword
|
||||
|
||||
+4
-3
@@ -13,13 +13,14 @@ import {
|
||||
} from 'src/engine/core-modules/billing/reminders/constants/billing-reminder-sent-keys.constant';
|
||||
import { BillingReminderService } from 'src/engine/core-modules/billing/reminders/services/billing-reminder.service';
|
||||
|
||||
jest.mock('@react-email/render', () => ({
|
||||
render: jest.fn().mockResolvedValue('<html></html>'),
|
||||
}));
|
||||
// render() resolves through a streaming scheduler that never advances under the
|
||||
// globally enabled fake timers; the real render path is covered by
|
||||
// email-templates-rendering.spec.ts.
|
||||
jest.mock('twenty-emails', () => ({
|
||||
BillingTrialEndingEmail: jest.fn(),
|
||||
BillingTrialConvertingEmail: jest.fn(),
|
||||
BillingSubscriptionRenewingEmail: jest.fn(),
|
||||
renderEmail: jest.fn().mockResolvedValue('<html></html>'),
|
||||
}));
|
||||
|
||||
const CONFIG: Record<string, unknown> = {
|
||||
|
||||
+3
-3
@@ -4,12 +4,12 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { render } from '@react-email/render';
|
||||
import { addDays, differenceInCalendarDays } from 'date-fns';
|
||||
import {
|
||||
BillingSubscriptionRenewingEmail,
|
||||
BillingTrialConvertingEmail,
|
||||
BillingTrialEndingEmail,
|
||||
renderEmail,
|
||||
} from 'twenty-emails';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
@@ -293,8 +293,8 @@ export class BillingReminderService {
|
||||
locale,
|
||||
});
|
||||
|
||||
const html = await render(emailTemplate, { pretty: true });
|
||||
const text = await render(emailTemplate, { plainText: true });
|
||||
const html = await renderEmail(emailTemplate, { pretty: true });
|
||||
const text = await renderEmail(emailTemplate, { plainText: true });
|
||||
|
||||
await this.emailService.send({
|
||||
to: workspaceMember.userEmail,
|
||||
|
||||
+4
-36
@@ -1,12 +1,10 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { render } from '@react-email/render';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { addMilliseconds, differenceInMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
import { SendEmailVerificationLinkEmail } from 'twenty-emails';
|
||||
import { SendEmailVerificationLinkEmail, renderEmail } from 'twenty-emails';
|
||||
import { type APP_LOCALES } from 'twenty-shared/translations';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
@@ -32,8 +30,6 @@ import { UserEntity } from 'src/engine/core-modules/user/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class EmailVerificationService {
|
||||
private readonly logger = new Logger(EmailVerificationService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(AppTokenEntity)
|
||||
private readonly appTokenRepository: Repository<AppTokenEntity>,
|
||||
@@ -97,37 +93,9 @@ export class EmailVerificationService {
|
||||
|
||||
const emailTemplate = SendEmailVerificationLinkEmail(emailData);
|
||||
|
||||
const html = await render(emailTemplate);
|
||||
const html = await renderEmail(emailTemplate);
|
||||
|
||||
// TEMPORARY DIAGNOSTIC (remove once the empty verification email body is root-caused):
|
||||
// render() wraps the tree in <Suspense> and uses renderToReadableStream WITHOUT an
|
||||
// onError, so any SSR throw is swallowed into an errored boundary and the body ships
|
||||
// as `<!DOCTYPE ...><!--$!--><template></template><!--/$-->`. Detect that and re-render
|
||||
// synchronously to surface the real error + stack (production strips it from the markup).
|
||||
if (html.length === 0 || html.includes('<!--$!-->')) {
|
||||
this.logger.error(
|
||||
`[EMAIL_VERIFICATION_RENDER_DEBUG] verification email rendered empty/errored (locale=${locale}, trigger=${verificationTrigger}, htmlLength=${html.length})`,
|
||||
);
|
||||
this.logger.error(
|
||||
`[EMAIL_VERIFICATION_RENDER_DEBUG] html start: ${html.slice(0, 400)}`,
|
||||
);
|
||||
try {
|
||||
renderToStaticMarkup(emailTemplate);
|
||||
this.logger.error(
|
||||
'[EMAIL_VERIFICATION_RENDER_DEBUG] renderToStaticMarkup did not throw — likely an async/Suspense rejection rather than a synchronous throw',
|
||||
);
|
||||
} catch (renderError) {
|
||||
this.logger.error(
|
||||
`[EMAIL_VERIFICATION_RENDER_DEBUG] underlying render error: ${
|
||||
renderError instanceof Error
|
||||
? `${renderError.message}\n${renderError.stack}`
|
||||
: String(renderError)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const text = await render(emailTemplate, {
|
||||
const text = await renderEmail(emailTemplate, {
|
||||
plainText: true,
|
||||
});
|
||||
|
||||
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import {
|
||||
BillingSubscriptionRenewingEmail,
|
||||
BillingTrialConvertingEmail,
|
||||
BillingTrialEndingEmail,
|
||||
CleanSuspendedWorkspaceEmail,
|
||||
PasswordResetLinkEmail,
|
||||
PasswordUpdateNotifyEmail,
|
||||
SendApprovedAccessDomainValidation,
|
||||
SendEmailVerificationLinkEmail,
|
||||
SendInviteLinkEmail,
|
||||
ServerAdminAccessChangedEmail,
|
||||
WarnSuspendedWorkspaceEmail,
|
||||
EmailRenderError,
|
||||
renderEmail,
|
||||
} from 'twenty-emails';
|
||||
|
||||
const WORKSPACE = { name: 'Acme Inc', logo: undefined };
|
||||
const SENDER = {
|
||||
email: 'tim@twenty.com',
|
||||
firstName: 'Tim',
|
||||
lastName: 'Apple',
|
||||
};
|
||||
|
||||
const TEMPLATES = [
|
||||
{
|
||||
name: 'BillingSubscriptionRenewingEmail',
|
||||
element: BillingSubscriptionRenewingEmail({
|
||||
userName: 'Tim',
|
||||
workspaceDisplayName: 'Acme Inc',
|
||||
renewsAt: new Date('2026-01-01'),
|
||||
link: 'https://app.twenty.com/settings/billing',
|
||||
locale: 'en',
|
||||
}),
|
||||
expectedContent: 'https://app.twenty.com/settings/billing',
|
||||
},
|
||||
{
|
||||
name: 'BillingTrialConvertingEmail',
|
||||
element: BillingTrialConvertingEmail({
|
||||
userName: 'Tim',
|
||||
workspaceDisplayName: 'Acme Inc',
|
||||
trialEndsAt: new Date('2026-01-01'),
|
||||
interval: 'month',
|
||||
link: 'https://app.twenty.com/settings/billing',
|
||||
locale: 'en',
|
||||
}),
|
||||
expectedContent: 'https://app.twenty.com/settings/billing',
|
||||
},
|
||||
{
|
||||
name: 'BillingTrialEndingEmail',
|
||||
element: BillingTrialEndingEmail({
|
||||
userName: 'Tim',
|
||||
workspaceDisplayName: 'Acme Inc',
|
||||
trialEndsAt: new Date('2026-01-01'),
|
||||
dataRetentionDays: 30,
|
||||
link: 'https://app.twenty.com/settings/billing',
|
||||
locale: 'en',
|
||||
}),
|
||||
expectedContent: 'https://app.twenty.com/settings/billing',
|
||||
},
|
||||
{
|
||||
name: 'CleanSuspendedWorkspaceEmail',
|
||||
element: CleanSuspendedWorkspaceEmail({
|
||||
daysSinceInactive: 30,
|
||||
userName: 'Tim',
|
||||
workspaceDisplayName: 'Acme Inc',
|
||||
locale: 'en',
|
||||
}),
|
||||
expectedContent: 'Acme Inc',
|
||||
},
|
||||
{
|
||||
name: 'PasswordResetLinkEmail',
|
||||
element: PasswordResetLinkEmail({
|
||||
duration: '5 minutes',
|
||||
hasPassword: true,
|
||||
link: 'https://app.twenty.com/reset-password',
|
||||
locale: 'en',
|
||||
}),
|
||||
expectedContent: 'https://app.twenty.com/reset-password',
|
||||
},
|
||||
{
|
||||
name: 'PasswordUpdateNotifyEmail',
|
||||
element: PasswordUpdateNotifyEmail({
|
||||
userName: 'Tim',
|
||||
email: 'tim@twenty.com',
|
||||
link: 'https://app.twenty.com',
|
||||
locale: 'en',
|
||||
}),
|
||||
expectedContent: 'tim@twenty.com',
|
||||
},
|
||||
{
|
||||
name: 'SendApprovedAccessDomainValidation',
|
||||
element: SendApprovedAccessDomainValidation({
|
||||
link: 'https://app.twenty.com/validate-approved-access-domain',
|
||||
domain: 'acme.com',
|
||||
workspace: WORKSPACE,
|
||||
sender: SENDER,
|
||||
serverUrl: 'https://app.twenty.com',
|
||||
locale: 'en',
|
||||
}),
|
||||
expectedContent: 'https://app.twenty.com/validate-approved-access-domain',
|
||||
},
|
||||
{
|
||||
name: 'SendEmailVerificationLinkEmail',
|
||||
element: SendEmailVerificationLinkEmail({
|
||||
link: 'https://app.twenty.com/verify-email',
|
||||
locale: 'en',
|
||||
}),
|
||||
expectedContent: 'https://app.twenty.com/verify-email',
|
||||
},
|
||||
{
|
||||
name: 'SendInviteLinkEmail',
|
||||
element: SendInviteLinkEmail({
|
||||
link: 'https://app.twenty.com/invite/token',
|
||||
workspace: WORKSPACE,
|
||||
sender: SENDER,
|
||||
serverUrl: 'https://app.twenty.com',
|
||||
locale: 'en',
|
||||
}),
|
||||
expectedContent: 'https://app.twenty.com/invite/token',
|
||||
},
|
||||
{
|
||||
name: 'ServerAdminAccessChangedEmail',
|
||||
element: ServerAdminAccessChangedEmail({
|
||||
actorName: 'Tim',
|
||||
targetName: 'Jony',
|
||||
targetEmail: 'jony@twenty.com',
|
||||
canAccessFullAdminPanel: true,
|
||||
canImpersonate: false,
|
||||
locale: 'en',
|
||||
}),
|
||||
expectedContent: 'jony@twenty.com',
|
||||
},
|
||||
{
|
||||
name: 'WarnSuspendedWorkspaceEmail',
|
||||
element: WarnSuspendedWorkspaceEmail({
|
||||
daysSinceInactive: 30,
|
||||
inactiveDaysBeforeDelete: 60,
|
||||
userName: 'Tim',
|
||||
workspaceDisplayName: 'Acme Inc',
|
||||
link: 'https://app.twenty.com/settings/billing',
|
||||
locale: 'en',
|
||||
}),
|
||||
expectedContent: 'https://app.twenty.com/settings/billing',
|
||||
},
|
||||
];
|
||||
|
||||
// Transactional emails went out with an empty body for a month without a single
|
||||
// test noticing, because every email spec mocks the renderer (#23307). These
|
||||
// specs run the real render path instead.
|
||||
describe('email templates rendering', () => {
|
||||
// render() resolves through a streaming scheduler that never advances under
|
||||
// the globally enabled fake timers.
|
||||
beforeAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
it.each(TEMPLATES)(
|
||||
'should render $name to a complete HTML body',
|
||||
async ({ element, expectedContent }) => {
|
||||
const html = await renderEmail(element);
|
||||
|
||||
expect(html).not.toContain('<!--$!-->');
|
||||
expect(html).not.toContain('<template></template>');
|
||||
expect(html).toContain('</html>');
|
||||
expect(html).toContain(expectedContent);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(TEMPLATES)(
|
||||
'should render $name to a non-empty plain text body',
|
||||
async ({ element }) => {
|
||||
const text = await renderEmail(element, { plainText: true });
|
||||
|
||||
expect(text.trim().length).toBeGreaterThan(0);
|
||||
expect(text).not.toContain('<');
|
||||
},
|
||||
);
|
||||
|
||||
it('should render translated content for a non-english locale', async () => {
|
||||
const html = await renderEmail(
|
||||
PasswordResetLinkEmail({
|
||||
duration: '5 minutes',
|
||||
hasPassword: true,
|
||||
link: 'https://app.twenty.com/reset-password',
|
||||
locale: 'fr-FR',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(html).toContain('mot de passe');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderEmail guard', () => {
|
||||
beforeAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
// @react-email/render 1.x turned a throw into `<!--$!--><template></template>`
|
||||
// and resolved, so the send went out blank. Whatever the renderer does, a
|
||||
// failed render must never reach the recipient as an empty email.
|
||||
it('should reject rather than resolve a blank body when a component throws', async () => {
|
||||
const ThrowingComponent = () => {
|
||||
throw new Error('boom');
|
||||
};
|
||||
|
||||
await expect(renderEmail(createElement(ThrowingComponent))).rejects.toThrow(
|
||||
/boom|Email template/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw EmailRenderError when a template produces no text body', async () => {
|
||||
const EmptyComponent = () => null;
|
||||
|
||||
await expect(
|
||||
renderEmail(createElement(EmptyComponent), { plainText: true }),
|
||||
).rejects.toThrow(EmailRenderError);
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -1,9 +1,9 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { toPlainText } from '@react-email/render';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { toPlainText } from 'twenty-emails';
|
||||
import { MAX_EMAIL_RECIPIENTS } from 'twenty-shared/constants';
|
||||
import {
|
||||
ConnectedAccountProvider,
|
||||
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
import { render } from '@react-email/render';
|
||||
import { type JSONContent, reactMarkupFromJSON } from 'twenty-emails';
|
||||
import { type JSONContent, reactMarkupFromJSON, render } from 'twenty-emails';
|
||||
|
||||
export const renderRichTextToHtml = async (
|
||||
jsonContent: JSONContent,
|
||||
|
||||
+6
-3
@@ -26,9 +26,12 @@ import { WorkspaceInvitationService } from './workspace-invitation.service';
|
||||
// To fix a circular dependency issue
|
||||
jest.mock('src/engine/core-modules/workspace/services/workspace.service');
|
||||
|
||||
// To avoid dynamic import issues in Jest
|
||||
jest.mock('@react-email/render', () => ({
|
||||
render: jest.fn().mockImplementation(async (template, options) => {
|
||||
// render() resolves through a streaming scheduler that never advances under the
|
||||
// globally enabled fake timers; the real render path is covered by
|
||||
// email-templates-rendering.spec.ts.
|
||||
jest.mock('twenty-emails', () => ({
|
||||
...jest.requireActual('twenty-emails'),
|
||||
renderEmail: jest.fn().mockImplementation(async (_template, options) => {
|
||||
if (options?.plainText) {
|
||||
return 'Plain Text Email';
|
||||
}
|
||||
|
||||
+3
-4
@@ -4,10 +4,9 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { render } from '@react-email/render';
|
||||
import { addMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
import { SendInviteLinkEmail } from 'twenty-emails';
|
||||
import { SendInviteLinkEmail, renderEmail } from 'twenty-emails';
|
||||
import { AppPath, FileFolder } from 'twenty-shared/types';
|
||||
import { getAppPath, isDefined } from 'twenty-shared/utils';
|
||||
import { In, IsNull, Repository } from 'typeorm';
|
||||
@@ -361,8 +360,8 @@ export class WorkspaceInvitationService {
|
||||
};
|
||||
|
||||
const emailTemplate = SendInviteLinkEmail(emailData);
|
||||
const html = await render(emailTemplate);
|
||||
const text = await render(emailTemplate, {
|
||||
const html = await renderEmail(emailTemplate);
|
||||
const text = await renderEmail(emailTemplate, {
|
||||
plainText: true,
|
||||
});
|
||||
|
||||
|
||||
+5
-5
@@ -2,11 +2,11 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { render } from '@react-email/render';
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import {
|
||||
CleanSuspendedWorkspaceEmail,
|
||||
WarnSuspendedWorkspaceEmail,
|
||||
renderEmail,
|
||||
} from 'twenty-emails';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
@@ -130,8 +130,8 @@ export class CleanerWorkspaceService {
|
||||
locale: workspaceMember.locale,
|
||||
};
|
||||
const emailTemplate = WarnSuspendedWorkspaceEmail(emailData);
|
||||
const html = await render(emailTemplate, { pretty: true });
|
||||
const text = await render(emailTemplate, { plainText: true });
|
||||
const html = await renderEmail(emailTemplate, { pretty: true });
|
||||
const text = await renderEmail(emailTemplate, { plainText: true });
|
||||
|
||||
const workspaceDeletionMsg = msg`Your workspace is paused — reactivate to keep your data`;
|
||||
const i18n = this.i18nService.getI18nInstance(workspaceMember.locale);
|
||||
@@ -220,8 +220,8 @@ export class CleanerWorkspaceService {
|
||||
locale: workspaceMember.locale,
|
||||
};
|
||||
const emailTemplate = CleanSuspendedWorkspaceEmail(emailData);
|
||||
const html = await render(emailTemplate, { pretty: true });
|
||||
const text = await render(emailTemplate, { plainText: true });
|
||||
const html = await renderEmail(emailTemplate, { pretty: true });
|
||||
const text = await renderEmail(emailTemplate, { plainText: true });
|
||||
|
||||
if (!isDefined(workspaceMember.userEmail)) {
|
||||
throw new Error('Workspace member email is missing');
|
||||
|
||||
@@ -2499,19 +2499,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/generator@npm:^7.27.0, @babel/generator@npm:^7.29.7":
|
||||
version: 7.29.7
|
||||
resolution: "@babel/generator@npm:7.29.7"
|
||||
dependencies:
|
||||
"@babel/parser": "npm:^7.29.7"
|
||||
"@babel/types": "npm:^7.29.7"
|
||||
"@jridgewell/gen-mapping": "npm:^0.3.12"
|
||||
"@jridgewell/trace-mapping": "npm:^0.3.28"
|
||||
jsesc: "npm:^3.0.2"
|
||||
checksum: 10c0/9bf72b01b5bd0ea5b1288a0e37dbd360bff2f2b1ce73342c0d40fb3db2ec3dc004ada5ffa925c5e12939a416eed59e600d562b8ecd938ce0d27dfd0eb6c6c2b7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/generator@npm:^7.28.5":
|
||||
version: 7.28.5
|
||||
resolution: "@babel/generator@npm:7.28.5"
|
||||
@@ -2538,6 +2525,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/generator@npm:^7.29.7":
|
||||
version: 7.29.7
|
||||
resolution: "@babel/generator@npm:7.29.7"
|
||||
dependencies:
|
||||
"@babel/parser": "npm:^7.29.7"
|
||||
"@babel/types": "npm:^7.29.7"
|
||||
"@jridgewell/gen-mapping": "npm:^0.3.12"
|
||||
"@jridgewell/trace-mapping": "npm:^0.3.28"
|
||||
jsesc: "npm:^3.0.2"
|
||||
checksum: 10c0/9bf72b01b5bd0ea5b1288a0e37dbd360bff2f2b1ce73342c0d40fb3db2ec3dc004ada5ffa925c5e12939a416eed59e600d562b8ecd938ce0d27dfd0eb6c6c2b7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/helper-annotate-as-pure@npm:^7.25.9":
|
||||
version: 7.25.9
|
||||
resolution: "@babel/helper-annotate-as-pure@npm:7.25.9"
|
||||
@@ -2983,14 +2983,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/parser@npm:7.27.0":
|
||||
version: 7.27.0
|
||||
resolution: "@babel/parser@npm:7.27.0"
|
||||
"@babel/parser@npm:7.29.2, @babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0":
|
||||
version: 7.29.2
|
||||
resolution: "@babel/parser@npm:7.29.2"
|
||||
dependencies:
|
||||
"@babel/types": "npm:^7.27.0"
|
||||
"@babel/types": "npm:^7.29.0"
|
||||
bin:
|
||||
parser: ./bin/babel-parser.js
|
||||
checksum: 10c0/ba2ed3f41735826546a3ef2a7634a8d10351df221891906e59b29b0a0cd748f9b0e7a6f07576858a9de8e77785aad925c8389ddef146de04ea2842047c9d2859
|
||||
checksum: 10c0/e5a4e69e3ac7acdde995f37cf299a68458cfe7009dff66bd0962fd04920bef287201169006af365af479c08ff216bfefbb595e331f87f6ae7283858aebbc3317
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3016,7 +3016,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/parser@npm:^7.27.0, @babel/parser@npm:^7.28.5":
|
||||
"@babel/parser@npm:^7.28.5":
|
||||
version: 7.28.5
|
||||
resolution: "@babel/parser@npm:7.28.5"
|
||||
dependencies:
|
||||
@@ -3027,17 +3027,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0":
|
||||
version: 7.29.2
|
||||
resolution: "@babel/parser@npm:7.29.2"
|
||||
dependencies:
|
||||
"@babel/types": "npm:^7.29.0"
|
||||
bin:
|
||||
parser: ./bin/babel-parser.js
|
||||
checksum: 10c0/e5a4e69e3ac7acdde995f37cf299a68458cfe7009dff66bd0962fd04920bef287201169006af365af479c08ff216bfefbb595e331f87f6ae7283858aebbc3317
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.9":
|
||||
version: 7.25.9
|
||||
resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.9"
|
||||
@@ -4454,7 +4443,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/template@npm:^7.20.7, @babel/template@npm:^7.27.0, @babel/template@npm:^7.29.7":
|
||||
"@babel/template@npm:^7.20.7, @babel/template@npm:^7.29.7":
|
||||
version: 7.29.7
|
||||
resolution: "@babel/template@npm:7.29.7"
|
||||
dependencies:
|
||||
@@ -4476,18 +4465,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/traverse@npm:7.27.0":
|
||||
version: 7.27.0
|
||||
resolution: "@babel/traverse@npm:7.27.0"
|
||||
"@babel/traverse@npm:7.29.0, @babel/traverse@npm:^7.28.6":
|
||||
version: 7.29.0
|
||||
resolution: "@babel/traverse@npm:7.29.0"
|
||||
dependencies:
|
||||
"@babel/code-frame": "npm:^7.26.2"
|
||||
"@babel/generator": "npm:^7.27.0"
|
||||
"@babel/parser": "npm:^7.27.0"
|
||||
"@babel/template": "npm:^7.27.0"
|
||||
"@babel/types": "npm:^7.27.0"
|
||||
"@babel/code-frame": "npm:^7.29.0"
|
||||
"@babel/generator": "npm:^7.29.0"
|
||||
"@babel/helper-globals": "npm:^7.28.0"
|
||||
"@babel/parser": "npm:^7.29.0"
|
||||
"@babel/template": "npm:^7.28.6"
|
||||
"@babel/types": "npm:^7.29.0"
|
||||
debug: "npm:^4.3.1"
|
||||
globals: "npm:^11.1.0"
|
||||
checksum: 10c0/c7af29781960dacaae51762e8bc6c4b13d6ab4b17312990fbca9fc38e19c4ad7fecaae24b1cf52fb844e8e6cdc76c70ad597f90e496bcb3cc0a1d66b41a0aa5b
|
||||
checksum: 10c0/f63ef6e58d02a9fbf3c0e2e5f1c877da3e0bc57f91a19d2223d53e356a76859cbaf51171c9211c71816d94a0e69efa2732fd27ffc0e1bbc84b636e60932333eb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4536,21 +4525,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/traverse@npm:^7.28.6":
|
||||
version: 7.29.0
|
||||
resolution: "@babel/traverse@npm:7.29.0"
|
||||
dependencies:
|
||||
"@babel/code-frame": "npm:^7.29.0"
|
||||
"@babel/generator": "npm:^7.29.0"
|
||||
"@babel/helper-globals": "npm:^7.28.0"
|
||||
"@babel/parser": "npm:^7.29.0"
|
||||
"@babel/template": "npm:^7.28.6"
|
||||
"@babel/types": "npm:^7.29.0"
|
||||
debug: "npm:^4.3.1"
|
||||
checksum: 10c0/f63ef6e58d02a9fbf3c0e2e5f1c877da3e0bc57f91a19d2223d53e356a76859cbaf51171c9211c71816d94a0e69efa2732fd27ffc0e1bbc84b636e60932333eb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.13, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.2, @babel/types@npm:^7.21.3, @babel/types@npm:^7.23.5, @babel/types@npm:^7.25.9, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.0, @babel/types@npm:^7.28.2, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
|
||||
version: 7.28.2
|
||||
resolution: "@babel/types@npm:7.28.2"
|
||||
@@ -4561,7 +4535,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.26.0, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.0, @babel/types@npm:^7.29.7":
|
||||
"@babel/types@npm:^7.26.0, @babel/types@npm:^7.26.10, @babel/types@npm:^7.29.7":
|
||||
version: 7.29.7
|
||||
resolution: "@babel/types@npm:7.29.7"
|
||||
dependencies:
|
||||
@@ -15208,245 +15182,28 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/body@npm:0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "@react-email/body@npm:0.1.0"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/deeee52e0326eed20b3267dfe970649705298efae9ff580009ee7f48c201cbca6cd200be163865178ccdab04fc8e44384c6dd7ab130557b93f421d3b263b33c8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/button@npm:0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "@react-email/button@npm:0.2.0"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/1145d096ace8a459271f550faa941668122bd816adc6547390ede5becab5b0dc99a6f19c838e539fe53a73953bd4a4de790b1a7ad3d5981c44f5d34149045bfc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/code-block@npm:0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "@react-email/code-block@npm:0.1.0"
|
||||
dependencies:
|
||||
prismjs: "npm:^1.30.0"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/55d84654cbf593e49102a11d1615e3beefb6943a82842ab1e51f435c6130a62f4ea2ebc034b127ebcc96822789b58cf352df340823278c9759bb3f031d8a796e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/code-inline@npm:0.0.5":
|
||||
version: 0.0.5
|
||||
resolution: "@react-email/code-inline@npm:0.0.5"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/4081233686dd09575c580303f2822c415e1acc3c21847e64536afee60d37108810f5325e50a417ca39ab796fb21aae488108b4dcb55b0bb5e5b750f1019f32d7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/column@npm:0.0.13":
|
||||
version: 0.0.13
|
||||
resolution: "@react-email/column@npm:0.0.13"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/56ebc6af1c546daca2c32b0700538242ab7d90ffff4be37bf9dcf929311b6996c66b39f506dfd37063bacd4382abdeab93baf8c1e3266aa62fe3b282b16a023f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/components@npm:^0.5.3":
|
||||
version: 0.5.3
|
||||
resolution: "@react-email/components@npm:0.5.3"
|
||||
dependencies:
|
||||
"@react-email/body": "npm:0.1.0"
|
||||
"@react-email/button": "npm:0.2.0"
|
||||
"@react-email/code-block": "npm:0.1.0"
|
||||
"@react-email/code-inline": "npm:0.0.5"
|
||||
"@react-email/column": "npm:0.0.13"
|
||||
"@react-email/container": "npm:0.0.15"
|
||||
"@react-email/font": "npm:0.0.9"
|
||||
"@react-email/head": "npm:0.0.12"
|
||||
"@react-email/heading": "npm:0.0.15"
|
||||
"@react-email/hr": "npm:0.0.11"
|
||||
"@react-email/html": "npm:0.0.11"
|
||||
"@react-email/img": "npm:0.0.11"
|
||||
"@react-email/link": "npm:0.0.12"
|
||||
"@react-email/markdown": "npm:0.0.15"
|
||||
"@react-email/preview": "npm:0.0.13"
|
||||
"@react-email/render": "npm:1.2.3"
|
||||
"@react-email/row": "npm:0.0.12"
|
||||
"@react-email/section": "npm:0.0.16"
|
||||
"@react-email/tailwind": "npm:1.2.2"
|
||||
"@react-email/text": "npm:0.1.5"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/a0839c635cde48d45d49d3ea19aa17a8e5616225e516d76de1c4d0915d88b903241033a55f4aa047d212672e48ff809840e0d05afd80bbaacb330cef7804971a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/container@npm:0.0.15":
|
||||
version: 0.0.15
|
||||
resolution: "@react-email/container@npm:0.0.15"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/5098468b5336682f0f833a57cea1182bfe4a46bb03d5aa9fc85f96ab1e79845e30a9487c4c56094a9482465e94aa4395f10978c99b1772ca4bd0823d2d9071f6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/font@npm:0.0.9":
|
||||
version: 0.0.9
|
||||
resolution: "@react-email/font@npm:0.0.9"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/b51c9bc22f3ea6e26c34fd5e4186be0b42a0414558f318b5784079145b267f7316729d54c8ba8c955ee5100e4b9ca9f5048c123a66ad2e277d49dd07c718287c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/head@npm:0.0.12":
|
||||
version: 0.0.12
|
||||
resolution: "@react-email/head@npm:0.0.12"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/511147ea52330f5752e2d226a3417192adb5c0f301e35077a07fd66b99504eda6c3bfa49713a10400cf8542d75dfb317e5050b5383b285129db9854d5760d97d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/heading@npm:0.0.15":
|
||||
version: 0.0.15
|
||||
resolution: "@react-email/heading@npm:0.0.15"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/74e58d2933eb6d37f4f3a6f82c63af6c9ffe2ce8cd14c7f6e27f8b76d39800cbf1a215d86e17a55d69eca4c73d5e9ea747ae1cd0a7df4a08a861a13afb9989dc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/hr@npm:0.0.11":
|
||||
version: 0.0.11
|
||||
resolution: "@react-email/hr@npm:0.0.11"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/9d8199bdcfdc7e180636cdc11b7af5c44d185598fedd97dd0734103d662074ed8b7204e362c31f0dfd3925c37181e2fe22dbf1f76908c862d489f34e7efea7a9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/html@npm:0.0.11":
|
||||
version: 0.0.11
|
||||
resolution: "@react-email/html@npm:0.0.11"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/04be96e135677cd507636877c2f9bf1a507d40f6d4442ff3fac58bc70d478e7f23dd03d94ecc1605d3e368d50129281e71f4b7afb242f1b32d285bfc37236a09
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/img@npm:0.0.11":
|
||||
version: 0.0.11
|
||||
resolution: "@react-email/img@npm:0.0.11"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/082e9f7d7290de4d340b06bc0ab0e4ffec08604885fdf06d93594e3684726279a3a65c0be2c83c9397bb832289078d533f0499b423ed3558f7736ad7d333d457
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/link@npm:0.0.12":
|
||||
version: 0.0.12
|
||||
resolution: "@react-email/link@npm:0.0.12"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/d831666bd52af9ba3a50bd4853e2b70387a041264e3781d3dab4708b755e8b2cb6df339f71eecd889b35221c205bd9997d47e950d1e3b226b4be50a22405ca87
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/markdown@npm:0.0.15":
|
||||
version: 0.0.15
|
||||
resolution: "@react-email/markdown@npm:0.0.15"
|
||||
dependencies:
|
||||
md-to-react-email: "npm:^5.0.5"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/68dc46d929ad0daf86f7d25e159455bdfbf4c0f0ca8ad152caf407f2eb154f4edb88912728cd5bba0c01f4806217e533e66baa02138c8ddcb5f2049719da7137
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/preview@npm:0.0.13":
|
||||
version: 0.0.13
|
||||
resolution: "@react-email/preview@npm:0.0.13"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/223cff5238f0bfc4f1c3cdd1ae4f8b92cde16a60594d4d5073607b7af0b3cfaf5ae10234ca531d9403c7143afcba2e71bc12c1aba75e51f0ae58fd9c0b8bf542
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/render@npm:1.2.3, @react-email/render@npm:^1.2.3":
|
||||
version: 1.2.3
|
||||
resolution: "@react-email/render@npm:1.2.3"
|
||||
dependencies:
|
||||
html-to-text: "npm:^9.0.5"
|
||||
prettier: "npm:^3.5.3"
|
||||
react-promise-suspense: "npm:^0.3.4"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/7ddd786e808fae6dc9aa456965ec5a67dff5d143b027c70538158e182a99a59b784a092c44f61ace20368ab5e31c31875ae1513593efe153d029c9870928d07b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/render@npm:>=2.0.8":
|
||||
version: 2.0.8
|
||||
resolution: "@react-email/render@npm:2.0.8"
|
||||
"@react-email/render@npm:>=2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "@react-email/render@npm:2.1.0"
|
||||
dependencies:
|
||||
entities: "npm:^4.5.0"
|
||||
html-to-text: "npm:^9.0.5"
|
||||
html5parser: "npm:^3.0.0"
|
||||
prettier: "npm:^3.5.3"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/183793beaab413523e1ffbf3e07b352e33c0b7b0912e6bee0da2aceb495f327694f98b55957255419cbcaf771c538f2b084580d4d3a78c92ccf7a5cad527f73c
|
||||
checksum: 10c0/d117fb48711fb491f8f958806278c23f5d816e5baf999faef9d93922581d5c9128c7b320418317c486f9a68760deb32a1aa97efde946f4027a7aadda18aab7fc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/row@npm:0.0.12":
|
||||
version: 0.0.12
|
||||
resolution: "@react-email/row@npm:0.0.12"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/440c54071543700ce7a0db4749c93c56fa0cfae1057464231b25d9d7598f81a2f395816b922376acd5445c29163ae2858e4fc840d8cf30f3d4520104f7f5e3ed
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/section@npm:0.0.16":
|
||||
version: 0.0.16
|
||||
resolution: "@react-email/section@npm:0.0.16"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/48bfacdd78d403b50c4c1098d100a96f3afe6159d910374a0306d6186daa1bf3803b4a2b3163fb38d9137835041b9f70145c7bb2f652ba347a82a0d396fe2ef2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/tailwind@npm:1.2.2":
|
||||
version: 1.2.2
|
||||
resolution: "@react-email/tailwind@npm:1.2.2"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/fa7d0a4d518a77cf0ec26c57aeac8a24509344225a19d6f3c1320a4357d88b2d48e592f312286ec59ef17333d875e3f3437cd09102d793896229e3f5502d079e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/text@npm:0.1.5":
|
||||
version: 0.1.5
|
||||
resolution: "@react-email/text@npm:0.1.5"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
checksum: 10c0/ad825332903917d1185ce7c5e4d19846496565996f82b71a873648dffc6342d382606a96a5a00a15a16abef5322540d56b481ad3ca21c8b34de9b598715ec149
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@react-email/ui@npm:6.5.0":
|
||||
version: 6.5.0
|
||||
resolution: "@react-email/ui@npm:6.5.0"
|
||||
"@react-email/ui@npm:^6.9.0":
|
||||
version: 6.9.0
|
||||
resolution: "@react-email/ui@npm:6.9.0"
|
||||
dependencies:
|
||||
esbuild: "npm:0.28.0"
|
||||
esbuild: "npm:0.28.1"
|
||||
next: "npm:16.2.6"
|
||||
checksum: 10c0/af9b7449f3b66a461e296ccc93a0b779898a262973f698729a67243fec4eef49d6e395d393c01399217e4c0f87186ebf90bb173b2b0802e2bfac04d02d9692d9
|
||||
checksum: 10c0/e031a5e96e19adbb51cb2c6c2425d4738ee034fcccc3741b4683e829abb94caa62d4f18b8d571f2d7b6e98eba64c46be6e2d5f17aa895f545e1e7f4a4e7870f2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -30761,13 +30518,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-deep-equal@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "fast-deep-equal@npm:2.0.1"
|
||||
checksum: 10c0/1602e0d6ed63493c865cc6b03f9070d6d3926e8cd086a123060b58f80a295f3f08b1ecfb479ae7c45b7fd45535202aea7cf5b49bc31bffb81c20b1502300be84
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
|
||||
version: 3.1.3
|
||||
resolution: "fast-deep-equal@npm:3.1.3"
|
||||
@@ -33328,6 +33078,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html5parser@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "html5parser@npm:3.0.0"
|
||||
checksum: 10c0/1f64db31ce882504ec73c36bc87e5225ad02e472f4953215cbda7a602ad5f5f85ea12d1b490bcf5713ae9ecbdc1b2b0dc8643075fd4cc3816624746762b5c0b3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"htmlparser2@npm:^8.0.2":
|
||||
version: 8.0.2
|
||||
resolution: "htmlparser2@npm:8.0.2"
|
||||
@@ -36075,6 +35832,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jiti@npm:2.6.1":
|
||||
version: 2.6.1
|
||||
resolution: "jiti@npm:2.6.1"
|
||||
bin:
|
||||
jiti: lib/jiti-cli.mjs
|
||||
checksum: 10c0/79b2e96a8e623f66c1b703b98ec1b8be4500e1d217e09b09e343471bbb9c105381b83edbb979d01cef18318cc45ce6e153571b6c83122170eefa531c64b6789b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jiti@npm:^1.17.1":
|
||||
version: 1.21.6
|
||||
resolution: "jiti@npm:1.21.6"
|
||||
@@ -37856,15 +37622,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"marked@npm:7.0.4":
|
||||
version: 7.0.4
|
||||
resolution: "marked@npm:7.0.4"
|
||||
bin:
|
||||
marked: bin/marked.js
|
||||
checksum: 10c0/7f5993bfb2d260806ffada81051c45952857117cba0fd82790779dc696c7ebd35a96be47409b4bdabd75e7ede286f5f5142a75a47200e3fa54eaf8b0cd6f74f6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"matchmediaquery@npm:^0.3.0":
|
||||
version: 0.3.1
|
||||
resolution: "matchmediaquery@npm:0.3.1"
|
||||
@@ -37881,17 +37638,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"md-to-react-email@npm:^5.0.5":
|
||||
version: 5.0.5
|
||||
resolution: "md-to-react-email@npm:5.0.5"
|
||||
dependencies:
|
||||
marked: "npm:7.0.4"
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0
|
||||
checksum: 10c0/0d6eedb905562d88025fbf45c79117d9d668c86427eac9a3ecc2f5082a7fac9cf55c271f8892083e994d794322e8f0ede928ee2380465c9f4f878cbd2a392999
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"md5.js@npm:^1.3.4":
|
||||
version: 1.3.5
|
||||
resolution: "md5.js@npm:1.3.5"
|
||||
@@ -43739,13 +43485,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-email@npm:6.5.0":
|
||||
version: 6.5.0
|
||||
resolution: "react-email@npm:6.5.0"
|
||||
"react-email@npm:^6.9.0":
|
||||
version: 6.9.0
|
||||
resolution: "react-email@npm:6.9.0"
|
||||
dependencies:
|
||||
"@babel/parser": "npm:7.27.0"
|
||||
"@babel/traverse": "npm:7.27.0"
|
||||
"@react-email/render": "npm:>=2.0.8"
|
||||
"@babel/parser": "npm:7.29.2"
|
||||
"@babel/traverse": "npm:7.29.0"
|
||||
"@react-email/render": "npm:>=2.1.0"
|
||||
chokidar: "npm:^4.0.3"
|
||||
commander: "npm:^13.0.0"
|
||||
conf: "npm:^15.0.2"
|
||||
@@ -43753,7 +43499,7 @@ __metadata:
|
||||
debounce: "npm:^2.0.0"
|
||||
esbuild: "npm:^0.28.0"
|
||||
glob: "npm:^13.0.6"
|
||||
jiti: "npm:2.4.2"
|
||||
jiti: "npm:2.6.1"
|
||||
log-symbols: "npm:^7.0.0"
|
||||
marked: "npm:^15.0.12"
|
||||
mime-types: "npm:^3.0.0"
|
||||
@@ -43770,7 +43516,7 @@ __metadata:
|
||||
react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
bin:
|
||||
email: ./dist/cli/index.mjs
|
||||
checksum: 10c0/5d49c7771db71f57358ca33f260db709b55ff6844de06a468a44aa2c36c8b5ab9b023289a9607a6a56b87f6b01bd9992cfa1ce2da9b82f76ab99ea3f79bf759c
|
||||
checksum: 10c0/7c2c5fbd2b1fe1d5a42e02aa9f9e9cbe1b08b6d2b9d2c4e74aeeccc15f192dcc3e2c4b7a9fb9995cd3e6bddaa864730864fdf6d671d53df3fb6e093821794536
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -43988,15 +43734,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-promise-suspense@npm:^0.3.4":
|
||||
version: 0.3.4
|
||||
resolution: "react-promise-suspense@npm:0.3.4"
|
||||
dependencies:
|
||||
fast-deep-equal: "npm:^2.0.1"
|
||||
checksum: 10c0/ab7a22f5400f9e9933995537bf6430a4c79e33a121aedb51864968e7604e5c40421fd539ead62554f32300b7d49755c79636de06caa36fe52973b626b4ddfebf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-qr-code@npm:^2.0.18":
|
||||
version: 2.0.18
|
||||
resolution: "react-qr-code@npm:2.0.18"
|
||||
@@ -49166,14 +48903,13 @@ __metadata:
|
||||
"@lingui/react": "npm:^5.9.5"
|
||||
"@lingui/swc-plugin": "npm:^5.11.0"
|
||||
"@lingui/vite-plugin": "npm:^5.9.5"
|
||||
"@react-email/components": "npm:^0.5.3"
|
||||
"@react-email/ui": "npm:6.5.0"
|
||||
"@react-email/ui": "npm:^6.9.0"
|
||||
"@tiptap/core": "npm:^3.4.2"
|
||||
"@types/react": "npm:^19.2.0"
|
||||
"@types/react-dom": "npm:^19.2.0"
|
||||
"@typescript/native-preview": "npm:^7.0.0-dev.20260116.1"
|
||||
"@vitejs/plugin-react-swc": "npm:^4.3.1"
|
||||
react-email: "npm:6.5.0"
|
||||
react-email: "npm:^6.9.0"
|
||||
tsc-alias: "npm:^1.8.16"
|
||||
twenty-shared: "workspace:*"
|
||||
vite-plugin-dts: "npm:^4.5.4"
|
||||
@@ -49264,7 +49000,6 @@ __metadata:
|
||||
"@nivo/pie": "npm:^0.99.0"
|
||||
"@nivo/radial-bar": "npm:^0.99.0"
|
||||
"@playwright/test": "npm:^1.60.0"
|
||||
"@react-email/components": "npm:^0.5.3"
|
||||
"@react-pdf/renderer": "npm:^4.1.6"
|
||||
"@scalar/api-reference-react": "npm:^0.9.42"
|
||||
"@sentry/react": "npm:^10.51.0"
|
||||
@@ -49520,7 +49255,6 @@ __metadata:
|
||||
"@ptc-org/nestjs-query-core": "npm:^9.4.0"
|
||||
"@ptc-org/nestjs-query-graphql": "patch:@ptc-org/nestjs-query-graphql@npm%3A9.4.0#~/.yarn/patches/@ptc-org-nestjs-query-graphql-npm-9.4.0-8e6f7894e1.patch"
|
||||
"@ptc-org/nestjs-query-typeorm": "patch:@ptc-org/nestjs-query-typeorm@npm%3A9.4.0#~/.yarn/patches/@ptc-org-nestjs-query-typeorm-npm-9.4.0-ca3414967e.patch"
|
||||
"@react-email/render": "npm:^1.2.3"
|
||||
"@react-pdf/renderer": "npm:^4.1.6"
|
||||
"@sentry/nestjs": "npm:^10.59.0"
|
||||
"@sentry/node": "npm:^10.59.0"
|
||||
|
||||
Reference in New Issue
Block a user