@emailens/engine 0.11.6 → 0.11.8

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.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, g as AccessibilityIssue, I as ImageReport, h as InboxPreview, i as SizeReport, j as TemplateReport, O as OverflowReport, V as VmlReport, k as VisualReport, l as DesignReport, m as DeliverabilityReport } from './types-bvGDId6f.cjs';
2
- export { B as BaseIssue, n as ClientTruncation, o as DeliverabilityCheck, p as DeliverabilityIssue, q as DesignIssue, r as EstimateOptions, s as ExportScope, t as FixType, u as ImageInfo, v as ImageIssue, w as InboxPreviewIssue, x as InputFormat, y as LinkIssue, z as OverflowIssue, P as PreviewResult, G as Severity, H as SizeIssue, J as SourceLocation, K as SpamIssue, M as TemplateIssue, N as TokenEstimate, Q as TokenEstimateWithWarnings, R as VisualIssue, U as VmlIssue, W as estimateAiFixTokens, X as generateFixPrompt, Y as heuristicTokenCount } from './types-bvGDId6f.cjs';
1
+ import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, g as AccessibilityIssue, I as ImageReport, h as InboxPreview, i as SizeReport, j as TemplateReport, O as OverflowReport, V as VmlReport, k as VisualReport, l as DesignReport, m as DeliverabilityReport } from './types-DiseE-UH.cjs';
2
+ export { B as BaseIssue, n as ClientTruncation, o as DeliverabilityCheck, p as DeliverabilityIssue, q as DesignIssue, r as EstimateOptions, s as ExportScope, t as FixType, u as ImageInfo, v as ImageIssue, w as InboxPreviewIssue, x as InputFormat, y as LinkInfo, z as LinkIssue, G as OverflowIssue, P as PreviewResult, H as Severity, J as SizeIssue, K as SourceLocation, M as SpamIssue, N as TemplateIssue, Q as TokenEstimate, R as TokenEstimateWithWarnings, U as VisualIssue, W as VmlIssue, X as estimateAiFixTokens, Y as generateFixPrompt, Z as heuristicTokenCount } from './types-DiseE-UH.cjs';
3
3
  import * as cheerio from 'cheerio';
4
- export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-DuZAgJtw.cjs';
4
+ export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-DRkmpgX-.cjs';
5
5
 
6
6
  declare const EMAIL_CLIENTS: EmailClient[];
7
7
  declare function getClient(id: string): EmailClient | undefined;
