react-email 4.1.0-canary.9 → 4.1.1

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,31 @@
1
1
  # react-email
2
2
 
3
+ ## 4.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - ef77691: fix path resolution done wrong breaking `email dev` on Windows
8
+
9
+ ## 4.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - abf8599: use a separate package for storing the preview server (@react-email/preview-server)
14
+
15
+ ### Patch Changes
16
+
17
+ - e173b44: Use the same version for the preview-server and react-email
18
+
19
+ ## 4.1.0-canary.12
20
+
21
+ ## 4.1.0-canary.11
22
+
23
+ ### Patch Changes
24
+
25
+ - 19d4b45: fix static file serving security issue with logging
26
+
27
+ ## 4.1.0-canary.10
28
+
3
29
  ## 4.1.0-canary.9
4
30
 
5
31
  ### Patch Changes
@@ -63,6 +89,24 @@
63
89
 
64
90
  - 4a0d4e3: Theme switcher for email template
65
91
 
92
+ ## 4.0.17
93
+
94
+ ### Patch Changes
95
+
96
+ - e352a67: fix `<svg>` not being flagged as incompatible
97
+ - 8f64ebd: fix the forced `color-scheme: dark` for the preview
98
+ - 6de4e9f: fix static file serving security issue with logging
99
+ - b2e96d5: Add support for hot reloading with tsconfig path aliases
100
+ - 6b0cfd6: fix hot reloading with collapsed directories
101
+ - 8c93330: Fix prettier errors causing NextJS serialization error
102
+ - a07eebf: Pre-render email templates on hover
103
+
104
+ ## 4.0.16
105
+
106
+ ### Patch Changes
107
+
108
+ - 1340a0a: fix mobile's sidebar broken in the preview server
109
+
66
110
  ## 4.0.15
67
111
 
68
112
  ### Patch Changes
@@ -0,0 +1,3 @@
1
+ # email-dev
2
+
3
+ ## 0.0.1
package/dev/index.js ADDED
@@ -0,0 +1,44 @@
1
+ import child_process from 'node:child_process';
2
+ import path from 'node:path';
3
+ import url from 'node:url';
4
+
5
+ const filename = url.fileURLToPath(import.meta.url);
6
+ const dirname = path.dirname(filename);
7
+
8
+ const root = path.resolve(dirname, '../src/index.ts');
9
+
10
+ const tsxPath = path.resolve(dirname, './node_modules/.bin/tsx');
11
+
12
+ const tsx = child_process.spawn(tsxPath, [root, ...process.argv.slice(2)], {
13
+ shell: true,
14
+ cwd: process.cwd(),
15
+ stdio: 'inherit',
16
+ });
17
+
18
+ tsx.on('close', (code) => {
19
+ process.exit(code);
20
+ });
21
+
22
+ process.on('uncaughtExceptionMonitor', () => {
23
+ tsx.kill();
24
+ });
25
+
26
+ process.on('exit', (code) => {
27
+ tsx.kill(code);
28
+ });
29
+
30
+ process.on('SIGINT', () => {
31
+ tsx.kill('SIGINT');
32
+ });
33
+
34
+ process.on('SIGTERM', () => {
35
+ tsx.kill('SIGTERM');
36
+ });
37
+
38
+ process.on('SIGUSR1', () => {
39
+ tsx.kill('SIGUSR1');
40
+ });
41
+
42
+ process.on('SIGUSR2', () => {
43
+ tsx.kill('SIGUSR2');
44
+ });
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "email-dev",
3
+ "version": "0.0.1",
4
+ "bin": "index.js",
5
+ "private": true,
6
+ "type": "module",
7
+ "scripts": {
8
+ "start": "node ."
9
+ },
10
+ "dependencies": {
11
+ "tsx": "4.19.3"
12
+ }
13
+ }
package/dist/index.js CHANGED
@@ -106,7 +106,7 @@ import prompts from "prompts";
106
106
  // package.json
