react-email 2.1.1 → 2.1.2
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/cli/index.js +3 -3
- package/cli/index.mjs +3 -3
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +2363 -0
- package/dist/cli/index.mjs +1087 -0
- package/package.json +3 -3
|
@@ -0,0 +1,1087 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined")
|
|
6
|
+
return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// src/cli/index.ts
|
|
11
|
+
import { program } from "commander";
|
|
12
|
+
|
|
13
|
+
// package.json
|
|
14
|
+
var package_default = {
|
|
15
|
+
name: "react-email",
|
|
16
|
+
version: "2.1.2-canary.0",
|
|
17
|
+
description: "A live preview of your emails right in your browser.",
|
|
18
|
+
bin: {
|
|
19
|
+
email: "./dist/cli/index.js"
|
|
20
|
+
},
|
|
21
|
+
scripts: {
|
|
22
|
+
build: "tsup-node && node build-preview-server.mjs",
|
|
23
|
+
dev: "tsup-node --watch",
|
|
24
|
+
test: "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
clean: "rm -rf dist",
|
|
27
|
+
lint: "eslint . && tsc"
|
|
28
|
+
},
|
|
29
|
+
license: "MIT",
|
|
30
|
+
repository: {
|
|
31
|
+
type: "git",
|
|
32
|
+
url: "https://github.com/resend/react-email.git",
|
|
33
|
+
directory: "packages/react-email"
|
|
34
|
+
},
|
|
35
|
+
keywords: [
|
|
36
|
+
"react",
|
|
37
|
+
"email"
|
|
38
|
+
],
|
|
39
|
+
engines: {
|
|
40
|
+
node: ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
dependencies: {
|
|
43
|
+
"@babel/core": "7.23.9",
|
|
44
|
+
"@babel/parser": "7.24.1",
|
|
45
|
+
"@react-email/render": "0.0.12",
|
|
46
|
+
chalk: "4.1.2",
|
|
47
|
+
chokidar: "3.5.3",
|
|
48
|
+
commander: "11.1.0",
|
|
49
|
+
debounce: "2.0.0",
|
|
50
|
+
esbuild: "0.19.11",
|
|
51
|
+
glob: "10.3.4",
|
|
52
|
+
"log-symbols": "4.1.0",
|
|
53
|
+
"mime-types": "2.1.35",
|
|
54
|
+
"normalize-path": "3.0.0",
|
|
55
|
+
ora: "5.4.1",
|
|
56
|
+
react: "^18.2.0",
|
|
57
|
+
"react-dom": "^18.2.0",
|
|
58
|
+
shelljs: "0.8.5",
|
|
59
|
+
"socket.io": "4.7.3",
|
|
60
|
+
"source-map-js": "1.0.2",
|
|
61
|
+
"stacktrace-parser": "0.1.10"
|
|
62
|
+
},
|
|
63
|
+
devDependencies: {
|
|
64
|
+
"@radix-ui/colors": "1.0.1",
|
|
65
|
+
"@radix-ui/react-collapsible": "1.0.3",
|
|
66
|
+
"@radix-ui/react-popover": "1.0.7",
|
|
67
|
+
"@radix-ui/react-slot": "1.0.2",
|
|
68
|
+
"@radix-ui/react-toggle-group": "1.0.4",
|
|
69
|
+
"@radix-ui/react-tooltip": "1.0.6",
|
|
70
|
+
"@swc/core": "1.4.15",
|
|
71
|
+
"@types/babel__core": "7.20.5",
|
|
72
|
+
"@types/fs-extra": "11.0.1",
|
|
73
|
+
"@types/mime-types": "2.1.4",
|
|
74
|
+
"@types/node": "18.0.0",
|
|
75
|
+
"@types/normalize-path": "3.0.2",
|
|
76
|
+
"@types/react": "^18.2.0",
|
|
77
|
+
"@types/react-dom": "^18.2.0",
|
|
78
|
+
"@types/shelljs": "0.8.15",
|
|
79
|
+
"@types/webpack": "5.28.5",
|
|
80
|
+
"@vercel/style-guide": "5.1.0",
|
|
81
|
+
autoprefixer: "10.4.14",
|
|
82
|
+
clsx: "2.1.0",
|
|
83
|
+
eslint: "8.50.0",
|
|
84
|
+
"eslint-config-prettier": "9.0.0",
|
|
85
|
+
"eslint-config-turbo": "1.10.12",
|
|
86
|
+
"framer-motion": "10.17.4",
|
|
87
|
+
next: "14.2.1",
|
|
88
|
+
postcss: "8.4.38",
|
|
89
|
+
"prism-react-renderer": "2.1.0",
|
|
90
|
+
sharp: "0.33.3",
|
|
91
|
+
"socket.io-client": "4.7.3",
|
|
92
|
+
sonner: "1.3.1",
|
|
93
|
+
"tailwind-merge": "2.2.0",
|
|
94
|
+
tailwindcss: "3.4.0",
|
|
95
|
+
tsup: "7.2.0",
|
|
96
|
+
tsx: "4.7.1",
|
|
97
|
+
typescript: "5.1.6",
|
|
98
|
+
vitest: "1.1.3",
|
|
99
|
+
watch: "1.0.2"
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/cli/commands/dev.ts
|
|
104
|
+
import fs4 from "node:fs";
|
|
105
|
+
|
|
106
|
+
// src/cli/utils/tree.ts
|
|
107
|
+
import { promises as fs } from "node:fs";
|
|
108
|
+
import os from "node:os";
|
|
109
|
+
import path from "node:path";
|
|
110
|
+
var SYMBOLS = {
|
|
111
|
+
BRANCH: "\u251C\u2500\u2500 ",
|
|
112
|
+
EMPTY: "",
|
|
113
|
+
INDENT: " ",
|
|
114
|
+
LAST_BRANCH: "\u2514\u2500\u2500 ",
|
|
115
|
+
VERTICAL: "\u2502 "
|
|
116
|
+
};
|
|
117
|
+
var getTreeLines = async (dirPath, depth, currentDepth = 0) => {
|
|
118
|
+
const base = process.cwd();
|
|
119
|
+
const dirFullpath = path.resolve(base, dirPath);
|
|
120
|
+
const dirname = path.basename(dirFullpath);
|
|
121
|
+
let lines = [dirname];
|
|
122
|
+
const dirStat = await fs.stat(dirFullpath);
|
|
123
|
+
if (dirStat.isDirectory() && currentDepth < depth) {
|
|
124
|
+
const childDirents = await fs.readdir(dirFullpath, { withFileTypes: true });
|
|
125
|
+
childDirents.sort((a, b) => {
|
|
126
|
+
if (a.isDirectory() && b.isFile()) {
|
|
127
|
+
return -1;
|
|
128
|
+
} else if (a.isFile() && b.isDirectory()) {
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
return b.name > a.name ? -1 : 1;
|
|
132
|
+
});
|
|
133
|
+
for (let i = 0; i < childDirents.length; i++) {
|
|
134
|
+
const dirent = childDirents[i];
|
|
135
|
+
const isLast = i === childDirents.length - 1;
|
|
136
|
+
const branchingSymbol = isLast ? SYMBOLS.LAST_BRANCH : SYMBOLS.BRANCH;
|
|
137
|
+
const verticalSymbol = isLast ? SYMBOLS.INDENT : SYMBOLS.VERTICAL;
|
|
138
|
+
if (dirent.isFile()) {
|
|
139
|
+
lines.push(`${branchingSymbol}${dirent.name}`);
|
|
140
|
+
} else {
|
|
141
|
+
const pathToDirectory = path.join(dirFullpath, dirent.name);
|
|
142
|
+
const treeLinesForSubDirectory = await getTreeLines(
|
|
143
|
+
pathToDirectory,
|
|
144
|
+
depth,
|
|
145
|
+
currentDepth + 1
|
|
146
|
+
);
|
|
147
|
+
lines = lines.concat(
|
|
148
|
+
treeLinesForSubDirectory.map(
|
|
149
|
+
(line, index) => index === 0 ? `${branchingSymbol}${line}` : `${verticalSymbol}${line}`
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return lines;
|
|
156
|
+
};
|
|
157
|
+
var tree = async (dirPath, depth) => {
|
|
158
|
+
const lines = await getTreeLines(dirPath, depth);
|
|
159
|
+
return lines.join(os.EOL);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// src/cli/utils/preview/hot-reloading/setup-hot-reloading.ts
|
|
163
|
+
import path3 from "node:path";
|
|
164
|
+
import { Server as SocketServer } from "socket.io";
|
|
165
|
+
import { watch } from "chokidar";
|
|
166
|
+
import debounce from "debounce";
|
|
167
|
+
|
|
168
|
+
// src/cli/utils/preview/hot-reloading/create-dependency-graph.ts
|
|
169
|
+
import path2 from "node:path";
|
|
170
|
+
import { promises as fs2 } from "node:fs";
|
|
171
|
+
|
|
172
|
+
// src/cli/utils/preview/hot-reloading/get-imported-modules.ts
|
|
173
|
+
import { traverse } from "@babel/core";
|
|
174
|
+
import { parse } from "@babel/parser";
|
|
175
|
+
var getImportedModules = (contents) => {
|
|
176
|
+
const importedPaths = [];
|
|
177
|
+
const parsedContents = parse(contents, {
|
|
178
|
+
sourceType: "unambiguous",
|
|
179
|
+
strictMode: false,
|
|
180
|
+
errorRecovery: true,
|
|
181
|
+
plugins: ["jsx", "typescript"]
|
|
182
|
+
});
|
|
183
|
+
traverse(parsedContents, {
|
|
184
|
+
ImportDeclaration(path11) {
|
|
185
|
+
importedPaths.push(path11.node.source.value);
|
|
186
|
+
},
|
|
187
|
+
CallExpression(path11) {
|
|
188
|
+
if ("name" in path11.node.callee && path11.node.callee.name === "require") {
|
|
189
|
+
if (path11.node.arguments.length === 1) {
|
|
190
|
+
const importPathNode = path11.node.arguments[0];
|
|
191
|
+
if (importPathNode.type === "StringLiteral") {
|
|
192
|
+
importedPaths.push(importPathNode.value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return importedPaths;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/cli/utils/preview/hot-reloading/create-dependency-graph.ts
|
|
202
|
+
var readAllFilesInsideDirectory = async (directory) => {
|
|
203
|
+
let allFilePaths = [];
|
|
204
|
+
const topLevelDirents = await fs2.readdir(directory, { withFileTypes: true });
|
|
205
|
+
for await (const dirent of topLevelDirents) {
|
|
206
|
+
const pathToDirent = path2.join(directory, dirent.name);
|
|
207
|
+
if (dirent.isDirectory()) {
|
|
208
|
+
allFilePaths = allFilePaths.concat(
|
|
209
|
+
await readAllFilesInsideDirectory(pathToDirent)
|
|
210
|
+
);
|
|
211
|
+
} else {
|
|
212
|
+
allFilePaths.push(pathToDirent);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return allFilePaths;
|
|
216
|
+
};
|
|
217
|
+
var isJavascriptModule = (filePath) => {
|
|
218
|
+
const extensionName = path2.extname(filePath);
|
|
219
|
+
return [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"].includes(extensionName);
|
|
220
|
+
};
|
|
221
|
+
var createDependencyGraph = async (directory) => {
|
|
222
|
+
const filePaths = await readAllFilesInsideDirectory(directory);
|
|
223
|
+
const modulePaths = filePaths.filter(isJavascriptModule);
|
|
224
|
+
const graph = Object.fromEntries(
|
|
225
|
+
modulePaths.map((path11) => [
|
|
226
|
+
path11,
|
|
227
|
+
{
|
|
228
|
+
path: path11,
|
|
229
|
+
dependencyPaths: [],
|
|
230
|
+
dependentPaths: [],
|
|
231
|
+
moduleDependencies: []
|
|
232
|
+
}
|
|
233
|
+
])
|
|
234
|
+
);
|
|
235
|
+
const getDependencyPaths = async (filePath) => {
|
|
236
|
+
const contents = await fs2.readFile(filePath, "utf8");
|
|
237
|
+
const importedPaths = getImportedModules(contents);
|
|
238
|
+
const importedPathsRelativeToDirectory = importedPaths.map(
|
|
239
|
+
(dependencyPath) => {
|
|
240
|
+
const isModulePath = !dependencyPath.startsWith(".");
|
|
241
|
+
if (!isModulePath && !path2.isAbsolute(dependencyPath)) {
|
|
242
|
+
let pathToDependencyFromDirectory = path2.resolve(
|
|
243
|
+
/*
|
|
244
|
+
path.resolve resolves paths differently from what imports on javascript do.
|
|
245
|
+
|
|
246
|
+
So if we wouldn't do this, for an email at "/path/to/email.tsx" with a dependecy path of "./other-email"
|
|
247
|
+
would end up going into /path/to/email.tsx/other-email instead of /path/to/other-email which is the
|
|
248
|
+
one the import is meant to go to
|
|
249
|
+
*/
|
|
250
|
+
path2.dirname(filePath),
|
|
251
|
+
dependencyPath
|
|
252
|
+
);
|
|
253
|
+
if (!isJavascriptModule(pathToDependencyFromDirectory)) {
|
|
254
|
+
pathToDependencyFromDirectory = `${pathToDependencyFromDirectory}${path2.extname(
|
|
255
|
+
filePath
|
|
256
|
+
)}`;
|
|
257
|
+
}
|
|
258
|
+
return pathToDependencyFromDirectory;
|
|
259
|
+
} else {
|
|
260
|
+
return dependencyPath;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
const moduleDependencies = importedPathsRelativeToDirectory.filter(
|
|
265
|
+
(dependencyPath) => !dependencyPath.startsWith(".") && !path2.isAbsolute(dependencyPath)
|
|
266
|
+
);
|
|
267
|
+
const nonNodeModuleImportPathsRelativeToDirectory = importedPathsRelativeToDirectory.filter(
|
|
268
|
+
(dependencyPath) => dependencyPath.startsWith(".") || path2.isAbsolute(dependencyPath)
|
|
269
|
+
);
|
|
270
|
+
return {
|
|
271
|
+
dependencyPaths: nonNodeModuleImportPathsRelativeToDirectory,
|
|
272
|
+
moduleDependencies
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
const updateModuleDependenciesInGraph = async (moduleFilePath) => {
|
|
276
|
+
const module = graph[moduleFilePath] ?? {
|
|
277
|
+
path: moduleFilePath,
|
|
278
|
+
dependencyPaths: [],
|
|
279
|
+
dependentPaths: [],
|
|
280
|
+
moduleDependencies: []
|
|
281
|
+
};
|
|
282
|
+
const { moduleDependencies, dependencyPaths: newDependencyPaths } = await getDependencyPaths(moduleFilePath);
|
|
283
|
+
module.moduleDependencies = moduleDependencies;
|
|
284
|
+
for (const dependencyPath of module.dependencyPaths) {
|
|
285
|
+
if (newDependencyPaths.includes(dependencyPath))
|
|
286
|
+
continue;
|
|
287
|
+
const dependencyModule = graph[dependencyPath];
|
|
288
|
+
if (dependencyModule !== void 0) {
|
|
289
|
+
dependencyModule.dependentPaths = dependencyModule.dependentPaths.filter(
|
|
290
|
+
(dependentPath) => dependentPath !== moduleFilePath
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
module.dependencyPaths = newDependencyPaths;
|
|
295
|
+
for (const dependencyPath of newDependencyPaths) {
|
|
296
|
+
const dependencyModule = graph[dependencyPath];
|
|
297
|
+
if (dependencyModule !== void 0 && !dependencyModule.dependentPaths.includes(moduleFilePath)) {
|
|
298
|
+
dependencyModule.dependentPaths.push(moduleFilePath);
|
|
299
|
+
} else {
|
|
300
|
+
graph[dependencyPath] = {
|
|
301
|
+
path: dependencyPath,
|
|
302
|
+
moduleDependencies: [],
|
|
303
|
+
dependencyPaths: [],
|
|
304
|
+
dependentPaths: [moduleFilePath]
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
graph[moduleFilePath] = module;
|
|
309
|
+
};
|
|
310
|
+
for (const filePath of modulePaths) {
|
|
311
|
+
await updateModuleDependenciesInGraph(filePath);
|
|
312
|
+
}
|
|
313
|
+
const removeModuleFromGraph = (filePath) => {
|
|
314
|
+
const module = graph[filePath];
|
|
315
|
+
if (module) {
|
|
316
|
+
for (const dependencyPath of module.dependencyPaths) {
|
|
317
|
+
if (graph[dependencyPath]) {
|
|
318
|
+
graph[dependencyPath].dependentPaths = graph[dependencyPath].dependentPaths.filter(
|
|
319
|
+
(dependentPath) => dependentPath !== filePath
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
delete graph[filePath];
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
return [
|
|
327
|
+
graph,
|
|
328
|
+
/**
|
|
329
|
+
* @param pathToModified - A path relative to the previosuly provided {@link directory}.
|
|
330
|
+
*/
|
|
331
|
+
async (event, pathToModified) => {
|
|
332
|
+
switch (event) {
|
|
333
|
+
case "change":
|
|
334
|
+
if (isJavascriptModule(pathToModified)) {
|
|
335
|
+
await updateModuleDependenciesInGraph(pathToModified);
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
338
|
+
case "add":
|
|
339
|
+
if (isJavascriptModule(pathToModified)) {
|
|
340
|
+
await updateModuleDependenciesInGraph(pathToModified);
|
|
341
|
+
}
|
|
342
|
+
break;
|
|
343
|
+
case "addDir":
|
|
344
|
+
const filesInsideAddedDirectory = await readAllFilesInsideDirectory(pathToModified);
|
|
345
|
+
const modulesInsideAddedDirectory = filesInsideAddedDirectory.filter(isJavascriptModule);
|
|
346
|
+
for await (const filePath of modulesInsideAddedDirectory) {
|
|
347
|
+
await updateModuleDependenciesInGraph(filePath);
|
|
348
|
+
}
|
|
349
|
+
break;
|
|
350
|
+
case "unlink":
|
|
351
|
+
if (isJavascriptModule(pathToModified)) {
|
|
352
|
+
removeModuleFromGraph(pathToModified);
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
355
|
+
case "unlinkDir":
|
|
356
|
+
const filesInsideDeletedDirectory = await readAllFilesInsideDirectory(pathToModified);
|
|
357
|
+
const modulesInsideDeletedDirectory = filesInsideDeletedDirectory.filter(isJavascriptModule);
|
|
358
|
+
for await (const filePath of modulesInsideDeletedDirectory) {
|
|
359
|
+
removeModuleFromGraph(filePath);
|
|
360
|
+
}
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
];
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// src/cli/utils/preview/hot-reloading/setup-hot-reloading.ts
|
|
368
|
+
var setupHotreloading = async (devServer2, emailDirRelativePath) => {
|
|
369
|
+
let clients = [];
|
|
370
|
+
const io = new SocketServer(devServer2);
|
|
371
|
+
io.on("connection", (client) => {
|
|
372
|
+
clients.push(client);
|
|
373
|
+
client.on("disconnect", () => {
|
|
374
|
+
clients = clients.filter((item) => item !== client);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
const absolutePathToEmailsDirectory = path3.resolve(
|
|
378
|
+
process.cwd(),
|
|
379
|
+
emailDirRelativePath
|
|
380
|
+
);
|
|
381
|
+
const watcher = watch("", {
|
|
382
|
+
ignoreInitial: true,
|
|
383
|
+
cwd: absolutePathToEmailsDirectory
|
|
384
|
+
});
|
|
385
|
+
const exit = () => {
|
|
386
|
+
void watcher.close();
|
|
387
|
+
};
|
|
388
|
+
process.on("SIGINT", exit);
|
|
389
|
+
process.on("uncaughtException", exit);
|
|
390
|
+
let changes = [];
|
|
391
|
+
const reload = debounce(() => {
|
|
392
|
+
clients.forEach((client) => {
|
|
393
|
+
client.emit("reload", changes);
|
|
394
|
+
});
|
|
395
|
+
changes = [];
|
|
396
|
+
}, 150);
|
|
397
|
+
const [dependencyGraph, updateDependencyGraph] = await createDependencyGraph(
|
|
398
|
+
absolutePathToEmailsDirectory
|
|
399
|
+
);
|
|
400
|
+
watcher.on("all", async (event, relativePathToChangeTarget) => {
|
|
401
|
+
const file = relativePathToChangeTarget.split(path3.sep);
|
|
402
|
+
if (file.length === 0) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
await updateDependencyGraph(event, relativePathToChangeTarget);
|
|
406
|
+
changes.push({
|
|
407
|
+
event,
|
|
408
|
+
filename: relativePathToChangeTarget
|
|
409
|
+
});
|
|
410
|
+
const pathToChangeTarget = path3.resolve(
|
|
411
|
+
absolutePathToEmailsDirectory,
|
|
412
|
+
relativePathToChangeTarget
|
|
413
|
+
);
|
|
414
|
+
changes.push(
|
|
415
|
+
...(dependencyGraph[pathToChangeTarget]?.dependentPaths ?? []).map(
|
|
416
|
+
(dependentPath) => ({
|
|
417
|
+
event: "change",
|
|
418
|
+
filename: path3.relative(absolutePathToEmailsDirectory, dependentPath)
|
|
419
|
+
})
|
|
420
|
+
)
|
|
421
|
+
);
|
|
422
|
+
reload();
|
|
423
|
+
});
|
|
424
|
+
return watcher;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// src/cli/utils/preview/start-dev-server.ts
|
|
428
|
+
import path6 from "node:path";
|
|
429
|
+
import http from "node:http";
|
|
430
|
+
import url from "node:url";
|
|
431
|
+
import next from "next";
|
|
432
|
+
import ora from "ora";
|
|
433
|
+
import logSymbols from "log-symbols";
|
|
434
|
+
import chalk from "chalk";
|
|
435
|
+
|
|
436
|
+
// src/cli/utils/close-ora-on-sigint.ts
|
|
437
|
+
var spinners = /* @__PURE__ */ new Set();
|
|
438
|
+
process.on("SIGINT", () => {
|
|
439
|
+
spinners.forEach((spinner) => {
|
|
440
|
+
if (spinner.isSpinning) {
|
|
441
|
+
spinner.stop();
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
var closeOraOnSIGNIT = (spinner) => {
|
|
446
|
+
spinners.add(spinner);
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// src/cli/utils/preview/serve-static-file.ts
|
|
450
|
+
import path4 from "node:path";
|
|
451
|
+
import { promises as fs3, existsSync } from "node:fs";
|
|
452
|
+
import { lookup } from "mime-types";
|
|
453
|
+
var serveStaticFile = async (res, parsedUrl, staticDirRelativePath) => {
|
|
454
|
+
const staticBaseDir = path4.join(process.cwd(), staticDirRelativePath);
|
|
455
|
+
const pathname = parsedUrl.pathname;
|
|
456
|
+
const ext = path4.parse(pathname).ext;
|
|
457
|
+
let fileAbsolutePath = path4.join(staticBaseDir, pathname);
|
|
458
|
+
const doesFileExist = existsSync(fileAbsolutePath);
|
|
459
|
+
if (!doesFileExist) {
|
|
460
|
+
res.statusCode = 404;
|
|
461
|
+
res.end(`File ${pathname} not found!`);
|
|
462
|
+
} else {
|
|
463
|
+
const fileStat = await fs3.stat(fileAbsolutePath);
|
|
464
|
+
if (fileStat.isDirectory()) {
|
|
465
|
+
fileAbsolutePath += `/index${ext}`;
|
|
466
|
+
}
|
|
467
|
+
const fileData = await fs3.readFile(fileAbsolutePath);
|
|
468
|
+
res.setHeader("Content-type", lookup(ext) || "text/plain");
|
|
469
|
+
res.end(fileData);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// src/cli/utils/preview/get-env-variables-for-preview-app.ts
|
|
474
|
+
import path5 from "path";
|
|
475
|
+
var getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, cwd) => {
|
|
476
|
+
return {
|
|
477
|
+
EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
|
|
478
|
+
EMAILS_DIR_ABSOLUTE_PATH: path5.resolve(cwd, relativePathToEmailsDirectory),
|
|
479
|
+
USER_PROJECT_LOCATION: cwd
|
|
480
|
+
};
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// src/cli/utils/preview/start-dev-server.ts
|
|
484
|
+
var devServer;
|
|
485
|
+
var safeAsyncServerListen = (server, port) => {
|
|
486
|
+
return new Promise((resolve) => {
|
|
487
|
+
server.listen(port, () => {
|
|
488
|
+
resolve({ portAlreadyInUse: false });
|
|
489
|
+
});
|
|
490
|
+
server.on("error", (e) => {
|
|
491
|
+
if (e.code === "EADDRINUSE") {
|
|
492
|
+
resolve({ portAlreadyInUse: true });
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
};
|
|
497
|
+
var isDev = !__filename.endsWith(path6.join("cli", "index.js"));
|
|
498
|
+
var cliPacakgeLocation = isDev ? path6.resolve(__dirname, "../../../..") : path6.resolve(__dirname, "../..");
|
|
499
|
+
var previewServerLocation = isDev ? path6.resolve(__dirname, "../../../..") : path6.resolve(__dirname, "../preview");
|
|
500
|
+
var startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port) => {
|
|
501
|
+
devServer = http.createServer((req, res) => {
|
|
502
|
+
if (!req.url) {
|
|
503
|
+
res.end(404);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const parsedUrl = url.parse(req.url, true);
|
|
507
|
+
res.setHeader(
|
|
508
|
+
"Cache-Control",
|
|
509
|
+
"no-cache, max-age=0, must-revalidate, no-store"
|
|
510
|
+
);
|
|
511
|
+
res.setHeader("Pragma", "no-cache");
|
|
512
|
+
res.setHeader("Expires", "-1");
|
|
513
|
+
try {
|
|
514
|
+
if (parsedUrl.path && parsedUrl.path.includes("static/") && !parsedUrl.path.includes("_next/static/")) {
|
|
515
|
+
void serveStaticFile(res, parsedUrl, staticBaseDirRelativePath);
|
|
516
|
+
} else if (!isNextReady) {
|
|
517
|
+
void nextReadyPromise.then(
|
|
518
|
+
() => nextHandleRequest?.(req, res, parsedUrl)
|
|
519
|
+
);
|
|
520
|
+
} else {
|
|
521
|
+
void nextHandleRequest?.(req, res, parsedUrl);
|
|
522
|
+
}
|
|
523
|
+
} catch (e) {
|
|
524
|
+
console.error("caught error", e);
|
|
525
|
+
res.writeHead(500);
|
|
526
|
+
res.end();
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
const { portAlreadyInUse } = await safeAsyncServerListen(devServer, port);
|
|
530
|
+
if (!portAlreadyInUse) {
|
|
531
|
+
console.log(chalk.greenBright(` React Email ${package_default.version}`));
|
|
532
|
+
console.log(` Running preview at: http://localhost:${port}
|
|
533
|
+
`);
|
|
534
|
+
} else {
|
|
535
|
+
const nextPortToTry = port + 1;
|
|
536
|
+
console.warn(
|
|
537
|
+
` ${logSymbols.warning} Port ${port} is already in use, trying ${nextPortToTry}`
|
|
538
|
+
);
|
|
539
|
+
return startDevServer(
|
|
540
|
+
emailsDirRelativePath,
|
|
541
|
+
staticBaseDirRelativePath,
|
|
542
|
+
nextPortToTry
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
devServer.on("close", async () => {
|
|
546
|
+
await app.close();
|
|
547
|
+
});
|
|
548
|
+
devServer.on("error", (e) => {
|
|
549
|
+
console.error(
|
|
550
|
+
` ${logSymbols.error} preview server error: `,
|
|
551
|
+
JSON.stringify(e)
|
|
552
|
+
);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
});
|
|
555
|
+
const spinner = ora({
|
|
556
|
+
text: "Getting react-email preview server ready...\n",
|
|
557
|
+
prefixText: " "
|
|
558
|
+
}).start();
|
|
559
|
+
closeOraOnSIGNIT(spinner);
|
|
560
|
+
const timeBeforeNextReady = performance.now();
|
|
561
|
+
process.env = {
|
|
562
|
+
...process.env,
|
|
563
|
+
...getEnvVariablesForPreviewApp(
|
|
564
|
+
// If we don't do normalization here, stuff like https://github.com/resend/react-email/issues/1354 happens.
|
|
565
|
+
path6.normalize(emailsDirRelativePath),
|
|
566
|
+
process.cwd()
|
|
567
|
+
)
|
|
568
|
+
};
|
|
569
|
+
const app = next({
|
|
570
|
+
// passing in env here does not get the environment variables there
|
|
571
|
+
dev: isDev,
|
|
572
|
+
hostname: "localhost",
|
|
573
|
+
port,
|
|
574
|
+
dir: previewServerLocation
|
|
575
|
+
});
|
|
576
|
+
let isNextReady = false;
|
|
577
|
+
const nextReadyPromise = app.prepare();
|
|
578
|
+
await nextReadyPromise;
|
|
579
|
+
isNextReady = true;
|
|
580
|
+
const nextHandleRequest = app.getRequestHandler();
|
|
581
|
+
const secondsToNextReady = ((performance.now() - timeBeforeNextReady) / 1e3).toFixed(1);
|
|
582
|
+
spinner.stopAndPersist({
|
|
583
|
+
text: `Ready in ${secondsToNextReady}s
|
|
584
|
+
`,
|
|
585
|
+
symbol: logSymbols.success
|
|
586
|
+
});
|
|
587
|
+
return devServer;
|
|
588
|
+
};
|
|
589
|
+
var makeExitHandler = (options) => (_codeOrSignal) => {
|
|
590
|
+
if (typeof devServer !== "undefined") {
|
|
591
|
+
console.log("\nshutting down dev server");
|
|
592
|
+
devServer.close();
|
|
593
|
+
devServer = void 0;
|
|
594
|
+
}
|
|
595
|
+
if (options?.shouldKillProcess) {
|
|
596
|
+
process.exit(options.killWithErrorCode ? 1 : 0);
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
process.on("exit", makeExitHandler());
|
|
600
|
+
process.on(
|
|
601
|
+
"SIGINT",
|
|
602
|
+
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
|
|
603
|
+
);
|
|
604
|
+
process.on(
|
|
605
|
+
"SIGUSR1",
|
|
606
|
+
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
|
|
607
|
+
);
|
|
608
|
+
process.on(
|
|
609
|
+
"SIGUSR2",
|
|
610
|
+
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
|
|
611
|
+
);
|
|
612
|
+
process.on(
|
|
613
|
+
"uncaughtException",
|
|
614
|
+
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: true })
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
// src/cli/commands/dev.ts
|
|
618
|
+
var dev = async ({ dir: emailsDirRelativePath, port }) => {
|
|
619
|
+
try {
|
|
620
|
+
if (!fs4.existsSync(emailsDirRelativePath)) {
|
|
621
|
+
throw new Error(`Missing ${emailsDirRelativePath} folder`);
|
|
622
|
+
}
|
|
623
|
+
const devServer2 = await startDevServer(
|
|
624
|
+
emailsDirRelativePath,
|
|
625
|
+
emailsDirRelativePath,
|
|
626
|
+
// defaults to ./emails/static for the static files that are served to the preview
|
|
627
|
+
parseInt(port)
|
|
628
|
+
);
|
|
629
|
+
await setupHotreloading(devServer2, emailsDirRelativePath);
|
|
630
|
+
} catch (error) {
|
|
631
|
+
console.log(error);
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
// src/cli/commands/export.ts
|
|
637
|
+
import fs6, { unlinkSync, writeFileSync } from "node:fs";
|
|
638
|
+
import path8 from "node:path";
|
|
639
|
+
import { glob } from "glob";
|
|
640
|
+
import { buildSync } from "esbuild";
|
|
641
|
+
import ora2 from "ora";
|
|
642
|
+
import logSymbols2 from "log-symbols";
|
|
643
|
+
import { renderAsync } from "@react-email/render";
|
|
644
|
+
import normalize from "normalize-path";
|
|
645
|
+
import { cp } from "shelljs";
|
|
646
|
+
|
|
647
|
+
// src/actions/get-emails-directory-metadata.ts
|
|
648
|
+
import fs5 from "node:fs";
|
|
649
|
+
import path7 from "node:path";
|
|
650
|
+
var isFileAnEmail = (fullPath) => {
|
|
651
|
+
const stat = fs5.statSync(fullPath);
|
|
652
|
+
if (stat.isDirectory())
|
|
653
|
+
return false;
|
|
654
|
+
const { ext } = path7.parse(fullPath);
|
|
655
|
+
if (![".js", ".tsx", ".jsx"].includes(ext))
|
|
656
|
+
return false;
|
|
657
|
+
const fileContents = fs5.readFileSync(fullPath, "utf8");
|
|
658
|
+
return /\bexport\s+default\b/gm.test(fileContents);
|
|
659
|
+
};
|
|
660
|
+
var mergeDirectoriesWithSubDirectories = (emailsDirectoryMetadata) => {
|
|
661
|
+
let currentResultingMergedDirectory = emailsDirectoryMetadata;
|
|
662
|
+
while (currentResultingMergedDirectory.emailFilenames.length === 0 && currentResultingMergedDirectory.subDirectories.length === 1) {
|
|
663
|
+
const onlySubDirectory = currentResultingMergedDirectory.subDirectories[0];
|
|
664
|
+
currentResultingMergedDirectory = {
|
|
665
|
+
...onlySubDirectory,
|
|
666
|
+
directoryName: path7.join(
|
|
667
|
+
currentResultingMergedDirectory.directoryName,
|
|
668
|
+
onlySubDirectory.directoryName
|
|
669
|
+
)
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
return currentResultingMergedDirectory;
|
|
673
|
+
};
|
|
674
|
+
var getEmailsDirectoryMetadata = async (absolutePathToEmailsDirectory, baseDirectoryPath = absolutePathToEmailsDirectory) => {
|
|
675
|
+
if (!fs5.existsSync(absolutePathToEmailsDirectory))
|
|
676
|
+
return;
|
|
677
|
+
const dirents = await fs5.promises.readdir(absolutePathToEmailsDirectory, {
|
|
678
|
+
withFileTypes: true
|
|
679
|
+
});
|
|
680
|
+
const emailFilenames = dirents.filter(
|
|
681
|
+
(dirent) => isFileAnEmail(path7.join(absolutePathToEmailsDirectory, dirent.name))
|
|
682
|
+
).map((dirent) => dirent.name.replace(path7.extname(dirent.name), ""));
|
|
683
|
+
const subDirectories = await Promise.all(
|
|
684
|
+
dirents.filter(
|
|
685
|
+
(dirent) => dirent.isDirectory() && !dirent.name.startsWith("_") && dirent.name !== "static"
|
|
686
|
+
).map((dirent) => {
|
|
687
|
+
const direntAbsolutePath = path7.join(
|
|
688
|
+
absolutePathToEmailsDirectory,
|
|
689
|
+
dirent.name
|
|
690
|
+
);
|
|
691
|
+
return getEmailsDirectoryMetadata(
|
|
692
|
+
direntAbsolutePath,
|
|
693
|
+
baseDirectoryPath
|
|
694
|
+
);
|
|
695
|
+
})
|
|
696
|
+
);
|
|
697
|
+
return mergeDirectoriesWithSubDirectories({
|
|
698
|
+
absolutePath: absolutePathToEmailsDirectory,
|
|
699
|
+
relativePath: path7.relative(
|
|
700
|
+
baseDirectoryPath,
|
|
701
|
+
absolutePathToEmailsDirectory
|
|
702
|
+
),
|
|
703
|
+
directoryName: absolutePathToEmailsDirectory.split(path7.sep).pop(),
|
|
704
|
+
emailFilenames,
|
|
705
|
+
subDirectories
|
|
706
|
+
});
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// src/cli/commands/export.ts
|
|
710
|
+
var getEmailTemplatesFromDirectory = (emailDirectory) => {
|
|
711
|
+
const templatePaths = [];
|
|
712
|
+
emailDirectory.emailFilenames.forEach(
|
|
713
|
+
(filename) => templatePaths.push(path8.join(emailDirectory.absolutePath, filename))
|
|
714
|
+
);
|
|
715
|
+
emailDirectory.subDirectories.forEach((directory) => {
|
|
716
|
+
templatePaths.push(...getEmailTemplatesFromDirectory(directory));
|
|
717
|
+
});
|
|
718
|
+
return templatePaths;
|
|
719
|
+
};
|
|
720
|
+
var exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirectoryPath, options) => {
|
|
721
|
+
if (fs6.existsSync(pathToWhereEmailMarkupShouldBeDumped)) {
|
|
722
|
+
fs6.rmSync(pathToWhereEmailMarkupShouldBeDumped, { recursive: true });
|
|
723
|
+
}
|
|
724
|
+
const spinner = ora2("Preparing files...\n").start();
|
|
725
|
+
closeOraOnSIGNIT(spinner);
|
|
726
|
+
const emailsDirectoryMetadata = await getEmailsDirectoryMetadata(
|
|
727
|
+
path8.join(process.cwd(), emailsDirectoryPath)
|
|
728
|
+
);
|
|
729
|
+
if (typeof emailsDirectoryMetadata === "undefined") {
|
|
730
|
+
spinner.stopAndPersist({
|
|
731
|
+
symbol: logSymbols2.error,
|
|
732
|
+
text: `Could not find the directory at ${emailsDirectoryPath}`
|
|
733
|
+
});
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const allTemplates = getEmailTemplatesFromDirectory(emailsDirectoryMetadata);
|
|
737
|
+
const buildResult = buildSync({
|
|
738
|
+
bundle: true,
|
|
739
|
+
entryPoints: allTemplates,
|
|
740
|
+
platform: "node",
|
|
741
|
+
format: "cjs",
|
|
742
|
+
loader: { ".js": "jsx" },
|
|
743
|
+
outExtension: { ".js": ".cjs" },
|
|
744
|
+
jsx: "transform",
|
|
745
|
+
write: true,
|
|
746
|
+
outdir: pathToWhereEmailMarkupShouldBeDumped
|
|
747
|
+
});
|
|
748
|
+
if (buildResult.warnings.length > 0) {
|
|
749
|
+
console.warn(buildResult.warnings);
|
|
750
|
+
}
|
|
751
|
+
if (buildResult.errors.length > 0) {
|
|
752
|
+
spinner.stopAndPersist({
|
|
753
|
+
symbol: logSymbols2.error,
|
|
754
|
+
text: "Failed to build emails"
|
|
755
|
+
});
|
|
756
|
+
console.error(buildResult.errors);
|
|
757
|
+
throw new Error(
|
|
758
|
+
`esbuild bundling process for email templates:
|
|
759
|
+
${allTemplates.map((p) => `- ${p}`).join("\n")}`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
spinner.succeed();
|
|
763
|
+
const allBuiltTemplates = glob.sync(
|
|
764
|
+
normalize(`${pathToWhereEmailMarkupShouldBeDumped}/**/*.cjs`),
|
|
765
|
+
{
|
|
766
|
+
absolute: true
|
|
767
|
+
}
|
|
768
|
+
);
|
|
769
|
+
for await (const template of allBuiltTemplates) {
|
|
770
|
+
try {
|
|
771
|
+
spinner.text = `rendering ${template.split("/").pop()}`;
|
|
772
|
+
spinner.render();
|
|
773
|
+
delete __require.cache[template];
|
|
774
|
+
const component = __require(template);
|
|
775
|
+
const rendered = await renderAsync(component.default({}), options);
|
|
776
|
+
const htmlPath = template.replace(
|
|
777
|
+
".cjs",
|
|
778
|
+
options.plainText ? ".txt" : ".html"
|
|
779
|
+
);
|
|
780
|
+
writeFileSync(htmlPath, rendered);
|
|
781
|
+
unlinkSync(template);
|
|
782
|
+
} catch (exception) {
|
|
783
|
+
spinner.stopAndPersist({
|
|
784
|
+
symbol: logSymbols2.error,
|
|
785
|
+
text: `failed when rendering ${template.split("/").pop()}`
|
|
786
|
+
});
|
|
787
|
+
console.error(exception);
|
|
788
|
+
throw exception;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
spinner.succeed("Rendered all files");
|
|
792
|
+
spinner.text = `Copying static files`;
|
|
793
|
+
spinner.render();
|
|
794
|
+
const staticDirectoryPath = path8.join(emailsDirectoryPath, "static");
|
|
795
|
+
if (fs6.existsSync(staticDirectoryPath)) {
|
|
796
|
+
const pathToDumpStaticFilesInto = path8.join(
|
|
797
|
+
pathToWhereEmailMarkupShouldBeDumped,
|
|
798
|
+
"static"
|
|
799
|
+
);
|
|
800
|
+
if (fs6.existsSync(pathToDumpStaticFilesInto))
|
|
801
|
+
await fs6.promises.rm(pathToDumpStaticFilesInto, { recursive: true });
|
|
802
|
+
const result = cp(
|
|
803
|
+
"-r",
|
|
804
|
+
staticDirectoryPath,
|
|
805
|
+
path8.join(pathToWhereEmailMarkupShouldBeDumped, "static")
|
|
806
|
+
);
|
|
807
|
+
if (result.code > 0) {
|
|
808
|
+
spinner.stopAndPersist({
|
|
809
|
+
symbol: logSymbols2.error,
|
|
810
|
+
text: "Failed to copy static files"
|
|
811
|
+
});
|
|
812
|
+
throw new Error(
|
|
813
|
+
`Something went wrong while copying the file to ${pathToWhereEmailMarkupShouldBeDumped}/static, ${result.stderr}`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
spinner.succeed();
|
|
818
|
+
const fileTree = await tree(pathToWhereEmailMarkupShouldBeDumped, 4);
|
|
819
|
+
console.log(fileTree);
|
|
820
|
+
spinner.stopAndPersist({
|
|
821
|
+
symbol: logSymbols2.success,
|
|
822
|
+
text: "Successfully exported emails"
|
|
823
|
+
});
|
|
824
|
+
process.exit();
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
// src/cli/commands/build.ts
|
|
828
|
+
import fs7 from "node:fs";
|
|
829
|
+
import path9 from "node:path";
|
|
830
|
+
import ora3 from "ora";
|
|
831
|
+
import shell from "shelljs";
|
|
832
|
+
import { spawn } from "node:child_process";
|
|
833
|
+
import logSymbols3 from "log-symbols";
|
|
834
|
+
var buildPreviewApp = (absoluteDirectory) => {
|
|
835
|
+
return new Promise((resolve, reject) => {
|
|
836
|
+
const nextBuild = spawn("npm", ["run", "build"], {
|
|
837
|
+
cwd: absoluteDirectory
|
|
838
|
+
});
|
|
839
|
+
nextBuild.stdout.on("data", (msg) => {
|
|
840
|
+
process.stdout.write(msg);
|
|
841
|
+
});
|
|
842
|
+
nextBuild.stderr.on("data", (msg) => {
|
|
843
|
+
process.stderr.write(msg);
|
|
844
|
+
});
|
|
845
|
+
nextBuild.on("close", (code) => {
|
|
846
|
+
if (code === 0) {
|
|
847
|
+
resolve();
|
|
848
|
+
} else {
|
|
849
|
+
reject(
|
|
850
|
+
new Error(
|
|
851
|
+
`Unable to build the Next app and it exited with code: ${code}`
|
|
852
|
+
)
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
};
|
|
858
|
+
var setNextEnvironmentVariablesForBuild = async (emailsDirRelativePath, builtPreviewAppPath) => {
|
|
859
|
+
const nextConfigContents = `
|
|
860
|
+
const path = require('path');
|
|
861
|
+
const emailsDirRelativePath = path.normalize('${emailsDirRelativePath}');
|
|
862
|
+
const userProjectLocation = path.resolve(process.cwd(), '../');
|
|
863
|
+
/** @type {import('next').NextConfig} */
|
|
864
|
+
module.exports = {
|
|
865
|
+
env: {
|
|
866
|
+
NEXT_PUBLIC_IS_BUILDING: 'true',
|
|
867
|
+
EMAILS_DIR_RELATIVE_PATH: emailsDirRelativePath,
|
|
868
|
+
EMAILS_DIR_ABSOLUTE_PATH: path.resolve(userProjectLocation, emailsDirRelativePath),
|
|
869
|
+
USER_PROJECT_LOCATION: userProjectLocation
|
|
870
|
+
},
|
|
871
|
+
// this is needed so that the code for building emails works properly
|
|
872
|
+
webpack: (
|
|
873
|
+
/** @type {import('webpack').Configuration & { externals: string[] }} */
|
|
874
|
+
config,
|
|
875
|
+
{ isServer }
|
|
876
|
+
) => {
|
|
877
|
+
if (isServer) {
|
|
878
|
+
config.externals.push('esbuild');
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
return config;
|
|
882
|
+
},
|
|
883
|
+
typescript: {
|
|
884
|
+
ignoreBuildErrors: true
|
|
885
|
+
},
|
|
886
|
+
eslint: {
|
|
887
|
+
ignoreDuringBuilds: true
|
|
888
|
+
},
|
|
889
|
+
experimental: {
|
|
890
|
+
webpackBuildWorker: true,
|
|
891
|
+
serverComponentsExternalPackages: [
|
|
892
|
+
'@react-email/components',
|
|
893
|
+
'@react-email/render',
|
|
894
|
+
'@react-email/tailwind',
|
|
895
|
+
],
|
|
896
|
+
},
|
|
897
|
+
}`;
|
|
898
|
+
await fs7.promises.writeFile(
|
|
899
|
+
path9.resolve(builtPreviewAppPath, "./next.config.js"),
|
|
900
|
+
nextConfigContents,
|
|
901
|
+
"utf8"
|
|
902
|
+
);
|
|
903
|
+
};
|
|
904
|
+
var getEmailSlugsFromEmailDirectory = (emailDirectory, emailsDirectoryAbsolutePath) => {
|
|
905
|
+
const directoryPathRelativeToEmailsDirectory = emailDirectory.absolutePath.replace(emailsDirectoryAbsolutePath, "").trim();
|
|
906
|
+
const slugs = [];
|
|
907
|
+
emailDirectory.emailFilenames.forEach(
|
|
908
|
+
(filename) => slugs.push(
|
|
909
|
+
path9.join(directoryPathRelativeToEmailsDirectory, filename).split(path9.sep).filter((segment) => segment.length > 0)
|
|
910
|
+
)
|
|
911
|
+
);
|
|
912
|
+
emailDirectory.subDirectories.forEach((directory) => {
|
|
913
|
+
slugs.push(
|
|
914
|
+
...getEmailSlugsFromEmailDirectory(
|
|
915
|
+
directory,
|
|
916
|
+
emailsDirectoryAbsolutePath
|
|
917
|
+
)
|
|
918
|
+
);
|
|
919
|
+
});
|
|
920
|
+
return slugs;
|
|
921
|
+
};
|
|
922
|
+
var forceSSGForEmailPreviews = async (emailsDirPath, builtPreviewAppPath) => {
|
|
923
|
+
const emailDirectoryMetadata = (
|
|
924
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
925
|
+
await getEmailsDirectoryMetadata(emailsDirPath)
|
|
926
|
+
);
|
|
927
|
+
const parameters = getEmailSlugsFromEmailDirectory(
|
|
928
|
+
emailDirectoryMetadata,
|
|
929
|
+
emailsDirPath
|
|
930
|
+
).map((slug) => ({ slug }));
|
|
931
|
+
const layoutContents = await fs7.promises.readFile(
|
|
932
|
+
path9.resolve(builtPreviewAppPath, "./src/app/layout.tsx"),
|
|
933
|
+
"utf8"
|
|
934
|
+
);
|
|
935
|
+
await fs7.promises.writeFile(
|
|
936
|
+
path9.resolve(builtPreviewAppPath, "./src/app/layout.tsx"),
|
|
937
|
+
layoutContents.replace("export const dynamic = 'force-dynamic';", ""),
|
|
938
|
+
"utf8"
|
|
939
|
+
);
|
|
940
|
+
await fs7.promises.appendFile(
|
|
941
|
+
path9.resolve(builtPreviewAppPath, "./src/app/preview/[...slug]/page.tsx"),
|
|
942
|
+
`
|
|
943
|
+
|
|
944
|
+
export async function generateStaticParams() {
|
|
945
|
+
return ${JSON.stringify(parameters)};
|
|
946
|
+
}`,
|
|
947
|
+
"utf8"
|
|
948
|
+
);
|
|
949
|
+
};
|
|
950
|
+
var updatePackageJson = async (builtPreviewAppPath) => {
|
|
951
|
+
const packageJsonPath = path9.resolve(builtPreviewAppPath, "./package.json");
|
|
952
|
+
const packageJson = JSON.parse(
|
|
953
|
+
await fs7.promises.readFile(packageJsonPath, "utf8")
|
|
954
|
+
);
|
|
955
|
+
packageJson.scripts.build = "next build";
|
|
956
|
+
packageJson.scripts.start = "next start";
|
|
957
|
+
await fs7.promises.writeFile(
|
|
958
|
+
packageJsonPath,
|
|
959
|
+
JSON.stringify(packageJson),
|
|
960
|
+
"utf8"
|
|
961
|
+
);
|
|
962
|
+
};
|
|
963
|
+
var npmInstall = async (builtPreviewAppPath, packageManager) => {
|
|
964
|
+
return new Promise((resolve, reject) => {
|
|
965
|
+
shell.exec(
|
|
966
|
+
`${packageManager} install --silent`,
|
|
967
|
+
{ cwd: builtPreviewAppPath },
|
|
968
|
+
(code) => {
|
|
969
|
+
if (code === 0) {
|
|
970
|
+
resolve();
|
|
971
|
+
} else {
|
|
972
|
+
reject(
|
|
973
|
+
new Error(
|
|
974
|
+
`Unable to install the dependencies and it exited with code: ${code}`
|
|
975
|
+
)
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
);
|
|
980
|
+
});
|
|
981
|
+
};
|
|
982
|
+
var build = async ({
|
|
983
|
+
dir: emailsDirRelativePath,
|
|
984
|
+
packageManager
|
|
985
|
+
}) => {
|
|
986
|
+
try {
|
|
987
|
+
const spinner = ora3({
|
|
988
|
+
text: "Starting build process...",
|
|
989
|
+
prefixText: " "
|
|
990
|
+
}).start();
|
|
991
|
+
closeOraOnSIGNIT(spinner);
|
|
992
|
+
spinner.text = "Checking if emails folder exists";
|
|
993
|
+
if (!fs7.existsSync(emailsDirRelativePath)) {
|
|
994
|
+
throw new Error(`Missing ${emailsDirRelativePath} folder`);
|
|
995
|
+
}
|
|
996
|
+
const emailsDirPath = path9.join(process.cwd(), emailsDirRelativePath);
|
|
997
|
+
const staticPath = path9.join(emailsDirPath, "static");
|
|
998
|
+
const builtPreviewAppPath = path9.join(process.cwd(), ".react-email");
|
|
999
|
+
if (fs7.existsSync(builtPreviewAppPath)) {
|
|
1000
|
+
spinner.text = "Deleting pre-existing `.react-email` folder";
|
|
1001
|
+
await fs7.promises.rm(builtPreviewAppPath, { recursive: true });
|
|
1002
|
+
}
|
|
1003
|
+
spinner.text = "Copying preview app from CLI to `.react-email`";
|
|
1004
|
+
await fs7.promises.cp(cliPacakgeLocation, builtPreviewAppPath, {
|
|
1005
|
+
recursive: true,
|
|
1006
|
+
filter: (source) => {
|
|
1007
|
+
return !source.includes("/cli/") && !source.includes("/.next/") && !/\/node_modules\/?$/.test(source);
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
if (fs7.existsSync(staticPath)) {
|
|
1011
|
+
spinner.text = "Copying `static` folder into `.react-email/public/static`";
|
|
1012
|
+
const builtStaticDirectory = path9.resolve(
|
|
1013
|
+
builtPreviewAppPath,
|
|
1014
|
+
"./public/static"
|
|
1015
|
+
);
|
|
1016
|
+
await fs7.promises.cp(staticPath, builtStaticDirectory, {
|
|
1017
|
+
recursive: true
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
spinner.text = "Setting Next environment variables for preview app to work properly";
|
|
1021
|
+
await setNextEnvironmentVariablesForBuild(
|
|
1022
|
+
emailsDirRelativePath,
|
|
1023
|
+
builtPreviewAppPath
|
|
1024
|
+
);
|
|
1025
|
+
spinner.text = "Setting server side generation for the email preview pages";
|
|
1026
|
+
await forceSSGForEmailPreviews(emailsDirPath, builtPreviewAppPath);
|
|
1027
|
+
spinner.text = "Updating package.json's build and start scripts";
|
|
1028
|
+
await updatePackageJson(builtPreviewAppPath);
|
|
1029
|
+
spinner.text = "Installing dependencies on `.react-email`";
|
|
1030
|
+
await npmInstall(builtPreviewAppPath, packageManager);
|
|
1031
|
+
spinner.stopAndPersist({
|
|
1032
|
+
text: "Successfully prepared `.react-email` for `next build`",
|
|
1033
|
+
symbol: logSymbols3.success
|
|
1034
|
+
});
|
|
1035
|
+
await buildPreviewApp(builtPreviewAppPath);
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
console.log(error);
|
|
1038
|
+
process.exit(1);
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
// src/cli/commands/start.ts
|
|
1043
|
+
import fs8 from "node:fs";
|
|
1044
|
+
import path10 from "node:path";
|
|
1045
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
1046
|
+
var start = async () => {
|
|
1047
|
+
try {
|
|
1048
|
+
const usersProjectLocation = process.cwd();
|
|
1049
|
+
const builtPreviewPath = path10.resolve(
|
|
1050
|
+
usersProjectLocation,
|
|
1051
|
+
"./.react-email"
|
|
1052
|
+
);
|
|
1053
|
+
if (!fs8.existsSync(builtPreviewPath)) {
|
|
1054
|
+
throw new Error(
|
|
1055
|
+
"Could not find `.react-email`, maybe you haven't ran `email build`?"
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
const nextStart = spawn2("npm", ["start"], {
|
|
1059
|
+
cwd: builtPreviewPath,
|
|
1060
|
+
stdio: "inherit"
|
|
1061
|
+
});
|
|
1062
|
+
process.on("SIGINT", () => {
|
|
1063
|
+
nextStart.kill("SIGINT");
|
|
1064
|
+
});
|
|
1065
|
+
nextStart.on("exit", (code) => {
|
|
1066
|
+
process.exit(code ?? 0);
|
|
1067
|
+
});
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
console.log(error);
|
|
1070
|
+
process.exit(1);
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
// src/cli/index.ts
|
|
1075
|
+
var PACKAGE_NAME = "react-email";
|
|
1076
|
+
program.name(PACKAGE_NAME).description("A live preview of your emails right in your browser").version(package_default.version);
|
|
1077
|
+
program.command("dev").description("Starts the preview email development app").option("-d, --dir <path>", "Directory with your email templates", "./emails").option("-p --port <port>", "Port to run dev server on", "3000").action(dev);
|
|
1078
|
+
program.command("build").description("Copies the preview app for onto .react-email and builds it").option("-d, --dir <path>", "Directory with your email templates", "./emails").option(
|
|
1079
|
+
"-p --packageManager <name>",
|
|
1080
|
+
"Package name to use on installation on `.react-email`",
|
|
1081
|
+
"npm"
|
|
1082
|
+
).action(build);
|
|
1083
|
+
program.command("start").description('Runs the built preview app that is inside of ".react-email"').action(start);
|
|
1084
|
+
program.command("export").description("Build the templates to the `out` directory").option("--outDir <path>", "Output directory", "out").option("-p, --pretty", "Pretty print the output", false).option("-t, --plainText", "Set output format as plain text", false).option("-d, --dir <path>", "Directory with your email templates", "./emails").action(
|
|
1085
|
+
({ outDir, pretty, plainText, dir: srcDir }) => exportTemplates(outDir, srcDir, { pretty, plainText })
|
|
1086
|
+
);
|
|
1087
|
+
program.parse();
|