react-email 3.0.6 → 4.0.0-alpha.0

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.
Files changed (103) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cli/index.js +768 -763
  3. package/dist/cli/index.mjs +480 -476
  4. package/dist/preview/.next/BUILD_ID +1 -1
  5. package/dist/preview/.next/app-build-manifest.json +14 -12
  6. package/dist/preview/.next/build-manifest.json +5 -5
  7. package/dist/preview/.next/cache/.rscinfo +1 -1
  8. package/dist/preview/.next/cache/webpack/client-production/0.pack +0 -0
  9. package/dist/preview/.next/cache/webpack/client-production/index.pack +0 -0
  10. package/dist/preview/.next/cache/webpack/edge-server-production/index.pack +0 -0
  11. package/dist/preview/.next/cache/webpack/server-production/0.pack +0 -0
  12. package/dist/preview/.next/cache/webpack/server-production/index.pack +0 -0
  13. package/dist/preview/.next/next-minimal-server.js.nft.json +1 -1
  14. package/dist/preview/.next/next-server.js.nft.json +1 -1
  15. package/dist/preview/.next/prerender-manifest.json +1 -1
  16. package/dist/preview/.next/server/app/_not-found/page.js +1 -1
  17. package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/dist/preview/.next/server/app/page.js +1 -1
  19. package/dist/preview/.next/server/app/page.js.nft.json +1 -1
  20. package/dist/preview/.next/server/app/page_client-reference-manifest.js +1 -1
  21. package/dist/preview/.next/server/app/preview/[...slug]/page.js +6 -6
  22. package/dist/preview/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
  23. package/dist/preview/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
  24. package/dist/preview/.next/server/chunks/273.js +1 -0
  25. package/dist/preview/.next/server/chunks/594.js +10 -0
  26. package/dist/preview/.next/server/middleware-build-manifest.js +1 -1
  27. package/dist/preview/.next/server/pages/500.html +1 -1
  28. package/dist/preview/.next/server/server-reference-manifest.js +1 -1
  29. package/dist/preview/.next/server/server-reference-manifest.json +1 -1
  30. package/dist/preview/.next/server/webpack-runtime.js +1 -1
  31. package/dist/preview/.next/static/chunks/18b16e15-6ad9b58e10ff8891.js +1 -0
  32. package/dist/preview/.next/static/chunks/490-48951f2e19ae3aef.js +1 -0
  33. package/dist/preview/.next/static/chunks/600-2e2ca4c8bbd97b61.js +1 -0
  34. package/dist/preview/.next/static/chunks/app/{layout-a2901ed1c2c53661.js → layout-490964e2c3604d33.js} +1 -1
  35. package/dist/preview/.next/static/chunks/app/page-d2432acd08db8fc0.js +1 -0
  36. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-f4e211e00c026401.js +1 -0
  37. package/dist/preview/.next/static/chunks/webpack-7bf1ffb05f5540be.js +1 -0
  38. package/dist/preview/.next/static/css/5e0736cafbb392a9.css +3 -0
  39. package/dist/preview/.next/trace +21 -21
  40. package/package.json +12 -7
  41. package/postcss.config.js +1 -1
  42. package/src/actions/email-validation/check-links.ts +88 -0
  43. package/src/actions/email-validation/get-line-and-column-from-index.spec.ts +22 -0
  44. package/src/actions/email-validation/get-line-and-column-from-index.ts +43 -0
  45. package/src/actions/email-validation/quick-fetch.ts +12 -0
  46. package/src/actions/get-email-path-from-slug.ts +7 -4
  47. package/src/actions/render-email-by-path.tsx +3 -3
  48. package/src/animated-icons-data/help.json +1082 -0
  49. package/src/animated-icons-data/link.json +1309 -0
  50. package/src/animated-icons-data/load.json +443 -0
  51. package/src/animated-icons-data/mail.json +1320 -0
  52. package/src/app/globals.css +0 -24
  53. package/src/app/layout.tsx +7 -3
  54. package/src/app/page.tsx +9 -10
  55. package/src/app/preview/[...slug]/page.tsx +3 -2
  56. package/src/app/preview/[...slug]/preview.tsx +5 -5
  57. package/src/app/preview/[...slug]/rendering-error.tsx +6 -6
  58. package/src/components/button.tsx +8 -8
  59. package/src/components/code-container.tsx +7 -7
  60. package/src/components/code-snippet.tsx +11 -0
  61. package/src/components/code.tsx +4 -4
  62. package/src/components/heading.tsx +1 -1
  63. package/src/components/icons/icon-button.tsx +1 -1
  64. package/src/components/icons/icon-circle-check.tsx +21 -0
  65. package/src/components/icons/icon-circle-close.tsx +17 -0
  66. package/src/components/icons/icon-circle-warning.tsx +17 -0
  67. package/src/components/icons/icon-email.tsx +18 -0
  68. package/src/components/icons/icon-link.tsx +14 -0
  69. package/src/components/icons/icon-stamp.tsx +14 -0
  70. package/src/components/send.tsx +9 -9
  71. package/src/components/shell.tsx +32 -34
  72. package/src/components/sidebar/{sidebar-directory-children.tsx → file-tree-directory-children.tsx} +22 -18
  73. package/src/components/sidebar/{sidebar-directory.tsx → file-tree-directory.tsx} +11 -12
  74. package/src/components/sidebar/file-tree.tsx +31 -0
  75. package/src/components/sidebar/link-checker.tsx +291 -0
  76. package/src/components/sidebar/sidebar.tsx +296 -22
  77. package/src/components/text.tsx +1 -1
  78. package/src/components/tooltip-content.tsx +3 -3
  79. package/src/components/tooltip.tsx +1 -1
  80. package/src/components/topbar.tsx +14 -17
  81. package/src/hooks/use-email-rendering-result.ts +2 -2
  82. package/src/hooks/use-icon-animation.ts +44 -0
  83. package/src/utils/cn.ts +1 -1
  84. package/src/utils/esbuild/renderring-utilities-exporter.ts +1 -1
  85. package/src/utils/get-email-component.ts +6 -6
  86. package/src/utils/get-emails-directory-metadata.spec.ts +0 -1
  87. package/src/utils/improve-error-with-sourcemap.ts +1 -1
  88. package/src/utils/static-node-modules-for-vm.ts +6 -6
  89. package/tsconfig.json +2 -6
  90. package/.eslintrc.js +0 -52
  91. package/.prettierignore +0 -3
  92. package/.prettierrc.js +0 -8
  93. package/dist/preview/.next/cache/eslint/.cache_1c3sgg +0 -1
  94. package/dist/preview/.next/server/chunks/391.js +0 -1
  95. package/dist/preview/.next/server/chunks/720.js +0 -10
  96. package/dist/preview/.next/static/chunks/12-b9450aa0845e7574.js +0 -1
  97. package/dist/preview/.next/static/chunks/154-4202f86af36ccff4.js +0 -1
  98. package/dist/preview/.next/static/chunks/app/page-54a86772095e22e0.js +0 -1
  99. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-2bfad134b65ddd79.js +0 -1
  100. package/dist/preview/.next/static/chunks/webpack-9255716c9496e606.js +0 -1
  101. package/dist/preview/.next/static/css/eb0a93282704d7ab.css +0 -3
  102. /package/dist/preview/.next/static/{Trk1e7GzgKOLunAXBDCy- → fZaiKz58wDr55pxLu9uHa}/_buildManifest.js +0 -0
  103. /package/dist/preview/.next/static/{Trk1e7GzgKOLunAXBDCy- → fZaiKz58wDr55pxLu9uHa}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "3.0.6",
