@emailens/engine 0.5.2 → 0.7.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/README.md +202 -4
- package/dist/{chunk-W4SPWESS.js → chunk-3NDQOTPM.js} +2 -2
- package/dist/{chunk-SZ5O5PDZ.js → chunk-OY3DGZVU.js} +2 -2
- package/dist/{chunk-EBNA3X7P.js → chunk-URZ4XPST.js} +6 -7
- package/dist/chunk-URZ4XPST.js.map +1 -0
- package/dist/{chunk-PFONR3YC.js → chunk-ZQF2XUIJ.js} +1 -8
- package/dist/{chunk-PFONR3YC.js.map → chunk-ZQF2XUIJ.js.map} +1 -1
- package/dist/compile/index.cjs +3 -3
- package/dist/compile/index.cjs.map +1 -1
- package/dist/compile/index.d.cts +2 -2
- package/dist/compile/index.d.ts +2 -2
- package/dist/compile/index.js +7 -7
- package/dist/index.cjs +594 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +147 -4
- package/dist/index.d.ts +147 -4
- package/dist/index.js +591 -61
- package/dist/index.js.map +1 -1
- package/dist/maizzle-Y2CLJ7GB.js +8 -0
- package/dist/mjml-D7IOYXXK.js +8 -0
- package/dist/react-email-4TW6IDAA.js +8 -0
- package/dist/{react-email-BQljgXbo.d.cts → react-email-CYOtHJch.d.cts} +48 -1
- package/dist/{react-email-BQljgXbo.d.ts → react-email-CYOtHJch.d.ts} +48 -1
- package/package.json +1 -1
- package/dist/chunk-EBNA3X7P.js.map +0 -1
- package/dist/maizzle-YDSYDVSM.js +0 -8
- package/dist/mjml-IYGC6AOM.js +0 -8
- package/dist/react-email-ZSQKM36O.js +0 -8
- /package/dist/{chunk-W4SPWESS.js.map → chunk-3NDQOTPM.js.map} +0 -0
- /package/dist/{chunk-SZ5O5PDZ.js.map → chunk-OY3DGZVU.js.map} +0 -0
- /package/dist/{maizzle-YDSYDVSM.js.map → maizzle-Y2CLJ7GB.js.map} +0 -0
- /package/dist/{mjml-IYGC6AOM.js.map → mjml-D7IOYXXK.js.map} +0 -0
- /package/dist/{react-email-ZSQKM36O.js.map → react-email-4TW6IDAA.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -71,10 +71,14 @@ __export(index_exports, {
|
|
|
71
71
|
analyzeSpam: () => analyzeSpam,
|
|
72
72
|
auditEmail: () => auditEmail,
|
|
73
73
|
checkAccessibility: () => checkAccessibility,
|
|
74
|
+
checkSize: () => checkSize,
|
|
75
|
+
checkTemplateVariables: () => checkTemplateVariables,
|
|
74
76
|
contrastRatio: () => contrastRatio,
|
|
77
|
+
createSession: () => createSession,
|
|
75
78
|
diffResults: () => diffResults,
|
|
76
79
|
errorWarnings: () => errorWarnings,
|
|
77
80
|
estimateAiFixTokens: () => estimateAiFixTokens,
|
|
81
|
+
extractInboxPreview: () => extractInboxPreview,
|
|
78
82
|
generateAiFix: () => generateAiFix,
|
|
79
83
|
generateCompatibilityScore: () => generateCompatibilityScore,
|
|
80
84
|
generateFixPrompt: () => generateFixPrompt,
|
|
@@ -2836,6 +2840,43 @@ var GENERIC_LINK_TEXT = /* @__PURE__ */ new Set([
|
|
|
2836
2840
|
"tap here",
|
|
2837
2841
|
"this"
|
|
2838
2842
|
]);
|
|
2843
|
+
var GMAIL_CLIP_THRESHOLD = 102 * 1024;
|
|
2844
|
+
var GMAIL_CLIP_WARNING_THRESHOLD = 90 * 1024;
|
|
2845
|
+
var CLIENT_DISPLAY_LIMITS = [
|
|
2846
|
+
{ client: "Gmail (Web)", subjectLimit: 70, preheaderLimit: 90 },
|
|
2847
|
+
{ client: "Gmail (Mobile)", subjectLimit: 40, preheaderLimit: 90 },
|
|
2848
|
+
{ client: "Outlook (Web)", subjectLimit: 60, preheaderLimit: 35 },
|
|
2849
|
+
{ client: "Outlook (Desktop)", subjectLimit: 55, preheaderLimit: 35 },
|
|
2850
|
+
{ client: "Apple Mail (macOS)", subjectLimit: 78, preheaderLimit: 140 },
|
|
2851
|
+
{ client: "Apple Mail (iOS)", subjectLimit: 35, preheaderLimit: 90 },
|
|
2852
|
+
{ client: "Yahoo Mail", subjectLimit: 46, preheaderLimit: 100 },
|
|
2853
|
+
{ client: "Samsung Email", subjectLimit: 40, preheaderLimit: 70 }
|
|
2854
|
+
];
|
|
2855
|
+
var TEMPLATE_VARIABLE_PATTERNS = [
|
|
2856
|
+
[/\{\{[\s\S]*?\}\}/g, "Handlebars/Mustache"],
|
|
2857
|
+
// {{var}}
|
|
2858
|
+
[/\$\{[^}]+\}/g, "ES template literal"],
|
|
2859
|
+
// ${var}
|
|
2860
|
+
[/<%=?\s*[^%]+%>/g, "ERB/EJS"],
|
|
2861
|
+
// <% %> / <%= %>
|
|
2862
|
+
[/\*\|[A-Z_][A-Z0-9_]*\|\*/g, "Mailchimp merge tag"],
|
|
2863
|
+
// *|TAG|*
|
|
2864
|
+
[/%%[A-Za-z_][A-Za-z0-9_]*%%/g, "Salesforce AMPscript"],
|
|
2865
|
+
// %%tag%%
|
|
2866
|
+
[/\{[A-Za-z_][A-Za-z0-9_.]{2,}\}/g, "Single-brace merge field"]
|
|
2867
|
+
// {merge_field} (3+ char names)
|
|
2868
|
+
];
|
|
2869
|
+
var EMPTY_SPAM = { score: 100, level: "low", issues: [] };
|
|
2870
|
+
var EMPTY_LINKS = {
|
|
2871
|
+
totalLinks: 0,
|
|
2872
|
+
issues: [],
|
|
2873
|
+
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
2874
|
+
};
|
|
2875
|
+
var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
|
|
2876
|
+
var EMPTY_IMAGES = { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
2877
|
+
var EMPTY_INBOX_PREVIEW = { subject: null, preheader: null, subjectLength: 0, preheaderLength: 0, truncation: [], issues: [] };
|
|
2878
|
+
var EMPTY_SIZE = { htmlBytes: 0, humanSize: "0 B", clipped: false, issues: [] };
|
|
2879
|
+
var EMPTY_TEMPLATE = { unresolvedCount: 0, issues: [] };
|
|
2839
2880
|
|
|
2840
2881
|
// src/transform.ts
|
|
2841
2882
|
function inlineStyles($) {
|
|
@@ -3344,15 +3385,8 @@ function transformForAllClients(html, framework) {
|
|
|
3344
3385
|
// src/analyze.ts
|
|
3345
3386
|
var cheerio2 = __toESM(require("cheerio"), 1);
|
|
3346
3387
|
var csstree2 = __toESM(require("css-tree"), 1);
|
|
3347
|
-
function
|
|
3388
|
+
function analyzeEmailFromDom($, framework) {
|
|
3348
3389
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
3349
|
-
if (!html || !html.trim()) {
|
|
3350
|
-
return [];
|
|
3351
|
-
}
|
|
3352
|
-
if (html.length > MAX_HTML_SIZE) {
|
|
3353
|
-
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
3354
|
-
}
|
|
3355
|
-
const $ = cheerio2.load(html);
|
|
3356
3390
|
const warnings = [];
|
|
3357
3391
|
const seenWarnings = /* @__PURE__ */ new Set();
|
|
3358
3392
|
function addWarning(w) {
|
|
@@ -3597,6 +3631,16 @@ function analyzeEmail(html, framework) {
|
|
|
3597
3631
|
warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
3598
3632
|
return warnings;
|
|
3599
3633
|
}
|
|
3634
|
+
function analyzeEmail(html, framework) {
|
|
3635
|
+
if (!html || !html.trim()) {
|
|
3636
|
+
return [];
|
|
3637
|
+
}
|
|
3638
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
3639
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
3640
|
+
}
|
|
3641
|
+
const $ = cheerio2.load(html);
|
|
3642
|
+
return analyzeEmailFromDom($, framework);
|
|
3643
|
+
}
|
|
3600
3644
|
function getFixType(prop) {
|
|
3601
3645
|
return STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css";
|
|
3602
3646
|
}
|
|
@@ -4495,7 +4539,9 @@ var WEIGHTS = {
|
|
|
4495
4539
|
"image-only": 20,
|
|
4496
4540
|
"high-image-ratio": 10,
|
|
4497
4541
|
"deceptive-link": 15,
|
|
4498
|
-
"all-caps-subject": 10
|
|
4542
|
+
"all-caps-subject": 10,
|
|
4543
|
+
"missing-physical-address": 8,
|
|
4544
|
+
"missing-one-click-unsubscribe": 5
|
|
4499
4545
|
};
|
|
4500
4546
|
function extractVisibleText($) {
|
|
4501
4547
|
const clone = $.root().clone();
|
|
@@ -4712,6 +4758,47 @@ Href: ${href}`
|
|
|
4712
4758
|
});
|
|
4713
4759
|
return issues;
|
|
4714
4760
|
}
|
|
4761
|
+
var STREET_ADDRESS_PATTERN = /\b\d{1,5}\s+[A-Za-z]+\s+(St(reet)?|Ave(nue)?|Blvd|Boulevard|Dr(ive)?|Rd|Road|Ln|Lane|Way|Ct|Court|Pl(ace)?|Pkwy|Parkway|Cir(cle)?|Terr(ace)?|Loop)\b/i;
|
|
4762
|
+
var PO_BOX_PATTERN = /\bP\.?\s*O\.?\s*Box\s+\d+/i;
|
|
4763
|
+
var ZIP_CODE_PATTERN = /\b\d{5}(-\d{4})?\b/;
|
|
4764
|
+
var ADDRESS_CLASS_PATTERN = /address|mailing|postal|footer-address|physical-address/i;
|
|
4765
|
+
function checkPhysicalAddress($, text, options) {
|
|
4766
|
+
if ((options == null ? void 0 : options.emailType) === "transactional") return null;
|
|
4767
|
+
if (STREET_ADDRESS_PATTERN.test(text) && ZIP_CODE_PATTERN.test(text)) return null;
|
|
4768
|
+
if (PO_BOX_PATTERN.test(text) && ZIP_CODE_PATTERN.test(text)) return null;
|
|
4769
|
+
let foundByClass = false;
|
|
4770
|
+
$("[class]").each((_, el) => {
|
|
4771
|
+
const cls = $(el).attr("class") || "";
|
|
4772
|
+
if (ADDRESS_CLASS_PATTERN.test(cls)) {
|
|
4773
|
+
const elText = $(el).text().trim();
|
|
4774
|
+
if (elText.length > 5) {
|
|
4775
|
+
foundByClass = true;
|
|
4776
|
+
return false;
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4779
|
+
});
|
|
4780
|
+
if (foundByClass) return null;
|
|
4781
|
+
if ($("address").length > 0 && $("address").text().trim().length > 5) return null;
|
|
4782
|
+
return {
|
|
4783
|
+
rule: "missing-physical-address",
|
|
4784
|
+
severity: "warning",
|
|
4785
|
+
message: "No physical mailing address detected \u2014 required by CAN-SPAM for marketing emails.",
|
|
4786
|
+
detail: "Include a street address or P.O. Box in the email footer."
|
|
4787
|
+
};
|
|
4788
|
+
}
|
|
4789
|
+
function checkOneClickUnsubscribe(options) {
|
|
4790
|
+
var _a, _b;
|
|
4791
|
+
if (!((_a = options == null ? void 0 : options.listUnsubscribeHeader) == null ? void 0 : _a.trim())) return null;
|
|
4792
|
+
if (!((_b = options == null ? void 0 : options.listUnsubscribePostHeader) == null ? void 0 : _b.trim())) {
|
|
4793
|
+
return {
|
|
4794
|
+
rule: "missing-one-click-unsubscribe",
|
|
4795
|
+
severity: "warning",
|
|
4796
|
+
message: "List-Unsubscribe header present but missing List-Unsubscribe-Post header (RFC 8058). Gmail and Yahoo require one-click unsubscribe.",
|
|
4797
|
+
detail: 'Add "List-Unsubscribe-Post: List-Unsubscribe=One-Click" header.'
|
|
4798
|
+
};
|
|
4799
|
+
}
|
|
4800
|
+
return null;
|
|
4801
|
+
}
|
|
4715
4802
|
function checkAllCapsTitle($) {
|
|
4716
4803
|
const title = $("title").text().trim();
|
|
4717
4804
|
if (title.length > 5 && title === title.toUpperCase() && /[A-Z]/.test(title)) {
|
|
@@ -4723,14 +4810,7 @@ function checkAllCapsTitle($) {
|
|
|
4723
4810
|
}
|
|
4724
4811
|
return null;
|
|
4725
4812
|
}
|
|
4726
|
-
function
|
|
4727
|
-
if (!html || !html.trim()) {
|
|
4728
|
-
return { score: 100, level: "low", issues: [] };
|
|
4729
|
-
}
|
|
4730
|
-
if (html.length > MAX_HTML_SIZE) {
|
|
4731
|
-
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
4732
|
-
}
|
|
4733
|
-
const $ = cheerio4.load(html);
|
|
4813
|
+
function analyzeSpamFromDom($, options) {
|
|
4734
4814
|
const text = extractVisibleText($);
|
|
4735
4815
|
const issues = [];
|
|
4736
4816
|
const capsIssue = checkCapsRatio(text);
|
|
@@ -4748,6 +4828,10 @@ function analyzeSpam(html, options) {
|
|
|
4748
4828
|
issues.push(...checkDeceptiveLinks($));
|
|
4749
4829
|
const capsTitle = checkAllCapsTitle($);
|
|
4750
4830
|
if (capsTitle) issues.push(capsTitle);
|
|
4831
|
+
const addressIssue = checkPhysicalAddress($, text, options);
|
|
4832
|
+
if (addressIssue) issues.push(addressIssue);
|
|
4833
|
+
const oneClickIssue = checkOneClickUnsubscribe(options);
|
|
4834
|
+
if (oneClickIssue) issues.push(oneClickIssue);
|
|
4751
4835
|
let penalty = 0;
|
|
4752
4836
|
const seenRules = /* @__PURE__ */ new Map();
|
|
4753
4837
|
for (const issue of issues) {
|
|
@@ -4768,6 +4852,16 @@ function analyzeSpam(html, options) {
|
|
|
4768
4852
|
const level = score >= 70 ? "low" : score >= 40 ? "medium" : "high";
|
|
4769
4853
|
return { score, level, issues };
|
|
4770
4854
|
}
|
|
4855
|
+
function analyzeSpam(html, options) {
|
|
4856
|
+
if (!html || !html.trim()) {
|
|
4857
|
+
return { score: 100, level: "low", issues: [] };
|
|
4858
|
+
}
|
|
4859
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
4860
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
4861
|
+
}
|
|
4862
|
+
const $ = cheerio4.load(html);
|
|
4863
|
+
return analyzeSpamFromDom($, options);
|
|
4864
|
+
}
|
|
4771
4865
|
|
|
4772
4866
|
// src/link-validator.ts
|
|
4773
4867
|
var cheerio5 = __toESM(require("cheerio"), 1);
|
|
@@ -4787,18 +4881,7 @@ function isPlaceholderHref(href) {
|
|
|
4787
4881
|
const h = href.trim().toLowerCase();
|
|
4788
4882
|
return h === "#" || h === "" || h === "javascript:void(0)" || h === "javascript:;";
|
|
4789
4883
|
}
|
|
4790
|
-
function
|
|
4791
|
-
if (!html || !html.trim()) {
|
|
4792
|
-
return {
|
|
4793
|
-
totalLinks: 0,
|
|
4794
|
-
issues: [],
|
|
4795
|
-
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
4796
|
-
};
|
|
4797
|
-
}
|
|
4798
|
-
if (html.length > MAX_HTML_SIZE) {
|
|
4799
|
-
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
4800
|
-
}
|
|
4801
|
-
const $ = cheerio5.load(html);
|
|
4884
|
+
function validateLinksFromDom($) {
|
|
4802
4885
|
const issues = [];
|
|
4803
4886
|
const breakdown = { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 };
|
|
4804
4887
|
const links = $("a");
|
|
@@ -4937,6 +5020,23 @@ function validateLinks(html) {
|
|
|
4937
5020
|
});
|
|
4938
5021
|
}
|
|
4939
5022
|
});
|
|
5023
|
+
links.each((_, el) => {
|
|
5024
|
+
const href = $(el).attr("href") || "";
|
|
5025
|
+
const trimmed = href.trim();
|
|
5026
|
+
if (trimmed.startsWith("#") && trimmed.length > 1) {
|
|
5027
|
+
const targetId = trimmed.slice(1);
|
|
5028
|
+
const target = $(`[id="${targetId}"]`);
|
|
5029
|
+
if (target.length === 0) {
|
|
5030
|
+
issues.push({
|
|
5031
|
+
severity: "error",
|
|
5032
|
+
rule: "broken-anchor",
|
|
5033
|
+
message: `Anchor link "${trimmed}" points to an element that does not exist`,
|
|
5034
|
+
href: trimmed,
|
|
5035
|
+
text: $(el).text().trim().slice(0, 80) || "(no text)"
|
|
5036
|
+
});
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
});
|
|
4940
5040
|
for (const [href, count] of hrefCounts) {
|
|
4941
5041
|
if (count > 5) {
|
|
4942
5042
|
issues.push({
|
|
@@ -4949,6 +5049,20 @@ function validateLinks(html) {
|
|
|
4949
5049
|
}
|
|
4950
5050
|
return { totalLinks, issues, breakdown };
|
|
4951
5051
|
}
|
|
5052
|
+
function validateLinks(html) {
|
|
5053
|
+
if (!html || !html.trim()) {
|
|
5054
|
+
return {
|
|
5055
|
+
totalLinks: 0,
|
|
5056
|
+
issues: [],
|
|
5057
|
+
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
5058
|
+
};
|
|
5059
|
+
}
|
|
5060
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5061
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5062
|
+
}
|
|
5063
|
+
const $ = cheerio5.load(html);
|
|
5064
|
+
return validateLinksFromDom($);
|
|
5065
|
+
}
|
|
4952
5066
|
|
|
4953
5067
|
// src/accessibility-checker.ts
|
|
4954
5068
|
var cheerio6 = __toESM(require("cheerio"), 1);
|
|
@@ -5176,6 +5290,21 @@ function checkTextSizeAndContrast($) {
|
|
|
5176
5290
|
}
|
|
5177
5291
|
return issues;
|
|
5178
5292
|
}
|
|
5293
|
+
function checkCharsetDeclaration($) {
|
|
5294
|
+
const metaCharset = $("meta[charset]");
|
|
5295
|
+
if (metaCharset.length > 0) return null;
|
|
5296
|
+
const httpEquiv = $('meta[http-equiv="Content-Type"]');
|
|
5297
|
+
if (httpEquiv.length > 0) {
|
|
5298
|
+
const content = httpEquiv.attr("content") || "";
|
|
5299
|
+
if (/charset\s*=/i.test(content)) return null;
|
|
5300
|
+
}
|
|
5301
|
+
return {
|
|
5302
|
+
severity: "warning",
|
|
5303
|
+
rule: "missing-charset",
|
|
5304
|
+
message: "Missing charset declaration",
|
|
5305
|
+
details: 'Add <meta charset="utf-8"> in <head> to prevent encoding issues across email clients.'
|
|
5306
|
+
};
|
|
5307
|
+
}
|
|
5179
5308
|
function checkSemanticStructure($) {
|
|
5180
5309
|
const issues = [];
|
|
5181
5310
|
const headings = [];
|
|
@@ -5197,14 +5326,7 @@ function checkSemanticStructure($) {
|
|
|
5197
5326
|
}
|
|
5198
5327
|
return issues;
|
|
5199
5328
|
}
|
|
5200
|
-
function
|
|
5201
|
-
if (!html || !html.trim()) {
|
|
5202
|
-
return { score: 100, issues: [] };
|
|
5203
|
-
}
|
|
5204
|
-
if (html.length > MAX_HTML_SIZE) {
|
|
5205
|
-
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5206
|
-
}
|
|
5207
|
-
const $ = cheerio6.load(html);
|
|
5329
|
+
function checkAccessibilityFromDom($) {
|
|
5208
5330
|
const issues = [];
|
|
5209
5331
|
const langIssue = checkLangAttribute($);
|
|
5210
5332
|
if (langIssue) issues.push(langIssue);
|
|
@@ -5215,6 +5337,8 @@ function checkAccessibility(html) {
|
|
|
5215
5337
|
issues.push(...checkTableAccessibility($));
|
|
5216
5338
|
issues.push(...checkTextSizeAndContrast($));
|
|
5217
5339
|
issues.push(...checkSemanticStructure($));
|
|
5340
|
+
const charsetIssue = checkCharsetDeclaration($);
|
|
5341
|
+
if (charsetIssue) issues.push(charsetIssue);
|
|
5218
5342
|
let penalty = 0;
|
|
5219
5343
|
const seenRules = /* @__PURE__ */ new Map();
|
|
5220
5344
|
for (const issue of issues) {
|
|
@@ -5237,6 +5361,16 @@ function checkAccessibility(html) {
|
|
|
5237
5361
|
const score = Math.max(0, 100 - penalty);
|
|
5238
5362
|
return { score, issues };
|
|
5239
5363
|
}
|
|
5364
|
+
function checkAccessibility(html) {
|
|
5365
|
+
if (!html || !html.trim()) {
|
|
5366
|
+
return { score: 100, issues: [] };
|
|
5367
|
+
}
|
|
5368
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5369
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5370
|
+
}
|
|
5371
|
+
const $ = cheerio6.load(html);
|
|
5372
|
+
return checkAccessibilityFromDom($);
|
|
5373
|
+
}
|
|
5240
5374
|
|
|
5241
5375
|
// src/image-analyzer.ts
|
|
5242
5376
|
var cheerio7 = __toESM(require("cheerio"), 1);
|
|
@@ -5270,14 +5404,7 @@ function truncateSrc(src, max = 60) {
|
|
|
5270
5404
|
}
|
|
5271
5405
|
return src.length > max ? src.slice(0, max - 3) + "..." : src;
|
|
5272
5406
|
}
|
|
5273
|
-
function
|
|
5274
|
-
if (!html || !html.trim()) {
|
|
5275
|
-
return { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
5276
|
-
}
|
|
5277
|
-
if (html.length > MAX_HTML_SIZE) {
|
|
5278
|
-
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5279
|
-
}
|
|
5280
|
-
const $ = cheerio7.load(html);
|
|
5407
|
+
function analyzeImagesFromDom($) {
|
|
5281
5408
|
const issues = [];
|
|
5282
5409
|
const images = [];
|
|
5283
5410
|
let totalDataUriBytes = 0;
|
|
@@ -5403,16 +5530,318 @@ function analyzeImages(html) {
|
|
|
5403
5530
|
}
|
|
5404
5531
|
return { total: images.length, totalDataUriBytes, issues, images };
|
|
5405
5532
|
}
|
|
5533
|
+
function analyzeImages(html) {
|
|
5534
|
+
if (!html || !html.trim()) {
|
|
5535
|
+
return { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
5536
|
+
}
|
|
5537
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5538
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5539
|
+
}
|
|
5540
|
+
const $ = cheerio7.load(html);
|
|
5541
|
+
return analyzeImagesFromDom($);
|
|
5542
|
+
}
|
|
5543
|
+
|
|
5544
|
+
// src/inbox-preview.ts
|
|
5545
|
+
var cheerio8 = __toESM(require("cheerio"), 1);
|
|
5546
|
+
var MAX_SUBJECT_LENGTH = 60;
|
|
5547
|
+
var MAX_PREHEADER_LENGTH = 100;
|
|
5548
|
+
var MIN_PREHEADER_LENGTH = 30;
|
|
5549
|
+
var EMOJI_PATTERN = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{2B50}-\u{2B55}\u{FE00}-\u{FE0F}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{200D}\u{20E3}\u{E0020}-\u{E007F}]/u;
|
|
5550
|
+
var ZWNJ_PADDING_PATTERN = /(\u200C\s*( |\u00A0)\s*){2,}|(‌\s* \s*){2,}/;
|
|
5551
|
+
function extractInboxPreview(html) {
|
|
5552
|
+
if (!html || !html.trim()) {
|
|
5553
|
+
return {
|
|
5554
|
+
subject: null,
|
|
5555
|
+
preheader: null,
|
|
5556
|
+
subjectLength: 0,
|
|
5557
|
+
preheaderLength: 0,
|
|
5558
|
+
truncation: [],
|
|
5559
|
+
issues: [{ rule: "missing-subject", severity: "warning", message: "No <title> tag found. Most email clients use this as the subject line." }]
|
|
5560
|
+
};
|
|
5561
|
+
}
|
|
5562
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5563
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5564
|
+
}
|
|
5565
|
+
const $ = cheerio8.load(html);
|
|
5566
|
+
return extractInboxPreviewFromDom($);
|
|
5567
|
+
}
|
|
5568
|
+
function extractInboxPreviewFromDom($) {
|
|
5569
|
+
var _a, _b;
|
|
5570
|
+
const issues = [];
|
|
5571
|
+
const titleEl = $("title");
|
|
5572
|
+
const subject = titleEl.length > 0 ? titleEl.first().text().trim() || null : null;
|
|
5573
|
+
const subjectLength = (_a = subject == null ? void 0 : subject.length) != null ? _a : 0;
|
|
5574
|
+
if (!subject) {
|
|
5575
|
+
issues.push({
|
|
5576
|
+
rule: "missing-subject",
|
|
5577
|
+
severity: "warning",
|
|
5578
|
+
message: "No <title> tag found. Most email clients use this as the subject line."
|
|
5579
|
+
});
|
|
5580
|
+
} else if (subjectLength > MAX_SUBJECT_LENGTH) {
|
|
5581
|
+
issues.push({
|
|
5582
|
+
rule: "subject-too-long",
|
|
5583
|
+
severity: "warning",
|
|
5584
|
+
message: `Subject is ${subjectLength} characters \u2014 may be truncated in inboxes (recommended: ${MAX_SUBJECT_LENGTH} or fewer).`
|
|
5585
|
+
});
|
|
5586
|
+
}
|
|
5587
|
+
const preheader = extractPreheaderText($);
|
|
5588
|
+
const preheaderLength = (_b = preheader == null ? void 0 : preheader.length) != null ? _b : 0;
|
|
5589
|
+
if (!preheader) {
|
|
5590
|
+
issues.push({
|
|
5591
|
+
rule: "missing-preheader",
|
|
5592
|
+
severity: "info",
|
|
5593
|
+
message: "No preheader text detected. Adding preview text improves open rates in crowded inboxes."
|
|
5594
|
+
});
|
|
5595
|
+
} else if (preheaderLength < MIN_PREHEADER_LENGTH) {
|
|
5596
|
+
issues.push({
|
|
5597
|
+
rule: "preheader-too-short",
|
|
5598
|
+
severity: "warning",
|
|
5599
|
+
message: `Preheader is only ${preheaderLength} characters \u2014 email clients may backfill with body text (recommended: ${MIN_PREHEADER_LENGTH}+).`
|
|
5600
|
+
});
|
|
5601
|
+
} else if (preheaderLength > MAX_PREHEADER_LENGTH) {
|
|
5602
|
+
issues.push({
|
|
5603
|
+
rule: "preheader-too-long",
|
|
5604
|
+
severity: "info",
|
|
5605
|
+
message: `Preheader is ${preheaderLength} characters \u2014 most clients show 40\u2013100 characters. Text beyond that is hidden.`
|
|
5606
|
+
});
|
|
5607
|
+
}
|
|
5608
|
+
if (hasZwnjPadding($)) {
|
|
5609
|
+
issues.push({
|
|
5610
|
+
rule: "zwnj-padding",
|
|
5611
|
+
severity: "info",
|
|
5612
|
+
message: "Preheader uses ‌ padding hack \u2014 works in most clients but may show garbled text in some."
|
|
5613
|
+
});
|
|
5614
|
+
}
|
|
5615
|
+
if (subject && EMOJI_PATTERN.test(subject)) {
|
|
5616
|
+
issues.push({
|
|
5617
|
+
rule: "emoji-in-subject",
|
|
5618
|
+
severity: "info",
|
|
5619
|
+
message: "Subject line contains emoji \u2014 renders inconsistently across email clients and may trigger spam filters."
|
|
5620
|
+
});
|
|
5621
|
+
}
|
|
5622
|
+
const truncation = computeTruncation(subject, preheader);
|
|
5623
|
+
return {
|
|
5624
|
+
subject,
|
|
5625
|
+
preheader,
|
|
5626
|
+
subjectLength,
|
|
5627
|
+
preheaderLength,
|
|
5628
|
+
truncation,
|
|
5629
|
+
issues
|
|
5630
|
+
};
|
|
5631
|
+
}
|
|
5632
|
+
function computeTruncation(subject, preheader) {
|
|
5633
|
+
return CLIENT_DISPLAY_LIMITS.map((limit) => {
|
|
5634
|
+
const truncatedSubject = subject && subject.length > limit.subjectLimit ? subject.slice(0, limit.subjectLimit - 1) + "\u2026" : subject;
|
|
5635
|
+
const truncatedPreheader = preheader && preheader.length > limit.preheaderLimit ? preheader.slice(0, limit.preheaderLimit - 1) + "\u2026" : preheader;
|
|
5636
|
+
return {
|
|
5637
|
+
client: limit.client,
|
|
5638
|
+
subjectLimit: limit.subjectLimit,
|
|
5639
|
+
preheaderLimit: limit.preheaderLimit,
|
|
5640
|
+
truncatedSubject: truncatedSubject != null ? truncatedSubject : null,
|
|
5641
|
+
truncatedPreheader: truncatedPreheader != null ? truncatedPreheader : null,
|
|
5642
|
+
subjectTruncated: !!subject && subject.length > limit.subjectLimit,
|
|
5643
|
+
preheaderTruncated: !!preheader && preheader.length > limit.preheaderLimit
|
|
5644
|
+
};
|
|
5645
|
+
});
|
|
5646
|
+
}
|
|
5647
|
+
function hasZwnjPadding($) {
|
|
5648
|
+
const body = $("body");
|
|
5649
|
+
if (!body.length) return false;
|
|
5650
|
+
const hiddenSelectors = [
|
|
5651
|
+
'div[style*="display:none"]',
|
|
5652
|
+
'div[style*="display: none"]',
|
|
5653
|
+
'span[style*="display:none"]',
|
|
5654
|
+
'span[style*="display: none"]',
|
|
5655
|
+
'div[style*="max-height:0"]',
|
|
5656
|
+
'div[style*="max-height: 0"]',
|
|
5657
|
+
'span[style*="max-height:0"]',
|
|
5658
|
+
'span[style*="max-height: 0"]',
|
|
5659
|
+
'[class*="preheader"]',
|
|
5660
|
+
'[class*="preview-text"]',
|
|
5661
|
+
'[class*="previewText"]'
|
|
5662
|
+
];
|
|
5663
|
+
for (const sel of hiddenSelectors) {
|
|
5664
|
+
const el = body.find(sel).first();
|
|
5665
|
+
if (el.length) {
|
|
5666
|
+
const rawHtml = el.html() || "";
|
|
5667
|
+
if (ZWNJ_PADDING_PATTERN.test(rawHtml)) return true;
|
|
5668
|
+
}
|
|
5669
|
+
}
|
|
5670
|
+
return false;
|
|
5671
|
+
}
|
|
5672
|
+
function extractPreheaderText($) {
|
|
5673
|
+
const body = $("body");
|
|
5674
|
+
if (!body.length) return null;
|
|
5675
|
+
const hiddenSelectors = [
|
|
5676
|
+
'div[style*="display:none"]',
|
|
5677
|
+
'div[style*="display: none"]',
|
|
5678
|
+
'span[style*="display:none"]',
|
|
5679
|
+
'span[style*="display: none"]',
|
|
5680
|
+
'div[style*="visibility:hidden"]',
|
|
5681
|
+
'div[style*="visibility: hidden"]',
|
|
5682
|
+
'span[style*="visibility:hidden"]',
|
|
5683
|
+
'span[style*="visibility: hidden"]',
|
|
5684
|
+
'div[style*="max-height:0"]',
|
|
5685
|
+
'div[style*="max-height: 0"]',
|
|
5686
|
+
'span[style*="max-height:0"]',
|
|
5687
|
+
'span[style*="max-height: 0"]',
|
|
5688
|
+
'div[style*="mso-hide:all"]',
|
|
5689
|
+
'div[style*="mso-hide: all"]'
|
|
5690
|
+
];
|
|
5691
|
+
for (const sel of hiddenSelectors) {
|
|
5692
|
+
const el = body.find(sel).first();
|
|
5693
|
+
if (el.length) {
|
|
5694
|
+
const text = el.text().trim();
|
|
5695
|
+
if (text) return text;
|
|
5696
|
+
}
|
|
5697
|
+
}
|
|
5698
|
+
const preheaderClasses = body.find(
|
|
5699
|
+
'[class*="preheader"], [class*="preview-text"], [class*="previewText"]'
|
|
5700
|
+
).first();
|
|
5701
|
+
if (preheaderClasses.length) {
|
|
5702
|
+
const text = preheaderClasses.text().trim();
|
|
5703
|
+
if (text) return text;
|
|
5704
|
+
}
|
|
5705
|
+
const firstText = getFirstVisibleText($, body);
|
|
5706
|
+
return firstText || null;
|
|
5707
|
+
}
|
|
5708
|
+
function getFirstVisibleText($, body) {
|
|
5709
|
+
const skipTags = /* @__PURE__ */ new Set(["style", "script", "head", "title"]);
|
|
5710
|
+
let result = null;
|
|
5711
|
+
body.find("*").each((_, el) => {
|
|
5712
|
+
var _a;
|
|
5713
|
+
if (result) return false;
|
|
5714
|
+
const tagName = (_a = el.tagName) == null ? void 0 : _a.toLowerCase();
|
|
5715
|
+
if (skipTags.has(tagName)) return;
|
|
5716
|
+
const style = $(el).attr("style") || "";
|
|
5717
|
+
if (style.includes("display:none") || style.includes("display: none") || style.includes("visibility:hidden") || style.includes("visibility: hidden")) {
|
|
5718
|
+
return;
|
|
5719
|
+
}
|
|
5720
|
+
const directText = $(el).contents().filter((_2, node) => node.type === "text").text().trim();
|
|
5721
|
+
if (directText && directText.length > 1) {
|
|
5722
|
+
const cleaned = directText.replace(/[\u200B\u00A0\s]+/g, " ").trim();
|
|
5723
|
+
if (cleaned.length > 1) {
|
|
5724
|
+
result = cleaned;
|
|
5725
|
+
return false;
|
|
5726
|
+
}
|
|
5727
|
+
}
|
|
5728
|
+
});
|
|
5729
|
+
return result;
|
|
5730
|
+
}
|
|
5731
|
+
|
|
5732
|
+
// src/size-checker.ts
|
|
5733
|
+
var cheerio9 = __toESM(require("cheerio"), 1);
|
|
5734
|
+
function humanizeBytes(bytes) {
|
|
5735
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
5736
|
+
const kb = bytes / 1024;
|
|
5737
|
+
if (kb < 1024) return `${kb.toFixed(1)} KB`;
|
|
5738
|
+
const mb = kb / 1024;
|
|
5739
|
+
return `${mb.toFixed(2)} MB`;
|
|
5740
|
+
}
|
|
5741
|
+
function checkSizeFromDom(_$, html) {
|
|
5742
|
+
const htmlBytes = new TextEncoder().encode(html).length;
|
|
5743
|
+
const humanSize = humanizeBytes(htmlBytes);
|
|
5744
|
+
const issues = [];
|
|
5745
|
+
let clipped = false;
|
|
5746
|
+
if (htmlBytes > GMAIL_CLIP_THRESHOLD) {
|
|
5747
|
+
clipped = true;
|
|
5748
|
+
issues.push({
|
|
5749
|
+
rule: "gmail-clipped",
|
|
5750
|
+
severity: "error",
|
|
5751
|
+
message: `Email is ${humanSize} \u2014 Gmail will clip it at ~102 KB. Recipients see a "View entire message" link instead of your content.`,
|
|
5752
|
+
detail: `${htmlBytes} bytes exceeds the ${GMAIL_CLIP_THRESHOLD} byte threshold.`
|
|
5753
|
+
});
|
|
5754
|
+
} else if (htmlBytes > GMAIL_CLIP_WARNING_THRESHOLD) {
|
|
5755
|
+
issues.push({
|
|
5756
|
+
rule: "gmail-clip-warning",
|
|
5757
|
+
severity: "warning",
|
|
5758
|
+
message: `Email is ${humanSize} \u2014 approaching Gmail's ~102 KB clip threshold. Consider trimming.`,
|
|
5759
|
+
detail: `${htmlBytes} bytes is within ${GMAIL_CLIP_THRESHOLD - htmlBytes} bytes of the clip threshold.`
|
|
5760
|
+
});
|
|
5761
|
+
}
|
|
5762
|
+
return { htmlBytes, humanSize, clipped, issues };
|
|
5763
|
+
}
|
|
5764
|
+
function checkSize(html) {
|
|
5765
|
+
if (!html || !html.trim()) {
|
|
5766
|
+
return { htmlBytes: 0, humanSize: "0 B", clipped: false, issues: [] };
|
|
5767
|
+
}
|
|
5768
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5769
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5770
|
+
}
|
|
5771
|
+
const $ = cheerio9.load(html);
|
|
5772
|
+
return checkSizeFromDom($, html);
|
|
5773
|
+
}
|
|
5774
|
+
|
|
5775
|
+
// src/template-checker.ts
|
|
5776
|
+
var cheerio10 = __toESM(require("cheerio"), 1);
|
|
5777
|
+
function checkTemplateVariablesFromDom($) {
|
|
5778
|
+
const issues = [];
|
|
5779
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5780
|
+
const textContent = extractTextContent($);
|
|
5781
|
+
for (const [pattern, label] of TEMPLATE_VARIABLE_PATTERNS) {
|
|
5782
|
+
pattern.lastIndex = 0;
|
|
5783
|
+
let match;
|
|
5784
|
+
while ((match = pattern.exec(textContent)) !== null) {
|
|
5785
|
+
const variable = match[0];
|
|
5786
|
+
const key = `text:${variable}`;
|
|
5787
|
+
if (seen.has(key)) continue;
|
|
5788
|
+
seen.add(key);
|
|
5789
|
+
issues.push({
|
|
5790
|
+
rule: "unresolved-variable",
|
|
5791
|
+
severity: "error",
|
|
5792
|
+
message: `Unresolved ${label} variable "${variable}" found in text content.`,
|
|
5793
|
+
variable,
|
|
5794
|
+
location: "text"
|
|
5795
|
+
});
|
|
5796
|
+
}
|
|
5797
|
+
}
|
|
5798
|
+
const attrSelectors = ["[href]", "[src]", "[alt]"];
|
|
5799
|
+
for (const sel of attrSelectors) {
|
|
5800
|
+
$(sel).each((_, el) => {
|
|
5801
|
+
const attrs = ["href", "src", "alt"];
|
|
5802
|
+
for (const attr of attrs) {
|
|
5803
|
+
const value = $(el).attr(attr);
|
|
5804
|
+
if (!value) continue;
|
|
5805
|
+
for (const [pattern, label] of TEMPLATE_VARIABLE_PATTERNS) {
|
|
5806
|
+
pattern.lastIndex = 0;
|
|
5807
|
+
let match;
|
|
5808
|
+
while ((match = pattern.exec(value)) !== null) {
|
|
5809
|
+
const variable = match[0];
|
|
5810
|
+
const key = `attr:${attr}:${variable}`;
|
|
5811
|
+
if (seen.has(key)) continue;
|
|
5812
|
+
seen.add(key);
|
|
5813
|
+
issues.push({
|
|
5814
|
+
rule: "unresolved-variable",
|
|
5815
|
+
severity: "error",
|
|
5816
|
+
message: `Unresolved ${label} variable "${variable}" found in ${attr} attribute.`,
|
|
5817
|
+
variable,
|
|
5818
|
+
location: "attribute"
|
|
5819
|
+
});
|
|
5820
|
+
}
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
});
|
|
5824
|
+
}
|
|
5825
|
+
return { unresolvedCount: issues.length, issues };
|
|
5826
|
+
}
|
|
5827
|
+
function extractTextContent($) {
|
|
5828
|
+
const clone = $.root().clone();
|
|
5829
|
+
clone.find("style, script, head").remove();
|
|
5830
|
+
return clone.text();
|
|
5831
|
+
}
|
|
5832
|
+
function checkTemplateVariables(html) {
|
|
5833
|
+
if (!html || !html.trim()) {
|
|
5834
|
+
return { unresolvedCount: 0, issues: [] };
|
|
5835
|
+
}
|
|
5836
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5837
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5838
|
+
}
|
|
5839
|
+
const $ = cheerio10.load(html);
|
|
5840
|
+
return checkTemplateVariablesFromDom($);
|
|
5841
|
+
}
|
|
5406
5842
|
|
|
5407
5843
|
// src/audit.ts
|
|
5408
|
-
var
|
|
5409
|
-
var EMPTY_LINKS = {
|
|
5410
|
-
totalLinks: 0,
|
|
5411
|
-
issues: [],
|
|
5412
|
-
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
5413
|
-
};
|
|
5414
|
-
var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
|
|
5415
|
-
var EMPTY_IMAGES = { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
5844
|
+
var cheerio11 = __toESM(require("cheerio"), 1);
|
|
5416
5845
|
function auditEmail(html, options) {
|
|
5417
5846
|
var _a;
|
|
5418
5847
|
if (!html || !html.trim()) {
|
|
@@ -5421,7 +5850,10 @@ function auditEmail(html, options) {
|
|
|
5421
5850
|
spam: EMPTY_SPAM,
|
|
5422
5851
|
links: EMPTY_LINKS,
|
|
5423
5852
|
accessibility: EMPTY_ACCESSIBILITY,
|
|
5424
|
-
images: EMPTY_IMAGES
|
|
5853
|
+
images: EMPTY_IMAGES,
|
|
5854
|
+
inboxPreview: EMPTY_INBOX_PREVIEW,
|
|
5855
|
+
size: EMPTY_SIZE,
|
|
5856
|
+
templateVariables: EMPTY_TEMPLATE
|
|
5425
5857
|
};
|
|
5426
5858
|
}
|
|
5427
5859
|
if (html.length > MAX_HTML_SIZE) {
|
|
@@ -5429,13 +5861,111 @@ function auditEmail(html, options) {
|
|
|
5429
5861
|
}
|
|
5430
5862
|
const framework = options == null ? void 0 : options.framework;
|
|
5431
5863
|
const skip = new Set((_a = options == null ? void 0 : options.skip) != null ? _a : []);
|
|
5432
|
-
const
|
|
5864
|
+
const $ = cheerio11.load(html);
|
|
5865
|
+
const warnings = skip.has("compatibility") ? [] : analyzeEmailFromDom($, framework);
|
|
5433
5866
|
const scores = skip.has("compatibility") ? {} : generateCompatibilityScore(warnings);
|
|
5434
|
-
const spam = skip.has("spam") ? EMPTY_SPAM :
|
|
5435
|
-
const links = skip.has("links") ? EMPTY_LINKS :
|
|
5436
|
-
const accessibility = skip.has("accessibility") ? EMPTY_ACCESSIBILITY :
|
|
5437
|
-
const images = skip.has("images") ? EMPTY_IMAGES :
|
|
5438
|
-
|
|
5867
|
+
const spam = skip.has("spam") ? EMPTY_SPAM : analyzeSpamFromDom($, options == null ? void 0 : options.spam);
|
|
5868
|
+
const links = skip.has("links") ? EMPTY_LINKS : validateLinksFromDom($);
|
|
5869
|
+
const accessibility = skip.has("accessibility") ? EMPTY_ACCESSIBILITY : checkAccessibilityFromDom($);
|
|
5870
|
+
const images = skip.has("images") ? EMPTY_IMAGES : analyzeImagesFromDom($);
|
|
5871
|
+
const inboxPreview = skip.has("inboxPreview") ? EMPTY_INBOX_PREVIEW : extractInboxPreviewFromDom($);
|
|
5872
|
+
const size = skip.has("size") ? EMPTY_SIZE : checkSizeFromDom($, html);
|
|
5873
|
+
const templateVariables = skip.has("templateVariables") ? EMPTY_TEMPLATE : checkTemplateVariablesFromDom($);
|
|
5874
|
+
return { compatibility: { warnings, scores }, spam, links, accessibility, images, inboxPreview, size, templateVariables };
|
|
5875
|
+
}
|
|
5876
|
+
|
|
5877
|
+
// src/session.ts
|
|
5878
|
+
var cheerio12 = __toESM(require("cheerio"), 1);
|
|
5879
|
+
function createSession(html, options) {
|
|
5880
|
+
if (!html || !html.trim()) {
|
|
5881
|
+
const fw = options == null ? void 0 : options.framework;
|
|
5882
|
+
return {
|
|
5883
|
+
html: html || "",
|
|
5884
|
+
framework: fw,
|
|
5885
|
+
audit: () => ({
|
|
5886
|
+
compatibility: { warnings: [], scores: {} },
|
|
5887
|
+
spam: EMPTY_SPAM,
|
|
5888
|
+
links: EMPTY_LINKS,
|
|
5889
|
+
accessibility: EMPTY_ACCESSIBILITY,
|
|
5890
|
+
images: EMPTY_IMAGES,
|
|
5891
|
+
inboxPreview: EMPTY_INBOX_PREVIEW,
|
|
5892
|
+
size: EMPTY_SIZE,
|
|
5893
|
+
templateVariables: EMPTY_TEMPLATE
|
|
5894
|
+
}),
|
|
5895
|
+
analyze: () => [],
|
|
5896
|
+
score: () => ({}),
|
|
5897
|
+
analyzeSpam: () => EMPTY_SPAM,
|
|
5898
|
+
validateLinks: () => EMPTY_LINKS,
|
|
5899
|
+
checkAccessibility: () => EMPTY_ACCESSIBILITY,
|
|
5900
|
+
analyzeImages: () => EMPTY_IMAGES,
|
|
5901
|
+
extractInboxPreview: () => EMPTY_INBOX_PREVIEW,
|
|
5902
|
+
checkSize: () => EMPTY_SIZE,
|
|
5903
|
+
checkTemplateVariables: () => EMPTY_TEMPLATE,
|
|
5904
|
+
transformForClient: (clientId) => ({ clientId, html: html || "", warnings: [] }),
|
|
5905
|
+
transformForAllClients: () => [],
|
|
5906
|
+
simulateDarkMode: (clientId) => ({ html: html || "", warnings: [] })
|
|
5907
|
+
};
|
|
5908
|
+
}
|
|
5909
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5910
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5911
|
+
}
|
|
5912
|
+
const $ = cheerio12.load(html);
|
|
5913
|
+
const framework = options == null ? void 0 : options.framework;
|
|
5914
|
+
return {
|
|
5915
|
+
html,
|
|
5916
|
+
framework,
|
|
5917
|
+
audit(opts) {
|
|
5918
|
+
var _a;
|
|
5919
|
+
const skip = new Set((_a = opts == null ? void 0 : opts.skip) != null ? _a : []);
|
|
5920
|
+
const warnings = skip.has("compatibility") ? [] : analyzeEmailFromDom($, framework);
|
|
5921
|
+
const scores = skip.has("compatibility") ? {} : generateCompatibilityScore(warnings);
|
|
5922
|
+
const spam = skip.has("spam") ? EMPTY_SPAM : analyzeSpamFromDom($, opts == null ? void 0 : opts.spam);
|
|
5923
|
+
const links = skip.has("links") ? EMPTY_LINKS : validateLinksFromDom($);
|
|
5924
|
+
const accessibility = skip.has("accessibility") ? EMPTY_ACCESSIBILITY : checkAccessibilityFromDom($);
|
|
5925
|
+
const images = skip.has("images") ? EMPTY_IMAGES : analyzeImagesFromDom($);
|
|
5926
|
+
const inboxPreview = skip.has("inboxPreview") ? EMPTY_INBOX_PREVIEW : extractInboxPreviewFromDom($);
|
|
5927
|
+
const size = skip.has("size") ? EMPTY_SIZE : checkSizeFromDom($, html);
|
|
5928
|
+
const templateVariables = skip.has("templateVariables") ? EMPTY_TEMPLATE : checkTemplateVariablesFromDom($);
|
|
5929
|
+
return { compatibility: { warnings, scores }, spam, links, accessibility, images, inboxPreview, size, templateVariables };
|
|
5930
|
+
},
|
|
5931
|
+
analyze() {
|
|
5932
|
+
return analyzeEmailFromDom($, framework);
|
|
5933
|
+
},
|
|
5934
|
+
score(warnings) {
|
|
5935
|
+
return generateCompatibilityScore(warnings);
|
|
5936
|
+
},
|
|
5937
|
+
analyzeSpam(opts) {
|
|
5938
|
+
return analyzeSpamFromDom($, opts);
|
|
5939
|
+
},
|
|
5940
|
+
validateLinks() {
|
|
5941
|
+
return validateLinksFromDom($);
|
|
5942
|
+
},
|
|
5943
|
+
checkAccessibility() {
|
|
5944
|
+
return checkAccessibilityFromDom($);
|
|
5945
|
+
},
|
|
5946
|
+
analyzeImages() {
|
|
5947
|
+
return analyzeImagesFromDom($);
|
|
5948
|
+
},
|
|
5949
|
+
extractInboxPreview() {
|
|
5950
|
+
return extractInboxPreviewFromDom($);
|
|
5951
|
+
},
|
|
5952
|
+
checkSize() {
|
|
5953
|
+
return checkSizeFromDom($, html);
|
|
5954
|
+
},
|
|
5955
|
+
checkTemplateVariables() {
|
|
5956
|
+
return checkTemplateVariablesFromDom($);
|
|
5957
|
+
},
|
|
5958
|
+
// Transforms create isolated copies since they mutate the DOM
|
|
5959
|
+
transformForClient(clientId) {
|
|
5960
|
+
return transformForClient(html, clientId, framework);
|
|
5961
|
+
},
|
|
5962
|
+
transformForAllClients() {
|
|
5963
|
+
return transformForAllClients(html, framework);
|
|
5964
|
+
},
|
|
5965
|
+
simulateDarkMode(clientId) {
|
|
5966
|
+
return simulateDarkMode(html, clientId);
|
|
5967
|
+
}
|
|
5968
|
+
};
|
|
5439
5969
|
}
|
|
5440
5970
|
|
|
5441
5971
|
// src/compile/errors.ts
|
|
@@ -5461,10 +5991,14 @@ var CompileError = class extends Error {
|
|
|
5461
5991
|
analyzeSpam,
|
|
5462
5992
|
auditEmail,
|
|
5463
5993
|
checkAccessibility,
|
|
5994
|
+
checkSize,
|
|
5995
|
+
checkTemplateVariables,
|
|
5464
5996
|
contrastRatio,
|
|
5997
|
+
createSession,
|
|
5465
5998
|
diffResults,
|
|
5466
5999
|
errorWarnings,
|
|
5467
6000
|
estimateAiFixTokens,
|
|
6001
|
+
extractInboxPreview,
|
|
5468
6002
|
generateAiFix,
|
|
5469
6003
|
generateCompatibilityScore,
|
|
5470
6004
|
generateFixPrompt,
|