react-email 4.1.0-canary.9 → 4.1.1
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 +44 -0
- package/dev/CHANGELOG.md +3 -0
- package/dev/index.js +44 -0
- package/dev/package.json +13 -0
- package/dist/index.js +257 -260
- package/package.json +3 -3
- package/readme.md +16 -0
- package/src/actions/email-validation/__snapshots__/check-images.spec.tsx.snap +84 -0
- package/src/utils/get-preview-server-location.ts +1 -2
- package/src/utils/preview/get-env-variables-for-preview-app.ts +0 -2
- package/src/utils/preview/hot-reloading/create-dependency-graph.spec.ts +1 -70
- package/src/utils/preview/hot-reloading/create-dependency-graph.ts +2 -5
- package/src/utils/preview/serve-static-file.ts +2 -1
- package/src/utils/preview/start-dev-server.ts +1 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# react-email
|
|
2
2
|
|
|
3
|
+
## 4.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ef77691: fix path resolution done wrong breaking `email dev` on Windows
|
|
8
|
+
|
|
9
|
+
## 4.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- abf8599: use a separate package for storing the preview server (@react-email/preview-server)
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- e173b44: Use the same version for the preview-server and react-email
|
|
18
|
+
|
|
19
|
+
## 4.1.0-canary.12
|
|
20
|
+
|
|
21
|
+
## 4.1.0-canary.11
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 19d4b45: fix static file serving security issue with logging
|
|
26
|
+
|
|
27
|
+
## 4.1.0-canary.10
|
|
28
|
+
|
|
3
29
|
## 4.1.0-canary.9
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
|
@@ -63,6 +89,24 @@
|
|
|
63
89
|
|
|
64
90
|
- 4a0d4e3: Theme switcher for email template
|
|
65
91
|
|
|
92
|
+
## 4.0.17
|
|
93
|
+
|
|
94
|
+
### Patch Changes
|
|
95
|
+
|
|
96
|
+
- e352a67: fix `<svg>` not being flagged as incompatible
|
|
97
|
+
- 8f64ebd: fix the forced `color-scheme: dark` for the preview
|
|
98
|
+
- 6de4e9f: fix static file serving security issue with logging
|
|
99
|
+
- b2e96d5: Add support for hot reloading with tsconfig path aliases
|
|
100
|
+
- 6b0cfd6: fix hot reloading with collapsed directories
|
|
101
|
+
- 8c93330: Fix prettier errors causing NextJS serialization error
|
|
102
|
+
- a07eebf: Pre-render email templates on hover
|
|
103
|
+
|
|
104
|
+
## 4.0.16
|
|
105
|
+
|
|
106
|
+
### Patch Changes
|
|
107
|
+
|
|
108
|
+
- 1340a0a: fix mobile's sidebar broken in the preview server
|
|
109
|
+
|
|
66
110
|
## 4.0.15
|
|
67
111
|
|
|
68
112
|
### Patch Changes
|
package/dev/CHANGELOG.md
ADDED
package/dev/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import child_process from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import url from 'node:url';
|
|
4
|
+
|
|
5
|
+
const filename = url.fileURLToPath(import.meta.url);
|
|
6
|
+
const dirname = path.dirname(filename);
|
|
7
|
+
|
|
8
|
+
const root = path.resolve(dirname, '../src/index.ts');
|
|
9
|
+
|
|
10
|
+
const tsxPath = path.resolve(dirname, './node_modules/.bin/tsx');
|
|
11
|
+
|
|
12
|
+
const tsx = child_process.spawn(tsxPath, [root, ...process.argv.slice(2)], {
|
|
13
|
+
shell: true,
|
|
14
|
+
cwd: process.cwd(),
|
|
15
|
+
stdio: 'inherit',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
tsx.on('close', (code) => {
|
|
19
|
+
process.exit(code);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
process.on('uncaughtExceptionMonitor', () => {
|
|
23
|
+
tsx.kill();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
process.on('exit', (code) => {
|
|
27
|
+
tsx.kill(code);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
process.on('SIGINT', () => {
|
|
31
|
+
tsx.kill('SIGINT');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
process.on('SIGTERM', () => {
|
|
35
|
+
tsx.kill('SIGTERM');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
process.on('SIGUSR1', () => {
|
|
39
|
+
tsx.kill('SIGUSR1');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
process.on('SIGUSR2', () => {
|
|
43
|
+
tsx.kill('SIGUSR2');
|
|
44
|
+
});
|
package/dev/package.json
ADDED
package/dist/index.js
CHANGED
|
@@ -106,7 +106,7 @@ import prompts from "prompts";
|
|
|
106
106
|
// package.json
|
|
107
107
|
var package_default = {
|
|
108
108
|
name: "react-email",
|
|
109
|
-
version: "4.1.
|
|
109
|
+
version: "4.1.1",
|
|
110
110
|
description: "A live preview of your emails right in your browser.",
|
|
111
111
|
bin: {
|
|
112
112
|
email: "./dist/index.js"
|
|
@@ -114,8 +114,8 @@ var package_default = {
|
|
|
114
114
|
type: "module",
|
|
115
115
|
scripts: {
|
|
116
116
|
build: "tsup-node",
|
|
117
|
+
"build:watch": "tsup-node --watch src",
|
|
117
118
|
clean: "rm -rf dist",
|
|
118
|
-
dev: "tsup-node --watch src",
|
|
119
119
|
test: "vitest run",
|
|
120
120
|
"test:watch": "vitest"
|
|
121
121
|
},
|
|
@@ -189,7 +189,7 @@ var getPreviewServerLocation = async () => {
|
|
|
189
189
|
let previewServerLocation;
|
|
190
190
|
try {
|
|
191
191
|
previewServerLocation = path2.dirname(
|
|
192
|
-
url.
|
|
192
|
+
url.fileURLToPath(usersProject.esmResolve("@react-email/preview-server"))
|
|
193
193
|
);
|
|
194
194
|
} catch (_exception) {
|
|
195
195
|
await ensurePreviewServerInstalled(
|
|
@@ -326,8 +326,8 @@ var getEmailSlugsFromEmailDirectory = (emailDirectory, emailsDirectoryAbsolutePa
|
|
|
326
326
|
const directoryPathRelativeToEmailsDirectory = emailDirectory.absolutePath.replace(emailsDirectoryAbsolutePath, "").trim();
|
|
327
327
|
const slugs = [];
|
|
328
328
|
emailDirectory.emailFilenames.forEach(
|
|
329
|
-
(
|
|
330
|
-
path3.join(directoryPathRelativeToEmailsDirectory,
|
|
329
|
+
(filename2) => slugs.push(
|
|
330
|
+
path3.join(directoryPathRelativeToEmailsDirectory, filename2).split(path3.sep).filter((segment) => segment.length > 0)
|
|
331
331
|
)
|
|
332
332
|
);
|
|
333
333
|
emailDirectory.subDirectories.forEach((directory) => {
|
|
@@ -458,234 +458,14 @@ var build = async ({
|
|
|
458
458
|
import fs6 from "node:fs";
|
|
459
459
|
|
|
460
460
|
// src/utils/preview/hot-reloading/setup-hot-reloading.ts
|
|
461
|
-
import
|
|
461
|
+
import path6 from "node:path";
|
|
462
462
|
import { watch } from "chokidar";
|
|
463
463
|
import debounce from "debounce";
|
|
464
464
|
import { Server as SocketServer } from "socket.io";
|
|
465
465
|
|
|
466
466
|
// src/utils/preview/hot-reloading/create-dependency-graph.ts
|
|
467
|
-
import { existsSync
|
|
468
|
-
import path8 from "node:path";
|
|
469
|
-
|
|
470
|
-
// src/utils/preview/start-dev-server.ts
|
|
471
|
-
import http from "node:http";
|
|
472
|
-
import path6 from "node:path";
|
|
473
|
-
import url2 from "node:url";
|
|
474
|
-
import chalk from "chalk";
|
|
475
|
-
import { createJiti as createJiti2 } from "jiti";
|
|
476
|
-
import logSymbols3 from "log-symbols";
|
|
477
|
-
import ora2 from "ora";
|
|
478
|
-
|
|
479
|
-
// src/utils/preview/get-env-variables-for-preview-app.ts
|
|
480
|
-
import path4 from "node:path";
|
|
481
|
-
var getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, cwd) => {
|
|
482
|
-
return {
|
|
483
|
-
EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
|
|
484
|
-
EMAILS_DIR_ABSOLUTE_PATH: path4.resolve(cwd, relativePathToEmailsDirectory),
|
|
485
|
-
USER_PROJECT_LOCATION: cwd,
|
|
486
|
-
NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT: isDev ? "true" : "false"
|
|
487
|
-
};
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
// src/utils/preview/serve-static-file.ts
|
|
491
|
-
import { existsSync, promises as fs3 } from "node:fs";
|
|
467
|
+
import { existsSync, promises as fs3, statSync } from "node:fs";
|
|
492
468
|
import path5 from "node:path";
|
|
493
|
-
import { lookup } from "mime-types";
|
|
494
|
-
var serveStaticFile = async (res, parsedUrl, staticDirRelativePath) => {
|
|
495
|
-
const pathname = parsedUrl.pathname.replace("/static", "./static");
|
|
496
|
-
const ext = path5.parse(pathname).ext;
|
|
497
|
-
const staticBaseDir = path5.resolve(process.cwd(), staticDirRelativePath);
|
|
498
|
-
const fileAbsolutePath = path5.resolve(staticBaseDir, pathname);
|
|
499
|
-
if (!fileAbsolutePath.startsWith(staticBaseDir)) {
|
|
500
|
-
res.statusCode = 403;
|
|
501
|
-
res.end();
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
try {
|
|
505
|
-
const fileHandle = await fs3.open(fileAbsolutePath, "r");
|
|
506
|
-
const fileData = await fs3.readFile(fileHandle);
|
|
507
|
-
res.setHeader("Content-type", lookup(ext) || "text/plain");
|
|
508
|
-
res.end(fileData);
|
|
509
|
-
fileHandle.close();
|
|
510
|
-
} catch (exception) {
|
|
511
|
-
if (!existsSync(fileAbsolutePath)) {
|
|
512
|
-
res.statusCode = 404;
|
|
513
|
-
res.end();
|
|
514
|
-
} else {
|
|
515
|
-
const sanitizedFilePath = fileAbsolutePath.replace(/\n|\r/g, "");
|
|
516
|
-
console.error(
|
|
517
|
-
`Could not read file at ${sanitizedFilePath} to be served, here's the exception:`,
|
|
518
|
-
exception
|
|
519
|
-
);
|
|
520
|
-
res.statusCode = 500;
|
|
521
|
-
res.end(
|
|
522
|
-
"Could not read file to be served! Check your terminal for more information."
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
// src/utils/preview/start-dev-server.ts
|
|
529
|
-
var devServer;
|
|
530
|
-
var safeAsyncServerListen = (server, port) => {
|
|
531
|
-
return new Promise((resolve) => {
|
|
532
|
-
server.listen(port, () => {
|
|
533
|
-
resolve({ portAlreadyInUse: false });
|
|
534
|
-
});
|
|
535
|
-
server.on("error", (e) => {
|
|
536
|
-
if (e.code === "EADDRINUSE") {
|
|
537
|
-
resolve({ portAlreadyInUse: true });
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
});
|
|
541
|
-
};
|
|
542
|
-
var filename = url2.fileURLToPath(import.meta.url);
|
|
543
|
-
var dirname = path6.dirname(filename);
|
|
544
|
-
var isDev = !dirname.includes("dist");
|
|
545
|
-
var startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port) => {
|
|
546
|
-
const [majorNodeVersion] = process.versions.node.split(".");
|
|
547
|
-
if (majorNodeVersion && Number.parseInt(majorNodeVersion) < 18) {
|
|
548
|
-
console.error(
|
|
549
|
-
` ${logSymbols3.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 18 or higher.`
|
|
550
|
-
);
|
|
551
|
-
process.exit(1);
|
|
552
|
-
}
|
|
553
|
-
const previewServerLocation = await getPreviewServerLocation();
|
|
554
|
-
const previewServer = createJiti2(previewServerLocation);
|
|
555
|
-
const { default: next } = await previewServer.import("next");
|
|
556
|
-
devServer = http.createServer((req, res) => {
|
|
557
|
-
if (!req.url) {
|
|
558
|
-
res.end(404);
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
const parsedUrl = url2.parse(req.url, true);
|
|
562
|
-
res.setHeader(
|
|
563
|
-
"Cache-Control",
|
|
564
|
-
"no-cache, max-age=0, must-revalidate, no-store"
|
|
565
|
-
);
|
|
566
|
-
res.setHeader("Pragma", "no-cache");
|
|
567
|
-
res.setHeader("Expires", "-1");
|
|
568
|
-
try {
|
|
569
|
-
if (parsedUrl.path?.includes("static/") && !parsedUrl.path.includes("_next/static/")) {
|
|
570
|
-
void serveStaticFile(res, parsedUrl, staticBaseDirRelativePath);
|
|
571
|
-
} else if (!isNextReady) {
|
|
572
|
-
void nextReadyPromise.then(
|
|
573
|
-
() => nextHandleRequest?.(req, res, parsedUrl)
|
|
574
|
-
);
|
|
575
|
-
} else {
|
|
576
|
-
void nextHandleRequest?.(req, res, parsedUrl);
|
|
577
|
-
}
|
|
578
|
-
} catch (e) {
|
|
579
|
-
console.error("caught error", e);
|
|
580
|
-
res.writeHead(500);
|
|
581
|
-
res.end();
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
const { portAlreadyInUse } = await safeAsyncServerListen(devServer, port);
|
|
585
|
-
if (!portAlreadyInUse) {
|
|
586
|
-
console.log(chalk.greenBright(` React Email ${package_default.version}`));
|
|
587
|
-
console.log(` Running preview at: http://localhost:${port}
|
|
588
|
-
`);
|
|
589
|
-
} else {
|
|
590
|
-
const nextPortToTry = port + 1;
|
|
591
|
-
console.warn(
|
|
592
|
-
` ${logSymbols3.warning} Port ${port} is already in use, trying ${nextPortToTry}`
|
|
593
|
-
);
|
|
594
|
-
return startDevServer(
|
|
595
|
-
emailsDirRelativePath,
|
|
596
|
-
staticBaseDirRelativePath,
|
|
597
|
-
nextPortToTry
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
devServer.on("close", async () => {
|
|
601
|
-
await app.close();
|
|
602
|
-
});
|
|
603
|
-
devServer.on("error", (e) => {
|
|
604
|
-
spinner.stopAndPersist({
|
|
605
|
-
symbol: logSymbols3.error,
|
|
606
|
-
text: `Preview Server had an error: ${e}`
|
|
607
|
-
});
|
|
608
|
-
process.exit(1);
|
|
609
|
-
});
|
|
610
|
-
const spinner = ora2({
|
|
611
|
-
text: "Getting react-email preview server ready...\n",
|
|
612
|
-
prefixText: " "
|
|
613
|
-
}).start();
|
|
614
|
-
registerSpinnerAutostopping(spinner);
|
|
615
|
-
const timeBeforeNextReady = performance.now();
|
|
616
|
-
process.env = {
|
|
617
|
-
NODE_ENV: "development",
|
|
618
|
-
...process.env,
|
|
619
|
-
...getEnvVariablesForPreviewApp(
|
|
620
|
-
// If we don't do normalization here, stuff like https://github.com/resend/react-email/issues/1354 happens.
|
|
621
|
-
path6.normalize(emailsDirRelativePath),
|
|
622
|
-
process.cwd()
|
|
623
|
-
)
|
|
624
|
-
};
|
|
625
|
-
const app = next({
|
|
626
|
-
// passing in env here does not get the environment variables there
|
|
627
|
-
dev: isDev,
|
|
628
|
-
conf: {
|
|
629
|
-
images: {
|
|
630
|
-
// This is to avoid the warning with sharp
|
|
631
|
-
unoptimized: true
|
|
632
|
-
}
|
|
633
|
-
},
|
|
634
|
-
hostname: "localhost",
|
|
635
|
-
port,
|
|
636
|
-
dir: previewServerLocation
|
|
637
|
-
});
|
|
638
|
-
let isNextReady = false;
|
|
639
|
-
const nextReadyPromise = app.prepare();
|
|
640
|
-
try {
|
|
641
|
-
await nextReadyPromise;
|
|
642
|
-
} catch (exception) {
|
|
643
|
-
spinner.stopAndPersist({
|
|
644
|
-
symbol: logSymbols3.error,
|
|
645
|
-
text: ` Preview Server had an error: ${exception}`
|
|
646
|
-
});
|
|
647
|
-
process.exit(1);
|
|
648
|
-
}
|
|
649
|
-
isNextReady = true;
|
|
650
|
-
const nextHandleRequest = app.getRequestHandler();
|
|
651
|
-
const secondsToNextReady = ((performance.now() - timeBeforeNextReady) / 1e3).toFixed(1);
|
|
652
|
-
spinner.stopAndPersist({
|
|
653
|
-
text: `Ready in ${secondsToNextReady}s
|
|
654
|
-
`,
|
|
655
|
-
symbol: logSymbols3.success
|
|
656
|
-
});
|
|
657
|
-
return devServer;
|
|
658
|
-
};
|
|
659
|
-
var makeExitHandler = (options) => (codeSignalOrError) => {
|
|
660
|
-
if (typeof devServer !== "undefined") {
|
|
661
|
-
console.log("\nshutting down dev server");
|
|
662
|
-
devServer.close();
|
|
663
|
-
devServer = void 0;
|
|
664
|
-
}
|
|
665
|
-
if (codeSignalOrError instanceof Error) {
|
|
666
|
-
console.error(codeSignalOrError);
|
|
667
|
-
}
|
|
668
|
-
if (options?.shouldKillProcess) {
|
|
669
|
-
process.exit(options.killWithErrorCode ? 1 : 0);
|
|
670
|
-
}
|
|
671
|
-
};
|
|
672
|
-
process.on("exit", makeExitHandler());
|
|
673
|
-
process.on(
|
|
674
|
-
"SIGINT",
|
|
675
|
-
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
|
|
676
|
-
);
|
|
677
|
-
process.on(
|
|
678
|
-
"SIGUSR1",
|
|
679
|
-
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
|
|
680
|
-
);
|
|
681
|
-
process.on(
|
|
682
|
-
"SIGUSR2",
|
|
683
|
-
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
|
|
684
|
-
);
|
|
685
|
-
process.on(
|
|
686
|
-
"uncaughtException",
|
|
687
|
-
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: true })
|
|
688
|
-
);
|
|
689
469
|
|
|
690
470
|
// src/utils/preview/hot-reloading/get-imported-modules.ts
|
|
691
471
|
import { parse } from "@babel/parser";
|
|
@@ -733,7 +513,7 @@ var getImportedModules = (contents) => {
|
|
|
733
513
|
};
|
|
734
514
|
|
|
735
515
|
// src/utils/preview/hot-reloading/resolve-path-aliases.ts
|
|
736
|
-
import
|
|
516
|
+
import path4 from "node:path";
|
|
737
517
|
import { createMatchPath, loadConfig } from "tsconfig-paths";
|
|
738
518
|
var resolvePathAliases = (importPaths, projectPath) => {
|
|
739
519
|
const configLoadResult = loadConfig(projectPath);
|
|
@@ -752,7 +532,7 @@ var resolvePathAliases = (importPaths, projectPath) => {
|
|
|
752
532
|
".mjs"
|
|
753
533
|
]);
|
|
754
534
|
if (unaliasedPath) {
|
|
755
|
-
return `./${
|
|
535
|
+
return `./${path4.relative(projectPath, unaliasedPath)}`;
|
|
756
536
|
}
|
|
757
537
|
return importedPath;
|
|
758
538
|
});
|
|
@@ -763,9 +543,9 @@ var resolvePathAliases = (importPaths, projectPath) => {
|
|
|
763
543
|
// src/utils/preview/hot-reloading/create-dependency-graph.ts
|
|
764
544
|
var readAllFilesInsideDirectory = async (directory) => {
|
|
765
545
|
let allFilePaths = [];
|
|
766
|
-
const topLevelDirents = await
|
|
546
|
+
const topLevelDirents = await fs3.readdir(directory, { withFileTypes: true });
|
|
767
547
|
for await (const dirent of topLevelDirents) {
|
|
768
|
-
const pathToDirent =
|
|
548
|
+
const pathToDirent = path5.join(directory, dirent.name);
|
|
769
549
|
if (dirent.isDirectory()) {
|
|
770
550
|
allFilePaths = allFilePaths.concat(
|
|
771
551
|
await readAllFilesInsideDirectory(pathToDirent)
|
|
@@ -777,26 +557,26 @@ var readAllFilesInsideDirectory = async (directory) => {
|
|
|
777
557
|
return allFilePaths;
|
|
778
558
|
};
|
|
779
559
|
var isJavascriptModule = (filePath) => {
|
|
780
|
-
const extensionName =
|
|
560
|
+
const extensionName = path5.extname(filePath);
|
|
781
561
|
return [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"].includes(extensionName);
|
|
782
562
|
};
|
|
783
563
|
var checkFileExtensionsUntilItExists = (pathWithoutExtension) => {
|
|
784
|
-
if (
|
|
564
|
+
if (existsSync(`${pathWithoutExtension}.ts`)) {
|
|
785
565
|
return `${pathWithoutExtension}.ts`;
|
|
786
566
|
}
|
|
787
|
-
if (
|
|
567
|
+
if (existsSync(`${pathWithoutExtension}.tsx`)) {
|
|
788
568
|
return `${pathWithoutExtension}.tsx`;
|
|
789
569
|
}
|
|
790
|
-
if (
|
|
570
|
+
if (existsSync(`${pathWithoutExtension}.js`)) {
|
|
791
571
|
return `${pathWithoutExtension}.js`;
|
|
792
572
|
}
|
|
793
|
-
if (
|
|
573
|
+
if (existsSync(`${pathWithoutExtension}.jsx`)) {
|
|
794
574
|
return `${pathWithoutExtension}.jsx`;
|
|
795
575
|
}
|
|
796
|
-
if (
|
|
576
|
+
if (existsSync(`${pathWithoutExtension}.mjs`)) {
|
|
797
577
|
return `${pathWithoutExtension}.mjs`;
|
|
798
578
|
}
|
|
799
|
-
if (
|
|
579
|
+
if (existsSync(`${pathWithoutExtension}.cjs`)) {
|
|
800
580
|
return `${pathWithoutExtension}.cjs`;
|
|
801
581
|
}
|
|
802
582
|
};
|
|
@@ -815,15 +595,15 @@ var createDependencyGraph = async (directory) => {
|
|
|
815
595
|
])
|
|
816
596
|
);
|
|
817
597
|
const getDependencyPaths = async (filePath) => {
|
|
818
|
-
const contents = await
|
|
819
|
-
const importedPaths = isJavascriptModule(filePath) ? resolvePathAliases(getImportedModules(contents),
|
|
598
|
+
const contents = await fs3.readFile(filePath, "utf8");
|
|
599
|
+
const importedPaths = isJavascriptModule(filePath) ? resolvePathAliases(getImportedModules(contents), path5.dirname(filePath)) : [];
|
|
820
600
|
const importedPathsRelativeToDirectory = importedPaths.map(
|
|
821
601
|
(dependencyPath) => {
|
|
822
602
|
const isModulePath = !dependencyPath.startsWith(".");
|
|
823
|
-
if (isModulePath ||
|
|
603
|
+
if (isModulePath || path5.isAbsolute(dependencyPath)) {
|
|
824
604
|
return dependencyPath;
|
|
825
605
|
}
|
|
826
|
-
let pathToDependencyFromDirectory =
|
|
606
|
+
let pathToDependencyFromDirectory = path5.resolve(
|
|
827
607
|
/*
|
|
828
608
|
path.resolve resolves paths differently from what imports on javascript do.
|
|
829
609
|
|
|
@@ -831,7 +611,7 @@ var createDependencyGraph = async (directory) => {
|
|
|
831
611
|
would end up going into /path/to/email.tsx/other-email instead of /path/to/other-email which is the
|
|
832
612
|
one the import is meant to go to
|
|
833
613
|
*/
|
|
834
|
-
|
|
614
|
+
path5.dirname(filePath),
|
|
835
615
|
dependencyPath
|
|
836
616
|
);
|
|
837
617
|
let isDirectory = false;
|
|
@@ -846,15 +626,15 @@ var createDependencyGraph = async (directory) => {
|
|
|
846
626
|
);
|
|
847
627
|
if (pathWithExtension) {
|
|
848
628
|
pathToDependencyFromDirectory = pathWithExtension;
|
|
849
|
-
} else
|
|
629
|
+
} else {
|
|
850
630
|
console.warn(
|
|
851
631
|
`Could not find index file for directory at ${pathToDependencyFromDirectory}. This is probably going to cause issues with both hot reloading and your code.`
|
|
852
632
|
);
|
|
853
633
|
}
|
|
854
634
|
}
|
|
855
|
-
const extension =
|
|
635
|
+
const extension = path5.extname(pathToDependencyFromDirectory);
|
|
856
636
|
const pathWithEnsuredExtension = (() => {
|
|
857
|
-
if (extension.length > 0 &&
|
|
637
|
+
if (extension.length > 0 && existsSync(pathToDependencyFromDirectory)) {
|
|
858
638
|
return pathToDependencyFromDirectory;
|
|
859
639
|
}
|
|
860
640
|
return checkFileExtensionsUntilItExists(
|
|
@@ -863,7 +643,7 @@ var createDependencyGraph = async (directory) => {
|
|
|
863
643
|
})();
|
|
864
644
|
if (pathWithEnsuredExtension) {
|
|
865
645
|
pathToDependencyFromDirectory = pathWithEnsuredExtension;
|
|
866
|
-
} else
|
|
646
|
+
} else {
|
|
867
647
|
console.warn(
|
|
868
648
|
`Could not find file at ${pathToDependencyFromDirectory}`
|
|
869
649
|
);
|
|
@@ -872,10 +652,10 @@ var createDependencyGraph = async (directory) => {
|
|
|
872
652
|
}
|
|
873
653
|
);
|
|
874
654
|
const moduleDependencies = importedPathsRelativeToDirectory.filter(
|
|
875
|
-
(dependencyPath) => !dependencyPath.startsWith(".") && !
|
|
655
|
+
(dependencyPath) => !dependencyPath.startsWith(".") && !path5.isAbsolute(dependencyPath)
|
|
876
656
|
);
|
|
877
657
|
const nonNodeModuleImportPathsRelativeToDirectory = importedPathsRelativeToDirectory.filter(
|
|
878
|
-
(dependencyPath) => dependencyPath.startsWith(".") ||
|
|
658
|
+
(dependencyPath) => dependencyPath.startsWith(".") || path5.isAbsolute(dependencyPath)
|
|
879
659
|
);
|
|
880
660
|
return {
|
|
881
661
|
dependencyPaths: nonNodeModuleImportPathsRelativeToDirectory,
|
|
@@ -1006,14 +786,14 @@ var setupHotreloading = async (devServer2, emailDirRelativePath) => {
|
|
|
1006
786
|
changes.filter(
|
|
1007
787
|
(change) => (
|
|
1008
788
|
// Ensures only changes inside the emails directory are emitted
|
|
1009
|
-
|
|
789
|
+
path6.resolve(absolutePathToEmailsDirectory, change.filename).startsWith(absolutePathToEmailsDirectory)
|
|
1010
790
|
)
|
|
1011
791
|
)
|
|
1012
792
|
);
|
|
1013
793
|
});
|
|
1014
794
|
changes = [];
|
|
1015
795
|
}, 150);
|
|
1016
|
-
const absolutePathToEmailsDirectory =
|
|
796
|
+
const absolutePathToEmailsDirectory = path6.resolve(
|
|
1017
797
|
process.cwd(),
|
|
1018
798
|
emailDirRelativePath
|
|
1019
799
|
);
|
|
@@ -1023,7 +803,7 @@ var setupHotreloading = async (devServer2, emailDirRelativePath) => {
|
|
|
1023
803
|
cwd: absolutePathToEmailsDirectory
|
|
1024
804
|
});
|
|
1025
805
|
const getFilesOutsideEmailsDirectory = () => Object.keys(dependencyGraph).filter(
|
|
1026
|
-
(p) =>
|
|
806
|
+
(p) => path6.relative(absolutePathToEmailsDirectory, p).startsWith("..")
|
|
1027
807
|
);
|
|
1028
808
|
let filesOutsideEmailsDirectory = getFilesOutsideEmailsDirectory();
|
|
1029
809
|
for (const p of filesOutsideEmailsDirectory) {
|
|
@@ -1035,11 +815,11 @@ var setupHotreloading = async (devServer2, emailDirRelativePath) => {
|
|
|
1035
815
|
process.on("SIGINT", exit);
|
|
1036
816
|
process.on("uncaughtException", exit);
|
|
1037
817
|
watcher.on("all", async (event, relativePathToChangeTarget) => {
|
|
1038
|
-
const file = relativePathToChangeTarget.split(
|
|
818
|
+
const file = relativePathToChangeTarget.split(path6.sep);
|
|
1039
819
|
if (file.length === 0) {
|
|
1040
820
|
return;
|
|
1041
821
|
}
|
|
1042
|
-
const pathToChangeTarget =
|
|
822
|
+
const pathToChangeTarget = path6.resolve(
|
|
1043
823
|
absolutePathToEmailsDirectory,
|
|
1044
824
|
relativePathToChangeTarget
|
|
1045
825
|
);
|
|
@@ -1063,7 +843,7 @@ var setupHotreloading = async (devServer2, emailDirRelativePath) => {
|
|
|
1063
843
|
for (const dependentPath of resolveDependentsOf(pathToChangeTarget)) {
|
|
1064
844
|
changes.push({
|
|
1065
845
|
event: "change",
|
|
1066
|
-
filename:
|
|
846
|
+
filename: path6.relative(absolutePathToEmailsDirectory, dependentPath)
|
|
1067
847
|
});
|
|
1068
848
|
}
|
|
1069
849
|
reload();
|
|
@@ -1071,6 +851,223 @@ var setupHotreloading = async (devServer2, emailDirRelativePath) => {
|
|
|
1071
851
|
return watcher;
|
|
1072
852
|
};
|
|
1073
853
|
|
|
854
|
+
// src/utils/preview/start-dev-server.ts
|
|
855
|
+
import http from "node:http";
|
|
856
|
+
import path9 from "node:path";
|
|
857
|
+
import url2 from "node:url";
|
|
858
|
+
import chalk from "chalk";
|
|
859
|
+
import { createJiti as createJiti2 } from "jiti";
|
|
860
|
+
import logSymbols3 from "log-symbols";
|
|
861
|
+
import ora2 from "ora";
|
|
862
|
+
|
|
863
|
+
// src/utils/preview/get-env-variables-for-preview-app.ts
|
|
864
|
+
import path7 from "node:path";
|
|
865
|
+
var getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, cwd) => {
|
|
866
|
+
return {
|
|
867
|
+
EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
|
|
868
|
+
EMAILS_DIR_ABSOLUTE_PATH: path7.resolve(cwd, relativePathToEmailsDirectory),
|
|
869
|
+
USER_PROJECT_LOCATION: cwd
|
|
870
|
+
};
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
// src/utils/preview/serve-static-file.ts
|
|
874
|
+
import { existsSync as existsSync2, promises as fs4 } from "node:fs";
|
|
875
|
+
import path8 from "node:path";
|
|
876
|
+
import { lookup } from "mime-types";
|
|
877
|
+
var serveStaticFile = async (res, parsedUrl, staticDirRelativePath) => {
|
|
878
|
+
const pathname = parsedUrl.pathname.replace("/static", "./static");
|
|
879
|
+
const ext = path8.parse(pathname).ext;
|
|
880
|
+
const staticBaseDir = path8.resolve(process.cwd(), staticDirRelativePath);
|
|
881
|
+
const fileAbsolutePath = path8.resolve(staticBaseDir, pathname);
|
|
882
|
+
if (!fileAbsolutePath.startsWith(staticBaseDir)) {
|
|
883
|
+
res.statusCode = 403;
|
|
884
|
+
res.end();
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
try {
|
|
888
|
+
const fileHandle = await fs4.open(fileAbsolutePath, "r");
|
|
889
|
+
const fileData = await fs4.readFile(fileHandle);
|
|
890
|
+
res.setHeader("Content-type", lookup(ext) || "text/plain");
|
|
891
|
+
res.end(fileData);
|
|
892
|
+
fileHandle.close();
|
|
893
|
+
} catch (exception) {
|
|
894
|
+
if (!existsSync2(fileAbsolutePath)) {
|
|
895
|
+
res.statusCode = 404;
|
|
896
|
+
res.end();
|
|
897
|
+
} else {
|
|
898
|
+
const sanitizedFilePath = fileAbsolutePath.replace(/\n|\r/g, "");
|
|
899
|
+
console.error(
|
|
900
|
+
`Could not read file at %s to be served, here's the exception:`,
|
|
901
|
+
sanitizedFilePath,
|
|
902
|
+
exception
|
|
903
|
+
);
|
|
904
|
+
res.statusCode = 500;
|
|
905
|
+
res.end(
|
|
906
|
+
"Could not read file to be served! Check your terminal for more information."
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
// src/utils/preview/start-dev-server.ts
|
|
913
|
+
var devServer;
|
|
914
|
+
var safeAsyncServerListen = (server, port) => {
|
|
915
|
+
return new Promise((resolve) => {
|
|
916
|
+
server.listen(port, () => {
|
|
917
|
+
resolve({ portAlreadyInUse: false });
|
|
918
|
+
});
|
|
919
|
+
server.on("error", (e) => {
|
|
920
|
+
if (e.code === "EADDRINUSE") {
|
|
921
|
+
resolve({ portAlreadyInUse: true });
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
});
|
|
925
|
+
};
|
|
926
|
+
var startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port) => {
|
|
927
|
+
const [majorNodeVersion] = process.versions.node.split(".");
|
|
928
|
+
if (majorNodeVersion && Number.parseInt(majorNodeVersion) < 18) {
|
|
929
|
+
console.error(
|
|
930
|
+
` ${logSymbols3.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 18 or higher.`
|
|
931
|
+
);
|
|
932
|
+
process.exit(1);
|
|
933
|
+
}
|
|
934
|
+
const previewServerLocation = await getPreviewServerLocation();
|
|
935
|
+
const previewServer = createJiti2(previewServerLocation);
|
|
936
|
+
const { default: next } = await previewServer.import("next");
|
|
937
|
+
devServer = http.createServer((req, res) => {
|
|
938
|
+
if (!req.url) {
|
|
939
|
+
res.end(404);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
const parsedUrl = url2.parse(req.url, true);
|
|
943
|
+
res.setHeader(
|
|
944
|
+
"Cache-Control",
|
|
945
|
+
"no-cache, max-age=0, must-revalidate, no-store"
|
|
946
|
+
);
|
|
947
|
+
res.setHeader("Pragma", "no-cache");
|
|
948
|
+
res.setHeader("Expires", "-1");
|
|
949
|
+
try {
|
|
950
|
+
if (parsedUrl.path?.includes("static/") && !parsedUrl.path.includes("_next/static/")) {
|
|
951
|
+
void serveStaticFile(res, parsedUrl, staticBaseDirRelativePath);
|
|
952
|
+
} else if (!isNextReady) {
|
|
953
|
+
void nextReadyPromise.then(
|
|
954
|
+
() => nextHandleRequest?.(req, res, parsedUrl)
|
|
955
|
+
);
|
|
956
|
+
} else {
|
|
957
|
+
void nextHandleRequest?.(req, res, parsedUrl);
|
|
958
|
+
}
|
|
959
|
+
} catch (e) {
|
|
960
|
+
console.error("caught error", e);
|
|
961
|
+
res.writeHead(500);
|
|
962
|
+
res.end();
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
const { portAlreadyInUse } = await safeAsyncServerListen(devServer, port);
|
|
966
|
+
if (!portAlreadyInUse) {
|
|
967
|
+
console.log(chalk.greenBright(` React Email ${package_default.version}`));
|
|
968
|
+
console.log(` Running preview at: http://localhost:${port}
|
|
969
|
+
`);
|
|
970
|
+
} else {
|
|
971
|
+
const nextPortToTry = port + 1;
|
|
972
|
+
console.warn(
|
|
973
|
+
` ${logSymbols3.warning} Port ${port} is already in use, trying ${nextPortToTry}`
|
|
974
|
+
);
|
|
975
|
+
return startDevServer(
|
|
976
|
+
emailsDirRelativePath,
|
|
977
|
+
staticBaseDirRelativePath,
|
|
978
|
+
nextPortToTry
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
devServer.on("close", async () => {
|
|
982
|
+
await app.close();
|
|
983
|
+
});
|
|
984
|
+
devServer.on("error", (e) => {
|
|
985
|
+
spinner.stopAndPersist({
|
|
986
|
+
symbol: logSymbols3.error,
|
|
987
|
+
text: `Preview Server had an error: ${e}`
|
|
988
|
+
});
|
|
989
|
+
process.exit(1);
|
|
990
|
+
});
|
|
991
|
+
const spinner = ora2({
|
|
992
|
+
text: "Getting react-email preview server ready...\n",
|
|
993
|
+
prefixText: " "
|
|
994
|
+
}).start();
|
|
995
|
+
registerSpinnerAutostopping(spinner);
|
|
996
|
+
const timeBeforeNextReady = performance.now();
|
|
997
|
+
process.env = {
|
|
998
|
+
NODE_ENV: "development",
|
|
999
|
+
...process.env,
|
|
1000
|
+
...getEnvVariablesForPreviewApp(
|
|
1001
|
+
// If we don't do normalization here, stuff like https://github.com/resend/react-email/issues/1354 happens.
|
|
1002
|
+
path9.normalize(emailsDirRelativePath),
|
|
1003
|
+
process.cwd()
|
|
1004
|
+
)
|
|
1005
|
+
};
|
|
1006
|
+
const app = next({
|
|
1007
|
+
// passing in env here does not get the environment variables there
|
|
1008
|
+
dev: false,
|
|
1009
|
+
conf: {
|
|
1010
|
+
images: {
|
|
1011
|
+
// This is to avoid the warning with sharp
|
|
1012
|
+
unoptimized: true
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
hostname: "localhost",
|
|
1016
|
+
port,
|
|
1017
|
+
dir: previewServerLocation
|
|
1018
|
+
});
|
|
1019
|
+
let isNextReady = false;
|
|
1020
|
+
const nextReadyPromise = app.prepare();
|
|
1021
|
+
try {
|
|
1022
|
+
await nextReadyPromise;
|
|
1023
|
+
} catch (exception) {
|
|
1024
|
+
spinner.stopAndPersist({
|
|
1025
|
+
symbol: logSymbols3.error,
|
|
1026
|
+
text: ` Preview Server had an error: ${exception}`
|
|
1027
|
+
});
|
|
1028
|
+
process.exit(1);
|
|
1029
|
+
}
|
|
1030
|
+
isNextReady = true;
|
|
1031
|
+
const nextHandleRequest = app.getRequestHandler();
|
|
1032
|
+
const secondsToNextReady = ((performance.now() - timeBeforeNextReady) / 1e3).toFixed(1);
|
|
1033
|
+
spinner.stopAndPersist({
|
|
1034
|
+
text: `Ready in ${secondsToNextReady}s
|
|
1035
|
+
`,
|
|
1036
|
+
symbol: logSymbols3.success
|
|
1037
|
+
});
|
|
1038
|
+
return devServer;
|
|
1039
|
+
};
|
|
1040
|
+
var makeExitHandler = (options) => (codeSignalOrError) => {
|
|
1041
|
+
if (typeof devServer !== "undefined") {
|
|
1042
|
+
console.log("\nshutting down dev server");
|
|
1043
|
+
devServer.close();
|
|
1044
|
+
devServer = void 0;
|
|
1045
|
+
}
|
|
1046
|
+
if (codeSignalOrError instanceof Error) {
|
|
1047
|
+
console.error(codeSignalOrError);
|
|
1048
|
+
}
|
|
1049
|
+
if (options?.shouldKillProcess) {
|
|
1050
|
+
process.exit(options.killWithErrorCode ? 1 : 0);
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
process.on("exit", makeExitHandler());
|
|
1054
|
+
process.on(
|
|
1055
|
+
"SIGINT",
|
|
1056
|
+
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
|
|
1057
|
+
);
|
|
1058
|
+
process.on(
|
|
1059
|
+
"SIGUSR1",
|
|
1060
|
+
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
|
|
1061
|
+
);
|
|
1062
|
+
process.on(
|
|
1063
|
+
"SIGUSR2",
|
|
1064
|
+
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
|
|
1065
|
+
);
|
|
1066
|
+
process.on(
|
|
1067
|
+
"uncaughtException",
|
|
1068
|
+
makeExitHandler({ shouldKillProcess: true, killWithErrorCode: true })
|
|
1069
|
+
);
|
|
1070
|
+
|
|
1074
1071
|
// src/utils/tree.ts
|
|
1075
1072
|
import { promises as fs5 } from "node:fs";
|
|
1076
1073
|
import os from "node:os";
|
|
@@ -1085,8 +1082,8 @@ var SYMBOLS = {
|
|
|
1085
1082
|
var getTreeLines = async (dirPath, depth, currentDepth = 0) => {
|
|
1086
1083
|
const base = process.cwd();
|
|
1087
1084
|
const dirFullpath = path10.resolve(base, dirPath);
|
|
1088
|
-
const
|
|
1089
|
-
let lines = [
|
|
1085
|
+
const dirname = path10.basename(dirFullpath);
|
|
1086
|
+
let lines = [dirname];
|
|
1090
1087
|
const dirStat = await fs5.stat(dirFullpath);
|
|
1091
1088
|
if (dirStat.isDirectory() && currentDepth < depth) {
|
|
1092
1089
|
const childDirents = await fs5.readdir(dirFullpath, { withFileTypes: true });
|
|
@@ -1215,15 +1212,15 @@ var renderingUtilitiesExporter = (emailTemplates) => ({
|
|
|
1215
1212
|
var getEmailTemplatesFromDirectory = (emailDirectory) => {
|
|
1216
1213
|
const templatePaths = [];
|
|
1217
1214
|
emailDirectory.emailFilenames.forEach(
|
|
1218
|
-
(
|
|
1215
|
+
(filename2) => templatePaths.push(path12.join(emailDirectory.absolutePath, filename2))
|
|
1219
1216
|
);
|
|
1220
1217
|
emailDirectory.subDirectories.forEach((directory) => {
|
|
1221
1218
|
templatePaths.push(...getEmailTemplatesFromDirectory(directory));
|
|
1222
1219
|
});
|
|
1223
1220
|
return templatePaths;
|
|
1224
1221
|
};
|
|
1225
|
-
var
|
|
1226
|
-
var require2 = createRequire(
|
|
1222
|
+
var filename = url3.fileURLToPath(import.meta.url);
|
|
1223
|
+
var require2 = createRequire(filename);
|
|
1227
1224
|
var exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirectoryPath, options) => {
|
|
1228
1225
|
if (fs8.existsSync(pathToWhereEmailMarkupShouldBeDumped)) {
|
|
1229
1226
|
fs8.rmSync(pathToWhereEmailMarkupShouldBeDumped, { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-email",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "A live preview of your emails right in your browser.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"email": "./dist/index.js"
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"tsup": "8.4.0",
|
|
50
50
|
"tsx": "4.19.3",
|
|
51
51
|
"typescript": "5.8.3",
|
|
52
|
-
"@react-email/components": "0.
|
|
52
|
+
"@react-email/components": "0.2.0"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsup-node",
|
|
56
|
+
"build:watch": "tsup-node --watch src",
|
|
56
57
|
"clean": "rm -rf dist",
|
|
57
|
-
"dev": "tsup-node --watch src",
|
|
58
58
|
"test": "vitest run",
|
|
59
59
|
"test:watch": "vitest"
|
|
60
60
|
}
|
package/readme.md
CHANGED
|
@@ -39,6 +39,22 @@ Generates the plain HTML files of your emails into a `out` directory.
|
|
|
39
39
|
npx react-email export
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
## Setting Up the Environment
|
|
43
|
+
|
|
44
|
+
When working in the CLI, a lot of friction can get introduced with installing it and rebuilding for every change. To avoid that, we have a script that can be linked globally to directly run the source code of the CLI. You can use it the same as you would the standard CLI.
|
|
45
|
+
|
|
46
|
+
### 1. Link `react-email` globally
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
pnpm link ./dev -g
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Run the CLI
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
email-dev [command] [flags]
|
|
56
|
+
```
|
|
57
|
+
|
|
42
58
|
## License
|
|
43
59
|
|
|
44
60
|
MIT License
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`checkImages() 1`] = `
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
"checks": [
|
|
7
|
+
{
|
|
8
|
+
"metadata": {
|
|
9
|
+
"alt": undefined,
|
|
10
|
+
},
|
|
11
|
+
"passed": false,
|
|
12
|
+
"type": "accessibility",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"passed": true,
|
|
16
|
+
"type": "syntax",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"passed": true,
|
|
20
|
+
"type": "security",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"metadata": {
|
|
24
|
+
"fetchStatusCode": 200,
|
|
25
|
+
},
|
|
26
|
+
"passed": true,
|
|
27
|
+
"type": "fetch_attempt",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"metadata": {
|
|
31
|
+
"byteCount": 26808,
|
|
32
|
+
},
|
|
33
|
+
"passed": true,
|
|
34
|
+
"type": "image_size",
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
"codeLocation": {
|
|
38
|
+
"column": 3,
|
|
39
|
+
"line": 2,
|
|
40
|
+
},
|
|
41
|
+
"source": "https://resend.com/static/brand/resend-icon-white.png",
|
|
42
|
+
"status": "warning",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"checks": [
|
|
46
|
+
{
|
|
47
|
+
"metadata": {
|
|
48
|
+
"alt": "codepen challenges",
|
|
49
|
+
},
|
|
50
|
+
"passed": true,
|
|
51
|
+
"type": "accessibility",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"passed": true,
|
|
55
|
+
"type": "syntax",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"passed": true,
|
|
59
|
+
"type": "security",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"metadata": {
|
|
63
|
+
"fetchStatusCode": 200,
|
|
64
|
+
},
|
|
65
|
+
"passed": true,
|
|
66
|
+
"type": "fetch_attempt",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"metadata": {
|
|
70
|
+
"byteCount": 111922,
|
|
71
|
+
},
|
|
72
|
+
"passed": true,
|
|
73
|
+
"type": "image_size",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
"codeLocation": {
|
|
77
|
+
"column": 3,
|
|
78
|
+
"line": 3,
|
|
79
|
+
},
|
|
80
|
+
"source": "/static/codepen-challengers.png",
|
|
81
|
+
"status": "success",
|
|
82
|
+
},
|
|
83
|
+
]
|
|
84
|
+
`;
|
|
@@ -30,8 +30,7 @@ export const getPreviewServerLocation = async () => {
|
|
|
30
30
|
let previewServerLocation!: string;
|
|
31
31
|
try {
|
|
32
32
|
previewServerLocation = path.dirname(
|
|
33
|
-
url.
|
|
34
|
-
.path!,
|
|
33
|
+
url.fileURLToPath(usersProject.esmResolve('@react-email/preview-server')),
|
|
35
34
|
);
|
|
36
35
|
} catch (_exception) {
|
|
37
36
|
await ensurePreviewServerInstalled(
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { isDev } from './start-dev-server.js';
|
|
3
2
|
|
|
4
3
|
export const getEnvVariablesForPreviewApp = (
|
|
5
4
|
relativePathToEmailsDirectory: string,
|
|
@@ -9,6 +8,5 @@ export const getEnvVariablesForPreviewApp = (
|
|
|
9
8
|
EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
|
|
10
9
|
EMAILS_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEmailsDirectory),
|
|
11
10
|
USER_PROJECT_LOCATION: cwd,
|
|
12
|
-
NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT: isDev ? 'true' : 'false',
|
|
13
11
|
} as const;
|
|
14
12
|
};
|
|
@@ -44,49 +44,21 @@ test('createDependencyGraph()', async () => {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
const initialDependencyGraph = convertPathsToAbsolute({
|
|
47
|
-
'../../../../package.json': {
|
|
48
|
-
dependencyPaths: [],
|
|
49
|
-
dependentPaths: ['../../packageJson.ts'],
|
|
50
|
-
moduleDependencies: [],
|
|
51
|
-
path: '../../../../package.json',
|
|
52
|
-
},
|
|
53
47
|
'create-dependency-graph.ts': {
|
|
54
48
|
path: 'create-dependency-graph.ts',
|
|
55
|
-
dependencyPaths: [
|
|
56
|
-
'../start-dev-server.ts',
|
|
57
|
-
'get-imported-modules.ts',
|
|
58
|
-
'resolve-path-aliases.ts',
|
|
59
|
-
],
|
|
49
|
+
dependencyPaths: ['get-imported-modules.ts', 'resolve-path-aliases.ts'],
|
|
60
50
|
dependentPaths: [
|
|
61
51
|
'create-dependency-graph.spec.ts',
|
|
62
52
|
'setup-hot-reloading.ts',
|
|
63
53
|
],
|
|
64
54
|
moduleDependencies: ['node:fs', 'node:path', 'chokidar/handler.js'],
|
|
65
55
|
},
|
|
66
|
-
'../../get-preview-server-location.ts': {
|
|
67
|
-
dependencyPaths: [],
|
|
68
|
-
dependentPaths: ['../../preview/start-dev-server.ts'],
|
|
69
|
-
moduleDependencies: ['node:path', 'node:url', 'jiti', 'nypm', 'prompts'],
|
|
70
|
-
path: '../../get-preview-server-location.ts',
|
|
71
|
-
},
|
|
72
|
-
'../../packageJson.ts': {
|
|
73
|
-
dependencyPaths: ['../../../../package.json'],
|
|
74
|
-
dependentPaths: ['../../preview/start-dev-server.ts'],
|
|
75
|
-
moduleDependencies: [],
|
|
76
|
-
path: '../../packageJson.ts',
|
|
77
|
-
},
|
|
78
56
|
'create-dependency-graph.spec.ts': {
|
|
79
57
|
path: 'create-dependency-graph.spec.ts',
|
|
80
58
|
dependencyPaths: ['create-dependency-graph.ts'],
|
|
81
59
|
dependentPaths: [],
|
|
82
60
|
moduleDependencies: ['node:fs', 'node:path'],
|
|
83
61
|
},
|
|
84
|
-
'../get-env-variables-for-preview-app.ts': {
|
|
85
|
-
dependencyPaths: ['../../preview/start-dev-server.ts'],
|
|
86
|
-
dependentPaths: ['../../preview/start-dev-server.ts'],
|
|
87
|
-
moduleDependencies: ['node:path'],
|
|
88
|
-
path: '../../preview/get-env-variables-for-preview-app.ts',
|
|
89
|
-
},
|
|
90
62
|
'./test/some-file.ts': {
|
|
91
63
|
dependencyPaths: [],
|
|
92
64
|
dependentPaths: [],
|
|
@@ -138,47 +110,6 @@ test('createDependencyGraph()', async () => {
|
|
|
138
110
|
'socket.io',
|
|
139
111
|
],
|
|
140
112
|
},
|
|
141
|
-
'../start-dev-server.ts': {
|
|
142
|
-
dependencyPaths: [
|
|
143
|
-
'../../register-spinner-autostopping.ts',
|
|
144
|
-
'../../get-preview-server-location.ts',
|
|
145
|
-
'../../packageJson.ts',
|
|
146
|
-
'../../preview/get-env-variables-for-preview-app.ts',
|
|
147
|
-
'../../preview/serve-static-file.ts',
|
|
148
|
-
],
|
|
149
|
-
path: '../start-dev-server.ts',
|
|
150
|
-
dependentPaths: [
|
|
151
|
-
'../../preview/get-env-variables-for-preview-app.ts',
|
|
152
|
-
'create-dependency-graph.ts',
|
|
153
|
-
],
|
|
154
|
-
moduleDependencies: [
|
|
155
|
-
'node:http',
|
|
156
|
-
'node:path',
|
|
157
|
-
'node:url',
|
|
158
|
-
'chalk',
|
|
159
|
-
'jiti',
|
|
160
|
-
'log-symbols',
|
|
161
|
-
'ora',
|
|
162
|
-
],
|
|
163
|
-
},
|
|
164
|
-
'../../preview/serve-static-file.ts': {
|
|
165
|
-
dependencyPaths: [],
|
|
166
|
-
dependentPaths: ['../../preview/start-dev-server.ts'],
|
|
167
|
-
moduleDependencies: [
|
|
168
|
-
'node:fs',
|
|
169
|
-
'node:http',
|
|
170
|
-
'node:path',
|
|
171
|
-
'node:url',
|
|
172
|
-
'mime-types',
|
|
173
|
-
],
|
|
174
|
-
path: '../../preview/serve-static-file.ts',
|
|
175
|
-
},
|
|
176
|
-
'../../register-spinner-autostopping.ts': {
|
|
177
|
-
dependencyPaths: [],
|
|
178
|
-
dependentPaths: ['../../preview/start-dev-server.ts'],
|
|
179
|
-
moduleDependencies: ['log-symbols', 'ora'],
|
|
180
|
-
path: '../../register-spinner-autostopping.ts',
|
|
181
|
-
},
|
|
182
113
|
'../../types/hot-reload-event.ts': {
|
|
183
114
|
dependencyPaths: [],
|
|
184
115
|
dependentPaths: ['../../types/hot-reload-change.ts'],
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, promises as fs, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import type { EventName } from 'chokidar/handler.js';
|
|
4
|
-
import { isDev } from '../start-dev-server.js';
|
|
5
4
|
import { getImportedModules } from './get-imported-modules.js';
|
|
6
5
|
import { resolvePathAliases } from './resolve-path-aliases.js';
|
|
7
6
|
|
|
@@ -134,8 +133,7 @@ export const createDependencyGraph = async (directory: string) => {
|
|
|
134
133
|
);
|
|
135
134
|
if (pathWithExtension) {
|
|
136
135
|
pathToDependencyFromDirectory = pathWithExtension;
|
|
137
|
-
} else
|
|
138
|
-
// only warn about this on development as it is probably going to be irrelevant otherwise
|
|
136
|
+
} else {
|
|
139
137
|
console.warn(
|
|
140
138
|
`Could not find index file for directory at ${pathToDependencyFromDirectory}. This is probably going to cause issues with both hot reloading and your code.`,
|
|
141
139
|
);
|
|
@@ -157,8 +155,7 @@ export const createDependencyGraph = async (directory: string) => {
|
|
|
157
155
|
|
|
158
156
|
if (pathWithEnsuredExtension) {
|
|
159
157
|
pathToDependencyFromDirectory = pathWithEnsuredExtension;
|
|
160
|
-
} else
|
|
161
|
-
// only warn about this on development as it is probably going to be irrelevant otherwise
|
|
158
|
+
} else {
|
|
162
159
|
console.warn(
|
|
163
160
|
`Could not find file at ${pathToDependencyFromDirectory}`,
|
|
164
161
|
);
|
|
@@ -38,7 +38,8 @@ export const serveStaticFile = async (
|
|
|
38
38
|
} else {
|
|
39
39
|
const sanitizedFilePath = fileAbsolutePath.replace(/\n|\r/g, '');
|
|
40
40
|
console.error(
|
|
41
|
-
`Could not read file at
|
|
41
|
+
`Could not read file at %s to be served, here's the exception:`,
|
|
42
|
+
sanitizedFilePath,
|
|
42
43
|
exception,
|
|
43
44
|
);
|
|
44
45
|
|
|
@@ -27,11 +27,6 @@ const safeAsyncServerListen = (server: http.Server, port: number) => {
|
|
|
27
27
|
});
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
const filename = url.fileURLToPath(import.meta.url);
|
|
31
|
-
const dirname = path.dirname(filename);
|
|
32
|
-
|
|
33
|
-
export const isDev = !dirname.includes('dist');
|
|
34
|
-
|
|
35
30
|
export const startDevServer = async (
|
|
36
31
|
emailsDirRelativePath: string,
|
|
37
32
|
staticBaseDirRelativePath: string,
|
|
@@ -143,7 +138,7 @@ export const startDevServer = async (
|
|
|
143
138
|
|
|
144
139
|
const app = next({
|
|
145
140
|
// passing in env here does not get the environment variables there
|
|
146
|
-
dev:
|
|
141
|
+
dev: false,
|
|
147
142
|
conf: {
|
|
148
143
|
images: {
|
|
149
144
|
// This is to avoid the warning with sharp
|