react-email 5.2.4 → 5.2.6
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 +30 -0
- package/dev/CHANGELOG.md +2 -0
- package/dev/package.json +1 -1
- package/dist/index.js +6372 -79
- package/package.json +3 -3
- package/src/commands/build.ts +44 -26
- package/src/index.ts +91 -52
- package/src/utils/preview/get-env-variables-for-preview-app.ts +9 -7
- package/src/utils/preview/hot-reloading/create-dependency-graph.spec.ts +20 -1
- package/src/utils/preview/hot-reloading/test/dependency-graph/inner/path-aliases.ts +1 -0
- package/src/actions/email-validation/__snapshots__/check-images.spec.tsx.snap +0 -84
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-email",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.6",
|
|
4
4
|
"description": "A live preview of your emails right in your browser.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"email": "./dist/index.js"
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"@types/babel__traverse": "7.20.7",
|
|
44
44
|
"@types/mime-types": "2.1.4",
|
|
45
45
|
"@types/prompts": "2.4.9",
|
|
46
|
-
"next": "16.
|
|
46
|
+
"next": "16.1.5",
|
|
47
47
|
"react": "19.0.0",
|
|
48
48
|
"react-dom": "19.0.0",
|
|
49
49
|
"shlex": "3.0.0",
|
|
50
50
|
"tsx": "4.21.0",
|
|
51
51
|
"typescript": "5.8.3",
|
|
52
|
-
"@react-email/components": "1.0.
|
|
52
|
+
"@react-email/components": "1.0.7"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsdown",
|
package/src/commands/build.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { getPackages } from '@manypkg/get-packages';
|
|
3
5
|
import logSymbols from 'log-symbols';
|
|
4
6
|
import { installDependencies, type PackageManagerName, runScript } from 'nypm';
|
|
5
7
|
import ora from 'ora';
|
|
@@ -15,25 +17,37 @@ interface Args {
|
|
|
15
17
|
packageManager: PackageManagerName;
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const isInReactEmailMonorepo = !dirname.includes('node_modules');
|
|
22
|
+
|
|
18
23
|
const setNextEnvironmentVariablesForBuild = async (
|
|
19
24
|
emailsDirRelativePath: string,
|
|
20
25
|
builtPreviewAppPath: string,
|
|
26
|
+
usersProjectLocation: string,
|
|
21
27
|
) => {
|
|
28
|
+
let rootDir = 'previewServerLocation';
|
|
29
|
+
if (isInReactEmailMonorepo) {
|
|
30
|
+
rootDir = `'${await getPackages(usersProjectLocation).then((p) => p.rootDir.replaceAll('\\', '/'))}'`;
|
|
31
|
+
}
|
|
22
32
|
const nextConfigContents = `
|
|
23
33
|
import path from 'path';
|
|
24
34
|
const emailsDirRelativePath = path.normalize('${emailsDirRelativePath}');
|
|
25
|
-
const userProjectLocation = '${process.cwd().
|
|
26
|
-
const previewServerLocation = '${builtPreviewAppPath.
|
|
35
|
+
const userProjectLocation = '${process.cwd().replaceAll('\\', '/')}';
|
|
36
|
+
const previewServerLocation = '${builtPreviewAppPath.replaceAll('\\', '/')}';
|
|
37
|
+
const rootDir = ${rootDir};
|
|
27
38
|
/** @type {import('next').NextConfig} */
|
|
28
39
|
const nextConfig = {
|
|
29
40
|
env: {
|
|
30
41
|
NEXT_PUBLIC_IS_BUILDING: 'true',
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
REACT_EMAIL_INTERNAL_EMAILS_DIR_RELATIVE_PATH: emailsDirRelativePath,
|
|
43
|
+
REACT_EMAIL_INTERNAL_EMAILS_DIR_ABSOLUTE_PATH: path.resolve(userProjectLocation, emailsDirRelativePath),
|
|
44
|
+
REACT_EMAIL_INTERNAL_PREVIEW_SERVER_LOCATION: previewServerLocation,
|
|
45
|
+
REACT_EMAIL_INTERNAL_USER_PROJECT_LOCATION: userProjectLocation
|
|
46
|
+
},
|
|
47
|
+
turbopack: {
|
|
48
|
+
root: rootDir,
|
|
35
49
|
},
|
|
36
|
-
outputFileTracingRoot:
|
|
50
|
+
outputFileTracingRoot: rootDir,
|
|
37
51
|
serverExternalPackages: ['esbuild'],
|
|
38
52
|
typescript: {
|
|
39
53
|
ignoreBuildErrors: true
|
|
@@ -135,8 +149,10 @@ const updatePackageJson = async (builtPreviewAppPath: string) => {
|
|
|
135
149
|
devDependencies: Record<string, string>;
|
|
136
150
|
};
|
|
137
151
|
// Turbopack has some errors with the imports in @react-email/tailwind
|
|
138
|
-
packageJson.scripts.build =
|
|
139
|
-
|
|
152
|
+
packageJson.scripts.build =
|
|
153
|
+
'cross-env NODE_OPTIONS="--experimental-vm-modules --disable-warning=ExperimentalWarning" next build';
|
|
154
|
+
packageJson.scripts.start =
|
|
155
|
+
'cross-env NODE_OPTIONS="--experimental-vm-modules --disable-warning=ExperimentalWarning" next start';
|
|
140
156
|
delete packageJson.scripts.postbuild;
|
|
141
157
|
|
|
142
158
|
packageJson.name = 'preview-server';
|
|
@@ -162,6 +178,7 @@ export const build = async ({
|
|
|
162
178
|
packageManager,
|
|
163
179
|
}: Args) => {
|
|
164
180
|
try {
|
|
181
|
+
const usersProjectLocation = process.cwd();
|
|
165
182
|
const previewServerLocation = await getPreviewServerLocation();
|
|
166
183
|
|
|
167
184
|
const spinner = ora({
|
|
@@ -175,10 +192,13 @@ export const build = async ({
|
|
|
175
192
|
process.exit(1);
|
|
176
193
|
}
|
|
177
194
|
|
|
178
|
-
const emailsDirPath = path.join(
|
|
195
|
+
const emailsDirPath = path.join(
|
|
196
|
+
usersProjectLocation,
|
|
197
|
+
emailsDirRelativePath,
|
|
198
|
+
);
|
|
179
199
|
const staticPath = path.join(emailsDirPath, 'static');
|
|
180
200
|
|
|
181
|
-
const builtPreviewAppPath = path.join(
|
|
201
|
+
const builtPreviewAppPath = path.join(usersProjectLocation, '.react-email');
|
|
182
202
|
|
|
183
203
|
if (fs.existsSync(builtPreviewAppPath)) {
|
|
184
204
|
spinner.text = 'Deleting pre-existing `.react-email` folder';
|
|
@@ -189,12 +209,11 @@ export const build = async ({
|
|
|
189
209
|
await fs.promises.cp(previewServerLocation, builtPreviewAppPath, {
|
|
190
210
|
recursive: true,
|
|
191
211
|
filter: (source: string) => {
|
|
192
|
-
|
|
212
|
+
const relativeSource = path.relative(previewServerLocation, source);
|
|
193
213
|
return (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
!/(
|
|
197
|
-
!/(\/|\\)node_modules(\/|\\)?$/.test(source)
|
|
214
|
+
!/\.next/.test(relativeSource) &&
|
|
215
|
+
!/\.turbo/.test(relativeSource) &&
|
|
216
|
+
(isInReactEmailMonorepo || !/node_modules/.test(relativeSource))
|
|
198
217
|
);
|
|
199
218
|
},
|
|
200
219
|
});
|
|
@@ -216,6 +235,7 @@ export const build = async ({
|
|
|
216
235
|
await setNextEnvironmentVariablesForBuild(
|
|
217
236
|
emailsDirRelativePath,
|
|
218
237
|
builtPreviewAppPath,
|
|
238
|
+
usersProjectLocation,
|
|
219
239
|
);
|
|
220
240
|
|
|
221
241
|
spinner.text = 'Setting server side generation for the email preview pages';
|
|
@@ -224,12 +244,14 @@ export const build = async ({
|
|
|
224
244
|
spinner.text = "Updating package.json's build and start scripts";
|
|
225
245
|
await updatePackageJson(builtPreviewAppPath);
|
|
226
246
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
247
|
+
if (!isInReactEmailMonorepo) {
|
|
248
|
+
spinner.text = 'Installing dependencies on `.react-email`';
|
|
249
|
+
await installDependencies({
|
|
250
|
+
cwd: builtPreviewAppPath,
|
|
251
|
+
silent: true,
|
|
252
|
+
packageManager,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
233
255
|
|
|
234
256
|
spinner.stopAndPersist({
|
|
235
257
|
text: 'Successfully prepared `.react-email` for `next build`',
|
|
@@ -238,10 +260,6 @@ export const build = async ({
|
|
|
238
260
|
|
|
239
261
|
await runScript('build', {
|
|
240
262
|
packageManager,
|
|
241
|
-
env: {
|
|
242
|
-
NODE_OPTIONS:
|
|
243
|
-
'--experimental-vm-modules --disable-warning=ExperimentalWarning',
|
|
244
|
-
},
|
|
245
263
|
cwd: builtPreviewAppPath,
|
|
246
264
|
});
|
|
247
265
|
} catch (error) {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
2
3
|
import { program } from 'commander';
|
|
3
4
|
import { build } from './commands/build.js';
|
|
4
5
|
import { dev } from './commands/dev.js';
|
|
@@ -8,64 +9,102 @@ import { resendSetup } from './commands/resend/setup.js';
|
|
|
8
9
|
import { start } from './commands/start.js';
|
|
9
10
|
import { packageJson } from './utils/packageJson.js';
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
+
const requiredFlags = [
|
|
13
|
+
'--experimental-vm-modules',
|
|
14
|
+
'--disable-warning=ExperimentalWarning',
|
|
15
|
+
];
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
|
|
16
|
-
.version(packageJson.version);
|
|
17
|
+
const hasRequiredFlags = requiredFlags.every((flag) =>
|
|
18
|
+
process.execArgv.includes(flag),
|
|
19
|
+
);
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
if (!hasRequiredFlags) {
|
|
22
|
+
const child = spawn(
|
|
23
|
+
process.execPath,
|
|
24
|
+
[
|
|
25
|
+
...requiredFlags,
|
|
26
|
+
...process.execArgv,
|
|
27
|
+
process.argv[1] ?? '',
|
|
28
|
+
...process.argv.slice(2),
|
|
29
|
+
],
|
|
30
|
+
{ stdio: 'inherit' },
|
|
31
|
+
);
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'-p --packageManager <name>',
|
|
31
|
-
'Package name to use on installation on `.react-email`',
|
|
32
|
-
'npm',
|
|
33
|
-
)
|
|
34
|
-
.action(build);
|
|
33
|
+
child.on('exit', (code) => {
|
|
34
|
+
process.exit(code ?? 0);
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
const PACKAGE_NAME = 'react-email';
|
|
35
38
|
|
|
36
|
-
program
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
program
|
|
40
|
+
.name(PACKAGE_NAME)
|
|
41
|
+
.description('A live preview of your emails right in your browser')
|
|
42
|
+
.version(packageJson.version);
|
|
40
43
|
|
|
41
|
-
program
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
'-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
44
|
+
program
|
|
45
|
+
.command('dev')
|
|
46
|
+
.description('Starts the preview email development app')
|
|
47
|
+
.option(
|
|
48
|
+
'-d, --dir <path>',
|
|
49
|
+
'Directory with your email templates',
|
|
50
|
+
'./emails',
|
|
51
|
+
)
|
|
52
|
+
.option('-p --port <port>', 'Port to run dev server on', '3000')
|
|
53
|
+
.action(dev);
|
|
54
|
+
|
|
55
|
+
program
|
|
56
|
+
.command('build')
|
|
57
|
+
.description('Copies the preview app for onto .react-email and builds it')
|
|
58
|
+
.option(
|
|
59
|
+
'-d, --dir <path>',
|
|
60
|
+
'Directory with your email templates',
|
|
61
|
+
'./emails',
|
|
62
|
+
)
|
|
63
|
+
.option(
|
|
64
|
+
'-p --packageManager <name>',
|
|
65
|
+
'Package name to use on installation on `.react-email`',
|
|
66
|
+
'npm',
|
|
67
|
+
)
|
|
68
|
+
.action(build);
|
|
69
|
+
|
|
70
|
+
program
|
|
71
|
+
.command('start')
|
|
72
|
+
.description('Runs the built preview app that is inside of ".react-email"')
|
|
73
|
+
.action(start);
|
|
74
|
+
|
|
75
|
+
program
|
|
76
|
+
.command('export')
|
|
77
|
+
.description('Build the templates to the `out` directory')
|
|
78
|
+
.option('--outDir <path>', 'Output directory', 'out')
|
|
79
|
+
.option('-p, --pretty', 'Pretty print the output', false)
|
|
80
|
+
.option('-t, --plainText', 'Set output format as plain text', false)
|
|
81
|
+
.option(
|
|
82
|
+
'-d, --dir <path>',
|
|
83
|
+
'Directory with your email templates',
|
|
84
|
+
'./emails',
|
|
85
|
+
)
|
|
86
|
+
.option(
|
|
87
|
+
'-s, --silent',
|
|
88
|
+
'To, or not to show a spinner with process information',
|
|
89
|
+
false,
|
|
90
|
+
)
|
|
91
|
+
.action(({ outDir, pretty, plainText, silent, dir: srcDir }) =>
|
|
92
|
+
exportTemplates(outDir, srcDir, { silent, plainText, pretty }),
|
|
93
|
+
);
|
|
56
94
|
|
|
57
|
-
const resend = program.command('resend');
|
|
95
|
+
const resend = program.command('resend');
|
|
58
96
|
|
|
59
|
-
resend
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
97
|
+
resend
|
|
98
|
+
.command('setup')
|
|
99
|
+
.description(
|
|
100
|
+
'Sets up the integration between the React Email CLI, and your Resend account through an API Key',
|
|
101
|
+
)
|
|
102
|
+
.action(resendSetup);
|
|
65
103
|
|
|
66
|
-
resend
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
104
|
+
resend
|
|
105
|
+
.command('reset')
|
|
106
|
+
.description('Deletes your API Key from the React Email configuration')
|
|
107
|
+
.action(resendReset);
|
|
70
108
|
|
|
71
|
-
program.parse();
|
|
109
|
+
program.parse();
|
|
110
|
+
}
|
|
@@ -7,12 +7,14 @@ export const getEnvVariablesForPreviewApp = (
|
|
|
7
7
|
resendApiKey?: string,
|
|
8
8
|
) => {
|
|
9
9
|
return {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
REACT_EMAIL_INTERNAL_EMAILS_DIR_RELATIVE_PATH:
|
|
11
|
+
relativePathToEmailsDirectory,
|
|
12
|
+
REACT_EMAIL_INTERNAL_EMAILS_DIR_ABSOLUTE_PATH: path.resolve(
|
|
13
|
+
cwd,
|
|
14
|
+
relativePathToEmailsDirectory,
|
|
15
|
+
),
|
|
16
|
+
REACT_EMAIL_INTERNAL_PREVIEW_SERVER_LOCATION: previewServerLocation,
|
|
17
|
+
REACT_EMAIL_INTERNAL_USER_PROJECT_LOCATION: cwd,
|
|
18
|
+
REACT_EMAIL_INTERNAL_RESEND_API_KEY: resendApiKey,
|
|
17
19
|
} as const;
|
|
18
20
|
};
|
|
@@ -5,7 +5,10 @@ import {
|
|
|
5
5
|
type DependencyGraph,
|
|
6
6
|
} from './create-dependency-graph.js';
|
|
7
7
|
|
|
8
|
-
const testingDiretctory = path.join(
|
|
8
|
+
const testingDiretctory = path.join(
|
|
9
|
+
import.meta.dirname,
|
|
10
|
+
'./test/dependency-graph/inner',
|
|
11
|
+
);
|
|
9
12
|
|
|
10
13
|
const pathToTemporaryFile = path.join(
|
|
11
14
|
testingDiretctory,
|
|
@@ -58,6 +61,14 @@ describe('createDependencyGraph()', async () => {
|
|
|
58
61
|
);
|
|
59
62
|
expect(relativePathDependencyGraph).toMatchInlineSnapshot(`
|
|
60
63
|
{
|
|
64
|
+
"../../some-file.ts": {
|
|
65
|
+
"dependencyPaths": [],
|
|
66
|
+
"dependentPaths": [
|
|
67
|
+
"path-aliases.ts",
|
|
68
|
+
],
|
|
69
|
+
"moduleDependencies": [],
|
|
70
|
+
"path": "../../some-file.ts",
|
|
71
|
+
},
|
|
61
72
|
"../outer.ts": {
|
|
62
73
|
"dependencyPaths": [
|
|
63
74
|
"outer-dependency.ts",
|
|
@@ -120,6 +131,14 @@ describe('createDependencyGraph()', async () => {
|
|
|
120
131
|
"moduleDependencies": [],
|
|
121
132
|
"path": "outer-dependency.ts",
|
|
122
133
|
},
|
|
134
|
+
"path-aliases.ts": {
|
|
135
|
+
"dependencyPaths": [
|
|
136
|
+
"../../some-file.ts",
|
|
137
|
+
],
|
|
138
|
+
"dependentPaths": [],
|
|
139
|
+
"moduleDependencies": [],
|
|
140
|
+
"path": "path-aliases.ts",
|
|
141
|
+
},
|
|
123
142
|
}
|
|
124
143
|
`);
|
|
125
144
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@/some-file';
|
|
@@ -1,84 +0,0 @@
|
|
|
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
|
-
`;
|