@react-email/preview-server 4.2.11 → 4.3.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 (76) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +9 -9
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/next-minimal-server.js.nft.json +1 -1
  5. package/.next/next-server.js.nft.json +1 -1
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/server/app/_not-found/page.js +1 -1
  8. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  9. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  10. package/.next/server/app/page.js +2 -2
  11. package/.next/server/app/page.js.nft.json +1 -1
  12. package/.next/server/app/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/preview/[...slug]/page.js +30 -30
  14. package/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
  15. package/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
  16. package/.next/server/chunks/235.js +1 -1
  17. package/.next/server/chunks/{597.js → 385.js} +7 -7
  18. package/.next/server/chunks/630.js +1 -1
  19. package/.next/server/chunks/727.js +1 -0
  20. package/.next/server/pages/500.html +1 -1
  21. package/.next/server/server-reference-manifest.js +1 -1
  22. package/.next/server/server-reference-manifest.json +1 -1
  23. package/.next/static/chunks/442-9645091f2b304619.js +1 -0
  24. package/.next/static/chunks/{615-5d450200bdf8a0cb.js → 615-aa01e647fd9055dc.js} +1 -1
  25. package/.next/static/chunks/900-d73ea57316faa50d.js +1 -0
  26. package/.next/static/chunks/app/layout-0337303a89a72f7e.js +1 -0
  27. package/.next/static/chunks/app/{page-af54466b804e69f7.js → page-80a93dc65160c488.js} +1 -1
  28. package/.next/static/chunks/app/preview/[...slug]/page-3205284657cb4573.js +1 -0
  29. package/.next/static/css/7d8cf00703036864.css +3 -0
  30. package/.next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
  31. package/.next/static/media/21350d82a1f187e9-s.woff2 +0 -0
  32. package/.next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
  33. package/.next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
  34. package/.next/trace +29 -29
  35. package/CHANGELOG.md +8 -0
  36. package/package.json +12 -12
  37. package/readme.md +3 -4
  38. package/src/actions/email-validation/check-compatibility.ts +1 -0
  39. package/src/actions/get-email-path-from-slug.ts +1 -1
  40. package/src/actions/render-email-by-path.tsx +8 -3
  41. package/src/app/env.ts +0 -4
  42. package/src/app/layout.tsx +8 -5
  43. package/src/app/page.tsx +2 -4
  44. package/src/app/preview/[...slug]/error-overlay.tsx +1 -0
  45. package/src/app/preview/[...slug]/page.tsx +3 -5
  46. package/src/app/preview/[...slug]/preview.tsx +28 -24
  47. package/src/components/resizable-wrapper.tsx +170 -71
  48. package/src/components/toolbar.tsx +2 -3
  49. package/src/components/topbar/active-view-toggle-group.tsx +4 -4
  50. package/src/components/topbar/view-size-controls.tsx +107 -186
  51. package/src/contexts/emails.tsx +3 -6
  52. package/src/contexts/preview.tsx +13 -1
  53. package/src/hooks/use-hot-reload.ts +2 -2
  54. package/src/utils/caniemail/get-compatibility-stats-for-entry.ts +0 -1
  55. package/src/utils/caniemail/tailwind/get-tailwind-config.spec.ts +2 -2
  56. package/src/utils/contains-email-template.spec.ts +6 -6
  57. package/src/utils/convert-stack-with-sourcemap.ts +0 -1
  58. package/src/utils/get-emails-directory-metadata.ts +0 -1
  59. package/src/utils/js-email-detection.spec.ts +3 -3
  60. package/src/utils/run-bundled-code.ts +1 -3
  61. package/src/utils/testing/request-response-email.tsx +0 -1
  62. package/tailwind-internals.d.ts +0 -2
  63. package/.next/server/chunks/420.js +0 -1
  64. package/.next/static/chunks/557-287480320fe241b8.js +0 -1
  65. package/.next/static/chunks/926-cd84f2c04e4197e1.js +0 -1
  66. package/.next/static/chunks/app/layout-581001443a6ac38a.js +0 -1
  67. package/.next/static/chunks/app/preview/[...slug]/page-356d4a373756b232.js +0 -1
  68. package/.next/static/css/e2f28c91a6a919eb.css +0 -3
  69. package/.next/static/media/26a46d62cd723877-s.woff2 +0 -0
  70. package/.next/static/media/55c55f0601d81cf3-s.woff2 +0 -0
  71. package/.next/static/media/581909926a08bbc8-s.woff2 +0 -0
  72. package/.next/static/media/97e0cb1ae144a2a9-s.woff2 +0 -0
  73. package/src/contexts/fragment-identifier.tsx +0 -48
  74. package/src/hooks/use-icon-animation.ts +0 -41
  75. /package/.next/static/{OIjazFWdAl7sqkAJKSCpp → bw7nsTv8dL6IXcqslAAMG}/_buildManifest.js +0 -0
  76. /package/.next/static/{OIjazFWdAl7sqkAJKSCpp → bw7nsTv8dL6IXcqslAAMG}/_ssgManifest.js +0 -0
