react-email 6.1.3 → 6.1.5
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 +14 -0
- package/dist/cli/index.mjs +171 -41
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# react-email
|
|
2
2
|
|
|
3
|
+
## 6.1.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1a61cb0: Avoid OOM when running `email export` on projects with many templates. esbuild builds now run in batches of 10 entry points, and the render phase runs each batch of 25 templates inside a `worker_threads` worker so V8 isolate memory is reclaimed between batches.
|
|
8
|
+
|
|
9
|
+
## 6.1.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 1c386ce: Avoid spamming each spinner frame as a new line on non-TTY streams (CI logs, pipes, dumb terminals). The spinner now logs each status text once instead of redrawing animated frames when the output is not a TTY.
|
|
14
|
+
- ad6a9de: - deprecate packageManager CLI option for `email build`, only supporting npm
|
|
15
|
+
- ensure `email build` dependency installation includes dev dependencies
|
|
16
|
+
|
|
3
17
|
## 6.1.3
|
|
4
18
|
|
|
5
19
|
## 6.1.2
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { program } from "commander";
|
|
4
|
+
import { Option, program } from "commander";
|
|
5
5
|
import * as fs$1 from "node:fs";
|
|
6
|
-
import fs, { existsSync, promises, statSync
|
|
6
|
+
import fs, { existsSync, promises, statSync } from "node:fs";
|
|
7
7
|
import * as path$2 from "node:path";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import url, { fileURLToPath } from "node:url";
|
|
@@ -31,7 +31,8 @@ import os from "node:os";
|
|
|
31
31
|
import Conf from "conf";
|
|
32
32
|
import * as nodeUtil from "node:util";
|
|
33
33
|
import { lookup } from "mime-types";
|
|
34
|
-
import {
|
|
34
|
+
import { Worker } from "node:worker_threads";
|
|
35
|
+
import { build, stop } from "esbuild";
|
|
35
36
|
import { glob } from "glob";
|
|
36
37
|
import normalize$1 from "normalize-path";
|
|
37
38
|
//#region \0rolldown/runtime.js
|
|
@@ -6522,7 +6523,7 @@ const getEmailsDirectoryMetadata = async (absolutePathToEmailsDirectory, keepFil
|
|
|
6522
6523
|
//#region package.json
|
|
6523
6524
|
var package_default = {
|
|
6524
6525
|
name: "react-email",
|
|
6525
|
-
version: "6.1.
|
|
6526
|
+
version: "6.1.5",
|
|
6526
6527
|
description: "A live preview of your emails right in your browser.",
|
|
6527
6528
|
bin: { "email": "./dist/cli/index.mjs" },
|
|
6528
6529
|
type: "module",
|
|
@@ -6657,7 +6658,70 @@ const normalizeDisplay = (display) => {
|
|
|
6657
6658
|
symbolFormatter: withPrefixText(prefixText, symbolFormatter)
|
|
6658
6659
|
};
|
|
6659
6660
|
};
|
|
6660
|
-
const
|
|
6661
|
+
const isInteractiveStream = (stream) => {
|
|
6662
|
+
if (!stream.isTTY) return false;
|
|
6663
|
+
if (process.env.TERM === "dumb") return false;
|
|
6664
|
+
if (process.env.CI) return false;
|
|
6665
|
+
return true;
|
|
6666
|
+
};
|
|
6667
|
+
var NonInteractiveSpinner = class {
|
|
6668
|
+
running = false;
|
|
6669
|
+
text = "";
|
|
6670
|
+
prefixText = "";
|
|
6671
|
+
stream;
|
|
6672
|
+
lastLoggedLine;
|
|
6673
|
+
constructor(display) {
|
|
6674
|
+
if (typeof display === "string") {
|
|
6675
|
+
this.text = display;
|
|
6676
|
+
this.stream = process.stdout;
|
|
6677
|
+
} else {
|
|
6678
|
+
this.text = display.text ?? "";
|
|
6679
|
+
this.prefixText = display.prefixText ?? "";
|
|
6680
|
+
this.stream = display.stream ?? process.stdout;
|
|
6681
|
+
}
|
|
6682
|
+
}
|
|
6683
|
+
start() {
|
|
6684
|
+
this.running = true;
|
|
6685
|
+
this.log();
|
|
6686
|
+
}
|
|
6687
|
+
stop() {
|
|
6688
|
+
this.running = false;
|
|
6689
|
+
}
|
|
6690
|
+
setText(text) {
|
|
6691
|
+
this.text = text;
|
|
6692
|
+
if (this.running) this.log();
|
|
6693
|
+
}
|
|
6694
|
+
setDisplay(display) {
|
|
6695
|
+
if (typeof display.text === "string") this.text = display.text;
|
|
6696
|
+
const { symbol } = display;
|
|
6697
|
+
this.log(symbol);
|
|
6698
|
+
if (typeof symbol === "string") this.running = false;
|
|
6699
|
+
}
|
|
6700
|
+
succeed(display) {
|
|
6701
|
+
this.finish("✔", display);
|
|
6702
|
+
}
|
|
6703
|
+
fail(display) {
|
|
6704
|
+
this.finish("✖", display);
|
|
6705
|
+
}
|
|
6706
|
+
finish(symbol, display) {
|
|
6707
|
+
if (typeof display === "string") this.text = display;
|
|
6708
|
+
else if (typeof display?.text === "string") this.text = display.text;
|
|
6709
|
+
this.log(symbol);
|
|
6710
|
+
this.running = false;
|
|
6711
|
+
}
|
|
6712
|
+
log(symbol) {
|
|
6713
|
+
const symbolPrefix = typeof symbol === "string" && symbol.length > 0 ? `${symbol} ` : "";
|
|
6714
|
+
const trimmedText = this.text.replace(/\n+$/, "");
|
|
6715
|
+
const line = `${this.prefixText}${symbolPrefix}${trimmedText}`;
|
|
6716
|
+
if (line === this.lastLoggedLine) return;
|
|
6717
|
+
this.lastLoggedLine = line;
|
|
6718
|
+
this.stream.write(`${line}\n`);
|
|
6719
|
+
}
|
|
6720
|
+
};
|
|
6721
|
+
const createSpinner = (display, options) => {
|
|
6722
|
+
if (!isInteractiveStream(typeof display !== "string" && display.stream || process.stdout)) return new NonInteractiveSpinner(display);
|
|
6723
|
+
return new Spinner(normalizeDisplay(display), options);
|
|
6724
|
+
};
|
|
6661
6725
|
const stopSpinnerAndPersist = (spinner, display) => {
|
|
6662
6726
|
spinner?.setDisplay(display);
|
|
6663
6727
|
};
|
|
@@ -6730,6 +6794,7 @@ const updatePackageJson = async (builtUiPath) => {
|
|
|
6730
6794
|
await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageJson), "utf8");
|
|
6731
6795
|
};
|
|
6732
6796
|
const build$1 = async ({ dir: emailsDirRelativePath, packageManager }) => {
|
|
6797
|
+
if (packageManager) console.warn("The --packageManager option is deprecated and ignored. The build command now just uses npm.");
|
|
6733
6798
|
try {
|
|
6734
6799
|
const usersProjectLocation = process.cwd();
|
|
6735
6800
|
const previewServerLocation = await getUiLocation();
|
|
@@ -6769,18 +6834,25 @@ const build$1 = async ({ dir: emailsDirRelativePath, packageManager }) => {
|
|
|
6769
6834
|
await updatePackageJson(builtPreviewAppPath);
|
|
6770
6835
|
if (!isInReactEmailMonorepo) {
|
|
6771
6836
|
spinner.setText("Installing dependencies on `.react-email`");
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6837
|
+
const previousInclude = process.env.NPM_CONFIG_INCLUDE;
|
|
6838
|
+
process.env.NPM_CONFIG_INCLUDE = "dev";
|
|
6839
|
+
try {
|
|
6840
|
+
await installDependencies({
|
|
6841
|
+
cwd: builtPreviewAppPath,
|
|
6842
|
+
silent: true,
|
|
6843
|
+
packageManager: "npm"
|
|
6844
|
+
});
|
|
6845
|
+
} finally {
|
|
6846
|
+
if (previousInclude === void 0) delete process.env.NPM_CONFIG_INCLUDE;
|
|
6847
|
+
else process.env.NPM_CONFIG_INCLUDE = previousInclude;
|
|
6848
|
+
}
|
|
6777
6849
|
}
|
|
6778
6850
|
stopSpinnerAndPersist(spinner, {
|
|
6779
6851
|
text: "Successfully prepared `.react-email` for `next build`",
|
|
6780
6852
|
symbol: logSymbols.success
|
|
6781
6853
|
});
|
|
6782
6854
|
await runScript("build", {
|
|
6783
|
-
packageManager,
|
|
6855
|
+
packageManager: "npm",
|
|
6784
6856
|
cwd: builtPreviewAppPath
|
|
6785
6857
|
});
|
|
6786
6858
|
} catch (error) {
|
|
@@ -7332,7 +7404,40 @@ const getEmailTemplatesFromDirectory = (emailDirectory) => {
|
|
|
7332
7404
|
for (const directory of emailDirectory.subDirectories) templatePaths.push(...getEmailTemplatesFromDirectory(directory));
|
|
7333
7405
|
return templatePaths;
|
|
7334
7406
|
};
|
|
7335
|
-
const
|
|
7407
|
+
const BUILD_BATCH_SIZE = 10;
|
|
7408
|
+
const RENDER_BATCH_SIZE = 25;
|
|
7409
|
+
const renderWorkerSource = `
|
|
7410
|
+
const { unlinkSync, writeFileSync } = require('node:fs');
|
|
7411
|
+
const { parentPort, workerData } = require('node:worker_threads');
|
|
7412
|
+
|
|
7413
|
+
const { templates, options } = workerData;
|
|
7414
|
+
|
|
7415
|
+
(async () => {
|
|
7416
|
+
for (const template of templates) {
|
|
7417
|
+
try {
|
|
7418
|
+
const emailModule = require(template);
|
|
7419
|
+
const rendered = await emailModule.render(
|
|
7420
|
+
emailModule.reactEmailCreateReactElement(emailModule.default, {}),
|
|
7421
|
+
options,
|
|
7422
|
+
);
|
|
7423
|
+
const htmlPath = template.replace(
|
|
7424
|
+
'.cjs',
|
|
7425
|
+
options.plainText ? '.txt' : '.html',
|
|
7426
|
+
);
|
|
7427
|
+
writeFileSync(htmlPath, rendered);
|
|
7428
|
+
unlinkSync(template);
|
|
7429
|
+
parentPort.postMessage({ type: 'progress', template });
|
|
7430
|
+
} catch (exception) {
|
|
7431
|
+
parentPort.postMessage({
|
|
7432
|
+
type: 'error',
|
|
7433
|
+
template,
|
|
7434
|
+
message: exception && exception.stack ? exception.stack : String(exception),
|
|
7435
|
+
});
|
|
7436
|
+
process.exit(1);
|
|
7437
|
+
}
|
|
7438
|
+
}
|
|
7439
|
+
})();
|
|
7440
|
+
`;
|
|
7336
7441
|
const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirectoryPath, options) => {
|
|
7337
7442
|
let spinner;
|
|
7338
7443
|
if (!options.silent) {
|
|
@@ -7352,20 +7457,24 @@ const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirec
|
|
|
7352
7457
|
if (fs.existsSync(pathToWhereEmailMarkupShouldBeDumped)) fs.rmSync(pathToWhereEmailMarkupShouldBeDumped, { recursive: true });
|
|
7353
7458
|
const allTemplates = getEmailTemplatesFromDirectory(emailsDirectoryMetadata);
|
|
7354
7459
|
try {
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7460
|
+
for (let i = 0; i < allTemplates.length; i += BUILD_BATCH_SIZE) {
|
|
7461
|
+
const batch = allTemplates.slice(i, i + BUILD_BATCH_SIZE);
|
|
7462
|
+
await build({
|
|
7463
|
+
bundle: true,
|
|
7464
|
+
entryPoints: batch,
|
|
7465
|
+
external: ["css-tree"],
|
|
7466
|
+
format: "cjs",
|
|
7467
|
+
jsx: "automatic",
|
|
7468
|
+
loader: { ".js": "jsx" },
|
|
7469
|
+
logLevel: "silent",
|
|
7470
|
+
outExtension: { ".js": ".cjs" },
|
|
7471
|
+
outdir: pathToWhereEmailMarkupShouldBeDumped,
|
|
7472
|
+
platform: "node",
|
|
7473
|
+
plugins: [renderingUtilitiesExporter(batch)],
|
|
7474
|
+
write: true
|
|
7475
|
+
});
|
|
7476
|
+
await stop();
|
|
7477
|
+
}
|
|
7369
7478
|
} catch (exception) {
|
|
7370
7479
|
if (spinner) stopSpinnerAndPersist(spinner, {
|
|
7371
7480
|
symbol: logSymbols.error,
|
|
@@ -7380,20 +7489,41 @@ const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirec
|
|
|
7380
7489
|
spinner.setText(`rendering ${allBuiltTemplates[0]?.split("/").pop()}`);
|
|
7381
7490
|
spinner.start();
|
|
7382
7491
|
}
|
|
7383
|
-
for
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7492
|
+
for (let i = 0; i < allBuiltTemplates.length; i += RENDER_BATCH_SIZE) {
|
|
7493
|
+
const batch = allBuiltTemplates.slice(i, i + RENDER_BATCH_SIZE);
|
|
7494
|
+
let failedTemplate;
|
|
7495
|
+
let failureMessage;
|
|
7496
|
+
try {
|
|
7497
|
+
await new Promise((resolve, reject) => {
|
|
7498
|
+
const worker = new Worker(renderWorkerSource, {
|
|
7499
|
+
eval: true,
|
|
7500
|
+
workerData: {
|
|
7501
|
+
templates: batch,
|
|
7502
|
+
options
|
|
7503
|
+
}
|
|
7504
|
+
});
|
|
7505
|
+
worker.on("message", (msg) => {
|
|
7506
|
+
if (msg.type === "progress") {
|
|
7507
|
+
if (spinner) spinner.setText(`rendering ${msg.template.split("/").pop()}`);
|
|
7508
|
+
} else if (msg.type === "error") {
|
|
7509
|
+
failedTemplate = msg.template;
|
|
7510
|
+
failureMessage = msg.message;
|
|
7511
|
+
}
|
|
7512
|
+
});
|
|
7513
|
+
worker.on("error", reject);
|
|
7514
|
+
worker.on("exit", (code) => {
|
|
7515
|
+
if (code !== 0) reject(new Error(failureMessage ?? `Render worker exited with code ${code}`));
|
|
7516
|
+
else resolve();
|
|
7517
|
+
});
|
|
7518
|
+
});
|
|
7519
|
+
} catch (exception) {
|
|
7520
|
+
if (spinner) stopSpinnerAndPersist(spinner, {
|
|
7521
|
+
symbol: logSymbols.error,
|
|
7522
|
+
text: failedTemplate ? `failed when rendering ${failedTemplate.split("/").pop()}` : "failed when rendering"
|
|
7523
|
+
});
|
|
7524
|
+
console.error(exception);
|
|
7525
|
+
process.exit(1);
|
|
7526
|
+
}
|
|
7397
7527
|
}
|
|
7398
7528
|
if (spinner) {
|
|
7399
7529
|
spinner.succeed("Rendered all files");
|
|
@@ -7489,7 +7619,7 @@ if (!requiredFlags.every((flag) => process.execArgv.includes(flag))) spawn(proce
|
|
|
7489
7619
|
else {
|
|
7490
7620
|
program.name("react-email").description("A live preview of your emails right in your browser").version(package_default.version);
|
|
7491
7621
|
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);
|
|
7492
|
-
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").
|
|
7622
|
+
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").addOption(new Option("-p, --packageManager <name>").hideHelp()).action(build$1);
|
|
7493
7623
|
program.command("start").description("Runs the built preview app that is inside of \".react-email\"").action(start);
|
|
7494
7624
|
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, {
|
|
7495
7625
|
silent,
|