react-email 5.0.0-canary.1 → 5.0.0-canary.10
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/dist/index.js +59 -35
- package/package.json +5 -4
- package/src/commands/build.ts +10 -27
- package/src/commands/dev.ts +1 -1
- package/src/commands/export.ts +5 -5
- package/src/commands/resend/reset.ts +8 -0
- package/src/commands/resend/setup.ts +29 -0
- package/src/index.ts +16 -0
- package/src/utils/__snapshots__/tree.spec.ts.snap +2 -0
- package/src/utils/conf.ts +9 -0
- package/src/utils/preview/get-env-variables-for-preview-app.ts +2 -0
- package/src/utils/preview/start-dev-server.ts +5 -3
- package/src/utils/style-text.ts +11 -0
- package/tsconfig.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# react-email
|
|
2
2
|
|
|
3
|
+
## 5.0.0-canary.10
|
|
4
|
+
|
|
5
|
+
## 5.0.0-canary.9
|
|
6
|
+
|
|
7
|
+
## 5.0.0-canary.8
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- b6b027c: improved integration setup flow
|
|
12
|
+
|
|
13
|
+
## 5.0.0-canary.7
|
|
14
|
+
|
|
15
|
+
## 5.0.0-canary.6
|
|
16
|
+
|
|
17
|
+
## 5.0.0-canary.5
|
|
18
|
+
|
|
19
|
+
## 5.0.0-canary.4
|
|
20
|
+
|
|
21
|
+
## 5.0.0-canary.3
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- 95c7417: Dark mode switcher emulating email client color inversion
|
|
26
|
+
|
|
27
|
+
## 5.0.0-canary.2
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- 1b3176e: fallback to not text coloring for Node.js < 20
|
|
32
|
+
|
|
3
33
|
## 5.0.0-canary.1
|
|
4
34
|
|
|
5
35
|
## 5.0.0-canary.0
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,8 @@ import { parse } from "@babel/parser";
|
|
|
17
17
|
import traverseModule from "@babel/traverse";
|
|
18
18
|
import { createMatchPath, loadConfig } from "tsconfig-paths";
|
|
19
19
|
import http from "node:http";
|
|
20
|
-
import
|
|
20
|
+
import Conf from "conf";
|
|
21
|
+
import * as nodeUtil from "node:util";
|
|
21
22
|
import { lookup } from "mime-types";
|
|
22
23
|
import os from "node:os";
|
|
23
24
|
import { build } from "esbuild";
|
|
@@ -86,7 +87,7 @@ const getEmailsDirectoryMetadata = async (absolutePathToEmailsDirectory, keepFil
|
|
|
86
87
|
//#region package.json
|
|
87
88
|
var package_default = {
|
|
88
89
|
name: "react-email",
|
|
89
|
-
version: "5.0.0-canary.
|
|
90
|
+
version: "5.0.0-canary.10",
|
|
90
91
|
description: "A live preview of your emails right in your browser.",
|
|
91
92
|
bin: { "email": "./dist/index.js" },
|
|
92
93
|
type: "module",
|
|
@@ -104,12 +105,13 @@ var package_default = {
|
|
|
104
105
|
"directory": "packages/react-email"
|
|
105
106
|
},
|
|
106
107
|
keywords: ["react", "email"],
|
|
107
|
-
engines: { "node": ">=
|
|
108
|
+
engines: { "node": ">=22.0.0" },
|
|
108
109
|
dependencies: {
|
|
109
110
|
"@babel/parser": "^7.27.0",
|
|
110
111
|
"@babel/traverse": "^7.27.0",
|
|
111
112
|
"chokidar": "^4.0.3",
|
|
112
113
|
"commander": "^13.0.0",
|
|
114
|
+
"conf": "^15.0.2",
|
|
113
115
|
"debounce": "^2.0.0",
|
|
114
116
|
"esbuild": "^0.25.0",
|
|
115
117
|
"glob": "^11.0.0",
|
|
@@ -129,7 +131,7 @@ var package_default = {
|
|
|
129
131
|
"@types/babel__traverse": "7.20.7",
|
|
130
132
|
"@types/mime-types": "2.1.4",
|
|
131
133
|
"@types/prompts": "2.4.9",
|
|
132
|
-
"next": "
|
|
134
|
+
"next": "16.0.1",
|
|
133
135
|
"react": "19.0.0",
|
|
134
136
|
"react-dom": "19.0.0",
|
|
135
137
|
"typescript": "5.8.3"
|
|
@@ -228,37 +230,22 @@ module.exports = {
|
|
|
228
230
|
PREVIEW_SERVER_LOCATION: '${builtPreviewAppPath.replace(/\\/g, "/")}',
|
|
229
231
|
USER_PROJECT_LOCATION: userProjectLocation
|
|
230
232
|
},
|
|
231
|
-
|
|
232
|
-
webpack: (
|
|
233
|
-
/** @type {import('webpack').Configuration & { externals: string[] }} */
|
|
234
|
-
config,
|
|
235
|
-
{ isServer }
|
|
236
|
-
) => {
|
|
237
|
-
if (isServer) {
|
|
238
|
-
config.externals.push('esbuild');
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return config;
|
|
242
|
-
},
|
|
233
|
+
serverExternalPackages: ['esbuild'],
|
|
243
234
|
typescript: {
|
|
244
235
|
ignoreBuildErrors: true
|
|
245
236
|
},
|
|
246
|
-
eslint: {
|
|
247
|
-
ignoreDuringBuilds: true
|
|
248
|
-
},
|
|
249
237
|
experimental: {
|
|
250
238
|
webpackBuildWorker: true
|
|
251
239
|
},
|
|
252
240
|
}`;
|
|
241
|
+
await fs.promises.rm(path.resolve(builtPreviewAppPath, "./next.config.ts"));
|
|
253
242
|
await fs.promises.writeFile(path.resolve(builtPreviewAppPath, "./next.config.js"), nextConfigContents, "utf8");
|
|
254
243
|
};
|
|
255
244
|
const getEmailSlugsFromEmailDirectory = (emailDirectory, emailsDirectoryAbsolutePath) => {
|
|
256
245
|
const directoryPathRelativeToEmailsDirectory = emailDirectory.absolutePath.replace(emailsDirectoryAbsolutePath, "").trim();
|
|
257
246
|
const slugs = [];
|
|
258
|
-
emailDirectory.emailFilenames
|
|
259
|
-
emailDirectory.subDirectories.
|
|
260
|
-
slugs.push(...getEmailSlugsFromEmailDirectory(directory, emailsDirectoryAbsolutePath));
|
|
261
|
-
});
|
|
247
|
+
for (const filename of emailDirectory.emailFilenames) slugs.push(path.join(directoryPathRelativeToEmailsDirectory, filename).split(path.sep).filter((segment) => segment.length > 0));
|
|
248
|
+
for (const directory of emailDirectory.subDirectories) slugs.push(...getEmailSlugsFromEmailDirectory(directory, emailsDirectoryAbsolutePath));
|
|
262
249
|
return slugs;
|
|
263
250
|
};
|
|
264
251
|
const forceSSGForEmailPreviews = async (emailsDirPath, builtPreviewAppPath) => {
|
|
@@ -280,12 +267,11 @@ export function generateStaticParams() {
|
|
|
280
267
|
const updatePackageJson = async (builtPreviewAppPath) => {
|
|
281
268
|
const packageJsonPath = path.resolve(builtPreviewAppPath, "./package.json");
|
|
282
269
|
const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf8"));
|
|
283
|
-
packageJson.scripts.build = "next build";
|
|
270
|
+
packageJson.scripts.build = "next build --webpack";
|
|
284
271
|
packageJson.scripts.start = "next start";
|
|
285
272
|
delete packageJson.scripts.postbuild;
|
|
286
273
|
packageJson.name = "preview-server";
|
|
287
274
|
for (const [dependency, version$1] of Object.entries(packageJson.dependencies)) packageJson.dependencies[dependency] = version$1.replace("workspace:", "");
|
|
288
|
-
delete packageJson.devDependencies["@react-email/render"];
|
|
289
275
|
delete packageJson.devDependencies["@react-email/components"];
|
|
290
276
|
delete packageJson.scripts.prepare;
|
|
291
277
|
await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageJson), "utf8");
|
|
@@ -601,14 +587,27 @@ const setupHotreloading = async (devServer$1, emailDirRelativePath) => {
|
|
|
601
587
|
return watcher;
|
|
602
588
|
};
|
|
603
589
|
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/utils/conf.ts
|
|
592
|
+
const encryptionKey = "h2#x658}1#qY(@!:7,BD1J)q12$[tM25";
|
|
593
|
+
const conf = new Conf({
|
|
594
|
+
projectName: "react-email",
|
|
595
|
+
encryptionKey
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
//#endregion
|
|
599
|
+
//#region src/utils/style-text.ts
|
|
600
|
+
const styleText = nodeUtil.styleText ? nodeUtil.styleText : (_, text) => text;
|
|
601
|
+
|
|
604
602
|
//#endregion
|
|
605
603
|
//#region src/utils/preview/get-env-variables-for-preview-app.ts
|
|
606
|
-
const getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, previewServerLocation, cwd) => {
|
|
604
|
+
const getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, previewServerLocation, cwd, resendApiKey) => {
|
|
607
605
|
return {
|
|
608
606
|
EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
|
|
609
607
|
EMAILS_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEmailsDirectory),
|
|
610
608
|
PREVIEW_SERVER_LOCATION: previewServerLocation,
|
|
611
|
-
USER_PROJECT_LOCATION: cwd
|
|
609
|
+
USER_PROJECT_LOCATION: cwd,
|
|
610
|
+
RESEND_API_KEY: resendApiKey
|
|
612
611
|
};
|
|
613
612
|
};
|
|
614
613
|
|
|
@@ -658,8 +657,8 @@ const safeAsyncServerListen = (server, port) => {
|
|
|
658
657
|
};
|
|
659
658
|
const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port) => {
|
|
660
659
|
const [majorNodeVersion] = process.versions.node.split(".");
|
|
661
|
-
if (majorNodeVersion && Number.parseInt(majorNodeVersion) <
|
|
662
|
-
console.error(` ${logSymbols.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node
|
|
660
|
+
if (majorNodeVersion && Number.parseInt(majorNodeVersion, 10) < 20) {
|
|
661
|
+
console.error(` ${logSymbols.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 20 or higher.`);
|
|
663
662
|
process.exit(1);
|
|
664
663
|
}
|
|
665
664
|
const previewServerLocation = await getPreviewServerLocation();
|
|
@@ -711,7 +710,7 @@ const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath,
|
|
|
711
710
|
process.env = {
|
|
712
711
|
NODE_ENV: "development",
|
|
713
712
|
...process.env,
|
|
714
|
-
...getEnvVariablesForPreviewApp(path.normalize(emailsDirRelativePath), previewServerLocation, process.cwd())
|
|
713
|
+
...getEnvVariablesForPreviewApp(path.normalize(emailsDirRelativePath), previewServerLocation, process.cwd(), conf.get("resendApiKey"))
|
|
715
714
|
};
|
|
716
715
|
const app = (await previewServer.import("next", { default: true }))({
|
|
717
716
|
dev: false,
|
|
@@ -813,7 +812,7 @@ const dev = async ({ dir: emailsDirRelativePath, port }) => {
|
|
|
813
812
|
console.error(`Missing ${emailsDirRelativePath} folder`);
|
|
814
813
|
process.exit(1);
|
|
815
814
|
}
|
|
816
|
-
await setupHotreloading(await startDevServer(emailsDirRelativePath, emailsDirRelativePath, Number.parseInt(port)), emailsDirRelativePath);
|
|
815
|
+
await setupHotreloading(await startDevServer(emailsDirRelativePath, emailsDirRelativePath, Number.parseInt(port, 10)), emailsDirRelativePath);
|
|
817
816
|
} catch (error) {
|
|
818
817
|
console.log(error);
|
|
819
818
|
process.exit(1);
|
|
@@ -871,10 +870,8 @@ const renderingUtilitiesExporter = (emailTemplates) => ({
|
|
|
871
870
|
//#region src/commands/export.ts
|
|
872
871
|
const getEmailTemplatesFromDirectory = (emailDirectory) => {
|
|
873
872
|
const templatePaths = [];
|
|
874
|
-
emailDirectory.emailFilenames
|
|
875
|
-
emailDirectory.subDirectories.
|
|
876
|
-
templatePaths.push(...getEmailTemplatesFromDirectory(directory));
|
|
877
|
-
});
|
|
873
|
+
for (const filename of emailDirectory.emailFilenames) templatePaths.push(path.join(emailDirectory.absolutePath, filename));
|
|
874
|
+
for (const directory of emailDirectory.subDirectories) templatePaths.push(...getEmailTemplatesFromDirectory(directory));
|
|
878
875
|
return templatePaths;
|
|
879
876
|
};
|
|
880
877
|
const require = createRequire(url.fileURLToPath(import.meta.url));
|
|
@@ -969,6 +966,30 @@ const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirec
|
|
|
969
966
|
}
|
|
970
967
|
};
|
|
971
968
|
|
|
969
|
+
//#endregion
|
|
970
|
+
//#region src/commands/resend/reset.ts
|
|
971
|
+
async function resendReset() {
|
|
972
|
+
conf.delete("resendApiKey");
|
|
973
|
+
console.info(`${logSymbols.success} Resend API Key successfully deleted`);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
//#endregion
|
|
977
|
+
//#region src/commands/resend/setup.ts
|
|
978
|
+
async function resendSetup() {
|
|
979
|
+
const previousValue = conf.get("resendApiKey");
|
|
980
|
+
if (typeof previousValue === "string" && previousValue.length > 0) console.info(`You already have a Resend API Key configured (${styleText("grey", previousValue.slice(0, 11))}...), continuing will replace it.`);
|
|
981
|
+
const { apiKey } = await prompts({
|
|
982
|
+
type: "password",
|
|
983
|
+
name: "apiKey",
|
|
984
|
+
message: "Enter your API Key (make sure it has \"Full Access\")"
|
|
985
|
+
});
|
|
986
|
+
if (apiKey?.trim().length > 0) {
|
|
987
|
+
conf.set("resendApiKey", apiKey);
|
|
988
|
+
console.info(`${logSymbols.success} Resend integration successfully set up`);
|
|
989
|
+
console.info(`You can always remove it with ${styleText("green", "npx react-email@latest resend reset")}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
972
993
|
//#endregion
|
|
973
994
|
//#region src/commands/start.ts
|
|
974
995
|
const start = async () => {
|
|
@@ -1011,6 +1032,9 @@ program.command("export").description("Build the templates to the `out` director
|
|
|
1011
1032
|
plainText,
|
|
1012
1033
|
pretty
|
|
1013
1034
|
}));
|
|
1035
|
+
const resend = program.command("resend");
|
|
1036
|
+
resend.command("setup").description("Sets up the integration between the React Email CLI, and your Resend account through an API Key").action(resendSetup);
|
|
1037
|
+
resend.command("reset").description("Deletes your API Key from the React Email configuration").action(resendReset);
|
|
1014
1038
|
program.parse();
|
|
1015
1039
|
|
|
1016
1040
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-email",
|
|
3
|
-
"version": "5.0.0-canary.
|
|
3
|
+
"version": "5.0.0-canary.10",
|
|
4
4
|
"description": "A live preview of your emails right in your browser.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"email": "./dist/index.js"
|
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
"email"
|
|
18
18
|
],
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": ">=
|
|
20
|
+
"node": ">=22.0.0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@babel/parser": "^7.27.0",
|
|
24
24
|
"@babel/traverse": "^7.27.0",
|
|
25
25
|
"chokidar": "^4.0.3",
|
|
26
26
|
"commander": "^13.0.0",
|
|
27
|
+
"conf": "^15.0.2",
|
|
27
28
|
"debounce": "^2.0.0",
|
|
28
29
|
"esbuild": "^0.25.0",
|
|
29
30
|
"glob": "^11.0.0",
|
|
@@ -42,11 +43,11 @@
|
|
|
42
43
|
"@types/babel__traverse": "7.20.7",
|
|
43
44
|
"@types/mime-types": "2.1.4",
|
|
44
45
|
"@types/prompts": "2.4.9",
|
|
45
|
-
"next": "
|
|
46
|
+
"next": "16.0.1",
|
|
46
47
|
"react": "19.0.0",
|
|
47
48
|
"react-dom": "19.0.0",
|
|
48
49
|
"typescript": "5.8.3",
|
|
49
|
-
"@react-email/components": "1.0.0-canary.
|
|
50
|
+
"@react-email/components": "1.0.0-canary.7"
|
|
50
51
|
},
|
|
51
52
|
"scripts": {
|
|
52
53
|
"build": "tsdown",
|
package/src/commands/build.ts
CHANGED
|
@@ -88,29 +88,17 @@ module.exports = {
|
|
|
88
88
|
PREVIEW_SERVER_LOCATION: '${builtPreviewAppPath.replace(/\\/g, '/')}',
|
|
89
89
|
USER_PROJECT_LOCATION: userProjectLocation
|
|
90
90
|
},
|
|
91
|
-
|
|
92
|
-
webpack: (
|
|
93
|
-
/** @type {import('webpack').Configuration & { externals: string[] }} */
|
|
94
|
-
config,
|
|
95
|
-
{ isServer }
|
|
96
|
-
) => {
|
|
97
|
-
if (isServer) {
|
|
98
|
-
config.externals.push('esbuild');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return config;
|
|
102
|
-
},
|
|
91
|
+
serverExternalPackages: ['esbuild'],
|
|
103
92
|
typescript: {
|
|
104
93
|
ignoreBuildErrors: true
|
|
105
94
|
},
|
|
106
|
-
eslint: {
|
|
107
|
-
ignoreDuringBuilds: true
|
|
108
|
-
},
|
|
109
95
|
experimental: {
|
|
110
96
|
webpackBuildWorker: true
|
|
111
97
|
},
|
|
112
98
|
}`;
|
|
113
99
|
|
|
100
|
+
await fs.promises.rm(path.resolve(builtPreviewAppPath, './next.config.ts'));
|
|
101
|
+
|
|
114
102
|
await fs.promises.writeFile(
|
|
115
103
|
path.resolve(builtPreviewAppPath, './next.config.js'),
|
|
116
104
|
nextConfigContents,
|
|
@@ -127,23 +115,23 @@ const getEmailSlugsFromEmailDirectory = (
|
|
|
127
115
|
.trim();
|
|
128
116
|
|
|
129
117
|
const slugs = [] as Array<string>[];
|
|
130
|
-
emailDirectory.emailFilenames
|
|
118
|
+
for (const filename of emailDirectory.emailFilenames) {
|
|
131
119
|
slugs.push(
|
|
132
120
|
path
|
|
133
121
|
.join(directoryPathRelativeToEmailsDirectory, filename)
|
|
134
122
|
.split(path.sep)
|
|
135
123
|
// sometimes it gets empty segments due to trailing slashes
|
|
136
124
|
.filter((segment) => segment.length > 0),
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
emailDirectory.subDirectories
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
for (const directory of emailDirectory.subDirectories) {
|
|
140
128
|
slugs.push(
|
|
141
129
|
...getEmailSlugsFromEmailDirectory(
|
|
142
130
|
directory,
|
|
143
131
|
emailsDirectoryAbsolutePath,
|
|
144
132
|
),
|
|
145
133
|
);
|
|
146
|
-
}
|
|
134
|
+
}
|
|
147
135
|
|
|
148
136
|
return slugs;
|
|
149
137
|
};
|
|
@@ -202,7 +190,8 @@ const updatePackageJson = async (builtPreviewAppPath: string) => {
|
|
|
202
190
|
dependencies: Record<string, string>;
|
|
203
191
|
devDependencies: Record<string, string>;
|
|
204
192
|
};
|
|
205
|
-
|
|
193
|
+
// Turbopack has some errors with the imports in @react-email/tailwind
|
|
194
|
+
packageJson.scripts.build = 'next build --webpack';
|
|
206
195
|
packageJson.scripts.start = 'next start';
|
|
207
196
|
delete packageJson.scripts.postbuild;
|
|
208
197
|
|
|
@@ -214,12 +203,6 @@ const updatePackageJson = async (builtPreviewAppPath: string) => {
|
|
|
214
203
|
packageJson.dependencies[dependency] = version.replace('workspace:', '');
|
|
215
204
|
}
|
|
216
205
|
|
|
217
|
-
// We remove this one to avoid having resolve issues on our demo build process.
|
|
218
|
-
// This is only used in the `export` command so it's irrelevant to have it here.
|
|
219
|
-
//
|
|
220
|
-
// See `src/actions/render-email-by-path` for more info on how we render the
|
|
221
|
-
// email templates without `@react-email/render` being installed.
|
|
222
|
-
delete packageJson.devDependencies['@react-email/render'];
|
|
223
206
|
delete packageJson.devDependencies['@react-email/components'];
|
|
224
207
|
delete packageJson.scripts.prepare;
|
|
225
208
|
|
package/src/commands/dev.ts
CHANGED
|
@@ -16,7 +16,7 @@ export const dev = async ({ dir: emailsDirRelativePath, port }: Args) => {
|
|
|
16
16
|
const devServer = await startDevServer(
|
|
17
17
|
emailsDirRelativePath,
|
|
18
18
|
emailsDirRelativePath, // defaults to ./emails/static for the static files that are served to the preview
|
|
19
|
-
Number.parseInt(port),
|
|
19
|
+
Number.parseInt(port, 10),
|
|
20
20
|
);
|
|
21
21
|
|
|
22
22
|
await setupHotreloading(devServer, emailsDirRelativePath);
|
package/src/commands/export.ts
CHANGED
|
@@ -19,12 +19,12 @@ import { registerSpinnerAutostopping } from '../utils/register-spinner-autostopp
|
|
|
19
19
|
|
|
20
20
|
const getEmailTemplatesFromDirectory = (emailDirectory: EmailsDirectory) => {
|
|
21
21
|
const templatePaths = [] as string[];
|
|
22
|
-
emailDirectory.emailFilenames
|
|
23
|
-
templatePaths.push(path.join(emailDirectory.absolutePath, filename))
|
|
24
|
-
|
|
25
|
-
emailDirectory.subDirectories
|
|
22
|
+
for (const filename of emailDirectory.emailFilenames) {
|
|
23
|
+
templatePaths.push(path.join(emailDirectory.absolutePath, filename));
|
|
24
|
+
}
|
|
25
|
+
for (const directory of emailDirectory.subDirectories) {
|
|
26
26
|
templatePaths.push(...getEmailTemplatesFromDirectory(directory));
|
|
27
|
-
}
|
|
27
|
+
}
|
|
28
28
|
|
|
29
29
|
return templatePaths;
|
|
30
30
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import logSymbols from 'log-symbols';
|
|
2
|
+
import prompts from 'prompts';
|
|
3
|
+
import { conf } from '../../utils/conf.js';
|
|
4
|
+
import { styleText } from '../../utils/style-text.js';
|
|
5
|
+
|
|
6
|
+
export async function resendSetup() {
|
|
7
|
+
const previousValue = conf.get('resendApiKey');
|
|
8
|
+
if (typeof previousValue === 'string' && previousValue.length > 0) {
|
|
9
|
+
console.info(
|
|
10
|
+
`You already have a Resend API Key configured (${styleText('grey', previousValue.slice(0, 11))}...), continuing will replace it.`,
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { apiKey } = await prompts({
|
|
15
|
+
type: 'password',
|
|
16
|
+
name: 'apiKey',
|
|
17
|
+
message: 'Enter your API Key (make sure it has "Full Access")',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (apiKey?.trim().length > 0) {
|
|
21
|
+
conf.set('resendApiKey', apiKey);
|
|
22
|
+
console.info(
|
|
23
|
+
`${logSymbols.success} Resend integration successfully set up`,
|
|
24
|
+
);
|
|
25
|
+
console.info(
|
|
26
|
+
`You can always remove it with ${styleText('green', 'npx react-email@latest resend reset')}`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { program } from 'commander';
|
|
|
3
3
|
import { build } from './commands/build.js';
|
|
4
4
|
import { dev } from './commands/dev.js';
|
|
5
5
|
import { exportTemplates } from './commands/export.js';
|
|
6
|
+
import { resendReset } from './commands/resend/reset.js';
|
|
7
|
+
import { resendSetup } from './commands/resend/setup.js';
|
|
6
8
|
import { start } from './commands/start.js';
|
|
7
9
|
import { packageJson } from './utils/packageJson.js';
|
|
8
10
|
|
|
@@ -52,4 +54,18 @@ program
|
|
|
52
54
|
exportTemplates(outDir, srcDir, { silent, plainText, pretty }),
|
|
53
55
|
);
|
|
54
56
|
|
|
57
|
+
const resend = program.command('resend');
|
|
58
|
+
|
|
59
|
+
resend
|
|
60
|
+
.command('setup')
|
|
61
|
+
.description(
|
|
62
|
+
'Sets up the integration between the React Email CLI, and your Resend account through an API Key',
|
|
63
|
+
)
|
|
64
|
+
.action(resendSetup);
|
|
65
|
+
|
|
66
|
+
resend
|
|
67
|
+
.command('reset')
|
|
68
|
+
.description('Deletes your API Key from the React Email configuration')
|
|
69
|
+
.action(resendReset);
|
|
70
|
+
|
|
55
71
|
program.parse();
|
|
@@ -16,12 +16,14 @@ exports[`tree(__dirname, 2) 1`] = `
|
|
|
16
16
|
├── types
|
|
17
17
|
│ ├── hot-reload-change.ts
|
|
18
18
|
│ └── hot-reload-event.ts
|
|
19
|
+
├── conf.ts
|
|
19
20
|
├── get-emails-directory-metadata.spec.ts
|
|
20
21
|
├── get-emails-directory-metadata.ts
|
|
21
22
|
├── get-preview-server-location.ts
|
|
22
23
|
├── index.ts
|
|
23
24
|
├── packageJson.ts
|
|
24
25
|
├── register-spinner-autostopping.ts
|
|
26
|
+
├── style-text.ts
|
|
25
27
|
├── tree.spec.ts
|
|
26
28
|
└── tree.ts"
|
|
27
29
|
`;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
// Just simple encryption. This isn't completely safe
|
|
4
|
+
// because anyone can find this key here
|
|
5
|
+
const encryptionKey = 'h2#x658}1#qY(@!:7,BD1J)q12$[tM25';
|
|
6
|
+
|
|
7
|
+
export const conf = new Conf<{
|
|
8
|
+
resendApiKey?: string;
|
|
9
|
+
}>({ projectName: 'react-email', encryptionKey });
|
|
@@ -4,11 +4,13 @@ export const getEnvVariablesForPreviewApp = (
|
|
|
4
4
|
relativePathToEmailsDirectory: string,
|
|
5
5
|
previewServerLocation: string,
|
|
6
6
|
cwd: string,
|
|
7
|
+
resendApiKey?: string,
|
|
7
8
|
) => {
|
|
8
9
|
return {
|
|
9
10
|
EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
|
|
10
11
|
EMAILS_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEmailsDirectory),
|
|
11
12
|
PREVIEW_SERVER_LOCATION: previewServerLocation,
|
|
12
13
|
USER_PROJECT_LOCATION: cwd,
|
|
14
|
+
RESEND_API_KEY: resendApiKey,
|
|
13
15
|
} as const;
|
|
14
16
|
};
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import url from 'node:url';
|
|
4
|
-
import { styleText } from 'node:util';
|
|
5
4
|
import { createJiti } from 'jiti';
|
|
6
5
|
import logSymbols from 'log-symbols';
|
|
7
6
|
import ora from 'ora';
|
|
8
7
|
import { registerSpinnerAutostopping } from '../../utils/register-spinner-autostopping.js';
|
|
8
|
+
import { conf } from '../conf.js';
|
|
9
9
|
import { getPreviewServerLocation } from '../get-preview-server-location.js';
|
|
10
10
|
import { packageJson } from '../packageJson.js';
|
|
11
|
+
import { styleText } from '../style-text.js';
|
|
11
12
|
import { getEnvVariablesForPreviewApp } from './get-env-variables-for-preview-app.js';
|
|
12
13
|
import { serveStaticFile } from './serve-static-file.js';
|
|
13
14
|
|
|
@@ -33,9 +34,9 @@ export const startDevServer = async (
|
|
|
33
34
|
port: number,
|
|
34
35
|
): Promise<http.Server> => {
|
|
35
36
|
const [majorNodeVersion] = process.versions.node.split('.');
|
|
36
|
-
if (majorNodeVersion && Number.parseInt(majorNodeVersion) <
|
|
37
|
+
if (majorNodeVersion && Number.parseInt(majorNodeVersion, 10) < 20) {
|
|
37
38
|
console.error(
|
|
38
|
-
` ${logSymbols.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node
|
|
39
|
+
` ${logSymbols.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 20 or higher.`,
|
|
39
40
|
);
|
|
40
41
|
process.exit(1);
|
|
41
42
|
}
|
|
@@ -131,6 +132,7 @@ export const startDevServer = async (
|
|
|
131
132
|
path.normalize(emailsDirRelativePath),
|
|
132
133
|
previewServerLocation,
|
|
133
134
|
process.cwd(),
|
|
135
|
+
conf.get('resendApiKey'),
|
|
134
136
|
),
|
|
135
137
|
};
|
|
136
138
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized fallback for Node versions (<20.12.0) without util.styleText.
|
|
3
|
+
* Returns the original text when styleText is unavailable.
|
|
4
|
+
*/
|
|
5
|
+
import * as nodeUtil from 'node:util';
|
|
6
|
+
|
|
7
|
+
type StyleTextFunction = typeof nodeUtil.styleText;
|
|
8
|
+
|
|
9
|
+
export const styleText: StyleTextFunction = (nodeUtil as any).styleText
|
|
10
|
+
? (nodeUtil as any).styleText
|
|
11
|
+
: (_: string, text: string) => text;
|
package/tsconfig.json
CHANGED