@@ -1,5 +1,4 @@
1
- import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
2
- import { motion } from 'framer-motion';
1
+ import * as Popover from '@radix-ui/react-popover';
3
2
  import * as React from 'react';
4
3
  import { cn } from '../../utils';
5
4
  import { IconArrowDown } from '../icons/icon-arrow-down';
@@ -11,208 +10,103 @@ interface ViewDimensions {
11
10
  }
12
11
 
13
12
  interface ViewSizeControlsProps {
13
+ minWidth: number;
14
+ minHeight: number;
14
15
  viewWidth: number;
15
16
  setViewWidth: (width: number) => void;
16
17
  viewHeight: number;
17
18
  setViewHeight: (height: number) => void;
18
19
  }
19
20
 
20
- interface DimensionInputProps {
21
- icon: React.ReactNode;
22
- isActive: boolean;
23
- label: string;
24
- onBlur: () => void;
25
- onChange: (value: number) => void;
26
- setIsActive: (active: boolean) => void;
27
- value: number;
28
- hasBorder?: boolean;
29
- }
30
-
31
21
  interface PresetOption {
32
22
  name: string;
33
23
  dimensions: ViewDimensions;
24
+ icon: React.ReactNode;
34
25
  }
35
26
 
36
- interface PresetMenuItemProps {
37
- name: string;
38
- dimensions: ViewDimensions;
39
- onSelect: (dimensions: ViewDimensions) => void;
40
- }
41
-
42
- const VIEW_PRESETS: PresetOption[] = [
43
- { name: 'Desktop', dimensions: { width: 600, height: 1024 } },
44
- { name: 'Mobile', dimensions: { width: 375, height: 812 } },
45
- ];
46
-
47
- const inputVariant = {
48
- active: {
49
- width: '3.5rem',
50
- padding: '0 0 0 0.5rem',
27
+ export const VIEW_PRESETS: PresetOption[] = [
28
+ {
29
+ name: 'Desktop',
30
+ dimensions: { width: 1024, height: 600 },
31
+ icon: (
32
+ <svg width="15" height="15" viewBox="0 0 15 15" fill="none">
33
+ <path
34
+ d="M1 3.25C1 3.11193 1.11193 3 1.25 3H13.75C13.8881 3 14 3.11193 14 3.25V10.75C14 10.8881 13.8881 11 13.75 11H1.25C1.11193 11 1 10.8881 1 10.75V3.25ZM1.25 2C0.559643 2 0 2.55964 0 3.25V10.75C0 11.4404 0.559644 12 1.25 12H5.07341L4.82991 13.2986C4.76645 13.6371 5.02612 13.95 5.37049 13.95H9.62951C9.97389 13.95 10.2336 13.6371 10.1701 13.2986L9.92659 12H13.75C14.4404 12 15 11.4404 15 10.75V3.25C15 2.55964 14.4404 2 13.75 2H1.25ZM9.01091 12H5.98909L5.79222 13.05H9.20778L9.01091 12Z"
35
+ fill="currentColor"
36
+ fillRule="evenodd"
37
+ clipRule="evenodd"
38
+ />
39
+ </svg>
40
+ ),
51
41
  },
52
- inactive: {
53
- width: '0',
42
+ {
43
+ name: 'Mobile',
44
+ dimensions: { width: 375, height: 667 },
45
+ icon: (
46
+ <svg width="15" height="15" viewBox="0 0 15 15" fill="none">
47
+ <path
48
+ d="M4 2.5C4 2.22386 4.22386 2 4.5 2H10.5C10.7761 2 11 2.22386 11 2.5V12.5C11 12.7761 10.7761 13 10.5 13H4.5C4.22386 13 4 12.7761 4 12.5V2.5ZM4.5 1C3.67157 1 3 1.67157 3 2.5V12.5C3 13.3284 3.67157 14 4.5 14H10.5C11.3284 14 12 13.3284 12 12.5V2.5C12 1.67157 11.3284 1 10.5 1H4.5ZM6 11.65C5.8067 11.65 5.65 11.8067 5.65 12C5.65 12.1933 5.8067 12.35 6 12.35H9C9.1933 12.35 9.35 12.1933 9.35 12C9.35 11.8067 9.1933 11.65 9 11.65H6Z"
49
+ fill="currentColor"
50
+ fillRule="evenodd"
51
+ clipRule="evenodd"
52
+ />
53
+ </svg>
54
+ ),
54
55
  },
55
- };
56
-
57
- const DimensionInput = ({
58
- icon,
59
- isActive,
60
- label,
61
- onBlur,
62
- onChange,
63
- setIsActive,
64
- value,
65
- hasBorder,
66
- }: DimensionInputProps) => {
67
- const inputRef = React.useRef<HTMLInputElement>(null);
68
-
69
- React.useEffect(() => {
70
- if (isActive && inputRef.current) {
71
- inputRef.current.focus();
72
- }
73
- }, [isActive]);
74
-
75
- const handleButtonClick = () => {
76
- if (isActive) {
77
- setIsActive(false);
78
- } else {
79
- setIsActive(true);
80
- }
81
- };
82
-
83
- return (
84
- <Tooltip.Provider>
85
- <Tooltip>
86
- <Tooltip.Trigger asChild>
87
- <motion.button
88
- onClick={handleButtonClick}
89
- className={cn('relative flex items-center justify-center p-2', {
90
- 'border-slate-6 border-r': hasBorder,
91
- })}
92
- >
93
- {icon}
94
- <motion.input
95
- ref={inputRef}
96
- initial={false}
97
- animate={isActive ? 'active' : 'inactive'}
98
- className="arrow-hide relative flex h-8 items-center justify-center bg-black text-sm outline-none"
99
- onChange={(e) => onChange(Number.parseInt(e.currentTarget.value))}
100
- onBlur={onBlur}
101
- type="number"
102
- value={value}
103
- variants={inputVariant}
104
- />
105
- </motion.button>
106
- </Tooltip.Trigger>
107
- <Tooltip.Content>
108
- <span>{label}: </span>
109
- <span className="font-mono">{value}px</span>
110
- </Tooltip.Content>
111
- </Tooltip>
112
- </Tooltip.Provider>
113
- );
114
- };
115
-
116
- const PresetMenuItem = ({
117
- name,
118
- dimensions,
119
- onSelect,
120
- }: PresetMenuItemProps) => (
121
- <DropdownMenu.Item
122
- className="group flex w-full cursor-pointer select-none items-center justify-between rounded-md py-1.5 pr-1 pl-2 text-sm outline-none transition-colors data-[highlighted]:bg-slate-5"
123
- onClick={() => onSelect(dimensions)}
124
- >
125
- {name}
126
- <span className="flex h-fit items-center rounded-full bg-slate-6 px-2 py-1 font-medium text-slate-11 text-xs">
127
- {dimensions.width}x{dimensions.height}
128
- </span>
129
- </DropdownMenu.Item>
130
- );
56
+ ];
131
57
 
132
58
  export const ViewSizeControls = ({
59
+ minWidth,
60
+ minHeight,
133
61
  viewWidth,
134
62
  viewHeight,
135
63
  setViewWidth,
136
64
  setViewHeight,
137
65
  }: ViewSizeControlsProps) => {
138
66
  const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
139
- const [activeInput, setActiveInput] = React.useState<
140
- 'width' | 'height' | null
141
- >(null);
67
+ const [internalWidth, setInternalWidth] = React.useState(viewWidth);
68
+ const [internalHeight, setInternalHeight] = React.useState(viewHeight);
142
69
 
143
70
  const handlePresetSelect = (dimensions: ViewDimensions) => {
144
71
  setViewWidth(dimensions.width);
145
72
  setViewHeight(dimensions.height);
146
73
  };
147
74
 
148
- const handleBlur = () => {
149
- setActiveInput(null);
150
- };
75
+ React.useEffect(() => {
76
+ setInternalWidth(viewWidth);
77
+ setInternalHeight(viewHeight);
78
+ }, [viewWidth, viewHeight]);
151
79
 
152
80
  return (
153
- <div className="relative flex h-9 w-fit overflow-hidden rounded-lg border border-slate-6 text-sm transition-colors duration-300 ease-in-out focus-within:border-slate-8 hover:border-slate-8">
154
- <DimensionInput
155
- icon={
156
- <svg
157
- xmlns="http://www.w3.org/2000/svg"
158
- width="16"
159
- height="16"
160
- viewBox="0 0 24 24"
161
- fill="none"
162
- stroke="currentColor"
163
- strokeWidth="2"
164
- strokeLinecap="round"
165
- strokeLinejoin="round"
166
- >
167
- <path d="M21 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v3" />
168
- <path d="M21 16v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-3" />
169
- <path d="M4 12H2" />
170
- <path d="M10 12H8" />
171
- <path d="M16 12h-2" />
172
- <path d="M22 12h-2" />
173
- </svg>
174
- }
175
- value={viewWidth}
176
- onChange={setViewWidth}
177
- isActive={activeInput === 'width'}
178
- setIsActive={(active) => setActiveInput(active ? 'width' : null)}
179
- onBlur={handleBlur}
180
- label="Width"
181
- hasBorder
182
- />
183
- <DimensionInput
184
- icon={
185
- <svg
186
- xmlns="http://www.w3.org/2000/svg"
187
- width="16"
188
- height="16"
189
- viewBox="0 0 24 24"
190
- fill="none"
191
- stroke="currentColor"
192
- strokeWidth="2"
193
- strokeLinecap="round"
194
- strokeLinejoin="round"
195
- >
196
- <path d="M8 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h3" />
197
- <path d="M16 3h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-3" />
198
- <path d="M12 20v2" />
199
- <path d="M12 14v2" />
200
- <path d="M12 8v2" />
201
- <path d="M12 2v2" />
202
- </svg>
203
- }
204
- value={viewHeight}
205
- onChange={setViewHeight}
206
- isActive={activeInput === 'height'}
207
- setIsActive={(active) => setActiveInput(active ? 'height' : null)}
208
- onBlur={handleBlur}
209
- label="Height"
210
- />
211
- <DropdownMenu.Root open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
212
- <DropdownMenu.Trigger asChild>
81
+ <div className="relative flex h-9 w-fit overflow-hidden rounded-lg border border-slate-6 text-sm transition-colors duration-300 ease-in-out">
82
+ {VIEW_PRESETS.map((preset) => (
83
+ <Tooltip>
84
+ <Tooltip.Trigger asChild>
85
+ <button
86
+ key={preset.name}
87
+ onClick={() => handlePresetSelect(preset.dimensions)}
88
+ className={cn(
89
+ 'relative flex items-center justify-center w-9 transition-colors hover:text-slate-12',
90
+ {
91
+ 'bg-slate-4':
92
+ viewWidth === preset.dimensions.width &&
93
+ viewHeight === preset.dimensions.height,
94
+ },
95
+ )}
96
+ type="button"
97
+ >
98
+ {preset.icon}
99
+ </button>
100
+ </Tooltip.Trigger>
101
+ <Tooltip.Content>{preset.name}</Tooltip.Content>
102
+ </Tooltip>
103
+ ))}
104
+
105
+ <Popover.Root open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
106
+ <Popover.Trigger asChild>
213
107
  <button
214
108
  type="button"
215
- className="relative flex items-center justify-center overflow-hidden bg-slate-5 p-2 text-slate-11 text-sm leading-none outline-none transition-colors ease-linear focus-within:text-slate-12 hover:text-slate-12 focus:text-slate-12"
109
+ className="relative flex items-center justify-center overflow-hidden w-9 text-slate-11 text-sm leading-none outline-none transition-colors ease-linear focus-within:text-slate-12 hover:text-slate-12 focus:text-slate-12"
216
110
  >
217
111
  <span className="sr-only">View presets</span>
218
112
  <IconArrowDown
@@ -224,24 +118,51 @@ export const ViewSizeControls = ({
224
118
  )}
225
119
  />
226
120
  </button>
227
- </DropdownMenu.Trigger>
228
- <DropdownMenu.Portal>
229
- <DropdownMenu.Content
121
+ </Popover.Trigger>
122
+ <Popover.Portal>
123
+ <Popover.Content
230
124
  align="end"
231
125
  className="flex min-w-[12rem] flex-col gap-2 rounded-md border border-slate-8 border-solid bg-black px-2 py-2 text-white"
232
126
  sideOffset={5}
233
127
  >
234
- {VIEW_PRESETS.map((preset) => (
235
- <PresetMenuItem
236
- key={preset.name}
237
- name={preset.name}
238
- dimensions={preset.dimensions}
239
- onSelect={handlePresetSelect}
128
+ <div className="flex w-full items-center justify-between text-sm gap-2">
129
+ <span className="font-medium text-slate-11 text-xs">Width</span>
130
+ <input
131
+ type="number"
132
+ value={internalWidth}
133
+ onChange={(e) => {
134
+ const value = Number(e.target.value);
135
+
136
+ setInternalWidth(value);
137
+
138
+ if (value >= minWidth) {
139
+ setViewWidth(value);
140
+ }
141
+ }}
142
+ className="w-20 appearance-none rounded-lg border border-slate-6 bg-slate-5 px-1 py-1 text-sm text-slate-12 placeholder-slate-10 outline-none transition duration-300 ease-in-out focus:ring-1 focus:ring-slate-10"
143
+ />
144
+ </div>
145
+
146
+ <div className="flex w-full items-center justify-between text-sm gap-2">
147
+ <span className="font-medium text-slate-11 text-xs">Height</span>
148
+ <input
149
+ type="number"
150
+ value={internalHeight}
151
+ onChange={(e) => {
152
+ const value = Number(e.target.value);
153
+
154
+ setInternalHeight(value);
155
+
156
+ if (value >= minHeight) {
157
+ setViewHeight(value);
158
+ }
159
+ }}
160
+ className="w-20 appearance-none rounded-lg border border-slate-6 bg-slate-5 px-1 py-1 text-sm text-slate-12 placeholder-slate-10 outline-none transition duration-300 ease-in-out focus:ring-1 focus:ring-slate-10"
240
161
  />
241
- ))}
242
- </DropdownMenu.Content>
243
- </DropdownMenu.Portal>
244
- </DropdownMenu.Root>
162
+ </div>
163
+ </Popover.Content>
164
+ </Popover.Portal>
165
+ </Popover.Root>
245
166
  </div>
246
167
  );
247
168
  };
@@ -1,4 +1,5 @@
1
1
  'use client';
2
+
2
3
  import { createContext, useContext, useState } from 'react';
3
4
  import { getEmailsDirectoryMetadataAction } from '../actions/get-emails-directory-metadata-action';
4
5
  import { isBuilding, isPreviewDevelopment } from '../app/env';
@@ -17,7 +18,7 @@ export const useEmails = () => {
17
18
 
18
19
  if (typeof providerValue === 'undefined') {
19
20
  throw new Error(
20
- 'Cannot call `useEmail()` outside of an EmailsContext provider!',
21
+ 'Cannot call `useEmails` outside of an `EmailsContext` provider.',
21
22
  );
22
23
  }
23
24
 
@@ -48,11 +49,7 @@ export const EmailsProvider = (props: {
48
49
  }
49
50
 
50
51
  return (
51
- <EmailsContext.Provider
52
- value={{
53
- emailsDirectoryMetadata,
54
- }}
55
- >
52
+ <EmailsContext.Provider value={{ emailsDirectoryMetadata }}>
56
53
  {props.children}
57
54
  </EmailsContext.Provider>
58
55
  );
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { useRouter } from 'next/navigation';
3
- import { createContext } from 'react';
3
+ import { createContext, useContext } from 'react';
4
4
  import type {
5
5
  EmailRenderingResult,
6
6
  RenderedEmailMetadata,
@@ -77,3 +77,15 @@ export const PreviewProvider = ({
77
77
  </PreviewContext.Provider>
78
78
  );
79
79
  };
80
+
81
+ export const usePreviewContext = () => {
82
+ const previewContext = useContext(PreviewContext);
83
+
84
+ if (typeof previewContext === 'undefined') {
85
+ throw new Error(
86
+ 'Cannot call `usePreviewContext` outside of an `PreviewContext` provider.',
87
+ );
88
+ }
89
+
90
+ return previewContext;
91
+ };
@@ -1,4 +1,5 @@
1
1
  'use client';
2
+
2
3
  import { useEffect, useRef } from 'react';
3
4
  import { io, type Socket } from 'socket.io-client';
4
5
  import type { HotReloadChange } from '../utils/types/hot-reload-change';
@@ -8,8 +9,7 @@ import type { HotReloadChange } from '../utils/types/hot-reload-change';
8
9
  * and calls the received parameter callback
9
10
  */
10
11
  export const useHotreload = (
11
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
- onShouldReload: (changes: HotReloadChange[]) => any,
12
+ onShouldReload: (changes: HotReloadChange[]) => void,
13
13
  ) => {
14
14
  const socketRef = useRef<Socket | null>(null);
15
15
 
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
1
  import type {
3
2
  EmailClient,
4
3
  Platform,
@@ -5,7 +5,7 @@ import { pixelBasedPreset } from '@react-email/components';
5
5
  import { getTailwindConfig } from './get-tailwind-config';
6
6
 
7
7
  describe('getTailwindConfig()', () => {
8
- it("should work on the demo's Vercel Invite template", async () => {
8
+ it("works on the demo's Vercel Invite template", async () => {
9
9
  const sourcePath = path.resolve(
10
10
  __dirname,
11
11
  '../../../../../../apps/demo/emails/notifications/vercel-invite-user.tsx',
@@ -23,7 +23,7 @@ describe('getTailwindConfig()', () => {
23
23
  });
24
24
  });
25
25
 
26
- it('should work with email templates that import the tailwind config', async () => {
26
+ it('works with email templates that import the tailwind config', async () => {
27
27
  const sourcePath = path.resolve(
28
28
  __dirname,
29
29
  './tests/dummy-email-template.tsx',
@@ -5,19 +5,19 @@ import {
5
5
  import type { EmailsDirectory } from './get-emails-directory-metadata';
6
6
 
7
7
  describe('removeFilenameExtension()', () => {
8
- it('should work with a single .', () => {
8
+ it('works with a single .', () => {
9
9
  expect(removeFilenameExtension('email-template.tsx')).toBe(
10
10
  'email-template',
11
11
  );
12
12
  });
13
13
 
14
- it('should work with an example test file', () => {
14
+ it('works with an example test file', () => {
15
15
  expect(removeFilenameExtension('email-template.spec.tsx')).toBe(
16
16
  'email-template.spec',
17
17
  );
18
18
  });
19
19
 
20
- it('should do nothing when there is no extension', () => {
20
+ it('does nothing when there is no extension', () => {
21
21
  expect(removeFilenameExtension('email-template')).toBe('email-template');
22
22
  });
23
23
  });
@@ -112,11 +112,11 @@ describe('containsEmailTemplate()', () => {
112
112
  ],
113
113
  };
114
114
 
115
- it('should work with collapsed email directory', () => {
115
+ it('works with collapsed email directory', () => {
116
116
  expect(containsEmailTemplate('first/second/email', directory)).toBe(true);
117
117
  });
118
118
 
119
- it('should work with email inside a single sub directory', () => {
119
+ it('works with email inside a single sub directory', () => {
120
120
  expect(containsEmailTemplate('welcome/koala-welcome', directory)).toBe(
121
121
  true,
122
122
  );
@@ -125,7 +125,7 @@ describe('containsEmailTemplate()', () => {
125
125
  );
126
126
  });
127
127
 
128
- it('should work with email inside a second sub directory', () => {
128
+ it('works with email inside a second sub directory', () => {
129
129
  expect(
130
130
  containsEmailTemplate('magic-links/resend/verify-email', directory),
131
131
  ).toBe(true);
@@ -47,7 +47,6 @@ export const convertStackWithSourceMap = (
47
47
  getStackLineFromMethodNameAndSource(
48
48
  stackFrame.methodName,
49
49
  // This can actually be null
50
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
51
50
  positionWithError.source ?? stackFrame.file,
52
51
  positionWithError.line,
53
52
  positionWithError.column,
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
 
@@ -5,19 +5,19 @@ describe('JavaScript Email Detection', async () => {
5
5
  const testingDir = path.resolve(__dirname, 'testing');
6
6
  const emailsMetadata = await getEmailsDirectoryMetadata(testingDir, true);
7
7
 
8
- it('should detect JavaScript files with ES6 export default syntax', async () => {
8
+ it('detects JavaScript files with ES6 export default syntax', async () => {
9
9
  expect(emailsMetadata).toBeDefined();
10
10
  expect(emailsMetadata?.emailFilenames).toContain(
11
11
  'js-email-export-default.js',
12
12
  );
13
13
  });
14
14
 
15
- it('should detect JavaScript files with CommonJS module.exports', async () => {
15
+ it('detects JavaScript files with CommonJS module.exports', async () => {
16
16
  expect(emailsMetadata).toBeDefined();
17
17
  expect(emailsMetadata?.emailFilenames).toContain('js-email-test.js');
18
18
  });
19
19
 
20
- it('should detect MDX-style JavaScript files with named exports', async () => {
20
+ it('detects MDX-style JavaScript files with named exports', async () => {
21
21
  expect(emailsMetadata).toBeDefined();
22
22
  expect(emailsMetadata?.emailFilenames).toContain('mdx-email-test.js');
23
23
  });
@@ -35,13 +35,11 @@ export const createContext = (filename: string): vm.Context => {
35
35
  }
36
36
 
37
37
  if (m in staticNodeModulesForVM) {
38
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
39
38
  return staticNodeModulesForVM[m];
40
39
  }
41
40
 
42
- // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-useless-template-literals
43
41
  return require(`${specifiedModule}`) as unknown;
44
- // this stupid string templating was necessary to not have
42
+ // this string templating was necessary to not have
45
43
  // webpack warnings like:
46
44
  //
47
45
  // Import trace for requested module:
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
1
  const _req = new Request('https://react.email');
3
2
  const _res = new Response('{}');
4
3
 
@@ -1,5 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
-
3
1
  declare module "tailwindcss/lib/lib/evaluateTailwindFunctions" {
4
2
  import type { JitContext } from "tailwindcss/lib/lib/setupContextUtils";
5
3
  import type { Root } from "postcss";
@@ -1 +0,0 @@
1
- exports.id=420,exports.ids=[420],exports.modules={8332:(a,b,c)=>{"use strict";c.r(b),c.d(b,{getEmailsDirectoryMetadata:()=>i});var d=c(73024),e=c.n(d),f=c(76760),g=c.n(f);let h=async a=>{let b;try{b=await e().promises.open(a,"r")}catch(a){return console.warn(a),!1}if((await b.stat()).isDirectory())return await b.close(),!1;let{ext:c}=g().parse(a);if(![".js",".tsx",".jsx"].includes(c))return await b.close(),!1;let d=await b.readFile("utf8");await b.close();let f=/\bexport\s+default\b/gm.test(d),h=/\bmodule\.exports\s*=/gm.test(d),i=/\bexport\s+\{[^}]*\bdefault\b[^}]*\}/gm.test(d);return f||h||i},i=async(a,b=!1,c=!1,d=a)=>{if(!e().existsSync(a))return;let f=await e().promises.readdir(a,{withFileTypes:!0}),j=await Promise.all(f.map(b=>h(g().join(a,b.name)))),k=f.filter((a,b)=>j[b]).map(a=>b?a.name:a.name.replace(g().extname(a.name),"")),l=await Promise.all(f.filter(a=>a.isDirectory()&&!a.name.startsWith("_")&&"static"!==a.name).map(c=>i(g().join(a,c.name),b,!0,d))),m={absolutePath:a,relativePath:g().relative(d,a),directoryName:a.split(g().sep).pop(),emailFilenames:k,subDirectories:l};return c?(a=>{let b=a;for(;0===b.emailFilenames.length&&1===b.subDirectories.length;){let a=b.subDirectories[0];b={...a,directoryName:g().join(b.directoryName,a.directoryName)}}return b})(m):m}},15482:(a,b,c)=>{"use strict";c.d(b,{EmailsProvider:()=>l,J:()=>k});var d=c(6362),e=c(2179),f=c(14298);let g=(0,f.createServerReference)("7f009fd45016bdb4200c108baf47a913fb024a9adb",f.callServer,void 0,f.findSourceMapURL,"getEmailsDirectoryMetadataAction");var h=c(70985),i=c(39520);let j=(0,e.createContext)(void 0),k=()=>{let a=(0,e.useContext)(j);if(void 0===a)throw Error("Cannot call `useEmail()` outside of an EmailsContext provider!");return a},l=a=>{let[b,c]=(0,e.useState)(a.initialEmailsDirectoryMetadata);return h.Hf||h.m4||(0,i.a)(async()=>{let b=await g(a.initialEmailsDirectoryMetadata.absolutePath);if(b)c(b);else throw Error("Hot reloading: unable to find the emails directory to update the sidebar")}),(0,d.jsx)(j.Provider,{value:{emailsDirectoryMetadata:b},children:a.children})}},25880:(a,b,c)=>{Promise.resolve().then(c.t.bind(c,36100,23)),Promise.resolve().then(c.t.bind(c,99455,23)),Promise.resolve().then(c.t.bind(c,26835,23)),Promise.resolve().then(c.t.bind(c,36726,23)),Promise.resolve().then(c.t.bind(c,33666,23)),Promise.resolve().then(c.t.bind(c,23670,23)),Promise.resolve().then(c.t.bind(c,30006,23)),Promise.resolve().then(c.t.bind(c,41971,23)),Promise.resolve().then(c.t.bind(c,40614,23))},27378:(a,b,c)=>{Promise.resolve().then(c.bind(c,90948))},35191:()=>{},35608:(a,b,c)=>{Promise.resolve().then(c.t.bind(c,5058,23)),Promise.resolve().then(c.t.bind(c,35621,23)),Promise.resolve().then(c.t.bind(c,44325,23)),Promise.resolve().then(c.t.bind(c,20908,23)),Promise.resolve().then(c.t.bind(c,83572,23)),Promise.resolve().then(c.t.bind(c,25928,23)),Promise.resolve().then(c.t.bind(c,57920,23)),Promise.resolve().then(c.t.bind(c,2761,23)),Promise.resolve().then(c.bind(c,98400))},39520:(a,b,c)=>{"use strict";c.d(b,{a:()=>f});var d=c(2179),e=c(80095);let f=a=>{let b=(0,d.useRef)(null);(0,d.useEffect)(()=>{b.current||(b.current=(0,e.io)());let c=b.current;return c.on("reload",b=>{console.debug("Reloading..."),a(b)}),()=>{c.off()}},[a])}},50938:(a,b,c)=>{Promise.resolve().then(c.bind(c,15482))},54208:(a,b,c)=>{"use strict";c.r(b),c.d(b,{default:()=>n,dynamic:()=>m,metadata:()=>l});var d=c(51064);c(35191);var e=c(90948),f=c(8332),g=c(88155),h=c(84858),i=c.n(h),j=c(54629),k=c.n(j);let l={title:"React Email"},m="force-dynamic",n=async({children:a})=>{let b=await (0,f.getEmailsDirectoryMetadata)(g.Z8);if(void 0===b)throw Error(`Could not find the emails directory specified under ${g.Z8}!`);return(0,d.jsx)("html",{className:`${i().variable} ${k().variable} font-sans`,lang:"en",children:(0,d.jsx)("body",{className:"relative h-screen bg-black text-slate-11 leading-loose selection:bg-cyan-5 selection:text-cyan-12",children:(0,d.jsx)("div",{className:"bg-gradient-to-t from-slate-3 flex flex-col",children:(0,d.jsx)(e.EmailsProvider,{initialEmailsDirectoryMetadata:b,children:a})})})})}},70985:(a,b,c)=>{"use strict";c.d(b,{Hf:()=>d,m4:()=>e}),process.env.EMAILS_DIR_RELATIVE_PATH,process.env.USER_PROJECT_LOCATION,process.env.PREVIEW_SERVER_LOCATION,process.env.EMAILS_DIR_ABSOLUTE_PATH;let d="true"===process.env.NEXT_PUBLIC_IS_BUILDING,e="true"===process.env.NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT},72112:()=>{},86944:(a,b,c)=>{"use strict";c.r(b),c.d(b,{default:()=>e});var d=c(89993);let e=async a=>[{type:"image/x-icon",sizes:"16x16",url:(0,d.fillMetadataSegment)(".",await a.params,"favicon.ico")+""}]},88155:(a,b,c)=>{"use strict";c.d(b,{Hf:()=>g,Z8:()=>f,m4:()=>h,n_:()=>e,w5:()=>d}),process.env.EMAILS_DIR_RELATIVE_PATH;let d=process.env.USER_PROJECT_LOCATION,e=process.env.PREVIEW_SERVER_LOCATION,f=process.env.EMAILS_DIR_ABSOLUTE_PATH,g="true"===process.env.NEXT_PUBLIC_IS_BUILDING,h="true"===process.env.NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT},88167:(a,b,c)=>{"use strict";c.r(b),c.d(b,{"7f009fd45016bdb4200c108baf47a913fb024a9adb":()=>l});var d=c(90466);c(69300);var e=c(73024),f=c.n(e),g=c(76760),h=c.n(g);let i=async a=>{let b;try{b=await f().promises.open(a,"r")}catch(a){return console.warn(a),!1}if((await b.stat()).isDirectory())return await b.close(),!1;let{ext:c}=h().parse(a);if(![".js",".tsx",".jsx"].includes(c))return await b.close(),!1;let d=await b.readFile("utf8");await b.close();let e=/\bexport\s+default\b/gm.test(d),g=/\bmodule\.exports\s*=/gm.test(d),i=/\bexport\s+\{[^}]*\bdefault\b[^}]*\}/gm.test(d);return e||g||i},j=async(a,b=!1,c=!1,d=a)=>{if(!f().existsSync(a))return;let e=await f().promises.readdir(a,{withFileTypes:!0}),g=await Promise.all(e.map(b=>i(h().join(a,b.name)))),k=e.filter((a,b)=>g[b]).map(a=>b?a.name:a.name.replace(h().extname(a.name),"")),l=await Promise.all(e.filter(a=>a.isDirectory()&&!a.name.startsWith("_")&&"static"!==a.name).map(c=>j(h().join(a,c.name),b,!0,d))),m={absolutePath:a,relativePath:h().relative(d,a),directoryName:a.split(h().sep).pop(),emailFilenames:k,subDirectories:l};return c?(a=>{let b=a;for(;0===b.emailFilenames.length&&1===b.subDirectories.length;){let a=b.subDirectories[0];b={...a,directoryName:h().join(b.directoryName,a.directoryName)}}return b})(m):m};var k=c(18012);let l=async(a,b=!1,c=!1,d=a)=>j(a,b,c,d);(0,k.D)([l]),(0,d.A)(l,"7f009fd45016bdb4200c108baf47a913fb024a9adb",null)},90948:(a,b,c)=>{"use strict";c.d(b,{EmailsProvider:()=>e});var d=c(9756);(0,d.registerClientReference)(function(){throw Error("Attempted to call useEmails() from the server but useEmails is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"/home/runner/actions-runner/_work/react-email/react-email/packages/preview-server/src/contexts/emails.tsx","useEmails");let e=(0,d.registerClientReference)(function(){throw Error("Attempted to call EmailsProvider() from the server but EmailsProvider is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"/home/runner/actions-runner/_work/react-email/react-email/packages/preview-server/src/contexts/emails.tsx","EmailsProvider")},92665:()=>{}};