3
+ "version": "4.0.0-alpha.0",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/cli/index.js"
@@ -19,8 +19,8 @@
19
19
  "node": ">=18.0.0"
20
20
  },
21
21
  "dependencies": {
22
- "@babel/core": "7.24.5",
23
22
  "@babel/parser": "7.24.5",
23
+ "@babel/traverse": "7.25.6",
24
24
  "chalk": "4.1.2",
25
25
  "chokidar": "4.0.3",
26
26
  "commander": "11.1.0",
@@ -34,15 +34,21 @@
34
34
  "ora": "5.4.1",
35
35
  "socket.io": "4.8.0"
36
36
  },
37
+ "overrides": {
38
+ "react": "^19",
39
+ "react-dom": "^19"
40
+ },
37
41
  "devDependencies": {
38
42
  "@radix-ui/colors": "1.0.1",
39
43
  "@radix-ui/react-collapsible": "1.1.0",
40
44
  "@radix-ui/react-popover": "1.1.1",
41
45
  "@radix-ui/react-slot": "1.1.0",
46
+ "@radix-ui/react-tabs": "1.1.1",
42
47
  "@radix-ui/react-toggle-group": "1.1.0",
43
48
  "@radix-ui/react-tooltip": "1.1.2",
44
49
  "@swc/core": "1.4.15",
45
50
  "@types/babel__core": "7.20.5",
51
+ "@types/babel__traverse": "*",
46
52
  "@types/fs-extra": "11.0.1",
47
53
  "@types/mime-types": "2.1.4",
48
54
  "@types/node": "22.10.2",
@@ -53,11 +59,11 @@
53
59
  "@vercel/style-guide": "5.1.0",
54
60
  "autoprefixer": "10.4.20",
55
61
  "clsx": "2.1.0",
56
- "eslint": "8.50.0",
57
- "eslint-config-prettier": "9.0.0",
58
- "eslint-config-turbo": "2.1.0",
59
62
  "framer-motion": "12.0.0-alpha.2",
63
+ "lottie-react": "^2.4.0",
64
+ "node-html-parser": "6.1.13",
60
65
  "postcss": "8.4.40",
66
+ "prettier-plugin-tailwindcss": "0.6.6",
61
67
  "prism-react-renderer": "2.1.0",
62
68
  "react": "^19",
63
69
  "react-dom": "^19",
@@ -79,7 +85,6 @@
79
85
  "dev": "tsup-node --watch",
80
86
  "test": "vitest run",
81
87
  "test:watch": "vitest",
82
- "clean": "rm -rf dist",
83
- "lint": "eslint . && tsc"
88
+ "clean": "rm -rf dist"
84
89
  }
