From be8cc2868f6ffecc36e333899aa2b0f78bee9ccf Mon Sep 17 00:00:00 2001 From: Hosmel Quintana Date: Thu, 9 Oct 2025 10:47:56 -0600 Subject: [PATCH 1/3] support custom export extension --- apps/docs/cli.mdx | 4 + packages/react-email/src/commands/export.ts | 13 +- .../testing/__snapshots__/export.spec.ts.snap | 196 ++++++++++++++++++ .../src/commands/testing/export.spec.ts | 19 ++ packages/react-email/src/index.ts | 8 +- 5 files changed, 234 insertions(+), 6 deletions(-) diff --git a/apps/docs/cli.mdx b/apps/docs/cli.mdx index 1651ca0342..7590eaaace 100644 --- a/apps/docs/cli.mdx +++ b/apps/docs/cli.mdx @@ -190,6 +190,10 @@ Generates the plain HTML files of your emails into a `out` directory. Change the directory of your email templates. + + Set a custom file extension for rendered templates (for example, `blade.php`). When omitted, + the extension defaults to `.html`, or `.txt` when `--plainText` is enabled. + ## `email help ` diff --git a/packages/react-email/src/commands/export.ts b/packages/react-email/src/commands/export.ts index 1086bd4adf..9f14927be4 100644 --- a/packages/react-email/src/commands/export.ts +++ b/packages/react-email/src/commands/export.ts @@ -30,6 +30,7 @@ const getEmailTemplatesFromDirectory = (emailDirectory: EmailsDirectory) => { }; type ExportTemplatesOptions = Options & { + extension?: string; silent?: boolean; pretty?: boolean; }; @@ -115,6 +116,13 @@ export const exportTemplates = async ( }, ); + const extension = + options.extension && options.extension.length > 0 + ? `.${options.extension}` + : options.plainText + ? '.txt' + : '.html'; + for await (const template of allBuiltTemplates) { try { if (spinner) { @@ -134,10 +142,7 @@ export const exportTemplates = async ( emailModule.reactEmailCreateReactElement(emailModule.default, {}), options, ); - const htmlPath = template.replace( - '.cjs', - options.plainText ? '.txt' : '.html', - ); + const htmlPath = template.replace('.cjs', extension); writeFileSync(htmlPath, rendered); unlinkSync(template); } catch (exception) { diff --git a/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap b/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap index 615acfb5ad..9304056de9 100644 --- a/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap +++ b/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap @@ -195,3 +195,199 @@ exports[`email export 1`] = ` " `; + +exports[`email export with custom extension 1`] = ` +" + + + + + + + + + + + + + + + +
+
+ Join undefined on Vercel +
+  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ +
+
+ + + + + + +
+ + + + + + +
+ Vercel Logo +
+

+ Join on Vercel +

+

+ Hello + , +

+

+ () has invited you to the team on + Vercel. +

+ + + + + + +
+ + + + + + + + +
+ undefined's profile picture + + Arrow indicating invitation + + undefined team logo +
+
+ + + + + + +
+ Join the team +
+

+ or copy and paste this URL into your browser: + +

+
+

+ This invitation was intended for + . This invite was + sent from + located in + . If you were not + expecting this invitation, you can ignore this email. If + you are concerned about your account's safety, please + reply to this email to get in touch with us. +

+
+
+ + + +" +`; diff --git a/packages/react-email/src/commands/testing/export.spec.ts b/packages/react-email/src/commands/testing/export.spec.ts index 0f688b30ab..ea7075f449 100644 --- a/packages/react-email/src/commands/testing/export.spec.ts +++ b/packages/react-email/src/commands/testing/export.spec.ts @@ -18,3 +18,22 @@ test('email export', { retry: 3 }, async () => { ), ).toMatchSnapshot(); }); + +test('email export with custom extension', { retry: 3 }, async () => { + const pathToEmailsDirectory = path.resolve(__dirname, './emails'); + const pathToDumpMarkup = path.resolve(__dirname, './out'); + + await exportTemplates(pathToDumpMarkup, pathToEmailsDirectory, { + silent: true, + pretty: true, + extension: 'blade.php', + }); + + const outputFile = path.resolve( + pathToDumpMarkup, + './vercel-invite-user.blade.php', + ); + + expect(fs.existsSync(outputFile)).toBe(true); + expect(await fs.promises.readFile(outputFile, 'utf8')).toMatchSnapshot(); +}); diff --git a/packages/react-email/src/index.ts b/packages/react-email/src/index.ts index 43f4fba483..c9b8ea862b 100644 --- a/packages/react-email/src/index.ts +++ b/packages/react-email/src/index.ts @@ -43,13 +43,17 @@ program .option('-p, --pretty', 'Pretty print the output', false) .option('-t, --plainText', 'Set output format as plain text', false) .option('-d, --dir ', 'Directory with your email templates', './emails') + .option( + '-e, --extension ', + 'Set a custom file extension for rendered emails (e.g. blade.php)', + ) .option( '-s, --silent', 'To, or not to show a spinner with process information', false, ) - .action(({ outDir, pretty, plainText, silent, dir: srcDir }) => - exportTemplates(outDir, srcDir, { silent, plainText, pretty }), + .action(({ outDir, pretty, plainText, silent, dir: srcDir, extension }) => + exportTemplates(outDir, srcDir, { silent, plainText, pretty, extension }), ); program.parse(); From 14668c27106b846cf57592d737a8c761f43be3b0 Mon Sep 17 00:00:00 2001 From: Hosmel Quintana Date: Thu, 9 Oct 2025 11:28:36 -0600 Subject: [PATCH 2/3] apply suggestion --- packages/react-email/src/commands/export.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-email/src/commands/export.ts b/packages/react-email/src/commands/export.ts index 9f14927be4..b77be35935 100644 --- a/packages/react-email/src/commands/export.ts +++ b/packages/react-email/src/commands/export.ts @@ -118,7 +118,9 @@ export const exportTemplates = async ( const extension = options.extension && options.extension.length > 0 - ? `.${options.extension}` + ? options.extension.startsWith('.') + ? options.extension + : `.${options.extension}` : options.plainText ? '.txt' : '.html'; From 9bd801427315111be84faede093a1a9c3ca0e8df Mon Sep 17 00:00:00 2001 From: Hosmel Quintana Date: Thu, 9 Oct 2025 11:29:10 -0600 Subject: [PATCH 3/3] add changeset --- .changeset/custom-extension-cli.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/custom-extension-cli.md diff --git a/.changeset/custom-extension-cli.md b/.changeset/custom-extension-cli.md new file mode 100644 index 0000000000..fe32ceba9a --- /dev/null +++ b/.changeset/custom-extension-cli.md @@ -0,0 +1,5 @@ +--- +"react-email": minor +--- + +Enable custom export extensions via --extension/-e (e.g. .blade.php).