diff --git a/.changeset/curly-lions-bake.md b/.changeset/curly-lions-bake.md new file mode 100644 index 0000000000..0ef4f77576 --- /dev/null +++ b/.changeset/curly-lions-bake.md @@ -0,0 +1,6 @@ +--- +"react-email": minor +"@react-email/ui": minor +--- + +add optional email config support diff --git a/packages/react-email/package.json b/packages/react-email/package.json index bd68fdbabc..1c04f81e90 100644 --- a/packages/react-email/package.json +++ b/packages/react-email/package.json @@ -18,13 +18,25 @@ }, "license": "MIT", "exports": { - "import": { - "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } }, - "require": { - "types": "./dist/index.d.cts", - "default": "./dist/index.cjs" + "./config": { + "import": { + "types": "./dist/config/index.d.mts", + "default": "./dist/config/index.mjs" + }, + "require": { + "types": "./dist/config/index.d.cts", + "default": "./dist/config/index.cjs" + } } }, "repository": { @@ -54,7 +66,7 @@ "debounce": "^2.0.0", "esbuild": "^0.28.0", "glob": "^13.0.6", - "jiti": "2.4.2", + "jiti": "catalog:", "log-symbols": "catalog:", "marked": "^15.0.12", "mime-types": "^3.0.0", diff --git a/packages/react-email/src/cli/commands/build.ts b/packages/react-email/src/cli/commands/build.ts index e2957d0634..cf313cb814 100644 --- a/packages/react-email/src/cli/commands/build.ts +++ b/packages/react-email/src/cli/commands/build.ts @@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'; import { getPackages } from '@manypkg/get-packages'; import logSymbols from 'log-symbols'; import { installDependencies, runScript } from 'nypm'; +import { getEmailConfigPath } from '../../config/get-email-config-path.js'; import { type EmailsDirectory, getEmailsDirectoryMetadata, @@ -29,11 +30,13 @@ const setNextEnvironmentVariablesForBuild = async ( if (isInReactEmailMonorepo) { rootDir = `'${await getPackages(usersProjectLocation).then((p) => p.rootDir.replaceAll('\\', '/'))}'`; } + const emailConfigPath = getEmailConfigPath(usersProjectLocation); const nextConfigContents = ` import path from 'path'; const emailsDirRelativePath = path.normalize('${emailsDirRelativePath}'); const userProjectLocation = '${process.cwd().replaceAll('\\', '/')}'; const previewServerLocation = '${builtPreviewAppPath.replaceAll('\\', '/')}'; +const emailConfigPath = ${emailConfigPath ? JSON.stringify(emailConfigPath.replaceAll('\\', '/')) : 'undefined'}; const rootDir = ${rootDir}; /** @type {import('next').NextConfig} */ const nextConfig = { @@ -42,13 +45,14 @@ const nextConfig = { REACT_EMAIL_INTERNAL_EMAILS_DIR_RELATIVE_PATH: emailsDirRelativePath, REACT_EMAIL_INTERNAL_EMAILS_DIR_ABSOLUTE_PATH: path.resolve(userProjectLocation, emailsDirRelativePath), REACT_EMAIL_INTERNAL_PREVIEW_SERVER_LOCATION: previewServerLocation, - REACT_EMAIL_INTERNAL_USER_PROJECT_LOCATION: userProjectLocation + REACT_EMAIL_INTERNAL_USER_PROJECT_LOCATION: userProjectLocation, + REACT_EMAIL_INTERNAL_EMAIL_CONFIG_PATH: emailConfigPath }, turbopack: { root: rootDir, }, outputFileTracingRoot: rootDir, - serverExternalPackages: ['esbuild'], + serverExternalPackages: ['esbuild', 'jiti'], typescript: { ignoreBuildErrors: true }, diff --git a/packages/react-email/src/cli/commands/export.ts b/packages/react-email/src/cli/commands/export.ts index 1a3c15d3ac..2e75cebe02 100644 --- a/packages/react-email/src/cli/commands/export.ts +++ b/packages/react-email/src/cli/commands/export.ts @@ -8,6 +8,7 @@ import { glob } from 'glob'; import logSymbols from 'log-symbols'; import normalize from 'normalize-path'; import type React from 'react'; +import { getEmailConfig, getEmailConfigPath } from '../../config/index.js'; import { renderingUtilitiesExporter } from '../utils/esbuild/renderring-utilities-exporter.js'; import { type EmailsDirectory, @@ -83,6 +84,10 @@ export const exportTemplates = async ( const allTemplates = getEmailTemplatesFromDirectory(emailsDirectoryMetadata); try { + const emailConfigPath = getEmailConfigPath(process.cwd()); + const emailConfig = await getEmailConfig(emailConfigPath); + const emailConfigPlugins = emailConfig.esbuild?.plugins ?? []; + await build({ bundle: true, entryPoints: allTemplates, @@ -94,7 +99,10 @@ export const exportTemplates = async ( outExtension: { '.js': '.cjs' }, outdir: pathToWhereEmailMarkupShouldBeDumped, platform: 'node', - plugins: [renderingUtilitiesExporter(allTemplates)], + plugins: [ + renderingUtilitiesExporter(allTemplates), + ...emailConfigPlugins, + ], write: true, }); } catch (exception) { diff --git a/packages/react-email/src/cli/commands/testing/export.spec.ts b/packages/react-email/src/cli/commands/testing/export.spec.ts index 382b20cbd4..df4eec30bd 100644 --- a/packages/react-email/src/cli/commands/testing/export.spec.ts +++ b/packages/react-email/src/cli/commands/testing/export.spec.ts @@ -1,7 +1,18 @@ import fs from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; +import { build } from 'esbuild'; +import * as config from '../../../config/index.js'; import { exportTemplates } from '../export.js'; +vi.mock('esbuild', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + build: vi.fn(actual.build), + }; +}); + test('email export', { retry: 3 }, async () => { const pathToEmailsDirectory = path.resolve(__dirname, './emails'); const pathToDumpMarkup = path.resolve(__dirname, './out'); @@ -212,3 +223,89 @@ test('email export', { retry: 3 }, async () => { " `); }); + +test('email export uses email config plugins', async () => { + const temporaryProjectRoot = fs.mkdtempSync( + path.join(os.tmpdir(), 'react-email-export-config-'), + ); + const previousWorkingDirectory = process.cwd(); + + try { + process.chdir(temporaryProjectRoot); + + fs.mkdirSync(path.join(temporaryProjectRoot, 'emails')); + fs.writeFileSync( + path.join(temporaryProjectRoot, 'emails', 'test-email.js'), + ` +export default function Email() { + return null; +} + `, + 'utf8', + ); + fs.writeFileSync( + path.join(temporaryProjectRoot, 'email.config.ts'), + 'export default {};\n', + 'utf8', + ); + + const mockedBuild = vi.mocked(build); + mockedBuild.mockClear(); + mockedBuild.mockImplementation(async (options: any) => { + fs.mkdirSync(options.outdir, { recursive: true }); + fs.writeFileSync( + path.join(options.outdir, 'test-email.cjs'), + ` +module.exports = { + default: function Email() { + return null; + }, + render: async () => 'rendered', + reactEmailCreateReactElement: (type, props) => ({ type, props }), +}; + `, + 'utf8', + ); + + return { outputFiles: [] } as any; + }); + const mockedGetEmailConfig = vi + .spyOn(config, 'getEmailConfig') + .mockResolvedValue({ + esbuild: { + plugins: [{ name: 'email-config-plugin', setup: vi.fn() }], + }, + }); + const mockedGetEmailConfigPath = vi + .spyOn(config, 'getEmailConfigPath') + .mockReturnValue(path.join(temporaryProjectRoot, 'email.config.ts')); + mockedGetEmailConfig.mockClear(); + mockedGetEmailConfigPath.mockClear(); + + const outDir = path.join(temporaryProjectRoot, 'out'); + await exportTemplates(outDir, path.join(temporaryProjectRoot, 'emails'), { + silent: true, + pretty: true, + }); + + const calledConfigPath = mockedGetEmailConfig.mock.calls[0]?.[0]; + expect(calledConfigPath).toContain('react-email-export-config-'); + expect(path.basename(calledConfigPath ?? '')).toBe('email.config.ts'); + const calledEmailConfigPathArg = + mockedGetEmailConfigPath.mock.calls[0]?.[0]; + expect(calledEmailConfigPathArg).toContain('react-email-export-config-'); + expect(path.basename(calledEmailConfigPathArg ?? '')).toBe( + path.basename(temporaryProjectRoot), + ); + expect(mockedBuild).toHaveBeenCalledTimes(1); + expect(mockedBuild.mock.calls[0]?.[0].plugins).toEqual([ + expect.objectContaining({ name: 'rendering-utilities-exporter' }), + expect.objectContaining({ name: 'email-config-plugin' }), + ]); + expect(fs.existsSync(path.join(outDir, 'test-email.html'))).toBe(true); + } finally { + process.chdir(previousWorkingDirectory); + fs.rmSync(temporaryProjectRoot, { recursive: true, force: true }); + vi.restoreAllMocks(); + } +}); diff --git a/packages/react-email/src/cli/utils/preview/get-env-variables-for-preview-app.ts b/packages/react-email/src/cli/utils/preview/get-env-variables-for-preview-app.ts index 6b60cb279e..10a35a43e5 100644 --- a/packages/react-email/src/cli/utils/preview/get-env-variables-for-preview-app.ts +++ b/packages/react-email/src/cli/utils/preview/get-env-variables-for-preview-app.ts @@ -1,4 +1,5 @@ import path from 'node:path'; +import { getEmailConfigPath } from '../../../config/get-email-config-path.js'; export const getEnvVariablesForPreviewApp = ( relativePathToEmailsDirectory: string, @@ -15,6 +16,7 @@ export const getEnvVariablesForPreviewApp = ( ), REACT_EMAIL_INTERNAL_PREVIEW_SERVER_LOCATION: previewServerLocation, REACT_EMAIL_INTERNAL_USER_PROJECT_LOCATION: cwd, + REACT_EMAIL_INTERNAL_EMAIL_CONFIG_PATH: getEmailConfigPath(cwd), REACT_EMAIL_INTERNAL_RESEND_API_KEY: resendApiKey, } as const; }; diff --git a/packages/react-email/src/config/get-email-config-path.spec.ts b/packages/react-email/src/config/get-email-config-path.spec.ts new file mode 100644 index 0000000000..86924e1af4 --- /dev/null +++ b/packages/react-email/src/config/get-email-config-path.spec.ts @@ -0,0 +1,48 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { + getEmailConfigPath, + supportedEmailConfigFilenames, +} from './get-email-config-path'; + +describe('getEmailConfigPath()', () => { + let temporaryDirectory = ''; + + beforeEach(() => { + temporaryDirectory = fs.mkdtempSync( + path.join(os.tmpdir(), 'react-email-config-path-'), + ); + }); + + afterEach(() => { + fs.rmSync(temporaryDirectory, { recursive: true, force: true }); + }); + + it('returns undefined when there is no config file', () => { + expect(getEmailConfigPath(temporaryDirectory)).toBeUndefined(); + }); + + it.each( + supportedEmailConfigFilenames, + )('detects a %s config file', (filename) => { + const emailConfigPath = path.join(temporaryDirectory, filename); + fs.writeFileSync(emailConfigPath, 'export default {};\n', 'utf8'); + + expect(getEmailConfigPath(temporaryDirectory)).toBe(emailConfigPath); + }); + + it('prefers the first supported filename when multiple configs exist', () => { + for (const filename of supportedEmailConfigFilenames) { + fs.writeFileSync( + path.join(temporaryDirectory, filename), + 'export default {};\n', + 'utf8', + ); + } + + expect(getEmailConfigPath(temporaryDirectory)).toBe( + path.join(temporaryDirectory, supportedEmailConfigFilenames[0]!), + ); + }); +}); diff --git a/packages/react-email/src/config/get-email-config-path.ts b/packages/react-email/src/config/get-email-config-path.ts new file mode 100644 index 0000000000..2658b681e6 --- /dev/null +++ b/packages/react-email/src/config/get-email-config-path.ts @@ -0,0 +1,23 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +export const supportedEmailConfigFilenames = [ + 'email.config.ts', + 'email.config.mts', + 'email.config.cts', + 'email.config.js', + 'email.config.mjs', + 'email.config.cjs', +]; + +export const getEmailConfigPath = ( + userProjectLocation: string, +): string | undefined => { + for (const filename of supportedEmailConfigFilenames) { + const emailConfigPath = path.join(userProjectLocation, filename); + + if (fs.existsSync(emailConfigPath)) { + return emailConfigPath; + } + } +}; diff --git a/packages/react-email/src/config/get-email-config.spec.ts b/packages/react-email/src/config/get-email-config.spec.ts new file mode 100644 index 0000000000..86686d690a --- /dev/null +++ b/packages/react-email/src/config/get-email-config.spec.ts @@ -0,0 +1,67 @@ +import { createJiti } from 'jiti'; +import { getEmailConfig } from './get-email-config'; + +vi.mock('jiti', () => ({ + createJiti: vi.fn(), +})); + +describe('getEmailConfig()', () => { + const mockedCreateJiti = vi.mocked(createJiti); + + beforeEach(() => { + mockedCreateJiti.mockReset(); + }); + + it('returns an empty config when no path is provided', async () => { + await expect(getEmailConfig()).resolves.toEqual({}); + expect(mockedCreateJiti).not.toHaveBeenCalled(); + }); + + it('loads a config object from disk', async () => { + const importMock = vi.fn().mockResolvedValue({ + esbuild: { + plugins: [{ name: 'test-plugin', setup: vi.fn() }], + }, + }); + mockedCreateJiti.mockReturnValue({ + import: importMock, + } as unknown as ReturnType); + + const config = await getEmailConfig('/tmp/email.config.ts'); + + expect(config).toMatchObject({ + esbuild: { + plugins: [{ name: 'test-plugin' }], + }, + }); + + expect(mockedCreateJiti).toHaveBeenCalledWith('/tmp/email.config.ts'); + expect(importMock).toHaveBeenCalledWith('/tmp/email.config.ts', { + default: true, + }); + }); + + it('rejects configs that do not export an object', async () => { + mockedCreateJiti.mockReturnValue({ + import: vi.fn().mockResolvedValue(null), + } as unknown as ReturnType); + + await expect(getEmailConfig('/tmp/email.config.ts')).rejects.toThrow( + 'Expected React Email config at /tmp/email.config.ts to export an object.', + ); + }); + + it('rejects configs with a non-array esbuild.plugins value', async () => { + mockedCreateJiti.mockReturnValue({ + import: vi.fn().mockResolvedValue({ + esbuild: { + plugins: {}, + }, + }), + } as unknown as ReturnType); + + await expect(getEmailConfig('/tmp/email.config.ts')).rejects.toThrow( + 'Expected "esbuild.plugins" in React Email config at /tmp/email.config.ts to be an array.', + ); + }); +}); diff --git a/packages/react-email/src/config/get-email-config.ts b/packages/react-email/src/config/get-email-config.ts new file mode 100644 index 0000000000..72a4dc652f --- /dev/null +++ b/packages/react-email/src/config/get-email-config.ts @@ -0,0 +1,41 @@ +import { createJiti } from 'jiti'; +import type { EmailConfig } from './index.js'; + +export const getEmailConfig = async ( + emailConfigPath?: string, +): Promise => { + if (!emailConfigPath) { + return {}; + } + + let emailConfig: EmailConfig; + try { + const jiti = createJiti(emailConfigPath); + emailConfig = await jiti.import(emailConfigPath, { default: true }); + } catch (exception) { + throw new Error( + `Failed to load React Email config at ${emailConfigPath}.`, + { cause: exception }, + ); + } + + if ( + !emailConfig || + typeof emailConfig !== 'object' || + Array.isArray(emailConfig) + ) { + throw new Error( + `Expected React Email config at ${emailConfigPath} to export an object.`, + ); + } + + const plugins = emailConfig.esbuild?.plugins; + + if (typeof plugins !== 'undefined' && !Array.isArray(plugins)) { + throw new Error( + `Expected "esbuild.plugins" in React Email config at ${emailConfigPath} to be an array.`, + ); + } + + return emailConfig; +}; diff --git a/packages/react-email/src/config/index.ts b/packages/react-email/src/config/index.ts new file mode 100644 index 0000000000..d9dc90b905 --- /dev/null +++ b/packages/react-email/src/config/index.ts @@ -0,0 +1,12 @@ +import type { Plugin } from 'esbuild'; + +export interface EmailConfig { + esbuild?: { + plugins?: Plugin[]; + }; +} + +export const defineConfig = (config: T) => config; + +export * from './get-email-config.js'; +export * from './get-email-config-path.js'; diff --git a/packages/react-email/tsdown.config.ts b/packages/react-email/tsdown.config.ts index d2db1eec15..91b8d64796 100644 --- a/packages/react-email/tsdown.config.ts +++ b/packages/react-email/tsdown.config.ts @@ -42,6 +42,12 @@ export default defineConfig([ format: ['esm'], outDir: 'dist/cli', }, + { + dts: true, + entry: ['./src/config/index.ts'], + format: ['esm', 'cjs'], + outDir: 'dist/config', + }, { dts: true, entry: ['./src/index.ts'], diff --git a/packages/ui/next.config.mjs b/packages/ui/next.config.mjs index 91cc898c4d..2f10db4cb9 100644 --- a/packages/ui/next.config.mjs +++ b/packages/ui/next.config.mjs @@ -2,7 +2,7 @@ * @type {import('next').NextConfig} */ const nextConfig = { - serverExternalPackages: ['esbuild'], + serverExternalPackages: ['esbuild', 'jiti'], // Noticed an issue with typescript transpilation when going from Next 14.1.1 to 14.1.2 // and I narrowed that down into this PR https://github.com/vercel/next.js/pull/62005 // diff --git a/packages/ui/package.json b/packages/ui/package.json index 1f5c071acf..c4b7a1ffc4 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -27,7 +27,9 @@ ], "dependencies": { "esbuild": "catalog:", - "next": "catalog:" + "jiti": "catalog:", + "next": "catalog:", + "react-email": "workspace:*" }, "devDependencies": { "@babel/core": "7.29.0", @@ -43,7 +45,6 @@ "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-tooltip": "catalog:", - "react-email": "workspace:*", "@tailwindcss/postcss": "catalog:", "@types/babel__core": "catalog:", "@types/babel__traverse": "catalog:", diff --git a/packages/ui/src/app/env.ts b/packages/ui/src/app/env.ts index b0b297aea8..b96f3fc024 100644 --- a/packages/ui/src/app/env.ts +++ b/packages/ui/src/app/env.ts @@ -2,6 +2,10 @@ export const userProjectLocation = process.env.REACT_EMAIL_INTERNAL_USER_PROJECT_LOCATION!; +/** ONLY ACCESSIBLE ON THE SERVER */ +export const emailConfigPath = + process.env.REACT_EMAIL_INTERNAL_EMAIL_CONFIG_PATH; + /** ONLY ACCESSIBLE ON THE SERVER */ export const previewServerLocation = process.env.REACT_EMAIL_INTERNAL_PREVIEW_SERVER_LOCATION!; diff --git a/packages/ui/src/utils/get-email-component.spec.ts b/packages/ui/src/utils/get-email-component.spec.ts index 46fbce45f0..544b56881c 100644 --- a/packages/ui/src/utils/get-email-component.spec.ts +++ b/packages/ui/src/utils/get-email-component.spec.ts @@ -1,7 +1,33 @@ +import fs from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; +import { build } from 'esbuild'; import { getEmailComponent } from './get-email-component'; +let mockedEmailConfigPath: string | undefined; + +vi.mock('../app/env', () => ({ + get emailConfigPath() { + return mockedEmailConfigPath; + }, +})); + +vi.mock('esbuild', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + build: vi.fn(actual.build), + }; +}); + describe('getEmailComponent()', () => { + const mockedBuild = vi.mocked(build); + + beforeEach(() => { + mockedBuild.mockClear(); + mockedEmailConfigPath = undefined; + }); + test('with a demo email template', { timeout: 10_000 }, async () => { const result = await getEmailComponent( path.resolve(__dirname, './testing/vercel-invite-user.tsx'), @@ -232,4 +258,110 @@ describe('getEmailComponent()', () => { `); } }); + + test('uses email config plugins when a config file exists', async () => { + const temporaryProjectRoot = fs.mkdtempSync( + path.join(os.tmpdir(), 'react-email-ui-config-'), + ); + const previousWorkingDirectory = process.cwd(); + + try { + process.chdir(temporaryProjectRoot); + mockedEmailConfigPath = path.join( + temporaryProjectRoot, + 'email.config.cjs', + ); + + fs.writeFileSync( + path.join(temporaryProjectRoot, 'email.config.cjs'), + ` +module.exports = { + esbuild: { + plugins: [{ + name: 'email-config-plugin', + setup() {}, + }], + }, +}; + `, + 'utf8', + ); + + mockedBuild.mockImplementation(async () => { + fs.writeFileSync( + path.join(temporaryProjectRoot, 'email.js.map'), + JSON.stringify({ + version: 3, + sources: [], + names: [], + mappings: '', + }), + 'utf8', + ); + fs.writeFileSync( + path.join(temporaryProjectRoot, 'email.js'), + ` +export default function Email() { + return null; +} +export async function render() { + return 'rendered'; +} +export function reactEmailCreateReactElement(type, props) { + return { type, props }; +} + `, + 'utf8', + ); + + return { + outputFiles: [ + { + path: path.join(temporaryProjectRoot, 'email.js.map'), + text: JSON.stringify({ + version: 3, + sources: [], + names: [], + mappings: '', + }), + }, + { + path: path.join(temporaryProjectRoot, 'email.js'), + text: ` +export default function Email() { + return null; +} +export async function render() { + return 'rendered'; +} +export function reactEmailCreateReactElement(type, props) { + return { type, props }; +} + `, + }, + ], + } as any; + }); + + const result = await getEmailComponent( + path.resolve(__dirname, './testing/vercel-invite-user.tsx'), + path.resolve(__dirname, '../../jsx-runtime'), + ); + + expect('error' in result).toBe(false); + if ('error' in result) { + return; + } + + expect(mockedBuild).toHaveBeenCalledTimes(1); + expect(mockedBuild.mock.calls[0]?.[0].plugins).toEqual([ + expect.objectContaining({ name: 'rendering-utilities-exporter' }), + expect.objectContaining({ name: 'email-config-plugin' }), + ]); + } finally { + process.chdir(previousWorkingDirectory); + fs.rmSync(temporaryProjectRoot, { recursive: true, force: true }); + mockedEmailConfigPath = undefined; + } + }); }); diff --git a/packages/ui/src/utils/get-email-component.ts b/packages/ui/src/utils/get-email-component.ts index 51a67cf5eb..acfc36015d 100644 --- a/packages/ui/src/utils/get-email-component.ts +++ b/packages/ui/src/utils/get-email-component.ts @@ -2,8 +2,10 @@ import path from 'node:path'; import { type BuildFailure, build, type OutputFile } from 'esbuild'; import type React from 'react'; import type { render } from 'react-email'; +import { getEmailConfig } from 'react-email/config'; import type { RawSourceMap } from 'source-map-js'; import { z } from 'zod'; +import { emailConfigPath } from '../app/env'; import { convertStackWithSourceMap } from './convert-stack-with-sourcemap'; import { renderingUtilitiesExporter } from './esbuild/renderring-utilities-exporter'; import { isErr } from './result'; @@ -57,10 +59,13 @@ export const getEmailComponent = async ( > => { let outputFiles: OutputFile[]; try { + const emailConfig = await getEmailConfig(emailConfigPath); + const emailConfigPlugins = emailConfig.esbuild?.plugins ?? []; + const buildData = await build({ bundle: true, entryPoints: [emailPath], - plugins: [renderingUtilitiesExporter([emailPath])], + plugins: [renderingUtilitiesExporter([emailPath]), ...emailConfigPlugins], platform: 'node', write: false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 561fd4f002..87793910b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,6 +69,9 @@ catalogs: framer-motion: specifier: 12.38.0 version: 12.38.0 + jiti: + specifier: 2.4.2 + version: 2.4.2 log-symbols: specifier: ^7.0.0 version: 7.0.1 @@ -688,7 +691,7 @@ importers: specifier: ^13.0.6 version: 13.0.6 jiti: - specifier: 2.4.2 + specifier: 'catalog:' version: 2.4.2 log-symbols: specifier: 'catalog:' @@ -828,9 +831,15 @@ importers: esbuild: specifier: 'catalog:' version: 0.28.0 + jiti: + specifier: 'catalog:' + version: 2.4.2 next: specifier: 'catalog:' version: 16.2.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-email: + specifier: workspace:* + version: link:../react-email devDependencies: '@babel/core': specifier: 7.29.0 @@ -946,9 +955,6 @@ importers: react-dom: specifier: 19.2.4 version: 19.2.4(react@19.2.4) - react-email: - specifier: workspace:* - version: link:../react-email resend: specifier: 'catalog:' version: 6.4.0(@react-email/render@2.0.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a449c6cef4..d7fa927a84 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -37,6 +37,7 @@ catalog: commander: ^13.0.0 esbuild: 0.28.0 framer-motion: 12.38.0 + jiti: 2.4.2 log-symbols: ^7.0.0 next: 16.2.6 nypm: 0.6.6