react-email 5.0.0-canary.0 → 5.0.0-canary.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # react-email
2
2
 
3
+ ## 5.0.0-canary.10
4
+
5
+ ## 5.0.0-canary.9
6
+
7
+ ## 5.0.0-canary.8
8
+
9
+ ### Patch Changes
10
+
11
+ - b6b027c: improved integration setup flow
12
+
13
+ ## 5.0.0-canary.7
14
+
15
+ ## 5.0.0-canary.6
16
+
17
+ ## 5.0.0-canary.5
18
+
19
+ ## 5.0.0-canary.4
20
+
21
+ ## 5.0.0-canary.3
22
+
23
+ ### Minor Changes
24
+
25
+ - 95c7417: Dark mode switcher emulating email client color inversion
26
+
27
+ ## 5.0.0-canary.2
28
+
29
+ ### Patch Changes
30
+
31
+ - 1b3176e: fallback to not text coloring for Node.js < 20
32
+
33
+ ## 5.0.0-canary.1
34
+
3
35
  ## 5.0.0-canary.0
4
36
 
5
37
  ## 4.3.2
package/dist/index.js CHANGED
@@ -17,7 +17,8 @@ import { parse } from "@babel/parser";
17
17
  import traverseModule from "@babel/traverse";
18
18
  import { createMatchPath, loadConfig } from "tsconfig-paths";
19
19
  import http from "node:http";
20
- import { styleText } from "node:util";
20
+ import Conf from "conf";
21
+ import * as nodeUtil from "node:util";
21
22
  import { lookup } from "mime-types";
22
23
  import os from "node:os";
23
24
  import { build } from "esbuild";