@@ -410,12 +410,32 @@ declare function checkVml(html: string, options?: {
410
410
  * `roundrect` and `rect` are the whole surface. Anything else is left alone
411
411
  * rather than approximated.
412
412
  *
413
- * Deliberately NOT emulated: how Word *mis*-renders a nested shape. That is a
414
- * real and confirmed failure (the container disappears and text stops drawing
415
- * further down the document), but reproducing it means perturbing Chromium's
416
- * layout into a picture that is wrong in a third way, different from both the
417
- * correct render and Outlook's. `checkVml` reports it with a line number
418
- * instead, which is more useful and cannot mislead.
413
+ * How Word *mis*-renders a nested shape is emulated, but only the parts with
414
+ * evidence behind them. This note used to say the failure was not emulated at
415
+ * all, on the grounds that reproducing it meant perturbing Chromium into a
416
+ * picture wrong in a third way. That argument does not survive contact with
417
+ * the specific effects:
418
+ *
419
+ * - the container's fill and geometry disappear -> drop the box. Exact.
420
+ * - VML shapes further down stop drawing text -> blank their labels.
421
+ * Exact, and scoped to
422
+ * shapes we created.
423
+ * - the table structure terminates early, so later
424
+ * content escapes the frame -> WITHDRAWN. It had no
425
+ * methodology, and the
426
+ * fixture built to
427
+ * measure it contradicts
428
+ * it.
429
+ *
430
+ * Two of the three needed no distortion at all; the third turned out not to be
431
+ * a rendering problem but an evidence problem. Refusing to draw any of it
432
+ * meant the preview showed a tidy box with a button in it, a picture no Word
433
+ * engine produces, sitting beside a finding that said the region was
434
+ * destroyed. Between a warning and a screenshot, people believe the
435
+ * screenshot.
436
+ *
437
+ * Affected regions carry `data-vml-outlook` so a surface can label them, since
438
+ * a blank button should read as "Outlook does this" and not as our bug.
419
439
  */
420
440
  /**
421
441
  * `arcsize` is a VgFraction: a proportion of *half the shape's shorter side*,
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, g as AccessibilityIssue, I as ImageReport, h as InboxPreview, i as SizeReport, j as TemplateReport, O as OverflowReport, V as VmlReport, k as VisualReport, l as DesignReport, m as DeliverabilityReport } from './types-bvGDId6f.js';
2
- export { B as BaseIssue, n as ClientTruncation, o as DeliverabilityCheck, p as DeliverabilityIssue, q as DesignIssue, r as EstimateOptions, s as ExportScope, t as FixType, u as ImageInfo, v as ImageIssue, w as InboxPreviewIssue, x as InputFormat, y as LinkIssue, z as OverflowIssue, P as PreviewResult, G as Severity, H as SizeIssue, J as SourceLocation, K as SpamIssue, M as TemplateIssue, N as TokenEstimate, Q as TokenEstimateWithWarnings, R as VisualIssue, U as VmlIssue, W as estimateAiFixTokens, X as generateFixPrompt, Y as heuristicTokenCount } from './types-bvGDId6f.js';
1
+ import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, g as AccessibilityIssue, I as ImageReport, h as InboxPreview, i as SizeReport, j as TemplateReport, O as OverflowReport, V as VmlReport, k as VisualReport, l as DesignReport, m as DeliverabilityReport } from './types-DiseE-UH.js';
2
+ export { B as BaseIssue, n as ClientTruncation, o as DeliverabilityCheck, p as DeliverabilityIssue, q as DesignIssue, r as EstimateOptions, s as ExportScope, t as FixType, u as ImageInfo, v as ImageIssue, w as InboxPreviewIssue, x as InputFormat, y as LinkInfo, z as LinkIssue, G as OverflowIssue, P as PreviewResult, H as Severity, J as SizeIssue, K as SourceLocation, M as SpamIssue, N as TemplateIssue, Q as TokenEstimate, R as TokenEstimateWithWarnings, U as VisualIssue, W as VmlIssue, X as estimateAiFixTokens, Y as generateFixPrompt, Z as heuristicTokenCount } from './types-DiseE-UH.js';
3
3
  import * as cheerio from 'cheerio';
4
- export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-BROBcC8u.js';
4
+ export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-B82A1R_K.js';
5
5
 
6
6
  declare const EMAIL_CLIENTS: EmailClient[];
7
7
  declare function getClient(id: string): EmailClient | undefined;
@@ -410,12 +410,32 @@ declare function checkVml(html: string, options?: {
410
410
  * `roundrect` and `rect` are the whole surface. Anything else is left alone
411
411
  * rather than approximated.
412
412
  *
413
- * Deliberately NOT emulated: how Word *mis*-renders a nested shape. That is a
414
- * real and confirmed failure (the container disappears and text stops drawing
415
- * further down the document), but reproducing it means perturbing Chromium's
416
- * layout into a picture that is wrong in a third way, different from both the
417
- * correct render and Outlook's. `checkVml` reports it with a line number
418
- * instead, which is more useful and cannot mislead.
413
+ * How Word *mis*-renders a nested shape is emulated, but only the parts with
414
+ * evidence behind them. This note used to say the failure was not emulated at
415
+ * all, on the grounds that reproducing it meant perturbing Chromium into a
416
+ * picture wrong in a third way. That argument does not survive contact with
417
+ * the specific effects:
418
+ *
419
+ * - the container's fill and geometry disappear -> drop the box. Exact.
420
+ * - VML shapes further down stop drawing text -> blank their labels.
421
+ * Exact, and scoped to
422
+ * shapes we created.
423
+ * - the table structure terminates early, so later
424
+ * content escapes the frame -> WITHDRAWN. It had no
425
+ * methodology, and the
426
+ * fixture built to
427
+ * measure it contradicts
428
+ * it.
429
+ *
430
+ * Two of the three needed no distortion at all; the third turned out not to be
431
+ * a rendering problem but an evidence problem. Refusing to draw any of it
432
+ * meant the preview showed a tidy box with a button in it, a picture no Word
433
+ * engine produces, sitting beside a finding that said the region was
434
+ * destroyed. Between a warning and a screenshot, people believe the
435
+ * screenshot.
436
+ *
437
+ * Affected regions carry `data-vml-outlook` so a surface can label them, since
438
+ * a blank button should read as "Outlook does this" and not as our bug.
419
439
  */
420
440
  /**
421
441
  * `arcsize` is a VgFraction: a proportion of *half the shape's shorter side*,
package/dist/index.js CHANGED
@@ -9768,8 +9768,10 @@ function arcsizeToRadius(arcsize, width, height) {
9768
9768
  if (!Number.isFinite(pct)) return 0;
9769
9769
  return Math.min(100, Math.max(0, pct)) / 100 * (Math.min(width, height) / 2);
9770
9770
  }
9771
+ var DOWNLEVEL_REVEALED = /<!--\[if[^\]]*!\s*(?:mso|vml)[^\]]*\]>\s*<!--(?:>|\s*-->)([\s\S]*?)<!--\s*<!\[endif\]-->/gi;
9772
+ var DOWNLEVEL_HIDDEN = /<!--\[if(?![^\]]*!)[^\]]*(?:mso|vml)[^\]]*\]>([\s\S]*?)<!\[endif\]-->/gi;
9771
9773
  function resolveMsoBranch(html) {
9772
- return html.replace(/<!--\[if\s*!\s*(?:mso|vml)[^\]]*\]><!-->([\s\S]*?)<!--<!\[endif\]-->/gi, "").replace(/<!--\[if[^\]]*(?:mso|vml)[^\]]*\]>([\s\S]*?)<!\[endif\]-->/gi, "$1");
9774
+ return html.replace(DOWNLEVEL_REVEALED, "").replace(DOWNLEVEL_HIDDEN, "$1");
9773
9775
  }
9774
9776
  function vmlToCss(html) {
9775
9777
  let out = html;
@@ -9838,10 +9840,23 @@ function vmlToCss(html) {
9838
9840
  solid ? `background-color:${solid}` : "",
9839
9841
  `padding:${padding}`
9840
9842
  ].filter(Boolean).join(";");
9843
+ if (/data-vml="/.test(content)) {
9844
+ return `<div data-vml="rect" data-vml-outlook="container-dropped">${content}</div>`;
9845
+ }
9841
9846
  return `<div data-vml="rect" style="${background}${box}">${content}</div>`;
9842
9847
  }
9843
9848
  );
9844
- return out.replace(/<\/?v:(?:fill|textbox|stroke|shadow|imagedata|path|formulas|handles)[^>]*?\/?>/gi, "");
9849
+ out = out.replace(/<\/?v:(?:fill|textbox|stroke|shadow|imagedata|path|formulas|handles)[^>]*?\/?>/gi, "");
9850
+ return blankLabelsAfterNestedShape(out);
9851
+ }
9852
+ function blankLabelsAfterNestedShape(html) {
9853
+ const at = html.indexOf('data-vml-outlook="container-dropped"');
9854
+ if (at === -1) return html;
9855
+ const tail = html.slice(at).replace(
9856
+ /(<div data-vml="roundrect")([^>]*>)([\s\S]*?)(<\/div>)/gi,
9857
+ (whole, open, rest, label, close) => label.trim() ? `${open}${rest.slice(0, -1)} data-vml-outlook="label-dropped">${close}` : whole
9858
+ );
9859
+ return html.slice(0, at) + tail;
9845
9860
  }
9846
9861
  function renderOutlookBranch(html) {
9847
9862
  if (!/<!--\[if/i.test(html)) return html;
@@ -9893,6 +9908,7 @@ var EMPTY_SPAM = { score: 100, level: "low", issues: [] };
9893
9908
  var EMPTY_LINKS = {
9894
9909
  totalLinks: 0,
9895
9910
  issues: [],
9911
+ links: [],
9896
9912
  breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
9897
9913
  };
9898
9914
  var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
@@ -12993,6 +13009,7 @@ function isPlaceholderHref(href) {
12993
13009
  }
12994
13010
  function validateLinksFromDom($) {
12995
13011
  const issues = [];
13012
+ const inventory = [];
12996
13013
  const breakdown = { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 };
12997
13014
  const links = $("a");
12998
13015
  const totalLinks = links.length;
@@ -13002,13 +13019,22 @@ function validateLinksFromDom($) {
13002
13019
  rule: "no-links",
13003
13020
  message: "Email contains no links"
13004
13021
  });
13005
- return { totalLinks: 0, issues, breakdown };
13022
+ return { totalLinks: 0, issues, breakdown, links: [] };
13006
13023
  }
13007
13024
  const hrefCounts = /* @__PURE__ */ new Map();
13008
13025
  links.each((_, el) => {
13009
13026
  const href = $(el).attr("href") || "";
13010
13027
  const text = $(el).text().trim();
13011
13028
  const category = classifyHref(href);
13029
+ const entry = {
13030
+ href,
13031
+ text: text.slice(0, 80),
13032
+ scheme: category,
13033
+ isPlaceholder: isPlaceholderHref(href),
13034
+ issues: []
13035
+ };
13036
+ inventory.push(entry);
13037
+ const flag = (rule) => entry.issues.push(rule);
13012
13038
  const elLoc = locOfElement(el);
13013
13039
  const hrefLoc = href ? locOfAttr(el, "href") : elLoc;
13014
13040
  switch (category) {
@@ -13041,6 +13067,7 @@ function validateLinksFromDom($) {
13041
13067
  hrefCounts.set(href, (hrefCounts.get(href) || 0) + 1);
13042
13068
  }
13043
13069
  if (!href || !href.trim()) {
13070
+ flag("empty-href");
13044
13071
  issues.push(__spreadValues({
13045
13072
  severity: "error",
13046
13073
  rule: "empty-href",
@@ -13050,6 +13077,7 @@ function validateLinksFromDom($) {
13050
13077
  return;
13051
13078
  }
13052
13079
  if (category === "javascript" && !isPlaceholderHref(href)) {
13080
+ flag("javascript-href");
13053
13081
  issues.push(__spreadValues({
13054
13082
  severity: "error",
13055
13083
  rule: "javascript-href",
@@ -13060,6 +13088,7 @@ function validateLinksFromDom($) {
13060
13088
  return;
13061
13089
  }
13062
13090
  if (isPlaceholderHref(href)) {
13091
+ flag("placeholder-href");
13063
13092
  issues.push(__spreadValues({
13064
13093
  severity: "warning",
13065
13094
  rule: "placeholder-href",
@@ -13070,6 +13099,7 @@ function validateLinksFromDom($) {
13070
13099
  return;
13071
13100
  }
13072
13101
  if (category === "http") {
13102
+ flag("insecure-link");
13073
13103
  issues.push(__spreadValues({
13074
13104
  severity: "warning",
13075
13105
  rule: "insecure-link",
@@ -13079,6 +13109,7 @@ function validateLinksFromDom($) {
13079
13109
  }, hrefLoc ? { loc: hrefLoc } : {}));
13080
13110
  }
13081
13111
  if (category === "protocol-relative") {
13112
+ flag("protocol-relative");
13082
13113
  issues.push(__spreadValues({
13083
13114
  severity: "warning",
13084
13115
  rule: "protocol-relative",
@@ -13088,6 +13119,7 @@ function validateLinksFromDom($) {
13088
13119
  }, hrefLoc ? { loc: hrefLoc } : {}));
13089
13120
  }
13090
13121
  if (text && GENERIC_LINK_TEXT.has(text.toLowerCase())) {
13122
+ flag("generic-link-text");
13091
13123
  issues.push(__spreadValues({
13092
13124
  severity: "warning",
13093
13125
  rule: "generic-link-text",
@@ -13097,6 +13129,7 @@ function validateLinksFromDom($) {
13097
13129
  }, elLoc ? { loc: elLoc } : {}));
13098
13130
  }
13099
13131
  if (!text && !$(el).attr("aria-label") && !$(el).find("img[alt]").length) {
13132
+ flag("empty-link-text");
13100
13133
  issues.push(__spreadValues({
13101
13134
  severity: "error",
13102
13135
  rule: "empty-link-text",
@@ -13105,6 +13138,7 @@ function validateLinksFromDom($) {
13105
13138
  }, elLoc ? { loc: elLoc } : {}));
13106
13139
  }
13107
13140
  if (category === "mailto" && href.trim().toLowerCase() === "mailto:") {
13141
+ flag("empty-mailto");
13108
13142
  issues.push(__spreadValues({
13109
13143
  severity: "error",
13110
13144
  rule: "empty-mailto",
@@ -13114,6 +13148,7 @@ function validateLinksFromDom($) {
13114
13148
  }, hrefLoc ? { loc: hrefLoc } : {}));
13115
13149
  }
13116
13150
  if (category === "tel" && href.trim().toLowerCase() === "tel:") {
13151
+ flag("empty-tel");
13117
13152
  issues.push(__spreadValues({
13118
13153
  severity: "error",
13119
13154
  rule: "empty-tel",
@@ -13123,6 +13158,7 @@ function validateLinksFromDom($) {
13123
13158
  }, hrefLoc ? { loc: hrefLoc } : {}));
13124
13159
  }
13125
13160
  if (href.length > 2e3) {
13161
+ flag("long-url");
13126
13162
  issues.push(__spreadValues({
13127
13163
  severity: "info",
13128
13164
  rule: "long-url",
@@ -13132,7 +13168,8 @@ function validateLinksFromDom($) {
13132
13168
  }, hrefLoc ? { loc: hrefLoc } : {}));
13133
13169
  }
13134
13170
  });
13135
- links.each((_, el) => {
13171
+ links.each((i, el) => {
13172
+ var _a;
13136
13173
  const href = $(el).attr("href") || "";
13137
13174
  const trimmed = href.trim();
13138
13175
  if (trimmed.startsWith("#") && trimmed.length > 1) {
@@ -13140,6 +13177,7 @@ function validateLinksFromDom($) {
13140
13177
  const target = $(`[id="${targetId}"]`);
13141
13178
  const anchorLoc = locOfAttr(el, "href");
13142
13179
  if (target.length === 0) {
13180
+ (_a = inventory[i]) == null ? void 0 : _a.issues.push("broken-anchor");
13143
13181
  issues.push(__spreadValues({
13144
13182
  severity: "error",
13145
13183
  rule: "broken-anchor",
@@ -13160,7 +13198,7 @@ function validateLinksFromDom($) {
13160
13198
  });
13161
13199
  }
13162
13200
  }
13163
- return { totalLinks, issues, breakdown };
13201
+ return { totalLinks, issues, breakdown, links: inventory };
13164
13202
  }
13165
13203
  function validateLinks(html, options) {
13166
13204
  return fromHtml(html, EMPTY_LINKS, validateLinksFromDom, options);
@@ -14699,7 +14737,20 @@ function checkVml(html, options) {
14699
14737
  rule: "vml-nested-shape",
14700
14738
  severity: "error",
14701
14739
  message: `<v:${name}> is nested inside <v:${parent.name}>. Outlook does not support nesting one VML shape inside another.`,
14702
- detail: `Verified in Outlook Classic, and the damage is not local. Three things happen: the containing <v:${parent.name}> does not render at all (its fill and everything inside it disappear, leaving the inner shape stranded); the table structure around it terminates early, so content *after* the shape falls out of the layout and renders outside the email frame; and every VML shape further down the email stops drawing its text, so buttons and headings after this point ship as blank coloured blocks. Confirmed with byte-identical probes either side of one nested shape: the one before it renders its labels, the one after it does not. Every other client renders the HTML fallback correctly, which is why none of this is visible outside the Word engine. Fix: lift the inner shape out of <v:${parent.name}>, or drop the container shape and keep the inner one (a framed background can degrade to a solid fill colour on the <td> instead).`
14740
+ // Two effects, not the three this used to claim. The withdrawn one
14741
+ // said the table structure terminates early so content after the
14742
+ // shape falls outside the email frame. It had no methodology behind
14743
+ // it, and `T1c-blast-radius.html`, the fixture built to measure
14744
+ // exactly this, contradicts it: the block after the nested shape
14745
+ // rendered in place inside the same table with its background and
14746
+ // borders intact, and only the VML labels were missing. The likely
14747
+ // origin is over-reading "layout breaks after it" from a run where a
14748
+ // 600px hero band vanished and everything below shifted up.
14749
+ //
14750
+ // Kept as a warning to whoever edits this next: the probe sentence
14751
+ // below covers the downstream text suppression only. Do not let it
14752
+ // lend its authority to a neighbouring clause again.
14753
+ detail: `Verified in Outlook Classic, and the damage is not local. Two things happen: the containing <v:${parent.name}> does not render at all (its fill and everything inside it disappear, leaving the inner shape stranded); and every VML shape further down the email stops drawing its text, so buttons and headings after this point ship as blank coloured blocks. That second effect was confirmed with byte-identical probes either side of one nested shape: the one before it renders its labels, the one after it does not. Every other client renders the HTML fallback correctly, which is why none of this is visible outside the Word engine. Fix: lift the inner shape out of <v:${parent.name}>, or drop the container shape and keep the inner one (a framed background can degrade to a solid fill colour on the <td> instead).`
14703
14754
  }, loc));
14704
14755
  }
14705
14756
  if (!tag.selfClosing) openShapes.push(tag);