@emailens/engine 0.5.1 → 0.6.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.d.cts CHANGED
@@ -223,9 +223,114 @@ interface AuditReport {
223
223
  * Returns a unified report with compatibility warnings + scores,
224
224
  * spam analysis, link validation, accessibility audit, and image analysis.
225
225
  * Use the `skip` option to omit checks you don't need.
226
+ *
227
+ * Internally parses the HTML once and shares the parsed DOM across
228
+ * all analyzers to avoid redundant parsing overhead.
226
229
  */
227
230
  declare function auditEmail(html: string, options?: AuditOptions): AuditReport;
228
231
 
232
+ interface CreateSessionOptions {
233
+ /** Framework for fix snippets (applies to analyze/audit/transform). */
234
+ framework?: Framework;
235
+ }
236
+ /**
237
+ * A pre-parsed email session.
238
+ *
239
+ * Analysis methods (`analyze`, `audit`, `analyzeSpam`, `validateLinks`,
240
+ * `checkAccessibility`, `analyzeImages`) share a single Cheerio DOM
241
+ * parse, eliminating redundant parsing overhead.
242
+ *
243
+ * Transformation methods (`transformForClient`, `transformForAllClients`,
244
+ * `simulateDarkMode`) parse internally since they mutate the DOM per
245
+ * client. They still benefit from having the session hold the HTML and
246
+ * framework so you don't need to pass them repeatedly.
247
+ */
248
+ interface EmailSession {
249
+ /** The original HTML string. */
250
+ readonly html: string;
251
+ /** The framework set at session creation. */
252
+ readonly framework: Framework | undefined;
253
+ /**
254
+ * Run all analysis checks in one call (shares pre-parsed DOM).
255
+ *
256
+ * Equivalent to `auditEmail()` but avoids re-parsing the HTML.
257
+ */
258
+ audit(options?: Omit<AuditOptions, "framework">): AuditReport;
259
+ /**
260
+ * Analyze CSS compatibility warnings (shares pre-parsed DOM).
261
+ *
262
+ * Equivalent to `analyzeEmail()` but avoids re-parsing the HTML.
263
+ */
264
+ analyze(): CSSWarning[];
265
+ /** Generate per-client compatibility scores from warnings. */
266
+ score(warnings: CSSWarning[]): Record<string, {
267
+ score: number;
268
+ errors: number;
269
+ warnings: number;
270
+ info: number;
271
+ }>;
272
+ /** Analyze spam indicators (shares pre-parsed DOM). */
273
+ analyzeSpam(options?: SpamAnalysisOptions): SpamReport;
274
+ /** Validate links (shares pre-parsed DOM). */
275
+ validateLinks(): LinkReport;
276
+ /** Check accessibility (shares pre-parsed DOM). */
277
+ checkAccessibility(): AccessibilityReport;
278
+ /** Analyze images (shares pre-parsed DOM). */
279
+ analyzeImages(): ImageReport;
280
+ /**
281
+ * Transform HTML for a specific client.
282
+ *
283
+ * Creates an isolated DOM copy per call (transforms mutate the DOM).
284
+ */
285
+ transformForClient(clientId: string): TransformResult;
286
+ /**
287
+ * Transform HTML for all 12 email clients.
288
+ *
289
+ * Creates an isolated DOM copy per client (transforms mutate the DOM).
290
+ */
291
+ transformForAllClients(): TransformResult[];
292
+ /**
293
+ * Simulate dark mode for a specific client.
294
+ *
295
+ * Creates an isolated DOM copy per call (simulation mutates the DOM).
296
+ * Operates on the **original** HTML — if you need dark mode on
297
+ * already-transformed HTML, use the standalone `simulateDarkMode()` instead.
298
+ */
299
+ simulateDarkMode(clientId: string): {
300
+ html: string;
301
+ warnings: CSSWarning[];
302
+ };
303
+ }
304
+ /**
305
+ * Create a session that pre-parses the HTML once and shares the parsed
306
+ * DOM across all read-only analysis operations.
307
+ *
308
+ * Use this when you need to call multiple analysis functions on the
309
+ * same HTML — it eliminates redundant `cheerio.load()` calls.
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * import { createSession } from "@emailens/engine";
314
+ *
315
+ * const session = createSession(html, { framework: "jsx" });
316
+ *
317
+ * // These all share a single DOM parse:
318
+ * const warnings = session.analyze();
319
+ * const scores = session.score(warnings);
320
+ * const spam = session.analyzeSpam();
321
+ * const links = session.validateLinks();
322
+ * const a11y = session.checkAccessibility();
323
+ * const images = session.analyzeImages();
324
+ *
325
+ * // Or run everything at once:
326
+ * const report = session.audit();
327
+ *
328
+ * // Transforms still work (parse internally per client):
329
+ * const transforms = session.transformForAllClients();
330
+ * ```
331
+ */
332
+ declare function createSession(html: string, options?: CreateSessionOptions): EmailSession;
333
+
229
334
  /**
230
335
  * Shared constants used across the engine.
231
336
  */
@@ -270,4 +375,4 @@ declare function wcagGrade(ratio: number): WcagGrade;
270
375
  */
271
376
  declare function alphaBlend(fg: RGBA, bgR: number, bgG: number, bgB: number): [number, number, number];
272
377
 
273
- export { AI_FIX_SYSTEM_PROMPT, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, CSSWarning, CodeFix, DiffResult, EMAIL_CLIENTS, EmailClient, ExportPromptOptions, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, ImageReport, LinkReport, MAX_HTML_SIZE, type RGBA, STRUCTURAL_FIX_PROPERTIES, SpamAnalysisOptions, SpamReport, TransformResult, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, checkAccessibility, contrastRatio, diffResults, errorWarnings, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, simulateDarkMode, structuralWarnings, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };
378
+ export { AI_FIX_SYSTEM_PROMPT, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, CSSWarning, CodeFix, type CreateSessionOptions, DiffResult, EMAIL_CLIENTS, EmailClient, type EmailSession, ExportPromptOptions, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, ImageReport, LinkReport, MAX_HTML_SIZE, type RGBA, STRUCTURAL_FIX_PROPERTIES, SpamAnalysisOptions, SpamReport, TransformResult, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, checkAccessibility, contrastRatio, createSession, diffResults, errorWarnings, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, simulateDarkMode, structuralWarnings, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };
package/dist/index.d.ts CHANGED
@@ -223,9 +223,114 @@ interface AuditReport {
223
223
  * Returns a unified report with compatibility warnings + scores,
224
224
  * spam analysis, link validation, accessibility audit, and image analysis.
225
225
  * Use the `skip` option to omit checks you don't need.
226
+ *
227
+ * Internally parses the HTML once and shares the parsed DOM across
228
+ * all analyzers to avoid redundant parsing overhead.
226
229
  */
227
230
  declare function auditEmail(html: string, options?: AuditOptions): AuditReport;
228
231
 
232
+ interface CreateSessionOptions {
233
+ /** Framework for fix snippets (applies to analyze/audit/transform). */
234
+ framework?: Framework;
235
+ }
236
+ /**
237
+ * A pre-parsed email session.
238
+ *
239
+ * Analysis methods (`analyze`, `audit`, `analyzeSpam`, `validateLinks`,
240
+ * `checkAccessibility`, `analyzeImages`) share a single Cheerio DOM
241
+ * parse, eliminating redundant parsing overhead.
242
+ *
243
+ * Transformation methods (`transformForClient`, `transformForAllClients`,
244
+ * `simulateDarkMode`) parse internally since they mutate the DOM per
245
+ * client. They still benefit from having the session hold the HTML and
246
+ * framework so you don't need to pass them repeatedly.
247
+ */
248
+ interface EmailSession {
249
+ /** The original HTML string. */
250
+ readonly html: string;
251
+ /** The framework set at session creation. */
252
+ readonly framework: Framework | undefined;
253
+ /**
254
+ * Run all analysis checks in one call (shares pre-parsed DOM).
255
+ *
256
+ * Equivalent to `auditEmail()` but avoids re-parsing the HTML.
257
+ */
258
+ audit(options?: Omit<AuditOptions, "framework">): AuditReport;
259
+ /**
260
+ * Analyze CSS compatibility warnings (shares pre-parsed DOM).
261
+ *
262
+ * Equivalent to `analyzeEmail()` but avoids re-parsing the HTML.
263
+ */
264
+ analyze(): CSSWarning[];
265
+ /** Generate per-client compatibility scores from warnings. */
266
+ score(warnings: CSSWarning[]): Record<string, {
267
+ score: number;
268
+ errors: number;
269
+ warnings: number;
270
+ info: number;
271
+ }>;
272
+ /** Analyze spam indicators (shares pre-parsed DOM). */
273
+ analyzeSpam(options?: SpamAnalysisOptions): SpamReport;
274
+ /** Validate links (shares pre-parsed DOM). */
275
+ validateLinks(): LinkReport;
276
+ /** Check accessibility (shares pre-parsed DOM). */
277
+ checkAccessibility(): AccessibilityReport;
278
+ /** Analyze images (shares pre-parsed DOM). */
279
+ analyzeImages(): ImageReport;
280
+ /**
281
+ * Transform HTML for a specific client.
282
+ *
283
+ * Creates an isolated DOM copy per call (transforms mutate the DOM).
284
+ */
285
+ transformForClient(clientId: string): TransformResult;
286
+ /**
287
+ * Transform HTML for all 12 email clients.
288
+ *
289
+ * Creates an isolated DOM copy per client (transforms mutate the DOM).
290
+ */
291
+ transformForAllClients(): TransformResult[];
292
+ /**
293
+ * Simulate dark mode for a specific client.
294
+ *
295
+ * Creates an isolated DOM copy per call (simulation mutates the DOM).
296
+ * Operates on the **original** HTML — if you need dark mode on
297
+ * already-transformed HTML, use the standalone `simulateDarkMode()` instead.
298
+ */
299
+ simulateDarkMode(clientId: string): {
300
+ html: string;
301
+ warnings: CSSWarning[];
302
+ };
303
+ }
304
+ /**
305
+ * Create a session that pre-parses the HTML once and shares the parsed
306
+ * DOM across all read-only analysis operations.
307
+ *
308
+ * Use this when you need to call multiple analysis functions on the
309
+ * same HTML — it eliminates redundant `cheerio.load()` calls.
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * import { createSession } from "@emailens/engine";
314
+ *
315
+ * const session = createSession(html, { framework: "jsx" });
316
+ *
317
+ * // These all share a single DOM parse:
318
+ * const warnings = session.analyze();
319
+ * const scores = session.score(warnings);
320
+ * const spam = session.analyzeSpam();
321
+ * const links = session.validateLinks();
322
+ * const a11y = session.checkAccessibility();
323
+ * const images = session.analyzeImages();
324
+ *
325
+ * // Or run everything at once:
326
+ * const report = session.audit();
327
+ *
328
+ * // Transforms still work (parse internally per client):
329
+ * const transforms = session.transformForAllClients();
330
+ * ```
331
+ */
332
+ declare function createSession(html: string, options?: CreateSessionOptions): EmailSession;
333
+
229
334
  /**
230
335
  * Shared constants used across the engine.
231
336
  */
@@ -270,4 +375,4 @@ declare function wcagGrade(ratio: number): WcagGrade;
270
375
  */
271
376
  declare function alphaBlend(fg: RGBA, bgR: number, bgG: number, bgB: number): [number, number, number];
272
377
 
273
- export { AI_FIX_SYSTEM_PROMPT, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, CSSWarning, CodeFix, DiffResult, EMAIL_CLIENTS, EmailClient, ExportPromptOptions, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, ImageReport, LinkReport, MAX_HTML_SIZE, type RGBA, STRUCTURAL_FIX_PROPERTIES, SpamAnalysisOptions, SpamReport, TransformResult, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, checkAccessibility, contrastRatio, diffResults, errorWarnings, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, simulateDarkMode, structuralWarnings, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };
378
+ export { AI_FIX_SYSTEM_PROMPT, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, CSSWarning, CodeFix, type CreateSessionOptions, DiffResult, EMAIL_CLIENTS, EmailClient, type EmailSession, ExportPromptOptions, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, ImageReport, LinkReport, MAX_HTML_SIZE, type RGBA, STRUCTURAL_FIX_PROPERTIES, SpamAnalysisOptions, SpamReport, TransformResult, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, checkAccessibility, contrastRatio, createSession, diffResults, errorWarnings, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, simulateDarkMode, structuralWarnings, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  __objRest,
4
4
  __spreadProps,
5
5
  __spreadValues
6
- } from "./chunk-PFONR3YC.js";
6
+ } from "./chunk-ZQF2XUIJ.js";
7
7
 
8
8
  // src/clients.ts
9
9
  var EMAIL_CLIENTS = [
@@ -2747,6 +2747,14 @@ var GENERIC_LINK_TEXT = /* @__PURE__ */ new Set([
2747
2747
  "tap here",
2748
2748
  "this"
2749
2749
  ]);
2750
+ var EMPTY_SPAM = { score: 100, level: "low", issues: [] };
2751
+ var EMPTY_LINKS = {
2752
+ totalLinks: 0,
2753
+ issues: [],
2754
+ breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
2755
+ };
2756
+ var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
2757
+ var EMPTY_IMAGES = { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
2750
2758
 
2751
2759
  // src/transform.ts
2752
2760
  function inlineStyles($) {
@@ -3255,15 +3263,8 @@ function transformForAllClients(html, framework) {
3255
3263
  // src/analyze.ts
3256
3264
  import * as cheerio2 from "cheerio";
3257
3265
  import * as csstree2 from "css-tree";
3258
- function analyzeEmail(html, framework) {
3266
+ function analyzeEmailFromDom($, framework) {
3259
3267
  var _a, _b, _c, _d, _e, _f, _g;
3260
- if (!html || !html.trim()) {
3261
- return [];
3262
- }
3263
- if (html.length > MAX_HTML_SIZE) {
3264
- throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
3265
- }
3266
- const $ = cheerio2.load(html);
3267
3268
  const warnings = [];
3268
3269
  const seenWarnings = /* @__PURE__ */ new Set();
3269
3270
  function addWarning(w) {
@@ -3508,6 +3509,16 @@ function analyzeEmail(html, framework) {
3508
3509
  warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
3509
3510
  return warnings;
3510
3511
  }
3512
+ function analyzeEmail(html, framework) {
3513
+ if (!html || !html.trim()) {
3514
+ return [];
3515
+ }
3516
+ if (html.length > MAX_HTML_SIZE) {
3517
+ throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
3518
+ }
3519
+ const $ = cheerio2.load(html);
3520
+ return analyzeEmailFromDom($, framework);
3521
+ }
3511
3522
  function getFixType(prop) {
3512
3523
  return STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css";
3513
3524
  }
@@ -4634,14 +4645,7 @@ function checkAllCapsTitle($) {
4634
4645
  }
4635
4646
  return null;
4636
4647
  }
4637
- function analyzeSpam(html, options) {
4638
- if (!html || !html.trim()) {
4639
- return { score: 100, level: "low", issues: [] };
4640
- }
4641
- if (html.length > MAX_HTML_SIZE) {
4642
- throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
4643
- }
4644
- const $ = cheerio4.load(html);
4648
+ function analyzeSpamFromDom($, options) {
4645
4649
  const text = extractVisibleText($);
4646
4650
  const issues = [];
4647
4651
  const capsIssue = checkCapsRatio(text);
@@ -4679,6 +4683,16 @@ function analyzeSpam(html, options) {
4679
4683
  const level = score >= 70 ? "low" : score >= 40 ? "medium" : "high";
4680
4684
  return { score, level, issues };
4681
4685
  }
4686
+ function analyzeSpam(html, options) {
4687
+ if (!html || !html.trim()) {
4688
+ return { score: 100, level: "low", issues: [] };
4689
+ }
4690
+ if (html.length > MAX_HTML_SIZE) {
4691
+ throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
4692
+ }
4693
+ const $ = cheerio4.load(html);
4694
+ return analyzeSpamFromDom($, options);
4695
+ }
4682
4696
 
4683
4697
  // src/link-validator.ts
4684
4698
  import * as cheerio5 from "cheerio";
@@ -4698,18 +4712,7 @@ function isPlaceholderHref(href) {
4698
4712
  const h = href.trim().toLowerCase();
4699
4713
  return h === "#" || h === "" || h === "javascript:void(0)" || h === "javascript:;";
4700
4714
  }
4701
- function validateLinks(html) {
4702
- if (!html || !html.trim()) {
4703
- return {
4704
- totalLinks: 0,
4705
- issues: [],
4706
- breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
4707
- };
4708
- }
4709
- if (html.length > MAX_HTML_SIZE) {
4710
- throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
4711
- }
4712
- const $ = cheerio5.load(html);
4715
+ function validateLinksFromDom($) {
4713
4716
  const issues = [];
4714
4717
  const breakdown = { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 };
4715
4718
  const links = $("a");
@@ -4860,6 +4863,20 @@ function validateLinks(html) {
4860
4863
  }
4861
4864
  return { totalLinks, issues, breakdown };
4862
4865
  }
4866
+ function validateLinks(html) {
4867
+ if (!html || !html.trim()) {
4868
+ return {
4869
+ totalLinks: 0,
4870
+ issues: [],
4871
+ breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
4872
+ };
4873
+ }
4874
+ if (html.length > MAX_HTML_SIZE) {
4875
+ throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
4876
+ }
4877
+ const $ = cheerio5.load(html);
4878
+ return validateLinksFromDom($);
4879
+ }
4863
4880
 
4864
4881
  // src/accessibility-checker.ts
4865
4882
  import * as cheerio6 from "cheerio";
@@ -5108,14 +5125,7 @@ function checkSemanticStructure($) {
5108
5125
  }
5109
5126
  return issues;
5110
5127
  }
5111
- function checkAccessibility(html) {
5112
- if (!html || !html.trim()) {
5113
- return { score: 100, issues: [] };
5114
- }
5115
- if (html.length > MAX_HTML_SIZE) {
5116
- throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
5117
- }
5118
- const $ = cheerio6.load(html);
5128
+ function checkAccessibilityFromDom($) {
5119
5129
  const issues = [];
5120
5130
  const langIssue = checkLangAttribute($);
5121
5131
  if (langIssue) issues.push(langIssue);
@@ -5148,6 +5158,16 @@ function checkAccessibility(html) {
5148
5158
  const score = Math.max(0, 100 - penalty);
5149
5159
  return { score, issues };
5150
5160
  }
5161
+ function checkAccessibility(html) {
5162
+ if (!html || !html.trim()) {
5163
+ return { score: 100, issues: [] };
5164
+ }
5165
+ if (html.length > MAX_HTML_SIZE) {
5166
+ throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
5167
+ }
5168
+ const $ = cheerio6.load(html);
5169
+ return checkAccessibilityFromDom($);
5170
+ }
5151
5171
 
5152
5172
  // src/image-analyzer.ts
5153
5173
  import * as cheerio7 from "cheerio";
@@ -5181,14 +5201,7 @@ function truncateSrc(src, max = 60) {
5181
5201
  }
5182
5202
  return src.length > max ? src.slice(0, max - 3) + "..." : src;
5183
5203
  }
5184
- function analyzeImages(html) {
5185
- if (!html || !html.trim()) {
5186
- return { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
5187
- }
5188
- if (html.length > MAX_HTML_SIZE) {
5189
- throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
5190
- }
5191
- const $ = cheerio7.load(html);
5204
+ function analyzeImagesFromDom($) {
5192
5205
  const issues = [];
5193
5206
  const images = [];
5194
5207
  let totalDataUriBytes = 0;
@@ -5314,16 +5327,19 @@ function analyzeImages(html) {
5314
5327
  }
5315
5328
  return { total: images.length, totalDataUriBytes, issues, images };
5316
5329
  }
5330
+ function analyzeImages(html) {
5331
+ if (!html || !html.trim()) {
5332
+ return { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
5333
+ }
5334
+ if (html.length > MAX_HTML_SIZE) {
5335
+ throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
5336
+ }
5337
+ const $ = cheerio7.load(html);
5338
+ return analyzeImagesFromDom($);
5339
+ }
5317
5340
 
5318
5341
  // src/audit.ts
5319
- var EMPTY_SPAM = { score: 100, level: "low", issues: [] };
5320
- var EMPTY_LINKS = {
5321
- totalLinks: 0,
5322
- issues: [],
5323
- breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
5324
- };
5325
- var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
5326
- var EMPTY_IMAGES = { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
5342
+ import * as cheerio8 from "cheerio";
5327
5343
  function auditEmail(html, options) {
5328
5344
  var _a;
5329
5345
  if (!html || !html.trim()) {
@@ -5340,14 +5356,91 @@ function auditEmail(html, options) {
5340
5356
  }
5341
5357
  const framework = options == null ? void 0 : options.framework;
5342
5358
  const skip = new Set((_a = options == null ? void 0 : options.skip) != null ? _a : []);
5343
- const warnings = skip.has("compatibility") ? [] : analyzeEmail(html, framework);
5359
+ const $ = cheerio8.load(html);
5360
+ const warnings = skip.has("compatibility") ? [] : analyzeEmailFromDom($, framework);
5344
5361
  const scores = skip.has("compatibility") ? {} : generateCompatibilityScore(warnings);
5345
- const spam = skip.has("spam") ? EMPTY_SPAM : analyzeSpam(html, options == null ? void 0 : options.spam);
5346
- const links = skip.has("links") ? EMPTY_LINKS : validateLinks(html);
5347
- const accessibility = skip.has("accessibility") ? EMPTY_ACCESSIBILITY : checkAccessibility(html);
5348
- const images = skip.has("images") ? EMPTY_IMAGES : analyzeImages(html);
5362
+ const spam = skip.has("spam") ? EMPTY_SPAM : analyzeSpamFromDom($, options == null ? void 0 : options.spam);
5363
+ const links = skip.has("links") ? EMPTY_LINKS : validateLinksFromDom($);
5364
+ const accessibility = skip.has("accessibility") ? EMPTY_ACCESSIBILITY : checkAccessibilityFromDom($);
5365
+ const images = skip.has("images") ? EMPTY_IMAGES : analyzeImagesFromDom($);
5349
5366
  return { compatibility: { warnings, scores }, spam, links, accessibility, images };
5350
5367
  }
5368
+
5369
+ // src/session.ts
5370
+ import * as cheerio9 from "cheerio";
5371
+ function createSession(html, options) {
5372
+ if (!html || !html.trim()) {
5373
+ const fw = options == null ? void 0 : options.framework;
5374
+ return {
5375
+ html: html || "",
5376
+ framework: fw,
5377
+ audit: () => ({
5378
+ compatibility: { warnings: [], scores: {} },
5379
+ spam: EMPTY_SPAM,
5380
+ links: EMPTY_LINKS,
5381
+ accessibility: EMPTY_ACCESSIBILITY,
5382
+ images: EMPTY_IMAGES
5383
+ }),
5384
+ analyze: () => [],
5385
+ score: () => ({}),
5386
+ analyzeSpam: () => EMPTY_SPAM,
5387
+ validateLinks: () => EMPTY_LINKS,
5388
+ checkAccessibility: () => EMPTY_ACCESSIBILITY,
5389
+ analyzeImages: () => EMPTY_IMAGES,
5390
+ transformForClient: (clientId) => ({ clientId, html: html || "", warnings: [] }),
5391
+ transformForAllClients: () => [],
5392
+ simulateDarkMode: (clientId) => ({ html: html || "", warnings: [] })
5393
+ };
5394
+ }
5395
+ if (html.length > MAX_HTML_SIZE) {
5396
+ throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
5397
+ }
5398
+ const $ = cheerio9.load(html);
5399
+ const framework = options == null ? void 0 : options.framework;
5400
+ return {
5401
+ html,
5402
+ framework,
5403
+ audit(opts) {
5404
+ var _a;
5405
+ const skip = new Set((_a = opts == null ? void 0 : opts.skip) != null ? _a : []);
5406
+ const warnings = skip.has("compatibility") ? [] : analyzeEmailFromDom($, framework);
5407
+ const scores = skip.has("compatibility") ? {} : generateCompatibilityScore(warnings);
5408
+ const spam = skip.has("spam") ? EMPTY_SPAM : analyzeSpamFromDom($, opts == null ? void 0 : opts.spam);
5409
+ const links = skip.has("links") ? EMPTY_LINKS : validateLinksFromDom($);
5410
+ const accessibility = skip.has("accessibility") ? EMPTY_ACCESSIBILITY : checkAccessibilityFromDom($);
5411
+ const images = skip.has("images") ? EMPTY_IMAGES : analyzeImagesFromDom($);
5412
+ return { compatibility: { warnings, scores }, spam, links, accessibility, images };
5413
+ },
5414
+ analyze() {
5415
+ return analyzeEmailFromDom($, framework);
5416
+ },
5417
+ score(warnings) {
5418
+ return generateCompatibilityScore(warnings);
5419
+ },
5420
+ analyzeSpam(opts) {
5421
+ return analyzeSpamFromDom($, opts);
5422
+ },
5423
+ validateLinks() {
5424
+ return validateLinksFromDom($);
5425
+ },
5426
+ checkAccessibility() {
5427
+ return checkAccessibilityFromDom($);
5428
+ },
5429
+ analyzeImages() {
5430
+ return analyzeImagesFromDom($);
5431
+ },
5432
+ // Transforms create isolated copies since they mutate the DOM
5433
+ transformForClient(clientId) {
5434
+ return transformForClient(html, clientId, framework);
5435
+ },
5436
+ transformForAllClients() {
5437
+ return transformForAllClients(html, framework);
5438
+ },
5439
+ simulateDarkMode(clientId) {
5440
+ return simulateDarkMode(html, clientId);
5441
+ }
5442
+ };
5443
+ }
5351
5444
  export {
5352
5445
  AI_FIX_SYSTEM_PROMPT,
5353
5446
  CompileError,
@@ -5362,6 +5455,7 @@ export {
5362
5455
  auditEmail,
5363
5456
  checkAccessibility,
5364
5457
  contrastRatio,
5458
+ createSession,
5365
5459
  diffResults,
5366
5460
  errorWarnings,
5367
5461
  estimateAiFixTokens,