react-email 1.5.2 → 1.6.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/dist/commands/dev.js +16 -8
- package/dist/commands/export.d.ts +2 -1
- package/dist/commands/export.js +3 -3
- package/dist/index.js +3 -2
- package/package.json +5 -4
- package/preview/src/components/code-container.tsx +114 -0
- package/preview/src/components/code.tsx +3 -50
- package/preview/src/components/send.tsx +6 -1
- package/preview/src/pages/preview/[slug].tsx +13 -3
- package/preview/src/utils/language-map.ts +6 -0
- package/readme.md +1 -1
- package/source/commands/dev.ts +18 -8
- package/source/commands/{exportTemplates.ts → export.ts} +10 -5
- package/source/index.ts +5 -2
package/dist/commands/dev.js
CHANGED
|
@@ -12,6 +12,7 @@ const utils_2 = require("../_preview/utils");
|
|
|
12
12
|
const root_1 = require("../_preview/root");
|
|
13
13
|
const pages_1 = require("../_preview/pages");
|
|
14
14
|
const cpy_1 = __importDefault(require("cpy"));
|
|
15
|
+
const detect_package_manager_1 = require("detect-package-manager");
|
|
15
16
|
const log_symbols_1 = __importDefault(require("log-symbols"));
|
|
16
17
|
const ora_1 = __importDefault(require("ora"));
|
|
17
18
|
const read_pkg_1 = __importDefault(require("read-pkg"));
|
|
@@ -20,12 +21,19 @@ const styles_1 = require("../_preview/styles");
|
|
|
20
21
|
const dev = async () => {
|
|
21
22
|
try {
|
|
22
23
|
const hasReactEmailDirectory = (0, utils_1.checkDirectoryExist)(utils_1.REACT_EMAIL_ROOT);
|
|
24
|
+
let packageManager;
|
|
25
|
+
try {
|
|
26
|
+
packageManager = await (0, detect_package_manager_1.detect)({ cwd: utils_1.CURRENT_PATH });
|
|
27
|
+
}
|
|
28
|
+
catch (_) {
|
|
29
|
+
packageManager = 'yarn';
|
|
30
|
+
}
|
|
23
31
|
if (hasReactEmailDirectory) {
|
|
24
32
|
const isUpToDate = await (0, utils_1.checkPackageIsUpToDate)();
|
|
25
33
|
if (isUpToDate) {
|
|
26
34
|
await Promise.all([generateEmailsPreview(), syncPkg()]);
|
|
27
|
-
await installDependencies();
|
|
28
|
-
shelljs_1.default.exec(
|
|
35
|
+
await installDependencies(packageManager);
|
|
36
|
+
shelljs_1.default.exec(`${packageManager} run dev`, { async: true });
|
|
29
37
|
(0, utils_1.watcher)();
|
|
30
38
|
return;
|
|
31
39
|
}
|
|
@@ -35,8 +43,8 @@ const dev = async () => {
|
|
|
35
43
|
await createAppDirectories();
|
|
36
44
|
await createAppFiles();
|
|
37
45
|
await Promise.all([generateEmailsPreview(), syncPkg()]);
|
|
38
|
-
await installDependencies();
|
|
39
|
-
shelljs_1.default.exec(
|
|
46
|
+
await installDependencies(packageManager);
|
|
47
|
+
shelljs_1.default.exec(`${packageManager} run dev`, { async: true });
|
|
40
48
|
(0, utils_1.watcher)();
|
|
41
49
|
}
|
|
42
50
|
catch (error) {
|
|
@@ -82,12 +90,12 @@ const createAppFiles = async () => {
|
|
|
82
90
|
return fs_1.default.promises.writeFile(location, file.content);
|
|
83
91
|
});
|
|
84
92
|
};
|
|
85
|
-
const pageCreation = pages_1.pages.map((page) => {
|
|
93
|
+
const pageCreation = pages_1.pages.map(async (page) => {
|
|
86
94
|
const location = page.dir
|
|
87
95
|
? `${utils_1.SRC_PATH}/pages/${page.dir}/${page.title}`
|
|
88
96
|
: `${utils_1.SRC_PATH}/pages/${page.title}`;
|
|
89
97
|
if (page.dir) {
|
|
90
|
-
(0, utils_1.createDirectory)(`${utils_1.SRC_PATH}/pages/${page.dir}`);
|
|
98
|
+
await (0, utils_1.createDirectory)(`${utils_1.SRC_PATH}/pages/${page.dir}`);
|
|
91
99
|
}
|
|
92
100
|
return fs_1.default.promises.writeFile(location, page.content);
|
|
93
101
|
});
|
|
@@ -167,10 +175,10 @@ const syncPkg = async () => {
|
|
|
167
175
|
};
|
|
168
176
|
await fs_1.default.promises.writeFile(path_1.default.join(utils_1.REACT_EMAIL_ROOT, 'package.json'), JSON.stringify(pkg));
|
|
169
177
|
};
|
|
170
|
-
const installDependencies = async () => {
|
|
178
|
+
const installDependencies = async (packageManager) => {
|
|
171
179
|
const spinner = (0, ora_1.default)('Installing dependencies...\n').start();
|
|
172
180
|
shelljs_1.default.cd(path_1.default.join(utils_1.REACT_EMAIL_ROOT));
|
|
173
|
-
shelljs_1.default.exec(
|
|
181
|
+
shelljs_1.default.exec(`${packageManager} install`);
|
|
174
182
|
spinner.stopAndPersist({
|
|
175
183
|
symbol: log_symbols_1.default.success,
|
|
176
184
|
text: 'Dependencies installed',
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { Options } from '@react-email/render';
|
|
2
|
+
export declare const exportTemplates: (outDir: string, options: Options) => Promise<void>;
|
package/dist/commands/export.js
CHANGED
|
@@ -42,7 +42,7 @@ const utils_1 = require("../utils");
|
|
|
42
42
|
files. Then these `.js` files are imported dynamically and rendered to `.html` files
|
|
43
43
|
using the `render` function.
|
|
44
44
|
*/
|
|
45
|
-
const exportTemplates = async (outDir,
|
|
45
|
+
const exportTemplates = async (outDir, options) => {
|
|
46
46
|
const spinner = (0, ora_1.default)('Preparing files...\n').start();
|
|
47
47
|
const allTemplates = glob_1.glob.sync((0, normalize_path_1.default)(`${utils_1.CLIENT_EMAILS_PATH}/*.{tsx,jsx}`));
|
|
48
48
|
esbuild_1.default.buildSync({
|
|
@@ -57,8 +57,8 @@ const exportTemplates = async (outDir, pretty) => {
|
|
|
57
57
|
});
|
|
58
58
|
for (const template of allBuiltTemplates) {
|
|
59
59
|
const component = await Promise.resolve().then(() => __importStar(require(template)));
|
|
60
|
-
const rendered = (0, render_1.render)(component.default(),
|
|
61
|
-
const htmlPath = template.replace('.js', '.html');
|
|
60
|
+
const rendered = (0, render_1.render)(component.default(), options);
|
|
61
|
+
const htmlPath = template.replace('.js', options.plainText ? '.txt' : '.html');
|
|
62
62
|
(0, fs_1.writeFileSync)(htmlPath, rendered);
|
|
63
63
|
(0, fs_1.unlinkSync)(template);
|
|
64
64
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const extra_typings_1 = require("@commander-js/extra-typings");
|
|
5
5
|
const constants_1 = require("./utils/constants");
|
|
6
6
|
const dev_1 = require("./commands/dev");
|
|
7
|
-
const
|
|
7
|
+
const export_1 = require("./commands/export");
|
|
8
8
|
extra_typings_1.program
|
|
9
9
|
.name(constants_1.PACKAGE_NAME)
|
|
10
10
|
.description('A live preview of your emails right in your browser')
|
|
@@ -18,5 +18,6 @@ extra_typings_1.program
|
|
|
18
18
|
.description('Build the templates to the `out` directory')
|
|
19
19
|
.option('--outDir <path>', 'Output directory', 'out')
|
|
20
20
|
.option('-p, --pretty', 'Pretty print the output', false)
|
|
21
|
-
.
|
|
21
|
+
.option('-t, --plainText', 'Set output format as plain Text', false)
|
|
22
|
+
.action(({ outDir, pretty, plainText }) => (0, export_1.exportTemplates)(outDir, { pretty, plainText }));
|
|
22
23
|
extra_typings_1.program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-email",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "A live preview of your emails right in your browser.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"email": "./dist/index.js"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "https://github.com/
|
|
21
|
+
"url": "https://github.com/resendlabs/react-email.git",
|
|
22
22
|
"directory": "packages/react-email"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
@@ -26,14 +26,15 @@
|
|
|
26
26
|
"email"
|
|
27
27
|
],
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
29
|
+
"node": ">=16.0.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@commander-js/extra-typings": "9.4.1",
|
|
33
|
-
"@react-email/render": "0.0.
|
|
33
|
+
"@react-email/render": "0.0.4",
|
|
34
34
|
"chokidar": "3.5.3",
|
|
35
35
|
"commander": "9.4.1",
|
|
36
36
|
"cpy": "8.1.2",
|
|
37
|
+
"detect-package-manager": "2.0.1",
|
|
37
38
|
"esbuild": "0.16.4",
|
|
38
39
|
"glob": "8.0.3",
|
|
39
40
|
"log-symbols": "4.1.0",
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Language } from 'prism-react-renderer';
|
|
2
|
+
import { IconButton } from './icon-button';
|
|
3
|
+
import { IconClipboard } from './icon-clipboard';
|
|
4
|
+
import { IconDownload } from './icon-download';
|
|
5
|
+
import { IconCheck } from './icon-check';
|
|
6
|
+
import { copyTextToClipboard } from '../utils';
|
|
7
|
+
import languageMap from '../utils/language-map';
|
|
8
|
+
import { Tooltip } from './tooltip';
|
|
9
|
+
import { Code } from './code';
|
|
10
|
+
import * as React from 'react';
|
|
11
|
+
|
|
12
|
+
interface CodeContainerProps {
|
|
13
|
+
markups: MarkupProps[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface MarkupProps {
|
|
17
|
+
language: Language;
|
|
18
|
+
content: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const CodeContainer: React.FC<Readonly<CodeContainerProps>> = ({
|
|
22
|
+
markups,
|
|
23
|
+
}) => {
|
|
24
|
+
const [isCopied, setIsCopied] = React.useState(false);
|
|
25
|
+
const [activeTab, setActiveTab] = React.useState(markups[0].language);
|
|
26
|
+
let file = null;
|
|
27
|
+
let url = null;
|
|
28
|
+
|
|
29
|
+
const renderDownloadIcon = () => {
|
|
30
|
+
let value = markups.filter((markup) => markup.language === activeTab);
|
|
31
|
+
file = new File([value[0].content], `email.${value[0].language}`);
|
|
32
|
+
url = URL.createObjectURL(file);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<a href={url} download={file.name}>
|
|
36
|
+
<IconDownload />
|
|
37
|
+
</a>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const renderClipboardIcon = () => {
|
|
42
|
+
const handleClipboard = async () => {
|
|
43
|
+
const activeContent = markups.filter(({ language }) => {
|
|
44
|
+
return activeTab === language;
|
|
45
|
+
});
|
|
46
|
+
setIsCopied(true);
|
|
47
|
+
await copyTextToClipboard(activeContent[0].content);
|
|
48
|
+
setTimeout(() => setIsCopied(false), 3000);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<IconButton onClick={handleClipboard}>
|
|
53
|
+
{isCopied ? <IconCheck /> : <IconClipboard />}
|
|
54
|
+
</IconButton>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
setIsCopied(false);
|
|
60
|
+
}, [activeTab]);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<pre
|
|
64
|
+
className={
|
|
65
|
+
'border-slate-6 relative w-full items-center overflow-auto whitespace-pre rounded-md border text-sm backdrop-blur-md'
|
|
66
|
+
}
|
|
67
|
+
style={{
|
|
68
|
+
lineHeight: '130%',
|
|
69
|
+
background:
|
|
70
|
+
'linear-gradient(145.37deg, rgba(255, 255, 255, 0.09) -8.75%, rgba(255, 255, 255, 0.027) 83.95%)',
|
|
71
|
+
boxShadow: 'rgb(0 0 0 / 10%) 0px 5px 30px -5px',
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
<div className="h-9 border-b border-slate-6">
|
|
75
|
+
<div className="py-[10px] px-4 text-xs flex gap-8">
|
|
76
|
+
{markups.map(({ language }) => {
|
|
77
|
+
return (
|
|
78
|
+
<div key={language}>
|
|
79
|
+
<button
|
|
80
|
+
className={`${activeTab !== language && 'opacity-25'}`}
|
|
81
|
+
onClick={() => setActiveTab(language)}
|
|
82
|
+
>
|
|
83
|
+
{languageMap[language]}
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
})}
|
|
88
|
+
</div>
|
|
89
|
+
<Tooltip>
|
|
90
|
+
<Tooltip.Trigger className="absolute top-2 right-2 hidden md:block">
|
|
91
|
+
{renderClipboardIcon()}
|
|
92
|
+
</Tooltip.Trigger>
|
|
93
|
+
<Tooltip.Content>Copy to Clipboard</Tooltip.Content>
|
|
94
|
+
</Tooltip>
|
|
95
|
+
<Tooltip>
|
|
96
|
+
<Tooltip.Trigger className="text-gray-11 absolute top-2 right-8 hidden md:block">
|
|
97
|
+
{renderDownloadIcon()}
|
|
98
|
+
</Tooltip.Trigger>
|
|
99
|
+
<Tooltip.Content>Download</Tooltip.Content>
|
|
100
|
+
</Tooltip>
|
|
101
|
+
</div>
|
|
102
|
+
{markups.map(({ language, content }) => {
|
|
103
|
+
return (
|
|
104
|
+
<div
|
|
105
|
+
className={`${activeTab !== language && 'hidden'}`}
|
|
106
|
+
key={language}
|
|
107
|
+
>
|
|
108
|
+
<Code language={language}>{content}</Code>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
})}
|
|
112
|
+
</pre>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import classnames from 'classnames';
|
|
2
2
|
import Highlight, { defaultProps, Language } from 'prism-react-renderer';
|
|
3
|
-
import { IconButton } from './icon-button';
|
|
4
|
-
import { IconClipboard } from './icon-clipboard';
|
|
5
|
-
import { IconDownload } from './icon-download';
|
|
6
|
-
import { IconCheck } from './icon-check';
|
|
7
|
-
import { copyTextToClipboard } from '../utils';
|
|
8
|
-
import { Tooltip } from './tooltip';
|
|
9
3
|
import * as React from 'react';
|
|
10
4
|
|
|
11
5
|
interface CodeProps {
|
|
@@ -50,9 +44,7 @@ const theme = {
|
|
|
50
44
|
|
|
51
45
|
export const Code: React.FC<Readonly<CodeProps>> = ({
|
|
52
46
|
children,
|
|
53
|
-
className,
|
|
54
47
|
language = 'html',
|
|
55
|
-
...props
|
|
56
48
|
}) => {
|
|
57
49
|
const [isCopied, setIsCopied] = React.useState(false);
|
|
58
50
|
const value = children.trim();
|
|
@@ -68,53 +60,14 @@ export const Code: React.FC<Readonly<CodeProps>> = ({
|
|
|
68
60
|
language={language as Language}
|
|
69
61
|
>
|
|
70
62
|
{({ tokens, getLineProps, getTokenProps }) => (
|
|
71
|
-
|
|
72
|
-
className={classnames(
|
|
73
|
-
'border-slate-6 relative w-full items-center overflow-auto whitespace-pre rounded-md border text-sm backdrop-blur-md',
|
|
74
|
-
className,
|
|
75
|
-
)}
|
|
76
|
-
style={{
|
|
77
|
-
lineHeight: '130%',
|
|
78
|
-
background:
|
|
79
|
-
'linear-gradient(145.37deg, rgba(255, 255, 255, 0.09) -8.75%, rgba(255, 255, 255, 0.027) 83.95%)',
|
|
80
|
-
boxShadow: 'rgb(0 0 0 / 10%) 0px 5px 30px -5px',
|
|
81
|
-
}}
|
|
82
|
-
>
|
|
83
|
-
<div className="h-9 border-b border-slate-6">
|
|
84
|
-
<div className="py-[10px] px-4 text-xs">
|
|
85
|
-
{language === 'jsx' ? 'React' : 'HTML'}
|
|
86
|
-
</div>
|
|
87
|
-
<Tooltip>
|
|
88
|
-
<Tooltip.Trigger className="absolute top-2 right-2 hidden md:block">
|
|
89
|
-
<IconButton
|
|
90
|
-
onClick={async () => {
|
|
91
|
-
setIsCopied(true);
|
|
92
|
-
await copyTextToClipboard(value);
|
|
93
|
-
setTimeout(() => setIsCopied(false), 3000);
|
|
94
|
-
}}
|
|
95
|
-
>
|
|
96
|
-
{isCopied ? <IconCheck /> : <IconClipboard />}
|
|
97
|
-
</IconButton>
|
|
98
|
-
</Tooltip.Trigger>
|
|
99
|
-
<Tooltip.Content>Copy to Clipboard</Tooltip.Content>
|
|
100
|
-
</Tooltip>
|
|
101
|
-
|
|
102
|
-
<Tooltip>
|
|
103
|
-
<Tooltip.Trigger className="text-gray-11 absolute top-2 right-8 hidden md:block">
|
|
104
|
-
<a href={url} download={file.name}>
|
|
105
|
-
<IconDownload />
|
|
106
|
-
</a>
|
|
107
|
-
</Tooltip.Trigger>
|
|
108
|
-
<Tooltip.Content>Download</Tooltip.Content>
|
|
109
|
-
</Tooltip>
|
|
110
|
-
</div>
|
|
111
|
-
|
|
63
|
+
<>
|
|
112
64
|
<div
|
|
113
65
|
className="absolute right-0 top-0 h-px w-[200px]"
|
|
114
66
|
style={{
|
|
115
67
|
background:
|
|
116
68
|
'linear-gradient(90deg, rgba(56, 189, 248, 0) 0%, rgba(56, 189, 248, 0) 0%, rgba(232, 232, 232, 0.2) 33.02%, rgba(143, 143, 143, 0.6719) 64.41%, rgba(236, 72, 153, 0) 98.93%)',
|
|
117
69
|
}}
|
|
70
|
+
|
|
118
71
|
/>
|
|
119
72
|
<div className="p-4">
|
|
120
73
|
{tokens.map((line, i) => {
|
|
@@ -153,7 +106,7 @@ export const Code: React.FC<Readonly<CodeProps>> = ({
|
|
|
153
106
|
'linear-gradient(90deg, rgba(56, 189, 248, 0) 0%, rgba(56, 189, 248, 0) 0%, rgba(232, 232, 232, 0.2) 33.02%, rgba(143, 143, 143, 0.6719) 64.41%, rgba(236, 72, 153, 0) 98.93%)',
|
|
154
107
|
}}
|
|
155
108
|
/>
|
|
156
|
-
|
|
109
|
+
</>
|
|
157
110
|
)}
|
|
158
111
|
</Highlight>
|
|
159
112
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { inter } from '../pages/_app';
|
|
2
2
|
import { Button } from './button';
|
|
3
|
+
import { Text } from './text';
|
|
3
4
|
import * as Popover from '@radix-ui/react-popover';
|
|
4
5
|
import * as React from 'react';
|
|
5
6
|
|
|
@@ -89,7 +90,11 @@ export const Send = ({ markup }: { markup: string }) => {
|
|
|
89
90
|
type="checkbox"
|
|
90
91
|
className="appearance-none checked:bg-blue-500"
|
|
91
92
|
/>
|
|
92
|
-
<div className="flex items-center justify-
|
|
93
|
+
<div className="flex items-center justify-between">
|
|
94
|
+
<Text className="inline-block" size="1">
|
|
95
|
+
Powered by{' '}
|
|
96
|
+
<a className="hover:text-slate-12 transition ease-in-out duration-300" href="https://resend.com" target="_blank" rel="noreferrer">Resend</a>
|
|
97
|
+
</Text>
|
|
93
98
|
<Button
|
|
94
99
|
type="submit"
|
|
95
100
|
disabled={subject.length === 0 || to.length === 0 || isSending}
|
|
@@ -4,11 +4,17 @@ import path from 'path';
|
|
|
4
4
|
import { render } from '@react-email/render';
|
|
5
5
|
import { GetStaticPaths } from 'next';
|
|
6
6
|
import { Layout } from '../../components/layout';
|
|
7
|
+
import { CodeContainer } from '../../components/code-container';
|
|
7
8
|
import { Code } from '../../components';
|
|
8
9
|
import Head from 'next/head';
|
|
9
10
|
import { useRouter } from 'next/router';
|
|
10
11
|
|
|
11
|
-
interface PreviewProps {
|
|
12
|
+
interface PreviewProps {
|
|
13
|
+
navItems: string;
|
|
14
|
+
markup: string;
|
|
15
|
+
reactMarkup: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
}
|
|
12
18
|
|
|
13
19
|
export const CONTENT_DIR = 'emails';
|
|
14
20
|
|
|
@@ -100,8 +106,12 @@ const Preview: React.FC<Readonly<PreviewProps>> = ({
|
|
|
100
106
|
/>
|
|
101
107
|
) : (
|
|
102
108
|
<div className="flex gap-6 mx-auto p-6">
|
|
103
|
-
<
|
|
104
|
-
|
|
109
|
+
<CodeContainer
|
|
110
|
+
markups={[
|
|
111
|
+
{ language: 'jsx', content: reactMarkup },
|
|
112
|
+
{ language: 'markup', content: markup },
|
|
113
|
+
]}
|
|
114
|
+
/>
|
|
105
115
|
</div>
|
|
106
116
|
)}
|
|
107
117
|
</Layout>
|
package/readme.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<div align="center">
|
|
7
7
|
<a href="https://react.email">Website</a>
|
|
8
8
|
<span> · </span>
|
|
9
|
-
<a href="https://github.com/
|
|
9
|
+
<a href="https://github.com/resendlabs/react-email">GitHub</a>
|
|
10
10
|
<span> · </span>
|
|
11
11
|
<a href="https://react.email/discord">Discord</a>
|
|
12
12
|
</div>
|
package/source/commands/dev.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
checkPackageIsUpToDate,
|
|
5
5
|
CLIENT_EMAILS_PATH,
|
|
6
6
|
createDirectory,
|
|
7
|
+
CURRENT_PATH,
|
|
7
8
|
PACKAGE_EMAILS_PATH,
|
|
8
9
|
REACT_EMAIL_ROOT,
|
|
9
10
|
SRC_PATH,
|
|
@@ -19,6 +20,7 @@ import { utils } from '../_preview/utils';
|
|
|
19
20
|
import { root } from '../_preview/root';
|
|
20
21
|
import { pages } from '../_preview/pages';
|
|
21
22
|
import copy from 'cpy';
|
|
23
|
+
import { detect as detectPackageManager } from 'detect-package-manager';
|
|
22
24
|
import logSymbols from 'log-symbols';
|
|
23
25
|
import ora from 'ora';
|
|
24
26
|
import readPackage from 'read-pkg';
|
|
@@ -28,14 +30,20 @@ import { styles } from '../_preview/styles';
|
|
|
28
30
|
export const dev = async () => {
|
|
29
31
|
try {
|
|
30
32
|
const hasReactEmailDirectory = checkDirectoryExist(REACT_EMAIL_ROOT);
|
|
33
|
+
let packageManager: PackageManager;
|
|
34
|
+
try {
|
|
35
|
+
packageManager = await detectPackageManager({ cwd: CURRENT_PATH });
|
|
36
|
+
} catch (_) {
|
|
37
|
+
packageManager = 'yarn';
|
|
38
|
+
}
|
|
31
39
|
|
|
32
40
|
if (hasReactEmailDirectory) {
|
|
33
41
|
const isUpToDate = await checkPackageIsUpToDate();
|
|
34
42
|
|
|
35
43
|
if (isUpToDate) {
|
|
36
44
|
await Promise.all([generateEmailsPreview(), syncPkg()]);
|
|
37
|
-
await installDependencies();
|
|
38
|
-
shell.exec(
|
|
45
|
+
await installDependencies(packageManager);
|
|
46
|
+
shell.exec(`${packageManager} run dev`, { async: true });
|
|
39
47
|
watcher();
|
|
40
48
|
return;
|
|
41
49
|
}
|
|
@@ -47,8 +55,8 @@ export const dev = async () => {
|
|
|
47
55
|
await createAppDirectories();
|
|
48
56
|
await createAppFiles();
|
|
49
57
|
await Promise.all([generateEmailsPreview(), syncPkg()]);
|
|
50
|
-
await installDependencies();
|
|
51
|
-
shell.exec(
|
|
58
|
+
await installDependencies(packageManager);
|
|
59
|
+
shell.exec(`${packageManager} run dev`, { async: true });
|
|
52
60
|
watcher();
|
|
53
61
|
} catch (error) {
|
|
54
62
|
await watcherInstance.close();
|
|
@@ -97,13 +105,13 @@ const createAppFiles = async () => {
|
|
|
97
105
|
});
|
|
98
106
|
};
|
|
99
107
|
|
|
100
|
-
const pageCreation = pages.map((page) => {
|
|
108
|
+
const pageCreation = pages.map(async (page) => {
|
|
101
109
|
const location = page.dir
|
|
102
110
|
? `${SRC_PATH}/pages/${page.dir}/${page.title}`
|
|
103
111
|
: `${SRC_PATH}/pages/${page.title}`;
|
|
104
112
|
|
|
105
113
|
if (page.dir) {
|
|
106
|
-
createDirectory(`${SRC_PATH}/pages/${page.dir}`);
|
|
114
|
+
await createDirectory(`${SRC_PATH}/pages/${page.dir}`);
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
return fs.promises.writeFile(location, page.content);
|
|
@@ -218,11 +226,13 @@ const syncPkg = async () => {
|
|
|
218
226
|
);
|
|
219
227
|
};
|
|
220
228
|
|
|
221
|
-
|
|
229
|
+
type PackageManager = 'yarn' | 'npm' | 'pnpm';
|
|
230
|
+
|
|
231
|
+
const installDependencies = async (packageManager: PackageManager) => {
|
|
222
232
|
const spinner = ora('Installing dependencies...\n').start();
|
|
223
233
|
|
|
224
234
|
shell.cd(path.join(REACT_EMAIL_ROOT));
|
|
225
|
-
shell.exec(
|
|
235
|
+
shell.exec(`${packageManager} install`);
|
|
226
236
|
spinner.stopAndPersist({
|
|
227
237
|
symbol: logSymbols.success,
|
|
228
238
|
text: 'Dependencies installed',
|
|
@@ -3,7 +3,7 @@ import esbuild from 'esbuild';
|
|
|
3
3
|
import tree from 'tree-node-cli';
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import logSymbols from 'log-symbols';
|
|
6
|
-
import { render } from '@react-email/render';
|
|
6
|
+
import { render, Options } from '@react-email/render';
|
|
7
7
|
import { unlinkSync, writeFileSync } from 'fs';
|
|
8
8
|
import copy from 'cpy';
|
|
9
9
|
import normalize from 'normalize-path';
|
|
@@ -14,9 +14,11 @@ import { checkDirectoryExist, CLIENT_EMAILS_PATH } from '../utils';
|
|
|
14
14
|
files. Then these `.js` files are imported dynamically and rendered to `.html` files
|
|
15
15
|
using the `render` function.
|
|
16
16
|
*/
|
|
17
|
-
export const exportTemplates = async (outDir: string,
|
|
17
|
+
export const exportTemplates = async (outDir: string, options: Options) => {
|
|
18
18
|
const spinner = ora('Preparing files...\n').start();
|
|
19
|
-
const allTemplates = glob.sync(
|
|
19
|
+
const allTemplates = glob.sync(
|
|
20
|
+
normalize(`${CLIENT_EMAILS_PATH}/*.{tsx,jsx}`),
|
|
21
|
+
);
|
|
20
22
|
|
|
21
23
|
esbuild.buildSync({
|
|
22
24
|
bundle: true,
|
|
@@ -32,8 +34,11 @@ export const exportTemplates = async (outDir: string, pretty: boolean) => {
|
|
|
32
34
|
|
|
33
35
|
for (const template of allBuiltTemplates) {
|
|
34
36
|
const component = await import(template);
|
|
35
|
-
const rendered = render(component.default(),
|
|
36
|
-
const htmlPath = template.replace(
|
|
37
|
+
const rendered = render(component.default(), options);
|
|
38
|
+
const htmlPath = template.replace(
|
|
39
|
+
'.js',
|
|
40
|
+
options.plainText ? '.txt' : '.html',
|
|
41
|
+
);
|
|
37
42
|
writeFileSync(htmlPath, rendered);
|
|
38
43
|
unlinkSync(template);
|
|
39
44
|
}
|
package/source/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { program } from '@commander-js/extra-typings';
|
|
3
3
|
import { PACKAGE_NAME } from './utils/constants';
|
|
4
4
|
import { dev } from './commands/dev';
|
|
5
|
-
import { exportTemplates } from './commands/
|
|
5
|
+
import { exportTemplates } from './commands/export';
|
|
6
6
|
|
|
7
7
|
program
|
|
8
8
|
.name(PACKAGE_NAME)
|
|
@@ -19,6 +19,9 @@ program
|
|
|
19
19
|
.description('Build the templates to the `out` directory')
|
|
20
20
|
.option('--outDir <path>', 'Output directory', 'out')
|
|
21
21
|
.option('-p, --pretty', 'Pretty print the output', false)
|
|
22
|
-
.
|
|
22
|
+
.option('-t, --plainText', 'Set output format as plain Text', false)
|
|
23
|
+
.action(({ outDir, pretty, plainText }) =>
|
|
24
|
+
exportTemplates(outDir, { pretty, plainText }),
|
|
25
|
+
);
|
|
23
26
|
|
|
24
27
|
program.parse();
|