@depup/react-email 5.2.10-depup.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.
- package/CHANGELOG.md +652 -0
- package/README.md +43 -0
- package/changes.json +58 -0
- package/dev/CHANGELOG.md +13 -0
- package/dev/index.js +46 -0
- package/dev/package.json +10 -0
- package/dist/index.mjs +7326 -0
- package/license.md +7 -0
- package/package.json +128 -0
- package/readme.md +59 -0
- package/src/commands/build.ts +269 -0
- package/src/commands/dev.ts +27 -0
- package/src/commands/export.ts +204 -0
- package/src/commands/resend/reset.ts +8 -0
- package/src/commands/resend/setup.ts +29 -0
- package/src/commands/start.ts +38 -0
- package/src/index.ts +110 -0
- package/src/utils/conf.ts +9 -0
- package/src/utils/esbuild/escape-string-for-regex.ts +3 -0
- package/src/utils/esbuild/renderring-utilities-exporter.ts +63 -0
- package/src/utils/get-emails-directory-metadata.spec.ts +82 -0
- package/src/utils/get-emails-directory-metadata.ts +140 -0
- package/src/utils/get-preview-server-location.ts +50 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/packageJson.ts +4 -0
- package/src/utils/preview/get-env-variables-for-preview-app.ts +20 -0
- package/src/utils/preview/hot-reloading/create-dependency-graph.spec.ts +226 -0
- package/src/utils/preview/hot-reloading/create-dependency-graph.ts +343 -0
- package/src/utils/preview/hot-reloading/get-imported-modules.spec.ts +151 -0
- package/src/utils/preview/hot-reloading/get-imported-modules.ts +49 -0
- package/src/utils/preview/hot-reloading/resolve-path-aliases.spec.ts +11 -0
- package/src/utils/preview/hot-reloading/resolve-path-aliases.ts +32 -0
- package/src/utils/preview/hot-reloading/setup-hot-reloading.ts +121 -0
- package/src/utils/preview/hot-reloading/test/dependency-graph/inner/data-to-import.json +1 -0
- package/src/utils/preview/hot-reloading/test/dependency-graph/inner/file-a.ts +5 -0
- package/src/utils/preview/hot-reloading/test/dependency-graph/inner/file-b.ts +5 -0
- package/src/utils/preview/hot-reloading/test/dependency-graph/inner/general-importing-file.ts +9 -0
- package/src/utils/preview/hot-reloading/test/dependency-graph/inner/outer-dependency.ts +3 -0
- package/src/utils/preview/hot-reloading/test/dependency-graph/inner/path-aliases.ts +1 -0
- package/src/utils/preview/hot-reloading/test/dependency-graph/outer.ts +5 -0
- package/src/utils/preview/hot-reloading/test/some-file.ts +0 -0
- package/src/utils/preview/hot-reloading/test/tsconfig.json +8 -0
- package/src/utils/preview/index.ts +2 -0
- package/src/utils/preview/serve-static-file.ts +51 -0
- package/src/utils/preview/start-dev-server.ts +252 -0
- package/src/utils/register-spinner-autostopping.ts +28 -0
- package/src/utils/style-text.ts +11 -0
- package/src/utils/tree.spec.ts +29 -0
- package/src/utils/tree.ts +76 -0
- package/src/utils/types/hot-reload-change.ts +6 -0
- package/src/utils/types/hot-reload-event.ts +3 -0
- package/tsconfig.json +39 -0
- package/tsdown.config.ts +8 -0
- package/vitest.config.ts +15 -0
package/license.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2024 Plus Five Five, Inc
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@depup/react-email",
|
|
3
|
+
"version": "5.2.10-depup.0",
|
|
4
|
+
"description": "A live preview of your emails right in your browser. (with updated dependencies)",
|
|
5
|
+
"bin": {
|
|
6
|
+
"email": "./dist/index.mjs"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/resend/react-email.git",
|
|
13
|
+
"directory": "packages/react-email"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"react-email",
|
|
17
|
+
"depup",
|
|
18
|
+
"updated-dependencies",
|
|
19
|
+
"security",
|
|
20
|
+
"latest",
|
|
21
|
+
"patched",
|
|
22
|
+
"react",
|
|
23
|
+
"email"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20.0.0"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@babel/parser": "^7.29.2",
|
|
30
|
+
"@babel/traverse": "^7.29.0",
|
|
31
|
+
"chokidar": "^5.0.0",
|
|
32
|
+
"commander": "^14.0.3",
|
|
33
|
+
"conf": "^15.1.0",
|
|
34
|
+
"debounce": "^3.0.0",
|
|
35
|
+
"esbuild": "^0.27.4",
|
|
36
|
+
"glob": "^13.0.6",
|
|
37
|
+
"jiti": "^2.6.1",
|
|
38
|
+
"log-symbols": "^7.0.1",
|
|
39
|
+
"mime-types": "^3.0.2",
|
|
40
|
+
"normalize-path": "^3.0.0",
|
|
41
|
+
"nypm": "^0.6.5",
|
|
42
|
+
"ora": "^9.3.0",
|
|
43
|
+
"prompts": "2.4.2",
|
|
44
|
+
"socket.io": "^4.8.3",
|
|
45
|
+
"tsconfig-paths": "4.2.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/babel__core": "7.20.5",
|
|
49
|
+
"@types/babel__traverse": "7.20.7",
|
|
50
|
+
"@types/mime-types": "2.1.4",
|
|
51
|
+
"@types/prompts": "2.4.9",
|
|
52
|
+
"next": "16.1.7",
|
|
53
|
+
"react": "19.0.0",
|
|
54
|
+
"react-dom": "19.0.0",
|
|
55
|
+
"shlex": "3.0.0",
|
|
56
|
+
"tsx": "4.21.0",
|
|
57
|
+
"typescript": "5.9.3",
|
|
58
|
+
"@react-email/components": "1.0.10"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsdown",
|
|
62
|
+
"build:watch": "tsdown --watch src",
|
|
63
|
+
"clean": "rm -rf dist",
|
|
64
|
+
"test": "vitest run",
|
|
65
|
+
"test:watch": "vitest"
|
|
66
|
+
},
|
|
67
|
+
"depup": {
|
|
68
|
+
"changes": {
|
|
69
|
+
"@babel/parser": {
|
|
70
|
+
"from": "7.27.0",
|
|
71
|
+
"to": "^7.29.2"
|
|
72
|
+
},
|
|
73
|
+
"@babel/traverse": {
|
|
74
|
+
"from": "7.27.0",
|
|
75
|
+
"to": "^7.29.0"
|
|
76
|
+
},
|
|
77
|
+
"chokidar": {
|
|
78
|
+
"from": "^4.0.3",
|
|
79
|
+
"to": "^5.0.0"
|
|
80
|
+
},
|
|
81
|
+
"commander": {
|
|
82
|
+
"from": "^13.0.0",
|
|
83
|
+
"to": "^14.0.3"
|
|
84
|
+
},
|
|
85
|
+
"conf": {
|
|
86
|
+
"from": "^15.0.2",
|
|
87
|
+
"to": "^15.1.0"
|
|
88
|
+
},
|
|
89
|
+
"debounce": {
|
|
90
|
+
"from": "^2.0.0",
|
|
91
|
+
"to": "^3.0.0"
|
|
92
|
+
},
|
|
93
|
+
"esbuild": {
|
|
94
|
+
"from": "0.27.3",
|
|
95
|
+
"to": "^0.27.4"
|
|
96
|
+
},
|
|
97
|
+
"jiti": {
|
|
98
|
+
"from": "2.4.2",
|
|
99
|
+
"to": "^2.6.1"
|
|
100
|
+
},
|
|
101
|
+
"log-symbols": {
|
|
102
|
+
"from": "^7.0.0",
|
|
103
|
+
"to": "^7.0.1"
|
|
104
|
+
},
|
|
105
|
+
"mime-types": {
|
|
106
|
+
"from": "^3.0.0",
|
|
107
|
+
"to": "^3.0.2"
|
|
108
|
+
},
|
|
109
|
+
"nypm": {
|
|
110
|
+
"from": "0.6.2",
|
|
111
|
+
"to": "^0.6.5"
|
|
112
|
+
},
|
|
113
|
+
"ora": {
|
|
114
|
+
"from": "^8.0.0",
|
|
115
|
+
"to": "^9.3.0"
|
|
116
|
+
},
|
|
117
|
+
"socket.io": {
|
|
118
|
+
"from": "^4.8.1",
|
|
119
|
+
"to": "^4.8.3"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"depsUpdated": 13,
|
|
123
|
+
"originalPackage": "react-email",
|
|
124
|
+
"originalVersion": "5.2.10",
|
|
125
|
+
"processedAt": "2026-03-18T23:28:27.758Z",
|
|
126
|
+
"smokeTest": "failed"
|
|
127
|
+
}
|
|
128
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
<div align="center"><strong>React Email</strong></div>
|
|
4
|
+
<div align="center">The next generation of writing emails.<br />High-quality, unstyled components for creating emails.</div>
|
|
5
|
+
<br />
|
|
6
|
+
<div align="center">
|
|
7
|
+
<a href="https://react.email">Website</a>
|
|
8
|
+
<span> · </span>
|
|
9
|
+
<a href="https://github.com/resend/react-email">GitHub</a>
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
## Getting started
|
|
14
|
+
|
|
15
|
+
To get started, open a new shell and run:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npx create-email
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This will create a new folder called `emails` with a few email templates.
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
### `email dev`
|
|
26
|
+
|
|
27
|
+
Starts a local development server that will watch your files and automatically rebuild your email when you make changes.
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npx react-email dev
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### `email export`
|
|
34
|
+
|
|
35
|
+
Generates the plain HTML files of your emails into a `out` directory.
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npx react-email export
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Setting Up the Environment
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
### 1. Link `react-email` globally
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
pnpm link ./dev -g
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Run the CLI
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
email-dev [command] [flags]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT License
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { getPackages } from '@manypkg/get-packages';
|
|
5
|
+
import logSymbols from 'log-symbols';
|
|
6
|
+
import { installDependencies, type PackageManagerName, runScript } from 'nypm';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import {
|
|
9
|
+
type EmailsDirectory,
|
|
10
|
+
getEmailsDirectoryMetadata,
|
|
11
|
+
} from '../utils/get-emails-directory-metadata.js';
|
|
12
|
+
import { getPreviewServerLocation } from '../utils/get-preview-server-location.js';
|
|
13
|
+
import { registerSpinnerAutostopping } from '../utils/register-spinner-autostopping.js';
|
|
14
|
+
|
|
15
|
+
interface Args {
|
|
16
|
+
dir: string;
|
|
17
|
+
packageManager: PackageManagerName;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const isInReactEmailMonorepo = !dirname.includes('node_modules');
|
|
22
|
+
|
|
23
|
+
const setNextEnvironmentVariablesForBuild = async (
|
|
24
|
+
emailsDirRelativePath: string,
|
|
25
|
+
builtPreviewAppPath: string,
|
|
26
|
+
usersProjectLocation: string,
|
|
27
|
+
) => {
|
|
28
|
+
let rootDir = 'previewServerLocation';
|
|
29
|
+
if (isInReactEmailMonorepo) {
|
|
30
|
+
rootDir = `'${await getPackages(usersProjectLocation).then((p) => p.rootDir.replaceAll('\\', '/'))}'`;
|
|
31
|
+
}
|
|
32
|
+
const nextConfigContents = `
|
|
33
|
+
import path from 'path';
|
|
34
|
+
const emailsDirRelativePath = path.normalize('${emailsDirRelativePath}');
|
|
35
|
+
const userProjectLocation = '${process.cwd().replaceAll('\\', '/')}';
|
|
36
|
+
const previewServerLocation = '${builtPreviewAppPath.replaceAll('\\', '/')}';
|
|
37
|
+
const rootDir = ${rootDir};
|
|
38
|
+
/** @type {import('next').NextConfig} */
|
|
39
|
+
const nextConfig = {
|
|
40
|
+
env: {
|
|
41
|
+
NEXT_PUBLIC_IS_BUILDING: 'true',
|
|
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,
|
|
49
|
+
},
|
|
50
|
+
outputFileTracingRoot: rootDir,
|
|
51
|
+
serverExternalPackages: ['esbuild'],
|
|
52
|
+
typescript: {
|
|
53
|
+
ignoreBuildErrors: true
|
|
54
|
+
},
|
|
55
|
+
staticPageGenerationTimeout: 600,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default nextConfig`;
|
|
59
|
+
|
|
60
|
+
await fs.promises.writeFile(
|
|
61
|
+
path.resolve(builtPreviewAppPath, './next.config.mjs'),
|
|
62
|
+
nextConfigContents,
|
|
63
|
+
'utf8',
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const getEmailSlugsFromEmailDirectory = (
|
|
68
|
+
emailDirectory: EmailsDirectory,
|
|
69
|
+
emailsDirectoryAbsolutePath: string,
|
|
70
|
+
) => {
|
|
71
|
+
const directoryPathRelativeToEmailsDirectory = emailDirectory.absolutePath
|
|
72
|
+
.replace(emailsDirectoryAbsolutePath, '')
|
|
73
|
+
.trim();
|
|
74
|
+
|
|
75
|
+
const slugs = [] as Array<string>[];
|
|
76
|
+
for (const filename of emailDirectory.emailFilenames) {
|
|
77
|
+
slugs.push(
|
|
78
|
+
path
|
|
79
|
+
.join(directoryPathRelativeToEmailsDirectory, filename)
|
|
80
|
+
.split(path.sep)
|
|
81
|
+
// sometimes it gets empty segments due to trailing slashes
|
|
82
|
+
.filter((segment) => segment.length > 0),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
for (const directory of emailDirectory.subDirectories) {
|
|
86
|
+
slugs.push(
|
|
87
|
+
...getEmailSlugsFromEmailDirectory(
|
|
88
|
+
directory,
|
|
89
|
+
emailsDirectoryAbsolutePath,
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return slugs;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// we do this because otherwise it won't be able to find the emails
|
|
98
|
+
// after build
|
|
99
|
+
const forceSSGForEmailPreviews = async (
|
|
100
|
+
emailsDirPath: string,
|
|
101
|
+
builtPreviewAppPath: string,
|
|
102
|
+
) => {
|
|
103
|
+
const emailDirectoryMetadata = (await getEmailsDirectoryMetadata(
|
|
104
|
+
emailsDirPath,
|
|
105
|
+
))!;
|
|
106
|
+
|
|
107
|
+
const parameters = getEmailSlugsFromEmailDirectory(
|
|
108
|
+
emailDirectoryMetadata,
|
|
109
|
+
emailsDirPath,
|
|
110
|
+
).map((slug) => ({ slug }));
|
|
111
|
+
|
|
112
|
+
const removeForceDynamic = async (filePath: string) => {
|
|
113
|
+
const contents = await fs.promises.readFile(filePath, 'utf8');
|
|
114
|
+
|
|
115
|
+
await fs.promises.writeFile(
|
|
116
|
+
filePath,
|
|
117
|
+
contents.replace("export const dynamic = 'force-dynamic';", ''),
|
|
118
|
+
'utf8',
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
await removeForceDynamic(
|
|
122
|
+
path.resolve(builtPreviewAppPath, './src/app/layout.tsx'),
|
|
123
|
+
);
|
|
124
|
+
await removeForceDynamic(
|
|
125
|
+
path.resolve(builtPreviewAppPath, './src/app/preview/[...slug]/page.tsx'),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
await fs.promises.appendFile(
|
|
129
|
+
path.resolve(builtPreviewAppPath, './src/app/preview/[...slug]/page.tsx'),
|
|
130
|
+
`
|
|
131
|
+
|
|
132
|
+
export function generateStaticParams() {
|
|
133
|
+
return Promise.resolve(
|
|
134
|
+
${JSON.stringify(parameters)}
|
|
135
|
+
);
|
|
136
|
+
}`,
|
|
137
|
+
'utf8',
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const updatePackageJson = async (builtPreviewAppPath: string) => {
|
|
142
|
+
const packageJsonPath = path.resolve(builtPreviewAppPath, './package.json');
|
|
143
|
+
const packageJson = JSON.parse(
|
|
144
|
+
await fs.promises.readFile(packageJsonPath, 'utf8'),
|
|
145
|
+
) as {
|
|
146
|
+
name: string;
|
|
147
|
+
scripts: Record<string, string>;
|
|
148
|
+
dependencies: Record<string, string>;
|
|
149
|
+
devDependencies: Record<string, string>;
|
|
150
|
+
};
|
|
151
|
+
// Turbopack has some errors with the imports in @react-email/tailwind
|
|
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';
|
|
156
|
+
delete packageJson.scripts.postbuild;
|
|
157
|
+
|
|
158
|
+
packageJson.name = 'preview-server';
|
|
159
|
+
|
|
160
|
+
for (const [dependency, version] of Object.entries(
|
|
161
|
+
packageJson.devDependencies,
|
|
162
|
+
)) {
|
|
163
|
+
packageJson.devDependencies[dependency] = version.replace('workspace:', '');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
delete packageJson.devDependencies['@react-email/components'];
|
|
167
|
+
delete packageJson.scripts.prepare;
|
|
168
|
+
|
|
169
|
+
await fs.promises.writeFile(
|
|
170
|
+
packageJsonPath,
|
|
171
|
+
JSON.stringify(packageJson),
|
|
172
|
+
'utf8',
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const build = async ({
|
|
177
|
+
dir: emailsDirRelativePath,
|
|
178
|
+
packageManager,
|
|
179
|
+
}: Args) => {
|
|
180
|
+
try {
|
|
181
|
+
const usersProjectLocation = process.cwd();
|
|
182
|
+
const previewServerLocation = await getPreviewServerLocation();
|
|
183
|
+
|
|
184
|
+
const spinner = ora({
|
|
185
|
+
text: 'Starting build process...',
|
|
186
|
+
prefixText: ' ',
|
|
187
|
+
}).start();
|
|
188
|
+
registerSpinnerAutostopping(spinner);
|
|
189
|
+
|
|
190
|
+
spinner.text = `Checking if ${emailsDirRelativePath} folder exists`;
|
|
191
|
+
if (!fs.existsSync(emailsDirRelativePath)) {
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const emailsDirPath = path.join(
|
|
196
|
+
usersProjectLocation,
|
|
197
|
+
emailsDirRelativePath,
|
|
198
|
+
);
|
|
199
|
+
const staticPath = path.join(emailsDirPath, 'static');
|
|
200
|
+
|
|
201
|
+
const builtPreviewAppPath = path.join(usersProjectLocation, '.react-email');
|
|
202
|
+
|
|
203
|
+
if (fs.existsSync(builtPreviewAppPath)) {
|
|
204
|
+
spinner.text = 'Deleting pre-existing `.react-email` folder';
|
|
205
|
+
await fs.promises.rm(builtPreviewAppPath, { recursive: true });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
spinner.text = 'Copying preview app from CLI to `.react-email`';
|
|
209
|
+
await fs.promises.cp(previewServerLocation, builtPreviewAppPath, {
|
|
210
|
+
recursive: true,
|
|
211
|
+
filter: (source: string) => {
|
|
212
|
+
const relativeSource = path.relative(previewServerLocation, source);
|
|
213
|
+
return (
|
|
214
|
+
!/\.next/.test(relativeSource) &&
|
|
215
|
+
!/\.turbo/.test(relativeSource) &&
|
|
216
|
+
(isInReactEmailMonorepo || !/node_modules/.test(relativeSource))
|
|
217
|
+
);
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (fs.existsSync(staticPath)) {
|
|
222
|
+
spinner.text =
|
|
223
|
+
'Copying `static` folder into `.react-email/public/static`';
|
|
224
|
+
const builtStaticDirectory = path.resolve(
|
|
225
|
+
builtPreviewAppPath,
|
|
226
|
+
'./public/static',
|
|
227
|
+
);
|
|
228
|
+
await fs.promises.cp(staticPath, builtStaticDirectory, {
|
|
229
|
+
recursive: true,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
spinner.text =
|
|
234
|
+
'Setting Next environment variables for preview app to work properly';
|
|
235
|
+
await setNextEnvironmentVariablesForBuild(
|
|
236
|
+
emailsDirRelativePath,
|
|
237
|
+
builtPreviewAppPath,
|
|
238
|
+
usersProjectLocation,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
spinner.text = 'Setting server side generation for the email preview pages';
|
|
242
|
+
await forceSSGForEmailPreviews(emailsDirPath, builtPreviewAppPath);
|
|
243
|
+
|
|
244
|
+
spinner.text = "Updating package.json's build and start scripts";
|
|
245
|
+
await updatePackageJson(builtPreviewAppPath);
|
|
246
|
+
|
|
247
|
+
if (!isInReactEmailMonorepo) {
|
|
248
|
+
spinner.text = 'Installing dependencies on `.react-email`';
|
|
249
|
+
await installDependencies({
|
|
250
|
+
cwd: builtPreviewAppPath,
|
|
251
|
+
silent: true,
|
|
252
|
+
packageManager,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
spinner.stopAndPersist({
|
|
257
|
+
text: 'Successfully prepared `.react-email` for `next build`',
|
|
258
|
+
symbol: logSymbols.success,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
await runScript('build', {
|
|
262
|
+
packageManager,
|
|
263
|
+
cwd: builtPreviewAppPath,
|
|
264
|
+
});
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.log(error);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { setupHotreloading, startDevServer } from '../utils/index.js';
|
|
3
|
+
|
|
4
|
+
interface Args {
|
|
5
|
+
dir: string;
|
|
6
|
+
port: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const dev = async ({ dir: emailsDirRelativePath, port }: Args) => {
|
|
10
|
+
try {
|
|
11
|
+
if (!fs.existsSync(emailsDirRelativePath)) {
|
|
12
|
+
console.error(`Missing ${emailsDirRelativePath} folder`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const devServer = await startDevServer(
|
|
17
|
+
emailsDirRelativePath,
|
|
18
|
+
emailsDirRelativePath, // defaults to ./emails/static for the static files that are served to the preview
|
|
19
|
+
Number.parseInt(port, 10),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
await setupHotreloading(devServer, emailsDirRelativePath);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.log(error);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import fs, { unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import url from 'node:url';
|
|
5
|
+
import type { Options } from '@react-email/components';
|
|
6
|
+
import { type BuildFailure, build } from 'esbuild';
|
|
7
|
+
import { glob } from 'glob';
|
|
8
|
+
import logSymbols from 'log-symbols';
|
|
9
|
+
import normalize from 'normalize-path';
|
|
10
|
+
import ora, { type Ora } from 'ora';
|
|
11
|
+
import type React from 'react';
|
|
12
|
+
import { renderingUtilitiesExporter } from '../utils/esbuild/renderring-utilities-exporter.js';
|
|
13
|
+
import {
|
|
14
|
+
type EmailsDirectory,
|
|
15
|
+
getEmailsDirectoryMetadata,
|
|
16
|
+
} from '../utils/get-emails-directory-metadata.js';
|
|
17
|
+
import { tree } from '../utils/index.js';
|
|
18
|
+
import { registerSpinnerAutostopping } from '../utils/register-spinner-autostopping.js';
|
|
19
|
+
|
|
20
|
+
const getEmailTemplatesFromDirectory = (emailDirectory: EmailsDirectory) => {
|
|
21
|
+
const templatePaths = [] as string[];
|
|
22
|
+
for (const filename of emailDirectory.emailFilenames) {
|
|
23
|
+
templatePaths.push(path.join(emailDirectory.absolutePath, filename));
|
|
24
|
+
}
|
|
25
|
+
for (const directory of emailDirectory.subDirectories) {
|
|
26
|
+
templatePaths.push(...getEmailTemplatesFromDirectory(directory));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return templatePaths;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type ExportTemplatesOptions = Options & {
|
|
33
|
+
silent?: boolean;
|
|
34
|
+
pretty?: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const filename = url.fileURLToPath(import.meta.url);
|
|
38
|
+
|
|
39
|
+
const require = createRequire(filename);
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
This first builds all the templates using esbuild and then puts the output in the `.js`
|
|
43
|
+
files. Then these `.js` files are imported dynamically and rendered to `.html` files
|
|
44
|
+
using the `render` function.
|
|
45
|
+
*/
|
|
46
|
+
export const exportTemplates = async (
|
|
47
|
+
pathToWhereEmailMarkupShouldBeDumped: string,
|
|
48
|
+
emailsDirectoryPath: string,
|
|
49
|
+
options: ExportTemplatesOptions,
|
|
50
|
+
) => {
|
|
51
|
+
/* Delete the out directory if it already exists */
|
|
52
|
+
if (fs.existsSync(pathToWhereEmailMarkupShouldBeDumped)) {
|
|
53
|
+
fs.rmSync(pathToWhereEmailMarkupShouldBeDumped, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let spinner: Ora | undefined;
|
|
57
|
+
if (!options.silent) {
|
|
58
|
+
spinner = ora('Preparing files...\n').start();
|
|
59
|
+
registerSpinnerAutostopping(spinner);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const emailsDirectoryMetadata = await getEmailsDirectoryMetadata(
|
|
63
|
+
path.resolve(process.cwd(), emailsDirectoryPath),
|
|
64
|
+
true,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (typeof emailsDirectoryMetadata === 'undefined') {
|
|
68
|
+
if (spinner) {
|
|
69
|
+
spinner.stopAndPersist({
|
|
70
|
+
symbol: logSymbols.error,
|
|
71
|
+
text: `Could not find the directory at ${emailsDirectoryPath}`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const allTemplates = getEmailTemplatesFromDirectory(emailsDirectoryMetadata);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await build({
|
|
81
|
+
bundle: true,
|
|
82
|
+
entryPoints: allTemplates,
|
|
83
|
+
format: 'cjs',
|
|
84
|
+
jsx: 'automatic',
|
|
85
|
+
loader: { '.js': 'jsx' },
|
|
86
|
+
logLevel: 'silent',
|
|
87
|
+
outExtension: { '.js': '.cjs' },
|
|
88
|
+
outdir: pathToWhereEmailMarkupShouldBeDumped,
|
|
89
|
+
platform: 'node',
|
|
90
|
+
plugins: [renderingUtilitiesExporter(allTemplates)],
|
|
91
|
+
write: true,
|
|
92
|
+
});
|
|
93
|
+
} catch (exception) {
|
|
94
|
+
if (spinner) {
|
|
95
|
+
spinner.stopAndPersist({
|
|
96
|
+
symbol: logSymbols.error,
|
|
97
|
+
text: 'Failed to build emails',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const buildFailure = exception as BuildFailure;
|
|
102
|
+
console.error(`\n${buildFailure.message}`);
|
|
103
|
+
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (spinner) {
|
|
108
|
+
spinner.succeed();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const allBuiltTemplates = glob.sync(
|
|
112
|
+
normalize(`${pathToWhereEmailMarkupShouldBeDumped}/**/*.cjs`),
|
|
113
|
+
{
|
|
114
|
+
absolute: true,
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
for await (const template of allBuiltTemplates) {
|
|
119
|
+
try {
|
|
120
|
+
if (spinner) {
|
|
121
|
+
spinner.text = `rendering ${template.split('/').pop()}`;
|
|
122
|
+
spinner.render();
|
|
123
|
+
}
|
|
124
|
+
delete require.cache[template];
|
|
125
|
+
const emailModule = require(template) as {
|
|
126
|
+
default: React.FC;
|
|
127
|
+
render: (
|
|
128
|
+
element: React.ReactElement,
|
|
129
|
+
options: Record<string, unknown>,
|
|
130
|
+
) => Promise<string>;
|
|
131
|
+
reactEmailCreateReactElement: typeof React.createElement;
|
|
132
|
+
};
|
|
133
|
+
const rendered = await emailModule.render(
|
|
134
|
+
emailModule.reactEmailCreateReactElement(emailModule.default, {}),
|
|
135
|
+
options,
|
|
136
|
+
);
|
|
137
|
+
const htmlPath = template.replace(
|
|
138
|
+
'.cjs',
|
|
139
|
+
options.plainText ? '.txt' : '.html',
|
|
140
|
+
);
|
|
141
|
+
writeFileSync(htmlPath, rendered);
|
|
142
|
+
unlinkSync(template);
|
|
143
|
+
} catch (exception) {
|
|
144
|
+
if (spinner) {
|
|
145
|
+
spinner.stopAndPersist({
|
|
146
|
+
symbol: logSymbols.error,
|
|
147
|
+
text: `failed when rendering ${template.split('/').pop()}`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
console.error(exception);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (spinner) {
|
|
155
|
+
spinner.succeed('Rendered all files');
|
|
156
|
+
spinner.text = 'Copying static files';
|
|
157
|
+
spinner.render();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ex: emails/static
|
|
161
|
+
const staticDirectoryPath = path.join(emailsDirectoryPath, 'static');
|
|
162
|
+
|
|
163
|
+
if (fs.existsSync(staticDirectoryPath)) {
|
|
164
|
+
const pathToDumpStaticFilesInto = path.join(
|
|
165
|
+
pathToWhereEmailMarkupShouldBeDumped,
|
|
166
|
+
'static',
|
|
167
|
+
);
|
|
168
|
+
// cp('-r', ...) will copy *inside* of the static directory if it exists
|
|
169
|
+
// causing a duplication of static files, so we need to delete ir first
|
|
170
|
+
if (fs.existsSync(pathToDumpStaticFilesInto))
|
|
171
|
+
await fs.promises.rm(pathToDumpStaticFilesInto, { recursive: true });
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
await fs.promises.cp(staticDirectoryPath, pathToDumpStaticFilesInto, {
|
|
175
|
+
recursive: true,
|
|
176
|
+
});
|
|
177
|
+
} catch (exception) {
|
|
178
|
+
console.error(exception);
|
|
179
|
+
if (spinner) {
|
|
180
|
+
spinner.stopAndPersist({
|
|
181
|
+
symbol: logSymbols.error,
|
|
182
|
+
text: 'Failed to copy static files',
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
console.error(
|
|
186
|
+
`Something went wrong while copying the file to ${pathToWhereEmailMarkupShouldBeDumped}/static, ${exception}`,
|
|
187
|
+
);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (spinner && !options.silent) {
|
|
193
|
+
spinner.succeed();
|
|
194
|
+
|
|
195
|
+
const fileTree = await tree(pathToWhereEmailMarkupShouldBeDumped, 4);
|
|
196
|
+
|
|
197
|
+
console.log(fileTree);
|
|
198
|
+
|
|
199
|
+
spinner.stopAndPersist({
|
|
200
|
+
symbol: logSymbols.success,
|
|
201
|
+
text: 'Successfully exported emails',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
};
|