85
90
  }
package/postcss.config.js CHANGED
@@ -5,4 +5,4 @@ module.exports = {
5
5
  tailwindcss: { config: path.resolve(__dirname, 'tailwind.config.ts') },
6
6
  autoprefixer: {},
7
7
  },
8
- }
8
+ };
@@ -0,0 +1,88 @@
1
+ 'use server';
2
+
3
+ import { parse } from 'node-html-parser';
4
+ import { quickFetch } from './quick-fetch';
5
+
6
+ type Check = { passed: boolean } & (
7
+ | {
8
+ type: 'fetch_attempt';
9
+ metadata: {
10
+ fetchStatusCode: number | undefined;
11
+ };
12
+ }
13
+ | {
14
+ type: 'syntax';
15
+ }
16
+ | {
17
+ type: 'security';
18
+ }
19
+ );
20
+
21
+ export interface LinkCheckingResult {
22
+ status: 'success' | 'warning' | 'error';
23
+ link: string;
24
+ checks: Check[];
25
+ }
26
+
27
+ export const checkLinks = async (code: string) => {
28
+ const ast = parse(code);
29
+
30
+ const linkCheckingResults: LinkCheckingResult[] = [];
31
+
32
+ const anchors = ast.querySelectorAll('a');
33
+ for await (const anchor of anchors) {
34
+ const link = anchor.attributes.href;
35
+ if (!link) continue;
36
+ if (link.startsWith('mailto:')) continue;
37
+
38
+ const result: LinkCheckingResult = {
39
+ link,
40
+ status: 'success',
41
+ checks: [],
42
+ };
43
+
44
+ try {
45
+ const url = new URL(link);
46
+
47
+ const res = await quickFetch(url);
48
+ const hasntSucceeded =
49
+ res.statusCode === undefined ||
50
+ !res.statusCode.toString().startsWith('2');
51
+ result.checks.push({
52
+ type: 'fetch_attempt',
53
+ passed: hasntSucceeded,
54
+ metadata: {
55
+ fetchStatusCode: res.statusCode,
56
+ },
57
+ });
58
+ if (hasntSucceeded) {
59
+ result.status = res.statusCode?.toString().startsWith('3')
60
+ ? 'warning'
61
+ : 'error';
62
+ }
63
+
64
+ if (link.startsWith('https://')) {
65
+ result.checks.push({
66
+ passed: true,
67
+ type: 'security',
68
+ });
69
+ } else {
70
+ result.checks.push({
71
+ passed: false,
72
+ type: 'security',
73
+ });
74
+ result.status = 'warning';
75
+ }
76
+ } catch (exception) {
77
+ result.checks.push({
78
+ passed: false,
79
+ type: 'syntax',
80
+ });
81
+ result.status = 'error';
82
+ }
83
+
84
+ linkCheckingResults.push(result);
85
+ }
86
+
87
+ return linkCheckingResults;
88
+ };
@@ -0,0 +1,22 @@
1
+ import { getLineAndColumnFromIndex } from './get-line-and-column-from-index';
2
+
3
+ test('getLineAndColumnFromIndex()', () => {
4
+ const code = `import { SomethingElse } from 'somewhere';
5
+
6
+ const myConstant = 'what';
7
+
8
+ const MyComponent = () => {
9
+ return <SomethingElse>
10
+ <div>
11
+ <a>Hello World!</a>{' '}
12
+ {myConstant}
13
+ </div>
14
+ </SomethingElse>;
15
+ }`;
16
+ const [line, column] = getLineAndColumnFromIndex(
17
+ code,
18
+ code.indexOf('Hello World!'),
19
+ );
20
+ expect(line).toBe(8);
21
+ expect(column).toBe(10);
22
+ });
@@ -0,0 +1,43 @@
1
+ const splitByLines = (text: string) => {
2
+ const properSplit: string[] = [];
3
+ const unevenSplit = text.split(/(?<eol>\n|\r|\r\n)/);
4
+
5
+ for (const [i, segment] of unevenSplit.entries()) {
6
+ if (i % 2 === 0) {
7
+ let segmentToInsert = segment;
8
+ if (i + 1 < unevenSplit.length) {
9
+ segmentToInsert += unevenSplit[i + 1];
10
+ }
11
+ properSplit.push(segmentToInsert);
12
+ }
13
+ }
14
+
15
+ return properSplit;
16
+ };
17
+
18
+ export const getLineAndColumnFromIndex = (
19
+ code: string,
20
+ index: number,
21
+ ): [line: number, column: number] => {
22
+ const lines = splitByLines(code);
23
+
24
+ let lineNumber = 1;
25
+ const line = () => {
26
+ const l = lines[lineNumber - 1];
27
+ if (l === undefined)
28
+ throw new Error(
29
+ 'Could not find the line for a specific index in the code',
30
+ { cause: { lines, lineNumber, index } },
31
+ );
32
+ return l;
33
+ };
34
+ let charactersUpToLineStart = 0;
35
+ while (charactersUpToLineStart + line().length < index) {
36
+ charactersUpToLineStart += line().length;
37
+ lineNumber++;
38
+ }
39
+
40
+ const columnNumber = index - charactersUpToLineStart + 1;
41
+
42
+ return [lineNumber, columnNumber];
43
+ };
@@ -0,0 +1,12 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ import http from 'node:http';
3
+ import https from 'node:https';
4
+
5
+ export const quickFetch = (url: URL) => {
6
+ return new Promise<IncomingMessage>((resolve) => {
7
+ const caller = url.protocol === 'https:' ? https : http;
8
+ caller.get(url, (res) => {
9
+ resolve(res);
10
+ });
11
+ });
12
+ };
@@ -1,6 +1,6 @@
1
1
  'use server';
