emailr-cli 1.2.0 → 1.3.0
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/index.js +175 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -343,6 +343,8 @@ Total: ${result.total}`);
|
|
|
343
343
|
// src/commands/templates.ts
|
|
344
344
|
import { Command as Command3 } from "commander";
|
|
345
345
|
import { Emailr as Emailr3 } from "emailr";
|
|
346
|
+
import fs4 from "fs";
|
|
347
|
+
import path4 from "path";
|
|
346
348
|
|
|
347
349
|
// src/server/template-store.ts
|
|
348
350
|
import fs2 from "fs";
|
|
@@ -625,6 +627,123 @@ function keepServerAlive() {
|
|
|
625
627
|
}
|
|
626
628
|
}
|
|
627
629
|
|
|
630
|
+
// src/server/live-preview-server.ts
|
|
631
|
+
import http2 from "http";
|
|
632
|
+
import fs3 from "fs";
|
|
633
|
+
import path3 from "path";
|
|
634
|
+
var serverInstance2 = null;
|
|
635
|
+
var serverPort2 = null;
|
|
636
|
+
var fileWatcher = null;
|
|
637
|
+
var sseClients = [];
|
|
638
|
+
var LIVE_RELOAD_SCRIPT = `
|
|
639
|
+
<script>
|
|
640
|
+
(function() {
|
|
641
|
+
const evtSource = new EventSource('/__live-reload');
|
|
642
|
+
evtSource.onmessage = function(e) {
|
|
643
|
+
if (e.data === 'reload') {
|
|
644
|
+
window.location.reload();
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
evtSource.onerror = function() {
|
|
648
|
+
console.log('Live reload disconnected');
|
|
649
|
+
};
|
|
650
|
+
})();
|
|
651
|
+
</script>
|
|
652
|
+
`;
|
|
653
|
+
function injectLiveReload(html) {
|
|
654
|
+
if (html.includes("</body>")) {
|
|
655
|
+
return html.replace("</body>", `${LIVE_RELOAD_SCRIPT}</body>`);
|
|
656
|
+
}
|
|
657
|
+
return html + LIVE_RELOAD_SCRIPT;
|
|
658
|
+
}
|
|
659
|
+
function notifyReload() {
|
|
660
|
+
sseClients.forEach((client) => {
|
|
661
|
+
try {
|
|
662
|
+
client.write("data: reload\n\n");
|
|
663
|
+
} catch {
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
async function startLivePreviewServer(filePath, onReload) {
|
|
668
|
+
const absolutePath = path3.resolve(filePath);
|
|
669
|
+
return new Promise((resolve, reject) => {
|
|
670
|
+
serverInstance2 = http2.createServer((req, res) => {
|
|
671
|
+
if (req.url === "/__live-reload") {
|
|
672
|
+
res.writeHead(200, {
|
|
673
|
+
"Content-Type": "text/event-stream",
|
|
674
|
+
"Cache-Control": "no-cache",
|
|
675
|
+
"Connection": "keep-alive",
|
|
676
|
+
"Access-Control-Allow-Origin": "*"
|
|
677
|
+
});
|
|
678
|
+
res.write("data: connected\n\n");
|
|
679
|
+
sseClients.push(res);
|
|
680
|
+
req.on("close", () => {
|
|
681
|
+
sseClients = sseClients.filter((c) => c !== res);
|
|
682
|
+
});
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
if (req.method === "GET") {
|
|
686
|
+
try {
|
|
687
|
+
const html = fs3.readFileSync(absolutePath, "utf-8");
|
|
688
|
+
const injectedHtml = injectLiveReload(html);
|
|
689
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
690
|
+
res.end(injectedHtml);
|
|
691
|
+
} catch (err) {
|
|
692
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
693
|
+
res.end(`Error reading file: ${err instanceof Error ? err.message : String(err)}`);
|
|
694
|
+
}
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
698
|
+
res.end("Method Not Allowed");
|
|
699
|
+
});
|
|
700
|
+
serverInstance2.listen(0, "127.0.0.1", () => {
|
|
701
|
+
const address = serverInstance2.address();
|
|
702
|
+
if (address && typeof address === "object") {
|
|
703
|
+
serverPort2 = address.port;
|
|
704
|
+
let debounceTimer = null;
|
|
705
|
+
fileWatcher = fs3.watch(absolutePath, (eventType) => {
|
|
706
|
+
if (eventType === "change") {
|
|
707
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
708
|
+
debounceTimer = setTimeout(() => {
|
|
709
|
+
notifyReload();
|
|
710
|
+
onReload?.();
|
|
711
|
+
}, 100);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
resolve(serverPort2);
|
|
715
|
+
} else {
|
|
716
|
+
reject(new Error("Failed to get server address"));
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
serverInstance2.on("error", (err) => {
|
|
720
|
+
reject(new Error(`Failed to start server: ${err.message}`));
|
|
721
|
+
});
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
async function stopLivePreviewServer() {
|
|
725
|
+
if (fileWatcher) {
|
|
726
|
+
fileWatcher.close();
|
|
727
|
+
fileWatcher = null;
|
|
728
|
+
}
|
|
729
|
+
sseClients.forEach((client) => {
|
|
730
|
+
try {
|
|
731
|
+
client.end();
|
|
732
|
+
} catch {
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
sseClients = [];
|
|
736
|
+
if (serverInstance2) {
|
|
737
|
+
return new Promise((resolve) => {
|
|
738
|
+
serverInstance2.close(() => {
|
|
739
|
+
serverInstance2 = null;
|
|
740
|
+
serverPort2 = null;
|
|
741
|
+
resolve();
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
628
747
|
// src/commands/templates.ts
|
|
629
748
|
async function handleTemplatePreview(template) {
|
|
630
749
|
try {
|
|
@@ -725,14 +844,14 @@ Total: ${result.length}`);
|
|
|
725
844
|
subject: options.subject
|
|
726
845
|
};
|
|
727
846
|
if (options.htmlFile) {
|
|
728
|
-
const
|
|
729
|
-
request.html_content =
|
|
847
|
+
const fs5 = await import("fs");
|
|
848
|
+
request.html_content = fs5.readFileSync(options.htmlFile, "utf-8");
|
|
730
849
|
} else if (options.html) {
|
|
731
850
|
request.html_content = options.html;
|
|
732
851
|
}
|
|
733
852
|
if (options.textFile) {
|
|
734
|
-
const
|
|
735
|
-
request.text_content =
|
|
853
|
+
const fs5 = await import("fs");
|
|
854
|
+
request.text_content = fs5.readFileSync(options.textFile, "utf-8");
|
|
736
855
|
} else if (options.text) {
|
|
737
856
|
request.text_content = options.text;
|
|
738
857
|
}
|
|
@@ -782,14 +901,14 @@ Open the Preview URL in your browser to view the rendered template.`);
|
|
|
782
901
|
if (options.name) request.name = options.name;
|
|
783
902
|
if (options.subject) request.subject = options.subject;
|
|
784
903
|
if (options.htmlFile) {
|
|
785
|
-
const
|
|
786
|
-
request.html_content =
|
|
904
|
+
const fs5 = await import("fs");
|
|
905
|
+
request.html_content = fs5.readFileSync(options.htmlFile, "utf-8");
|
|
787
906
|
} else if (options.html) {
|
|
788
907
|
request.html_content = options.html;
|
|
789
908
|
}
|
|
790
909
|
if (options.textFile) {
|
|
791
|
-
const
|
|
792
|
-
request.text_content =
|
|
910
|
+
const fs5 = await import("fs");
|
|
911
|
+
request.text_content = fs5.readFileSync(options.textFile, "utf-8");
|
|
793
912
|
} else if (options.text) {
|
|
794
913
|
request.text_content = options.text;
|
|
795
914
|
}
|
|
@@ -867,6 +986,52 @@ Template: ${template.name}`);
|
|
|
867
986
|
process.exit(1);
|
|
868
987
|
}
|
|
869
988
|
});
|
|
989
|
+
cmd.command("edit <id>").description("Edit a template with live preview - saves HTML to a local file for editing").option("--file <path>", "Path to save the HTML file", "./template.html").option("--no-open", "Do not automatically open browser").action(async (id, options) => {
|
|
990
|
+
try {
|
|
991
|
+
const config = loadConfig();
|
|
992
|
+
const client = new Emailr3({
|
|
993
|
+
apiKey: config.apiKey,
|
|
994
|
+
baseUrl: config.baseUrl
|
|
995
|
+
});
|
|
996
|
+
const filePath = path4.resolve(options.file);
|
|
997
|
+
console.log(`Fetching template ${id}...`);
|
|
998
|
+
const template = await client.templates.get(id);
|
|
999
|
+
const htmlContent = template.html_content ?? "";
|
|
1000
|
+
fs4.writeFileSync(filePath, htmlContent, "utf-8");
|
|
1001
|
+
console.log(`Template saved to: ${filePath}`);
|
|
1002
|
+
const port = await startLivePreviewServer(filePath, () => {
|
|
1003
|
+
console.log("File changed - browser refreshed");
|
|
1004
|
+
});
|
|
1005
|
+
const previewUrl = `http://127.0.0.1:${port}/`;
|
|
1006
|
+
console.log(`
|
|
1007
|
+
Template: ${template.name}`);
|
|
1008
|
+
console.log(`Template ID: ${template.id}`);
|
|
1009
|
+
console.log(`Live Preview: ${previewUrl}`);
|
|
1010
|
+
console.log(`
|
|
1011
|
+
Watching for changes... Edit the file and see live updates.`);
|
|
1012
|
+
console.log(`When done, run: emailr templates update ${id} --html-file ${options.file}`);
|
|
1013
|
+
console.log(`
|
|
1014
|
+
Press Ctrl+C to stop.`);
|
|
1015
|
+
if (options.open !== false) {
|
|
1016
|
+
try {
|
|
1017
|
+
const open = await import("open");
|
|
1018
|
+
await open.default(previewUrl);
|
|
1019
|
+
} catch {
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
process.on("SIGINT", async () => {
|
|
1023
|
+
console.log("\n\nStopping live preview server...");
|
|
1024
|
+
await stopLivePreviewServer();
|
|
1025
|
+
console.log("Done.");
|
|
1026
|
+
console.log(`
|
|
1027
|
+
To save changes: emailr templates update ${id} --html-file ${options.file}`);
|
|
1028
|
+
process.exit(0);
|
|
1029
|
+
});
|
|
1030
|
+
} catch (err) {
|
|
1031
|
+
error(err instanceof Error ? err.message : "Failed to edit template");
|
|
1032
|
+
process.exit(1);
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
870
1035
|
return cmd;
|
|
871
1036
|
}
|
|
872
1037
|
|
|
@@ -1680,7 +1845,7 @@ function createSegmentsCommand() {
|
|
|
1680
1845
|
import { Command as Command9 } from "commander";
|
|
1681
1846
|
|
|
1682
1847
|
// src/server/callback.ts
|
|
1683
|
-
import
|
|
1848
|
+
import http3 from "http";
|
|
1684
1849
|
import { URL } from "url";
|
|
1685
1850
|
function parseCallbackParams(url) {
|
|
1686
1851
|
try {
|
|
@@ -1813,7 +1978,7 @@ function createCallbackServer() {
|
|
|
1813
1978
|
return {
|
|
1814
1979
|
async start() {
|
|
1815
1980
|
return new Promise((resolve, reject) => {
|
|
1816
|
-
server =
|
|
1981
|
+
server = http3.createServer((req, res) => {
|
|
1817
1982
|
if (req.method !== "GET" || !req.url?.startsWith("/callback")) {
|
|
1818
1983
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1819
1984
|
res.end("Not Found");
|