@@ -86,7 +87,7 @@ const getEmailsDirectoryMetadata = async (absolutePathToEmailsDirectory, keepFil
86
87
  //#region package.json
87
88
  var package_default = {
88
89
  name: "react-email",
89
- version: "5.0.0-canary.0",
90
+ version: "5.0.0-canary.10",
90
91
  description: "A live preview of your emails right in your browser.",
91
92
  bin: { "email": "./dist/index.js" },
92
93
  type: "module",
@@ -104,12 +105,13 @@ var package_default = {
104
105
  "directory": "packages/react-email"
105
106
  },
106
107
  keywords: ["react", "email"],
107
- engines: { "node": ">=18.0.0" },
108
+ engines: { "node": ">=22.0.0" },
108
109
  dependencies: {
109
110
  "@babel/parser": "^7.27.0",
110
111
  "@babel/traverse": "^7.27.0",
111
112
  "chokidar": "^4.0.3",
112
113
  "commander": "^13.0.0",
114
+ "conf": "^15.0.2",
113
115
  "debounce": "^2.0.0",
114
116
  "esbuild": "^0.25.0",
115
117
  "glob": "^11.0.0",
@@ -129,7 +131,7 @@ var package_default = {
129
131
  "@types/babel__traverse": "7.20.7",
130
132
  "@types/mime-types": "2.1.4",
131
133
  "@types/prompts": "2.4.9",
132
- "next": "^15.3.2",
134
+ "next": "16.0.1",
133
135
  "react": "19.0.0",
134
136
  "react-dom": "19.0.0",
135
137
  "typescript": "5.8.3"
@@ -228,37 +230,22 @@ module.exports = {
228
230
  PREVIEW_SERVER_LOCATION: '${builtPreviewAppPath.replace(/\\/g, "/")}',
229
231
  USER_PROJECT_LOCATION: userProjectLocation
230
232
  },
231
- // this is needed so that the code for building emails works properly
232
- webpack: (
233
- /** @type {import('webpack').Configuration & { externals: string[] }} */
234
- config,
235
- { isServer }
236
- ) => {
237
- if (isServer) {
238
- config.externals.push('esbuild');
239
- }
240
-
241
- return config;
242
- },
233
+ serverExternalPackages: ['esbuild'],
243
234
  typescript: {
244
235
  ignoreBuildErrors: true
245
236
  },
246
- eslint: {
247
- ignoreDuringBuilds: true
248
- },
249
237
  experimental: {
250
238
  webpackBuildWorker: true
251
239
  },
252
240
  }`;
241
+ await fs.promises.rm(path.resolve(builtPreviewAppPath, "./next.config.ts"));
253
242
  await fs.promises.writeFile(path.resolve(builtPreviewAppPath, "./next.config.js"), nextConfigContents, "utf8");
254
243
  };
255
244
  const getEmailSlugsFromEmailDirectory = (emailDirectory, emailsDirectoryAbsolutePath) => {
256
245
  const directoryPathRelativeToEmailsDirectory = emailDirectory.absolutePath.replace(emailsDirectoryAbsolutePath, "").trim();
257
246
  const slugs = [];
258
- emailDirectory.emailFilenames.forEach((filename) => slugs.push(path.join(directoryPathRelativeToEmailsDirectory, filename).split(path.sep).filter((segment) => segment.length > 0)));
259
- emailDirectory.subDirectories.forEach((directory) => {
260
- slugs.push(...getEmailSlugsFromEmailDirectory(directory, emailsDirectoryAbsolutePath));
261
- });
247
+ for (const filename of emailDirectory.emailFilenames) slugs.push(path.join(directoryPathRelativeToEmailsDirectory, filename).split(path.sep).filter((segment) => segment.length > 0));
248
+ for (const directory of emailDirectory.subDirectories) slugs.push(...getEmailSlugsFromEmailDirectory(directory, emailsDirectoryAbsolutePath));
262
249
  return slugs;
263
250
  };
264
251
  const forceSSGForEmailPreviews = async (emailsDirPath, builtPreviewAppPath) => {
@@ -280,12 +267,11 @@ export function generateStaticParams() {
280
267
  const updatePackageJson = async (builtPreviewAppPath) => {
281
268
  const packageJsonPath = path.resolve(builtPreviewAppPath, "./package.json");
282
269
  const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf8"));
283
- packageJson.scripts.build = "next build";
270
+ packageJson.scripts.build = "next build --webpack";
284
271
  packageJson.scripts.start = "next start";
285
272
  delete packageJson.scripts.postbuild;
286
273
  packageJson.name = "preview-server";
287
274
  for (const [dependency, version$1] of Object.entries(packageJson.dependencies)) packageJson.dependencies[dependency] = version$1.replace("workspace:", "");
288
- delete packageJson.devDependencies["@react-email/render"];
289
275
  delete packageJson.devDependencies["@react-email/components"];
290
276
  delete packageJson.scripts.prepare;
291
277
  await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageJson), "utf8");
@@ -601,14 +587,27 @@ const setupHotreloading = async (devServer$1, emailDirRelativePath) => {
601
587
  return watcher;
602
588
  };
603
589
 
590
+ //#endregion
591
+ //#region src/utils/conf.ts
592
+ const encryptionKey = "h2#x658}1#qY(@!:7,BD1J)q12$[tM25";
593
+ const conf = new Conf({
594
+ projectName: "react-email",
595
+ encryptionKey
596
+ });
597
+
598
+ //#endregion
599
+ //#region src/utils/style-text.ts
600
+ const styleText = nodeUtil.styleText ? nodeUtil.styleText : (_, text) => text;
601
+
604
602
  //#endregion
605
603
  //#region src/utils/preview/get-env-variables-for-preview-app.ts
606
- const getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, previewServerLocation, cwd) => {
604
+ const getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, previewServerLocation, cwd, resendApiKey) => {
607
605
  return {
608
606
  EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
609
607
  EMAILS_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEmailsDirectory),
610
608
  PREVIEW_SERVER_LOCATION: previewServerLocation,
611
- USER_PROJECT_LOCATION: cwd
609
+ USER_PROJECT_LOCATION: cwd,
610
+ RESEND_API_KEY: resendApiKey
612
611
  };
613
612
  };
614
613
 
@@ -658,8 +657,8 @@ const safeAsyncServerListen = (server, port) => {
658
657
  };
659
658
  const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port) => {
660
659
  const [majorNodeVersion] = process.versions.node.split(".");
661
- if (majorNodeVersion && Number.parseInt(majorNodeVersion) < 18) {
662
- console.error(` ${logSymbols.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 18 or higher.`);
660
+ if (majorNodeVersion && Number.parseInt(majorNodeVersion, 10) < 20) {
661
+ console.error(` ${logSymbols.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 20 or higher.`);
663
662
  process.exit(1);
664
663
  }
665
664
  const previewServerLocation = await getPreviewServerLocation();
@@ -711,7 +710,7 @@ const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath,
711
710
  process.env = {
712
711
  NODE_ENV: "development",
713
712
  ...process.env,
714
- ...getEnvVariablesForPreviewApp(path.normalize(emailsDirRelativePath), previewServerLocation, process.cwd())
713
+ ...getEnvVariablesForPreviewApp(path.normalize(emailsDirRelativePath), previewServerLocation, process.cwd(), conf.get("resendApiKey"))
715
714
  };
716
715
  const app = (await previewServer.import("next", { default: true }))({
717
716
  dev: false,
@@ -813,7 +812,7 @@ const dev = async ({ dir: emailsDirRelativePath, port }) => {
813
812
  console.error(`Missing ${emailsDirRelativePath} folder`);
814
813
  process.exit(1);
815
814
  }
816
- await setupHotreloading(await startDevServer(emailsDirRelativePath, emailsDirRelativePath, Number.parseInt(port)), emailsDirRelativePath);
815
+ await setupHotreloading(await startDevServer(emailsDirRelativePath, emailsDirRelativePath, Number.parseInt(port, 10)), emailsDirRelativePath);
817
816
  } catch (error) {
818
817
  console.log(error);
819
818
  process.exit(1);
@@ -871,10 +870,8 @@ const renderingUtilitiesExporter = (emailTemplates) => ({
871
870
  //#region src/commands/export.ts
872
871
  const getEmailTemplatesFromDirectory = (emailDirectory) => {
873
872
  const templatePaths = [];
874
- emailDirectory.emailFilenames.forEach((filename) => templatePaths.push(path.join(emailDirectory.absolutePath, filename)));
875
- emailDirectory.subDirectories.forEach((directory) => {
876
- templatePaths.push(...getEmailTemplatesFromDirectory(directory));
877
- });
873
+ for (const filename of emailDirectory.emailFilenames) templatePaths.push(path.join(emailDirectory.absolutePath, filename));
874
+ for (const directory of emailDirectory.subDirectories) templatePaths.push(...getEmailTemplatesFromDirectory(directory));
878
875
  return templatePaths;
879
876
  };
880
877
  const require = createRequire(url.fileURLToPath(import.meta.url));
@@ -969,6 +966,30 @@ const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirec
969
966
  }
970
967
  };
971
968
 
969
+ //#endregion
970
+ //#region src/commands/resend/reset.ts
971
+ async function resendReset() {
972
+ conf.delete("resendApiKey");
973
+ console.info(`${logSymbols.success} Resend API Key successfully deleted`);
974
+ }
975
+
976
+ //#endregion
977
+ //#region src/commands/resend/setup.ts
978
+ async function resendSetup() {
979
+ const previousValue = conf.get("resendApiKey");
980
+ if (typeof previousValue === "string" && previousValue.length > 0) console.info(`You already have a Resend API Key configured (${styleText("grey", previousValue.slice(0, 11))}...), continuing will replace it.`);
981
+ const { apiKey } = await prompts({
982
+ type: "password",
983
+ name: "apiKey",
984
+ message: "Enter your API Key (make sure it has \"Full Access\")"
985
+ });
986
+ if (apiKey?.trim().length > 0) {
987
+ conf.set("resendApiKey", apiKey);
988
+ console.info(`${logSymbols.success} Resend integration successfully set up`);
989
+ console.info(`You can always remove it with ${styleText("green", "npx react-email@latest resend reset")}`);
990
+ }
991
+ }
992
+
972
993
  //#endregion
973
994
  //#region src/commands/start.ts
974
995
  const start = async () => {
@@ -1011,6 +1032,9 @@ program.command("export").description("Build the templates to the `out` director
1011
1032
  plainText,
1012
1033
  pretty
1013
1034
  }));
1035
+ const resend = program.command("resend");
1036
+ resend.command("setup").description("Sets up the integration between the React Email CLI, and your Resend account through an API Key").action(resendSetup);
1037
+ resend.command("reset").description("Deletes your API Key from the React Email configuration").action(resendReset);
1014
1038
  program.parse();
1015
1039
 
1016
1040
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "5.0.0-canary.0",
3
+ "version": "5.0.0-canary.10",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/index.js"
@@ -17,13 +17,14 @@
17
17
  "email"
18
18
  ],
19
19
  "engines": {
20
- "node": ">=18.0.0"
20
+ "node": ">=22.0.0"
21
21
  },
22
22
  "dependencies": {
23
23
  "@babel/parser": "^7.27.0",
24
24
  "@babel/traverse": "^7.27.0",
25
25
  "chokidar": "^4.0.3",
26
26
  "commander": "^13.0.0",
27
+ "conf": "^15.0.2",
27
28
  "debounce": "^2.0.0",
28
29
  "esbuild": "^0.25.0",
29
30
  "glob": "^11.0.0",
@@ -42,11 +43,11 @@
42
43
  "@types/babel__traverse": "7.20.7",
43
44
  "@types/mime-types": "2.1.4",
44
45
  "@types/prompts": "2.4.9",
45
- "next": "^15.3.2",
46
+ "next": "16.0.1",
46
47
  "react": "19.0.0",
47
48
  "react-dom": "19.0.0",
48
49
  "typescript": "5.8.3",
49
- "@react-email/components": "1.0.0-canary.1"
50
+ "@react-email/components": "1.0.0-canary.7"
50
51
  },
51
52
  "scripts": {
52
53
  "build": "tsdown",
@@ -88,29 +88,17 @@ module.exports = {
88
88
  PREVIEW_SERVER_LOCATION: '${builtPreviewAppPath.replace(/\\/g, '/')}',
89
89
  USER_PROJECT_LOCATION: userProjectLocation
90
90
  },
91
- // this is needed so that the code for building emails works properly
92
- webpack: (
93
- /** @type {import('webpack').Configuration & { externals: string[] }} */
94
- config,
95
- { isServer }
96
- ) => {
97
- if (isServer) {
98
- config.externals.push('esbuild');
99
- }
100
-
101
- return config;
102
- },
91
+ serverExternalPackages: ['esbuild'],
103
92
  typescript: {
104
93
  ignoreBuildErrors: true
105
94
  },
106
- eslint: {
107
- ignoreDuringBuilds: true
108
- },
109
95
  experimental: {
110
96
  webpackBuildWorker: true
111
97
  },
112
98
  }`;
113
99
 
100
+ await fs.promises.rm(path.resolve(builtPreviewAppPath, './next.config.ts'));
101
+
114
102
  await fs.promises.writeFile(
115
103
  path.resolve(builtPreviewAppPath, './next.config.js'),
116
104
  nextConfigContents,
@@ -127,23 +115,23 @@ const getEmailSlugsFromEmailDirectory = (
127
115
  .trim();
128
116
 
129
117
  const slugs = [] as Array<string>[];
130
- emailDirectory.emailFilenames.forEach((filename) =>
118
+ for (const filename of emailDirectory.emailFilenames) {
131
119
  slugs.push(
132
120
  path
133
121
  .join(directoryPathRelativeToEmailsDirectory, filename)
134
122
  .split(path.sep)
135
123
  // sometimes it gets empty segments due to trailing slashes
136
124
  .filter((segment) => segment.length > 0),
137
- ),
138
- );
139
- emailDirectory.subDirectories.forEach((directory) => {
125
+ );
126
+ }
127
+ for (const directory of emailDirectory.subDirectories) {
140
128
  slugs.push(
141
129
  ...getEmailSlugsFromEmailDirectory(
142
130
  directory,
143
131
  emailsDirectoryAbsolutePath,
144
132
  ),
145
133
  );
146
- });
134
+ }
147
135
 
148
136
  return slugs;
149
137
  };
@@ -202,7 +190,8 @@ const updatePackageJson = async (builtPreviewAppPath: string) => {
202
190
  dependencies: Record<string, string>;
203
191
  devDependencies: Record<string, string>;
204
192
  };
205
- packageJson.scripts.build = 'next build';
193
+ // Turbopack has some errors with the imports in @react-email/tailwind
194
+ packageJson.scripts.build = 'next build --webpack';
206
195
  packageJson.scripts.start = 'next start';
207
196
  delete packageJson.scripts.postbuild;
208
197
 
@@ -214,12 +203,6 @@ const updatePackageJson = async (builtPreviewAppPath: string) => {
214
203
  packageJson.dependencies[dependency] = version.replace('workspace:', '');
215
204
  }
216
205
 
217
- // We remove this one to avoid having resolve issues on our demo build process.
218
- // This is only used in the `export` command so it's irrelevant to have it here.
219
- //
220
- // See `src/actions/render-email-by-path` for more info on how we render the
221
- // email templates without `@react-email/render` being installed.
222
- delete packageJson.devDependencies['@react-email/render'];
223
206
  delete packageJson.devDependencies['@react-email/components'];
224
207
  delete packageJson.scripts.prepare;
225
208
 
@@ -16,7 +16,7 @@ export const dev = async ({ dir: emailsDirRelativePath, port }: Args) => {
16
16
  const devServer = await startDevServer(
17
17
  emailsDirRelativePath,
18
18
  emailsDirRelativePath, // defaults to ./emails/static for the static files that are served to the preview
19
- Number.parseInt(port),
19
+ Number.parseInt(port, 10),
20
20
  );
21
21
 
22
22
  await setupHotreloading(devServer, emailsDirRelativePath);
@@ -19,12 +19,12 @@ import { registerSpinnerAutostopping } from '../utils/register-spinner-autostopp
19
19
 
20
20
  const getEmailTemplatesFromDirectory = (emailDirectory: EmailsDirectory) => {
21
21
  const templatePaths = [] as string[];
22
- emailDirectory.emailFilenames.forEach((filename) =>
23
- templatePaths.push(path.join(emailDirectory.absolutePath, filename)),
24
- );
25
- emailDirectory.subDirectories.forEach((directory) => {
22
+ for (const filename of emailDirectory.emailFilenames) {
23
+ templatePaths.push(path.join(emailDirectory.absolutePath, filename));
24
+ }
25
+ for (const directory of emailDirectory.subDirectories) {
26
26
  templatePaths.push(...getEmailTemplatesFromDirectory(directory));
27
- });
27
+ }
28
28
 
29
29
  return templatePaths;
30
30
  };
@@ -0,0 +1,8 @@
1
+ import logSymbols from 'log-symbols';
2
+ import { conf } from '../../utils/conf.js';
3
+
4
+ export async function resendReset() {
5
+ conf.delete('resendApiKey');
6
+
7
+ console.info(`${logSymbols.success} Resend API Key successfully deleted`);
8
+ }
@@ -0,0 +1,29 @@
1
+ import logSymbols from 'log-symbols';
2
+ import prompts from 'prompts';
3
+ import { conf } from '../../utils/conf.js';
4
+ import { styleText } from '../../utils/style-text.js';
5
+
6
+ export async function resendSetup() {
7
+ const previousValue = conf.get('resendApiKey');
8
+ if (typeof previousValue === 'string' && previousValue.length > 0) {
9
+ console.info(
10
+ `You already have a Resend API Key configured (${styleText('grey', previousValue.slice(0, 11))}...), continuing will replace it.`,
11
+ );
12
+ }
13
+
14
+ const { apiKey } = await prompts({
15
+ type: 'password',
16
+ name: 'apiKey',
17
+ message: 'Enter your API Key (make sure it has "Full Access")',
18
+ });
19
+
20
+ if (apiKey?.trim().length > 0) {
21
+ conf.set('resendApiKey', apiKey);
22
+ console.info(
23
+ `${logSymbols.success} Resend integration successfully set up`,
24
+ );
25
+ console.info(
26
+ `You can always remove it with ${styleText('green', 'npx react-email@latest resend reset')}`,
27
+ );
28
+ }
29
+ }
package/src/index.ts CHANGED
@@ -3,6 +3,8 @@ import { program } from 'commander';
3
3
  import { build } from './commands/build.js';
4
4
  import { dev } from './commands/dev.js';
5
5
  import { exportTemplates } from './commands/export.js';
6
+ import { resendReset } from './commands/resend/reset.js';
7
+ import { resendSetup } from './commands/resend/setup.js';
6
8
  import { start } from './commands/start.js';
7
9
  import { packageJson } from './utils/packageJson.js';
8
10
 
@@ -52,4 +54,18 @@ program
52
54
  exportTemplates(outDir, srcDir, { silent, plainText, pretty }),
53
55
  );
54
56
 
57
+ const resend = program.command('resend');
58
+
59
+ resend
60
+ .command('setup')
61
+ .description(
62
+ 'Sets up the integration between the React Email CLI, and your Resend account through an API Key',
63
+ )
64
+ .action(resendSetup);
65
+
66
+ resend
67
+ .command('reset')
68
+ .description('Deletes your API Key from the React Email configuration')
69
+ .action(resendReset);
70
+
55
71
  program.parse();
@@ -16,12 +16,14 @@ exports[`tree(__dirname, 2) 1`] = `
16
16
  ├── types
17
17
  │ ├── hot-reload-change.ts
18
18
  │ └── hot-reload-event.ts
19
+ ├── conf.ts
19
20
  ├── get-emails-directory-metadata.spec.ts
20
21
  ├── get-emails-directory-metadata.ts
21
22
  ├── get-preview-server-location.ts
22
23
  ├── index.ts
23
24
  ├── packageJson.ts
24
25
  ├── register-spinner-autostopping.ts
26
+ ├── style-text.ts
25
27
  ├── tree.spec.ts
26
28
  └── tree.ts"
27
29
  `;
@@ -0,0 +1,9 @@
1
+ import Conf from 'conf';
2
+
3
+ // Just simple encryption. This isn't completely safe
4
+ // because anyone can find this key here
5
+ const encryptionKey = 'h2#x658}1#qY(@!:7,BD1J)q12$[tM25';
6
+
7
+ export const conf = new Conf<{
8
+ resendApiKey?: string;
9
+ }>({ projectName: 'react-email', encryptionKey });
@@ -4,11 +4,13 @@ export const getEnvVariablesForPreviewApp = (
4
4
  relativePathToEmailsDirectory: string,
5
5
  previewServerLocation: string,
6
6
  cwd: string,
7
+ resendApiKey?: string,
7
8
  ) => {
8
9
  return {
9
10
  EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
10
11
  EMAILS_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEmailsDirectory),
11
12
  PREVIEW_SERVER_LOCATION: previewServerLocation,
12
13
  USER_PROJECT_LOCATION: cwd,
14
+ RESEND_API_KEY: resendApiKey,
13
15
  } as const;
14
16
  };
@@ -1,13 +1,14 @@
1
1
  import http from 'node:http';
2
2
  import path from 'node:path';
3
3
  import url from 'node:url';
4
- import { styleText } from 'node:util';
5
4
  import { createJiti } from 'jiti';
6
5
  import logSymbols from 'log-symbols';
7
6
  import ora from 'ora';
8
7
  import { registerSpinnerAutostopping } from '../../utils/register-spinner-autostopping.js';
8
+ import { conf } from '../conf.js';
9
9
  import { getPreviewServerLocation } from '../get-preview-server-location.js';
10
10
  import { packageJson } from '../packageJson.js';
11
+ import { styleText } from '../style-text.js';
11
12
  import { getEnvVariablesForPreviewApp } from './get-env-variables-for-preview-app.js';
12
13
  import { serveStaticFile } from './serve-static-file.js';
13
14
 
@@ -33,9 +34,9 @@ export const startDevServer = async (
33
34
  port: number,
34
35
  ): Promise<http.Server> => {
35
36
  const [majorNodeVersion] = process.versions.node.split('.');
36
- if (majorNodeVersion && Number.parseInt(majorNodeVersion) < 18) {
37
+ if (majorNodeVersion && Number.parseInt(majorNodeVersion, 10) < 20) {
37
38
  console.error(
38
- ` ${logSymbols.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 18 or higher.`,
39
+ ` ${logSymbols.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 20 or higher.`,
39
40
  );
40
41
  process.exit(1);
41
42
  }
@@ -131,6 +132,7 @@ export const startDevServer = async (
131
132
  path.normalize(emailsDirRelativePath),
132
133
  previewServerLocation,
133
134
  process.cwd(),
135
+ conf.get('resendApiKey'),
134
136
  ),
135
137
  };
136
138
 
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Centralized fallback for Node versions (<20.12.0) without util.styleText.
3
+ * Returns the original text when styleText is unavailable.
4
+ */
5
+ import * as nodeUtil from 'node:util';
6
+
7
+ type StyleTextFunction = typeof nodeUtil.styleText;
8
+
9
+ export const styleText: StyleTextFunction = (nodeUtil as any).styleText
10
+ ? (nodeUtil as any).styleText
11
+ : (_: string, text: string) => text;
package/tsconfig.json CHANGED
@@ -23,7 +23,7 @@
23
23
  "declaration": false,
24
24
  "declarationMap": false,
25
25
  "incremental": false,
26
- "jsx": "preserve",
26
+ "jsx": "react-jsx",
27
27
  "lib": ["dom", "dom.iterable", "esnext", "ESNext.AsyncIterable"],
28
28
  "noEmit": true,
29
29
  "strict": false,