@emailens/cli 0.3.5 → 0.4.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/README.md CHANGED
@@ -1,9 +1,23 @@
1
- # @emailens/cli
1
+ <div align="center">
2
+
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="./docs/wordmark-dark.svg">
5
+ <img src="./docs/wordmark-light.svg" alt="emailens / cli" width="444">
6
+ </picture>
7
+
8
+ **The rendering linter for email, in your terminal**
2
9
 
3
10
  [![npm](https://img.shields.io/npm/v/@emailens/cli)](https://www.npmjs.com/package/@emailens/cli)
4
11
  [![MCP](https://img.shields.io/badge/MCP-Server-blue)](https://github.com/emailens/mcp)
12
+ [![GitHub stars](https://img.shields.io/github/stars/emailens/cli?style=flat)](https://github.com/emailens/cli/stargazers)
13
+
14
+ </div>
15
+
16
+ CLI tool for email compatibility analysis — preview how HTML emails render across 21 email clients (Gmail, Outlook, Apple Mail, Yahoo, Samsung, Thunderbird, HEY, Proton Mail, AOL, Fastmail, Superhuman).
5
17
 
6
- CLI tool for email compatibility analysis — preview how HTML emails render across 15 email clients (Gmail, Outlook, Apple Mail, Yahoo, Samsung, Thunderbird, HEY, Superhuman).
18
+ Across the 255 CSS and HTML features we track, only 6 are fully supported in every major client ([see the data](https://emailens.dev/email-css/report)). This tool catches the other 249 before your users do.
19
+
20
+ ![emailens lint output showing errors and warnings across email clients](./docs/lint-demo.png)
7
21
 
8
22
  > **Prefer AI?** Use the [MCP server](https://github.com/emailens/mcp) — same engine, works with Claude, Cursor, and any MCP client.
9
23
 
@@ -32,6 +46,17 @@ emailens analyze email.html --json
32
46
  cat email.html | emailens analyze -
33
47
  ```
34
48
 
49
+ Warnings show where the property is used, and how many other places share the
50
+ problem:
51
+
52
+ ```
53
+ ⚠ Outlook (New) (1 issue)
54
+ ⚠ border-radius (4:8 +2 more) — Outlook (New) does not support "border-radius".
55
+ ```
56
+
57
+ `--json` carries the full `loc` / `locs` for each warning. As with `lint`,
58
+ positions are reported for HTML input only — see below.
59
+
35
60
  ### `emailens preview <file>`
36
61
 
37
62
  Full preview pipeline: transforms, analysis, dark mode simulation, and optional screenshots.
@@ -81,7 +106,7 @@ cat email.html | emailens fix - --format jsx # Pipe from stdin
81
106
 
82
107
  ### `emailens lint <file|glob>`
83
108
 
84
- CI/CD-friendly linting with structured exit codes. Flattens all audit checks (compatibility, content hygiene, links, accessibility, images, inbox preview, size, template variables) into a unified issue list.
109
+ CI/CD-friendly linting with structured exit codes. Flattens all audit checks (compatibility, content hygiene, links, accessibility, images, inbox preview, size, template variables, content overflow, visual bugs) into a unified issue list.
85
110
 
86
111
  ```bash
87
112
  emailens lint email.html
@@ -97,7 +122,7 @@ emailens lint email.html --max-warnings 5
97
122
  | `--format` | `-f` | Input format: `html`, `jsx`, `mjml`, `maizzle` |
98
123
  | `--json` | | Output as JSON |
99
124
  | `--fail-on-warning` | | Exit 2 if warnings found |
100
- | `--skip` | | Comma-separated checks to skip: `spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables` |
125
+ | `--skip` | | Comma-separated checks to skip: `spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables,overflow,visual` |
101
126
  | `--max-warnings` | | Fail if more than n warnings |
102
127
 
103
128
  **Exit codes:**
@@ -109,8 +134,8 @@ emailens lint email.html --max-warnings 5
109
134
 
110
135
  ```
111
136
  src/emails/welcome.html
112
- error outlook-windows border-radius Not supported in Outlook Windows
113
- warn spam caps-ratio 20%+ of words are ALL CAPS
137
+ error 12:8 outlook-windows border-radius Not supported in Outlook Windows
138
+ warn spam caps-ratio 20%+ of words are ALL CAPS
114
139
 
115
140
  src/emails/newsletter.html
116
141
  pass No issues found
@@ -118,6 +143,22 @@ src/emails/newsletter.html
118
143
  2 files | 1 error | 1 warning
119
144
  ```
120
145
 
146
+ Issues that belong to a specific place in the file carry a `line:col`; findings
147
+ about the document as a whole (spam signals, Gmail clipping, inbox preview) have
148
+ no position and leave the column blank. With `--json`, each issue carries a
149
+ `loc` object instead — `line`, `column`, `endLine`, `endColumn`, `offset`,
150
+ `length` — for editors, annotations, and agents that need to point at or edit
151
+ the exact source.
152
+
153
+ One property can break in many places, so CSS issues also carry `locs`: every
154
+ occurrence in document order, with `loc` as the first, and `locsTruncated: true`
155
+ when there were more than 100.
156
+
157
+ Positions are reported for **HTML sources only**. JSX, MJML and Maizzle are
158
+ compiled before analysis, so a line number would refer to generated output
159
+ rather than the file you wrote; the CLI omits it rather than print one that
160
+ looks authoritative and isn't.
161
+
121
162
  #### CI / GitHub Actions
122
163
 
123
164
  Drop this into `.github/workflows/email-lint.yml` to fail PRs that introduce broken email CSS, spam triggers, or accessibility regressions:
@@ -147,7 +188,7 @@ For React Email / MJML / Maizzle source files, the CLI auto-detects the format f
147
188
 
148
189
  ### `emailens clients`
149
190
 
150
- List all 15 supported email clients.
191
+ List all 21 supported email clients.
151
192
 
152
193
  ```bash
153
194
  emailens clients
@@ -231,3 +272,7 @@ echo '<html><body>Hello</body></html>' | emailens preview - --json
231
272
  ## License
232
273
 
233
274
  MIT
275
+
276
+ ---
277
+
278
+ If this saved you from an Outlook surprise, [a star](https://github.com/emailens/cli) helps other email developers find it.
package/dist/index.js CHANGED
@@ -12,8 +12,8 @@ import { EMAIL_CLIENTS } from "@emailens/engine";
12
12
  // package.json
13
13
  var package_default = {
14
14
  name: "@emailens/cli",
15
- version: "0.3.5",
16
- description: "CLI tool for email compatibility analysis \u2014 analyze, preview, and export email rendering across 15 email clients.",
15
+ version: "0.4.1",
16
+ description: "CLI tool for email compatibility analysis \u2014 analyze, preview, and export email rendering across 21 email clients.",
17
17
  type: "module",
18
18
  bin: {
19
19
  emailens: "./dist/index.js"
@@ -39,7 +39,7 @@ var package_default = {
39
39
  prepublishOnly: "bun run build"
40
40
  },
41
41
  dependencies: {
42
- "@emailens/engine": "^0.9.3",
42
+ "@emailens/engine": "^0.10.2",
43
43
  citty: "^0.1.6",
44
44
  "cli-table3": "^0.6.5",
45
45
  ora: "^8.2.0",
@@ -168,6 +168,9 @@ function toFramework(format) {
168
168
  if (format === "jsx" || format === "mjml" || format === "maizzle") return format;
169
169
  return void 0;
170
170
  }
171
+ function positionsApply(format) {
172
+ return format === "html";
173
+ }
171
174
 
172
175
  // src/commands/analyze.ts
173
176
  import { compile } from "@emailens/engine/compile";
@@ -242,6 +245,12 @@ function printScoreTable(scores, warnings, options) {
242
245
  console.log(` Overall: ${colorScore(overall)}/100`);
243
246
  console.log();
244
247
  }
248
+ function formatWhere(w) {
249
+ if (!w.loc) return "";
250
+ const more = (w.locs?.length ?? 1) - 1;
251
+ const extra = more > 0 ? ` +${more}${w.locsTruncated ? "+" : ""} more` : "";
252
+ return pc.dim(` (${w.loc.line}:${w.loc.column}${extra})`);
253
+ }
245
254
  function printWarnings(warnings, options) {
246
255
  if (warnings.length === 0) {
247
256
  if (!options?.quiet) {
@@ -263,7 +272,7 @@ function printWarnings(warnings, options) {
263
272
  console.log(` ${severityIcon(worst)} ${pc.bold(clientName)} (${clientWarnings.length} issue${clientWarnings.length === 1 ? "" : "s"})`);
264
273
  for (const w of clientWarnings) {
265
274
  const prop = pc.dim(w.property);
266
- console.log(` ${severityIcon(w.severity)} ${prop} \u2014 ${w.message}`);
275
+ console.log(` ${severityIcon(w.severity)} ${prop}${formatWhere(w)} \u2014 ${w.message}`);
267
276
  if (w.suggestion) {
268
277
  console.log(` ${pc.dim("\u2192")} ${w.suggestion}`);
269
278
  }
@@ -290,7 +299,10 @@ function formatJsonOutput(opts) {
290
299
  after: w.fix.after,
291
300
  language: w.fix.language,
292
301
  description: w.fix.description
293
- } : void 0
302
+ } : void 0,
303
+ loc: w.loc,
304
+ locs: w.locs,
305
+ locsTruncated: w.locsTruncated
294
306
  }))
295
307
  };
296
308
  if (opts.transforms) {
@@ -353,11 +365,11 @@ var analyze_default = defineCommand({
353
365
  spinner?.start("Reading input...");
354
366
  const source = await readInput(args.input);
355
367
  spinner?.succeed("Input read");
356
- const html = await compile(source, format, args.input !== "-" ? args.input : void 0);
368
+ const html = await compile(source, format);
357
369
  const clientIds = resolveClients(args.clients);
358
370
  spinner?.start("Analyzing compatibility...");
359
371
  const framework = toFramework(format);
360
- const warnings = analyzeEmail(html, framework);
372
+ const warnings = analyzeEmail(html, framework, { positions: positionsApply(format) });
361
373
  const allScores = generateCompatibilityScore(warnings);
362
374
  const clientSet = new Set(clientIds);
363
375
  const scores = {};
@@ -449,7 +461,7 @@ var preview_default = defineCommand2({
449
461
  spinner?.start("Reading input...");
450
462
  const source = await readInput(args.input);
451
463
  spinner?.succeed("Input read");
452
- const html = await compile2(source, format, args.input !== "-" ? args.input : void 0);
464
+ const html = await compile2(source, format);
453
465
  const clientIds = resolveClients(args.clients);
454
466
  const clientSet = new Set(clientIds);
455
467
  spinner?.start("Transforming for all clients...");
@@ -715,7 +727,7 @@ var export_default = defineCommand4({
715
727
  spinner?.start("Reading input...");
716
728
  const source = await readInput(args.input);
717
729
  spinner?.succeed("Input read");
718
- const html = await compile3(source, format, args.input !== "-" ? args.input : void 0);
730
+ const html = await compile3(source, format);
719
731
  const clientIds = resolveClients(args.clients);
720
732
  const clientSet = new Set(clientIds);
721
733
  spinner?.start("Transforming for all clients...");
@@ -869,7 +881,7 @@ var fix_default = defineCommand5({
869
881
  const framework = toFramework(format);
870
882
  spinner?.start("Reading input...");
871
883
  const source = await readInput(args.input);
872
- const html = await compile4(source, format, args.input !== "-" ? args.input : void 0);
884
+ const html = await compile4(source, format);
873
885
  spinner?.succeed("Input read");
874
886
  spinner?.start("Analyzing compatibility...");
875
887
  const warnings = analyzeEmail4(html, framework);
@@ -1012,11 +1024,11 @@ import {
1012
1024
  CompileError as CompileError5
1013
1025
  } from "@emailens/engine";
1014
1026
  import { compile as compile5 } from "@emailens/engine/compile";
1015
- var VALID_SKIPS = /* @__PURE__ */ new Set(["spam", "links", "accessibility", "images", "compatibility"]);
1027
+ var VALID_SKIPS = /* @__PURE__ */ new Set(["spam", "links", "accessibility", "images", "compatibility", "overflow", "visual"]);
1016
1028
  var audit_default = defineCommand6({
1017
1029
  meta: {
1018
1030
  name: "audit",
1019
- description: "Full email audit: compatibility + spam + links + accessibility + images"
1031
+ description: "Full email audit: compatibility + spam + links + accessibility + images + overflow + visual"
1020
1032
  },
1021
1033
  args: {
1022
1034
  input: {
@@ -1040,7 +1052,7 @@ var audit_default = defineCommand6({
1040
1052
  },
1041
1053
  skip: {
1042
1054
  type: "string",
1043
- description: "Comma-separated checks to skip: spam,links,accessibility,images,compatibility"
1055
+ description: "Comma-separated checks to skip: spam,links,accessibility,images,compatibility,overflow,visual"
1044
1056
  }
1045
1057
  },
1046
1058
  async run({ args }) {
@@ -1050,7 +1062,7 @@ var audit_default = defineCommand6({
1050
1062
  spinner?.start("Reading input...");
1051
1063
  const source = await readInput(args.input);
1052
1064
  spinner?.succeed("Input read");
1053
- const html = await compile5(source, format, args.input !== "-" ? args.input : void 0);
1065
+ const html = await compile5(source, format);
1054
1066
  const skip = [];
1055
1067
  if (args.skip) {
1056
1068
  for (const s of args.skip.split(",").map((s2) => s2.trim()).filter(Boolean)) {
@@ -1112,6 +1124,16 @@ function printQualitySummary(report, skip, options) {
1112
1124
  const color = issues.length === 0 ? pc4.green : pc4.yellow;
1113
1125
  table.push(["Images", `${total} image${total === 1 ? "" : "s"} \u2014 ${color(`${issues.length} issue${issues.length === 1 ? "" : "s"}`)}`]);
1114
1126
  }
1127
+ if (!skip.includes("overflow")) {
1128
+ const { issues } = report.overflow;
1129
+ const color = issues.length === 0 ? pc4.green : pc4.yellow;
1130
+ table.push(["Content Overflow", color(`${issues.length} issue${issues.length === 1 ? "" : "s"}`)]);
1131
+ }
1132
+ if (!skip.includes("visual")) {
1133
+ const { issues } = report.visual;
1134
+ const color = issues.length === 0 ? pc4.green : pc4.yellow;
1135
+ table.push(["Visual Bugs", color(`${issues.length} issue${issues.length === 1 ? "" : "s"}`)]);
1136
+ }
1115
1137
  console.log(table.toString());
1116
1138
  console.log();
1117
1139
  }
@@ -1122,7 +1144,8 @@ import pc5 from "picocolors";
1122
1144
  import { compile as compile6 } from "@emailens/engine/compile";
1123
1145
  import {
1124
1146
  auditEmail as auditEmail2,
1125
- CompileError as CompileError6
1147
+ CompileError as CompileError6,
1148
+ MAX_WARNING_LOCATIONS
1126
1149
  } from "@emailens/engine";
1127
1150
  import { readdir } from "fs/promises";
1128
1151
  import { resolve as resolve2, relative } from "path";
@@ -1134,7 +1157,9 @@ var VALID_SKIPS2 = /* @__PURE__ */ new Set([
1134
1157
  "compatibility",
1135
1158
  "inboxPreview",
1136
1159
  "size",
1137
- "templateVariables"
1160
+ "templateVariables",
1161
+ "overflow",
1162
+ "visual"
1138
1163
  ]);
1139
1164
  var lint_default = defineCommand7({
1140
1165
  meta: {
@@ -1162,7 +1187,7 @@ var lint_default = defineCommand7({
1162
1187
  },
1163
1188
  skip: {
1164
1189
  type: "string",
1165
- description: "Comma-separated checks to skip: spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables"
1190
+ description: "Comma-separated checks to skip: spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables,overflow,visual"
1166
1191
  },
1167
1192
  maxWarnings: {
1168
1193
  type: "string",
@@ -1200,11 +1225,13 @@ var lint_default = defineCommand7({
1200
1225
  for (const file of files) {
1201
1226
  const format = resolveFormat(args.format, file);
1202
1227
  const source = await readInput(file);
1203
- const html = await compile6(source, format, file);
1228
+ const html = await compile6(source, format);
1204
1229
  const framework = toFramework(format);
1230
+ const positions = positionsApply(format);
1205
1231
  const report = auditEmail2(html, {
1206
1232
  framework,
1207
- skip
1233
+ skip,
1234
+ positions
1208
1235
  });
1209
1236
  const issues = flattenToLintIssues(report, skip);
1210
1237
  const errors = issues.filter((i) => i.severity === "error").length;
@@ -1221,9 +1248,14 @@ var lint_default = defineCommand7({
1221
1248
  if (result.issues.length === 0) {
1222
1249
  console.log(` ${pc5.green("pass")} No issues found`);
1223
1250
  } else {
1251
+ const width = result.issues.reduce(
1252
+ (w, i) => i.loc ? Math.max(w, `${i.loc.line}:${i.loc.column}`.length) : w,
1253
+ 0
1254
+ );
1224
1255
  for (const issue of result.issues) {
1225
1256
  const sev = issue.severity === "error" ? pc5.red("error") : issue.severity === "warning" ? pc5.yellow("warn ") : pc5.blue("info ");
1226
- console.log(` ${sev} ${pc5.dim(issue.category.padEnd(18))} ${pc5.bold(issue.rule.padEnd(22))} ${issue.message}`);
1257
+ const pos = width > 0 ? `${pc5.dim((issue.loc ? `${issue.loc.line}:${issue.loc.column}` : "").padStart(width))} ` : "";
1258
+ console.log(` ${sev} ${pos}${pc5.dim(issue.category.padEnd(18))} ${pc5.bold(issue.rule.padEnd(22))} ${issue.message}`);
1227
1259
  }
1228
1260
  }
1229
1261
  console.log();
@@ -1277,6 +1309,20 @@ async function resolveGlob(pattern) {
1277
1309
  const entries = await readdir(resolve2(dir), { withFileTypes: true });
1278
1310
  return entries.filter((e) => e.isFile() && regex.test(e.name)).map((e) => resolve2(dir, e.name)).sort();
1279
1311
  }
1312
+ function mergeLocs(into, from) {
1313
+ if (!from) return false;
1314
+ let truncated = false;
1315
+ for (const loc of from) {
1316
+ if (into.some((l) => l.offset === loc.offset)) continue;
1317
+ if (into.length >= MAX_WARNING_LOCATIONS) {
1318
+ truncated = true;
1319
+ break;
1320
+ }
1321
+ into.push(loc);
1322
+ }
1323
+ into.sort((a, b) => a.offset - b.offset);
1324
+ return truncated;
1325
+ }
1280
1326
  function flattenToLintIssues(report, skip) {
1281
1327
  const issues = [];
1282
1328
  if (!skip.includes("compatibility")) {
@@ -1286,11 +1332,17 @@ function flattenToLintIssues(report, skip) {
1286
1332
  const existing = seenProps.get(key);
1287
1333
  if (existing) {
1288
1334
  existing.clients.push(w.client);
1335
+ existing.loc ??= w.loc;
1336
+ if (mergeLocs(existing.locs, w.locs)) existing.locsTruncated = true;
1337
+ if (w.locsTruncated) existing.locsTruncated = true;
1289
1338
  } else {
1290
1339
  seenProps.set(key, {
1291
1340
  severity: w.severity,
1292
1341
  clients: [w.client],
1293
- message: w.message
1342
+ message: w.message,
1343
+ loc: w.loc,
1344
+ locs: (w.locs ?? []).slice(0, MAX_WARNING_LOCATIONS),
1345
+ ...w.locsTruncated ? { locsTruncated: true } : {}
1294
1346
  });
1295
1347
  }
1296
1348
  }
@@ -1300,7 +1352,10 @@ function flattenToLintIssues(report, skip) {
1300
1352
  severity: val.severity,
1301
1353
  category: val.clients.slice(0, 3).join(",") + (val.clients.length > 3 ? `+${val.clients.length - 3}` : ""),
1302
1354
  rule: property,
1303
- message: val.message
1355
+ message: val.message,
1356
+ ...val.loc ? { loc: val.loc } : {},
1357
+ ...val.locs.length ? { locs: val.locs } : {},
1358
+ ...val.locsTruncated ? { locsTruncated: true } : {}
1304
1359
  });
1305
1360
  }
1306
1361
  }
@@ -1311,7 +1366,8 @@ function flattenToLintIssues(report, skip) {
1311
1366
  category: "spam",
1312
1367
  rule: issue.rule,
1313
1368
  message: issue.message,
1314
- detail: issue.detail
1369
+ detail: issue.detail,
1370
+ ...issue.loc ? { loc: issue.loc } : {}
1315
1371
  });
1316
1372
  }
1317
1373
  }
@@ -1321,7 +1377,8 @@ function flattenToLintIssues(report, skip) {
1321
1377
  severity: issue.severity,
1322
1378
  category: "links",
1323
1379
  rule: issue.rule,
1324
- message: issue.message
1380
+ message: issue.message,
1381
+ ...issue.loc ? { loc: issue.loc } : {}
1325
1382
  });
1326
1383
  }
1327
1384
  }
@@ -1331,7 +1388,8 @@ function flattenToLintIssues(report, skip) {
1331
1388
  severity: issue.severity,
1332
1389
  category: "accessibility",
1333
1390
  rule: issue.rule,
1334
- message: issue.message
1391
+ message: issue.message,
1392
+ ...issue.loc ? { loc: issue.loc } : {}
1335
1393
  });
1336
1394
  }
1337
1395
  }
@@ -1341,7 +1399,8 @@ function flattenToLintIssues(report, skip) {
1341
1399
  severity: issue.severity,
1342
1400
  category: "images",
1343
1401
  rule: issue.rule,
1344
- message: issue.message
1402
+ message: issue.message,
1403
+ ...issue.loc ? { loc: issue.loc } : {}
1345
1404
  });
1346
1405
  }
1347
1406
  }
@@ -1351,7 +1410,8 @@ function flattenToLintIssues(report, skip) {
1351
1410
  severity: issue.severity,
1352
1411
  category: "inboxPreview",
1353
1412
  rule: issue.rule,
1354
- message: issue.message
1413
+ message: issue.message,
1414
+ ...issue.loc ? { loc: issue.loc } : {}
1355
1415
  });
1356
1416
  }
1357
1417
  }
@@ -1361,7 +1421,8 @@ function flattenToLintIssues(report, skip) {
1361
1421
  severity: issue.severity,
1362
1422
  category: "size",
1363
1423
  rule: issue.rule,
1364
- message: issue.message
1424
+ message: issue.message,
1425
+ ...issue.loc ? { loc: issue.loc } : {}
1365
1426
  });
1366
1427
  }
1367
1428
  }
@@ -1371,7 +1432,30 @@ function flattenToLintIssues(report, skip) {
1371
1432
  severity: issue.severity,
1372
1433
  category: "templateVars",
1373
1434
  rule: issue.rule,
1374
- message: issue.message
1435
+ message: issue.message,
1436
+ ...issue.loc ? { loc: issue.loc } : {}
1437
+ });
1438
+ }
1439
+ }
1440
+ if (!skip.includes("overflow")) {
1441
+ for (const issue of report.overflow.issues) {
1442
+ issues.push({
1443
+ severity: issue.severity,
1444
+ category: "overflow",
1445
+ rule: issue.rule,
1446
+ message: issue.message,
1447
+ ...issue.loc ? { loc: issue.loc } : {}
1448
+ });
1449
+ }
1450
+ }
1451
+ if (!skip.includes("visual")) {
1452
+ for (const issue of report.visual.issues) {
1453
+ issues.push({
1454
+ severity: issue.severity,
1455
+ category: "visual",
1456
+ rule: issue.rule,
1457
+ message: issue.message,
1458
+ ...issue.loc ? { loc: issue.loc } : {}
1375
1459
  });
1376
1460
  }
1377
1461
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/meta.ts","../package.json","../src/commands/analyze.ts","../src/utils.ts","../src/output/terminal.ts","../src/output/json.ts","../src/commands/preview.ts","../src/commands/clients.ts","../src/commands/export.ts","../src/output/html-report.ts","../src/commands/fix.ts","../src/commands/audit.ts","../src/commands/lint.ts"],"sourcesContent":["import { defineCommand, runMain } from \"citty\";\nimport { meta } from \"./meta.js\";\nimport analyze from \"./commands/analyze.js\";\nimport preview from \"./commands/preview.js\";\nimport clients from \"./commands/clients.js\";\nimport exportCmd from \"./commands/export.js\";\nimport fix from \"./commands/fix.js\";\nimport audit from \"./commands/audit.js\";\nimport lint from \"./commands/lint.js\";\n\nconst main = defineCommand({\n meta,\n subCommands: {\n analyze,\n preview,\n clients,\n export: exportCmd,\n fix,\n audit,\n lint,\n },\n});\n\nrunMain(main);\n","import { EMAIL_CLIENTS } from \"@emailens/engine\";\nimport pkg from \"../package.json\" with { type: \"json\" };\n\n/**\n * CLI identity. Both fields used to be hardcoded in index.ts and both had\n * drifted: `--version` reported 0.4.0 while the package was 0.3.4, and the\n * description claimed 12 clients while the engine shipped 15. Anyone filing a\n * bug would have quoted a version that never existed.\n *\n * Kept in its own module so it can be asserted directly. Testing it through the\n * binary means racing citty's process.exit() against the stdout flush.\n */\nexport const meta = {\n name: \"emailens\",\n version: pkg.version,\n description: `Email compatibility analysis CLI — preview how HTML emails render across ${EMAIL_CLIENTS.length} email clients.`,\n} as const;\n","{\n \"name\": \"@emailens/cli\",\n \"version\": \"0.3.5\",\n \"description\": \"CLI tool for email compatibility analysis — analyze, preview, and export email rendering across 15 email clients.\",\n \"type\": \"module\",\n \"bin\": {\n \"emailens\": \"./dist/index.js\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n }\n },\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"start\": \"bun run dist/index.js\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"bun test\",\n \"prepublishOnly\": \"bun run build\"\n },\n \"dependencies\": {\n \"@emailens/engine\": \"^0.9.3\",\n \"citty\": \"^0.1.6\",\n \"cli-table3\": \"^0.6.5\",\n \"ora\": \"^8.2.0\",\n \"picocolors\": \"^1.1.1\"\n },\n \"optionalDependencies\": {\n \"@anthropic-ai/sdk\": \"^0.52.0\",\n \"playwright-core\": \"^1.52.0\"\n },\n \"devDependencies\": {\n \"@types/bun\": \"^1.3.14\",\n \"@types/mjml\": \"^4.7.4\",\n \"@types/node\": \"^25.3.0\",\n \"bun-types\": \"^1.3.9\",\n \"tsup\": \"^8.4.0\",\n \"typescript\": \"^5.9.3\"\n },\n \"keywords\": [\n \"email\",\n \"html-email\",\n \"css\",\n \"compatibility\",\n \"cli\",\n \"gmail\",\n \"outlook\",\n \"apple-mail\",\n \"email-testing\",\n \"dark-mode\"\n ],\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/emailens/cli.git\"\n },\n \"engines\": {\n \"node\": \">=18\"\n },\n \"homepage\": \"https://github.com/emailens/cli#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/emailens/cli/issues\"\n }\n}","import { defineCommand } from \"citty\";\r\nimport ora from \"ora\";\r\nimport {\r\n analyzeEmail,\r\n generateCompatibilityScore,\r\n warningsForClient,\r\n CompileError,\r\n} from \"@emailens/engine\";\r\nimport { readInput, resolveClients, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\nimport { printScoreTable, printWarnings } from \"../output/terminal.js\";\r\nimport { printJson } from \"../output/json.js\";\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"analyze\",\r\n description: \"Analyze email CSS compatibility across clients\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or - for stdin\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n clients: {\r\n type: \"string\",\r\n alias: \"c\",\r\n description: \"Comma-separated client IDs to filter\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Output as JSON\",\r\n },\r\n quiet: {\r\n type: \"boolean\",\r\n alias: \"q\",\r\n description: \"Suppress spinners and decorations\",\r\n },\r\n },\r\n async run({ args }) {\r\n // --json implies quiet spinners\r\n const spinner = (args.quiet || args.json) ? null : ora();\r\n\r\n try {\r\n // Resolve format early so we fail fast on invalid --format\r\n const format = resolveFormat(args.format, args.input);\r\n\r\n // Read input\r\n spinner?.start(\"Reading input...\");\r\n const source = await readInput(args.input);\r\n spinner?.succeed(\"Input read\");\r\n\r\n // Compile (passthrough for html, auto-detects from extension)\r\n const html = await compile(source, format, args.input !== \"-\" ? args.input : undefined);\r\n\r\n // Resolve clients\r\n const clientIds = resolveClients(args.clients);\r\n\r\n // Analyze\r\n spinner?.start(\"Analyzing compatibility...\");\r\n const framework = toFramework(format);\r\n const warnings = analyzeEmail(html, framework);\r\n const allScores = generateCompatibilityScore(warnings);\r\n\r\n // Filter scores to requested clients\r\n const clientSet = new Set(clientIds);\r\n const scores: typeof allScores = {};\r\n for (const [id, data] of Object.entries(allScores)) {\r\n if (clientSet.has(id)) {\r\n scores[id] = data;\r\n }\r\n }\r\n\r\n // Filter warnings to requested clients\r\n const filteredWarnings = clientIds.flatMap(id => warningsForClient(warnings, id));\r\n\r\n spinner?.succeed(\"Analysis complete\");\r\n\r\n // Output\r\n if (args.json) {\r\n printJson({ scores, warnings: filteredWarnings });\r\n } else {\r\n printScoreTable(scores, filteredWarnings, { quiet: args.quiet });\r\n printWarnings(filteredWarnings, { quiet: args.quiet });\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n spinner?.fail(`Compilation failed (${err.phase}): ${err.message}`);\r\n } else {\r\n spinner?.fail((err as Error).message);\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n","import { readFileSync } from \"node:fs\";\r\nimport { EMAIL_CLIENTS, type Framework, type InputFormat } from \"@emailens/engine\";\r\nimport { detectFormat } from \"@emailens/engine/compile\";\r\n\r\nconst VALID_FORMATS = new Set([\"html\", \"jsx\", \"mjml\", \"maizzle\"]);\r\n\r\n/**\r\n * Read HTML input from a file path or stdin (`-`).\r\n * Always returns a Promise for consistent caller ergonomics.\r\n */\r\nexport async function readInput(pathOrStdin: string): Promise<string> {\r\n if (pathOrStdin === \"-\") {\r\n // Try /dev/stdin first (works on Unix and Git Bash on Windows)\r\n try {\r\n return readFileSync(\"/dev/stdin\", \"utf-8\");\r\n } catch {\r\n // Fall through to async approach\r\n }\r\n\r\n // Async stdin reading for Windows compatibility\r\n return new Promise<string>((resolve, reject) => {\r\n const chunks: Buffer[] = [];\r\n process.stdin.resume();\r\n process.stdin.on(\"data\", (chunk) => chunks.push(Buffer.from(chunk)));\r\n process.stdin.on(\"end\", () => {\r\n const data = Buffer.concat(chunks).toString(\"utf-8\");\r\n if (!data.trim()) {\r\n reject(new Error(\"No input received from stdin. Pipe HTML or provide a file path.\"));\r\n } else {\r\n resolve(data);\r\n }\r\n });\r\n process.stdin.on(\"error\", () => {\r\n reject(new Error(\"No input received from stdin. Pipe HTML or provide a file path.\"));\r\n });\r\n });\r\n }\r\n\r\n try {\r\n return readFileSync(pathOrStdin, \"utf-8\");\r\n } catch (err) {\r\n const message = err instanceof Error ? err.message : String(err);\r\n throw new Error(`Failed to read file \"${pathOrStdin}\": ${message}`);\r\n }\r\n}\r\n\r\n/**\r\n * Validate and resolve client IDs. Returns all clients if none specified.\r\n */\r\nexport function resolveClients(ids?: string): string[] {\r\n const validIds = new Set(EMAIL_CLIENTS.map((c) => c.id));\r\n\r\n if (!ids) {\r\n return EMAIL_CLIENTS.map((c) => c.id);\r\n }\r\n\r\n const requested = ids.split(\",\").map((s) => s.trim()).filter(Boolean);\r\n const invalid = requested.filter((id) => !validIds.has(id));\r\n\r\n if (invalid.length > 0) {\r\n throw new Error(\r\n `Unknown client ID(s): ${invalid.join(\", \")}.\\n` +\r\n `Valid IDs: ${Array.from(validIds).join(\", \")}`\r\n );\r\n }\r\n\r\n return requested;\r\n}\r\n\r\n/**\r\n * Resolve the input format from explicit flag, file extension, or default.\r\n * Validates the --format flag and errors on unknown values.\r\n * Auto-detects from file extension when no flag is given.\r\n */\r\nexport function resolveFormat(formatFlag?: string, filePath?: string): InputFormat {\r\n if (formatFlag) {\r\n if (!VALID_FORMATS.has(formatFlag)) {\r\n throw new Error(\r\n `Unknown format \"${formatFlag}\". Valid formats: ${Array.from(VALID_FORMATS).join(\", \")}`\r\n );\r\n }\r\n return formatFlag as InputFormat;\r\n }\r\n\r\n // Auto-detect from file extension\r\n if (filePath && filePath !== \"-\") {\r\n return detectFormat(filePath);\r\n }\r\n\r\n return \"html\";\r\n}\r\n\r\n/**\r\n * Convert a format string to the engine's Framework type.\r\n */\r\nexport function toFramework(format: InputFormat): Framework | undefined {\r\n if (format === \"jsx\" || format === \"mjml\" || format === \"maizzle\") return format;\r\n return undefined;\r\n}\r\n","import pc from \"picocolors\";\r\nimport Table from \"cli-table3\";\r\nimport {\r\n EMAIL_CLIENTS,\r\n type CSSWarning,\r\n} from \"@emailens/engine\";\r\n\r\n/**\r\n * Color-code a score value: green >=90, yellow >=70, red <70.\r\n */\r\nfunction colorScore(score: number): string {\r\n const str = String(score);\r\n if (score >= 90) return pc.green(str);\r\n if (score >= 70) return pc.yellow(str);\r\n return pc.red(str);\r\n}\r\n\r\n/**\r\n * Get severity icon.\r\n */\r\nfunction severityIcon(severity: CSSWarning[\"severity\"]): string {\r\n switch (severity) {\r\n case \"error\": return pc.red(\"✗\");\r\n case \"warning\": return pc.yellow(\"⚠\");\r\n case \"info\": return pc.blue(\"ℹ\");\r\n }\r\n}\r\n\r\n/**\r\n * Get the worst severity from a list of warnings.\r\n */\r\nfunction worstSeverity(warnings: CSSWarning[]): CSSWarning[\"severity\"] {\r\n if (warnings.some((w) => w.severity === \"error\")) return \"error\";\r\n if (warnings.some((w) => w.severity === \"warning\")) return \"warning\";\r\n return \"info\";\r\n}\r\n\r\n/**\r\n * Format issue count with severity summary.\r\n */\r\nfunction formatIssueCount(warnings: CSSWarning[]): string {\r\n const errors = warnings.filter((w) => w.severity === \"error\").length;\r\n const warns = warnings.filter((w) => w.severity === \"warning\").length;\r\n const infos = warnings.filter((w) => w.severity === \"info\").length;\r\n\r\n const parts: string[] = [];\r\n if (errors > 0) parts.push(pc.red(`${errors} err`));\r\n if (warns > 0) parts.push(pc.yellow(`${warns} warn`));\r\n if (infos > 0) parts.push(pc.blue(`${infos} info`));\r\n\r\n return parts.join(\", \") || pc.green(\"none\");\r\n}\r\n\r\n/**\r\n * Print the compatibility score table to terminal.\r\n */\r\nexport function printScoreTable(\r\n scores: Record<string, { score: number; errors: number; warnings: number; info: number }>,\r\n warnings: CSSWarning[],\r\n options?: { quiet?: boolean }\r\n): void {\r\n if (!options?.quiet) {\r\n console.log();\r\n console.log(pc.bold(\" Emailens — Compatibility Report\"));\r\n console.log();\r\n }\r\n\r\n const clientMap = new Map(EMAIL_CLIENTS.map((c) => [c.id, c.name]));\r\n\r\n // Group warnings by client\r\n const warningsByClient = new Map<string, CSSWarning[]>();\r\n for (const w of warnings) {\r\n const existing = warningsByClient.get(w.client) ?? [];\r\n existing.push(w);\r\n warningsByClient.set(w.client, existing);\r\n }\r\n\r\n // Score table\r\n const table = new Table({\r\n head: [pc.bold(\"Client\"), pc.bold(\"Score\"), pc.bold(\"Issues\")],\r\n style: { head: [], border: [] },\r\n });\r\n\r\n const sortedClients = Object.entries(scores).sort(([, a], [, b]) => a.score - b.score);\r\n\r\n for (const [clientId, { score }] of sortedClients) {\r\n const clientWarnings = warningsByClient.get(clientId) ?? [];\r\n table.push([\r\n clientMap.get(clientId) ?? clientId,\r\n colorScore(score),\r\n formatIssueCount(clientWarnings),\r\n ]);\r\n }\r\n\r\n console.log(table.toString());\r\n\r\n // Overall score\r\n const scoreValues = Object.values(scores);\r\n const overall = scoreValues.length > 0\r\n ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\r\n : 0;\r\n\r\n console.log();\r\n console.log(` Overall: ${colorScore(overall)}/100`);\r\n console.log();\r\n}\r\n\r\n/**\r\n * Print detailed warnings grouped by client.\r\n */\r\nexport function printWarnings(\r\n warnings: CSSWarning[],\r\n options?: { quiet?: boolean }\r\n): void {\r\n if (warnings.length === 0) {\r\n if (!options?.quiet) {\r\n console.log(pc.green(\" No compatibility issues found.\"));\r\n console.log();\r\n }\r\n return;\r\n }\r\n\r\n // Group by client\r\n const byClient = new Map<string, CSSWarning[]>();\r\n for (const w of warnings) {\r\n const existing = byClient.get(w.client) ?? [];\r\n existing.push(w);\r\n byClient.set(w.client, existing);\r\n }\r\n\r\n const clientMap = new Map(EMAIL_CLIENTS.map((c) => [c.id, c.name]));\r\n\r\n for (const [clientId, clientWarnings] of byClient) {\r\n const clientName = clientMap.get(clientId) ?? clientId;\r\n const worst = worstSeverity(clientWarnings);\r\n console.log(` ${severityIcon(worst)} ${pc.bold(clientName)} (${clientWarnings.length} issue${clientWarnings.length === 1 ? \"\" : \"s\"})`);\r\n\r\n for (const w of clientWarnings) {\r\n const prop = pc.dim(w.property);\r\n console.log(` ${severityIcon(w.severity)} ${prop} — ${w.message}`);\r\n if (w.suggestion) {\r\n console.log(` ${pc.dim(\"→\")} ${w.suggestion}`);\r\n }\r\n }\r\n console.log();\r\n }\r\n}\r\n","import type { CSSWarning, TransformResult } from \"@emailens/engine\";\r\n\r\ninterface JsonOutput {\r\n overallScore: number;\r\n scores: Record<string, { score: number; errors: number; warnings: number; info: number }>;\r\n warnings: Array<{\r\n client: string;\r\n property: string;\r\n severity: string;\r\n message: string;\r\n suggestion?: string;\r\n fix?: {\r\n before: string;\r\n after: string;\r\n language: string;\r\n description: string;\r\n };\r\n }>;\r\n transforms?: Array<{\r\n clientId: string;\r\n html: string;\r\n }>;\r\n darkMode?: Record<string, {\r\n html: string;\r\n warningCount: number;\r\n }>;\r\n}\r\n\r\n/**\r\n * Build the JSON output object. Reusable by both printJson() and export command.\r\n */\r\nexport function formatJsonOutput(opts: {\r\n scores: Record<string, { score: number; errors: number; warnings: number; info: number }>;\r\n warnings: CSSWarning[];\r\n transforms?: TransformResult[];\r\n darkMode?: Record<string, { html: string; warnings: CSSWarning[] }>;\r\n}): JsonOutput {\r\n const scoreValues = Object.values(opts.scores);\r\n const overallScore = scoreValues.length > 0\r\n ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\r\n : 0;\r\n\r\n const output: JsonOutput = {\r\n overallScore,\r\n scores: opts.scores,\r\n warnings: opts.warnings.map((w) => ({\r\n client: w.client,\r\n property: w.property,\r\n severity: w.severity,\r\n message: w.message,\r\n suggestion: w.suggestion,\r\n fix: w.fix ? {\r\n before: w.fix.before,\r\n after: w.fix.after,\r\n language: w.fix.language,\r\n description: w.fix.description,\r\n } : undefined,\r\n })),\r\n };\r\n\r\n if (opts.transforms) {\r\n output.transforms = opts.transforms.map((t) => ({\r\n clientId: t.clientId,\r\n html: t.html,\r\n }));\r\n }\r\n\r\n if (opts.darkMode) {\r\n output.darkMode = {};\r\n for (const [clientId, dm] of Object.entries(opts.darkMode)) {\r\n output.darkMode[clientId] = {\r\n html: dm.html,\r\n warningCount: dm.warnings.length,\r\n };\r\n }\r\n }\r\n\r\n return output;\r\n}\r\n\r\n/**\r\n * Format analysis/preview results as JSON and print to stdout.\r\n */\r\nexport function printJson(opts: {\r\n scores: Record<string, { score: number; errors: number; warnings: number; info: number }>;\r\n warnings: CSSWarning[];\r\n transforms?: TransformResult[];\r\n darkMode?: Record<string, { html: string; warnings: CSSWarning[] }>;\r\n}): void {\r\n console.log(JSON.stringify(formatJsonOutput(opts), null, 2));\r\n}\r\n","import { defineCommand } from \"citty\";\r\nimport ora from \"ora\";\r\nimport {\r\n transformForAllClients,\r\n analyzeEmail,\r\n generateCompatibilityScore,\r\n simulateDarkMode,\r\n warningsForClient,\r\n CompileError,\r\n type CSSWarning,\r\n} from \"@emailens/engine\";\r\nimport { readInput, resolveClients, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\nimport { printScoreTable, printWarnings } from \"../output/terminal.js\";\r\nimport { printJson } from \"../output/json.js\";\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"preview\",\r\n description: \"Full email preview: transforms, analysis, dark mode, screenshots\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or - for stdin\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n clients: {\r\n type: \"string\",\r\n alias: \"c\",\r\n description: \"Comma-separated client IDs to filter\",\r\n },\r\n \"dark-mode\": {\r\n type: \"boolean\",\r\n alias: \"d\",\r\n description: \"Include dark mode simulation\",\r\n },\r\n screenshots: {\r\n type: \"boolean\",\r\n description: \"Capture screenshots (requires BROWSERLESS_URL)\",\r\n },\r\n out: {\r\n type: \"string\",\r\n alias: \"o\",\r\n description: \"Output directory for screenshots\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Output as JSON\",\r\n },\r\n quiet: {\r\n type: \"boolean\",\r\n alias: \"q\",\r\n description: \"Suppress spinners and decorations\",\r\n },\r\n },\r\n async run({ args }) {\r\n const spinner = (args.quiet || args.json) ? null : ora();\r\n\r\n try {\r\n // Resolve format early so we fail fast on invalid --format\r\n const format = resolveFormat(args.format, args.input);\r\n\r\n // Read input\r\n spinner?.start(\"Reading input...\");\r\n const source = await readInput(args.input);\r\n spinner?.succeed(\"Input read\");\r\n\r\n // Compile (passthrough for html, auto-detects from extension)\r\n const html = await compile(source, format, args.input !== \"-\" ? args.input : undefined);\r\n\r\n // Resolve clients\r\n const clientIds = resolveClients(args.clients);\r\n const clientSet = new Set(clientIds);\r\n\r\n // Transform\r\n spinner?.start(\"Transforming for all clients...\");\r\n const allTransforms = transformForAllClients(html);\r\n const transforms = allTransforms.filter((t) => clientSet.has(t.clientId));\r\n spinner?.succeed(`Transformed for ${transforms.length} clients`);\r\n\r\n // Analyze\r\n spinner?.start(\"Analyzing compatibility...\");\r\n const framework = toFramework(format);\r\n const warnings = analyzeEmail(html, framework);\r\n const allScores = generateCompatibilityScore(warnings);\r\n\r\n const scores: typeof allScores = {};\r\n for (const [id, data] of Object.entries(allScores)) {\r\n if (clientSet.has(id)) {\r\n scores[id] = data;\r\n }\r\n }\r\n const filteredWarnings = clientIds.flatMap(id => warningsForClient(warnings, id));\r\n spinner?.succeed(\"Analysis complete\");\r\n\r\n // Dark mode\r\n let darkMode: Record<string, { html: string; warnings: CSSWarning[] }> | undefined;\r\n if (args[\"dark-mode\"]) {\r\n spinner?.start(\"Simulating dark mode...\");\r\n darkMode = {};\r\n for (const t of transforms) {\r\n darkMode[t.clientId] = simulateDarkMode(t.html, t.clientId);\r\n }\r\n spinner?.succeed(\"Dark mode simulation complete\");\r\n }\r\n\r\n // Screenshots\r\n if (args.screenshots) {\r\n const outDir = args.out ?? \"./emailens-screenshots\";\r\n spinner?.start(\"Capturing screenshots...\");\r\n try {\r\n const { captureScreenshots } = await import(\"../screenshots.js\");\r\n const results = await captureScreenshots(\r\n transforms,\r\n outDir,\r\n (clientId, index, total) => {\r\n if (spinner) spinner.text = `Capturing screenshots (${index}/${total}: ${clientId})...`;\r\n },\r\n );\r\n spinner?.succeed(`Screenshots saved to ${outDir} (${results.size} files)`);\r\n } catch (err) {\r\n spinner?.warn(`Screenshots skipped: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n // Output\r\n if (args.json) {\r\n printJson({ scores, warnings: filteredWarnings, transforms, darkMode });\r\n } else {\r\n printScoreTable(scores, filteredWarnings, { quiet: args.quiet });\r\n printWarnings(filteredWarnings, { quiet: args.quiet });\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n spinner?.fail(`Compilation failed (${err.phase}): ${err.message}`);\r\n } else {\r\n spinner?.fail((err as Error).message);\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n","import { defineCommand } from \"citty\";\r\nimport pc from \"picocolors\";\r\nimport Table from \"cli-table3\";\r\nimport { EMAIL_CLIENTS } from \"@emailens/engine\";\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"clients\",\r\n description: \"List all supported email clients\",\r\n },\r\n args: {\r\n json: {\r\n type: \"boolean\",\r\n description: \"Output as JSON\",\r\n },\r\n },\r\n run({ args }) {\r\n if (args.json) {\r\n const data = EMAIL_CLIENTS.map((c) => ({\r\n id: c.id,\r\n name: c.name,\r\n category: c.category,\r\n engine: c.engine,\r\n darkModeSupport: c.darkModeSupport,\r\n }));\r\n console.log(JSON.stringify(data, null, 2));\r\n return;\r\n }\r\n\r\n console.log();\r\n console.log(pc.bold(\" Emailens — Supported Email Clients\"));\r\n console.log();\r\n\r\n const table = new Table({\r\n head: [\r\n pc.bold(\"ID\"),\r\n pc.bold(\"Name\"),\r\n pc.bold(\"Category\"),\r\n pc.bold(\"Engine\"),\r\n pc.bold(\"Dark Mode\"),\r\n ],\r\n style: { head: [], border: [] },\r\n });\r\n\r\n for (const client of EMAIL_CLIENTS) {\r\n table.push([\r\n client.id,\r\n client.name,\r\n client.category,\r\n client.engine,\r\n client.darkModeSupport ? pc.green(\"Yes\") : pc.dim(\"No\"),\r\n ]);\r\n }\r\n\r\n console.log(table.toString());\r\n console.log();\r\n console.log(pc.dim(` ${EMAIL_CLIENTS.length} clients supported`));\r\n console.log();\r\n },\r\n});\r\n","import { mkdirSync, writeFileSync } from \"node:fs\";\r\nimport { join, resolve } from \"node:path\";\r\nimport { defineCommand } from \"citty\";\r\nimport ora from \"ora\";\r\nimport {\r\n transformForAllClients,\r\n analyzeEmail,\r\n generateCompatibilityScore,\r\n simulateDarkMode,\r\n warningsForClient,\r\n CompileError,\r\n type CSSWarning,\r\n} from \"@emailens/engine\";\r\n\r\n// toPlainText available in engine >=0.8.6\r\nlet toPlainText: ((html: string) => string) | null = null;\r\ntry {\r\n // eslint-disable-next-line @typescript-eslint/no-require-imports\r\n ({ toPlainText } = require(\"@emailens/engine\"));\r\n} catch {\r\n // older engine version\r\n}\r\nimport { readInput, resolveClients, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\nimport { generateHtmlReport } from \"../output/html-report.js\";\r\nimport { formatJsonOutput } from \"../output/json.js\";\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"export\",\r\n description: \"Export compatibility report as JSON or HTML\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or - for stdin\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n clients: {\r\n type: \"string\",\r\n alias: \"c\",\r\n description: \"Comma-separated client IDs to filter\",\r\n },\r\n \"dark-mode\": {\r\n type: \"boolean\",\r\n alias: \"d\",\r\n description: \"Include dark mode simulation\",\r\n },\r\n screenshots: {\r\n type: \"boolean\",\r\n description: \"Capture screenshots (requires BROWSERLESS_URL)\",\r\n },\r\n out: {\r\n type: \"string\",\r\n alias: \"o\",\r\n description: \"Output directory (default: ./emailens-report)\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Export as JSON instead of HTML\",\r\n },\r\n \"plain-text\": {\r\n type: \"boolean\",\r\n description: \"Also export a plain text version (.txt) for multipart emails\",\r\n },\r\n quiet: {\r\n type: \"boolean\",\r\n alias: \"q\",\r\n description: \"Suppress spinners and decorations\",\r\n },\r\n },\r\n async run({ args }) {\r\n const spinner = args.quiet ? null : ora();\r\n const outDir = resolve(args.out ?? \"./emailens-report\");\r\n\r\n try {\r\n // Resolve format early so we fail fast on invalid --format\r\n const format = resolveFormat(args.format, args.input);\r\n\r\n // Read input\r\n spinner?.start(\"Reading input...\");\r\n const source = await readInput(args.input);\r\n spinner?.succeed(\"Input read\");\r\n\r\n // Compile (passthrough for html, auto-detects from extension)\r\n const html = await compile(source, format, args.input !== \"-\" ? args.input : undefined);\r\n\r\n // Resolve clients\r\n const clientIds = resolveClients(args.clients);\r\n const clientSet = new Set(clientIds);\r\n\r\n // Transform\r\n spinner?.start(\"Transforming for all clients...\");\r\n const allTransforms = transformForAllClients(html);\r\n const transforms = allTransforms.filter((t) => clientSet.has(t.clientId));\r\n spinner?.succeed(`Transformed for ${transforms.length} clients`);\r\n\r\n // Analyze\r\n spinner?.start(\"Analyzing compatibility...\");\r\n const framework = toFramework(format);\r\n const warnings = analyzeEmail(html, framework);\r\n const allScores = generateCompatibilityScore(warnings);\r\n\r\n const scores: typeof allScores = {};\r\n for (const [id, data] of Object.entries(allScores)) {\r\n if (clientSet.has(id)) {\r\n scores[id] = data;\r\n }\r\n }\r\n const filteredWarnings = clientIds.flatMap(id => warningsForClient(warnings, id));\r\n spinner?.succeed(\"Analysis complete\");\r\n\r\n // Dark mode\r\n let darkMode: Record<string, { html: string; warnings: CSSWarning[] }> | undefined;\r\n if (args[\"dark-mode\"]) {\r\n spinner?.start(\"Simulating dark mode...\");\r\n darkMode = {};\r\n for (const t of transforms) {\r\n darkMode[t.clientId] = simulateDarkMode(t.html, t.clientId);\r\n }\r\n spinner?.succeed(\"Dark mode simulation complete\");\r\n }\r\n\r\n // Screenshots\r\n if (args.screenshots) {\r\n spinner?.start(\"Capturing screenshots...\");\r\n try {\r\n const { captureScreenshots } = await import(\"../screenshots.js\");\r\n const screenshotDir = join(outDir, \"screenshots\");\r\n const results = await captureScreenshots(\r\n transforms,\r\n screenshotDir,\r\n (clientId, index, total) => {\r\n if (spinner) spinner.text = `Capturing screenshots (${index}/${total}: ${clientId})...`;\r\n },\r\n );\r\n spinner?.succeed(`Screenshots saved (${results.size} files)`);\r\n } catch (err) {\r\n spinner?.warn(`Screenshots skipped: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n // Export\r\n mkdirSync(outDir, { recursive: true });\r\n\r\n if (args.json) {\r\n spinner?.start(\"Exporting JSON...\");\r\n const jsonData = formatJsonOutput({ scores, warnings: filteredWarnings, transforms });\r\n const jsonPath = join(outDir, \"report.json\");\r\n writeFileSync(jsonPath, JSON.stringify(jsonData, null, 2));\r\n spinner?.succeed(`JSON report saved to ${jsonPath}`);\r\n } else {\r\n spinner?.start(\"Generating HTML report...\");\r\n const htmlReport = generateHtmlReport({\r\n scores,\r\n warnings: filteredWarnings,\r\n transforms,\r\n darkMode,\r\n originalHtml: html,\r\n });\r\n\r\n const htmlPath = join(outDir, \"report.html\");\r\n writeFileSync(htmlPath, htmlReport);\r\n spinner?.succeed(`HTML report saved to ${htmlPath}`);\r\n }\r\n\r\n // Plain text export\r\n if (args[\"plain-text\"] && toPlainText) {\r\n spinner?.start(\"Generating plain text version...\");\r\n const plainText = toPlainText(html);\r\n const txtPath = join(outDir, \"email.txt\");\r\n writeFileSync(txtPath, plainText);\r\n spinner?.succeed(`Plain text saved to ${txtPath}`);\r\n } else if (args[\"plain-text\"] && !toPlainText) {\r\n spinner?.warn(\"Plain text export requires @emailens/engine >=0.8.6\");\r\n }\r\n\r\n if (!args.quiet) {\r\n console.log();\r\n console.log(` Output: ${outDir}`);\r\n console.log();\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n spinner?.fail(`Compilation failed (${err.phase}): ${err.message}`);\r\n } else {\r\n spinner?.fail((err as Error).message);\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n","import { EMAIL_CLIENTS, type CSSWarning, type TransformResult } from \"@emailens/engine\";\r\n\r\n/**\r\n * Generate a self-contained HTML report file.\r\n */\r\nexport function generateHtmlReport(opts: {\r\n scores: Record<string, { score: number; errors: number; warnings: number; info: number }>;\r\n warnings: CSSWarning[];\r\n transforms?: TransformResult[];\r\n darkMode?: Record<string, { html: string; warnings: CSSWarning[] }>;\r\n originalHtml?: string;\r\n}): string {\r\n const clientMap = new Map(EMAIL_CLIENTS.map((c) => [c.id, c]));\r\n\r\n const scoreValues = Object.values(opts.scores);\r\n const overallScore = scoreValues.length > 0\r\n ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\r\n : 0;\r\n\r\n const scoreColor = (s: number) => s >= 90 ? \"#22c55e\" : s >= 70 ? \"#eab308\" : \"#ef4444\";\r\n\r\n // Build client cards\r\n const clientCards = Object.entries(opts.scores)\r\n .sort(([, a], [, b]) => a.score - b.score)\r\n .map(([clientId, { score, errors, warnings: warns, info }]) => {\r\n const client = clientMap.get(clientId);\r\n const name = client?.name ?? clientId;\r\n const clientWarnings = opts.warnings.filter((w) => w.client === clientId);\r\n const issueCount = errors + warns + info;\r\n\r\n const warningRows = clientWarnings.map((w) => {\r\n const icon = w.severity === \"error\" ? \"&#10007;\" : w.severity === \"warning\" ? \"&#9888;\" : \"&#8505;\";\r\n const color = w.severity === \"error\" ? \"#ef4444\" : w.severity === \"warning\" ? \"#eab308\" : \"#3b82f6\";\r\n return `<div style=\"padding:4px 0;border-bottom:1px solid #f1f1f1\">\r\n <span style=\"color:${color}\">${icon}</span>\r\n <strong>${escapeHtml(w.property)}</strong> — ${escapeHtml(w.message)}\r\n ${w.suggestion ? `<br><span style=\"color:#888;margin-left:20px\">&rarr; ${escapeHtml(w.suggestion)}</span>` : \"\"}\r\n </div>`;\r\n }).join(\"\");\r\n\r\n return `<div style=\"border:1px solid #e5e5e5;border-radius:8px;padding:16px;margin-bottom:16px\">\r\n <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:12px\">\r\n <h3 style=\"margin:0;font-size:16px\">${escapeHtml(name)}</h3>\r\n <span style=\"font-size:24px;font-weight:bold;color:${scoreColor(score)}\">${score}</span>\r\n </div>\r\n <div style=\"font-size:13px;color:#888;margin-bottom:8px\">${client?.engine ?? \"\"} &middot; ${client?.category ?? \"\"} &middot; ${issueCount} issue${issueCount === 1 ? \"\" : \"s\"}</div>\r\n ${clientWarnings.length > 0 ? `<div style=\"font-size:13px\">${warningRows}</div>` : '<div style=\"color:#22c55e;font-size:13px\">No issues</div>'}\r\n </div>`;\r\n }).join(\"\");\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n<meta charset=\"utf-8\">\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n<title>Emailens Compatibility Report</title>\r\n<style>\r\n * { box-sizing: border-box; }\r\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif; margin: 0; padding: 24px; background: #fafafa; color: #333; }\r\n .container { max-width: 900px; margin: 0 auto; }\r\n .header { text-align: center; margin-bottom: 32px; }\r\n .header h1 { margin: 0 0 8px; font-size: 24px; }\r\n .overall { font-size: 48px; font-weight: bold; }\r\n .subtitle { color: #888; font-size: 14px; }\r\n .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 16px; }\r\n .footer { text-align: center; margin-top: 32px; color: #aaa; font-size: 12px; }\r\n</style>\r\n</head>\r\n<body>\r\n<div class=\"container\">\r\n <div class=\"header\">\r\n <h1>Emailens Compatibility Report</h1>\r\n <div class=\"overall\" style=\"color:${scoreColor(overallScore)}\">${overallScore}/100</div>\r\n <div class=\"subtitle\">${Object.keys(opts.scores).length} clients analyzed &middot; ${opts.warnings.length} total issues</div>\r\n </div>\r\n <div class=\"grid\">\r\n ${clientCards}\r\n </div>\r\n <div class=\"footer\">\r\n Generated by <a href=\"https://github.com/emailens/cli\" style=\"color:#888\">@emailens/cli</a>\r\n </div>\r\n</div>\r\n</body>\r\n</html>`;\r\n}\r\n\r\nfunction escapeHtml(str: string): string {\r\n return str\r\n .replace(/&/g, \"&amp;\")\r\n .replace(/</g, \"&lt;\")\r\n .replace(/>/g, \"&gt;\")\r\n .replace(/\"/g, \"&quot;\");\r\n}\r\n","import { defineCommand } from \"citty\";\r\nimport ora from \"ora\";\r\nimport pc from \"picocolors\";\r\nimport {\r\n analyzeEmail,\r\n generateCompatibilityScore,\r\n generateAiFix,\r\n estimateAiFixTokens,\r\n AI_FIX_SYSTEM_PROMPT,\r\n CompileError,\r\n type AiProvider,\r\n} from \"@emailens/engine\";\r\nimport { readInput, resolveClients, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"fix\",\r\n description: \"Generate AI-powered fixes for email compatibility issues\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or - for stdin\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n clients: {\r\n type: \"string\",\r\n alias: \"c\",\r\n description: \"Comma-separated client IDs to scope the fix\",\r\n },\r\n output: {\r\n type: \"string\",\r\n alias: \"o\",\r\n description: \"Write fixed code to file instead of stdout\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Output as JSON (includes token estimates and metadata)\",\r\n },\r\n quiet: {\r\n type: \"boolean\",\r\n alias: \"q\",\r\n description: \"Suppress spinners and decorations\",\r\n },\r\n estimate: {\r\n type: \"boolean\",\r\n description: \"Only show token estimate without calling the AI\",\r\n },\r\n \"max-tokens\": {\r\n type: \"string\",\r\n description: \"Maximum input tokens for the prompt (default: 16000)\",\r\n },\r\n },\r\n async run({ args }) {\r\n const spinner = (args.quiet || args.json) ? null : ora();\r\n\r\n try {\r\n const format = resolveFormat(args.format, args.input);\r\n const framework = toFramework(format);\r\n\r\n // Read and compile input\r\n spinner?.start(\"Reading input...\");\r\n const source = await readInput(args.input);\r\n const html = await compile(source, format, args.input !== \"-\" ? args.input : undefined);\r\n spinner?.succeed(\"Input read\");\r\n\r\n // Analyze\r\n spinner?.start(\"Analyzing compatibility...\");\r\n const warnings = analyzeEmail(html, framework);\r\n const scores = generateCompatibilityScore(warnings);\r\n spinner?.succeed(`Found ${warnings.length} warnings`);\r\n\r\n if (warnings.length === 0) {\r\n if (args.json) {\r\n console.log(JSON.stringify({ message: \"No issues found\", code: source }, null, 2));\r\n } else {\r\n console.log(pc.green(\"\\n No compatibility issues found — nothing to fix.\\n\"));\r\n }\r\n return;\r\n }\r\n\r\n // Resolve scope\r\n const clientIds = resolveClients(args.clients);\r\n let scope: \"current\" | \"all\";\r\n let selectedClientId: string | undefined;\r\n let scopedWarnings = warnings;\r\n let scopedScores = scores;\r\n\r\n if (args.clients && clientIds.length === 1) {\r\n // Single client: use engine's \"current\" scope\r\n scope = \"current\";\r\n selectedClientId = clientIds[0];\r\n } else if (args.clients && clientIds.length > 1) {\r\n // Multiple clients: pre-filter warnings/scores, then use \"all\" scope\r\n // so the engine processes all of them without needing \"current\" + single ID\r\n const clientSet = new Set(clientIds);\r\n scope = \"all\";\r\n selectedClientId = undefined;\r\n scopedWarnings = warnings.filter((w) => clientSet.has(w.client));\r\n scopedScores = Object.fromEntries(\r\n Object.entries(scores).filter(([id]) => clientSet.has(id)),\r\n );\r\n } else {\r\n // No --clients flag: fix for all clients\r\n scope = \"all\";\r\n selectedClientId = undefined;\r\n }\r\n\r\n // Token estimate\r\n const maxInputTokens = args[\"max-tokens\"] ? parseInt(args[\"max-tokens\"], 10) : 16000;\r\n const estimate = await estimateAiFixTokens({\r\n originalHtml: html,\r\n warnings: scopedWarnings,\r\n scores: scopedScores,\r\n scope,\r\n selectedClientId,\r\n format,\r\n maxInputTokens,\r\n });\r\n\r\n const structuralPct = estimate.warningCount > 0\r\n ? Math.round((estimate.structuralCount / estimate.warningCount) * 100)\r\n : 0;\r\n\r\n if (args.estimate) {\r\n if (args.json) {\r\n console.log(JSON.stringify(estimate, null, 2));\r\n } else {\r\n console.log(`\\n ${pc.bold(\"Token Estimate\")}`);\r\n console.log(` Input tokens: ~${estimate.inputTokens.toLocaleString()}`);\r\n console.log(` Output tokens: ~${estimate.estimatedOutputTokens.toLocaleString()}`);\r\n console.log(` Warnings: ${estimate.warningCount} (${estimate.structuralCount} structural)`);\r\n if (estimate.truncated) {\r\n console.log(` ${pc.yellow(`Truncated: ${estimate.warningsRemoved} low-priority warnings removed`)}`);\r\n }\r\n console.log();\r\n }\r\n return;\r\n }\r\n\r\n // Check for API key\r\n const apiKey = process.env.ANTHROPIC_API_KEY;\r\n if (!apiKey) {\r\n spinner?.fail(\"ANTHROPIC_API_KEY environment variable is required\");\r\n console.error(pc.dim(\"\\n Set it with: export ANTHROPIC_API_KEY=sk-ant-...\\n\"));\r\n process.exit(1);\r\n }\r\n\r\n // Show estimate before calling\r\n if (!args.quiet && !args.json) {\r\n console.log(pc.dim(` ~${estimate.inputTokens.toLocaleString()} input tokens, ${estimate.warningCount} warnings (${structuralPct}% structural)`));\r\n if (estimate.truncated) {\r\n console.log(pc.yellow(` Truncated ${estimate.warningsRemoved} low-priority warnings to fit token limit`));\r\n }\r\n }\r\n\r\n // Call AI\r\n spinner?.start(\"Generating AI fix...\");\r\n\r\n let Anthropic: typeof import(\"@anthropic-ai/sdk\").default;\r\n try {\r\n Anthropic = (await import(\"@anthropic-ai/sdk\")).default;\r\n } catch {\r\n spinner?.fail(\"@anthropic-ai/sdk is required for the fix command\");\r\n console.error(pc.dim(\"\\n Install it with: npm install @anthropic-ai/sdk\\n\"));\r\n process.exit(1);\r\n }\r\n\r\n const anthropic = new Anthropic({ apiKey });\r\n\r\n const provider: AiProvider = async (prompt) => {\r\n const msg = await anthropic.messages.create({\r\n model: \"claude-sonnet-4-6\",\r\n max_tokens: 8192,\r\n system: AI_FIX_SYSTEM_PROMPT,\r\n messages: [{ role: \"user\", content: prompt }],\r\n });\r\n const textBlock = msg.content.find((b: { type: string }) => b.type === \"text\");\r\n return textBlock?.type === \"text\" ? (textBlock as { type: \"text\"; text: string }).text : \"\";\r\n };\r\n\r\n const result = await generateAiFix({\r\n originalHtml: html,\r\n warnings: scopedWarnings,\r\n scores: scopedScores,\r\n scope,\r\n selectedClientId,\r\n format,\r\n maxInputTokens,\r\n provider,\r\n });\r\n\r\n spinner?.succeed(`Fixed ${result.targetedWarnings} warnings (${result.structuralCount} structural)`);\r\n\r\n // Output\r\n if (args.output) {\r\n const { writeFileSync } = await import(\"node:fs\");\r\n writeFileSync(args.output, result.code, \"utf-8\");\r\n if (!args.quiet) {\r\n console.log(pc.green(`\\n Fixed code written to ${args.output}\\n`));\r\n }\r\n } else if (args.json) {\r\n console.log(JSON.stringify({\r\n code: result.code,\r\n targetedWarnings: result.targetedWarnings,\r\n structuralCount: result.structuralCount,\r\n tokenEstimate: result.tokenEstimate,\r\n }, null, 2));\r\n } else {\r\n console.log(\"\\n\" + result.code);\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n spinner?.fail(`Compilation failed (${err.phase}): ${err.message}`);\r\n } else {\r\n spinner?.fail((err as Error).message);\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n","import { defineCommand } from \"citty\";\r\nimport ora from \"ora\";\r\nimport pc from \"picocolors\";\r\nimport Table from \"cli-table3\";\r\nimport {\r\n auditEmail,\r\n CompileError,\r\n type AuditReport,\r\n} from \"@emailens/engine\";\r\nimport { readInput, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\nimport { printScoreTable, printWarnings } from \"../output/terminal.js\";\r\n\r\nconst VALID_SKIPS = new Set([\"spam\", \"links\", \"accessibility\", \"images\", \"compatibility\"]);\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"audit\",\r\n description: \"Full email audit: compatibility + spam + links + accessibility + images\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or - for stdin\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Output as JSON\",\r\n },\r\n quiet: {\r\n type: \"boolean\",\r\n alias: \"q\",\r\n description: \"Suppress spinners and decorations\",\r\n },\r\n skip: {\r\n type: \"string\",\r\n description: \"Comma-separated checks to skip: spam,links,accessibility,images,compatibility\",\r\n },\r\n },\r\n async run({ args }) {\r\n const spinner = (args.quiet || args.json) ? null : ora();\r\n\r\n try {\r\n const format = resolveFormat(args.format, args.input);\r\n\r\n // Read input\r\n spinner?.start(\"Reading input...\");\r\n const source = await readInput(args.input);\r\n spinner?.succeed(\"Input read\");\r\n\r\n // Compile\r\n const html = await compile(source, format, args.input !== \"-\" ? args.input : undefined);\r\n\r\n // Parse --skip flag\r\n const skip: Array<\"spam\" | \"links\" | \"accessibility\" | \"images\" | \"compatibility\"> = [];\r\n if (args.skip) {\r\n for (const s of args.skip.split(\",\").map((s) => s.trim()).filter(Boolean)) {\r\n if (!VALID_SKIPS.has(s)) {\r\n throw new Error(`Unknown skip value \"${s}\". Valid: ${Array.from(VALID_SKIPS).join(\", \")}`);\r\n }\r\n skip.push(s as typeof skip[number]);\r\n }\r\n }\r\n\r\n // Run audit\r\n spinner?.start(\"Running full audit...\");\r\n const framework = toFramework(format);\r\n const report = auditEmail(html, { framework, skip });\r\n spinner?.succeed(\"Audit complete\");\r\n\r\n // Output\r\n if (args.json) {\r\n console.log(JSON.stringify(report, null, 2));\r\n } else {\r\n // Compatibility section\r\n if (!skip.includes(\"compatibility\")) {\r\n printScoreTable(report.compatibility.scores, report.compatibility.warnings, { quiet: args.quiet });\r\n printWarnings(report.compatibility.warnings, { quiet: args.quiet });\r\n }\r\n\r\n // Quality summary\r\n printQualitySummary(report, skip, { quiet: args.quiet });\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n spinner?.fail(`Compilation failed (${err.phase}): ${err.message}`);\r\n } else {\r\n spinner?.fail((err as Error).message);\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n\r\nfunction printQualitySummary(\r\n report: AuditReport,\r\n skip: string[],\r\n options?: { quiet?: boolean },\r\n): void {\r\n if (!options?.quiet) {\r\n console.log(pc.bold(\" Quality Summary\"));\r\n console.log();\r\n }\r\n\r\n const table = new Table({\r\n head: [pc.bold(\"Check\"), pc.bold(\"Result\")],\r\n style: { head: [], border: [] },\r\n });\r\n\r\n if (!skip.includes(\"spam\")) {\r\n const { score, level, issues } = report.spam;\r\n const color = score >= 90 ? pc.green : score >= 70 ? pc.yellow : pc.red;\r\n table.push([\"Content Hygiene\", `${color(String(score))}/100 (${level}) — ${issues.length} issue${issues.length === 1 ? \"\" : \"s\"}`]);\r\n }\r\n\r\n if (!skip.includes(\"links\")) {\r\n const { totalLinks, issues } = report.links;\r\n const color = issues.length === 0 ? pc.green : pc.yellow;\r\n table.push([\"Links\", `${totalLinks} link${totalLinks === 1 ? \"\" : \"s\"} — ${color(`${issues.length} issue${issues.length === 1 ? \"\" : \"s\"}`)}`]);\r\n }\r\n\r\n if (!skip.includes(\"accessibility\")) {\r\n const { score, issues } = report.accessibility;\r\n const color = score >= 90 ? pc.green : score >= 70 ? pc.yellow : pc.red;\r\n table.push([\"Accessibility\", `${color(String(score))}/100 — ${issues.length} issue${issues.length === 1 ? \"\" : \"s\"}`]);\r\n }\r\n\r\n if (!skip.includes(\"images\")) {\r\n const { total, issues } = report.images;\r\n const color = issues.length === 0 ? pc.green : pc.yellow;\r\n table.push([\"Images\", `${total} image${total === 1 ? \"\" : \"s\"} — ${color(`${issues.length} issue${issues.length === 1 ? \"\" : \"s\"}`)}`]);\r\n }\r\n\r\n console.log(table.toString());\r\n console.log();\r\n}\r\n","import { defineCommand } from \"citty\";\r\nimport pc from \"picocolors\";\r\nimport { readInput, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\nimport {\r\n auditEmail,\r\n CompileError,\r\n type AuditReport,\r\n} from \"@emailens/engine\";\r\nimport { readdir } from \"node:fs/promises\";\r\nimport { resolve, relative } from \"node:path\";\r\n\r\ninterface LintIssue {\r\n severity: \"error\" | \"warning\" | \"info\";\r\n category: string;\r\n rule: string;\r\n message: string;\r\n detail?: string;\r\n}\r\n\r\ninterface LintFileResult {\r\n file: string;\r\n issues: LintIssue[];\r\n errors: number;\r\n warnings: number;\r\n}\r\n\r\nconst VALID_SKIPS = new Set([\r\n \"spam\", \"links\", \"accessibility\", \"images\",\r\n \"compatibility\", \"inboxPreview\", \"size\", \"templateVariables\",\r\n]);\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"lint\",\r\n description: \"Lint HTML emails for compatibility, spam, accessibility, and more. CI/CD-friendly with structured exit codes.\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or glob pattern\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Output as JSON\",\r\n },\r\n failOnWarning: {\r\n type: \"boolean\",\r\n description: \"Exit 2 if warnings found\",\r\n },\r\n skip: {\r\n type: \"string\",\r\n description: \"Comma-separated checks to skip: spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables\",\r\n },\r\n maxWarnings: {\r\n type: \"string\",\r\n description: \"Fail if more than n warnings\",\r\n },\r\n },\r\n async run({ args }) {\r\n try {\r\n // Parse --skip\r\n const skip: Array<string> = [];\r\n if (args.skip) {\r\n for (const s of args.skip.split(\",\").map((s) => s.trim()).filter(Boolean)) {\r\n if (!VALID_SKIPS.has(s)) {\r\n throw new Error(`Unknown skip value \"${s}\". Valid: ${Array.from(VALID_SKIPS).join(\", \")}`);\r\n }\r\n skip.push(s);\r\n }\r\n }\r\n\r\n const maxWarningsRaw = args.maxWarnings !== undefined ? parseInt(args.maxWarnings, 10) : undefined;\r\n if (maxWarningsRaw !== undefined && isNaN(maxWarningsRaw)) {\r\n throw new Error(`--maxWarnings must be a number, got: ${args.maxWarnings}`);\r\n }\r\n const maxWarnings = maxWarningsRaw ?? Infinity;\r\n const files = await resolveGlob(args.input);\r\n\r\n if (files.length === 0) {\r\n if (args.json) {\r\n console.log(JSON.stringify({ files: [], totalErrors: 0, totalWarnings: 0 }));\r\n } else {\r\n console.error(pc.red(`No files found matching: ${args.input}`));\r\n }\r\n process.exit(1);\r\n }\r\n\r\n const results: LintFileResult[] = [];\r\n let totalErrors = 0;\r\n let totalWarnings = 0;\r\n\r\n for (const file of files) {\r\n const format = resolveFormat(args.format, file);\r\n const source = await readInput(file);\r\n const html = await compile(source, format, file);\r\n const framework = toFramework(format);\r\n\r\n const report = auditEmail(html, {\r\n framework,\r\n skip: skip as AuditSkipType[],\r\n });\r\n\r\n const issues = flattenToLintIssues(report, skip);\r\n const errors = issues.filter((i) => i.severity === \"error\").length;\r\n const warnings = issues.filter((i) => i.severity === \"warning\").length;\r\n\r\n results.push({ file: relative(process.cwd(), file), issues, errors, warnings });\r\n totalErrors += errors;\r\n totalWarnings += warnings;\r\n }\r\n\r\n // Output\r\n if (args.json) {\r\n console.log(JSON.stringify({ files: results, totalErrors, totalWarnings }, null, 2));\r\n } else {\r\n for (const result of results) {\r\n console.log(pc.underline(result.file));\r\n if (result.issues.length === 0) {\r\n console.log(` ${pc.green(\"pass\")} No issues found`);\r\n } else {\r\n for (const issue of result.issues) {\r\n const sev = issue.severity === \"error\"\r\n ? pc.red(\"error\")\r\n : issue.severity === \"warning\"\r\n ? pc.yellow(\"warn \")\r\n : pc.blue(\"info \");\r\n console.log(` ${sev} ${pc.dim(issue.category.padEnd(18))} ${pc.bold(issue.rule.padEnd(22))} ${issue.message}`);\r\n }\r\n }\r\n console.log();\r\n }\r\n\r\n const summary = [\r\n `${files.length} file${files.length === 1 ? \"\" : \"s\"}`,\r\n totalErrors > 0 ? pc.red(`${totalErrors} error${totalErrors === 1 ? \"\" : \"s\"}`) : null,\r\n totalWarnings > 0 ? pc.yellow(`${totalWarnings} warning${totalWarnings === 1 ? \"\" : \"s\"}`) : null,\r\n totalErrors === 0 && totalWarnings === 0 ? pc.green(\"all clean\") : null,\r\n ].filter(Boolean).join(\" | \");\r\n\r\n console.log(summary);\r\n }\r\n\r\n // Exit codes\r\n if (totalErrors > 0) {\r\n process.exit(1);\r\n }\r\n if (args.failOnWarning && totalWarnings > 0) {\r\n if (!args.json) {\r\n console.error(pc.yellow(`\\nFailed: ${totalWarnings} warning${totalWarnings === 1 ? \"\" : \"s\"} found (--failOnWarning)`));\r\n }\r\n process.exit(2);\r\n }\r\n if (Number.isFinite(maxWarnings) && totalWarnings > maxWarnings) {\r\n if (!args.json) {\r\n console.error(pc.red(`\\nExceeded max warnings: ${totalWarnings} > ${maxWarnings}`));\r\n }\r\n process.exit(2);\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n console.error(pc.red(`Compilation failed (${err.phase}): ${err.message}`));\r\n } else {\r\n console.error(pc.red((err as Error).message));\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n\r\ntype AuditSkipType = \"spam\" | \"links\" | \"accessibility\" | \"images\" | \"compatibility\" | \"inboxPreview\" | \"size\" | \"templateVariables\";\r\n\r\n/**\r\n * Resolve a file path or simple glob pattern to an array of files.\r\n * Supports basic * glob in the last path segment.\r\n */\r\nasync function resolveGlob(pattern: string): Promise<string[]> {\r\n // If it's a direct file, just return it\r\n if (!pattern.includes(\"*\")) {\r\n return [resolve(pattern)];\r\n }\r\n\r\n // Recursive glob is not supported — provide clear error\r\n if (pattern.includes(\"**\")) {\r\n throw new Error(\"Recursive glob (**) is not supported. Use a single-level wildcard (e.g., src/*.html) or list files explicitly.\");\r\n }\r\n\r\n // Simple glob: split into dir + glob pattern\r\n const lastSep = Math.max(pattern.lastIndexOf(\"/\"), pattern.lastIndexOf(\"\\\\\"));\r\n const dir = lastSep >= 0 ? pattern.slice(0, lastSep) : \".\";\r\n const glob = lastSep >= 0 ? pattern.slice(lastSep + 1) : pattern;\r\n\r\n // Convert glob to regex (only support * wildcard)\r\n const regex = new RegExp(\"^\" + glob.replace(/\\*/g, \".*\") + \"$\");\r\n\r\n const entries = await readdir(resolve(dir), { withFileTypes: true });\r\n return entries\r\n .filter((e) => e.isFile() && regex.test(e.name))\r\n .map((e) => resolve(dir, e.name))\r\n .sort();\r\n}\r\n\r\n/**\r\n * Flatten an AuditReport into a unified LintIssue array.\r\n */\r\nfunction flattenToLintIssues(report: AuditReport, skip: string[]): LintIssue[] {\r\n const issues: LintIssue[] = [];\r\n\r\n // CSS compatibility warnings → group by property (deduplicate across clients)\r\n if (!skip.includes(\"compatibility\")) {\r\n const seenProps = new Map<string, { severity: \"error\" | \"warning\" | \"info\"; clients: string[]; message: string }>();\r\n\r\n for (const w of report.compatibility.warnings) {\r\n const key = `${w.property}\\0${w.severity}`;\r\n const existing = seenProps.get(key);\r\n if (existing) {\r\n existing.clients.push(w.client);\r\n } else {\r\n seenProps.set(key, {\r\n severity: w.severity,\r\n clients: [w.client],\r\n message: w.message,\r\n });\r\n }\r\n }\r\n\r\n for (const [key, val] of seenProps) {\r\n const property = key.split(\"\\0\")[0];\r\n issues.push({\r\n severity: val.severity,\r\n category: val.clients.slice(0, 3).join(\",\") + (val.clients.length > 3 ? `+${val.clients.length - 3}` : \"\"),\r\n rule: property,\r\n message: val.message,\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"spam\")) {\r\n for (const issue of report.spam.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"spam\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n detail: issue.detail,\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"links\")) {\r\n for (const issue of report.links.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"links\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"accessibility\")) {\r\n for (const issue of report.accessibility.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"accessibility\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"images\")) {\r\n for (const issue of report.images.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"images\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"inboxPreview\")) {\r\n for (const issue of report.inboxPreview.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"inboxPreview\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"size\")) {\r\n for (const issue of report.size.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"size\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"templateVariables\")) {\r\n for (const issue of report.templateVariables.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"templateVars\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n });\r\n }\r\n }\r\n\r\n // Sort: errors first, then warnings, then info\r\n const order = { error: 0, warning: 1, info: 2 };\r\n issues.sort((a, b) => order[a.severity] - order[b.severity]);\r\n\r\n return issues;\r\n}\r\n"],"mappings":"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","names":["defineCommand","EMAIL_CLIENTS","resolve","EMAIL_CLIENTS","defineCommand","ora","analyzeEmail","generateCompatibilityScore","warningsForClient","CompileError","compile","defineCommand","ora","compile","analyzeEmail","generateCompatibilityScore","warningsForClient","CompileError","defineCommand","pc","Table","EMAIL_CLIENTS","defineCommand","ora","transformForAllClients","analyzeEmail","generateCompatibilityScore","simulateDarkMode","warningsForClient","CompileError","compile","EMAIL_CLIENTS","defineCommand","ora","compile","transformForAllClients","analyzeEmail","generateCompatibilityScore","warningsForClient","simulateDarkMode","CompileError","defineCommand","ora","pc","analyzeEmail","generateCompatibilityScore","CompileError","compile","defineCommand","ora","analyzeEmail","generateCompatibilityScore","pc","writeFileSync","CompileError","defineCommand","ora","pc","Table","CompileError","compile","defineCommand","ora","compile","s","CompileError","pc","Table","defineCommand","pc","compile","auditEmail","CompileError","resolve","VALID_SKIPS","defineCommand","s","pc","defineCommand"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/meta.ts","../package.json","../src/commands/analyze.ts","../src/utils.ts","../src/output/terminal.ts","../src/output/json.ts","../src/commands/preview.ts","../src/commands/clients.ts","../src/commands/export.ts","../src/output/html-report.ts","../src/commands/fix.ts","../src/commands/audit.ts","../src/commands/lint.ts"],"sourcesContent":["import { defineCommand, runMain } from \"citty\";\nimport { meta } from \"./meta.js\";\nimport analyze from \"./commands/analyze.js\";\nimport preview from \"./commands/preview.js\";\nimport clients from \"./commands/clients.js\";\nimport exportCmd from \"./commands/export.js\";\nimport fix from \"./commands/fix.js\";\nimport audit from \"./commands/audit.js\";\nimport lint from \"./commands/lint.js\";\n\nconst main = defineCommand({\n meta,\n subCommands: {\n analyze,\n preview,\n clients,\n export: exportCmd,\n fix,\n audit,\n lint,\n },\n});\n\nrunMain(main);\n","import { EMAIL_CLIENTS } from \"@emailens/engine\";\nimport pkg from \"../package.json\" with { type: \"json\" };\n\n/**\n * CLI identity. Both fields used to be hardcoded in index.ts and both had\n * drifted: `--version` reported 0.4.0 while the package was 0.3.4, and the\n * description claimed 12 clients while the engine shipped 15. Anyone filing a\n * bug would have quoted a version that never existed.\n *\n * Kept in its own module so it can be asserted directly. Testing it through the\n * binary means racing citty's process.exit() against the stdout flush.\n */\nexport const meta = {\n name: \"emailens\",\n version: pkg.version,\n description: `Email compatibility analysis CLI — preview how HTML emails render across ${EMAIL_CLIENTS.length} email clients.`,\n} as const;\n","{\r\n \"name\": \"@emailens/cli\",\r\n \"version\": \"0.4.1\",\r\n \"description\": \"CLI tool for email compatibility analysis \\u2014 analyze, preview, and export email rendering across 21 email clients.\",\r\n \"type\": \"module\",\r\n \"bin\": {\r\n \"emailens\": \"./dist/index.js\"\r\n },\r\n \"exports\": {\r\n \".\": {\r\n \"types\": \"./dist/index.d.ts\",\r\n \"import\": \"./dist/index.js\"\r\n }\r\n },\r\n \"main\": \"./dist/index.js\",\r\n \"types\": \"./dist/index.d.ts\",\r\n \"files\": [\r\n \"dist\",\r\n \"LICENSE\"\r\n ],\r\n \"scripts\": {\r\n \"build\": \"tsup\",\r\n \"dev\": \"tsup --watch\",\r\n \"start\": \"bun run dist/index.js\",\r\n \"typecheck\": \"tsc --noEmit\",\r\n \"test\": \"bun test\",\r\n \"prepublishOnly\": \"bun run build\"\r\n },\r\n \"dependencies\": {\r\n \"@emailens/engine\": \"^0.10.2\",\r\n \"citty\": \"^0.1.6\",\r\n \"cli-table3\": \"^0.6.5\",\r\n \"ora\": \"^8.2.0\",\r\n \"picocolors\": \"^1.1.1\"\r\n },\r\n \"optionalDependencies\": {\r\n \"@anthropic-ai/sdk\": \"^0.52.0\",\r\n \"playwright-core\": \"^1.52.0\"\r\n },\r\n \"devDependencies\": {\r\n \"@types/bun\": \"^1.3.14\",\r\n \"@types/mjml\": \"^4.7.4\",\r\n \"@types/node\": \"^25.3.0\",\r\n \"bun-types\": \"^1.3.9\",\r\n \"tsup\": \"^8.4.0\",\r\n \"typescript\": \"^5.9.3\"\r\n },\r\n \"keywords\": [\r\n \"email\",\r\n \"html-email\",\r\n \"css\",\r\n \"compatibility\",\r\n \"cli\",\r\n \"gmail\",\r\n \"outlook\",\r\n \"apple-mail\",\r\n \"email-testing\",\r\n \"dark-mode\"\r\n ],\r\n \"license\": \"MIT\",\r\n \"repository\": {\r\n \"type\": \"git\",\r\n \"url\": \"git+https://github.com/emailens/cli.git\"\r\n },\r\n \"engines\": {\r\n \"node\": \">=18\"\r\n },\r\n \"homepage\": \"https://github.com/emailens/cli#readme\",\r\n \"bugs\": {\r\n \"url\": \"https://github.com/emailens/cli/issues\"\r\n }\r\n}\r\n","import { defineCommand } from \"citty\";\r\nimport ora from \"ora\";\r\nimport {\r\n analyzeEmail,\r\n generateCompatibilityScore,\r\n warningsForClient,\r\n CompileError,\r\n} from \"@emailens/engine\";\r\nimport { positionsApply, readInput, resolveClients, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\nimport { printScoreTable, printWarnings } from \"../output/terminal.js\";\r\nimport { printJson } from \"../output/json.js\";\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"analyze\",\r\n description: \"Analyze email CSS compatibility across clients\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or - for stdin\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n clients: {\r\n type: \"string\",\r\n alias: \"c\",\r\n description: \"Comma-separated client IDs to filter\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Output as JSON\",\r\n },\r\n quiet: {\r\n type: \"boolean\",\r\n alias: \"q\",\r\n description: \"Suppress spinners and decorations\",\r\n },\r\n },\r\n async run({ args }) {\r\n // --json implies quiet spinners\r\n const spinner = (args.quiet || args.json) ? null : ora();\r\n\r\n try {\r\n // Resolve format early so we fail fast on invalid --format\r\n const format = resolveFormat(args.format, args.input);\r\n\r\n // Read input\r\n spinner?.start(\"Reading input...\");\r\n const source = await readInput(args.input);\r\n spinner?.succeed(\"Input read\");\r\n\r\n // Compile (passthrough for html, auto-detects from extension)\r\n const html = await compile(source, format);\r\n\r\n // Resolve clients\r\n const clientIds = resolveClients(args.clients);\r\n\r\n // Analyze\r\n spinner?.start(\"Analyzing compatibility...\");\r\n const framework = toFramework(format);\r\n // Positions only mean something when the file analyzed is the file the\r\n // user wrote — see positionsApply().\r\n const warnings = analyzeEmail(html, framework, { positions: positionsApply(format) });\r\n const allScores = generateCompatibilityScore(warnings);\r\n\r\n // Filter scores to requested clients\r\n const clientSet = new Set(clientIds);\r\n const scores: typeof allScores = {};\r\n for (const [id, data] of Object.entries(allScores)) {\r\n if (clientSet.has(id)) {\r\n scores[id] = data;\r\n }\r\n }\r\n\r\n // Filter warnings to requested clients\r\n const filteredWarnings = clientIds.flatMap(id => warningsForClient(warnings, id));\r\n\r\n spinner?.succeed(\"Analysis complete\");\r\n\r\n // Output\r\n if (args.json) {\r\n printJson({ scores, warnings: filteredWarnings });\r\n } else {\r\n printScoreTable(scores, filteredWarnings, { quiet: args.quiet });\r\n printWarnings(filteredWarnings, { quiet: args.quiet });\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n spinner?.fail(`Compilation failed (${err.phase}): ${err.message}`);\r\n } else {\r\n spinner?.fail((err as Error).message);\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n","import { readFileSync } from \"node:fs\";\r\nimport { EMAIL_CLIENTS, type Framework, type InputFormat } from \"@emailens/engine\";\r\nimport { detectFormat } from \"@emailens/engine/compile\";\r\n\r\nconst VALID_FORMATS = new Set([\"html\", \"jsx\", \"mjml\", \"maizzle\"]);\r\n\r\n/**\r\n * Read HTML input from a file path or stdin (`-`).\r\n * Always returns a Promise for consistent caller ergonomics.\r\n */\r\nexport async function readInput(pathOrStdin: string): Promise<string> {\r\n if (pathOrStdin === \"-\") {\r\n // Try /dev/stdin first (works on Unix and Git Bash on Windows)\r\n try {\r\n return readFileSync(\"/dev/stdin\", \"utf-8\");\r\n } catch {\r\n // Fall through to async approach\r\n }\r\n\r\n // Async stdin reading for Windows compatibility\r\n return new Promise<string>((resolve, reject) => {\r\n const chunks: Buffer[] = [];\r\n process.stdin.resume();\r\n process.stdin.on(\"data\", (chunk) => chunks.push(Buffer.from(chunk)));\r\n process.stdin.on(\"end\", () => {\r\n const data = Buffer.concat(chunks).toString(\"utf-8\");\r\n if (!data.trim()) {\r\n reject(new Error(\"No input received from stdin. Pipe HTML or provide a file path.\"));\r\n } else {\r\n resolve(data);\r\n }\r\n });\r\n process.stdin.on(\"error\", () => {\r\n reject(new Error(\"No input received from stdin. Pipe HTML or provide a file path.\"));\r\n });\r\n });\r\n }\r\n\r\n try {\r\n return readFileSync(pathOrStdin, \"utf-8\");\r\n } catch (err) {\r\n const message = err instanceof Error ? err.message : String(err);\r\n throw new Error(`Failed to read file \"${pathOrStdin}\": ${message}`);\r\n }\r\n}\r\n\r\n/**\r\n * Validate and resolve client IDs. Returns all clients if none specified.\r\n */\r\nexport function resolveClients(ids?: string): string[] {\r\n const validIds = new Set(EMAIL_CLIENTS.map((c) => c.id));\r\n\r\n if (!ids) {\r\n return EMAIL_CLIENTS.map((c) => c.id);\r\n }\r\n\r\n const requested = ids.split(\",\").map((s) => s.trim()).filter(Boolean);\r\n const invalid = requested.filter((id) => !validIds.has(id));\r\n\r\n if (invalid.length > 0) {\r\n throw new Error(\r\n `Unknown client ID(s): ${invalid.join(\", \")}.\\n` +\r\n `Valid IDs: ${Array.from(validIds).join(\", \")}`\r\n );\r\n }\r\n\r\n return requested;\r\n}\r\n\r\n/**\r\n * Resolve the input format from explicit flag, file extension, or default.\r\n * Validates the --format flag and errors on unknown values.\r\n * Auto-detects from file extension when no flag is given.\r\n */\r\nexport function resolveFormat(formatFlag?: string, filePath?: string): InputFormat {\r\n if (formatFlag) {\r\n if (!VALID_FORMATS.has(formatFlag)) {\r\n throw new Error(\r\n `Unknown format \"${formatFlag}\". Valid formats: ${Array.from(VALID_FORMATS).join(\", \")}`\r\n );\r\n }\r\n return formatFlag as InputFormat;\r\n }\r\n\r\n // Auto-detect from file extension\r\n if (filePath && filePath !== \"-\") {\r\n return detectFormat(filePath);\r\n }\r\n\r\n return \"html\";\r\n}\r\n\r\n/**\r\n * Convert a format string to the engine's Framework type.\r\n */\r\nexport function toFramework(format: InputFormat): Framework | undefined {\r\n if (format === \"jsx\" || format === \"mjml\" || format === \"maizzle\") return format;\r\n return undefined;\r\n}\r\n\r\n/**\r\n * Do source positions refer to the file the user wrote?\r\n *\r\n * Only for plain HTML. JSX, MJML and Maizzle are compiled before analysis, so\r\n * a position would point into generated output — worse than no position at all,\r\n * because it looks authoritative.\r\n */\r\nexport function positionsApply(format: string): boolean {\r\n return format === \"html\";\r\n}\r\n","import pc from \"picocolors\";\r\nimport Table from \"cli-table3\";\r\nimport {\r\n EMAIL_CLIENTS,\r\n type CSSWarning,\r\n} from \"@emailens/engine\";\r\n\r\n/**\r\n * Color-code a score value: green >=90, yellow >=70, red <70.\r\n */\r\nfunction colorScore(score: number): string {\r\n const str = String(score);\r\n if (score >= 90) return pc.green(str);\r\n if (score >= 70) return pc.yellow(str);\r\n return pc.red(str);\r\n}\r\n\r\n/**\r\n * Get severity icon.\r\n */\r\nfunction severityIcon(severity: CSSWarning[\"severity\"]): string {\r\n switch (severity) {\r\n case \"error\": return pc.red(\"✗\");\r\n case \"warning\": return pc.yellow(\"⚠\");\r\n case \"info\": return pc.blue(\"ℹ\");\r\n }\r\n}\r\n\r\n/**\r\n * Get the worst severity from a list of warnings.\r\n */\r\nfunction worstSeverity(warnings: CSSWarning[]): CSSWarning[\"severity\"] {\r\n if (warnings.some((w) => w.severity === \"error\")) return \"error\";\r\n if (warnings.some((w) => w.severity === \"warning\")) return \"warning\";\r\n return \"info\";\r\n}\r\n\r\n/**\r\n * Format issue count with severity summary.\r\n */\r\nfunction formatIssueCount(warnings: CSSWarning[]): string {\r\n const errors = warnings.filter((w) => w.severity === \"error\").length;\r\n const warns = warnings.filter((w) => w.severity === \"warning\").length;\r\n const infos = warnings.filter((w) => w.severity === \"info\").length;\r\n\r\n const parts: string[] = [];\r\n if (errors > 0) parts.push(pc.red(`${errors} err`));\r\n if (warns > 0) parts.push(pc.yellow(`${warns} warn`));\r\n if (infos > 0) parts.push(pc.blue(`${infos} info`));\r\n\r\n return parts.join(\", \") || pc.green(\"none\");\r\n}\r\n\r\n/**\r\n * Print the compatibility score table to terminal.\r\n */\r\nexport function printScoreTable(\r\n scores: Record<string, { score: number; errors: number; warnings: number; info: number }>,\r\n warnings: CSSWarning[],\r\n options?: { quiet?: boolean }\r\n): void {\r\n if (!options?.quiet) {\r\n console.log();\r\n console.log(pc.bold(\" Emailens — Compatibility Report\"));\r\n console.log();\r\n }\r\n\r\n const clientMap = new Map(EMAIL_CLIENTS.map((c) => [c.id, c.name]));\r\n\r\n // Group warnings by client\r\n const warningsByClient = new Map<string, CSSWarning[]>();\r\n for (const w of warnings) {\r\n const existing = warningsByClient.get(w.client) ?? [];\r\n existing.push(w);\r\n warningsByClient.set(w.client, existing);\r\n }\r\n\r\n // Score table\r\n const table = new Table({\r\n head: [pc.bold(\"Client\"), pc.bold(\"Score\"), pc.bold(\"Issues\")],\r\n style: { head: [], border: [] },\r\n });\r\n\r\n const sortedClients = Object.entries(scores).sort(([, a], [, b]) => a.score - b.score);\r\n\r\n for (const [clientId, { score }] of sortedClients) {\r\n const clientWarnings = warningsByClient.get(clientId) ?? [];\r\n table.push([\r\n clientMap.get(clientId) ?? clientId,\r\n colorScore(score),\r\n formatIssueCount(clientWarnings),\r\n ]);\r\n }\r\n\r\n console.log(table.toString());\r\n\r\n // Overall score\r\n const scoreValues = Object.values(scores);\r\n const overall = scoreValues.length > 0\r\n ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\r\n : 0;\r\n\r\n console.log();\r\n console.log(` Overall: ${colorScore(overall)}/100`);\r\n console.log();\r\n}\r\n\r\n/**\r\n * Where the warning fires: the first position, and how many others share it.\r\n * Empty for compiled sources and document-level findings, which have none.\r\n */\r\nfunction formatWhere(w: CSSWarning): string {\r\n if (!w.loc) return \"\";\r\n const more = (w.locs?.length ?? 1) - 1;\r\n const extra = more > 0 ? ` +${more}${w.locsTruncated ? \"+\" : \"\"} more` : \"\";\r\n return pc.dim(` (${w.loc.line}:${w.loc.column}${extra})`);\r\n}\r\n\r\n/**\r\n * Print detailed warnings grouped by client.\r\n */\r\nexport function printWarnings(\r\n warnings: CSSWarning[],\r\n options?: { quiet?: boolean }\r\n): void {\r\n if (warnings.length === 0) {\r\n if (!options?.quiet) {\r\n console.log(pc.green(\" No compatibility issues found.\"));\r\n console.log();\r\n }\r\n return;\r\n }\r\n\r\n // Group by client\r\n const byClient = new Map<string, CSSWarning[]>();\r\n for (const w of warnings) {\r\n const existing = byClient.get(w.client) ?? [];\r\n existing.push(w);\r\n byClient.set(w.client, existing);\r\n }\r\n\r\n const clientMap = new Map(EMAIL_CLIENTS.map((c) => [c.id, c.name]));\r\n\r\n for (const [clientId, clientWarnings] of byClient) {\r\n const clientName = clientMap.get(clientId) ?? clientId;\r\n const worst = worstSeverity(clientWarnings);\r\n console.log(` ${severityIcon(worst)} ${pc.bold(clientName)} (${clientWarnings.length} issue${clientWarnings.length === 1 ? \"\" : \"s\"})`);\r\n\r\n for (const w of clientWarnings) {\r\n const prop = pc.dim(w.property);\r\n console.log(` ${severityIcon(w.severity)} ${prop}${formatWhere(w)} — ${w.message}`);\r\n if (w.suggestion) {\r\n console.log(` ${pc.dim(\"→\")} ${w.suggestion}`);\r\n }\r\n }\r\n console.log();\r\n }\r\n}\r\n","import type { CSSWarning, SourceLocation, TransformResult } from \"@emailens/engine\";\r\n\r\ninterface JsonOutput {\r\n overallScore: number;\r\n scores: Record<string, { score: number; errors: number; warnings: number; info: number }>;\r\n warnings: Array<{\r\n client: string;\r\n property: string;\r\n severity: string;\r\n message: string;\r\n suggestion?: string;\r\n fix?: {\r\n before: string;\r\n after: string;\r\n language: string;\r\n description: string;\r\n };\r\n /** First occurrence in the source. HTML input only. */\r\n loc?: SourceLocation;\r\n /** Every occurrence, in document order. */\r\n locs?: SourceLocation[];\r\n /** `locs` is capped and does not list every occurrence. */\r\n locsTruncated?: boolean;\r\n }>;\r\n transforms?: Array<{\r\n clientId: string;\r\n html: string;\r\n }>;\r\n darkMode?: Record<string, {\r\n html: string;\r\n warningCount: number;\r\n }>;\r\n}\r\n\r\n/**\r\n * Build the JSON output object. Reusable by both printJson() and export command.\r\n */\r\nexport function formatJsonOutput(opts: {\r\n scores: Record<string, { score: number; errors: number; warnings: number; info: number }>;\r\n warnings: CSSWarning[];\r\n transforms?: TransformResult[];\r\n darkMode?: Record<string, { html: string; warnings: CSSWarning[] }>;\r\n}): JsonOutput {\r\n const scoreValues = Object.values(opts.scores);\r\n const overallScore = scoreValues.length > 0\r\n ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\r\n : 0;\r\n\r\n const output: JsonOutput = {\r\n overallScore,\r\n scores: opts.scores,\r\n warnings: opts.warnings.map((w) => ({\r\n client: w.client,\r\n property: w.property,\r\n severity: w.severity,\r\n message: w.message,\r\n suggestion: w.suggestion,\r\n fix: w.fix ? {\r\n before: w.fix.before,\r\n after: w.fix.after,\r\n language: w.fix.language,\r\n description: w.fix.description,\r\n } : undefined,\r\n loc: w.loc,\r\n locs: w.locs,\r\n locsTruncated: w.locsTruncated,\r\n })),\r\n };\r\n\r\n if (opts.transforms) {\r\n output.transforms = opts.transforms.map((t) => ({\r\n clientId: t.clientId,\r\n html: t.html,\r\n }));\r\n }\r\n\r\n if (opts.darkMode) {\r\n output.darkMode = {};\r\n for (const [clientId, dm] of Object.entries(opts.darkMode)) {\r\n output.darkMode[clientId] = {\r\n html: dm.html,\r\n warningCount: dm.warnings.length,\r\n };\r\n }\r\n }\r\n\r\n return output;\r\n}\r\n\r\n/**\r\n * Format analysis/preview results as JSON and print to stdout.\r\n */\r\nexport function printJson(opts: {\r\n scores: Record<string, { score: number; errors: number; warnings: number; info: number }>;\r\n warnings: CSSWarning[];\r\n transforms?: TransformResult[];\r\n darkMode?: Record<string, { html: string; warnings: CSSWarning[] }>;\r\n}): void {\r\n console.log(JSON.stringify(formatJsonOutput(opts), null, 2));\r\n}\r\n","import { defineCommand } from \"citty\";\r\nimport ora from \"ora\";\r\nimport {\r\n transformForAllClients,\r\n analyzeEmail,\r\n generateCompatibilityScore,\r\n simulateDarkMode,\r\n warningsForClient,\r\n CompileError,\r\n type CSSWarning,\r\n} from \"@emailens/engine\";\r\nimport { readInput, resolveClients, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\nimport { printScoreTable, printWarnings } from \"../output/terminal.js\";\r\nimport { printJson } from \"../output/json.js\";\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"preview\",\r\n description: \"Full email preview: transforms, analysis, dark mode, screenshots\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or - for stdin\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n clients: {\r\n type: \"string\",\r\n alias: \"c\",\r\n description: \"Comma-separated client IDs to filter\",\r\n },\r\n \"dark-mode\": {\r\n type: \"boolean\",\r\n alias: \"d\",\r\n description: \"Include dark mode simulation\",\r\n },\r\n screenshots: {\r\n type: \"boolean\",\r\n description: \"Capture screenshots (requires BROWSERLESS_URL)\",\r\n },\r\n out: {\r\n type: \"string\",\r\n alias: \"o\",\r\n description: \"Output directory for screenshots\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Output as JSON\",\r\n },\r\n quiet: {\r\n type: \"boolean\",\r\n alias: \"q\",\r\n description: \"Suppress spinners and decorations\",\r\n },\r\n },\r\n async run({ args }) {\r\n const spinner = (args.quiet || args.json) ? null : ora();\r\n\r\n try {\r\n // Resolve format early so we fail fast on invalid --format\r\n const format = resolveFormat(args.format, args.input);\r\n\r\n // Read input\r\n spinner?.start(\"Reading input...\");\r\n const source = await readInput(args.input);\r\n spinner?.succeed(\"Input read\");\r\n\r\n // Compile (passthrough for html, auto-detects from extension)\r\n const html = await compile(source, format);\r\n\r\n // Resolve clients\r\n const clientIds = resolveClients(args.clients);\r\n const clientSet = new Set(clientIds);\r\n\r\n // Transform\r\n spinner?.start(\"Transforming for all clients...\");\r\n const allTransforms = transformForAllClients(html);\r\n const transforms = allTransforms.filter((t) => clientSet.has(t.clientId));\r\n spinner?.succeed(`Transformed for ${transforms.length} clients`);\r\n\r\n // Analyze\r\n spinner?.start(\"Analyzing compatibility...\");\r\n const framework = toFramework(format);\r\n const warnings = analyzeEmail(html, framework);\r\n const allScores = generateCompatibilityScore(warnings);\r\n\r\n const scores: typeof allScores = {};\r\n for (const [id, data] of Object.entries(allScores)) {\r\n if (clientSet.has(id)) {\r\n scores[id] = data;\r\n }\r\n }\r\n const filteredWarnings = clientIds.flatMap(id => warningsForClient(warnings, id));\r\n spinner?.succeed(\"Analysis complete\");\r\n\r\n // Dark mode\r\n let darkMode: Record<string, { html: string; warnings: CSSWarning[] }> | undefined;\r\n if (args[\"dark-mode\"]) {\r\n spinner?.start(\"Simulating dark mode...\");\r\n darkMode = {};\r\n for (const t of transforms) {\r\n darkMode[t.clientId] = simulateDarkMode(t.html, t.clientId);\r\n }\r\n spinner?.succeed(\"Dark mode simulation complete\");\r\n }\r\n\r\n // Screenshots\r\n if (args.screenshots) {\r\n const outDir = args.out ?? \"./emailens-screenshots\";\r\n spinner?.start(\"Capturing screenshots...\");\r\n try {\r\n const { captureScreenshots } = await import(\"../screenshots.js\");\r\n const results = await captureScreenshots(\r\n transforms,\r\n outDir,\r\n (clientId, index, total) => {\r\n if (spinner) spinner.text = `Capturing screenshots (${index}/${total}: ${clientId})...`;\r\n },\r\n );\r\n spinner?.succeed(`Screenshots saved to ${outDir} (${results.size} files)`);\r\n } catch (err) {\r\n spinner?.warn(`Screenshots skipped: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n // Output\r\n if (args.json) {\r\n printJson({ scores, warnings: filteredWarnings, transforms, darkMode });\r\n } else {\r\n printScoreTable(scores, filteredWarnings, { quiet: args.quiet });\r\n printWarnings(filteredWarnings, { quiet: args.quiet });\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n spinner?.fail(`Compilation failed (${err.phase}): ${err.message}`);\r\n } else {\r\n spinner?.fail((err as Error).message);\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n","import { defineCommand } from \"citty\";\nimport pc from \"picocolors\";\nimport Table from \"cli-table3\";\nimport { EMAIL_CLIENTS } from \"@emailens/engine\";\n\nexport default defineCommand({\n meta: {\n name: \"clients\",\n description: \"List all supported email clients\",\n },\n args: {\n json: {\n type: \"boolean\",\n description: \"Output as JSON\",\n },\n },\n run({ args }) {\n if (args.json) {\n const data = EMAIL_CLIENTS.map((c) => ({\n id: c.id,\n name: c.name,\n category: c.category,\n engine: c.engine,\n darkModeSupport: c.darkModeSupport,\n }));\n console.log(JSON.stringify(data, null, 2));\n return;\n }\n\n console.log();\n console.log(pc.bold(\" Emailens — Supported Email Clients\"));\n console.log();\n\n const table = new Table({\n head: [\n pc.bold(\"ID\"),\n pc.bold(\"Name\"),\n pc.bold(\"Category\"),\n pc.bold(\"Engine\"),\n pc.bold(\"Dark Mode\"),\n ],\n style: { head: [], border: [] },\n });\n\n for (const client of EMAIL_CLIENTS) {\n table.push([\n client.id,\n client.name,\n client.category,\n client.engine,\n client.darkModeSupport ? pc.green(\"Yes\") : pc.dim(\"No\"),\n ]);\n }\n\n console.log(table.toString());\n console.log();\n console.log(pc.dim(` ${EMAIL_CLIENTS.length} clients supported`));\n console.log();\n },\n});\n","import { mkdirSync, writeFileSync } from \"node:fs\";\r\nimport { join, resolve } from \"node:path\";\r\nimport { defineCommand } from \"citty\";\r\nimport ora from \"ora\";\r\nimport {\r\n transformForAllClients,\r\n analyzeEmail,\r\n generateCompatibilityScore,\r\n simulateDarkMode,\r\n warningsForClient,\r\n CompileError,\r\n type CSSWarning,\r\n} from \"@emailens/engine\";\r\n\r\n// toPlainText available in engine >=0.8.6\r\nlet toPlainText: ((html: string) => string) | null = null;\r\ntry {\r\n // eslint-disable-next-line @typescript-eslint/no-require-imports\r\n ({ toPlainText } = require(\"@emailens/engine\"));\r\n} catch {\r\n // older engine version\r\n}\r\nimport { readInput, resolveClients, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\nimport { generateHtmlReport } from \"../output/html-report.js\";\r\nimport { formatJsonOutput } from \"../output/json.js\";\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"export\",\r\n description: \"Export compatibility report as JSON or HTML\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or - for stdin\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n clients: {\r\n type: \"string\",\r\n alias: \"c\",\r\n description: \"Comma-separated client IDs to filter\",\r\n },\r\n \"dark-mode\": {\r\n type: \"boolean\",\r\n alias: \"d\",\r\n description: \"Include dark mode simulation\",\r\n },\r\n screenshots: {\r\n type: \"boolean\",\r\n description: \"Capture screenshots (requires BROWSERLESS_URL)\",\r\n },\r\n out: {\r\n type: \"string\",\r\n alias: \"o\",\r\n description: \"Output directory (default: ./emailens-report)\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Export as JSON instead of HTML\",\r\n },\r\n \"plain-text\": {\r\n type: \"boolean\",\r\n description: \"Also export a plain text version (.txt) for multipart emails\",\r\n },\r\n quiet: {\r\n type: \"boolean\",\r\n alias: \"q\",\r\n description: \"Suppress spinners and decorations\",\r\n },\r\n },\r\n async run({ args }) {\r\n const spinner = args.quiet ? null : ora();\r\n const outDir = resolve(args.out ?? \"./emailens-report\");\r\n\r\n try {\r\n // Resolve format early so we fail fast on invalid --format\r\n const format = resolveFormat(args.format, args.input);\r\n\r\n // Read input\r\n spinner?.start(\"Reading input...\");\r\n const source = await readInput(args.input);\r\n spinner?.succeed(\"Input read\");\r\n\r\n // Compile (passthrough for html, auto-detects from extension)\r\n const html = await compile(source, format);\r\n\r\n // Resolve clients\r\n const clientIds = resolveClients(args.clients);\r\n const clientSet = new Set(clientIds);\r\n\r\n // Transform\r\n spinner?.start(\"Transforming for all clients...\");\r\n const allTransforms = transformForAllClients(html);\r\n const transforms = allTransforms.filter((t) => clientSet.has(t.clientId));\r\n spinner?.succeed(`Transformed for ${transforms.length} clients`);\r\n\r\n // Analyze\r\n spinner?.start(\"Analyzing compatibility...\");\r\n const framework = toFramework(format);\r\n const warnings = analyzeEmail(html, framework);\r\n const allScores = generateCompatibilityScore(warnings);\r\n\r\n const scores: typeof allScores = {};\r\n for (const [id, data] of Object.entries(allScores)) {\r\n if (clientSet.has(id)) {\r\n scores[id] = data;\r\n }\r\n }\r\n const filteredWarnings = clientIds.flatMap(id => warningsForClient(warnings, id));\r\n spinner?.succeed(\"Analysis complete\");\r\n\r\n // Dark mode\r\n let darkMode: Record<string, { html: string; warnings: CSSWarning[] }> | undefined;\r\n if (args[\"dark-mode\"]) {\r\n spinner?.start(\"Simulating dark mode...\");\r\n darkMode = {};\r\n for (const t of transforms) {\r\n darkMode[t.clientId] = simulateDarkMode(t.html, t.clientId);\r\n }\r\n spinner?.succeed(\"Dark mode simulation complete\");\r\n }\r\n\r\n // Screenshots\r\n if (args.screenshots) {\r\n spinner?.start(\"Capturing screenshots...\");\r\n try {\r\n const { captureScreenshots } = await import(\"../screenshots.js\");\r\n const screenshotDir = join(outDir, \"screenshots\");\r\n const results = await captureScreenshots(\r\n transforms,\r\n screenshotDir,\r\n (clientId, index, total) => {\r\n if (spinner) spinner.text = `Capturing screenshots (${index}/${total}: ${clientId})...`;\r\n },\r\n );\r\n spinner?.succeed(`Screenshots saved (${results.size} files)`);\r\n } catch (err) {\r\n spinner?.warn(`Screenshots skipped: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n // Export\r\n mkdirSync(outDir, { recursive: true });\r\n\r\n if (args.json) {\r\n spinner?.start(\"Exporting JSON...\");\r\n const jsonData = formatJsonOutput({ scores, warnings: filteredWarnings, transforms });\r\n const jsonPath = join(outDir, \"report.json\");\r\n writeFileSync(jsonPath, JSON.stringify(jsonData, null, 2));\r\n spinner?.succeed(`JSON report saved to ${jsonPath}`);\r\n } else {\r\n spinner?.start(\"Generating HTML report...\");\r\n const htmlReport = generateHtmlReport({\r\n scores,\r\n warnings: filteredWarnings,\r\n transforms,\r\n darkMode,\r\n originalHtml: html,\r\n });\r\n\r\n const htmlPath = join(outDir, \"report.html\");\r\n writeFileSync(htmlPath, htmlReport);\r\n spinner?.succeed(`HTML report saved to ${htmlPath}`);\r\n }\r\n\r\n // Plain text export\r\n if (args[\"plain-text\"] && toPlainText) {\r\n spinner?.start(\"Generating plain text version...\");\r\n const plainText = toPlainText(html);\r\n const txtPath = join(outDir, \"email.txt\");\r\n writeFileSync(txtPath, plainText);\r\n spinner?.succeed(`Plain text saved to ${txtPath}`);\r\n } else if (args[\"plain-text\"] && !toPlainText) {\r\n spinner?.warn(\"Plain text export requires @emailens/engine >=0.8.6\");\r\n }\r\n\r\n if (!args.quiet) {\r\n console.log();\r\n console.log(` Output: ${outDir}`);\r\n console.log();\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n spinner?.fail(`Compilation failed (${err.phase}): ${err.message}`);\r\n } else {\r\n spinner?.fail((err as Error).message);\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n","import { EMAIL_CLIENTS, type CSSWarning, type TransformResult } from \"@emailens/engine\";\r\n\r\n/**\r\n * Generate a self-contained HTML report file.\r\n */\r\nexport function generateHtmlReport(opts: {\r\n scores: Record<string, { score: number; errors: number; warnings: number; info: number }>;\r\n warnings: CSSWarning[];\r\n transforms?: TransformResult[];\r\n darkMode?: Record<string, { html: string; warnings: CSSWarning[] }>;\r\n originalHtml?: string;\r\n}): string {\r\n const clientMap = new Map(EMAIL_CLIENTS.map((c) => [c.id, c]));\r\n\r\n const scoreValues = Object.values(opts.scores);\r\n const overallScore = scoreValues.length > 0\r\n ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\r\n : 0;\r\n\r\n const scoreColor = (s: number) => s >= 90 ? \"#22c55e\" : s >= 70 ? \"#eab308\" : \"#ef4444\";\r\n\r\n // Build client cards\r\n const clientCards = Object.entries(opts.scores)\r\n .sort(([, a], [, b]) => a.score - b.score)\r\n .map(([clientId, { score, errors, warnings: warns, info }]) => {\r\n const client = clientMap.get(clientId);\r\n const name = client?.name ?? clientId;\r\n const clientWarnings = opts.warnings.filter((w) => w.client === clientId);\r\n const issueCount = errors + warns + info;\r\n\r\n const warningRows = clientWarnings.map((w) => {\r\n const icon = w.severity === \"error\" ? \"&#10007;\" : w.severity === \"warning\" ? \"&#9888;\" : \"&#8505;\";\r\n const color = w.severity === \"error\" ? \"#ef4444\" : w.severity === \"warning\" ? \"#eab308\" : \"#3b82f6\";\r\n return `<div style=\"padding:4px 0;border-bottom:1px solid #f1f1f1\">\r\n <span style=\"color:${color}\">${icon}</span>\r\n <strong>${escapeHtml(w.property)}</strong> — ${escapeHtml(w.message)}\r\n ${w.suggestion ? `<br><span style=\"color:#888;margin-left:20px\">&rarr; ${escapeHtml(w.suggestion)}</span>` : \"\"}\r\n </div>`;\r\n }).join(\"\");\r\n\r\n return `<div style=\"border:1px solid #e5e5e5;border-radius:8px;padding:16px;margin-bottom:16px\">\r\n <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:12px\">\r\n <h3 style=\"margin:0;font-size:16px\">${escapeHtml(name)}</h3>\r\n <span style=\"font-size:24px;font-weight:bold;color:${scoreColor(score)}\">${score}</span>\r\n </div>\r\n <div style=\"font-size:13px;color:#888;margin-bottom:8px\">${client?.engine ?? \"\"} &middot; ${client?.category ?? \"\"} &middot; ${issueCount} issue${issueCount === 1 ? \"\" : \"s\"}</div>\r\n ${clientWarnings.length > 0 ? `<div style=\"font-size:13px\">${warningRows}</div>` : '<div style=\"color:#22c55e;font-size:13px\">No issues</div>'}\r\n </div>`;\r\n }).join(\"\");\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n<meta charset=\"utf-8\">\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n<title>Emailens Compatibility Report</title>\r\n<style>\r\n * { box-sizing: border-box; }\r\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif; margin: 0; padding: 24px; background: #fafafa; color: #333; }\r\n .container { max-width: 900px; margin: 0 auto; }\r\n .header { text-align: center; margin-bottom: 32px; }\r\n .header h1 { margin: 0 0 8px; font-size: 24px; }\r\n .overall { font-size: 48px; font-weight: bold; }\r\n .subtitle { color: #888; font-size: 14px; }\r\n .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 16px; }\r\n .footer { text-align: center; margin-top: 32px; color: #aaa; font-size: 12px; }\r\n</style>\r\n</head>\r\n<body>\r\n<div class=\"container\">\r\n <div class=\"header\">\r\n <h1>Emailens Compatibility Report</h1>\r\n <div class=\"overall\" style=\"color:${scoreColor(overallScore)}\">${overallScore}/100</div>\r\n <div class=\"subtitle\">${Object.keys(opts.scores).length} clients analyzed &middot; ${opts.warnings.length} total issues</div>\r\n </div>\r\n <div class=\"grid\">\r\n ${clientCards}\r\n </div>\r\n <div class=\"footer\">\r\n Generated by <a href=\"https://github.com/emailens/cli\" style=\"color:#888\">@emailens/cli</a>\r\n </div>\r\n</div>\r\n</body>\r\n</html>`;\r\n}\r\n\r\nfunction escapeHtml(str: string): string {\r\n return str\r\n .replace(/&/g, \"&amp;\")\r\n .replace(/</g, \"&lt;\")\r\n .replace(/>/g, \"&gt;\")\r\n .replace(/\"/g, \"&quot;\");\r\n}\r\n","import { defineCommand } from \"citty\";\r\nimport ora from \"ora\";\r\nimport pc from \"picocolors\";\r\nimport {\r\n analyzeEmail,\r\n generateCompatibilityScore,\r\n generateAiFix,\r\n estimateAiFixTokens,\r\n AI_FIX_SYSTEM_PROMPT,\r\n CompileError,\r\n type AiProvider,\r\n} from \"@emailens/engine\";\r\nimport { readInput, resolveClients, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"fix\",\r\n description: \"Generate AI-powered fixes for email compatibility issues\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or - for stdin\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n clients: {\r\n type: \"string\",\r\n alias: \"c\",\r\n description: \"Comma-separated client IDs to scope the fix\",\r\n },\r\n output: {\r\n type: \"string\",\r\n alias: \"o\",\r\n description: \"Write fixed code to file instead of stdout\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Output as JSON (includes token estimates and metadata)\",\r\n },\r\n quiet: {\r\n type: \"boolean\",\r\n alias: \"q\",\r\n description: \"Suppress spinners and decorations\",\r\n },\r\n estimate: {\r\n type: \"boolean\",\r\n description: \"Only show token estimate without calling the AI\",\r\n },\r\n \"max-tokens\": {\r\n type: \"string\",\r\n description: \"Maximum input tokens for the prompt (default: 16000)\",\r\n },\r\n },\r\n async run({ args }) {\r\n const spinner = (args.quiet || args.json) ? null : ora();\r\n\r\n try {\r\n const format = resolveFormat(args.format, args.input);\r\n const framework = toFramework(format);\r\n\r\n // Read and compile input\r\n spinner?.start(\"Reading input...\");\r\n const source = await readInput(args.input);\r\n const html = await compile(source, format);\r\n spinner?.succeed(\"Input read\");\r\n\r\n // Analyze\r\n spinner?.start(\"Analyzing compatibility...\");\r\n const warnings = analyzeEmail(html, framework);\r\n const scores = generateCompatibilityScore(warnings);\r\n spinner?.succeed(`Found ${warnings.length} warnings`);\r\n\r\n if (warnings.length === 0) {\r\n if (args.json) {\r\n console.log(JSON.stringify({ message: \"No issues found\", code: source }, null, 2));\r\n } else {\r\n console.log(pc.green(\"\\n No compatibility issues found — nothing to fix.\\n\"));\r\n }\r\n return;\r\n }\r\n\r\n // Resolve scope\r\n const clientIds = resolveClients(args.clients);\r\n let scope: \"current\" | \"all\";\r\n let selectedClientId: string | undefined;\r\n let scopedWarnings = warnings;\r\n let scopedScores = scores;\r\n\r\n if (args.clients && clientIds.length === 1) {\r\n // Single client: use engine's \"current\" scope\r\n scope = \"current\";\r\n selectedClientId = clientIds[0];\r\n } else if (args.clients && clientIds.length > 1) {\r\n // Multiple clients: pre-filter warnings/scores, then use \"all\" scope\r\n // so the engine processes all of them without needing \"current\" + single ID\r\n const clientSet = new Set(clientIds);\r\n scope = \"all\";\r\n selectedClientId = undefined;\r\n scopedWarnings = warnings.filter((w) => clientSet.has(w.client));\r\n scopedScores = Object.fromEntries(\r\n Object.entries(scores).filter(([id]) => clientSet.has(id)),\r\n );\r\n } else {\r\n // No --clients flag: fix for all clients\r\n scope = \"all\";\r\n selectedClientId = undefined;\r\n }\r\n\r\n // Token estimate\r\n const maxInputTokens = args[\"max-tokens\"] ? parseInt(args[\"max-tokens\"], 10) : 16000;\r\n const estimate = await estimateAiFixTokens({\r\n originalHtml: html,\r\n warnings: scopedWarnings,\r\n scores: scopedScores,\r\n scope,\r\n selectedClientId,\r\n format,\r\n maxInputTokens,\r\n });\r\n\r\n const structuralPct = estimate.warningCount > 0\r\n ? Math.round((estimate.structuralCount / estimate.warningCount) * 100)\r\n : 0;\r\n\r\n if (args.estimate) {\r\n if (args.json) {\r\n console.log(JSON.stringify(estimate, null, 2));\r\n } else {\r\n console.log(`\\n ${pc.bold(\"Token Estimate\")}`);\r\n console.log(` Input tokens: ~${estimate.inputTokens.toLocaleString()}`);\r\n console.log(` Output tokens: ~${estimate.estimatedOutputTokens.toLocaleString()}`);\r\n console.log(` Warnings: ${estimate.warningCount} (${estimate.structuralCount} structural)`);\r\n if (estimate.truncated) {\r\n console.log(` ${pc.yellow(`Truncated: ${estimate.warningsRemoved} low-priority warnings removed`)}`);\r\n }\r\n console.log();\r\n }\r\n return;\r\n }\r\n\r\n // Check for API key\r\n const apiKey = process.env.ANTHROPIC_API_KEY;\r\n if (!apiKey) {\r\n spinner?.fail(\"ANTHROPIC_API_KEY environment variable is required\");\r\n console.error(pc.dim(\"\\n Set it with: export ANTHROPIC_API_KEY=sk-ant-...\\n\"));\r\n process.exit(1);\r\n }\r\n\r\n // Show estimate before calling\r\n if (!args.quiet && !args.json) {\r\n console.log(pc.dim(` ~${estimate.inputTokens.toLocaleString()} input tokens, ${estimate.warningCount} warnings (${structuralPct}% structural)`));\r\n if (estimate.truncated) {\r\n console.log(pc.yellow(` Truncated ${estimate.warningsRemoved} low-priority warnings to fit token limit`));\r\n }\r\n }\r\n\r\n // Call AI\r\n spinner?.start(\"Generating AI fix...\");\r\n\r\n let Anthropic: typeof import(\"@anthropic-ai/sdk\").default;\r\n try {\r\n Anthropic = (await import(\"@anthropic-ai/sdk\")).default;\r\n } catch {\r\n spinner?.fail(\"@anthropic-ai/sdk is required for the fix command\");\r\n console.error(pc.dim(\"\\n Install it with: npm install @anthropic-ai/sdk\\n\"));\r\n process.exit(1);\r\n }\r\n\r\n const anthropic = new Anthropic({ apiKey });\r\n\r\n const provider: AiProvider = async (prompt) => {\r\n const msg = await anthropic.messages.create({\r\n model: \"claude-sonnet-4-6\",\r\n max_tokens: 8192,\r\n system: AI_FIX_SYSTEM_PROMPT,\r\n messages: [{ role: \"user\", content: prompt }],\r\n });\r\n const textBlock = msg.content.find((b: { type: string }) => b.type === \"text\");\r\n return textBlock?.type === \"text\" ? (textBlock as { type: \"text\"; text: string }).text : \"\";\r\n };\r\n\r\n const result = await generateAiFix({\r\n originalHtml: html,\r\n warnings: scopedWarnings,\r\n scores: scopedScores,\r\n scope,\r\n selectedClientId,\r\n format,\r\n maxInputTokens,\r\n provider,\r\n });\r\n\r\n spinner?.succeed(`Fixed ${result.targetedWarnings} warnings (${result.structuralCount} structural)`);\r\n\r\n // Output\r\n if (args.output) {\r\n const { writeFileSync } = await import(\"node:fs\");\r\n writeFileSync(args.output, result.code, \"utf-8\");\r\n if (!args.quiet) {\r\n console.log(pc.green(`\\n Fixed code written to ${args.output}\\n`));\r\n }\r\n } else if (args.json) {\r\n console.log(JSON.stringify({\r\n code: result.code,\r\n targetedWarnings: result.targetedWarnings,\r\n structuralCount: result.structuralCount,\r\n tokenEstimate: result.tokenEstimate,\r\n }, null, 2));\r\n } else {\r\n console.log(\"\\n\" + result.code);\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n spinner?.fail(`Compilation failed (${err.phase}): ${err.message}`);\r\n } else {\r\n spinner?.fail((err as Error).message);\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n","import { defineCommand } from \"citty\";\r\nimport ora from \"ora\";\r\nimport pc from \"picocolors\";\r\nimport Table from \"cli-table3\";\r\nimport {\r\n auditEmail,\r\n CompileError,\r\n type AuditReport,\r\n} from \"@emailens/engine\";\r\nimport { readInput, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\nimport { printScoreTable, printWarnings } from \"../output/terminal.js\";\r\n\r\nconst VALID_SKIPS = new Set([\"spam\", \"links\", \"accessibility\", \"images\", \"compatibility\", \"overflow\", \"visual\"]);\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"audit\",\r\n description: \"Full email audit: compatibility + spam + links + accessibility + images + overflow + visual\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or - for stdin\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Output as JSON\",\r\n },\r\n quiet: {\r\n type: \"boolean\",\r\n alias: \"q\",\r\n description: \"Suppress spinners and decorations\",\r\n },\r\n skip: {\r\n type: \"string\",\r\n description: \"Comma-separated checks to skip: spam,links,accessibility,images,compatibility,overflow,visual\",\r\n },\r\n },\r\n async run({ args }) {\r\n const spinner = (args.quiet || args.json) ? null : ora();\r\n\r\n try {\r\n const format = resolveFormat(args.format, args.input);\r\n\r\n // Read input\r\n spinner?.start(\"Reading input...\");\r\n const source = await readInput(args.input);\r\n spinner?.succeed(\"Input read\");\r\n\r\n // Compile\r\n const html = await compile(source, format);\r\n\r\n // Parse --skip flag\r\n const skip: Array<\"spam\" | \"links\" | \"accessibility\" | \"images\" | \"compatibility\" | \"overflow\" | \"visual\"> = [];\r\n if (args.skip) {\r\n for (const s of args.skip.split(\",\").map((s) => s.trim()).filter(Boolean)) {\r\n if (!VALID_SKIPS.has(s)) {\r\n throw new Error(`Unknown skip value \"${s}\". Valid: ${Array.from(VALID_SKIPS).join(\", \")}`);\r\n }\r\n skip.push(s as typeof skip[number]);\r\n }\r\n }\r\n\r\n // Run audit\r\n spinner?.start(\"Running full audit...\");\r\n const framework = toFramework(format);\r\n const report = auditEmail(html, { framework, skip });\r\n spinner?.succeed(\"Audit complete\");\r\n\r\n // Output\r\n if (args.json) {\r\n console.log(JSON.stringify(report, null, 2));\r\n } else {\r\n // Compatibility section\r\n if (!skip.includes(\"compatibility\")) {\r\n printScoreTable(report.compatibility.scores, report.compatibility.warnings, { quiet: args.quiet });\r\n printWarnings(report.compatibility.warnings, { quiet: args.quiet });\r\n }\r\n\r\n // Quality summary\r\n printQualitySummary(report, skip, { quiet: args.quiet });\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n spinner?.fail(`Compilation failed (${err.phase}): ${err.message}`);\r\n } else {\r\n spinner?.fail((err as Error).message);\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n\r\nfunction printQualitySummary(\r\n report: AuditReport,\r\n skip: string[],\r\n options?: { quiet?: boolean },\r\n): void {\r\n if (!options?.quiet) {\r\n console.log(pc.bold(\" Quality Summary\"));\r\n console.log();\r\n }\r\n\r\n const table = new Table({\r\n head: [pc.bold(\"Check\"), pc.bold(\"Result\")],\r\n style: { head: [], border: [] },\r\n });\r\n\r\n if (!skip.includes(\"spam\")) {\r\n const { score, level, issues } = report.spam;\r\n const color = score >= 90 ? pc.green : score >= 70 ? pc.yellow : pc.red;\r\n table.push([\"Content Hygiene\", `${color(String(score))}/100 (${level}) — ${issues.length} issue${issues.length === 1 ? \"\" : \"s\"}`]);\r\n }\r\n\r\n if (!skip.includes(\"links\")) {\r\n const { totalLinks, issues } = report.links;\r\n const color = issues.length === 0 ? pc.green : pc.yellow;\r\n table.push([\"Links\", `${totalLinks} link${totalLinks === 1 ? \"\" : \"s\"} — ${color(`${issues.length} issue${issues.length === 1 ? \"\" : \"s\"}`)}`]);\r\n }\r\n\r\n if (!skip.includes(\"accessibility\")) {\r\n const { score, issues } = report.accessibility;\r\n const color = score >= 90 ? pc.green : score >= 70 ? pc.yellow : pc.red;\r\n table.push([\"Accessibility\", `${color(String(score))}/100 — ${issues.length} issue${issues.length === 1 ? \"\" : \"s\"}`]);\r\n }\r\n\r\n if (!skip.includes(\"images\")) {\r\n const { total, issues } = report.images;\r\n const color = issues.length === 0 ? pc.green : pc.yellow;\r\n table.push([\"Images\", `${total} image${total === 1 ? \"\" : \"s\"} — ${color(`${issues.length} issue${issues.length === 1 ? \"\" : \"s\"}`)}`]);\r\n }\r\n\r\n if (!skip.includes(\"overflow\")) {\r\n const { issues } = report.overflow;\r\n const color = issues.length === 0 ? pc.green : pc.yellow;\r\n table.push([\"Content Overflow\", color(`${issues.length} issue${issues.length === 1 ? \"\" : \"s\"}`)]);\r\n }\r\n\r\n if (!skip.includes(\"visual\")) {\r\n const { issues } = report.visual;\r\n const color = issues.length === 0 ? pc.green : pc.yellow;\r\n table.push([\"Visual Bugs\", color(`${issues.length} issue${issues.length === 1 ? \"\" : \"s\"}`)]);\r\n }\r\n\r\n console.log(table.toString());\r\n console.log();\r\n}\r\n","import { defineCommand } from \"citty\";\r\nimport pc from \"picocolors\";\r\nimport { positionsApply, readInput, resolveFormat, toFramework } from \"../utils.js\";\r\nimport { compile } from \"@emailens/engine/compile\";\r\nimport {\r\n auditEmail,\r\n CompileError,\r\n MAX_WARNING_LOCATIONS,\r\n type AuditReport,\r\n type SourceLocation,\r\n} from \"@emailens/engine\";\r\nimport { readdir } from \"node:fs/promises\";\r\nimport { resolve, relative } from \"node:path\";\r\n\r\ninterface LintIssue {\r\n severity: \"error\" | \"warning\" | \"info\";\r\n category: string;\r\n rule: string;\r\n message: string;\r\n detail?: string;\r\n /** Where in the file the issue is. HTML sources only — see `positionsApply`. */\r\n loc?: SourceLocation;\r\n /** Every place a CSS property breaks, in document order. */\r\n locs?: SourceLocation[];\r\n /** `locs` is capped and does not list every occurrence. */\r\n locsTruncated?: boolean;\r\n}\r\n\r\ninterface LintFileResult {\r\n file: string;\r\n issues: LintIssue[];\r\n errors: number;\r\n warnings: number;\r\n}\r\n\r\nconst VALID_SKIPS = new Set([\r\n \"spam\", \"links\", \"accessibility\", \"images\",\r\n \"compatibility\", \"inboxPreview\", \"size\", \"templateVariables\", \"overflow\", \"visual\",\r\n]);\r\n\r\nexport default defineCommand({\r\n meta: {\r\n name: \"lint\",\r\n description: \"Lint HTML emails for compatibility, spam, accessibility, and more. CI/CD-friendly with structured exit codes.\",\r\n },\r\n args: {\r\n input: {\r\n type: \"positional\",\r\n description: \"HTML file path or glob pattern\",\r\n required: true,\r\n },\r\n format: {\r\n type: \"string\",\r\n alias: \"f\",\r\n description: \"Input format: html, jsx, mjml, maizzle\",\r\n },\r\n json: {\r\n type: \"boolean\",\r\n description: \"Output as JSON\",\r\n },\r\n failOnWarning: {\r\n type: \"boolean\",\r\n description: \"Exit 2 if warnings found\",\r\n },\r\n skip: {\r\n type: \"string\",\r\n description: \"Comma-separated checks to skip: spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables,overflow,visual\",\r\n },\r\n maxWarnings: {\r\n type: \"string\",\r\n description: \"Fail if more than n warnings\",\r\n },\r\n },\r\n async run({ args }) {\r\n try {\r\n // Parse --skip\r\n const skip: Array<string> = [];\r\n if (args.skip) {\r\n for (const s of args.skip.split(\",\").map((s) => s.trim()).filter(Boolean)) {\r\n if (!VALID_SKIPS.has(s)) {\r\n throw new Error(`Unknown skip value \"${s}\". Valid: ${Array.from(VALID_SKIPS).join(\", \")}`);\r\n }\r\n skip.push(s);\r\n }\r\n }\r\n\r\n const maxWarningsRaw = args.maxWarnings !== undefined ? parseInt(args.maxWarnings, 10) : undefined;\r\n if (maxWarningsRaw !== undefined && isNaN(maxWarningsRaw)) {\r\n throw new Error(`--maxWarnings must be a number, got: ${args.maxWarnings}`);\r\n }\r\n const maxWarnings = maxWarningsRaw ?? Infinity;\r\n const files = await resolveGlob(args.input);\r\n\r\n if (files.length === 0) {\r\n if (args.json) {\r\n console.log(JSON.stringify({ files: [], totalErrors: 0, totalWarnings: 0 }));\r\n } else {\r\n console.error(pc.red(`No files found matching: ${args.input}`));\r\n }\r\n process.exit(1);\r\n }\r\n\r\n const results: LintFileResult[] = [];\r\n let totalErrors = 0;\r\n let totalWarnings = 0;\r\n\r\n for (const file of files) {\r\n const format = resolveFormat(args.format, file);\r\n const source = await readInput(file);\r\n const html = await compile(source, format);\r\n const framework = toFramework(format);\r\n\r\n // Positions describe the HTML that was analyzed. For jsx/mjml/maizzle\r\n // that is compiled output whose lines have nothing to do with the file\r\n // the user wrote, so we only ask for — and only report — positions when\r\n // the source IS the analyzed HTML.\r\n const positions = positionsApply(format);\r\n\r\n const report = auditEmail(html, {\r\n framework,\r\n skip: skip as AuditSkipType[],\r\n positions,\r\n });\r\n\r\n const issues = flattenToLintIssues(report, skip);\r\n const errors = issues.filter((i) => i.severity === \"error\").length;\r\n const warnings = issues.filter((i) => i.severity === \"warning\").length;\r\n\r\n results.push({ file: relative(process.cwd(), file), issues, errors, warnings });\r\n totalErrors += errors;\r\n totalWarnings += warnings;\r\n }\r\n\r\n // Output\r\n if (args.json) {\r\n console.log(JSON.stringify({ files: results, totalErrors, totalWarnings }, null, 2));\r\n } else {\r\n for (const result of results) {\r\n console.log(pc.underline(result.file));\r\n if (result.issues.length === 0) {\r\n console.log(` ${pc.green(\"pass\")} No issues found`);\r\n } else {\r\n // Only reserve the position column when this file has positions to\r\n // show — compiled sources never do.\r\n const width = result.issues.reduce(\r\n (w, i) => (i.loc ? Math.max(w, `${i.loc.line}:${i.loc.column}`.length) : w),\r\n 0,\r\n );\r\n for (const issue of result.issues) {\r\n const sev = issue.severity === \"error\"\r\n ? pc.red(\"error\")\r\n : issue.severity === \"warning\"\r\n ? pc.yellow(\"warn \")\r\n : pc.blue(\"info \");\r\n const pos = width > 0\r\n ? `${pc.dim((issue.loc ? `${issue.loc.line}:${issue.loc.column}` : \"\").padStart(width))} `\r\n : \"\";\r\n console.log(` ${sev} ${pos}${pc.dim(issue.category.padEnd(18))} ${pc.bold(issue.rule.padEnd(22))} ${issue.message}`);\r\n }\r\n }\r\n console.log();\r\n }\r\n\r\n const summary = [\r\n `${files.length} file${files.length === 1 ? \"\" : \"s\"}`,\r\n totalErrors > 0 ? pc.red(`${totalErrors} error${totalErrors === 1 ? \"\" : \"s\"}`) : null,\r\n totalWarnings > 0 ? pc.yellow(`${totalWarnings} warning${totalWarnings === 1 ? \"\" : \"s\"}`) : null,\r\n totalErrors === 0 && totalWarnings === 0 ? pc.green(\"all clean\") : null,\r\n ].filter(Boolean).join(\" | \");\r\n\r\n console.log(summary);\r\n }\r\n\r\n // Exit codes\r\n if (totalErrors > 0) {\r\n process.exit(1);\r\n }\r\n if (args.failOnWarning && totalWarnings > 0) {\r\n if (!args.json) {\r\n console.error(pc.yellow(`\\nFailed: ${totalWarnings} warning${totalWarnings === 1 ? \"\" : \"s\"} found (--failOnWarning)`));\r\n }\r\n process.exit(2);\r\n }\r\n if (Number.isFinite(maxWarnings) && totalWarnings > maxWarnings) {\r\n if (!args.json) {\r\n console.error(pc.red(`\\nExceeded max warnings: ${totalWarnings} > ${maxWarnings}`));\r\n }\r\n process.exit(2);\r\n }\r\n } catch (err) {\r\n if (err instanceof CompileError) {\r\n console.error(pc.red(`Compilation failed (${err.phase}): ${err.message}`));\r\n } else {\r\n console.error(pc.red((err as Error).message));\r\n }\r\n process.exit(1);\r\n }\r\n },\r\n});\r\n\r\ntype AuditSkipType = \"spam\" | \"links\" | \"accessibility\" | \"images\" | \"compatibility\" | \"inboxPreview\" | \"size\" | \"templateVariables\" | \"overflow\" | \"visual\";\r\n\r\n\r\n/**\r\n * Resolve a file path or simple glob pattern to an array of files.\r\n * Supports basic * glob in the last path segment.\r\n */\r\nasync function resolveGlob(pattern: string): Promise<string[]> {\r\n // If it's a direct file, just return it\r\n if (!pattern.includes(\"*\")) {\r\n return [resolve(pattern)];\r\n }\r\n\r\n // Recursive glob is not supported — provide clear error\r\n if (pattern.includes(\"**\")) {\r\n throw new Error(\"Recursive glob (**) is not supported. Use a single-level wildcard (e.g., src/*.html) or list files explicitly.\");\r\n }\r\n\r\n // Simple glob: split into dir + glob pattern\r\n const lastSep = Math.max(pattern.lastIndexOf(\"/\"), pattern.lastIndexOf(\"\\\\\"));\r\n const dir = lastSep >= 0 ? pattern.slice(0, lastSep) : \".\";\r\n const glob = lastSep >= 0 ? pattern.slice(lastSep + 1) : pattern;\r\n\r\n // Convert glob to regex (only support * wildcard)\r\n const regex = new RegExp(\"^\" + glob.replace(/\\*/g, \".*\") + \"$\");\r\n\r\n const entries = await readdir(resolve(dir), { withFileTypes: true });\r\n return entries\r\n .filter((e) => e.isFile() && regex.test(e.name))\r\n .map((e) => resolve(dir, e.name))\r\n .sort();\r\n}\r\n\r\n/**\r\n * Add occurrences we haven't already recorded, keeping document order.\r\n *\r\n * Grouping unions several engine warnings, each already capped, so the cap has\r\n * to be applied again here or a document with many selector shapes produces an\r\n * unbounded list. Returns true when it bit, so the issue can say so.\r\n */\r\nfunction mergeLocs(into: SourceLocation[], from: SourceLocation[] | undefined): boolean {\r\n if (!from) return false;\r\n let truncated = false;\r\n for (const loc of from) {\r\n if (into.some((l) => l.offset === loc.offset)) continue;\r\n if (into.length >= MAX_WARNING_LOCATIONS) {\r\n truncated = true;\r\n break;\r\n }\r\n into.push(loc);\r\n }\r\n into.sort((a, b) => a.offset - b.offset);\r\n return truncated;\r\n}\r\n\r\n/**\r\n * Flatten an AuditReport into a unified LintIssue array.\r\n */\r\nfunction flattenToLintIssues(report: AuditReport, skip: string[]): LintIssue[] {\r\n const issues: LintIssue[] = [];\r\n\r\n // CSS compatibility warnings → group by property (deduplicate across clients)\r\n if (!skip.includes(\"compatibility\")) {\r\n const seenProps = new Map<string, {\r\n severity: \"error\" | \"warning\" | \"info\";\r\n clients: string[];\r\n message: string;\r\n loc?: SourceLocation;\r\n locs: SourceLocation[];\r\n locsTruncated?: boolean;\r\n }>();\r\n\r\n for (const w of report.compatibility.warnings) {\r\n const key = `${w.property}\\0${w.severity}`;\r\n const existing = seenProps.get(key);\r\n if (existing) {\r\n existing.clients.push(w.client);\r\n // Clients repeat the same finding, but selector groups don't: `div` and\r\n // `span` breaking one property are separate warnings, so union their\r\n // occurrences to get every place it actually breaks.\r\n existing.loc ??= w.loc;\r\n if (mergeLocs(existing.locs, w.locs)) existing.locsTruncated = true;\r\n if (w.locsTruncated) existing.locsTruncated = true;\r\n } else {\r\n seenProps.set(key, {\r\n severity: w.severity,\r\n clients: [w.client],\r\n message: w.message,\r\n loc: w.loc,\r\n locs: (w.locs ?? []).slice(0, MAX_WARNING_LOCATIONS),\r\n ...(w.locsTruncated ? { locsTruncated: true } : {}),\r\n });\r\n }\r\n }\r\n\r\n for (const [key, val] of seenProps) {\r\n const property = key.split(\"\\0\")[0];\r\n issues.push({\r\n severity: val.severity,\r\n category: val.clients.slice(0, 3).join(\",\") + (val.clients.length > 3 ? `+${val.clients.length - 3}` : \"\"),\r\n rule: property,\r\n message: val.message,\r\n ...(val.loc ? { loc: val.loc } : {}),\r\n ...(val.locs.length ? { locs: val.locs } : {}),\r\n ...(val.locsTruncated ? { locsTruncated: true } : {}),\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"spam\")) {\r\n for (const issue of report.spam.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"spam\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n detail: issue.detail,\r\n ...(issue.loc ? { loc: issue.loc } : {}),\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"links\")) {\r\n for (const issue of report.links.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"links\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n ...(issue.loc ? { loc: issue.loc } : {}),\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"accessibility\")) {\r\n for (const issue of report.accessibility.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"accessibility\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n ...(issue.loc ? { loc: issue.loc } : {}),\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"images\")) {\r\n for (const issue of report.images.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"images\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n ...(issue.loc ? { loc: issue.loc } : {}),\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"inboxPreview\")) {\r\n for (const issue of report.inboxPreview.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"inboxPreview\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n ...(issue.loc ? { loc: issue.loc } : {}),\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"size\")) {\r\n for (const issue of report.size.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"size\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n ...(issue.loc ? { loc: issue.loc } : {}),\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"templateVariables\")) {\r\n for (const issue of report.templateVariables.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"templateVars\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n ...(issue.loc ? { loc: issue.loc } : {}),\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"overflow\")) {\r\n for (const issue of report.overflow.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"overflow\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n ...(issue.loc ? { loc: issue.loc } : {}),\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"visual\")) {\r\n for (const issue of report.visual.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"visual\",\r\n rule: issue.rule,\r\n message: issue.message,\r\n ...(issue.loc ? { loc: issue.loc } : {}),\r\n });\r\n }\r\n }\r\n\r\n // Sort: errors first, then warnings, then info\r\n const order = { error: 0, warning: 1, info: 2 };\r\n issues.sort((a, b) => order[a.severity] - order[b.severity]);\r\n\r\n return issues;\r\n}\r\n"],"mappings":"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","names":["defineCommand","EMAIL_CLIENTS","resolve","EMAIL_CLIENTS","defineCommand","ora","analyzeEmail","generateCompatibilityScore","warningsForClient","CompileError","compile","defineCommand","ora","compile","analyzeEmail","generateCompatibilityScore","warningsForClient","CompileError","defineCommand","pc","Table","EMAIL_CLIENTS","defineCommand","ora","transformForAllClients","analyzeEmail","generateCompatibilityScore","simulateDarkMode","warningsForClient","CompileError","compile","EMAIL_CLIENTS","defineCommand","ora","compile","transformForAllClients","analyzeEmail","generateCompatibilityScore","warningsForClient","simulateDarkMode","CompileError","defineCommand","ora","pc","analyzeEmail","generateCompatibilityScore","CompileError","compile","defineCommand","ora","analyzeEmail","generateCompatibilityScore","pc","writeFileSync","CompileError","defineCommand","ora","pc","Table","CompileError","compile","defineCommand","ora","compile","s","CompileError","pc","Table","defineCommand","pc","compile","auditEmail","CompileError","resolve","VALID_SKIPS","defineCommand","s","pc","defineCommand"]}
package/package.json CHANGED
@@ -1,72 +1,72 @@
1
- {
2
- "name": "@emailens/cli",
3
- "version": "0.3.5",
4
- "description": "CLI tool for email compatibility analysis — analyze, preview, and export email rendering across 15 email clients.",
5
- "type": "module",
6
- "bin": {
7
- "emailens": "./dist/index.js"
8
- },
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js"
13
- }
14
- },
15
- "main": "./dist/index.js",
16
- "types": "./dist/index.d.ts",
17
- "files": [
18
- "dist",
19
- "LICENSE"
20
- ],
21
- "scripts": {
22
- "build": "tsup",
23
- "dev": "tsup --watch",
24
- "start": "bun run dist/index.js",
25
- "typecheck": "tsc --noEmit",
26
- "test": "bun test",
27
- "prepublishOnly": "bun run build"
28
- },
29
- "dependencies": {
30
- "@emailens/engine": "^0.9.3",
31
- "citty": "^0.1.6",
32
- "cli-table3": "^0.6.5",
33
- "ora": "^8.2.0",
34
- "picocolors": "^1.1.1"
35
- },
36
- "optionalDependencies": {
37
- "@anthropic-ai/sdk": "^0.52.0",
38
- "playwright-core": "^1.52.0"
39
- },
40
- "devDependencies": {
41
- "@types/bun": "^1.3.14",
42
- "@types/mjml": "^4.7.4",
43
- "@types/node": "^25.3.0",
44
- "bun-types": "^1.3.9",
45
- "tsup": "^8.4.0",
46
- "typescript": "^5.9.3"
47
- },
48
- "keywords": [
49
- "email",
50
- "html-email",
51
- "css",
52
- "compatibility",
53
- "cli",
54
- "gmail",
55
- "outlook",
56
- "apple-mail",
57
- "email-testing",
58
- "dark-mode"
59
- ],
60
- "license": "MIT",
61
- "repository": {
62
- "type": "git",
63
- "url": "git+https://github.com/emailens/cli.git"
64
- },
65
- "engines": {
66
- "node": ">=18"
67
- },
68
- "homepage": "https://github.com/emailens/cli#readme",
69
- "bugs": {
70
- "url": "https://github.com/emailens/cli/issues"
71
- }
72
- }
1
+ {
2
+ "name": "@emailens/cli",
3
+ "version": "0.4.1",
4
+ "description": "CLI tool for email compatibility analysis \u2014 analyze, preview, and export email rendering across 21 email clients.",
5
+ "type": "module",
6
+ "bin": {
7
+ "emailens": "./dist/index.js"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "files": [
18
+ "dist",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "start": "bun run dist/index.js",
25
+ "typecheck": "tsc --noEmit",
26
+ "test": "bun test",
27
+ "prepublishOnly": "bun run build"
28
+ },
29
+ "dependencies": {
30
+ "@emailens/engine": "^0.10.2",
31
+ "citty": "^0.1.6",
32
+ "cli-table3": "^0.6.5",
33
+ "ora": "^8.2.0",
34
+ "picocolors": "^1.1.1"
35
+ },
36
+ "optionalDependencies": {
37
+ "@anthropic-ai/sdk": "^0.52.0",
38
+ "playwright-core": "^1.52.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/bun": "^1.3.14",
42
+ "@types/mjml": "^4.7.4",
43
+ "@types/node": "^25.3.0",
44
+ "bun-types": "^1.3.9",
45
+ "tsup": "^8.4.0",
46
+ "typescript": "^5.9.3"
47
+ },
48
+ "keywords": [
49
+ "email",
50
+ "html-email",
51
+ "css",
52
+ "compatibility",
53
+ "cli",
54
+ "gmail",
55
+ "outlook",
56
+ "apple-mail",
57
+ "email-testing",
58
+ "dark-mode"
59
+ ],
60
+ "license": "MIT",
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "git+https://github.com/emailens/cli.git"
64
+ },
65
+ "engines": {
66
+ "node": ">=18"
67
+ },
68
+ "homepage": "https://github.com/emailens/cli#readme",
69
+ "bugs": {
70
+ "url": "https://github.com/emailens/cli/issues"
71
+ }
72
+ }