2
- import path from 'node:path';
3
2
  import fs from 'node:fs';
3
+ import path from 'node:path';
4
4
  import { cache } from 'react';
5
5
  import { emailsDirectoryAbsolutePath } from '../utils/emails-directory-absolute-path';
6
6
 
@@ -13,11 +13,14 @@ export const getEmailPathFromSlug = cache(async (slug: string) => {
13
13
 
14
14
  if (fs.existsSync(`${pathWithoutExtension}.tsx`)) {
15
15
  return `${pathWithoutExtension}.tsx`;
16
- } else if (fs.existsSync(`${pathWithoutExtension}.jsx`)) {
16
+ }
17
+ if (fs.existsSync(`${pathWithoutExtension}.jsx`)) {
17
18
  return `${pathWithoutExtension}.jsx`;
18
- } else if (fs.existsSync(`${pathWithoutExtension}.ts`)) {
19
+ }
20
+ if (fs.existsSync(`${pathWithoutExtension}.ts`)) {
19
21
  return `${pathWithoutExtension}.ts`;
20
- } else if (fs.existsSync(`${pathWithoutExtension}.js`)) {
22
+ }
23
+ if (fs.existsSync(`${pathWithoutExtension}.js`)) {
21
24
  return `${pathWithoutExtension}.js`;
22
25
  }
23
26
 
@@ -1,13 +1,13 @@
1
1
  'use server';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import ora from 'ora';
5
- import logSymbols from 'log-symbols';
6
4
  import chalk from 'chalk';
5
+ import logSymbols from 'log-symbols';
6
+ import ora from 'ora';
7
7
  import { getEmailComponent } from '../utils/get-email-component';
8
- import type { ErrorObject } from '../utils/types/error-object';
9
8
  import { improveErrorWithSourceMap } from '../utils/improve-error-with-sourcemap';
10
9
  import { registerSpinnerAutostopping } from '../utils/register-spinner-autostopping';
10
+ import type { ErrorObject } from '../utils/types/error-object';
11
11
 
12
12
  export interface RenderedEmailMetadata {
13
13
  markup: string;