emailr-cli 1.2.1 → 1.3.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/index.js +183 -11
- 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
|
}
|
|
@@ -801,7 +920,14 @@ Open the Preview URL in your browser to view the rendered template.`);
|
|
|
801
920
|
output(template, "json");
|
|
802
921
|
} else {
|
|
803
922
|
success(`Template updated: ${template.id}`);
|
|
804
|
-
|
|
923
|
+
const tableData = {
|
|
924
|
+
ID: template.id,
|
|
925
|
+
Name: template.name,
|
|
926
|
+
Subject: template.subject,
|
|
927
|
+
Variables: template.variables?.join(", ") || "-",
|
|
928
|
+
Updated: template.updated_at
|
|
929
|
+
};
|
|
930
|
+
output(tableData, "table");
|
|
805
931
|
}
|
|
806
932
|
} catch (err) {
|
|
807
933
|
error(err instanceof Error ? err.message : "Failed to update template");
|
|
@@ -867,6 +993,52 @@ Template: ${template.name}`);
|
|
|
867
993
|
process.exit(1);
|
|
868
994
|
}
|
|
869
995
|
});
|
|
996
|
+
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) => {
|
|
997
|
+
try {
|
|
998
|
+
const config = loadConfig();
|
|
999
|
+
const client = new Emailr3({
|
|
1000
|
+
apiKey: config.apiKey,
|
|
1001
|
+
baseUrl: config.baseUrl
|
|
1002
|
+
});
|
|
1003
|
+
const filePath = path4.resolve(options.file);
|
|
1004
|
+
console.log(`Fetching template ${id}...`);
|
|
1005
|
+
const template = await client.templates.get(id);
|
|
1006
|
+
const htmlContent = template.html_content ?? "";
|
|
1007
|
+
fs4.writeFileSync(filePath, htmlContent, "utf-8");
|
|
1008
|
+
console.log(`Template saved to: ${filePath}`);
|
|
1009
|
+
const port = await startLivePreviewServer(filePath, () => {
|
|
1010
|
+
console.log("File changed - browser refreshed");
|
|
1011
|
+
});
|
|
1012
|
+
const previewUrl = `http://127.0.0.1:${port}/`;
|
|
1013
|
+
console.log(`
|
|
1014
|
+
Template: ${template.name}`);
|
|
1015
|
+
console.log(`Template ID: ${template.id}`);
|
|
1016
|
+
console.log(`Live Preview: ${previewUrl}`);
|
|
1017
|
+
console.log(`
|
|
1018
|
+
Watching for changes... Edit the file and see live updates.`);
|
|
1019
|
+
console.log(`When done, run: emailr templates update ${id} --html-file ${options.file}`);
|
|
1020
|
+
console.log(`
|
|
1021
|
+
Press Ctrl+C to stop.`);
|
|
1022
|
+
if (options.open !== false) {
|
|
1023
|
+
try {
|
|
1024
|
+
const open = await import("open");
|
|
1025
|
+
await open.default(previewUrl);
|
|
1026
|
+
} catch {
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
process.on("SIGINT", async () => {
|
|
1030
|
+
console.log("\n\nStopping live preview server...");
|
|
1031
|
+
await stopLivePreviewServer();
|
|
1032
|
+
console.log("Done.");
|
|
1033
|
+
console.log(`
|
|
1034
|
+
To save changes: emailr templates update ${id} --html-file ${options.file}`);
|
|
1035
|
+
process.exit(0);
|
|
1036
|
+
});
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
error(err instanceof Error ? err.message : "Failed to edit template");
|
|
1039
|
+
process.exit(1);
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
870
1042
|
return cmd;
|
|
871
1043
|
}
|
|
872
1044
|
|
|
@@ -1680,7 +1852,7 @@ function createSegmentsCommand() {
|
|
|
1680
1852
|
import { Command as Command9 } from "commander";
|
|
1681
1853
|
|
|
1682
1854
|
// src/server/callback.ts
|
|
1683
|
-
import
|
|
1855
|
+
import http3 from "http";
|
|
1684
1856
|
import { URL } from "url";
|
|
1685
1857
|
function parseCallbackParams(url) {
|
|
1686
1858
|
try {
|
|
@@ -1813,7 +1985,7 @@ function createCallbackServer() {
|
|
|
1813
1985
|
return {
|
|
1814
1986
|
async start() {
|
|
1815
1987
|
return new Promise((resolve, reject) => {
|
|
1816
|
-
server =
|
|
1988
|
+
server = http3.createServer((req, res) => {
|
|
1817
1989
|
if (req.method !== "GET" || !req.url?.startsWith("/callback")) {
|
|
1818
1990
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1819
1991
|
res.end("Not Found");
|