react-email 4.2.11 → 4.2.12
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 +6 -0
- package/dist/index.js +931 -1321
- package/package.json +4 -5
- package/readme.md +3 -4
- package/src/commands/build.ts +5 -5
- package/src/utils/get-emails-directory-metadata.ts +0 -1
- package/src/utils/preview/serve-static-file.ts +0 -1
- package/src/utils/preview/start-dev-server.ts +0 -2
- package/tsdown.config.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -1,296 +1,244 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/index.ts
|
|
2
|
+
import { createRequire } from "node:module";
|
|
4
3
|
import { program } from "commander";
|
|
5
|
-
|
|
6
|
-
// src/commands/build.ts
|
|
7
4
|
import { spawn } from "node:child_process";
|
|
8
|
-
import
|
|
9
|
-
import path3 from "node:path";
|
|
10
|
-
import logSymbols2 from "log-symbols";
|
|
11
|
-
import ora from "ora";
|
|
12
|
-
|
|
13
|
-
// src/utils/get-emails-directory-metadata.ts
|
|
14
|
-
import fs from "node:fs";
|
|
5
|
+
import fs, { existsSync, promises, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
6
|
import path from "node:path";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
fileHandle = await fs.promises.open(fullPath, "r");
|
|
20
|
-
} catch (exception) {
|
|
21
|
-
console.warn(exception);
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
const stat = await fileHandle.stat();
|
|
25
|
-
if (stat.isDirectory()) {
|
|
26
|
-
await fileHandle.close();
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
const { ext } = path.parse(fullPath);
|
|
30
|
-
if (![".js", ".tsx", ".jsx"].includes(ext)) {
|
|
31
|
-
await fileHandle.close();
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
const fileContents = await fileHandle.readFile("utf8");
|
|
35
|
-
await fileHandle.close();
|
|
36
|
-
const hasES6DefaultExport = /\bexport\s+default\b/gm.test(fileContents);
|
|
37
|
-
const hasCommonJSExport = /\bmodule\.exports\s*=/gm.test(fileContents);
|
|
38
|
-
const hasNamedExport = /\bexport\s+\{[^}]*\bdefault\b[^}]*\}/gm.test(
|
|
39
|
-
fileContents
|
|
40
|
-
);
|
|
41
|
-
return hasES6DefaultExport || hasCommonJSExport || hasNamedExport;
|
|
42
|
-
};
|
|
43
|
-
var mergeDirectoriesWithSubDirectories = (emailsDirectoryMetadata) => {
|
|
44
|
-
let currentResultingMergedDirectory = emailsDirectoryMetadata;
|
|
45
|
-
while (currentResultingMergedDirectory.emailFilenames.length === 0 && currentResultingMergedDirectory.subDirectories.length === 1) {
|
|
46
|
-
const onlySubDirectory = currentResultingMergedDirectory.subDirectories[0];
|
|
47
|
-
currentResultingMergedDirectory = {
|
|
48
|
-
...onlySubDirectory,
|
|
49
|
-
directoryName: path.join(
|
|
50
|
-
currentResultingMergedDirectory.directoryName,
|
|
51
|
-
onlySubDirectory.directoryName
|
|
52
|
-
)
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
return currentResultingMergedDirectory;
|
|
56
|
-
};
|
|
57
|
-
var getEmailsDirectoryMetadata = async (absolutePathToEmailsDirectory, keepFileExtensions = false, isSubDirectory = false, baseDirectoryPath = absolutePathToEmailsDirectory) => {
|
|
58
|
-
if (!fs.existsSync(absolutePathToEmailsDirectory)) return;
|
|
59
|
-
const dirents = await fs.promises.readdir(absolutePathToEmailsDirectory, {
|
|
60
|
-
withFileTypes: true
|
|
61
|
-
});
|
|
62
|
-
const isEmailPredicates = await Promise.all(
|
|
63
|
-
dirents.map(
|
|
64
|
-
(dirent) => isFileAnEmail(path.join(absolutePathToEmailsDirectory, dirent.name))
|
|
65
|
-
)
|
|
66
|
-
);
|
|
67
|
-
const emailFilenames = dirents.filter((_, i) => isEmailPredicates[i]).map(
|
|
68
|
-
(dirent) => keepFileExtensions ? dirent.name : dirent.name.replace(path.extname(dirent.name), "")
|
|
69
|
-
);
|
|
70
|
-
const subDirectories = await Promise.all(
|
|
71
|
-
dirents.filter(
|
|
72
|
-
(dirent) => dirent.isDirectory() && !dirent.name.startsWith("_") && dirent.name !== "static"
|
|
73
|
-
).map((dirent) => {
|
|
74
|
-
const direntAbsolutePath = path.join(
|
|
75
|
-
absolutePathToEmailsDirectory,
|
|
76
|
-
dirent.name
|
|
77
|
-
);
|
|
78
|
-
return getEmailsDirectoryMetadata(
|
|
79
|
-
direntAbsolutePath,
|
|
80
|
-
keepFileExtensions,
|
|
81
|
-
true,
|
|
82
|
-
baseDirectoryPath
|
|
83
|
-
);
|
|
84
|
-
})
|
|
85
|
-
);
|
|
86
|
-
const emailsMetadata = {
|
|
87
|
-
absolutePath: absolutePathToEmailsDirectory,
|
|
88
|
-
relativePath: path.relative(
|
|
89
|
-
baseDirectoryPath,
|
|
90
|
-
absolutePathToEmailsDirectory
|
|
91
|
-
),
|
|
92
|
-
directoryName: absolutePathToEmailsDirectory.split(path.sep).pop(),
|
|
93
|
-
emailFilenames,
|
|
94
|
-
subDirectories
|
|
95
|
-
};
|
|
96
|
-
return isSubDirectory ? mergeDirectoriesWithSubDirectories(emailsMetadata) : emailsMetadata;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// src/utils/get-preview-server-location.ts
|
|
100
|
-
import path2 from "node:path";
|
|
7
|
+
import logSymbols from "log-symbols";
|
|
8
|
+
import ora from "ora";
|
|
101
9
|
import url from "node:url";
|
|
102
10
|
import { createJiti } from "jiti";
|
|
103
11
|
import { addDevDependency } from "nypm";
|
|
104
12
|
import prompts from "prompts";
|
|
13
|
+
import { watch } from "chokidar";
|
|
14
|
+
import debounce from "debounce";
|
|
15
|
+
import { Server } from "socket.io";
|
|
16
|
+
import { parse } from "@babel/parser";
|
|
17
|
+
import traverseModule from "@babel/traverse";
|
|
18
|
+
import { createMatchPath, loadConfig } from "tsconfig-paths";
|
|
19
|
+
import http from "node:http";
|
|
20
|
+
import { styleText } from "node:util";
|
|
21
|
+
import { lookup } from "mime-types";
|
|
22
|
+
import os from "node:os";
|
|
23
|
+
import { build } from "esbuild";
|
|
24
|
+
import { glob } from "glob";
|
|
25
|
+
import normalize from "normalize-path";
|
|
105
26
|
|
|
106
|
-
|
|
27
|
+
//#region src/utils/get-emails-directory-metadata.ts
|
|
28
|
+
const isFileAnEmail = async (fullPath) => {
|
|
29
|
+
let fileHandle;
|
|
30
|
+
try {
|
|
31
|
+
fileHandle = await fs.promises.open(fullPath, "r");
|
|
32
|
+
} catch (exception) {
|
|
33
|
+
console.warn(exception);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if ((await fileHandle.stat()).isDirectory()) {
|
|
37
|
+
await fileHandle.close();
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
const { ext } = path.parse(fullPath);
|
|
41
|
+
if (![
|
|
42
|
+
".js",
|
|
43
|
+
".tsx",
|
|
44
|
+
".jsx"
|
|
45
|
+
].includes(ext)) {
|
|
46
|
+
await fileHandle.close();
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const fileContents = await fileHandle.readFile("utf8");
|
|
50
|
+
await fileHandle.close();
|
|
51
|
+
const hasES6DefaultExport = /\bexport\s+default\b/gm.test(fileContents);
|
|
52
|
+
const hasCommonJSExport = /\bmodule\.exports\s*=/gm.test(fileContents);
|
|
53
|
+
const hasNamedExport = /\bexport\s+\{[^}]*\bdefault\b[^}]*\}/gm.test(fileContents);
|
|
54
|
+
return hasES6DefaultExport || hasCommonJSExport || hasNamedExport;
|
|
55
|
+
};
|
|
56
|
+
const mergeDirectoriesWithSubDirectories = (emailsDirectoryMetadata) => {
|
|
57
|
+
let currentResultingMergedDirectory = emailsDirectoryMetadata;
|
|
58
|
+
while (currentResultingMergedDirectory.emailFilenames.length === 0 && currentResultingMergedDirectory.subDirectories.length === 1) {
|
|
59
|
+
const onlySubDirectory = currentResultingMergedDirectory.subDirectories[0];
|
|
60
|
+
currentResultingMergedDirectory = {
|
|
61
|
+
...onlySubDirectory,
|
|
62
|
+
directoryName: path.join(currentResultingMergedDirectory.directoryName, onlySubDirectory.directoryName)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return currentResultingMergedDirectory;
|
|
66
|
+
};
|
|
67
|
+
const getEmailsDirectoryMetadata = async (absolutePathToEmailsDirectory, keepFileExtensions = false, isSubDirectory = false, baseDirectoryPath = absolutePathToEmailsDirectory) => {
|
|
68
|
+
if (!fs.existsSync(absolutePathToEmailsDirectory)) return;
|
|
69
|
+
const dirents = await fs.promises.readdir(absolutePathToEmailsDirectory, { withFileTypes: true });
|
|
70
|
+
const isEmailPredicates = await Promise.all(dirents.map((dirent) => isFileAnEmail(path.join(absolutePathToEmailsDirectory, dirent.name))));
|
|
71
|
+
const emailFilenames = dirents.filter((_, i) => isEmailPredicates[i]).map((dirent) => keepFileExtensions ? dirent.name : dirent.name.replace(path.extname(dirent.name), ""));
|
|
72
|
+
const subDirectories = await Promise.all(dirents.filter((dirent) => dirent.isDirectory() && !dirent.name.startsWith("_") && dirent.name !== "static").map((dirent) => {
|
|
73
|
+
const direntAbsolutePath = path.join(absolutePathToEmailsDirectory, dirent.name);
|
|
74
|
+
return getEmailsDirectoryMetadata(direntAbsolutePath, keepFileExtensions, true, baseDirectoryPath);
|
|
75
|
+
}));
|
|
76
|
+
const emailsMetadata = {
|
|
77
|
+
absolutePath: absolutePathToEmailsDirectory,
|
|
78
|
+
relativePath: path.relative(baseDirectoryPath, absolutePathToEmailsDirectory),
|
|
79
|
+
directoryName: absolutePathToEmailsDirectory.split(path.sep).pop(),
|
|
80
|
+
emailFilenames,
|
|
81
|
+
subDirectories
|
|
82
|
+
};
|
|
83
|
+
return isSubDirectory ? mergeDirectoriesWithSubDirectories(emailsMetadata) : emailsMetadata;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region package.json
|
|
88
|
+
var name = "react-email";
|
|
89
|
+
var version = "4.2.12";
|
|
90
|
+
var description = "A live preview of your emails right in your browser.";
|
|
91
|
+
var bin = { "email": "./dist/index.js" };
|
|
92
|
+
var type = "module";
|
|
93
|
+
var scripts = {
|
|
94
|
+
"build": "tsdown",
|
|
95
|
+
"build:watch": "tsdown --watch src",
|
|
96
|
+
"clean": "rm -rf dist",
|
|
97
|
+
"test": "vitest run",
|
|
98
|
+
"test:watch": "vitest"
|
|
99
|
+
};
|
|
100
|
+
var license = "MIT";
|
|
101
|
+
var repository = {
|
|
102
|
+
"type": "git",
|
|
103
|
+
"url": "https://github.com/resend/react-email.git",
|
|
104
|
+
"directory": "packages/react-email"
|
|
105
|
+
};
|
|
106
|
+
var keywords = ["react", "email"];
|
|
107
|
+
var engines = { "node": ">=18.0.0" };
|
|
108
|
+
var dependencies = {
|
|
109
|
+
"@babel/parser": "^7.27.0",
|
|
110
|
+
"@babel/traverse": "^7.27.0",
|
|
111
|
+
"chokidar": "^4.0.3",
|
|
112
|
+
"commander": "^13.0.0",
|
|
113
|
+
"debounce": "^2.0.0",
|
|
114
|
+
"esbuild": "^0.25.0",
|
|
115
|
+
"glob": "^11.0.0",
|
|
116
|
+
"jiti": "2.4.2",
|
|
117
|
+
"log-symbols": "^7.0.0",
|
|
118
|
+
"mime-types": "^3.0.0",
|
|
119
|
+
"normalize-path": "^3.0.0",
|
|
120
|
+
"nypm": "0.6.0",
|
|
121
|
+
"ora": "^8.0.0",
|
|
122
|
+
"prompts": "2.4.2",
|
|
123
|
+
"socket.io": "^4.8.1",
|
|
124
|
+
"tsconfig-paths": "4.2.0"
|
|
125
|
+
};
|
|
126
|
+
var devDependencies = {
|
|
127
|
+
"@react-email/components": "workspace:*",
|
|
128
|
+
"@types/babel__core": "7.20.5",
|
|
129
|
+
"@types/babel__traverse": "7.20.7",
|
|
130
|
+
"@types/mime-types": "2.1.4",
|
|
131
|
+
"@types/prompts": "2.4.9",
|
|
132
|
+
"next": "^15.3.2",
|
|
133
|
+
"react": "19.0.0",
|
|
134
|
+
"react-dom": "19.0.0",
|
|
135
|
+
"typescript": "5.8.3"
|
|
136
|
+
};
|
|
107
137
|
var package_default = {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
"test:watch": "vitest"
|
|
121
|
-
},
|
|
122
|
-
license: "MIT",
|
|
123
|
-
repository: {
|
|
124
|
-
type: "git",
|
|
125
|
-
url: "https://github.com/resend/react-email.git",
|
|
126
|
-
directory: "packages/react-email"
|
|
127
|
-
},
|
|
128
|
-
keywords: [
|
|
129
|
-
"react",
|
|
130
|
-
"email"
|
|
131
|
-
],
|
|
132
|
-
engines: {
|
|
133
|
-
node: ">=18.0.0"
|
|
134
|
-
},
|
|
135
|
-
dependencies: {
|
|
136
|
-
"@babel/parser": "^7.27.0",
|
|
137
|
-
"@babel/traverse": "^7.27.0",
|
|
138
|
-
chokidar: "^4.0.3",
|
|
139
|
-
commander: "^13.0.0",
|
|
140
|
-
debounce: "^2.0.0",
|
|
141
|
-
esbuild: "^0.25.0",
|
|
142
|
-
glob: "^11.0.0",
|
|
143
|
-
jiti: "2.4.2",
|
|
144
|
-
"log-symbols": "^7.0.0",
|
|
145
|
-
"mime-types": "^3.0.0",
|
|
146
|
-
"normalize-path": "^3.0.0",
|
|
147
|
-
nypm: "0.6.0",
|
|
148
|
-
ora: "^8.0.0",
|
|
149
|
-
prompts: "2.4.2",
|
|
150
|
-
"socket.io": "^4.8.1",
|
|
151
|
-
"tsconfig-paths": "4.2.0"
|
|
152
|
-
},
|
|
153
|
-
devDependencies: {
|
|
154
|
-
"@react-email/components": "workspace:*",
|
|
155
|
-
"@types/babel__core": "7.20.5",
|
|
156
|
-
"@types/babel__traverse": "7.20.7",
|
|
157
|
-
"@types/mime-types": "2.1.4",
|
|
158
|
-
"@types/prompts": "2.4.9",
|
|
159
|
-
next: "^15.3.2",
|
|
160
|
-
react: "19.0.0",
|
|
161
|
-
"react-dom": "19.0.0",
|
|
162
|
-
tsup: "8.4.0",
|
|
163
|
-
typescript: "5.8.3"
|
|
164
|
-
}
|
|
138
|
+
name,
|
|
139
|
+
version,
|
|
140
|
+
description,
|
|
141
|
+
bin,
|
|
142
|
+
type,
|
|
143
|
+
scripts,
|
|
144
|
+
license,
|
|
145
|
+
repository,
|
|
146
|
+
keywords,
|
|
147
|
+
engines,
|
|
148
|
+
dependencies,
|
|
149
|
+
devDependencies
|
|
165
150
|
};
|
|
166
151
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
process.exit(0);
|
|
181
|
-
} else {
|
|
182
|
-
process.exit(0);
|
|
183
|
-
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/utils/get-preview-server-location.ts
|
|
154
|
+
const ensurePreviewServerInstalled = async (message) => {
|
|
155
|
+
if ((await prompts({
|
|
156
|
+
type: "confirm",
|
|
157
|
+
name: "installPreviewServer",
|
|
158
|
+
message,
|
|
159
|
+
initial: true
|
|
160
|
+
})).installPreviewServer) {
|
|
161
|
+
console.log("Installing \"@react-email/preview-server\"");
|
|
162
|
+
await addDevDependency(`@react-email/preview-server@${package_default.version}`);
|
|
163
|
+
process.exit(0);
|
|
164
|
+
} else process.exit(0);
|
|
184
165
|
};
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
const { version } = await usersProject.import("@react-email/preview-server");
|
|
198
|
-
if (version !== package_default.version) {
|
|
199
|
-
await ensurePreviewServerInstalled(
|
|
200
|
-
`To run the preview server, the version of "@react-email/preview-server" must match the version of "react-email" (${package_default.version}). Would you like to install it?`
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
return previewServerLocation;
|
|
166
|
+
const getPreviewServerLocation = async () => {
|
|
167
|
+
const usersProject = createJiti(process.cwd());
|
|
168
|
+
let previewServerLocation;
|
|
169
|
+
try {
|
|
170
|
+
previewServerLocation = path.dirname(url.fileURLToPath(usersProject.esmResolve("@react-email/preview-server")));
|
|
171
|
+
} catch (_exception) {
|
|
172
|
+
await ensurePreviewServerInstalled("To run the preview server, the package \"@react-email/preview-server\" must be installed. Would you like to install it?");
|
|
173
|
+
}
|
|
174
|
+
const { version: version$1 } = await usersProject.import("@react-email/preview-server");
|
|
175
|
+
if (version$1 !== package_default.version) await ensurePreviewServerInstalled(`To run the preview server, the version of "@react-email/preview-server" must match the version of "react-email" (${package_default.version}). Would you like to install it?`);
|
|
176
|
+
return previewServerLocation;
|
|
204
177
|
};
|
|
205
178
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/utils/register-spinner-autostopping.ts
|
|
181
|
+
const spinners = /* @__PURE__ */ new Set();
|
|
209
182
|
process.on("SIGINT", () => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
});
|
|
183
|
+
spinners.forEach((spinner) => {
|
|
184
|
+
if (spinner.isSpinning) spinner.stop();
|
|
185
|
+
});
|
|
215
186
|
});
|
|
216
187
|
process.on("exit", (code) => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
spinner.stopAndPersist({
|
|
221
|
-
symbol: logSymbols.error
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
}
|
|
188
|
+
if (code !== 0) spinners.forEach((spinner) => {
|
|
189
|
+
if (spinner.isSpinning) spinner.stopAndPersist({ symbol: logSymbols.error });
|
|
190
|
+
});
|
|
226
191
|
});
|
|
227
|
-
|
|
228
|
-
|
|
192
|
+
const registerSpinnerAutostopping = (spinner) => {
|
|
193
|
+
spinners.add(spinner);
|
|
229
194
|
};
|
|
230
195
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
`Unable to build the Next app and it exited with code: ${code}`
|
|
247
|
-
)
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
});
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region src/commands/build.ts
|
|
198
|
+
const buildPreviewApp = (absoluteDirectory) => {
|
|
199
|
+
return new Promise((resolve, reject) => {
|
|
200
|
+
const nextBuild = spawn("npm", ["run", "build"], {
|
|
201
|
+
cwd: absoluteDirectory,
|
|
202
|
+
shell: true
|
|
203
|
+
});
|
|
204
|
+
nextBuild.stdout.pipe(process.stdout);
|
|
205
|
+
nextBuild.stderr.pipe(process.stderr);
|
|
206
|
+
nextBuild.on("close", (code) => {
|
|
207
|
+
if (code === 0) resolve();
|
|
208
|
+
else reject(/* @__PURE__ */ new Error(`Unable to build the Next app and it exited with code: ${code}`));
|
|
209
|
+
});
|
|
210
|
+
});
|
|
252
211
|
};
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (code === 0) {
|
|
271
|
-
resolve();
|
|
272
|
-
} else {
|
|
273
|
-
reject(
|
|
274
|
-
new Error(
|
|
275
|
-
`Unable to install the dependencies and it exited with code: ${code}`
|
|
276
|
-
)
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
});
|
|
212
|
+
const npmInstall = async (builtPreviewAppPath, packageManager) => {
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
const childProc = spawn(packageManager, [
|
|
215
|
+
"install",
|
|
216
|
+
packageManager === "deno" ? "" : "--include=dev",
|
|
217
|
+
packageManager === "deno" ? "--quiet" : "--silent"
|
|
218
|
+
], {
|
|
219
|
+
cwd: builtPreviewAppPath,
|
|
220
|
+
shell: true
|
|
221
|
+
});
|
|
222
|
+
childProc.stdout.pipe(process.stdout);
|
|
223
|
+
childProc.stderr.pipe(process.stderr);
|
|
224
|
+
childProc.on("close", (code) => {
|
|
225
|
+
if (code === 0) resolve();
|
|
226
|
+
else reject(/* @__PURE__ */ new Error(`Unable to install the dependencies and it exited with code: ${code}`));
|
|
227
|
+
});
|
|
228
|
+
});
|
|
281
229
|
};
|
|
282
|
-
|
|
283
|
-
|
|
230
|
+
const setNextEnvironmentVariablesForBuild = async (emailsDirRelativePath, builtPreviewAppPath) => {
|
|
231
|
+
const nextConfigContents = `
|
|
284
232
|
const path = require('path');
|
|
285
233
|
const emailsDirRelativePath = path.normalize('${emailsDirRelativePath}');
|
|
286
|
-
const userProjectLocation = '${process.cwd()}';
|
|
234
|
+
const userProjectLocation = '${process.cwd().replace(/\\/g, "/")}';
|
|
287
235
|
/** @type {import('next').NextConfig} */
|
|
288
236
|
module.exports = {
|
|
289
237
|
env: {
|
|
290
238
|
NEXT_PUBLIC_IS_BUILDING: 'true',
|
|
291
239
|
EMAILS_DIR_RELATIVE_PATH: emailsDirRelativePath,
|
|
292
240
|
EMAILS_DIR_ABSOLUTE_PATH: path.resolve(userProjectLocation, emailsDirRelativePath),
|
|
293
|
-
PREVIEW_SERVER_LOCATION: '${builtPreviewAppPath}',
|
|
241
|
+
PREVIEW_SERVER_LOCATION: '${builtPreviewAppPath.replace(/\\/g, "/")}',
|
|
294
242
|
USER_PROJECT_LOCATION: userProjectLocation
|
|
295
243
|
},
|
|
296
244
|
// this is needed so that the code for building emails works properly
|
|
@@ -315,1114 +263,776 @@ module.exports = {
|
|
|
315
263
|
webpackBuildWorker: true
|
|
316
264
|
},
|
|
317
265
|
}`;
|
|
318
|
-
|
|
319
|
-
path3.resolve(builtPreviewAppPath, "./next.config.js"),
|
|
320
|
-
nextConfigContents,
|
|
321
|
-
"utf8"
|
|
322
|
-
);
|
|
266
|
+
await fs.promises.writeFile(path.resolve(builtPreviewAppPath, "./next.config.js"), nextConfigContents, "utf8");
|
|
323
267
|
};
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
emailDirectory.subDirectories.forEach((directory) => {
|
|
333
|
-
slugs.push(
|
|
334
|
-
...getEmailSlugsFromEmailDirectory(
|
|
335
|
-
directory,
|
|
336
|
-
emailsDirectoryAbsolutePath
|
|
337
|
-
)
|
|
338
|
-
);
|
|
339
|
-
});
|
|
340
|
-
return slugs;
|
|
268
|
+
const getEmailSlugsFromEmailDirectory = (emailDirectory, emailsDirectoryAbsolutePath) => {
|
|
269
|
+
const directoryPathRelativeToEmailsDirectory = emailDirectory.absolutePath.replace(emailsDirectoryAbsolutePath, "").trim();
|
|
270
|
+
const slugs = [];
|
|
271
|
+
emailDirectory.emailFilenames.forEach((filename$1) => slugs.push(path.join(directoryPathRelativeToEmailsDirectory, filename$1).split(path.sep).filter((segment) => segment.length > 0)));
|
|
272
|
+
emailDirectory.subDirectories.forEach((directory) => {
|
|
273
|
+
slugs.push(...getEmailSlugsFromEmailDirectory(directory, emailsDirectoryAbsolutePath));
|
|
274
|
+
});
|
|
275
|
+
return slugs;
|
|
341
276
|
};
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const contents = await fs2.promises.readFile(filePath, "utf8");
|
|
353
|
-
await fs2.promises.writeFile(
|
|
354
|
-
filePath,
|
|
355
|
-
contents.replace("export const dynamic = 'force-dynamic';", ""),
|
|
356
|
-
"utf8"
|
|
357
|
-
);
|
|
358
|
-
};
|
|
359
|
-
await removeForceDynamic(
|
|
360
|
-
path3.resolve(builtPreviewAppPath, "./src/app/layout.tsx")
|
|
361
|
-
);
|
|
362
|
-
await removeForceDynamic(
|
|
363
|
-
path3.resolve(builtPreviewAppPath, "./src/app/preview/[...slug]/page.tsx")
|
|
364
|
-
);
|
|
365
|
-
await fs2.promises.appendFile(
|
|
366
|
-
path3.resolve(builtPreviewAppPath, "./src/app/preview/[...slug]/page.tsx"),
|
|
367
|
-
`
|
|
277
|
+
const forceSSGForEmailPreviews = async (emailsDirPath, builtPreviewAppPath) => {
|
|
278
|
+
const emailDirectoryMetadata = await getEmailsDirectoryMetadata(emailsDirPath);
|
|
279
|
+
const parameters = getEmailSlugsFromEmailDirectory(emailDirectoryMetadata, emailsDirPath).map((slug) => ({ slug }));
|
|
280
|
+
const removeForceDynamic = async (filePath) => {
|
|
281
|
+
const contents = await fs.promises.readFile(filePath, "utf8");
|
|
282
|
+
await fs.promises.writeFile(filePath, contents.replace("export const dynamic = 'force-dynamic';", ""), "utf8");
|
|
283
|
+
};
|
|
284
|
+
await removeForceDynamic(path.resolve(builtPreviewAppPath, "./src/app/layout.tsx"));
|
|
285
|
+
await removeForceDynamic(path.resolve(builtPreviewAppPath, "./src/app/preview/[...slug]/page.tsx"));
|
|
286
|
+
await fs.promises.appendFile(path.resolve(builtPreviewAppPath, "./src/app/preview/[...slug]/page.tsx"), `
|
|
368
287
|
|
|
369
288
|
export function generateStaticParams() {
|
|
370
289
|
return Promise.resolve(
|
|
371
290
|
${JSON.stringify(parameters)}
|
|
372
291
|
);
|
|
373
|
-
}`,
|
|
374
|
-
"utf8"
|
|
375
|
-
);
|
|
292
|
+
}`, "utf8");
|
|
376
293
|
};
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
delete packageJson.scripts.prepare;
|
|
389
|
-
await fs2.promises.writeFile(
|
|
390
|
-
packageJsonPath,
|
|
391
|
-
JSON.stringify(packageJson),
|
|
392
|
-
"utf8"
|
|
393
|
-
);
|
|
294
|
+
const updatePackageJson = async (builtPreviewAppPath) => {
|
|
295
|
+
const packageJsonPath = path.resolve(builtPreviewAppPath, "./package.json");
|
|
296
|
+
const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf8"));
|
|
297
|
+
packageJson.scripts.build = "next build";
|
|
298
|
+
packageJson.scripts.start = "next start";
|
|
299
|
+
delete packageJson.scripts.postbuild;
|
|
300
|
+
packageJson.name = "preview-server";
|
|
301
|
+
delete packageJson.devDependencies["@react-email/render"];
|
|
302
|
+
delete packageJson.devDependencies["@react-email/components"];
|
|
303
|
+
delete packageJson.scripts.prepare;
|
|
304
|
+
await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageJson), "utf8");
|
|
394
305
|
};
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
spinner.text = "Updating package.json's build and start scripts";
|
|
442
|
-
await updatePackageJson(builtPreviewAppPath);
|
|
443
|
-
spinner.text = "Installing dependencies on `.react-email`";
|
|
444
|
-
await npmInstall(builtPreviewAppPath, packageManager);
|
|
445
|
-
spinner.stopAndPersist({
|
|
446
|
-
text: "Successfully prepared `.react-email` for `next build`",
|
|
447
|
-
symbol: logSymbols2.success
|
|
448
|
-
});
|
|
449
|
-
await buildPreviewApp(builtPreviewAppPath);
|
|
450
|
-
} catch (error) {
|
|
451
|
-
console.log(error);
|
|
452
|
-
process.exit(1);
|
|
453
|
-
}
|
|
306
|
+
const build$1 = async ({ dir: emailsDirRelativePath, packageManager }) => {
|
|
307
|
+
try {
|
|
308
|
+
const previewServerLocation = await getPreviewServerLocation();
|
|
309
|
+
const spinner = ora({
|
|
310
|
+
text: "Starting build process...",
|
|
311
|
+
prefixText: " "
|
|
312
|
+
}).start();
|
|
313
|
+
registerSpinnerAutostopping(spinner);
|
|
314
|
+
spinner.text = `Checking if ${emailsDirRelativePath} folder exists`;
|
|
315
|
+
if (!fs.existsSync(emailsDirRelativePath)) process.exit(1);
|
|
316
|
+
const emailsDirPath = path.join(process.cwd(), emailsDirRelativePath);
|
|
317
|
+
const staticPath = path.join(emailsDirPath, "static");
|
|
318
|
+
const builtPreviewAppPath = path.join(process.cwd(), ".react-email");
|
|
319
|
+
if (fs.existsSync(builtPreviewAppPath)) {
|
|
320
|
+
spinner.text = "Deleting pre-existing `.react-email` folder";
|
|
321
|
+
await fs.promises.rm(builtPreviewAppPath, { recursive: true });
|
|
322
|
+
}
|
|
323
|
+
spinner.text = "Copying preview app from CLI to `.react-email`";
|
|
324
|
+
await fs.promises.cp(previewServerLocation, builtPreviewAppPath, {
|
|
325
|
+
recursive: true,
|
|
326
|
+
filter: (source) => {
|
|
327
|
+
return !/(\/|\\)cli(\/|\\)?/.test(source) && !/(\/|\\)\.next(\/|\\)?/.test(source) && !/(\/|\\)\.turbo(\/|\\)?/.test(source) && !/(\/|\\)node_modules(\/|\\)?$/.test(source);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
if (fs.existsSync(staticPath)) {
|
|
331
|
+
spinner.text = "Copying `static` folder into `.react-email/public/static`";
|
|
332
|
+
const builtStaticDirectory = path.resolve(builtPreviewAppPath, "./public/static");
|
|
333
|
+
await fs.promises.cp(staticPath, builtStaticDirectory, { recursive: true });
|
|
334
|
+
}
|
|
335
|
+
spinner.text = "Setting Next environment variables for preview app to work properly";
|
|
336
|
+
await setNextEnvironmentVariablesForBuild(emailsDirRelativePath, builtPreviewAppPath);
|
|
337
|
+
spinner.text = "Setting server side generation for the email preview pages";
|
|
338
|
+
await forceSSGForEmailPreviews(emailsDirPath, builtPreviewAppPath);
|
|
339
|
+
spinner.text = "Updating package.json's build and start scripts";
|
|
340
|
+
await updatePackageJson(builtPreviewAppPath);
|
|
341
|
+
spinner.text = "Installing dependencies on `.react-email`";
|
|
342
|
+
await npmInstall(builtPreviewAppPath, packageManager);
|
|
343
|
+
spinner.stopAndPersist({
|
|
344
|
+
text: "Successfully prepared `.react-email` for `next build`",
|
|
345
|
+
symbol: logSymbols.success
|
|
346
|
+
});
|
|
347
|
+
await buildPreviewApp(builtPreviewAppPath);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.log(error);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
454
352
|
};
|
|
455
353
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
);
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
importedPaths.push(node.source.value);
|
|
495
|
-
}
|
|
496
|
-
},
|
|
497
|
-
TSExternalModuleReference({ node }) {
|
|
498
|
-
importedPaths.push(node.expression.value);
|
|
499
|
-
},
|
|
500
|
-
CallExpression({ node }) {
|
|
501
|
-
if ("name" in node.callee && node.callee.name === "require") {
|
|
502
|
-
if (node.arguments.length === 1) {
|
|
503
|
-
const importPathNode = node.arguments[0];
|
|
504
|
-
if (importPathNode.type === "StringLiteral") {
|
|
505
|
-
importedPaths.push(importPathNode.value);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
-
return importedPaths;
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/utils/preview/hot-reloading/get-imported-modules.ts
|
|
356
|
+
const traverse = typeof traverseModule === "function" ? traverseModule : traverseModule.default;
|
|
357
|
+
const getImportedModules = (contents) => {
|
|
358
|
+
const importedPaths = [];
|
|
359
|
+
const parsedContents = parse(contents, {
|
|
360
|
+
sourceType: "unambiguous",
|
|
361
|
+
strictMode: false,
|
|
362
|
+
errorRecovery: true,
|
|
363
|
+
plugins: [
|
|
364
|
+
"jsx",
|
|
365
|
+
"typescript",
|
|
366
|
+
"decorators"
|
|
367
|
+
]
|
|
368
|
+
});
|
|
369
|
+
traverse(parsedContents, {
|
|
370
|
+
ImportDeclaration({ node }) {
|
|
371
|
+
importedPaths.push(node.source.value);
|
|
372
|
+
},
|
|
373
|
+
ExportAllDeclaration({ node }) {
|
|
374
|
+
importedPaths.push(node.source.value);
|
|
375
|
+
},
|
|
376
|
+
ExportNamedDeclaration({ node }) {
|
|
377
|
+
if (node.source) importedPaths.push(node.source.value);
|
|
378
|
+
},
|
|
379
|
+
TSExternalModuleReference({ node }) {
|
|
380
|
+
importedPaths.push(node.expression.value);
|
|
381
|
+
},
|
|
382
|
+
CallExpression({ node }) {
|
|
383
|
+
if ("name" in node.callee && node.callee.name === "require") {
|
|
384
|
+
if (node.arguments.length === 1) {
|
|
385
|
+
const importPathNode = node.arguments[0];
|
|
386
|
+
if (importPathNode.type === "StringLiteral") importedPaths.push(importPathNode.value);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
return importedPaths;
|
|
512
392
|
};
|
|
513
393
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return `./${path4.relative(projectPath, unaliasedPath)}`;
|
|
535
|
-
}
|
|
536
|
-
return importedPath;
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
return importPaths;
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/utils/preview/hot-reloading/resolve-path-aliases.ts
|
|
396
|
+
const resolvePathAliases = (importPaths, projectPath) => {
|
|
397
|
+
const configLoadResult = loadConfig(projectPath);
|
|
398
|
+
if (configLoadResult.resultType === "success") {
|
|
399
|
+
const matchPath = createMatchPath(configLoadResult.absoluteBaseUrl, configLoadResult.paths);
|
|
400
|
+
return importPaths.map((importedPath) => {
|
|
401
|
+
const unaliasedPath = matchPath(importedPath, void 0, void 0, [
|
|
402
|
+
".tsx",
|
|
403
|
+
".ts",
|
|
404
|
+
".js",
|
|
405
|
+
".jsx",
|
|
406
|
+
".cjs",
|
|
407
|
+
".mjs"
|
|
408
|
+
]);
|
|
409
|
+
if (unaliasedPath) return `./${path.relative(projectPath, unaliasedPath)}`;
|
|
410
|
+
return importedPath;
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
return importPaths;
|
|
540
414
|
};
|
|
541
415
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
allFilePaths.push(pathToDirent);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
return allFilePaths;
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/utils/preview/hot-reloading/create-dependency-graph.ts
|
|
418
|
+
const readAllFilesInsideDirectory = async (directory) => {
|
|
419
|
+
let allFilePaths = [];
|
|
420
|
+
const topLevelDirents = await promises.readdir(directory, { withFileTypes: true });
|
|
421
|
+
for await (const dirent of topLevelDirents) {
|
|
422
|
+
const pathToDirent = path.join(directory, dirent.name);
|
|
423
|
+
if (dirent.isDirectory()) allFilePaths = allFilePaths.concat(await readAllFilesInsideDirectory(pathToDirent));
|
|
424
|
+
else allFilePaths.push(pathToDirent);
|
|
425
|
+
}
|
|
426
|
+
return allFilePaths;
|
|
557
427
|
};
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
428
|
+
const javascriptExtensions = [
|
|
429
|
+
".js",
|
|
430
|
+
".ts",
|
|
431
|
+
".jsx",
|
|
432
|
+
".tsx",
|
|
433
|
+
".mjs",
|
|
434
|
+
".cjs"
|
|
435
|
+
];
|
|
436
|
+
const isJavascriptModule = (filePath) => {
|
|
437
|
+
const extensionName = path.extname(filePath);
|
|
438
|
+
return javascriptExtensions.includes(extensionName);
|
|
562
439
|
};
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (existsSync(`${pathWithoutExtension}.js`)) {
|
|
571
|
-
return `${pathWithoutExtension}.js`;
|
|
572
|
-
}
|
|
573
|
-
if (existsSync(`${pathWithoutExtension}.jsx`)) {
|
|
574
|
-
return `${pathWithoutExtension}.jsx`;
|
|
575
|
-
}
|
|
576
|
-
if (existsSync(`${pathWithoutExtension}.mjs`)) {
|
|
577
|
-
return `${pathWithoutExtension}.mjs`;
|
|
578
|
-
}
|
|
579
|
-
if (existsSync(`${pathWithoutExtension}.cjs`)) {
|
|
580
|
-
return `${pathWithoutExtension}.cjs`;
|
|
581
|
-
}
|
|
440
|
+
const checkFileExtensionsUntilItExists = (pathWithoutExtension) => {
|
|
441
|
+
if (existsSync(`${pathWithoutExtension}.ts`)) return `${pathWithoutExtension}.ts`;
|
|
442
|
+
if (existsSync(`${pathWithoutExtension}.tsx`)) return `${pathWithoutExtension}.tsx`;
|
|
443
|
+
if (existsSync(`${pathWithoutExtension}.js`)) return `${pathWithoutExtension}.js`;
|
|
444
|
+
if (existsSync(`${pathWithoutExtension}.jsx`)) return `${pathWithoutExtension}.jsx`;
|
|
445
|
+
if (existsSync(`${pathWithoutExtension}.mjs`)) return `${pathWithoutExtension}.mjs`;
|
|
446
|
+
if (existsSync(`${pathWithoutExtension}.cjs`)) return `${pathWithoutExtension}.cjs`;
|
|
582
447
|
};
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
dependencyModule.dependentPaths.push(moduleFilePath);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
};
|
|
706
|
-
for (const filePath of modulePaths) {
|
|
707
|
-
await updateModuleDependenciesInGraph(filePath);
|
|
708
|
-
}
|
|
709
|
-
const removeModuleFromGraph = (filePath) => {
|
|
710
|
-
const module = graph[filePath];
|
|
711
|
-
if (module) {
|
|
712
|
-
for (const dependencyPath of module.dependencyPaths) {
|
|
713
|
-
if (graph[dependencyPath]) {
|
|
714
|
-
graph[dependencyPath].dependentPaths = graph[dependencyPath].dependentPaths.filter(
|
|
715
|
-
(dependentPath) => dependentPath !== filePath
|
|
716
|
-
);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
delete graph[filePath];
|
|
720
|
-
}
|
|
721
|
-
};
|
|
722
|
-
return [
|
|
723
|
-
graph,
|
|
724
|
-
async (event, pathToModified) => {
|
|
725
|
-
switch (event) {
|
|
726
|
-
case "change":
|
|
727
|
-
if (isJavascriptModule(pathToModified)) {
|
|
728
|
-
await updateModuleDependenciesInGraph(pathToModified);
|
|
729
|
-
}
|
|
730
|
-
break;
|
|
731
|
-
case "add":
|
|
732
|
-
if (isJavascriptModule(pathToModified)) {
|
|
733
|
-
await updateModuleDependenciesInGraph(pathToModified);
|
|
734
|
-
}
|
|
735
|
-
break;
|
|
736
|
-
case "addDir": {
|
|
737
|
-
const filesInsideAddedDirectory = await readAllFilesInsideDirectory(pathToModified);
|
|
738
|
-
const modulesInsideAddedDirectory = filesInsideAddedDirectory.filter(isJavascriptModule);
|
|
739
|
-
for await (const filePath of modulesInsideAddedDirectory) {
|
|
740
|
-
await updateModuleDependenciesInGraph(filePath);
|
|
741
|
-
}
|
|
742
|
-
break;
|
|
743
|
-
}
|
|
744
|
-
case "unlink":
|
|
745
|
-
if (isJavascriptModule(pathToModified)) {
|
|
746
|
-
removeModuleFromGraph(pathToModified);
|
|
747
|
-
}
|
|
748
|
-
break;
|
|
749
|
-
case "unlinkDir": {
|
|
750
|
-
const filesInsideDeletedDirectory = await readAllFilesInsideDirectory(pathToModified);
|
|
751
|
-
const modulesInsideDeletedDirectory = filesInsideDeletedDirectory.filter(isJavascriptModule);
|
|
752
|
-
for await (const filePath of modulesInsideDeletedDirectory) {
|
|
753
|
-
removeModuleFromGraph(filePath);
|
|
754
|
-
}
|
|
755
|
-
break;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
/**
|
|
761
|
-
* Resolves all modules that depend on the specified module, directly or indirectly.
|
|
762
|
-
*
|
|
763
|
-
* @param pathToModule - The path to the module whose dependents we want to find
|
|
764
|
-
* @returns An array of paths to all modules that depend on the specified module
|
|
765
|
-
*/
|
|
766
|
-
resolveDependentsOf: function resolveDependentsOf(pathToModule) {
|
|
767
|
-
const dependentPaths = /* @__PURE__ */ new Set();
|
|
768
|
-
const stack = [pathToModule];
|
|
769
|
-
while (stack.length > 0) {
|
|
770
|
-
const currentPath = stack.pop();
|
|
771
|
-
const moduleEntry = graph[currentPath];
|
|
772
|
-
if (!moduleEntry) continue;
|
|
773
|
-
for (const dependentPath of moduleEntry.dependentPaths) {
|
|
774
|
-
if (dependentPaths.has(dependentPath) || dependentPath === pathToModule)
|
|
775
|
-
continue;
|
|
776
|
-
dependentPaths.add(dependentPath);
|
|
777
|
-
stack.push(dependentPath);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
return [...dependentPaths.values()];
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
];
|
|
448
|
+
/**
|
|
449
|
+
* Creates a stateful dependency graph that is structured in a way that you can get
|
|
450
|
+
* the dependents of a module from its path.
|
|
451
|
+
*
|
|
452
|
+
* Stateful in the sense that it provides a `getter` and an "`updater`". The updater
|
|
453
|
+
* will receive changes to the files, that can be perceived through some file watching mechanism,
|
|
454
|
+
* so that it doesn't need to recompute the entire dependency graph but only the parts changed.
|
|
455
|
+
*/
|
|
456
|
+
const createDependencyGraph = async (directory) => {
|
|
457
|
+
const modulePaths = (await readAllFilesInsideDirectory(directory)).filter(isJavascriptModule);
|
|
458
|
+
const graph = Object.fromEntries(modulePaths.map((path$1) => [path$1, {
|
|
459
|
+
path: path$1,
|
|
460
|
+
dependencyPaths: [],
|
|
461
|
+
dependentPaths: [],
|
|
462
|
+
moduleDependencies: []
|
|
463
|
+
}]));
|
|
464
|
+
const getDependencyPaths = async (filePath) => {
|
|
465
|
+
const contents = await promises.readFile(filePath, "utf8");
|
|
466
|
+
const importedPathsRelativeToDirectory = (isJavascriptModule(filePath) ? resolvePathAliases(getImportedModules(contents), path.dirname(filePath)) : []).map((dependencyPath) => {
|
|
467
|
+
if (!dependencyPath.startsWith(".") || path.isAbsolute(dependencyPath)) return dependencyPath;
|
|
468
|
+
let pathToDependencyFromDirectory = path.resolve(path.dirname(filePath), dependencyPath);
|
|
469
|
+
let isDirectory = false;
|
|
470
|
+
try {
|
|
471
|
+
isDirectory = statSync(pathToDependencyFromDirectory).isDirectory();
|
|
472
|
+
} catch (_) {}
|
|
473
|
+
if (isDirectory) {
|
|
474
|
+
const pathWithExtension = checkFileExtensionsUntilItExists(`${pathToDependencyFromDirectory}/index`);
|
|
475
|
+
if (pathWithExtension) pathToDependencyFromDirectory = pathWithExtension;
|
|
476
|
+
else console.warn(`Could not find index file for directory at ${pathToDependencyFromDirectory}. This is probably going to cause issues with both hot reloading and your code.`);
|
|
477
|
+
}
|
|
478
|
+
const extension = path.extname(pathToDependencyFromDirectory);
|
|
479
|
+
const pathWithEnsuredExtension = (() => {
|
|
480
|
+
if (extension.length > 0 && javascriptExtensions.includes(extension)) {
|
|
481
|
+
if (existsSync(pathToDependencyFromDirectory)) return pathToDependencyFromDirectory;
|
|
482
|
+
return checkFileExtensionsUntilItExists(pathToDependencyFromDirectory.replace(extension, ""));
|
|
483
|
+
}
|
|
484
|
+
return checkFileExtensionsUntilItExists(pathToDependencyFromDirectory);
|
|
485
|
+
})();
|
|
486
|
+
if (pathWithEnsuredExtension) pathToDependencyFromDirectory = pathWithEnsuredExtension;
|
|
487
|
+
else console.warn(`Could not find file at ${pathToDependencyFromDirectory}`);
|
|
488
|
+
return pathToDependencyFromDirectory;
|
|
489
|
+
});
|
|
490
|
+
const moduleDependencies = importedPathsRelativeToDirectory.filter((dependencyPath) => !dependencyPath.startsWith(".") && !path.isAbsolute(dependencyPath));
|
|
491
|
+
return {
|
|
492
|
+
dependencyPaths: importedPathsRelativeToDirectory.filter((dependencyPath) => dependencyPath.startsWith(".") || path.isAbsolute(dependencyPath)),
|
|
493
|
+
moduleDependencies
|
|
494
|
+
};
|
|
495
|
+
};
|
|
496
|
+
const updateModuleDependenciesInGraph = async (moduleFilePath) => {
|
|
497
|
+
if (graph[moduleFilePath] === void 0) graph[moduleFilePath] = {
|
|
498
|
+
path: moduleFilePath,
|
|
499
|
+
dependencyPaths: [],
|
|
500
|
+
dependentPaths: [],
|
|
501
|
+
moduleDependencies: []
|
|
502
|
+
};
|
|
503
|
+
const { moduleDependencies, dependencyPaths: newDependencyPaths } = await getDependencyPaths(moduleFilePath);
|
|
504
|
+
graph[moduleFilePath].moduleDependencies = moduleDependencies;
|
|
505
|
+
for (const dependencyPath of graph[moduleFilePath].dependencyPaths) {
|
|
506
|
+
if (newDependencyPaths.includes(dependencyPath)) continue;
|
|
507
|
+
const dependencyModule = graph[dependencyPath];
|
|
508
|
+
if (dependencyModule !== void 0) dependencyModule.dependentPaths = dependencyModule.dependentPaths.filter((dependentPath) => dependentPath !== moduleFilePath);
|
|
509
|
+
}
|
|
510
|
+
graph[moduleFilePath].dependencyPaths = newDependencyPaths;
|
|
511
|
+
for await (const dependencyPath of newDependencyPaths) {
|
|
512
|
+
if (graph[dependencyPath] === void 0) await updateModuleDependenciesInGraph(dependencyPath);
|
|
513
|
+
const dependencyModule = graph[dependencyPath];
|
|
514
|
+
if (dependencyModule === void 0) throw new Error(`Loading the dependency path ${dependencyPath} did not initialize it at all. This is a bug in React Email.`);
|
|
515
|
+
if (!dependencyModule.dependentPaths.includes(moduleFilePath)) dependencyModule.dependentPaths.push(moduleFilePath);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
for (const filePath of modulePaths) await updateModuleDependenciesInGraph(filePath);
|
|
519
|
+
const removeModuleFromGraph = (filePath) => {
|
|
520
|
+
const module = graph[filePath];
|
|
521
|
+
if (module) {
|
|
522
|
+
for (const dependencyPath of module.dependencyPaths) if (graph[dependencyPath]) graph[dependencyPath].dependentPaths = graph[dependencyPath].dependentPaths.filter((dependentPath) => dependentPath !== filePath);
|
|
523
|
+
delete graph[filePath];
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
return [
|
|
527
|
+
graph,
|
|
528
|
+
async (event, pathToModified) => {
|
|
529
|
+
switch (event) {
|
|
530
|
+
case "change":
|
|
531
|
+
if (isJavascriptModule(pathToModified)) await updateModuleDependenciesInGraph(pathToModified);
|
|
532
|
+
break;
|
|
533
|
+
case "add":
|
|
534
|
+
if (isJavascriptModule(pathToModified)) await updateModuleDependenciesInGraph(pathToModified);
|
|
535
|
+
break;
|
|
536
|
+
case "addDir": {
|
|
537
|
+
const modulesInsideAddedDirectory = (await readAllFilesInsideDirectory(pathToModified)).filter(isJavascriptModule);
|
|
538
|
+
for await (const filePath of modulesInsideAddedDirectory) await updateModuleDependenciesInGraph(filePath);
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
case "unlink":
|
|
542
|
+
if (isJavascriptModule(pathToModified)) removeModuleFromGraph(pathToModified);
|
|
543
|
+
break;
|
|
544
|
+
case "unlinkDir": {
|
|
545
|
+
const modulesInsideDeletedDirectory = (await readAllFilesInsideDirectory(pathToModified)).filter(isJavascriptModule);
|
|
546
|
+
for await (const filePath of modulesInsideDeletedDirectory) removeModuleFromGraph(filePath);
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
{ resolveDependentsOf: function resolveDependentsOf(pathToModule) {
|
|
552
|
+
const dependentPaths = /* @__PURE__ */ new Set();
|
|
553
|
+
const stack = [pathToModule];
|
|
554
|
+
while (stack.length > 0) {
|
|
555
|
+
const currentPath = stack.pop();
|
|
556
|
+
const moduleEntry = graph[currentPath];
|
|
557
|
+
if (!moduleEntry) continue;
|
|
558
|
+
for (const dependentPath of moduleEntry.dependentPaths) {
|
|
559
|
+
if (dependentPaths.has(dependentPath) || dependentPath === pathToModule) continue;
|
|
560
|
+
dependentPaths.add(dependentPath);
|
|
561
|
+
stack.push(dependentPath);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return [...dependentPaths.values()];
|
|
565
|
+
} }
|
|
566
|
+
];
|
|
784
567
|
};
|
|
785
568
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}
|
|
837
|
-
const pathToChangeTarget = path6.resolve(
|
|
838
|
-
absolutePathToEmailsDirectory,
|
|
839
|
-
relativePathToChangeTarget
|
|
840
|
-
);
|
|
841
|
-
await updateDependencyGraph(event, pathToChangeTarget);
|
|
842
|
-
const newFilesOutsideEmailsDirectory = getFilesOutsideEmailsDirectory();
|
|
843
|
-
for (const p of filesOutsideEmailsDirectory) {
|
|
844
|
-
if (!newFilesOutsideEmailsDirectory.includes(p)) {
|
|
845
|
-
watcher.unwatch(p);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
for (const p of newFilesOutsideEmailsDirectory) {
|
|
849
|
-
if (!filesOutsideEmailsDirectory.includes(p)) {
|
|
850
|
-
watcher.add(p);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
filesOutsideEmailsDirectory = newFilesOutsideEmailsDirectory;
|
|
854
|
-
changes.push({
|
|
855
|
-
event,
|
|
856
|
-
filename: relativePathToChangeTarget
|
|
857
|
-
});
|
|
858
|
-
for (const dependentPath of resolveDependentsOf(pathToChangeTarget)) {
|
|
859
|
-
changes.push({
|
|
860
|
-
event: "change",
|
|
861
|
-
filename: path6.relative(absolutePathToEmailsDirectory, dependentPath)
|
|
862
|
-
});
|
|
863
|
-
}
|
|
864
|
-
reload();
|
|
865
|
-
});
|
|
866
|
-
return watcher;
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region src/utils/preview/hot-reloading/setup-hot-reloading.ts
|
|
571
|
+
const setupHotreloading = async (devServer$1, emailDirRelativePath) => {
|
|
572
|
+
let clients = [];
|
|
573
|
+
new Server(devServer$1).on("connection", (client) => {
|
|
574
|
+
clients.push(client);
|
|
575
|
+
client.on("disconnect", () => {
|
|
576
|
+
clients = clients.filter((item) => item !== client);
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
let changes = [];
|
|
580
|
+
const reload = debounce(() => {
|
|
581
|
+
clients.forEach((client) => {
|
|
582
|
+
client.emit("reload", changes.filter((change) => path.resolve(absolutePathToEmailsDirectory, change.filename).startsWith(absolutePathToEmailsDirectory)));
|
|
583
|
+
});
|
|
584
|
+
changes = [];
|
|
585
|
+
}, 150);
|
|
586
|
+
const absolutePathToEmailsDirectory = path.resolve(process.cwd(), emailDirRelativePath);
|
|
587
|
+
const [dependencyGraph, updateDependencyGraph, { resolveDependentsOf }] = await createDependencyGraph(absolutePathToEmailsDirectory);
|
|
588
|
+
const watcher = watch("", {
|
|
589
|
+
ignoreInitial: true,
|
|
590
|
+
cwd: absolutePathToEmailsDirectory
|
|
591
|
+
});
|
|
592
|
+
const getFilesOutsideEmailsDirectory = () => Object.keys(dependencyGraph).filter((p) => path.relative(absolutePathToEmailsDirectory, p).startsWith(".."));
|
|
593
|
+
let filesOutsideEmailsDirectory = getFilesOutsideEmailsDirectory();
|
|
594
|
+
for (const p of filesOutsideEmailsDirectory) watcher.add(p);
|
|
595
|
+
const exit = async () => {
|
|
596
|
+
await watcher.close();
|
|
597
|
+
};
|
|
598
|
+
process.on("SIGINT", exit);
|
|
599
|
+
process.on("uncaughtException", exit);
|
|
600
|
+
watcher.on("all", async (event, relativePathToChangeTarget) => {
|
|
601
|
+
if (relativePathToChangeTarget.split(path.sep).length === 0) return;
|
|
602
|
+
const pathToChangeTarget = path.resolve(absolutePathToEmailsDirectory, relativePathToChangeTarget);
|
|
603
|
+
await updateDependencyGraph(event, pathToChangeTarget);
|
|
604
|
+
const newFilesOutsideEmailsDirectory = getFilesOutsideEmailsDirectory();
|
|
605
|
+
for (const p of filesOutsideEmailsDirectory) if (!newFilesOutsideEmailsDirectory.includes(p)) watcher.unwatch(p);
|
|
606
|
+
for (const p of newFilesOutsideEmailsDirectory) if (!filesOutsideEmailsDirectory.includes(p)) watcher.add(p);
|
|
607
|
+
filesOutsideEmailsDirectory = newFilesOutsideEmailsDirectory;
|
|
608
|
+
changes.push({
|
|
609
|
+
event,
|
|
610
|
+
filename: relativePathToChangeTarget
|
|
611
|
+
});
|
|
612
|
+
for (const dependentPath of resolveDependentsOf(pathToChangeTarget)) changes.push({
|
|
613
|
+
event: "change",
|
|
614
|
+
filename: path.relative(absolutePathToEmailsDirectory, dependentPath)
|
|
615
|
+
});
|
|
616
|
+
reload();
|
|
617
|
+
});
|
|
618
|
+
return watcher;
|
|
867
619
|
};
|
|
868
620
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
// src/utils/preview/get-env-variables-for-preview-app.ts
|
|
879
|
-
import path7 from "node:path";
|
|
880
|
-
var getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, previewServerLocation, cwd) => {
|
|
881
|
-
return {
|
|
882
|
-
EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
|
|
883
|
-
EMAILS_DIR_ABSOLUTE_PATH: path7.resolve(cwd, relativePathToEmailsDirectory),
|
|
884
|
-
PREVIEW_SERVER_LOCATION: previewServerLocation,
|
|
885
|
-
USER_PROJECT_LOCATION: cwd
|
|
886
|
-
};
|
|
621
|
+
//#endregion
|
|
622
|
+
//#region src/utils/preview/get-env-variables-for-preview-app.ts
|
|
623
|
+
const getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, previewServerLocation, cwd) => {
|
|
624
|
+
return {
|
|
625
|
+
EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
|
|
626
|
+
EMAILS_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEmailsDirectory),
|
|
627
|
+
PREVIEW_SERVER_LOCATION: previewServerLocation,
|
|
628
|
+
USER_PROJECT_LOCATION: cwd
|
|
629
|
+
};
|
|
887
630
|
};
|
|
888
631
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
exception
|
|
919
|
-
);
|
|
920
|
-
res.statusCode = 500;
|
|
921
|
-
res.end(
|
|
922
|
-
"Could not read file to be served! Check your terminal for more information."
|
|
923
|
-
);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
632
|
+
//#endregion
|
|
633
|
+
//#region src/utils/preview/serve-static-file.ts
|
|
634
|
+
const serveStaticFile = async (res, parsedUrl, staticDirRelativePath) => {
|
|
635
|
+
const pathname = parsedUrl.pathname.replace("/static", "./static");
|
|
636
|
+
const ext = path.parse(pathname).ext;
|
|
637
|
+
const staticBaseDir = path.resolve(process.cwd(), staticDirRelativePath);
|
|
638
|
+
const fileAbsolutePath = path.resolve(staticBaseDir, pathname);
|
|
639
|
+
if (!fileAbsolutePath.startsWith(staticBaseDir)) {
|
|
640
|
+
res.statusCode = 403;
|
|
641
|
+
res.end();
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
try {
|
|
645
|
+
const fileHandle = await promises.open(fileAbsolutePath, "r");
|
|
646
|
+
const fileData = await promises.readFile(fileHandle);
|
|
647
|
+
res.setHeader("Content-type", lookup(ext) || "text/plain");
|
|
648
|
+
res.end(fileData);
|
|
649
|
+
fileHandle.close();
|
|
650
|
+
} catch (exception) {
|
|
651
|
+
if (!existsSync(fileAbsolutePath)) {
|
|
652
|
+
res.statusCode = 404;
|
|
653
|
+
res.end();
|
|
654
|
+
} else {
|
|
655
|
+
const sanitizedFilePath = fileAbsolutePath.replace(/\n|\r/g, "");
|
|
656
|
+
console.error(`Could not read file at %s to be served, here's the exception:`, sanitizedFilePath, exception);
|
|
657
|
+
res.statusCode = 500;
|
|
658
|
+
res.end("Could not read file to be served! Check your terminal for more information.");
|
|
659
|
+
}
|
|
660
|
+
}
|
|
926
661
|
};
|
|
927
662
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
});
|
|
663
|
+
//#endregion
|
|
664
|
+
//#region src/utils/preview/start-dev-server.ts
|
|
665
|
+
let devServer;
|
|
666
|
+
const safeAsyncServerListen = (server, port) => {
|
|
667
|
+
return new Promise((resolve) => {
|
|
668
|
+
server.listen(port, () => {
|
|
669
|
+
resolve({ portAlreadyInUse: false });
|
|
670
|
+
});
|
|
671
|
+
server.on("error", (e) => {
|
|
672
|
+
if (e.code === "EADDRINUSE") resolve({ portAlreadyInUse: true });
|
|
673
|
+
});
|
|
674
|
+
});
|
|
941
675
|
};
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
"next",
|
|
1026
|
-
{
|
|
1027
|
-
default: true
|
|
1028
|
-
}
|
|
1029
|
-
);
|
|
1030
|
-
const app = next({
|
|
1031
|
-
// passing in env here does not get the environment variables there
|
|
1032
|
-
dev: false,
|
|
1033
|
-
conf: {
|
|
1034
|
-
images: {
|
|
1035
|
-
// This is to avoid the warning with sharp
|
|
1036
|
-
unoptimized: true
|
|
1037
|
-
}
|
|
1038
|
-
},
|
|
1039
|
-
hostname: "localhost",
|
|
1040
|
-
port,
|
|
1041
|
-
dir: previewServerLocation
|
|
1042
|
-
});
|
|
1043
|
-
let isNextReady = false;
|
|
1044
|
-
const nextReadyPromise = app.prepare();
|
|
1045
|
-
try {
|
|
1046
|
-
await nextReadyPromise;
|
|
1047
|
-
} catch (exception) {
|
|
1048
|
-
spinner.stopAndPersist({
|
|
1049
|
-
symbol: logSymbols3.error,
|
|
1050
|
-
text: ` Preview Server had an error: ${exception}`
|
|
1051
|
-
});
|
|
1052
|
-
process.exit(1);
|
|
1053
|
-
}
|
|
1054
|
-
isNextReady = true;
|
|
1055
|
-
const nextHandleRequest = app.getRequestHandler();
|
|
1056
|
-
const secondsToNextReady = ((performance.now() - timeBeforeNextReady) / 1e3).toFixed(1);
|
|
1057
|
-
spinner.stopAndPersist({
|
|
1058
|
-
text: `Ready in ${secondsToNextReady}s
|
|
1059
|
-
`,
|
|
1060
|
-
symbol: logSymbols3.success
|
|
1061
|
-
});
|
|
1062
|
-
return devServer;
|
|
676
|
+
const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port) => {
|
|
677
|
+
const [majorNodeVersion] = process.versions.node.split(".");
|
|
678
|
+
if (majorNodeVersion && Number.parseInt(majorNodeVersion) < 18) {
|
|
679
|
+
console.error(` ${logSymbols.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 18 or higher.`);
|
|
680
|
+
process.exit(1);
|
|
681
|
+
}
|
|
682
|
+
const previewServerLocation = await getPreviewServerLocation();
|
|
683
|
+
const previewServer = createJiti(previewServerLocation);
|
|
684
|
+
devServer = http.createServer((req, res) => {
|
|
685
|
+
if (!req.url) {
|
|
686
|
+
res.end(404);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const parsedUrl = url.parse(req.url, true);
|
|
690
|
+
res.setHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store");
|
|
691
|
+
res.setHeader("Pragma", "no-cache");
|
|
692
|
+
res.setHeader("Expires", "-1");
|
|
693
|
+
try {
|
|
694
|
+
if (parsedUrl.path?.includes("static/") && !parsedUrl.path.includes("_next/static/")) serveStaticFile(res, parsedUrl, staticBaseDirRelativePath);
|
|
695
|
+
else if (!isNextReady) nextReadyPromise.then(() => nextHandleRequest?.(req, res, parsedUrl));
|
|
696
|
+
else nextHandleRequest?.(req, res, parsedUrl);
|
|
697
|
+
} catch (e) {
|
|
698
|
+
console.error("caught error", e);
|
|
699
|
+
res.writeHead(500);
|
|
700
|
+
res.end();
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
const { portAlreadyInUse } = await safeAsyncServerListen(devServer, port);
|
|
704
|
+
if (!portAlreadyInUse) {
|
|
705
|
+
console.log(styleText("greenBright", ` React Email ${package_default.version}`));
|
|
706
|
+
console.log(` Running preview at: http://localhost:${port}\n`);
|
|
707
|
+
} else {
|
|
708
|
+
const nextPortToTry = port + 1;
|
|
709
|
+
console.warn(` ${logSymbols.warning} Port ${port} is already in use, trying ${nextPortToTry}`);
|
|
710
|
+
return startDevServer(emailsDirRelativePath, staticBaseDirRelativePath, nextPortToTry);
|
|
711
|
+
}
|
|
712
|
+
devServer.on("close", async () => {
|
|
713
|
+
await app.close();
|
|
714
|
+
});
|
|
715
|
+
devServer.on("error", (e) => {
|
|
716
|
+
spinner.stopAndPersist({
|
|
717
|
+
symbol: logSymbols.error,
|
|
718
|
+
text: `Preview Server had an error: ${e}`
|
|
719
|
+
});
|
|
720
|
+
process.exit(1);
|
|
721
|
+
});
|
|
722
|
+
const spinner = ora({
|
|
723
|
+
text: "Getting react-email preview server ready...\n",
|
|
724
|
+
prefixText: " "
|
|
725
|
+
}).start();
|
|
726
|
+
registerSpinnerAutostopping(spinner);
|
|
727
|
+
const timeBeforeNextReady = performance.now();
|
|
728
|
+
process.env = {
|
|
729
|
+
NODE_ENV: "development",
|
|
730
|
+
...process.env,
|
|
731
|
+
...getEnvVariablesForPreviewApp(path.normalize(emailsDirRelativePath), previewServerLocation, process.cwd())
|
|
732
|
+
};
|
|
733
|
+
const app = (await previewServer.import("next", { default: true }))({
|
|
734
|
+
dev: false,
|
|
735
|
+
conf: { images: { unoptimized: true } },
|
|
736
|
+
hostname: "localhost",
|
|
737
|
+
port,
|
|
738
|
+
dir: previewServerLocation
|
|
739
|
+
});
|
|
740
|
+
let isNextReady = false;
|
|
741
|
+
const nextReadyPromise = app.prepare();
|
|
742
|
+
try {
|
|
743
|
+
await nextReadyPromise;
|
|
744
|
+
} catch (exception) {
|
|
745
|
+
spinner.stopAndPersist({
|
|
746
|
+
symbol: logSymbols.error,
|
|
747
|
+
text: ` Preview Server had an error: ${exception}`
|
|
748
|
+
});
|
|
749
|
+
process.exit(1);
|
|
750
|
+
}
|
|
751
|
+
isNextReady = true;
|
|
752
|
+
const nextHandleRequest = app.getRequestHandler();
|
|
753
|
+
const secondsToNextReady = ((performance.now() - timeBeforeNextReady) / 1e3).toFixed(1);
|
|
754
|
+
spinner.stopAndPersist({
|
|
755
|
+
text: `Ready in ${secondsToNextReady}s\n`,
|
|
756
|
+
symbol: logSymbols.success
|
|
757
|
+
});
|
|
758
|
+
return devServer;
|
|
1063
759
|
};
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1073
|
-
if (options?.shouldKillProcess) {
|
|
1074
|
-
process.exit(options.killWithErrorCode ? 1 : 0);
|
|
1075
|
-
}
|
|
760
|
+
const makeExitHandler = (options) => (codeSignalOrError) => {
|
|
761
|
+
if (typeof devServer !== "undefined") {
|
|
762
|
+
console.log("\nshutting down dev server");
|
|
763
|
+
devServer.close();
|
|
764
|
+
devServer = void 0;
|
|
765
|
+
}
|
|
766
|
+
if (codeSignalOrError instanceof Error) console.error(codeSignalOrError);
|
|
767
|
+
if (options?.shouldKillProcess) process.exit(options.killWithErrorCode ? 1 : 0);
|
|
1076
768
|
};
|
|
1077
769
|
process.on("exit", makeExitHandler());
|
|
1078
|
-
process.on(
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
);
|
|
1082
|
-
process.on(
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
);
|
|
1086
|
-
process.on(
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
);
|
|
1090
|
-
process.on(
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
);
|
|
770
|
+
process.on("SIGINT", makeExitHandler({
|
|
771
|
+
shouldKillProcess: true,
|
|
772
|
+
killWithErrorCode: false
|
|
773
|
+
}));
|
|
774
|
+
process.on("SIGUSR1", makeExitHandler({
|
|
775
|
+
shouldKillProcess: true,
|
|
776
|
+
killWithErrorCode: false
|
|
777
|
+
}));
|
|
778
|
+
process.on("SIGUSR2", makeExitHandler({
|
|
779
|
+
shouldKillProcess: true,
|
|
780
|
+
killWithErrorCode: false
|
|
781
|
+
}));
|
|
782
|
+
process.on("uncaughtException", makeExitHandler({
|
|
783
|
+
shouldKillProcess: true,
|
|
784
|
+
killWithErrorCode: true
|
|
785
|
+
}));
|
|
1094
786
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
LAST_BRANCH: "\u2514\u2500\u2500 ",
|
|
1104
|
-
VERTICAL: "\u2502 "
|
|
787
|
+
//#endregion
|
|
788
|
+
//#region src/utils/tree.ts
|
|
789
|
+
const SYMBOLS = {
|
|
790
|
+
BRANCH: "├── ",
|
|
791
|
+
EMPTY: "",
|
|
792
|
+
INDENT: " ",
|
|
793
|
+
LAST_BRANCH: "└── ",
|
|
794
|
+
VERTICAL: "│ "
|
|
1105
795
|
};
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
const pathToDirectory = path10.join(dirFullpath, dirent.name);
|
|
1132
|
-
const treeLinesForSubDirectory = await getTreeLines(
|
|
1133
|
-
pathToDirectory,
|
|
1134
|
-
depth,
|
|
1135
|
-
currentDepth + 1
|
|
1136
|
-
);
|
|
1137
|
-
lines = lines.concat(
|
|
1138
|
-
treeLinesForSubDirectory.map(
|
|
1139
|
-
(line, index) => index === 0 ? `${branchingSymbol}${line}` : `${verticalSymbol}${line}`
|
|
1140
|
-
)
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
return lines;
|
|
796
|
+
const getTreeLines = async (dirPath, depth, currentDepth = 0) => {
|
|
797
|
+
const base = process.cwd();
|
|
798
|
+
const dirFullpath = path.resolve(base, dirPath);
|
|
799
|
+
let lines = [path.basename(dirFullpath)];
|
|
800
|
+
if ((await promises.stat(dirFullpath)).isDirectory() && currentDepth < depth) {
|
|
801
|
+
const childDirents = await promises.readdir(dirFullpath, { withFileTypes: true });
|
|
802
|
+
childDirents.sort((a, b) => {
|
|
803
|
+
if (a.isDirectory() && b.isFile()) return -1;
|
|
804
|
+
if (a.isFile() && b.isDirectory()) return 1;
|
|
805
|
+
return b.name > a.name ? -1 : 1;
|
|
806
|
+
});
|
|
807
|
+
for (let i = 0; i < childDirents.length; i++) {
|
|
808
|
+
const dirent = childDirents[i];
|
|
809
|
+
const isLast = i === childDirents.length - 1;
|
|
810
|
+
const branchingSymbol = isLast ? SYMBOLS.LAST_BRANCH : SYMBOLS.BRANCH;
|
|
811
|
+
const verticalSymbol = isLast ? SYMBOLS.INDENT : SYMBOLS.VERTICAL;
|
|
812
|
+
if (dirent.isFile()) lines.push(`${branchingSymbol}${dirent.name}`);
|
|
813
|
+
else {
|
|
814
|
+
const pathToDirectory = path.join(dirFullpath, dirent.name);
|
|
815
|
+
const treeLinesForSubDirectory = await getTreeLines(pathToDirectory, depth, currentDepth + 1);
|
|
816
|
+
lines = lines.concat(treeLinesForSubDirectory.map((line, index) => index === 0 ? `${branchingSymbol}${line}` : `${verticalSymbol}${line}`));
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return lines;
|
|
1146
821
|
};
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
return lines.join(os.EOL);
|
|
822
|
+
const tree = async (dirPath, depth) => {
|
|
823
|
+
return (await getTreeLines(dirPath, depth)).join(os.EOL);
|
|
1150
824
|
};
|
|
1151
825
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
} catch (error) {
|
|
1167
|
-
console.log(error);
|
|
1168
|
-
process.exit(1);
|
|
1169
|
-
}
|
|
826
|
+
//#endregion
|
|
827
|
+
//#region src/commands/dev.ts
|
|
828
|
+
const dev = async ({ dir: emailsDirRelativePath, port }) => {
|
|
829
|
+
try {
|
|
830
|
+
if (!fs.existsSync(emailsDirRelativePath)) {
|
|
831
|
+
console.error(`Missing ${emailsDirRelativePath} folder`);
|
|
832
|
+
process.exit(1);
|
|
833
|
+
}
|
|
834
|
+
const devServer$1 = await startDevServer(emailsDirRelativePath, emailsDirRelativePath, Number.parseInt(port));
|
|
835
|
+
await setupHotreloading(devServer$1, emailsDirRelativePath);
|
|
836
|
+
} catch (error) {
|
|
837
|
+
console.log(error);
|
|
838
|
+
process.exit(1);
|
|
839
|
+
}
|
|
1170
840
|
};
|
|
1171
841
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
import { createRequire } from "node:module";
|
|
1175
|
-
import path12 from "node:path";
|
|
1176
|
-
import url3 from "node:url";
|
|
1177
|
-
import { build as build2 } from "esbuild";
|
|
1178
|
-
import { glob } from "glob";
|
|
1179
|
-
import logSymbols4 from "log-symbols";
|
|
1180
|
-
import normalize from "normalize-path";
|
|
1181
|
-
import ora3 from "ora";
|
|
1182
|
-
|
|
1183
|
-
// src/utils/esbuild/renderring-utilities-exporter.ts
|
|
1184
|
-
import { promises as fs7 } from "node:fs";
|
|
1185
|
-
import path11 from "node:path";
|
|
1186
|
-
|
|
1187
|
-
// src/utils/esbuild/escape-string-for-regex.ts
|
|
842
|
+
//#endregion
|
|
843
|
+
//#region src/utils/esbuild/escape-string-for-regex.ts
|
|
1188
844
|
function escapeStringForRegex(string) {
|
|
1189
|
-
|
|
845
|
+
return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
|
|
1190
846
|
}
|
|
1191
847
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
848
|
+
//#endregion
|
|
849
|
+
//#region src/utils/esbuild/renderring-utilities-exporter.ts
|
|
850
|
+
/**
|
|
851
|
+
* Made to export the `render` function out of the user's email template
|
|
852
|
+
* so that issues like https://github.com/resend/react-email/issues/649 don't
|
|
853
|
+
* happen.
|
|
854
|
+
*
|
|
855
|
+
* This also exports the `createElement` from the user's React version as well
|
|
856
|
+
* to avoid mismatches.
|
|
857
|
+
*
|
|
858
|
+
* This avoids multiple versions of React being involved, i.e., the version
|
|
859
|
+
* in the CLI vs. the version the user has on their emails.
|
|
860
|
+
*/
|
|
861
|
+
const renderingUtilitiesExporter = (emailTemplates) => ({
|
|
862
|
+
name: "rendering-utilities-exporter",
|
|
863
|
+
setup: (b) => {
|
|
864
|
+
b.onLoad({ filter: new RegExp(emailTemplates.map((emailPath) => escapeStringForRegex(emailPath)).join("|")) }, async ({ path: pathToFile }) => {
|
|
865
|
+
return {
|
|
866
|
+
contents: `${await promises.readFile(pathToFile, "utf8")};
|
|
1205
867
|
export { render } from 'react-email-module-that-will-export-render'
|
|
1206
868
|
export { createElement as reactEmailCreateReactElement } from 'react';
|
|
1207
869
|
`,
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
result = await b.resolve("@react-email/components", options);
|
|
1226
|
-
if (result.errors.length > 0 && result.errors[0]) {
|
|
1227
|
-
result.errors[0].text = "Failed trying to import `render` from either `@react-email/render` or `@react-email/components` to be able to render your email template.\n Maybe you don't have either of them installed?";
|
|
1228
|
-
}
|
|
1229
|
-
return result;
|
|
1230
|
-
}
|
|
1231
|
-
);
|
|
1232
|
-
}
|
|
870
|
+
loader: path.extname(pathToFile).slice(1)
|
|
871
|
+
};
|
|
872
|
+
});
|
|
873
|
+
b.onResolve({ filter: /^react-email-module-that-will-export-render$/ }, async (args) => {
|
|
874
|
+
const options = {
|
|
875
|
+
kind: "import-statement",
|
|
876
|
+
importer: args.importer,
|
|
877
|
+
resolveDir: args.resolveDir,
|
|
878
|
+
namespace: args.namespace
|
|
879
|
+
};
|
|
880
|
+
let result = await b.resolve("@react-email/render", options);
|
|
881
|
+
if (result.errors.length === 0) return result;
|
|
882
|
+
result = await b.resolve("@react-email/components", options);
|
|
883
|
+
if (result.errors.length > 0 && result.errors[0]) result.errors[0].text = "Failed trying to import `render` from either `@react-email/render` or `@react-email/components` to be able to render your email template.\n Maybe you don't have either of them installed?";
|
|
884
|
+
return result;
|
|
885
|
+
});
|
|
886
|
+
}
|
|
1233
887
|
});
|
|
1234
888
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
return templatePaths;
|
|
889
|
+
//#endregion
|
|
890
|
+
//#region src/commands/export.ts
|
|
891
|
+
const getEmailTemplatesFromDirectory = (emailDirectory) => {
|
|
892
|
+
const templatePaths = [];
|
|
893
|
+
emailDirectory.emailFilenames.forEach((filename$1) => templatePaths.push(path.join(emailDirectory.absolutePath, filename$1)));
|
|
894
|
+
emailDirectory.subDirectories.forEach((directory) => {
|
|
895
|
+
templatePaths.push(...getEmailTemplatesFromDirectory(directory));
|
|
896
|
+
});
|
|
897
|
+
return templatePaths;
|
|
1245
898
|
};
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
spinner.render();
|
|
1339
|
-
}
|
|
1340
|
-
const staticDirectoryPath = path12.join(emailsDirectoryPath, "static");
|
|
1341
|
-
if (fs8.existsSync(staticDirectoryPath)) {
|
|
1342
|
-
const pathToDumpStaticFilesInto = path12.join(
|
|
1343
|
-
pathToWhereEmailMarkupShouldBeDumped,
|
|
1344
|
-
"static"
|
|
1345
|
-
);
|
|
1346
|
-
if (fs8.existsSync(pathToDumpStaticFilesInto))
|
|
1347
|
-
await fs8.promises.rm(pathToDumpStaticFilesInto, { recursive: true });
|
|
1348
|
-
try {
|
|
1349
|
-
await fs8.promises.cp(staticDirectoryPath, pathToDumpStaticFilesInto, {
|
|
1350
|
-
recursive: true
|
|
1351
|
-
});
|
|
1352
|
-
} catch (exception) {
|
|
1353
|
-
console.error(exception);
|
|
1354
|
-
if (spinner) {
|
|
1355
|
-
spinner.stopAndPersist({
|
|
1356
|
-
symbol: logSymbols4.error,
|
|
1357
|
-
text: "Failed to copy static files"
|
|
1358
|
-
});
|
|
1359
|
-
}
|
|
1360
|
-
console.error(
|
|
1361
|
-
`Something went wrong while copying the file to ${pathToWhereEmailMarkupShouldBeDumped}/static, ${exception}`
|
|
1362
|
-
);
|
|
1363
|
-
process.exit(1);
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
if (spinner && !options.silent) {
|
|
1367
|
-
spinner.succeed();
|
|
1368
|
-
const fileTree = await tree(pathToWhereEmailMarkupShouldBeDumped, 4);
|
|
1369
|
-
console.log(fileTree);
|
|
1370
|
-
spinner.stopAndPersist({
|
|
1371
|
-
symbol: logSymbols4.success,
|
|
1372
|
-
text: "Successfully exported emails"
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
899
|
+
const filename = url.fileURLToPath(import.meta.url);
|
|
900
|
+
const require = createRequire(filename);
|
|
901
|
+
const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirectoryPath, options) => {
|
|
902
|
+
if (fs.existsSync(pathToWhereEmailMarkupShouldBeDumped)) fs.rmSync(pathToWhereEmailMarkupShouldBeDumped, { recursive: true });
|
|
903
|
+
let spinner;
|
|
904
|
+
if (!options.silent) {
|
|
905
|
+
spinner = ora("Preparing files...\n").start();
|
|
906
|
+
registerSpinnerAutostopping(spinner);
|
|
907
|
+
}
|
|
908
|
+
const emailsDirectoryMetadata = await getEmailsDirectoryMetadata(path.resolve(process.cwd(), emailsDirectoryPath), true);
|
|
909
|
+
if (typeof emailsDirectoryMetadata === "undefined") {
|
|
910
|
+
if (spinner) spinner.stopAndPersist({
|
|
911
|
+
symbol: logSymbols.error,
|
|
912
|
+
text: `Could not find the directory at ${emailsDirectoryPath}`
|
|
913
|
+
});
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const allTemplates = getEmailTemplatesFromDirectory(emailsDirectoryMetadata);
|
|
917
|
+
try {
|
|
918
|
+
await build({
|
|
919
|
+
bundle: true,
|
|
920
|
+
entryPoints: allTemplates,
|
|
921
|
+
format: "cjs",
|
|
922
|
+
jsx: "automatic",
|
|
923
|
+
loader: { ".js": "jsx" },
|
|
924
|
+
logLevel: "silent",
|
|
925
|
+
outExtension: { ".js": ".cjs" },
|
|
926
|
+
outdir: pathToWhereEmailMarkupShouldBeDumped,
|
|
927
|
+
platform: "node",
|
|
928
|
+
plugins: [renderingUtilitiesExporter(allTemplates)],
|
|
929
|
+
write: true
|
|
930
|
+
});
|
|
931
|
+
} catch (exception) {
|
|
932
|
+
if (spinner) spinner.stopAndPersist({
|
|
933
|
+
symbol: logSymbols.error,
|
|
934
|
+
text: "Failed to build emails"
|
|
935
|
+
});
|
|
936
|
+
const buildFailure = exception;
|
|
937
|
+
console.error(`\n${buildFailure.message}`);
|
|
938
|
+
process.exit(1);
|
|
939
|
+
}
|
|
940
|
+
if (spinner) spinner.succeed();
|
|
941
|
+
const allBuiltTemplates = glob.sync(normalize(`${pathToWhereEmailMarkupShouldBeDumped}/**/*.cjs`), { absolute: true });
|
|
942
|
+
for await (const template of allBuiltTemplates) try {
|
|
943
|
+
if (spinner) {
|
|
944
|
+
spinner.text = `rendering ${template.split("/").pop()}`;
|
|
945
|
+
spinner.render();
|
|
946
|
+
}
|
|
947
|
+
delete require.cache[template];
|
|
948
|
+
const emailModule = require(template);
|
|
949
|
+
const rendered = await emailModule.render(emailModule.reactEmailCreateReactElement(emailModule.default, {}), options);
|
|
950
|
+
const htmlPath = template.replace(".cjs", options.plainText ? ".txt" : ".html");
|
|
951
|
+
writeFileSync(htmlPath, rendered);
|
|
952
|
+
unlinkSync(template);
|
|
953
|
+
} catch (exception) {
|
|
954
|
+
if (spinner) spinner.stopAndPersist({
|
|
955
|
+
symbol: logSymbols.error,
|
|
956
|
+
text: `failed when rendering ${template.split("/").pop()}`
|
|
957
|
+
});
|
|
958
|
+
console.error(exception);
|
|
959
|
+
process.exit(1);
|
|
960
|
+
}
|
|
961
|
+
if (spinner) {
|
|
962
|
+
spinner.succeed("Rendered all files");
|
|
963
|
+
spinner.text = "Copying static files";
|
|
964
|
+
spinner.render();
|
|
965
|
+
}
|
|
966
|
+
const staticDirectoryPath = path.join(emailsDirectoryPath, "static");
|
|
967
|
+
if (fs.existsSync(staticDirectoryPath)) {
|
|
968
|
+
const pathToDumpStaticFilesInto = path.join(pathToWhereEmailMarkupShouldBeDumped, "static");
|
|
969
|
+
if (fs.existsSync(pathToDumpStaticFilesInto)) await fs.promises.rm(pathToDumpStaticFilesInto, { recursive: true });
|
|
970
|
+
try {
|
|
971
|
+
await fs.promises.cp(staticDirectoryPath, pathToDumpStaticFilesInto, { recursive: true });
|
|
972
|
+
} catch (exception) {
|
|
973
|
+
console.error(exception);
|
|
974
|
+
if (spinner) spinner.stopAndPersist({
|
|
975
|
+
symbol: logSymbols.error,
|
|
976
|
+
text: "Failed to copy static files"
|
|
977
|
+
});
|
|
978
|
+
console.error(`Something went wrong while copying the file to ${pathToWhereEmailMarkupShouldBeDumped}/static, ${exception}`);
|
|
979
|
+
process.exit(1);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (spinner && !options.silent) {
|
|
983
|
+
spinner.succeed();
|
|
984
|
+
const fileTree = await tree(pathToWhereEmailMarkupShouldBeDumped, 4);
|
|
985
|
+
console.log(fileTree);
|
|
986
|
+
spinner.stopAndPersist({
|
|
987
|
+
symbol: logSymbols.success,
|
|
988
|
+
text: "Successfully exported emails"
|
|
989
|
+
});
|
|
990
|
+
}
|
|
1375
991
|
};
|
|
1376
992
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
console.log(error);
|
|
1407
|
-
process.exit(1);
|
|
1408
|
-
}
|
|
993
|
+
//#endregion
|
|
994
|
+
//#region src/commands/start.ts
|
|
995
|
+
const start = async () => {
|
|
996
|
+
try {
|
|
997
|
+
const previewServerLocation = await getPreviewServerLocation();
|
|
998
|
+
const usersProjectLocation = process.cwd();
|
|
999
|
+
const builtPreviewPath = path.resolve(usersProjectLocation, "./.react-email");
|
|
1000
|
+
if (!fs.existsSync(builtPreviewPath)) {
|
|
1001
|
+
console.error("Could not find .react-email, maybe you haven't ran email build?");
|
|
1002
|
+
process.exit(1);
|
|
1003
|
+
}
|
|
1004
|
+
const nextStart = spawn("npx", [
|
|
1005
|
+
"next",
|
|
1006
|
+
"start",
|
|
1007
|
+
builtPreviewPath
|
|
1008
|
+
], {
|
|
1009
|
+
cwd: previewServerLocation,
|
|
1010
|
+
stdio: "inherit"
|
|
1011
|
+
});
|
|
1012
|
+
process.on("SIGINT", () => {
|
|
1013
|
+
nextStart.kill("SIGINT");
|
|
1014
|
+
});
|
|
1015
|
+
nextStart.on("exit", (code) => {
|
|
1016
|
+
process.exit(code ?? 0);
|
|
1017
|
+
});
|
|
1018
|
+
} catch (error) {
|
|
1019
|
+
console.log(error);
|
|
1020
|
+
process.exit(1);
|
|
1021
|
+
}
|
|
1409
1022
|
};
|
|
1410
1023
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
program.name(
|
|
1024
|
+
//#endregion
|
|
1025
|
+
//#region src/index.ts
|
|
1026
|
+
program.name("react-email").description("A live preview of your emails right in your browser").version(package_default.version);
|
|
1414
1027
|
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);
|
|
1415
|
-
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(
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
"-s, --silent",
|
|
1423
|
-
"To, or not to show a spinner with process information",
|
|
1424
|
-
false
|
|
1425
|
-
).action(
|
|
1426
|
-
({ outDir, pretty, plainText, silent, dir: srcDir }) => exportTemplates(outDir, srcDir, { silent, plainText, pretty })
|
|
1427
|
-
);
|
|
1028
|
+
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("-p --packageManager <name>", "Package name to use on installation on `.react-email`", "npm").action(build$1);
|
|
1029
|
+
program.command("start").description("Runs the built preview app that is inside of \".react-email\"").action(start);
|
|
1030
|
+
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").option("-s, --silent", "To, or not to show a spinner with process information", false).action(({ outDir, pretty, plainText, silent, dir: srcDir }) => exportTemplates(outDir, srcDir, {
|
|
1031
|
+
silent,
|
|
1032
|
+
plainText,
|
|
1033
|
+
pretty
|
|
1034
|
+
}));
|
|
1428
1035
|
program.parse();
|
|
1036
|
+
|
|
1037
|
+
//#endregion
|
|
1038
|
+
export { };
|