107
107
  var package_default = {
108
108
  name: "react-email",
109
- version: "4.1.0-canary.9",
109
+ version: "4.1.1",
110
110
  description: "A live preview of your emails right in your browser.",
111
111
  bin: {
112
112
  email: "./dist/index.js"
@@ -114,8 +114,8 @@ var package_default = {
114
114
  type: "module",
115
115
  scripts: {
116
116
  build: "tsup-node",
117
+ "build:watch": "tsup-node --watch src",
117
118
  clean: "rm -rf dist",
118
- dev: "tsup-node --watch src",
119
119
  test: "vitest run",
120
120
  "test:watch": "vitest"
121
121
  },
@@ -189,7 +189,7 @@ var getPreviewServerLocation = async () => {
189
189
  let previewServerLocation;
190
190
  try {
191
191
  previewServerLocation = path2.dirname(
192
- url.parse(usersProject.esmResolve("@react-email/preview-server"), true).path
192
+ url.fileURLToPath(usersProject.esmResolve("@react-email/preview-server"))
193
193
  );
194
194
  } catch (_exception) {
195
195
  await ensurePreviewServerInstalled(
@@ -326,8 +326,8 @@ var getEmailSlugsFromEmailDirectory = (emailDirectory, emailsDirectoryAbsolutePa
326
326
  const directoryPathRelativeToEmailsDirectory = emailDirectory.absolutePath.replace(emailsDirectoryAbsolutePath, "").trim();
327
327
  const slugs = [];
328
328
  emailDirectory.emailFilenames.forEach(
329
- (filename3) => slugs.push(
330
- path3.join(directoryPathRelativeToEmailsDirectory, filename3).split(path3.sep).filter((segment) => segment.length > 0)
329
+ (filename2) => slugs.push(
330
+ path3.join(directoryPathRelativeToEmailsDirectory, filename2).split(path3.sep).filter((segment) => segment.length > 0)
331
331
  )
332
332
  );
333
333
  emailDirectory.subDirectories.forEach((directory) => {
@@ -458,234 +458,14 @@ var build = async ({
458
458
  import fs6 from "node:fs";
459
459
 
460
460
  // src/utils/preview/hot-reloading/setup-hot-reloading.ts
461
- import path9 from "node:path";
461
+ import path6 from "node:path";
462
462
  import { watch } from "chokidar";
463
463
  import debounce from "debounce";
464
464
  import { Server as SocketServer } from "socket.io";
465
465
 
466
466
  // src/utils/preview/hot-reloading/create-dependency-graph.ts
467
- import { existsSync as existsSync2, promises as fs4, statSync } from "node:fs";
468
- import path8 from "node:path";
469
-
470
- // src/utils/preview/start-dev-server.ts
471
- import http from "node:http";
472
- import path6 from "node:path";
473
- import url2 from "node:url";
474
- import chalk from "chalk";
475
- import { createJiti as createJiti2 } from "jiti";
476
- import logSymbols3 from "log-symbols";
477
- import ora2 from "ora";
478
-
479
- // src/utils/preview/get-env-variables-for-preview-app.ts
480
- import path4 from "node:path";
481
- var getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, cwd) => {
482
- return {
483
- EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
484
- EMAILS_DIR_ABSOLUTE_PATH: path4.resolve(cwd, relativePathToEmailsDirectory),
485
- USER_PROJECT_LOCATION: cwd,
486
- NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT: isDev ? "true" : "false"
487
- };
488
- };
489
-
490
- // src/utils/preview/serve-static-file.ts
491
- import { existsSync, promises as fs3 } from "node:fs";
467
+ import { existsSync, promises as fs3, statSync } from "node:fs";
492
468
  import path5 from "node:path";
493
- import { lookup } from "mime-types";
494
- var serveStaticFile = async (res, parsedUrl, staticDirRelativePath) => {
495
- const pathname = parsedUrl.pathname.replace("/static", "./static");
496
- const ext = path5.parse(pathname).ext;
497
- const staticBaseDir = path5.resolve(process.cwd(), staticDirRelativePath);
498
- const fileAbsolutePath = path5.resolve(staticBaseDir, pathname);
499
- if (!fileAbsolutePath.startsWith(staticBaseDir)) {
500
- res.statusCode = 403;
501
- res.end();
502
- return;
503
- }
504
- try {
505
- const fileHandle = await fs3.open(fileAbsolutePath, "r");
506
- const fileData = await fs3.readFile(fileHandle);
507
- res.setHeader("Content-type", lookup(ext) || "text/plain");
508
- res.end(fileData);
509
- fileHandle.close();
510
- } catch (exception) {
511
- if (!existsSync(fileAbsolutePath)) {
512
- res.statusCode = 404;
513
- res.end();
514
- } else {
515
- const sanitizedFilePath = fileAbsolutePath.replace(/\n|\r/g, "");
516
- console.error(
517
- `Could not read file at ${sanitizedFilePath} to be served, here's the exception:`,
518
- exception
519
- );
520
- res.statusCode = 500;
521
- res.end(
522
- "Could not read file to be served! Check your terminal for more information."
523
- );
524
- }
525
- }
526
- };
527
-
528
- // src/utils/preview/start-dev-server.ts
529
- var devServer;
530
- var safeAsyncServerListen = (server, port) => {
531
- return new Promise((resolve) => {
532
- server.listen(port, () => {
533
- resolve({ portAlreadyInUse: false });
534
- });
535
- server.on("error", (e) => {
536
- if (e.code === "EADDRINUSE") {
537
- resolve({ portAlreadyInUse: true });
538
- }
539
- });
540
- });
541
- };
542
- var filename = url2.fileURLToPath(import.meta.url);
543
- var dirname = path6.dirname(filename);
544
- var isDev = !dirname.includes("dist");
545
- var startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port) => {
546
- const [majorNodeVersion] = process.versions.node.split(".");
547
- if (majorNodeVersion && Number.parseInt(majorNodeVersion) < 18) {
548
- console.error(
549
- ` ${logSymbols3.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 18 or higher.`
550
- );
551
- process.exit(1);
552
- }
553
- const previewServerLocation = await getPreviewServerLocation();
554
- const previewServer = createJiti2(previewServerLocation);
555
- const { default: next } = await previewServer.import("next");
556
- devServer = http.createServer((req, res) => {
557
- if (!req.url) {
558
- res.end(404);
559
- return;
560
- }
561
- const parsedUrl = url2.parse(req.url, true);
562
- res.setHeader(
563
- "Cache-Control",
564
- "no-cache, max-age=0, must-revalidate, no-store"
565
- );
566
- res.setHeader("Pragma", "no-cache");
567
- res.setHeader("Expires", "-1");
568
- try {
569
- if (parsedUrl.path?.includes("static/") && !parsedUrl.path.includes("_next/static/")) {
570
- void serveStaticFile(res, parsedUrl, staticBaseDirRelativePath);
571
- } else if (!isNextReady) {
572
- void nextReadyPromise.then(
573
- () => nextHandleRequest?.(req, res, parsedUrl)
574
- );
575
- } else {
576
- void nextHandleRequest?.(req, res, parsedUrl);
577
- }
578
- } catch (e) {
579
- console.error("caught error", e);
580
- res.writeHead(500);
581
- res.end();
582
- }
583
- });
584
- const { portAlreadyInUse } = await safeAsyncServerListen(devServer, port);
585
- if (!portAlreadyInUse) {
586
- console.log(chalk.greenBright(` React Email ${package_default.version}`));
587
- console.log(` Running preview at: http://localhost:${port}
588
- `);
589
- } else {
590
- const nextPortToTry = port + 1;
591
- console.warn(
592
- ` ${logSymbols3.warning} Port ${port} is already in use, trying ${nextPortToTry}`
593
- );
594
- return startDevServer(
595
- emailsDirRelativePath,
596
- staticBaseDirRelativePath,
597
- nextPortToTry
598
- );
599
- }
600
- devServer.on("close", async () => {
601
- await app.close();
602
- });
603
- devServer.on("error", (e) => {
604
- spinner.stopAndPersist({
605
- symbol: logSymbols3.error,
606
- text: `Preview Server had an error: ${e}`
607
- });
608
- process.exit(1);
609
- });
610
- const spinner = ora2({
611
- text: "Getting react-email preview server ready...\n",
612
- prefixText: " "
613
- }).start();
614
- registerSpinnerAutostopping(spinner);
615
- const timeBeforeNextReady = performance.now();
616
- process.env = {
617
- NODE_ENV: "development",
618
- ...process.env,
619
- ...getEnvVariablesForPreviewApp(
620
- // If we don't do normalization here, stuff like https://github.com/resend/react-email/issues/1354 happens.
621
- path6.normalize(emailsDirRelativePath),
622
- process.cwd()
623
- )
624
- };
625
- const app = next({
626
- // passing in env here does not get the environment variables there
627
- dev: isDev,
628
- conf: {
629
- images: {
630
- // This is to avoid the warning with sharp
631
- unoptimized: true
632
- }
633
- },
634
- hostname: "localhost",
635
- port,
636
- dir: previewServerLocation
637
- });
638
- let isNextReady = false;
639
- const nextReadyPromise = app.prepare();
640
- try {
641
- await nextReadyPromise;
642
- } catch (exception) {
643
- spinner.stopAndPersist({
644
- symbol: logSymbols3.error,
645
- text: ` Preview Server had an error: ${exception}`
646
- });
647
- process.exit(1);
648
- }
649
- isNextReady = true;
650
- const nextHandleRequest = app.getRequestHandler();
651
- const secondsToNextReady = ((performance.now() - timeBeforeNextReady) / 1e3).toFixed(1);
652
- spinner.stopAndPersist({
653
- text: `Ready in ${secondsToNextReady}s
654
- `,
655
- symbol: logSymbols3.success
656
- });
657
- return devServer;
658
- };
659
- var makeExitHandler = (options) => (codeSignalOrError) => {
660
- if (typeof devServer !== "undefined") {
661
- console.log("\nshutting down dev server");
662
- devServer.close();
663
- devServer = void 0;
664
- }
665
- if (codeSignalOrError instanceof Error) {
666
- console.error(codeSignalOrError);
667
- }
668
- if (options?.shouldKillProcess) {
669
- process.exit(options.killWithErrorCode ? 1 : 0);
670
- }
671
- };
672
- process.on("exit", makeExitHandler());
673
- process.on(
674
- "SIGINT",
675
- makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
676
- );
677
- process.on(
678
- "SIGUSR1",
679
- makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
680
- );
681
- process.on(
682
- "SIGUSR2",
683
- makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
684
- );
685
- process.on(
686
- "uncaughtException",
687
- makeExitHandler({ shouldKillProcess: true, killWithErrorCode: true })
688
- );
689
469
 
690
470
  // src/utils/preview/hot-reloading/get-imported-modules.ts
691
471
  import { parse } from "@babel/parser";
@@ -733,7 +513,7 @@ var getImportedModules = (contents) => {
733
513
  };
734
514
 
735
515
  // src/utils/preview/hot-reloading/resolve-path-aliases.ts
736
- import path7 from "node:path";
516
+ import path4 from "node:path";
737
517
  import { createMatchPath, loadConfig } from "tsconfig-paths";
738
518
  var resolvePathAliases = (importPaths, projectPath) => {
739
519
  const configLoadResult = loadConfig(projectPath);
@@ -752,7 +532,7 @@ var resolvePathAliases = (importPaths, projectPath) => {
752
532
  ".mjs"
753
533
  ]);
754
534
  if (unaliasedPath) {
755
- return `./${path7.relative(projectPath, unaliasedPath)}`;
535
+ return `./${path4.relative(projectPath, unaliasedPath)}`;
756
536
  }
757
537
  return importedPath;
758
538
  });
@@ -763,9 +543,9 @@ var resolvePathAliases = (importPaths, projectPath) => {
763
543
  // src/utils/preview/hot-reloading/create-dependency-graph.ts
764
544
  var readAllFilesInsideDirectory = async (directory) => {
765
545
  let allFilePaths = [];
766
- const topLevelDirents = await fs4.readdir(directory, { withFileTypes: true });
546
+ const topLevelDirents = await fs3.readdir(directory, { withFileTypes: true });
767
547
  for await (const dirent of topLevelDirents) {
768
- const pathToDirent = path8.join(directory, dirent.name);
548
+ const pathToDirent = path5.join(directory, dirent.name);
769
549
  if (dirent.isDirectory()) {
770
550
  allFilePaths = allFilePaths.concat(
771
551
  await readAllFilesInsideDirectory(pathToDirent)
@@ -777,26 +557,26 @@ var readAllFilesInsideDirectory = async (directory) => {
777
557
  return allFilePaths;
778
558
  };
779
559
  var isJavascriptModule = (filePath) => {
780
- const extensionName = path8.extname(filePath);
560
+ const extensionName = path5.extname(filePath);
781
561
  return [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"].includes(extensionName);
782
562
  };
783
563
  var checkFileExtensionsUntilItExists = (pathWithoutExtension) => {
784
- if (existsSync2(`${pathWithoutExtension}.ts`)) {
564
+ if (existsSync(`${pathWithoutExtension}.ts`)) {
785
565
  return `${pathWithoutExtension}.ts`;
786
566
  }
787
- if (existsSync2(`${pathWithoutExtension}.tsx`)) {
567
+ if (existsSync(`${pathWithoutExtension}.tsx`)) {
788
568
  return `${pathWithoutExtension}.tsx`;
789
569
  }
790
- if (existsSync2(`${pathWithoutExtension}.js`)) {
570
+ if (existsSync(`${pathWithoutExtension}.js`)) {
791
571
  return `${pathWithoutExtension}.js`;
792
572
  }
793
- if (existsSync2(`${pathWithoutExtension}.jsx`)) {
573
+ if (existsSync(`${pathWithoutExtension}.jsx`)) {
794
574
  return `${pathWithoutExtension}.jsx`;
795
575
  }
796
- if (existsSync2(`${pathWithoutExtension}.mjs`)) {
576
+ if (existsSync(`${pathWithoutExtension}.mjs`)) {
797
577
  return `${pathWithoutExtension}.mjs`;
798
578
  }
799
- if (existsSync2(`${pathWithoutExtension}.cjs`)) {
579
+ if (existsSync(`${pathWithoutExtension}.cjs`)) {
800
580
  return `${pathWithoutExtension}.cjs`;
801
581
  }
802
582
  };
@@ -815,15 +595,15 @@ var createDependencyGraph = async (directory) => {
815
595
  ])
816
596
  );
817
597
  const getDependencyPaths = async (filePath) => {
818
- const contents = await fs4.readFile(filePath, "utf8");
819
- const importedPaths = isJavascriptModule(filePath) ? resolvePathAliases(getImportedModules(contents), path8.dirname(filePath)) : [];
598
+ const contents = await fs3.readFile(filePath, "utf8");
599
+ const importedPaths = isJavascriptModule(filePath) ? resolvePathAliases(getImportedModules(contents), path5.dirname(filePath)) : [];
820
600
  const importedPathsRelativeToDirectory = importedPaths.map(
821
601
  (dependencyPath) => {
822
602
  const isModulePath = !dependencyPath.startsWith(".");
823
- if (isModulePath || path8.isAbsolute(dependencyPath)) {
603
+ if (isModulePath || path5.isAbsolute(dependencyPath)) {
824
604
  return dependencyPath;
825
605
  }
826
- let pathToDependencyFromDirectory = path8.resolve(
606
+ let pathToDependencyFromDirectory = path5.resolve(
827
607
  /*
828
608
  path.resolve resolves paths differently from what imports on javascript do.
829
609
 
@@ -831,7 +611,7 @@ var createDependencyGraph = async (directory) => {
831
611
  would end up going into /path/to/email.tsx/other-email instead of /path/to/other-email which is the
832
612
  one the import is meant to go to
833
613
  */
834
- path8.dirname(filePath),
614
+ path5.dirname(filePath),
835
615
  dependencyPath
836
616
  );
837
617
  let isDirectory = false;
@@ -846,15 +626,15 @@ var createDependencyGraph = async (directory) => {
846
626
  );
847
627
  if (pathWithExtension) {
848
628
  pathToDependencyFromDirectory = pathWithExtension;
849
- } else if (isDev) {
629
+ } else {
850
630
  console.warn(
851
631
  `Could not find index file for directory at ${pathToDependencyFromDirectory}. This is probably going to cause issues with both hot reloading and your code.`
852
632
  );
853
633
  }
854
634
  }
855
- const extension = path8.extname(pathToDependencyFromDirectory);
635
+ const extension = path5.extname(pathToDependencyFromDirectory);
856
636
  const pathWithEnsuredExtension = (() => {
857
- if (extension.length > 0 && existsSync2(pathToDependencyFromDirectory)) {
637
+ if (extension.length > 0 && existsSync(pathToDependencyFromDirectory)) {
858
638
  return pathToDependencyFromDirectory;
859
639
  }
860
640
  return checkFileExtensionsUntilItExists(
@@ -863,7 +643,7 @@ var createDependencyGraph = async (directory) => {
863
643
  })();
864
644
  if (pathWithEnsuredExtension) {
865
645
  pathToDependencyFromDirectory = pathWithEnsuredExtension;
866
- } else if (isDev) {
646
+ } else {
867
647
  console.warn(
868
648
  `Could not find file at ${pathToDependencyFromDirectory}`
869
649
  );
@@ -872,10 +652,10 @@ var createDependencyGraph = async (directory) => {
872
652
  }
873
653
  );
874
654
  const moduleDependencies = importedPathsRelativeToDirectory.filter(
875
- (dependencyPath) => !dependencyPath.startsWith(".") && !path8.isAbsolute(dependencyPath)
655
+ (dependencyPath) => !dependencyPath.startsWith(".") && !path5.isAbsolute(dependencyPath)
876
656
  );
877
657
  const nonNodeModuleImportPathsRelativeToDirectory = importedPathsRelativeToDirectory.filter(
878
- (dependencyPath) => dependencyPath.startsWith(".") || path8.isAbsolute(dependencyPath)
658
+ (dependencyPath) => dependencyPath.startsWith(".") || path5.isAbsolute(dependencyPath)
879
659
  );
880
660
  return {
881
661
  dependencyPaths: nonNodeModuleImportPathsRelativeToDirectory,
@@ -1006,14 +786,14 @@ var setupHotreloading = async (devServer2, emailDirRelativePath) => {
1006
786
  changes.filter(
1007
787
  (change) => (
1008
788
  // Ensures only changes inside the emails directory are emitted
1009
- path9.resolve(absolutePathToEmailsDirectory, change.filename).startsWith(absolutePathToEmailsDirectory)
789
+ path6.resolve(absolutePathToEmailsDirectory, change.filename).startsWith(absolutePathToEmailsDirectory)
1010
790
  )
1011
791
  )
1012
792
  );
1013
793
  });
1014
794
  changes = [];
1015
795
  }, 150);
1016
- const absolutePathToEmailsDirectory = path9.resolve(
796
+ const absolutePathToEmailsDirectory = path6.resolve(
1017
797
  process.cwd(),
1018
798
  emailDirRelativePath
1019
799
  );
@@ -1023,7 +803,7 @@ var setupHotreloading = async (devServer2, emailDirRelativePath) => {
1023
803
  cwd: absolutePathToEmailsDirectory
1024
804
  });
1025
805
  const getFilesOutsideEmailsDirectory = () => Object.keys(dependencyGraph).filter(
1026
- (p) => path9.relative(absolutePathToEmailsDirectory, p).startsWith("..")
806
+ (p) => path6.relative(absolutePathToEmailsDirectory, p).startsWith("..")
1027
807
  );
1028
808
  let filesOutsideEmailsDirectory = getFilesOutsideEmailsDirectory();
1029
809
  for (const p of filesOutsideEmailsDirectory) {
@@ -1035,11 +815,11 @@ var setupHotreloading = async (devServer2, emailDirRelativePath) => {
1035
815
  process.on("SIGINT", exit);
1036
816
  process.on("uncaughtException", exit);
1037
817
  watcher.on("all", async (event, relativePathToChangeTarget) => {
1038
- const file = relativePathToChangeTarget.split(path9.sep);
818
+ const file = relativePathToChangeTarget.split(path6.sep);
1039
819
  if (file.length === 0) {
1040
820
  return;
1041
821
  }
1042
- const pathToChangeTarget = path9.resolve(
822
+ const pathToChangeTarget = path6.resolve(
1043
823
  absolutePathToEmailsDirectory,
1044
824
  relativePathToChangeTarget
1045
825
  );
@@ -1063,7 +843,7 @@ var setupHotreloading = async (devServer2, emailDirRelativePath) => {
1063
843
  for (const dependentPath of resolveDependentsOf(pathToChangeTarget)) {
1064
844
  changes.push({
1065
845
  event: "change",
1066
- filename: path9.relative(absolutePathToEmailsDirectory, dependentPath)
846
+ filename: path6.relative(absolutePathToEmailsDirectory, dependentPath)
1067
847
  });
1068
848
  }
1069
849
  reload();
@@ -1071,6 +851,223 @@ var setupHotreloading = async (devServer2, emailDirRelativePath) => {
1071
851
  return watcher;
1072
852
  };
1073
853
 
854
+ // src/utils/preview/start-dev-server.ts
855
+ import http from "node:http";
856
+ import path9 from "node:path";
857
+ import url2 from "node:url";
858
+ import chalk from "chalk";
859
+ import { createJiti as createJiti2 } from "jiti";
860
+ import logSymbols3 from "log-symbols";
861
+ import ora2 from "ora";
862
+
863
+ // src/utils/preview/get-env-variables-for-preview-app.ts
864
+ import path7 from "node:path";
865
+ var getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, cwd) => {
866
+ return {
867
+ EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
868
+ EMAILS_DIR_ABSOLUTE_PATH: path7.resolve(cwd, relativePathToEmailsDirectory),
869
+ USER_PROJECT_LOCATION: cwd
870
+ };
871
+ };
872
+
873
+ // src/utils/preview/serve-static-file.ts
874
+ import { existsSync as existsSync2, promises as fs4 } from "node:fs";
875
+ import path8 from "node:path";
876
+ import { lookup } from "mime-types";
877
+ var serveStaticFile = async (res, parsedUrl, staticDirRelativePath) => {
878
+ const pathname = parsedUrl.pathname.replace("/static", "./static");
879
+ const ext = path8.parse(pathname).ext;
880
+ const staticBaseDir = path8.resolve(process.cwd(), staticDirRelativePath);
881
+ const fileAbsolutePath = path8.resolve(staticBaseDir, pathname);
882
+ if (!fileAbsolutePath.startsWith(staticBaseDir)) {
883
+ res.statusCode = 403;
884
+ res.end();
885
+ return;
886
+ }
887
+ try {
888
+ const fileHandle = await fs4.open(fileAbsolutePath, "r");
889
+ const fileData = await fs4.readFile(fileHandle);
890
+ res.setHeader("Content-type", lookup(ext) || "text/plain");
891
+ res.end(fileData);
892
+ fileHandle.close();
893
+ } catch (exception) {
894
+ if (!existsSync2(fileAbsolutePath)) {
895
+ res.statusCode = 404;
896
+ res.end();
897
+ } else {
898
+ const sanitizedFilePath = fileAbsolutePath.replace(/\n|\r/g, "");
899
+ console.error(
900
+ `Could not read file at %s to be served, here's the exception:`,
901
+ sanitizedFilePath,
902
+ exception
903
+ );
904
+ res.statusCode = 500;
905
+ res.end(
906
+ "Could not read file to be served! Check your terminal for more information."
907
+ );
908
+ }
909
+ }
910
+ };
911
+
912
+ // src/utils/preview/start-dev-server.ts
913
+ var devServer;
914
+ var safeAsyncServerListen = (server, port) => {
915
+ return new Promise((resolve) => {
916
+ server.listen(port, () => {
917
+ resolve({ portAlreadyInUse: false });
918
+ });
919
+ server.on("error", (e) => {
920
+ if (e.code === "EADDRINUSE") {
921
+ resolve({ portAlreadyInUse: true });
922
+ }
923
+ });
924
+ });
925
+ };
926
+ var startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port) => {
927
+ const [majorNodeVersion] = process.versions.node.split(".");
928
+ if (majorNodeVersion && Number.parseInt(majorNodeVersion) < 18) {
929
+ console.error(
930
+ ` ${logSymbols3.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 18 or higher.`
931
+ );
932
+ process.exit(1);
933
+ }
934
+ const previewServerLocation = await getPreviewServerLocation();
935
+ const previewServer = createJiti2(previewServerLocation);
936
+ const { default: next } = await previewServer.import("next");
937
+ devServer = http.createServer((req, res) => {
938
+ if (!req.url) {
939
+ res.end(404);
940
+ return;
941
+ }
942
+ const parsedUrl = url2.parse(req.url, true);
943
+ res.setHeader(
944
+ "Cache-Control",
945
+ "no-cache, max-age=0, must-revalidate, no-store"
946
+ );
947
+ res.setHeader("Pragma", "no-cache");
948
+ res.setHeader("Expires", "-1");
949
+ try {
950
+ if (parsedUrl.path?.includes("static/") && !parsedUrl.path.includes("_next/static/")) {
951
+ void serveStaticFile(res, parsedUrl, staticBaseDirRelativePath);
952
+ } else if (!isNextReady) {
953
+ void nextReadyPromise.then(
954
+ () => nextHandleRequest?.(req, res, parsedUrl)
955
+ );
956
+ } else {
957
+ void nextHandleRequest?.(req, res, parsedUrl);
958
+ }
959
+ } catch (e) {
960
+ console.error("caught error", e);
961
+ res.writeHead(500);
962
+ res.end();
963
+ }
964
+ });
965
+ const { portAlreadyInUse } = await safeAsyncServerListen(devServer, port);
966
+ if (!portAlreadyInUse) {
967
+ console.log(chalk.greenBright(` React Email ${package_default.version}`));
968
+ console.log(` Running preview at: http://localhost:${port}
969
+ `);
970
+ } else {
971
+ const nextPortToTry = port + 1;
972
+ console.warn(
973
+ ` ${logSymbols3.warning} Port ${port} is already in use, trying ${nextPortToTry}`
974
+ );
975
+ return startDevServer(
976
+ emailsDirRelativePath,
977
+ staticBaseDirRelativePath,
978
+ nextPortToTry
979
+ );
980
+ }
981
+ devServer.on("close", async () => {
982
+ await app.close();
983
+ });
984
+ devServer.on("error", (e) => {
985
+ spinner.stopAndPersist({
986
+ symbol: logSymbols3.error,
987
+ text: `Preview Server had an error: ${e}`
988
+ });
989
+ process.exit(1);
990
+ });
991
+ const spinner = ora2({
992
+ text: "Getting react-email preview server ready...\n",
993
+ prefixText: " "
994
+ }).start();
995
+ registerSpinnerAutostopping(spinner);
996
+ const timeBeforeNextReady = performance.now();
997
+ process.env = {
998
+ NODE_ENV: "development",
999
+ ...process.env,
1000
+ ...getEnvVariablesForPreviewApp(
1001
+ // If we don't do normalization here, stuff like https://github.com/resend/react-email/issues/1354 happens.
1002
+ path9.normalize(emailsDirRelativePath),
1003
+ process.cwd()
1004
+ )
1005
+ };
1006
+ const app = next({
1007
+ // passing in env here does not get the environment variables there
1008
+ dev: false,
1009
+ conf: {
1010
+ images: {
1011
+ // This is to avoid the warning with sharp
1012
+ unoptimized: true
1013
+ }
1014
+ },
1015
+ hostname: "localhost",
1016
+ port,
1017
+ dir: previewServerLocation
1018
+ });
1019
+ let isNextReady = false;
1020
+ const nextReadyPromise = app.prepare();
1021
+ try {
1022
+ await nextReadyPromise;
1023
+ } catch (exception) {
1024
+ spinner.stopAndPersist({
1025
+ symbol: logSymbols3.error,
1026
+ text: ` Preview Server had an error: ${exception}`
1027
+ });
1028
+ process.exit(1);
1029
+ }
1030
+ isNextReady = true;
1031
+ const nextHandleRequest = app.getRequestHandler();
1032
+ const secondsToNextReady = ((performance.now() - timeBeforeNextReady) / 1e3).toFixed(1);
1033
+ spinner.stopAndPersist({
1034
+ text: `Ready in ${secondsToNextReady}s
1035
+ `,
1036
+ symbol: logSymbols3.success
1037
+ });
1038
+ return devServer;
1039
+ };
1040
+ var makeExitHandler = (options) => (codeSignalOrError) => {
1041
+ if (typeof devServer !== "undefined") {
1042
+ console.log("\nshutting down dev server");
1043
+ devServer.close();
1044
+ devServer = void 0;
1045
+ }
1046
+ if (codeSignalOrError instanceof Error) {
1047
+ console.error(codeSignalOrError);
1048
+ }
1049
+ if (options?.shouldKillProcess) {
1050
+ process.exit(options.killWithErrorCode ? 1 : 0);
1051
+ }
1052
+ };
1053
+ process.on("exit", makeExitHandler());
1054
+ process.on(
1055
+ "SIGINT",
1056
+ makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
1057
+ );
1058
+ process.on(
1059
+ "SIGUSR1",
1060
+ makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
1061
+ );
1062
+ process.on(
1063
+ "SIGUSR2",
1064
+ makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
1065
+ );
1066
+ process.on(
1067
+ "uncaughtException",
1068
+ makeExitHandler({ shouldKillProcess: true, killWithErrorCode: true })
1069
+ );
1070
+
1074
1071
  // src/utils/tree.ts
1075
1072
  import { promises as fs5 } from "node:fs";
1076
1073
  import os from "node:os";
@@ -1085,8 +1082,8 @@ var SYMBOLS = {
1085
1082
  var getTreeLines = async (dirPath, depth, currentDepth = 0) => {
1086
1083
  const base = process.cwd();
1087
1084
  const dirFullpath = path10.resolve(base, dirPath);
1088
- const dirname2 = path10.basename(dirFullpath);
1089
- let lines = [dirname2];
1085
+ const dirname = path10.basename(dirFullpath);
1086
+ let lines = [dirname];
1090
1087
  const dirStat = await fs5.stat(dirFullpath);
1091
1088
  if (dirStat.isDirectory() && currentDepth < depth) {
1092
1089
  const childDirents = await fs5.readdir(dirFullpath, { withFileTypes: true });
@@ -1215,15 +1212,15 @@ var renderingUtilitiesExporter = (emailTemplates) => ({
1215
1212
  var getEmailTemplatesFromDirectory = (emailDirectory) => {
1216
1213
  const templatePaths = [];
1217
1214
  emailDirectory.emailFilenames.forEach(
1218
- (filename3) => templatePaths.push(path12.join(emailDirectory.absolutePath, filename3))
1215
+ (filename2) => templatePaths.push(path12.join(emailDirectory.absolutePath, filename2))
1219
1216
  );
1220
1217
  emailDirectory.subDirectories.forEach((directory) => {
1221
1218
  templatePaths.push(...getEmailTemplatesFromDirectory(directory));
1222
1219
  });
1223
1220
  return templatePaths;
1224
1221
  };
1225
- var filename2 = url3.fileURLToPath(import.meta.url);
1226
- var require2 = createRequire(filename2);
1222
+ var filename = url3.fileURLToPath(import.meta.url);
1223
+ var require2 = createRequire(filename);
1227
1224
  var exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirectoryPath, options) => {
1228
1225
  if (fs8.existsSync(pathToWhereEmailMarkupShouldBeDumped)) {
1229
1226
  fs8.rmSync(pathToWhereEmailMarkupShouldBeDumped, { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "4.1.0-canary.9",
3
+ "version": "4.1.1",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/index.js"
@@ -49,12 +49,12 @@
49
49
  "tsup": "8.4.0",
50
50
  "tsx": "4.19.3",
51
51
  "typescript": "5.8.3",
52
- "@react-email/components": "0.1.0-canary.4"
52
+ "@react-email/components": "0.2.0"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsup-node",
56
+ "build:watch": "tsup-node --watch src",
56
57
  "clean": "rm -rf dist",
57
- "dev": "tsup-node --watch src",
58
58
  "test": "vitest run",
59
59
  "test:watch": "vitest"
60
60
  }
package/readme.md CHANGED
@@ -39,6 +39,22 @@ Generates the plain HTML files of your emails into a `out` directory.
39
39
  npx react-email export
40
40
  ```
41
41
 
42
+ ## Setting Up the Environment
43
+
44
+ When working in the CLI, a lot of friction can get introduced with installing it and rebuilding for every change. To avoid that, we have a script that can be linked globally to directly run the source code of the CLI. You can use it the same as you would the standard CLI.
45
+
46
+ ### 1. Link `react-email` globally
47
+
48
+ ```sh
49
+ pnpm link ./dev -g
50
+ ```
51
+
52
+ ### 2. Run the CLI
53
+
54
+ ```sh
55
+ email-dev [command] [flags]
56
+ ```
57
+
42
58
  ## License
43
59
 
44
60
  MIT License
@@ -0,0 +1,84 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`checkImages() 1`] = `
4
+ [
5
+ {
6
+ "checks": [
7
+ {
8
+ "metadata": {
9
+ "alt": undefined,
10
+ },
11
+ "passed": false,
12
+ "type": "accessibility",
13
+ },
14
+ {
15
+ "passed": true,
16
+ "type": "syntax",
17
+ },
18
+ {
19
+ "passed": true,
20
+ "type": "security",
21
+ },
22
+ {
23
+ "metadata": {
24
+ "fetchStatusCode": 200,
25
+ },
26
+ "passed": true,
27
+ "type": "fetch_attempt",
28
+ },
29
+ {
30
+ "metadata": {
31
+ "byteCount": 26808,
32
+ },
33
+ "passed": true,
34
+ "type": "image_size",
35
+ },
36
+ ],
37
+ "codeLocation": {
38
+ "column": 3,
39
+ "line": 2,
40
+ },
41
+ "source": "https://resend.com/static/brand/resend-icon-white.png",
42
+ "status": "warning",
43
+ },
44
+ {
45
+ "checks": [
46
+ {
47
+ "metadata": {
48
+ "alt": "codepen challenges",
49
+ },
50
+ "passed": true,
51
+ "type": "accessibility",
52
+ },
53
+ {
54
+ "passed": true,
55
+ "type": "syntax",
56
+ },
57
+ {
58
+ "passed": true,
59
+ "type": "security",
60
+ },
61
+ {
62
+ "metadata": {
63
+ "fetchStatusCode": 200,
64
+ },
65
+ "passed": true,
66
+ "type": "fetch_attempt",
67
+ },
68
+ {
69
+ "metadata": {
70
+ "byteCount": 111922,
71
+ },
72
+ "passed": true,
73
+ "type": "image_size",
74
+ },
75
+ ],
76
+ "codeLocation": {
77
+ "column": 3,
78
+ "line": 3,
79
+ },
80
+ "source": "/static/codepen-challengers.png",
81
+ "status": "success",
82
+ },
83
+ ]
84
+ `;
@@ -30,8 +30,7 @@ export const getPreviewServerLocation = async () => {
30
30
  let previewServerLocation!: string;
31
31
  try {
32
32
  previewServerLocation = path.dirname(
33
- url.parse(usersProject.esmResolve('@react-email/preview-server'), true)
34
- .path!,
33
+ url.fileURLToPath(usersProject.esmResolve('@react-email/preview-server')),
35
34
  );
36
35
  } catch (_exception) {
37
36
  await ensurePreviewServerInstalled(
@@ -1,5 +1,4 @@
1
1
  import path from 'node:path';
2
- import { isDev } from './start-dev-server.js';
3
2
 
4
3
  export const getEnvVariablesForPreviewApp = (
5
4
  relativePathToEmailsDirectory: string,
@@ -9,6 +8,5 @@ export const getEnvVariablesForPreviewApp = (
9
8
  EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
10
9
  EMAILS_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEmailsDirectory),
11
10
  USER_PROJECT_LOCATION: cwd,
12
- NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT: isDev ? 'true' : 'false',
13
11
  } as const;
14
12
  };
@@ -44,49 +44,21 @@ test('createDependencyGraph()', async () => {
44
44
  };
45
45
 
46
46
  const initialDependencyGraph = convertPathsToAbsolute({
47
- '../../../../package.json': {
48
- dependencyPaths: [],
49
- dependentPaths: ['../../packageJson.ts'],
50
- moduleDependencies: [],
51
- path: '../../../../package.json',
52
- },
53
47
  'create-dependency-graph.ts': {
54
48
  path: 'create-dependency-graph.ts',
55
- dependencyPaths: [
56
- '../start-dev-server.ts',
57
- 'get-imported-modules.ts',
58
- 'resolve-path-aliases.ts',
59
- ],
49
+ dependencyPaths: ['get-imported-modules.ts', 'resolve-path-aliases.ts'],
60
50
  dependentPaths: [
61
51
  'create-dependency-graph.spec.ts',
62
52
  'setup-hot-reloading.ts',
63
53
  ],
64
54
  moduleDependencies: ['node:fs', 'node:path', 'chokidar/handler.js'],
65
55
  },
66
- '../../get-preview-server-location.ts': {
67
- dependencyPaths: [],
68
- dependentPaths: ['../../preview/start-dev-server.ts'],
69
- moduleDependencies: ['node:path', 'node:url', 'jiti', 'nypm', 'prompts'],
70
- path: '../../get-preview-server-location.ts',
71
- },
72
- '../../packageJson.ts': {
73
- dependencyPaths: ['../../../../package.json'],
74
- dependentPaths: ['../../preview/start-dev-server.ts'],
75
- moduleDependencies: [],
76
- path: '../../packageJson.ts',
77
- },
78
56
  'create-dependency-graph.spec.ts': {
79
57
  path: 'create-dependency-graph.spec.ts',
80
58
  dependencyPaths: ['create-dependency-graph.ts'],
81
59
  dependentPaths: [],
82
60
  moduleDependencies: ['node:fs', 'node:path'],
83
61
  },
84
- '../get-env-variables-for-preview-app.ts': {
85
- dependencyPaths: ['../../preview/start-dev-server.ts'],
86
- dependentPaths: ['../../preview/start-dev-server.ts'],
87
- moduleDependencies: ['node:path'],
88
- path: '../../preview/get-env-variables-for-preview-app.ts',
89
- },
90
62
  './test/some-file.ts': {
91
63
  dependencyPaths: [],
92
64
  dependentPaths: [],
@@ -138,47 +110,6 @@ test('createDependencyGraph()', async () => {
138
110
  'socket.io',
139
111
  ],
140
112
  },
141
- '../start-dev-server.ts': {
142
- dependencyPaths: [
143
- '../../register-spinner-autostopping.ts',
144
- '../../get-preview-server-location.ts',
145
- '../../packageJson.ts',
146
- '../../preview/get-env-variables-for-preview-app.ts',
147
- '../../preview/serve-static-file.ts',
148
- ],
149
- path: '../start-dev-server.ts',
150
- dependentPaths: [
151
- '../../preview/get-env-variables-for-preview-app.ts',
152
- 'create-dependency-graph.ts',
153
- ],
154
- moduleDependencies: [
155
- 'node:http',
156
- 'node:path',
157
- 'node:url',
158
- 'chalk',
159
- 'jiti',
160
- 'log-symbols',
161
- 'ora',
162
- ],
163
- },
164
- '../../preview/serve-static-file.ts': {
165
- dependencyPaths: [],
166
- dependentPaths: ['../../preview/start-dev-server.ts'],
167
- moduleDependencies: [
168
- 'node:fs',
169
- 'node:http',
170
- 'node:path',
171
- 'node:url',
172
- 'mime-types',
173
- ],
174
- path: '../../preview/serve-static-file.ts',
175
- },
176
- '../../register-spinner-autostopping.ts': {
177
- dependencyPaths: [],
178
- dependentPaths: ['../../preview/start-dev-server.ts'],
179
- moduleDependencies: ['log-symbols', 'ora'],
180
- path: '../../register-spinner-autostopping.ts',
181
- },
182
113
  '../../types/hot-reload-event.ts': {
183
114
  dependencyPaths: [],
184
115
  dependentPaths: ['../../types/hot-reload-change.ts'],
@@ -1,7 +1,6 @@
1
1
  import { existsSync, promises as fs, statSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import type { EventName } from 'chokidar/handler.js';
4
- import { isDev } from '../start-dev-server.js';
5
4
  import { getImportedModules } from './get-imported-modules.js';
6
5
  import { resolvePathAliases } from './resolve-path-aliases.js';
7
6
 
@@ -134,8 +133,7 @@ export const createDependencyGraph = async (directory: string) => {
134
133
  );
135
134
  if (pathWithExtension) {
136
135
  pathToDependencyFromDirectory = pathWithExtension;
137
- } else if (isDev) {
138
- // only warn about this on development as it is probably going to be irrelevant otherwise
136
+ } else {
139
137
  console.warn(
140
138
  `Could not find index file for directory at ${pathToDependencyFromDirectory}. This is probably going to cause issues with both hot reloading and your code.`,
141
139
  );
@@ -157,8 +155,7 @@ export const createDependencyGraph = async (directory: string) => {
157
155
 
158
156
  if (pathWithEnsuredExtension) {
159
157
  pathToDependencyFromDirectory = pathWithEnsuredExtension;
160
- } else if (isDev) {
161
- // only warn about this on development as it is probably going to be irrelevant otherwise
158
+ } else {
162
159
  console.warn(
163
160
  `Could not find file at ${pathToDependencyFromDirectory}`,
164
161
  );
@@ -38,7 +38,8 @@ export const serveStaticFile = async (
38
38
  } else {
39
39
  const sanitizedFilePath = fileAbsolutePath.replace(/\n|\r/g, '');
40
40
  console.error(
41
- `Could not read file at ${sanitizedFilePath} to be served, here's the exception:`,
41
+ `Could not read file at %s to be served, here's the exception:`,
42
+ sanitizedFilePath,
42
43
  exception,
43
44
  );
44
45
 
@@ -27,11 +27,6 @@ const safeAsyncServerListen = (server: http.Server, port: number) => {
27
27
  });
28
28
  };
29
29
 
30
- const filename = url.fileURLToPath(import.meta.url);
31
- const dirname = path.dirname(filename);
32
-
33
- export const isDev = !dirname.includes('dist');
34
-
35
30
  export const startDevServer = async (
36
31
  emailsDirRelativePath: string,
37
32
  staticBaseDirRelativePath: string,
@@ -143,7 +138,7 @@ export const startDevServer = async (
143
138
 
144
139
  const app = next({
145
140
  // passing in env here does not get the environment variables there
146
- dev: isDev,
141
+ dev: false,
147
142
  conf: {
148
143
  images: {
149
144
  // This is to avoid the warning with sharp