@emailens/cli 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -13
- package/dist/index.js +158 -19
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -7,19 +7,22 @@
|
|
|
7
7
|
|
|
8
8
|
**The rendering linter for email, in your terminal**
|
|
9
9
|
|
|
10
|
+
[](https://github.com/emailens/cli/actions/workflows/ci.yml)
|
|
10
11
|
[](https://www.npmjs.com/package/@emailens/cli)
|
|
11
12
|
[](https://github.com/emailens/mcp)
|
|
12
13
|
[](https://github.com/emailens/cli/stargazers)
|
|
13
14
|
|
|
14
15
|
</div>
|
|
15
16
|
|
|
16
|
-
CLI tool for email compatibility analysis
|
|
17
|
+
CLI tool for email compatibility analysis; preview how your emails render across 21 email clients (Gmail, Outlook, Apple Mail, Yahoo, Samsung, Thunderbird, HEY, Proton Mail, AOL, Fastmail, Superhuman).
|
|
18
|
+
|
|
19
|
+
Point it at **HTML, MJML, Maizzle or React Email**. The format is detected from the file extension, the template is compiled with your project's own compiler, and what gets linted is the HTML your readers actually receive.
|
|
17
20
|
|
|
18
21
|
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
22
|
|
|
20
23
|

|
|
21
24
|
|
|
22
|
-
> **Prefer AI?** Use the [MCP server](https://github.com/emailens/mcp)
|
|
25
|
+
> **Prefer AI?** Use the [MCP server](https://github.com/emailens/mcp); same engine, works with Claude, Cursor, and any MCP client.
|
|
23
26
|
|
|
24
27
|
## Install
|
|
25
28
|
|
|
@@ -51,11 +54,11 @@ problem:
|
|
|
51
54
|
|
|
52
55
|
```
|
|
53
56
|
⚠ Outlook (New) (1 issue)
|
|
54
|
-
⚠ border-radius (4:8 +2 more)
|
|
57
|
+
⚠ border-radius (4:8 +2 more): Outlook (New) does not support "border-radius".
|
|
55
58
|
```
|
|
56
59
|
|
|
57
60
|
`--json` carries the full `loc` / `locs` for each warning. As with `lint`,
|
|
58
|
-
positions are reported for HTML input only
|
|
61
|
+
positions are reported for HTML input only, see below.
|
|
59
62
|
|
|
60
63
|
### `emailens preview <file>`
|
|
61
64
|
|
|
@@ -106,7 +109,7 @@ cat email.html | emailens fix - --format jsx # Pipe from stdin
|
|
|
106
109
|
|
|
107
110
|
### `emailens lint <file|glob>`
|
|
108
111
|
|
|
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.
|
|
112
|
+
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, dark-mode and mobile contrast, design consistency) into a unified issue list.
|
|
110
113
|
|
|
111
114
|
```bash
|
|
112
115
|
emailens lint email.html
|
|
@@ -122,13 +125,13 @@ emailens lint email.html --max-warnings 5
|
|
|
122
125
|
| `--format` | `-f` | Input format: `html`, `jsx`, `mjml`, `maizzle` |
|
|
123
126
|
| `--json` | | Output as JSON |
|
|
124
127
|
| `--fail-on-warning` | | Exit 2 if warnings found |
|
|
125
|
-
| `--skip` | | Comma-separated checks to skip: `spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables,overflow,visual` |
|
|
128
|
+
| `--skip` | | Comma-separated checks to skip: `spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables,overflow,visual,darkContrast,mobileContrast,design` |
|
|
126
129
|
| `--max-warnings` | | Fail if more than n warnings |
|
|
127
130
|
|
|
128
131
|
**Exit codes:**
|
|
129
|
-
- `0
|
|
130
|
-
- `1
|
|
131
|
-
- `2
|
|
132
|
+
- `0`: clean
|
|
133
|
+
- `1`: errors found
|
|
134
|
+
- `2`: warnings only (with `--fail-on-warning` or `--max-warnings` exceeded)
|
|
132
135
|
|
|
133
136
|
**Output format:**
|
|
134
137
|
|
|
@@ -146,8 +149,8 @@ src/emails/newsletter.html
|
|
|
146
149
|
Issues that belong to a specific place in the file carry a `line:col`; findings
|
|
147
150
|
about the document as a whole (spam signals, Gmail clipping, inbox preview) have
|
|
148
151
|
no position and leave the column blank. With `--json`, each issue carries a
|
|
149
|
-
`loc` object instead
|
|
150
|
-
`length`
|
|
152
|
+
`loc` object instead (`line`, `column`, `endLine`, `endColumn`, `offset`,
|
|
153
|
+
`length`) for editors, annotations, and agents that need to point at or edit
|
|
151
154
|
the exact source.
|
|
152
155
|
|
|
153
156
|
One property can break in many places, so CSS issues also carry `locs`: every
|
|
@@ -184,7 +187,42 @@ jobs:
|
|
|
184
187
|
run: npx -y @emailens/cli lint 'emails/**/*.{html,tsx,mjml}' --fail-on-warning
|
|
185
188
|
```
|
|
186
189
|
|
|
187
|
-
For React Email / MJML / Maizzle source files, the CLI auto-detects the format from the extension. Want full preview reports (with screenshots and shareable links) on every PR? Use the [Emailens GitHub Action](https://github.com/marketplace/actions/emailens-email-preview-check) instead
|
|
190
|
+
For React Email / MJML / Maizzle source files, the CLI auto-detects the format from the extension. Want full preview reports (with screenshots and shareable links) on every PR? Use the [Emailens GitHub Action](https://github.com/marketplace/actions/emailens-email-preview-check) instead; it wraps the same engine.
|
|
191
|
+
|
|
192
|
+
### `.emailensrc`
|
|
193
|
+
|
|
194
|
+
The project file the [VS Code extension](https://github.com/emailens/vscode)
|
|
195
|
+
already reads, so the editor and CI agree about what matters. Put it at the
|
|
196
|
+
repo root, or use an `emailens` key in `package.json`:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"skip": ["spam"],
|
|
201
|
+
"rules": {
|
|
202
|
+
"border-radius": "error",
|
|
203
|
+
"font-size": "off"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**`rules`** sets the severity of one rule, keyed by the code `lint` prints: a
|
|
209
|
+
CSS property (`border-radius`) or a rule id (`insecure-link`). `off` drops it
|
|
210
|
+
entirely. Promoting a rule to `error` makes it exit 1, which is the point:
|
|
211
|
+
without this the only control is `--skip`, and a team that cares about one
|
|
212
|
+
Outlook property has to keep the whole compatibility check at warning level.
|
|
213
|
+
|
|
214
|
+
A rule demoted in the editor that still fails the build is the worst of both
|
|
215
|
+
worlds, and this is the file that stops that happening.
|
|
216
|
+
|
|
217
|
+
**Precedence.** A command-line flag wins over the file; it is an explicit
|
|
218
|
+
choice for one invocation. In the editor it is the other way round: the repo's
|
|
219
|
+
file wins over personal settings, because those are ambient and the file is
|
|
220
|
+
the team's.
|
|
221
|
+
|
|
222
|
+
A malformed file is linted without rather than fatal. A severity that is not
|
|
223
|
+
one of `error`, `warning`, `info`, `off` is named on stderr and ignored, so a
|
|
224
|
+
typo cannot quietly leave a rule on that you believe is off. Warnings go to
|
|
225
|
+
stderr, so `lint --json | jq` stays parseable.
|
|
188
226
|
|
|
189
227
|
### `emailens clients`
|
|
190
228
|
|
|
@@ -242,7 +280,7 @@ emailens analyze template.mjml # Auto-detected as MJML
|
|
|
242
280
|
emailens preview email.html --format maizzle # Explicit format
|
|
243
281
|
```
|
|
244
282
|
|
|
245
|
-
Framework compilers are optional peer dependencies
|
|
283
|
+
Framework compilers are optional peer dependencies; install only what you need:
|
|
246
284
|
|
|
247
285
|
```bash
|
|
248
286
|
npm install sucrase react @react-email/components @react-email/render # For JSX
|
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.
|
|
16
|
-
description: "CLI tool for email compatibility analysis
|
|
15
|
+
version: "0.5.0",
|
|
16
|
+
description: "CLI tool for email compatibility analysis: lint, preview and export HTML, MJML, Maizzle and React 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.
|
|
42
|
+
"@emailens/engine": "^0.11.0",
|
|
43
43
|
citty: "^0.1.6",
|
|
44
44
|
"cli-table3": "^0.6.5",
|
|
45
45
|
ora: "^8.2.0",
|
|
@@ -67,7 +67,10 @@ var package_default = {
|
|
|
67
67
|
"outlook",
|
|
68
68
|
"apple-mail",
|
|
69
69
|
"email-testing",
|
|
70
|
-
"dark-mode"
|
|
70
|
+
"dark-mode",
|
|
71
|
+
"mjml",
|
|
72
|
+
"maizzle",
|
|
73
|
+
"react-email"
|
|
71
74
|
],
|
|
72
75
|
license: "MIT",
|
|
73
76
|
repository: {
|
|
@@ -87,7 +90,7 @@ var package_default = {
|
|
|
87
90
|
var meta = {
|
|
88
91
|
name: "emailens",
|
|
89
92
|
version: package_default.version,
|
|
90
|
-
description: `Email compatibility analysis CLI
|
|
93
|
+
description: `Email compatibility analysis CLI; preview how HTML emails render across ${EMAIL_CLIENTS.length} email clients.`
|
|
91
94
|
};
|
|
92
95
|
|
|
93
96
|
// src/commands/analyze.ts
|
|
@@ -215,7 +218,7 @@ function formatIssueCount(warnings) {
|
|
|
215
218
|
function printScoreTable(scores, warnings, options) {
|
|
216
219
|
if (!options?.quiet) {
|
|
217
220
|
console.log();
|
|
218
|
-
console.log(pc.bold(" Emailens
|
|
221
|
+
console.log(pc.bold(" Emailens: Compatibility Report"));
|
|
219
222
|
console.log();
|
|
220
223
|
}
|
|
221
224
|
const clientMap = new Map(EMAIL_CLIENTS3.map((c) => [c.id, c.name]));
|
|
@@ -272,7 +275,7 @@ function printWarnings(warnings, options) {
|
|
|
272
275
|
console.log(` ${severityIcon(worst)} ${pc.bold(clientName)} (${clientWarnings.length} issue${clientWarnings.length === 1 ? "" : "s"})`);
|
|
273
276
|
for (const w of clientWarnings) {
|
|
274
277
|
const prop = pc.dim(w.property);
|
|
275
|
-
console.log(` ${severityIcon(w.severity)} ${prop}${formatWhere(w)}
|
|
278
|
+
console.log(` ${severityIcon(w.severity)} ${prop}${formatWhere(w)}: ${w.message}`);
|
|
276
279
|
if (w.suggestion) {
|
|
277
280
|
console.log(` ${pc.dim("\u2192")} ${w.suggestion}`);
|
|
278
281
|
}
|
|
@@ -552,7 +555,7 @@ var clients_default = defineCommand3({
|
|
|
552
555
|
return;
|
|
553
556
|
}
|
|
554
557
|
console.log();
|
|
555
|
-
console.log(pc2.bold(" Emailens
|
|
558
|
+
console.log(pc2.bold(" Emailens: Supported Email Clients"));
|
|
556
559
|
console.log();
|
|
557
560
|
const table = new Table2({
|
|
558
561
|
head: [
|
|
@@ -612,7 +615,7 @@ function generateHtmlReport(opts) {
|
|
|
612
615
|
const color = w.severity === "error" ? "#ef4444" : w.severity === "warning" ? "#eab308" : "#3b82f6";
|
|
613
616
|
return `<div style="padding:4px 0;border-bottom:1px solid #f1f1f1">
|
|
614
617
|
<span style="color:${color}">${icon}</span>
|
|
615
|
-
<strong>${escapeHtml(w.property)}</strong
|
|
618
|
+
<strong>${escapeHtml(w.property)}</strong>, ${escapeHtml(w.message)}
|
|
616
619
|
${w.suggestion ? `<br><span style="color:#888;margin-left:20px">→ ${escapeHtml(w.suggestion)}</span>` : ""}
|
|
617
620
|
</div>`;
|
|
618
621
|
}).join("");
|
|
@@ -891,7 +894,7 @@ var fix_default = defineCommand5({
|
|
|
891
894
|
if (args.json) {
|
|
892
895
|
console.log(JSON.stringify({ message: "No issues found", code: source }, null, 2));
|
|
893
896
|
} else {
|
|
894
|
-
console.log(pc3.green("\n No compatibility issues found
|
|
897
|
+
console.log(pc3.green("\n No compatibility issues found, nothing to fix.\n"));
|
|
895
898
|
}
|
|
896
899
|
return;
|
|
897
900
|
}
|
|
@@ -1024,11 +1027,11 @@ import {
|
|
|
1024
1027
|
CompileError as CompileError5
|
|
1025
1028
|
} from "@emailens/engine";
|
|
1026
1029
|
import { compile as compile5 } from "@emailens/engine/compile";
|
|
1027
|
-
var VALID_SKIPS = /* @__PURE__ */ new Set(["spam", "links", "accessibility", "images", "compatibility", "overflow", "visual"]);
|
|
1030
|
+
var VALID_SKIPS = /* @__PURE__ */ new Set(["spam", "links", "accessibility", "images", "compatibility", "overflow", "visual", "darkContrast", "mobileContrast", "design"]);
|
|
1028
1031
|
var audit_default = defineCommand6({
|
|
1029
1032
|
meta: {
|
|
1030
1033
|
name: "audit",
|
|
1031
|
-
description: "Full email audit: compatibility + spam + links + accessibility + images + overflow + visual"
|
|
1034
|
+
description: "Full email audit: compatibility + spam + links + accessibility + images + overflow + visual + contrast + design"
|
|
1032
1035
|
},
|
|
1033
1036
|
args: {
|
|
1034
1037
|
input: {
|
|
@@ -1052,7 +1055,7 @@ var audit_default = defineCommand6({
|
|
|
1052
1055
|
},
|
|
1053
1056
|
skip: {
|
|
1054
1057
|
type: "string",
|
|
1055
|
-
description: "Comma-separated checks to skip: spam,links,accessibility,images,compatibility,overflow,visual"
|
|
1058
|
+
description: "Comma-separated checks to skip: spam,links,accessibility,images,compatibility,overflow,visual,darkContrast,mobileContrast,design"
|
|
1056
1059
|
}
|
|
1057
1060
|
},
|
|
1058
1061
|
async run({ args }) {
|
|
@@ -1107,22 +1110,22 @@ function printQualitySummary(report, skip, options) {
|
|
|
1107
1110
|
if (!skip.includes("spam")) {
|
|
1108
1111
|
const { score, level, issues } = report.spam;
|
|
1109
1112
|
const color = score >= 90 ? pc4.green : score >= 70 ? pc4.yellow : pc4.red;
|
|
1110
|
-
table.push(["Content Hygiene", `${color(String(score))}/100 (${level})
|
|
1113
|
+
table.push(["Content Hygiene", `${color(String(score))}/100 (${level}), ${issues.length} issue${issues.length === 1 ? "" : "s"}`]);
|
|
1111
1114
|
}
|
|
1112
1115
|
if (!skip.includes("links")) {
|
|
1113
1116
|
const { totalLinks, issues } = report.links;
|
|
1114
1117
|
const color = issues.length === 0 ? pc4.green : pc4.yellow;
|
|
1115
|
-
table.push(["Links", `${totalLinks} link${totalLinks === 1 ? "" : "s"}
|
|
1118
|
+
table.push(["Links", `${totalLinks} link${totalLinks === 1 ? "" : "s"}, ${color(`${issues.length} issue${issues.length === 1 ? "" : "s"}`)}`]);
|
|
1116
1119
|
}
|
|
1117
1120
|
if (!skip.includes("accessibility")) {
|
|
1118
1121
|
const { score, issues } = report.accessibility;
|
|
1119
1122
|
const color = score >= 90 ? pc4.green : score >= 70 ? pc4.yellow : pc4.red;
|
|
1120
|
-
table.push(["Accessibility", `${color(String(score))}/100
|
|
1123
|
+
table.push(["Accessibility", `${color(String(score))}/100, ${issues.length} issue${issues.length === 1 ? "" : "s"}`]);
|
|
1121
1124
|
}
|
|
1122
1125
|
if (!skip.includes("images")) {
|
|
1123
1126
|
const { total, issues } = report.images;
|
|
1124
1127
|
const color = issues.length === 0 ? pc4.green : pc4.yellow;
|
|
1125
|
-
table.push(["Images", `${total} image${total === 1 ? "" : "s"}
|
|
1128
|
+
table.push(["Images", `${total} image${total === 1 ? "" : "s"}, ${color(`${issues.length} issue${issues.length === 1 ? "" : "s"}`)}`]);
|
|
1126
1129
|
}
|
|
1127
1130
|
if (!skip.includes("overflow")) {
|
|
1128
1131
|
const { issues } = report.overflow;
|
|
@@ -1134,6 +1137,21 @@ function printQualitySummary(report, skip, options) {
|
|
|
1134
1137
|
const color = issues.length === 0 ? pc4.green : pc4.yellow;
|
|
1135
1138
|
table.push(["Visual Bugs", color(`${issues.length} issue${issues.length === 1 ? "" : "s"}`)]);
|
|
1136
1139
|
}
|
|
1140
|
+
if (!skip.includes("darkContrast")) {
|
|
1141
|
+
const n = report.darkContrast.length;
|
|
1142
|
+
const color = n === 0 ? pc4.green : pc4.red;
|
|
1143
|
+
table.push(["Dark Mode Contrast", color(`${n} issue${n === 1 ? "" : "s"}`)]);
|
|
1144
|
+
}
|
|
1145
|
+
if (!skip.includes("mobileContrast")) {
|
|
1146
|
+
const n = report.mobileContrast.length;
|
|
1147
|
+
const color = n === 0 ? pc4.green : pc4.red;
|
|
1148
|
+
table.push(["Mobile Contrast", color(`${n} issue${n === 1 ? "" : "s"}`)]);
|
|
1149
|
+
}
|
|
1150
|
+
if (!skip.includes("design")) {
|
|
1151
|
+
const { issues } = report.design;
|
|
1152
|
+
const color = issues.length === 0 ? pc4.green : pc4.yellow;
|
|
1153
|
+
table.push(["Design Consistency", color(`${issues.length} issue${issues.length === 1 ? "" : "s"}`)]);
|
|
1154
|
+
}
|
|
1137
1155
|
console.log(table.toString());
|
|
1138
1156
|
console.log();
|
|
1139
1157
|
}
|
|
@@ -1147,6 +1165,75 @@ import {
|
|
|
1147
1165
|
CompileError as CompileError6,
|
|
1148
1166
|
MAX_WARNING_LOCATIONS
|
|
1149
1167
|
} from "@emailens/engine";
|
|
1168
|
+
|
|
1169
|
+
// src/project-config.ts
|
|
1170
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
1171
|
+
import { dirname, join as join2, parse } from "path";
|
|
1172
|
+
var SEVERITIES = ["error", "warning", "info", "off"];
|
|
1173
|
+
function parseRules(value) {
|
|
1174
|
+
const rules = {};
|
|
1175
|
+
const invalid = [];
|
|
1176
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { rules, invalid };
|
|
1177
|
+
for (const [rule, severity] of Object.entries(value)) {
|
|
1178
|
+
if (typeof severity === "string" && SEVERITIES.includes(severity)) {
|
|
1179
|
+
rules[rule] = severity;
|
|
1180
|
+
} else {
|
|
1181
|
+
invalid.push(`${rule}: ${JSON.stringify(severity)}`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return { rules, invalid };
|
|
1185
|
+
}
|
|
1186
|
+
function parseProjectConfig(source, from) {
|
|
1187
|
+
let parsed;
|
|
1188
|
+
try {
|
|
1189
|
+
parsed = JSON.parse(source);
|
|
1190
|
+
} catch {
|
|
1191
|
+
return void 0;
|
|
1192
|
+
}
|
|
1193
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
1194
|
+
const body = from === "package.json" ? parsed.emailens : parsed;
|
|
1195
|
+
if (!body || typeof body !== "object") return void 0;
|
|
1196
|
+
const config = { invalid: [] };
|
|
1197
|
+
const skip = body.skip;
|
|
1198
|
+
if (Array.isArray(skip) && skip.every((s) => typeof s === "string")) config.skip = skip;
|
|
1199
|
+
const { rules, invalid } = parseRules(body.rules);
|
|
1200
|
+
if (Object.keys(rules).length) config.rules = rules;
|
|
1201
|
+
config.invalid = invalid;
|
|
1202
|
+
return config.skip || config.rules || invalid.length ? config : void 0;
|
|
1203
|
+
}
|
|
1204
|
+
function loadProjectConfig(from = process.cwd()) {
|
|
1205
|
+
const root = parse(from).root;
|
|
1206
|
+
for (let dir = from; ; dir = dirname(dir)) {
|
|
1207
|
+
for (const [name, kind] of [
|
|
1208
|
+
[".emailensrc", "emailensrc"],
|
|
1209
|
+
["package.json", "package.json"]
|
|
1210
|
+
]) {
|
|
1211
|
+
const path = join2(dir, name);
|
|
1212
|
+
if (!existsSync(path)) continue;
|
|
1213
|
+
let body;
|
|
1214
|
+
try {
|
|
1215
|
+
body = readFileSync2(path, "utf-8");
|
|
1216
|
+
} catch {
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
const config = parseProjectConfig(body, kind);
|
|
1220
|
+
if (config) return { ...config, source: path };
|
|
1221
|
+
}
|
|
1222
|
+
if (dir === root) return void 0;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
function applySeverities(issues, rules) {
|
|
1226
|
+
if (!rules || !Object.keys(rules).length) return issues;
|
|
1227
|
+
const out = [];
|
|
1228
|
+
for (const issue of issues) {
|
|
1229
|
+
const severity = rules[issue.rule];
|
|
1230
|
+
if (severity === "off") continue;
|
|
1231
|
+
out.push(severity ? { ...issue, severity } : issue);
|
|
1232
|
+
}
|
|
1233
|
+
return out;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// src/commands/lint.ts
|
|
1150
1237
|
import { readdir } from "fs/promises";
|
|
1151
1238
|
import { resolve as resolve2, relative } from "path";
|
|
1152
1239
|
var VALID_SKIPS2 = /* @__PURE__ */ new Set([
|
|
@@ -1159,7 +1246,10 @@ var VALID_SKIPS2 = /* @__PURE__ */ new Set([
|
|
|
1159
1246
|
"size",
|
|
1160
1247
|
"templateVariables",
|
|
1161
1248
|
"overflow",
|
|
1162
|
-
"visual"
|
|
1249
|
+
"visual",
|
|
1250
|
+
"darkContrast",
|
|
1251
|
+
"mobileContrast",
|
|
1252
|
+
"design"
|
|
1163
1253
|
]);
|
|
1164
1254
|
var lint_default = defineCommand7({
|
|
1165
1255
|
meta: {
|
|
@@ -1196,7 +1286,23 @@ var lint_default = defineCommand7({
|
|
|
1196
1286
|
},
|
|
1197
1287
|
async run({ args }) {
|
|
1198
1288
|
try {
|
|
1289
|
+
const project = loadProjectConfig();
|
|
1290
|
+
if (project?.invalid.length && !args.json) {
|
|
1291
|
+
console.error(
|
|
1292
|
+
pc5.yellow(
|
|
1293
|
+
`${project.source}: ignoring ${project.invalid.join(", ")}, a severity must be "error", "warning", "info" or "off".`
|
|
1294
|
+
)
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1199
1297
|
const skip = [];
|
|
1298
|
+
if (!args.skip) {
|
|
1299
|
+
for (const s of project?.skip ?? []) {
|
|
1300
|
+
if (VALID_SKIPS2.has(s)) skip.push(s);
|
|
1301
|
+
else if (!args.json) {
|
|
1302
|
+
console.error(pc5.yellow(`${project?.source}: unknown skip value "${s}", ignoring.`));
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1200
1306
|
if (args.skip) {
|
|
1201
1307
|
for (const s of args.skip.split(",").map((s2) => s2.trim()).filter(Boolean)) {
|
|
1202
1308
|
if (!VALID_SKIPS2.has(s)) {
|
|
@@ -1233,7 +1339,7 @@ var lint_default = defineCommand7({
|
|
|
1233
1339
|
skip,
|
|
1234
1340
|
positions
|
|
1235
1341
|
});
|
|
1236
|
-
const issues = flattenToLintIssues(report, skip);
|
|
1342
|
+
const issues = applySeverities(flattenToLintIssues(report, skip), project?.rules);
|
|
1237
1343
|
const errors = issues.filter((i) => i.severity === "error").length;
|
|
1238
1344
|
const warnings = issues.filter((i) => i.severity === "warning").length;
|
|
1239
1345
|
results.push({ file: relative(process.cwd(), file), issues, errors, warnings });
|
|
@@ -1459,6 +1565,39 @@ function flattenToLintIssues(report, skip) {
|
|
|
1459
1565
|
});
|
|
1460
1566
|
}
|
|
1461
1567
|
}
|
|
1568
|
+
if (!skip.includes("darkContrast")) {
|
|
1569
|
+
for (const issue of report.darkContrast) {
|
|
1570
|
+
issues.push({
|
|
1571
|
+
severity: issue.severity,
|
|
1572
|
+
category: "darkContrast",
|
|
1573
|
+
rule: issue.rule,
|
|
1574
|
+
message: issue.message.replace(/^Dark mode: /, ""),
|
|
1575
|
+
...issue.loc ? { loc: issue.loc } : {}
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
if (!skip.includes("mobileContrast")) {
|
|
1580
|
+
for (const issue of report.mobileContrast) {
|
|
1581
|
+
issues.push({
|
|
1582
|
+
severity: issue.severity,
|
|
1583
|
+
category: "mobileContrast",
|
|
1584
|
+
rule: issue.rule,
|
|
1585
|
+
message: issue.message.replace(/^At mobile width: /, ""),
|
|
1586
|
+
...issue.loc ? { loc: issue.loc } : {}
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
if (!skip.includes("design")) {
|
|
1591
|
+
for (const issue of report.design.issues) {
|
|
1592
|
+
issues.push({
|
|
1593
|
+
severity: issue.severity,
|
|
1594
|
+
category: "design",
|
|
1595
|
+
rule: issue.rule,
|
|
1596
|
+
message: issue.message,
|
|
1597
|
+
detail: issue.detail
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1462
1601
|
const order = { error: 0, warning: 1, info: 2 };
|
|
1463
1602
|
issues.sort((a, b) => order[a.severity] - order[b.severity]);
|
|
1464
1603
|
return issues;
|
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","{\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\" ? \"✗\" : w.severity === \"warning\" ? \"⚠\" : \"ℹ\";\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\">→ ${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 ?? \"\"} · ${client?.category ?? \"\"} · ${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 · ${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, \"&\")\r\n .replace(/</g, \"<\")\r\n .replace(/>/g, \">\")\r\n .replace(/\"/g, \""\");\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":";;;;;;AAAA,SAAS,iBAAAA,gBAAe,eAAe;;;ACAvC,SAAS,qBAAqB;;;ACA9B;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,MAAQ;AAAA,EACR,KAAO;AAAA,IACL,UAAY;AAAA,EACd;AAAA,EACA,SAAW;AAAA,IACT,KAAK;AAAA,MACH,OAAS;AAAA,MACT,QAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,MAAQ;AAAA,EACR,OAAS;AAAA,EACT,OAAS;AAAA,IACP;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,KAAO;AAAA,IACP,OAAS;AAAA,IACT,WAAa;AAAA,IACb,MAAQ;AAAA,IACR,gBAAkB;AAAA,EACpB;AAAA,EACA,cAAgB;AAAA,IACd,oBAAoB;AAAA,IACpB,OAAS;AAAA,IACT,cAAc;AAAA,IACd,KAAO;AAAA,IACP,YAAc;AAAA,EAChB;AAAA,EACA,sBAAwB;AAAA,IACtB,qBAAqB;AAAA,IACrB,mBAAmB;AAAA,EACrB;AAAA,EACA,iBAAmB;AAAA,IACjB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,eAAe;AAAA,IACf,aAAa;AAAA,IACb,MAAQ;AAAA,IACR,YAAc;AAAA,EAChB;AAAA,EACA,UAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAW;AAAA,EACX,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,UAAY;AAAA,EACZ,MAAQ;AAAA,IACN,KAAO;AAAA,EACT;AACF;;;AD3DO,IAAM,OAAO;AAAA,EAClB,MAAM;AAAA,EACN,SAAS,gBAAI;AAAA,EACb,aAAa,iFAA4E,cAAc,MAAM;AAC/G;;;AEhBA,SAAS,qBAAqB;AAC9B,OAAO,SAAS;AAChB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACPP,SAAS,oBAAoB;AAC7B,SAAS,iBAAAC,sBAAuD;AAChE,SAAS,oBAAoB;AAE7B,IAAM,gBAAgB,oBAAI,IAAI,CAAC,QAAQ,OAAO,QAAQ,SAAS,CAAC;AAMhE,eAAsB,UAAU,aAAsC;AACpE,MAAI,gBAAgB,KAAK;AAEvB,QAAI;AACF,aAAO,aAAa,cAAc,OAAO;AAAA,IAC3C,QAAQ;AAAA,IAER;AAGA,WAAO,IAAI,QAAgB,CAACC,UAAS,WAAW;AAC9C,YAAM,SAAmB,CAAC;AAC1B,cAAQ,MAAM,OAAO;AACrB,cAAQ,MAAM,GAAG,QAAQ,CAAC,UAAU,OAAO,KAAK,OAAO,KAAK,KAAK,CAAC,CAAC;AACnE,cAAQ,MAAM,GAAG,OAAO,MAAM;AAC5B,cAAM,OAAO,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO;AACnD,YAAI,CAAC,KAAK,KAAK,GAAG;AAChB,iBAAO,IAAI,MAAM,iEAAiE,CAAC;AAAA,QACrF,OAAO;AACL,UAAAA,SAAQ,IAAI;AAAA,QACd;AAAA,MACF,CAAC;AACD,cAAQ,MAAM,GAAG,SAAS,MAAM;AAC9B,eAAO,IAAI,MAAM,iEAAiE,CAAC;AAAA,MACrF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAEA,MAAI;AACF,WAAO,aAAa,aAAa,OAAO;AAAA,EAC1C,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,UAAM,IAAI,MAAM,wBAAwB,WAAW,MAAM,OAAO,EAAE;AAAA,EACpE;AACF;AAKO,SAAS,eAAe,KAAwB;AACrD,QAAM,WAAW,IAAI,IAAID,eAAc,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAEvD,MAAI,CAAC,KAAK;AACR,WAAOA,eAAc,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,EACtC;AAEA,QAAM,YAAY,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AACpE,QAAM,UAAU,UAAU,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;AAE1D,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI;AAAA,MACR,yBAAyB,QAAQ,KAAK,IAAI,CAAC;AAAA,aAC7B,MAAM,KAAK,QAAQ,EAAE,KAAK,IAAI,CAAC;AAAA,IAC/C;AAAA,EACF;AAEA,SAAO;AACT;AAOO,SAAS,cAAc,YAAqB,UAAgC;AACjF,MAAI,YAAY;AACd,QAAI,CAAC,cAAc,IAAI,UAAU,GAAG;AAClC,YAAM,IAAI;AAAA,QACR,mBAAmB,UAAU,qBAAqB,MAAM,KAAK,aAAa,EAAE,KAAK,IAAI,CAAC;AAAA,MACxF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAGA,MAAI,YAAY,aAAa,KAAK;AAChC,WAAO,aAAa,QAAQ;AAAA,EAC9B;AAEA,SAAO;AACT;AAKO,SAAS,YAAY,QAA4C;AACtE,MAAI,WAAW,SAAS,WAAW,UAAU,WAAW,UAAW,QAAO;AAC1E,SAAO;AACT;AASO,SAAS,eAAe,QAAyB;AACtD,SAAO,WAAW;AACpB;;;ADpGA,SAAS,eAAe;;;AETxB,OAAO,QAAQ;AACf,OAAO,WAAW;AAClB;AAAA,EACE,iBAAAE;AAAA,OAEK;AAKP,SAAS,WAAW,OAAuB;AACzC,QAAM,MAAM,OAAO,KAAK;AACxB,MAAI,SAAS,GAAI,QAAO,GAAG,MAAM,GAAG;AACpC,MAAI,SAAS,GAAI,QAAO,GAAG,OAAO,GAAG;AACrC,SAAO,GAAG,IAAI,GAAG;AACnB;AAKA,SAAS,aAAa,UAA0C;AAC9D,UAAQ,UAAU;AAAA,IAChB,KAAK;AAAS,aAAO,GAAG,IAAI,QAAG;AAAA,IAC/B,KAAK;AAAW,aAAO,GAAG,OAAO,QAAG;AAAA,IACpC,KAAK;AAAQ,aAAO,GAAG,KAAK,QAAG;AAAA,EACjC;AACF;AAKA,SAAS,cAAc,UAAgD;AACrE,MAAI,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,OAAO,EAAG,QAAO;AACzD,MAAI,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,SAAS,EAAG,QAAO;AAC3D,SAAO;AACT;AAKA,SAAS,iBAAiB,UAAgC;AACxD,QAAM,SAAS,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO,EAAE;AAC9D,QAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,SAAS,EAAE;AAC/D,QAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE;AAE5D,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAS,EAAG,OAAM,KAAK,GAAG,IAAI,GAAG,MAAM,MAAM,CAAC;AAClD,MAAI,QAAQ,EAAG,OAAM,KAAK,GAAG,OAAO,GAAG,KAAK,OAAO,CAAC;AACpD,MAAI,QAAQ,EAAG,OAAM,KAAK,GAAG,KAAK,GAAG,KAAK,OAAO,CAAC;AAElD,SAAO,MAAM,KAAK,IAAI,KAAK,GAAG,MAAM,MAAM;AAC5C;AAKO,SAAS,gBACd,QACA,UACA,SACM;AACN,MAAI,CAAC,SAAS,OAAO;AACnB,YAAQ,IAAI;AACZ,YAAQ,IAAI,GAAG,KAAK,wCAAmC,CAAC;AACxD,YAAQ,IAAI;AAAA,EACd;AAEA,QAAM,YAAY,IAAI,IAAIA,eAAc,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAGlE,QAAM,mBAAmB,oBAAI,IAA0B;AACvD,aAAW,KAAK,UAAU;AACxB,UAAM,WAAW,iBAAiB,IAAI,EAAE,MAAM,KAAK,CAAC;AACpD,aAAS,KAAK,CAAC;AACf,qBAAiB,IAAI,EAAE,QAAQ,QAAQ;AAAA,EACzC;AAGA,QAAM,QAAQ,IAAI,MAAM;AAAA,IACtB,MAAM,CAAC,GAAG,KAAK,QAAQ,GAAG,GAAG,KAAK,OAAO,GAAG,GAAG,KAAK,QAAQ,CAAC;AAAA,IAC7D,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,EAChC,CAAC;AAED,QAAM,gBAAgB,OAAO,QAAQ,MAAM,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK;AAErF,aAAW,CAAC,UAAU,EAAE,MAAM,CAAC,KAAK,eAAe;AACjD,UAAM,iBAAiB,iBAAiB,IAAI,QAAQ,KAAK,CAAC;AAC1D,UAAM,KAAK;AAAA,MACT,UAAU,IAAI,QAAQ,KAAK;AAAA,MAC3B,WAAW,KAAK;AAAA,MAChB,iBAAiB,cAAc;AAAA,IACjC,CAAC;AAAA,EACH;AAEA,UAAQ,IAAI,MAAM,SAAS,CAAC;AAG5B,QAAM,cAAc,OAAO,OAAO,MAAM;AACxC,QAAM,UAAU,YAAY,SAAS,IACjC,KAAK,MAAM,YAAY,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC,IAAI,YAAY,MAAM,IAC5E;AAEJ,UAAQ,IAAI;AACZ,UAAQ,IAAI,cAAc,WAAW,OAAO,CAAC,MAAM;AACnD,UAAQ,IAAI;AACd;AAMA,SAAS,YAAY,GAAuB;AAC1C,MAAI,CAAC,EAAE,IAAK,QAAO;AACnB,QAAM,QAAQ,EAAE,MAAM,UAAU,KAAK;AACrC,QAAM,QAAQ,OAAO,IAAI,KAAK,IAAI,GAAG,EAAE,gBAAgB,MAAM,EAAE,UAAU;AACzE,SAAO,GAAG,IAAI,KAAK,EAAE,IAAI,IAAI,IAAI,EAAE,IAAI,MAAM,GAAG,KAAK,GAAG;AAC1D;AAKO,SAAS,cACd,UACA,SACM;AACN,MAAI,SAAS,WAAW,GAAG;AACzB,QAAI,CAAC,SAAS,OAAO;AACnB,cAAQ,IAAI,GAAG,MAAM,kCAAkC,CAAC;AACxD,cAAQ,IAAI;AAAA,IACd;AACA;AAAA,EACF;AAGA,QAAM,WAAW,oBAAI,IAA0B;AAC/C,aAAW,KAAK,UAAU;AACxB,UAAM,WAAW,SAAS,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,aAAS,KAAK,CAAC;AACf,aAAS,IAAI,EAAE,QAAQ,QAAQ;AAAA,EACjC;AAEA,QAAM,YAAY,IAAI,IAAIA,eAAc,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAElE,aAAW,CAAC,UAAU,cAAc,KAAK,UAAU;AACjD,UAAM,aAAa,UAAU,IAAI,QAAQ,KAAK;AAC9C,UAAM,QAAQ,cAAc,cAAc;AAC1C,YAAQ,IAAI,KAAK,aAAa,KAAK,CAAC,IAAI,GAAG,KAAK,UAAU,CAAC,KAAK,eAAe,MAAM,SAAS,eAAe,WAAW,IAAI,KAAK,GAAG,GAAG;AAEvI,eAAW,KAAK,gBAAgB;AAC9B,YAAM,OAAO,GAAG,IAAI,EAAE,QAAQ;AAC9B,cAAQ,IAAI,OAAO,aAAa,EAAE,QAAQ,CAAC,IAAI,IAAI,GAAG,YAAY,CAAC,CAAC,WAAM,EAAE,OAAO,EAAE;AACrF,UAAI,EAAE,YAAY;AAChB,gBAAQ,IAAI,SAAS,GAAG,IAAI,QAAG,CAAC,IAAI,EAAE,UAAU,EAAE;AAAA,MACpD;AAAA,IACF;AACA,YAAQ,IAAI;AAAA,EACd;AACF;;;ACxHO,SAAS,iBAAiB,MAKlB;AACb,QAAM,cAAc,OAAO,OAAO,KAAK,MAAM;AAC7C,QAAM,eAAe,YAAY,SAAS,IACtC,KAAK,MAAM,YAAY,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC,IAAI,YAAY,MAAM,IAC5E;AAEJ,QAAM,SAAqB;AAAA,IACzB;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,UAAU,KAAK,SAAS,IAAI,CAAC,OAAO;AAAA,MAClC,QAAQ,EAAE;AAAA,MACV,UAAU,EAAE;AAAA,MACZ,UAAU,EAAE;AAAA,MACZ,SAAS,EAAE;AAAA,MACX,YAAY,EAAE;AAAA,MACd,KAAK,EAAE,MAAM;AAAA,QACX,QAAQ,EAAE,IAAI;AAAA,QACd,OAAO,EAAE,IAAI;AAAA,QACb,UAAU,EAAE,IAAI;AAAA,QAChB,aAAa,EAAE,IAAI;AAAA,MACrB,IAAI;AAAA,MACJ,KAAK,EAAE;AAAA,MACP,MAAM,EAAE;AAAA,MACR,eAAe,EAAE;AAAA,IACnB,EAAE;AAAA,EACJ;AAEA,MAAI,KAAK,YAAY;AACnB,WAAO,aAAa,KAAK,WAAW,IAAI,CAAC,OAAO;AAAA,MAC9C,UAAU,EAAE;AAAA,MACZ,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACJ;AAEA,MAAI,KAAK,UAAU;AACjB,WAAO,WAAW,CAAC;AACnB,eAAW,CAAC,UAAU,EAAE,KAAK,OAAO,QAAQ,KAAK,QAAQ,GAAG;AAC1D,aAAO,SAAS,QAAQ,IAAI;AAAA,QAC1B,MAAM,GAAG;AAAA,QACT,cAAc,GAAG,SAAS;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,UAAU,MAKjB;AACP,UAAQ,IAAI,KAAK,UAAU,iBAAiB,IAAI,GAAG,MAAM,CAAC,CAAC;AAC7D;;;AHtFA,IAAO,kBAAQ,cAAc;AAAA,EAC3B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAElB,UAAM,UAAW,KAAK,SAAS,KAAK,OAAQ,OAAO,IAAI;AAEvD,QAAI;AAEF,YAAM,SAAS,cAAc,KAAK,QAAQ,KAAK,KAAK;AAGpD,eAAS,MAAM,kBAAkB;AACjC,YAAM,SAAS,MAAM,UAAU,KAAK,KAAK;AACzC,eAAS,QAAQ,YAAY;AAG7B,YAAM,OAAO,MAAM,QAAQ,QAAQ,MAAM;AAGzC,YAAM,YAAY,eAAe,KAAK,OAAO;AAG7C,eAAS,MAAM,4BAA4B;AAC3C,YAAM,YAAY,YAAY,MAAM;AAGpC,YAAM,WAAW,aAAa,MAAM,WAAW,EAAE,WAAW,eAAe,MAAM,EAAE,CAAC;AACpF,YAAM,YAAY,2BAA2B,QAAQ;AAGrD,YAAM,YAAY,IAAI,IAAI,SAAS;AACnC,YAAM,SAA2B,CAAC;AAClC,iBAAW,CAAC,IAAI,IAAI,KAAK,OAAO,QAAQ,SAAS,GAAG;AAClD,YAAI,UAAU,IAAI,EAAE,GAAG;AACrB,iBAAO,EAAE,IAAI;AAAA,QACf;AAAA,MACF;AAGA,YAAM,mBAAmB,UAAU,QAAQ,QAAM,kBAAkB,UAAU,EAAE,CAAC;AAEhF,eAAS,QAAQ,mBAAmB;AAGpC,UAAI,KAAK,MAAM;AACb,kBAAU,EAAE,QAAQ,UAAU,iBAAiB,CAAC;AAAA,MAClD,OAAO;AACL,wBAAgB,QAAQ,kBAAkB,EAAE,OAAO,KAAK,MAAM,CAAC;AAC/D,sBAAc,kBAAkB,EAAE,OAAO,KAAK,MAAM,CAAC;AAAA,MACvD;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAe,cAAc;AAC/B,iBAAS,KAAK,uBAAuB,IAAI,KAAK,MAAM,IAAI,OAAO,EAAE;AAAA,MACnE,OAAO;AACL,iBAAS,KAAM,IAAc,OAAO;AAAA,MACtC;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF,CAAC;;;AIrGD,SAAS,iBAAAC,sBAAqB;AAC9B,OAAOC,UAAS;AAChB;AAAA,EACE;AAAA,EACA,gBAAAC;AAAA,EACA,8BAAAC;AAAA,EACA;AAAA,EACA,qBAAAC;AAAA,EACA,gBAAAC;AAAA,OAEK;AAEP,SAAS,WAAAC,gBAAe;AAIxB,IAAO,kBAAQC,eAAc;AAAA,EAC3B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,UAAW,KAAK,SAAS,KAAK,OAAQ,OAAOC,KAAI;AAEvD,QAAI;AAEF,YAAM,SAAS,cAAc,KAAK,QAAQ,KAAK,KAAK;AAGpD,eAAS,MAAM,kBAAkB;AACjC,YAAM,SAAS,MAAM,UAAU,KAAK,KAAK;AACzC,eAAS,QAAQ,YAAY;AAG7B,YAAM,OAAO,MAAMC,SAAQ,QAAQ,MAAM;AAGzC,YAAM,YAAY,eAAe,KAAK,OAAO;AAC7C,YAAM,YAAY,IAAI,IAAI,SAAS;AAGnC,eAAS,MAAM,iCAAiC;AAChD,YAAM,gBAAgB,uBAAuB,IAAI;AACjD,YAAM,aAAa,cAAc,OAAO,CAAC,MAAM,UAAU,IAAI,EAAE,QAAQ,CAAC;AACxE,eAAS,QAAQ,mBAAmB,WAAW,MAAM,UAAU;AAG/D,eAAS,MAAM,4BAA4B;AAC3C,YAAM,YAAY,YAAY,MAAM;AACpC,YAAM,WAAWC,cAAa,MAAM,SAAS;AAC7C,YAAM,YAAYC,4BAA2B,QAAQ;AAErD,YAAM,SAA2B,CAAC;AAClC,iBAAW,CAAC,IAAI,IAAI,KAAK,OAAO,QAAQ,SAAS,GAAG;AAClD,YAAI,UAAU,IAAI,EAAE,GAAG;AACrB,iBAAO,EAAE,IAAI;AAAA,QACf;AAAA,MACF;AACA,YAAM,mBAAmB,UAAU,QAAQ,QAAMC,mBAAkB,UAAU,EAAE,CAAC;AAChF,eAAS,QAAQ,mBAAmB;AAGpC,UAAI;AACJ,UAAI,KAAK,WAAW,GAAG;AACrB,iBAAS,MAAM,yBAAyB;AACxC,mBAAW,CAAC;AACZ,mBAAW,KAAK,YAAY;AAC1B,mBAAS,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,EAAE,QAAQ;AAAA,QAC5D;AACA,iBAAS,QAAQ,+BAA+B;AAAA,MAClD;AAGA,UAAI,KAAK,aAAa;AACpB,cAAM,SAAS,KAAK,OAAO;AAC3B,iBAAS,MAAM,0BAA0B;AACzC,YAAI;AACF,gBAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,2BAAmB;AAC/D,gBAAM,UAAU,MAAM;AAAA,YACpB;AAAA,YACA;AAAA,YACA,CAAC,UAAU,OAAO,UAAU;AAC1B,kBAAI,QAAS,SAAQ,OAAO,0BAA0B,KAAK,IAAI,KAAK,KAAK,QAAQ;AAAA,YACnF;AAAA,UACF;AACA,mBAAS,QAAQ,wBAAwB,MAAM,KAAK,QAAQ,IAAI,SAAS;AAAA,QAC3E,SAAS,KAAK;AACZ,mBAAS,KAAK,wBAAyB,IAAc,OAAO,EAAE;AAAA,QAChE;AAAA,MACF;AAGA,UAAI,KAAK,MAAM;AACb,kBAAU,EAAE,QAAQ,UAAU,kBAAkB,YAAY,SAAS,CAAC;AAAA,MACxE,OAAO;AACL,wBAAgB,QAAQ,kBAAkB,EAAE,OAAO,KAAK,MAAM,CAAC;AAC/D,sBAAc,kBAAkB,EAAE,OAAO,KAAK,MAAM,CAAC;AAAA,MACvD;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAeC,eAAc;AAC/B,iBAAS,KAAK,uBAAuB,IAAI,KAAK,MAAM,IAAI,OAAO,EAAE;AAAA,MACnE,OAAO;AACL,iBAAS,KAAM,IAAc,OAAO;AAAA,MACtC;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF,CAAC;;;ACnJD,SAAS,iBAAAC,sBAAqB;AAC9B,OAAOC,SAAQ;AACf,OAAOC,YAAW;AAClB,SAAS,iBAAAC,sBAAqB;AAE9B,IAAO,kBAAQH,eAAc;AAAA,EAC3B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,IAAI,EAAE,KAAK,GAAG;AACZ,QAAI,KAAK,MAAM;AACb,YAAM,OAAOG,eAAc,IAAI,CAAC,OAAO;AAAA,QACrC,IAAI,EAAE;AAAA,QACN,MAAM,EAAE;AAAA,QACR,UAAU,EAAE;AAAA,QACZ,QAAQ,EAAE;AAAA,QACV,iBAAiB,EAAE;AAAA,MACrB,EAAE;AACF,cAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AACzC;AAAA,IACF;AAEA,YAAQ,IAAI;AACZ,YAAQ,IAAIF,IAAG,KAAK,2CAAsC,CAAC;AAC3D,YAAQ,IAAI;AAEZ,UAAM,QAAQ,IAAIC,OAAM;AAAA,MACtB,MAAM;AAAA,QACJD,IAAG,KAAK,IAAI;AAAA,QACZA,IAAG,KAAK,MAAM;AAAA,QACdA,IAAG,KAAK,UAAU;AAAA,QAClBA,IAAG,KAAK,QAAQ;AAAA,QAChBA,IAAG,KAAK,WAAW;AAAA,MACrB;AAAA,MACA,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,IAChC,CAAC;AAED,eAAW,UAAUE,gBAAe;AAClC,YAAM,KAAK;AAAA,QACT,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO,kBAAkBF,IAAG,MAAM,KAAK,IAAIA,IAAG,IAAI,IAAI;AAAA,MACxD,CAAC;AAAA,IACH;AAEA,YAAQ,IAAI,MAAM,SAAS,CAAC;AAC5B,YAAQ,IAAI;AACZ,YAAQ,IAAIA,IAAG,IAAI,KAAKE,eAAc,MAAM,oBAAoB,CAAC;AACjE,YAAQ,IAAI;AAAA,EACd;AACF,CAAC;;;AC3DD,SAAS,WAAW,qBAAqB;AACzC,SAAS,MAAM,eAAe;AAC9B,SAAS,iBAAAC,sBAAqB;AAC9B,OAAOC,UAAS;AAChB;AAAA,EACE,0BAAAC;AAAA,EACA,gBAAAC;AAAA,EACA,8BAAAC;AAAA,EACA,oBAAAC;AAAA,EACA,qBAAAC;AAAA,EACA,gBAAAC;AAAA,OAEK;AAWP,SAAS,WAAAC,gBAAe;;;ACvBxB,SAAS,iBAAAC,sBAA4D;AAK9D,SAAS,mBAAmB,MAMxB;AACT,QAAM,YAAY,IAAI,IAAIA,eAAc,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AAE7D,QAAM,cAAc,OAAO,OAAO,KAAK,MAAM;AAC7C,QAAM,eAAe,YAAY,SAAS,IACtC,KAAK,MAAM,YAAY,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC,IAAI,YAAY,MAAM,IAC5E;AAEJ,QAAM,aAAa,CAAC,MAAc,KAAK,KAAK,YAAY,KAAK,KAAK,YAAY;AAG9E,QAAM,cAAc,OAAO,QAAQ,KAAK,MAAM,EAC3C,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EACxC,IAAI,CAAC,CAAC,UAAU,EAAE,OAAO,QAAQ,UAAU,OAAO,KAAK,CAAC,MAAM;AAC7D,UAAM,SAAS,UAAU,IAAI,QAAQ;AACrC,UAAM,OAAO,QAAQ,QAAQ;AAC7B,UAAM,iBAAiB,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ;AACxE,UAAM,aAAa,SAAS,QAAQ;AAEpC,UAAM,cAAc,eAAe,IAAI,CAAC,MAAM;AAC5C,YAAM,OAAO,EAAE,aAAa,UAAU,aAAa,EAAE,aAAa,YAAY,YAAY;AAC1F,YAAM,QAAQ,EAAE,aAAa,UAAU,YAAY,EAAE,aAAa,YAAY,YAAY;AAC1F,aAAO;AAAA,+BACgB,KAAK,KAAK,IAAI;AAAA,oBACzB,WAAW,EAAE,QAAQ,CAAC,oBAAe,WAAW,EAAE,OAAO,CAAC;AAAA,YAClE,EAAE,aAAa,wDAAwD,WAAW,EAAE,UAAU,CAAC,YAAY,EAAE;AAAA;AAAA,IAEnH,CAAC,EAAE,KAAK,EAAE;AAEV,WAAO;AAAA;AAAA,gDAEmC,WAAW,IAAI,CAAC;AAAA,+DACD,WAAW,KAAK,CAAC,KAAK,KAAK;AAAA;AAAA,mEAEvB,QAAQ,UAAU,EAAE,aAAa,QAAQ,YAAY,EAAE,aAAa,UAAU,SAAS,eAAe,IAAI,KAAK,GAAG;AAAA,UAC3K,eAAe,SAAS,IAAI,+BAA+B,WAAW,WAAW,2DAA2D;AAAA;AAAA,EAElJ,CAAC,EAAE,KAAK,EAAE;AAEZ,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wCAsB+B,WAAW,YAAY,CAAC,KAAK,YAAY;AAAA,4BACrD,OAAO,KAAK,KAAK,MAAM,EAAE,MAAM,8BAA8B,KAAK,SAAS,MAAM;AAAA;AAAA;AAAA,MAGvG,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQjB;AAEA,SAAS,WAAW,KAAqB;AACvC,SAAO,IACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ;AAC3B;;;AD7EA,IAAI,cAAiD;AACrD,IAAI;AAEF,GAAC,EAAE,YAAY,IAAI,UAAQ,kBAAkB;AAC/C,QAAQ;AAER;AAMA,IAAO,iBAAQC,eAAc;AAAA,EAC3B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,cAAc;AAAA,MACZ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,UAAU,KAAK,QAAQ,OAAOC,KAAI;AACxC,UAAM,SAAS,QAAQ,KAAK,OAAO,mBAAmB;AAEtD,QAAI;AAEF,YAAM,SAAS,cAAc,KAAK,QAAQ,KAAK,KAAK;AAGpD,eAAS,MAAM,kBAAkB;AACjC,YAAM,SAAS,MAAM,UAAU,KAAK,KAAK;AACzC,eAAS,QAAQ,YAAY;AAG7B,YAAM,OAAO,MAAMC,SAAQ,QAAQ,MAAM;AAGzC,YAAM,YAAY,eAAe,KAAK,OAAO;AAC7C,YAAM,YAAY,IAAI,IAAI,SAAS;AAGnC,eAAS,MAAM,iCAAiC;AAChD,YAAM,gBAAgBC,wBAAuB,IAAI;AACjD,YAAM,aAAa,cAAc,OAAO,CAAC,MAAM,UAAU,IAAI,EAAE,QAAQ,CAAC;AACxE,eAAS,QAAQ,mBAAmB,WAAW,MAAM,UAAU;AAG/D,eAAS,MAAM,4BAA4B;AAC3C,YAAM,YAAY,YAAY,MAAM;AACpC,YAAM,WAAWC,cAAa,MAAM,SAAS;AAC7C,YAAM,YAAYC,4BAA2B,QAAQ;AAErD,YAAM,SAA2B,CAAC;AAClC,iBAAW,CAAC,IAAI,IAAI,KAAK,OAAO,QAAQ,SAAS,GAAG;AAClD,YAAI,UAAU,IAAI,EAAE,GAAG;AACrB,iBAAO,EAAE,IAAI;AAAA,QACf;AAAA,MACF;AACA,YAAM,mBAAmB,UAAU,QAAQ,QAAMC,mBAAkB,UAAU,EAAE,CAAC;AAChF,eAAS,QAAQ,mBAAmB;AAGpC,UAAI;AACJ,UAAI,KAAK,WAAW,GAAG;AACrB,iBAAS,MAAM,yBAAyB;AACxC,mBAAW,CAAC;AACZ,mBAAW,KAAK,YAAY;AAC1B,mBAAS,EAAE,QAAQ,IAAIC,kBAAiB,EAAE,MAAM,EAAE,QAAQ;AAAA,QAC5D;AACA,iBAAS,QAAQ,+BAA+B;AAAA,MAClD;AAGA,UAAI,KAAK,aAAa;AACpB,iBAAS,MAAM,0BAA0B;AACzC,YAAI;AACF,gBAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,2BAAmB;AAC/D,gBAAM,gBAAgB,KAAK,QAAQ,aAAa;AAChD,gBAAM,UAAU,MAAM;AAAA,YACpB;AAAA,YACA;AAAA,YACA,CAAC,UAAU,OAAO,UAAU;AAC1B,kBAAI,QAAS,SAAQ,OAAO,0BAA0B,KAAK,IAAI,KAAK,KAAK,QAAQ;AAAA,YACnF;AAAA,UACF;AACA,mBAAS,QAAQ,sBAAsB,QAAQ,IAAI,SAAS;AAAA,QAC9D,SAAS,KAAK;AACZ,mBAAS,KAAK,wBAAyB,IAAc,OAAO,EAAE;AAAA,QAChE;AAAA,MACF;AAGA,gBAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AAErC,UAAI,KAAK,MAAM;AACb,iBAAS,MAAM,mBAAmB;AAClC,cAAM,WAAW,iBAAiB,EAAE,QAAQ,UAAU,kBAAkB,WAAW,CAAC;AACpF,cAAM,WAAW,KAAK,QAAQ,aAAa;AAC3C,sBAAc,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AACzD,iBAAS,QAAQ,wBAAwB,QAAQ,EAAE;AAAA,MACrD,OAAO;AACL,iBAAS,MAAM,2BAA2B;AAC1C,cAAM,aAAa,mBAAmB;AAAA,UACpC;AAAA,UACA,UAAU;AAAA,UACV;AAAA,UACA;AAAA,UACA,cAAc;AAAA,QAChB,CAAC;AAED,cAAM,WAAW,KAAK,QAAQ,aAAa;AAC3C,sBAAc,UAAU,UAAU;AAClC,iBAAS,QAAQ,wBAAwB,QAAQ,EAAE;AAAA,MACrD;AAGA,UAAI,KAAK,YAAY,KAAK,aAAa;AACrC,iBAAS,MAAM,kCAAkC;AACjD,cAAM,YAAY,YAAY,IAAI;AAClC,cAAM,UAAU,KAAK,QAAQ,WAAW;AACxC,sBAAc,SAAS,SAAS;AAChC,iBAAS,QAAQ,uBAAuB,OAAO,EAAE;AAAA,MACnD,WAAW,KAAK,YAAY,KAAK,CAAC,aAAa;AAC7C,iBAAS,KAAK,qDAAqD;AAAA,MACrE;AAEA,UAAI,CAAC,KAAK,OAAO;AACf,gBAAQ,IAAI;AACZ,gBAAQ,IAAI,aAAa,MAAM,EAAE;AACjC,gBAAQ,IAAI;AAAA,MACd;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAeC,eAAc;AAC/B,iBAAS,KAAK,uBAAuB,IAAI,KAAK,MAAM,IAAI,OAAO,EAAE;AAAA,MACnE,OAAO;AACL,iBAAS,KAAM,IAAc,OAAO;AAAA,MACtC;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF,CAAC;;;AEpMD,SAAS,iBAAAC,sBAAqB;AAC9B,OAAOC,UAAS;AAChB,OAAOC,SAAQ;AACf;AAAA,EACE,gBAAAC;AAAA,EACA,8BAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAAC;AAAA,OAEK;AAEP,SAAS,WAAAC,gBAAe;AAExB,IAAO,cAAQC,eAAc;AAAA,EAC3B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,cAAc;AAAA,MACZ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,UAAW,KAAK,SAAS,KAAK,OAAQ,OAAOC,KAAI;AAEvD,QAAI;AACF,YAAM,SAAS,cAAc,KAAK,QAAQ,KAAK,KAAK;AACpD,YAAM,YAAY,YAAY,MAAM;AAGpC,eAAS,MAAM,kBAAkB;AACjC,YAAM,SAAS,MAAM,UAAU,KAAK,KAAK;AACzC,YAAM,OAAO,MAAMF,SAAQ,QAAQ,MAAM;AACzC,eAAS,QAAQ,YAAY;AAG7B,eAAS,MAAM,4BAA4B;AAC3C,YAAM,WAAWG,cAAa,MAAM,SAAS;AAC7C,YAAM,SAASC,4BAA2B,QAAQ;AAClD,eAAS,QAAQ,SAAS,SAAS,MAAM,WAAW;AAEpD,UAAI,SAAS,WAAW,GAAG;AACzB,YAAI,KAAK,MAAM;AACb,kBAAQ,IAAI,KAAK,UAAU,EAAE,SAAS,mBAAmB,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC;AAAA,QACnF,OAAO;AACL,kBAAQ,IAAIC,IAAG,MAAM,4DAAuD,CAAC;AAAA,QAC/E;AACA;AAAA,MACF;AAGA,YAAM,YAAY,eAAe,KAAK,OAAO;AAC7C,UAAI;AACJ,UAAI;AACJ,UAAI,iBAAiB;AACrB,UAAI,eAAe;AAEnB,UAAI,KAAK,WAAW,UAAU,WAAW,GAAG;AAE1C,gBAAQ;AACR,2BAAmB,UAAU,CAAC;AAAA,MAChC,WAAW,KAAK,WAAW,UAAU,SAAS,GAAG;AAG/C,cAAM,YAAY,IAAI,IAAI,SAAS;AACnC,gBAAQ;AACR,2BAAmB;AACnB,yBAAiB,SAAS,OAAO,CAAC,MAAM,UAAU,IAAI,EAAE,MAAM,CAAC;AAC/D,uBAAe,OAAO;AAAA,UACpB,OAAO,QAAQ,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,MAAM,UAAU,IAAI,EAAE,CAAC;AAAA,QAC3D;AAAA,MACF,OAAO;AAEL,gBAAQ;AACR,2BAAmB;AAAA,MACrB;AAGA,YAAM,iBAAiB,KAAK,YAAY,IAAI,SAAS,KAAK,YAAY,GAAG,EAAE,IAAI;AAC/E,YAAM,WAAW,MAAM,oBAAoB;AAAA,QACzC,cAAc;AAAA,QACd,UAAU;AAAA,QACV,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,YAAM,gBAAgB,SAAS,eAAe,IAC1C,KAAK,MAAO,SAAS,kBAAkB,SAAS,eAAgB,GAAG,IACnE;AAEJ,UAAI,KAAK,UAAU;AACjB,YAAI,KAAK,MAAM;AACb,kBAAQ,IAAI,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,QAC/C,OAAO;AACL,kBAAQ,IAAI;AAAA,IAAOA,IAAG,KAAK,gBAAgB,CAAC,EAAE;AAC9C,kBAAQ,IAAI,uBAAuB,SAAS,YAAY,eAAe,CAAC,EAAE;AAC1E,kBAAQ,IAAI,uBAAuB,SAAS,sBAAsB,eAAe,CAAC,EAAE;AACpF,kBAAQ,IAAI,sBAAsB,SAAS,YAAY,KAAK,SAAS,eAAe,cAAc;AAClG,cAAI,SAAS,WAAW;AACtB,oBAAQ,IAAI,KAAKA,IAAG,OAAO,cAAc,SAAS,eAAe,gCAAgC,CAAC,EAAE;AAAA,UACtG;AACA,kBAAQ,IAAI;AAAA,QACd;AACA;AAAA,MACF;AAGA,YAAM,SAAS,QAAQ,IAAI;AAC3B,UAAI,CAAC,QAAQ;AACX,iBAAS,KAAK,oDAAoD;AAClE,gBAAQ,MAAMA,IAAG,IAAI,wDAAwD,CAAC;AAC9E,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAGA,UAAI,CAAC,KAAK,SAAS,CAAC,KAAK,MAAM;AAC7B,gBAAQ,IAAIA,IAAG,IAAI,MAAM,SAAS,YAAY,eAAe,CAAC,kBAAkB,SAAS,YAAY,cAAc,aAAa,eAAe,CAAC;AAChJ,YAAI,SAAS,WAAW;AACtB,kBAAQ,IAAIA,IAAG,OAAO,eAAe,SAAS,eAAe,2CAA2C,CAAC;AAAA,QAC3G;AAAA,MACF;AAGA,eAAS,MAAM,sBAAsB;AAErC,UAAI;AACJ,UAAI;AACF,qBAAa,MAAM,OAAO,mBAAmB,GAAG;AAAA,MAClD,QAAQ;AACN,iBAAS,KAAK,mDAAmD;AACjE,gBAAQ,MAAMA,IAAG,IAAI,sDAAsD,CAAC;AAC5E,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,YAAM,YAAY,IAAI,UAAU,EAAE,OAAO,CAAC;AAE1C,YAAM,WAAuB,OAAO,WAAW;AAC7C,cAAM,MAAM,MAAM,UAAU,SAAS,OAAO;AAAA,UAC1C,OAAO;AAAA,UACP,YAAY;AAAA,UACZ,QAAQ;AAAA,UACR,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAAA,QAC9C,CAAC;AACD,cAAM,YAAY,IAAI,QAAQ,KAAK,CAAC,MAAwB,EAAE,SAAS,MAAM;AAC7E,eAAO,WAAW,SAAS,SAAU,UAA6C,OAAO;AAAA,MAC3F;AAEA,YAAM,SAAS,MAAM,cAAc;AAAA,QACjC,cAAc;AAAA,QACd,UAAU;AAAA,QACV,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,eAAS,QAAQ,SAAS,OAAO,gBAAgB,cAAc,OAAO,eAAe,cAAc;AAGnG,UAAI,KAAK,QAAQ;AACf,cAAM,EAAE,eAAAC,eAAc,IAAI,MAAM,OAAO,IAAS;AAChD,QAAAA,eAAc,KAAK,QAAQ,OAAO,MAAM,OAAO;AAC/C,YAAI,CAAC,KAAK,OAAO;AACf,kBAAQ,IAAID,IAAG,MAAM;AAAA,0BAA6B,KAAK,MAAM;AAAA,CAAI,CAAC;AAAA,QACpE;AAAA,MACF,WAAW,KAAK,MAAM;AACpB,gBAAQ,IAAI,KAAK,UAAU;AAAA,UACzB,MAAM,OAAO;AAAA,UACb,kBAAkB,OAAO;AAAA,UACzB,iBAAiB,OAAO;AAAA,UACxB,eAAe,OAAO;AAAA,QACxB,GAAG,MAAM,CAAC,CAAC;AAAA,MACb,OAAO;AACL,gBAAQ,IAAI,OAAO,OAAO,IAAI;AAAA,MAChC;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAeE,eAAc;AAC/B,iBAAS,KAAK,uBAAuB,IAAI,KAAK,MAAM,IAAI,OAAO,EAAE;AAAA,MACnE,OAAO;AACL,iBAAS,KAAM,IAAc,OAAO;AAAA,MACtC;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF,CAAC;;;AClOD,SAAS,iBAAAC,sBAAqB;AAC9B,OAAOC,UAAS;AAChB,OAAOC,SAAQ;AACf,OAAOC,YAAW;AAClB;AAAA,EACE;AAAA,EACA,gBAAAC;AAAA,OAEK;AAEP,SAAS,WAAAC,gBAAe;AAGxB,IAAM,cAAc,oBAAI,IAAI,CAAC,QAAQ,SAAS,iBAAiB,UAAU,iBAAiB,YAAY,QAAQ,CAAC;AAE/G,IAAO,gBAAQC,eAAc;AAAA,EAC3B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,UAAW,KAAK,SAAS,KAAK,OAAQ,OAAOC,KAAI;AAEvD,QAAI;AACF,YAAM,SAAS,cAAc,KAAK,QAAQ,KAAK,KAAK;AAGpD,eAAS,MAAM,kBAAkB;AACjC,YAAM,SAAS,MAAM,UAAU,KAAK,KAAK;AACzC,eAAS,QAAQ,YAAY;AAG7B,YAAM,OAAO,MAAMC,SAAQ,QAAQ,MAAM;AAGzC,YAAM,OAAuG,CAAC;AAC9G,UAAI,KAAK,MAAM;AACb,mBAAW,KAAK,KAAK,KAAK,MAAM,GAAG,EAAE,IAAI,CAACC,OAAMA,GAAE,KAAK,CAAC,EAAE,OAAO,OAAO,GAAG;AACzE,cAAI,CAAC,YAAY,IAAI,CAAC,GAAG;AACvB,kBAAM,IAAI,MAAM,uBAAuB,CAAC,aAAa,MAAM,KAAK,WAAW,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,UAC3F;AACA,eAAK,KAAK,CAAwB;AAAA,QACpC;AAAA,MACF;AAGA,eAAS,MAAM,uBAAuB;AACtC,YAAM,YAAY,YAAY,MAAM;AACpC,YAAM,SAAS,WAAW,MAAM,EAAE,WAAW,KAAK,CAAC;AACnD,eAAS,QAAQ,gBAAgB;AAGjC,UAAI,KAAK,MAAM;AACb,gBAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,MAC7C,OAAO;AAEL,YAAI,CAAC,KAAK,SAAS,eAAe,GAAG;AACnC,0BAAgB,OAAO,cAAc,QAAQ,OAAO,cAAc,UAAU,EAAE,OAAO,KAAK,MAAM,CAAC;AACjG,wBAAc,OAAO,cAAc,UAAU,EAAE,OAAO,KAAK,MAAM,CAAC;AAAA,QACpE;AAGA,4BAAoB,QAAQ,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC;AAAA,MACzD;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAeC,eAAc;AAC/B,iBAAS,KAAK,uBAAuB,IAAI,KAAK,MAAM,IAAI,OAAO,EAAE;AAAA,MACnE,OAAO;AACL,iBAAS,KAAM,IAAc,OAAO;AAAA,MACtC;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF,CAAC;AAED,SAAS,oBACP,QACA,MACA,SACM;AACN,MAAI,CAAC,SAAS,OAAO;AACnB,YAAQ,IAAIC,IAAG,KAAK,mBAAmB,CAAC;AACxC,YAAQ,IAAI;AAAA,EACd;AAEA,QAAM,QAAQ,IAAIC,OAAM;AAAA,IACtB,MAAM,CAACD,IAAG,KAAK,OAAO,GAAGA,IAAG,KAAK,QAAQ,CAAC;AAAA,IAC1C,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,EAChC,CAAC;AAED,MAAI,CAAC,KAAK,SAAS,MAAM,GAAG;AAC1B,UAAM,EAAE,OAAO,OAAO,OAAO,IAAI,OAAO;AACxC,UAAM,QAAQ,SAAS,KAAKA,IAAG,QAAQ,SAAS,KAAKA,IAAG,SAASA,IAAG;AACpE,UAAM,KAAK,CAAC,mBAAmB,GAAG,MAAM,OAAO,KAAK,CAAC,CAAC,SAAS,KAAK,YAAO,OAAO,MAAM,SAAS,OAAO,WAAW,IAAI,KAAK,GAAG,EAAE,CAAC;AAAA,EACpI;AAEA,MAAI,CAAC,KAAK,SAAS,OAAO,GAAG;AAC3B,UAAM,EAAE,YAAY,OAAO,IAAI,OAAO;AACtC,UAAM,QAAQ,OAAO,WAAW,IAAIA,IAAG,QAAQA,IAAG;AAClD,UAAM,KAAK,CAAC,SAAS,GAAG,UAAU,QAAQ,eAAe,IAAI,KAAK,GAAG,WAAM,MAAM,GAAG,OAAO,MAAM,SAAS,OAAO,WAAW,IAAI,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC;AAAA,EAChJ;AAEA,MAAI,CAAC,KAAK,SAAS,eAAe,GAAG;AACnC,UAAM,EAAE,OAAO,OAAO,IAAI,OAAO;AACjC,UAAM,QAAQ,SAAS,KAAKA,IAAG,QAAQ,SAAS,KAAKA,IAAG,SAASA,IAAG;AACpE,UAAM,KAAK,CAAC,iBAAiB,GAAG,MAAM,OAAO,KAAK,CAAC,CAAC,eAAU,OAAO,MAAM,SAAS,OAAO,WAAW,IAAI,KAAK,GAAG,EAAE,CAAC;AAAA,EACvH;AAEA,MAAI,CAAC,KAAK,SAAS,QAAQ,GAAG;AAC5B,UAAM,EAAE,OAAO,OAAO,IAAI,OAAO;AACjC,UAAM,QAAQ,OAAO,WAAW,IAAIA,IAAG,QAAQA,IAAG;AAClD,UAAM,KAAK,CAAC,UAAU,GAAG,KAAK,SAAS,UAAU,IAAI,KAAK,GAAG,WAAM,MAAM,GAAG,OAAO,MAAM,SAAS,OAAO,WAAW,IAAI,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC;AAAA,EACxI;AAEA,MAAI,CAAC,KAAK,SAAS,UAAU,GAAG;AAC9B,UAAM,EAAE,OAAO,IAAI,OAAO;AAC1B,UAAM,QAAQ,OAAO,WAAW,IAAIA,IAAG,QAAQA,IAAG;AAClD,UAAM,KAAK,CAAC,oBAAoB,MAAM,GAAG,OAAO,MAAM,SAAS,OAAO,WAAW,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC;AAAA,EACnG;AAEA,MAAI,CAAC,KAAK,SAAS,QAAQ,GAAG;AAC5B,UAAM,EAAE,OAAO,IAAI,OAAO;AAC1B,UAAM,QAAQ,OAAO,WAAW,IAAIA,IAAG,QAAQA,IAAG;AAClD,UAAM,KAAK,CAAC,eAAe,MAAM,GAAG,OAAO,MAAM,SAAS,OAAO,WAAW,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC;AAAA,EAC9F;AAEA,UAAQ,IAAI,MAAM,SAAS,CAAC;AAC5B,UAAQ,IAAI;AACd;;;ACzJA,SAAS,iBAAAE,sBAAqB;AAC9B,OAAOC,SAAQ;AAEf,SAAS,WAAAC,gBAAe;AACxB;AAAA,EACE,cAAAC;AAAA,EACA,gBAAAC;AAAA,EACA;AAAA,OAGK;AACP,SAAS,eAAe;AACxB,SAAS,WAAAC,UAAS,gBAAgB;AAuBlC,IAAMC,eAAc,oBAAI,IAAI;AAAA,EAC1B;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAiB;AAAA,EAClC;AAAA,EAAiB;AAAA,EAAgB;AAAA,EAAQ;AAAA,EAAqB;AAAA,EAAY;AAC5E,CAAC;AAED,IAAO,eAAQC,eAAc;AAAA,EAC3B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,eAAe;AAAA,MACb,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,QAAI;AAEF,YAAM,OAAsB,CAAC;AAC7B,UAAI,KAAK,MAAM;AACb,mBAAW,KAAK,KAAK,KAAK,MAAM,GAAG,EAAE,IAAI,CAACC,OAAMA,GAAE,KAAK,CAAC,EAAE,OAAO,OAAO,GAAG;AACzE,cAAI,CAACF,aAAY,IAAI,CAAC,GAAG;AACvB,kBAAM,IAAI,MAAM,uBAAuB,CAAC,aAAa,MAAM,KAAKA,YAAW,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,UAC3F;AACA,eAAK,KAAK,CAAC;AAAA,QACb;AAAA,MACF;AAEA,YAAM,iBAAiB,KAAK,gBAAgB,SAAY,SAAS,KAAK,aAAa,EAAE,IAAI;AACzF,UAAI,mBAAmB,UAAa,MAAM,cAAc,GAAG;AACzD,cAAM,IAAI,MAAM,wCAAwC,KAAK,WAAW,EAAE;AAAA,MAC5E;AACA,YAAM,cAAc,kBAAkB;AACtC,YAAM,QAAQ,MAAM,YAAY,KAAK,KAAK;AAE1C,UAAI,MAAM,WAAW,GAAG;AACtB,YAAI,KAAK,MAAM;AACb,kBAAQ,IAAI,KAAK,UAAU,EAAE,OAAO,CAAC,GAAG,aAAa,GAAG,eAAe,EAAE,CAAC,CAAC;AAAA,QAC7E,OAAO;AACL,kBAAQ,MAAMG,IAAG,IAAI,4BAA4B,KAAK,KAAK,EAAE,CAAC;AAAA,QAChE;AACA,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,YAAM,UAA4B,CAAC;AACnC,UAAI,cAAc;AAClB,UAAI,gBAAgB;AAEpB,iBAAW,QAAQ,OAAO;AACxB,cAAM,SAAS,cAAc,KAAK,QAAQ,IAAI;AAC9C,cAAM,SAAS,MAAM,UAAU,IAAI;AACnC,cAAM,OAAO,MAAMP,SAAQ,QAAQ,MAAM;AACzC,cAAM,YAAY,YAAY,MAAM;AAMpC,cAAM,YAAY,eAAe,MAAM;AAEvC,cAAM,SAASC,YAAW,MAAM;AAAA,UAC9B;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,cAAM,SAAS,oBAAoB,QAAQ,IAAI;AAC/C,cAAM,SAAS,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO,EAAE;AAC5D,cAAM,WAAW,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,SAAS,EAAE;AAEhE,gBAAQ,KAAK,EAAE,MAAM,SAAS,QAAQ,IAAI,GAAG,IAAI,GAAG,QAAQ,QAAQ,SAAS,CAAC;AAC9E,uBAAe;AACf,yBAAiB;AAAA,MACnB;AAGA,UAAI,KAAK,MAAM;AACb,gBAAQ,IAAI,KAAK,UAAU,EAAE,OAAO,SAAS,aAAa,cAAc,GAAG,MAAM,CAAC,CAAC;AAAA,MACrF,OAAO;AACL,mBAAW,UAAU,SAAS;AAC5B,kBAAQ,IAAIM,IAAG,UAAU,OAAO,IAAI,CAAC;AACrC,cAAI,OAAO,OAAO,WAAW,GAAG;AAC9B,oBAAQ,IAAI,KAAKA,IAAG,MAAM,MAAM,CAAC,mBAAmB;AAAA,UACtD,OAAO;AAGL,kBAAM,QAAQ,OAAO,OAAO;AAAA,cAC1B,CAAC,GAAG,MAAO,EAAE,MAAM,KAAK,IAAI,GAAG,GAAG,EAAE,IAAI,IAAI,IAAI,EAAE,IAAI,MAAM,GAAG,MAAM,IAAI;AAAA,cACzE;AAAA,YACF;AACA,uBAAW,SAAS,OAAO,QAAQ;AACjC,oBAAM,MAAM,MAAM,aAAa,UAC3BA,IAAG,IAAI,OAAO,IACd,MAAM,aAAa,YACjBA,IAAG,OAAO,OAAO,IACjBA,IAAG,KAAK,OAAO;AACrB,oBAAM,MAAM,QAAQ,IAChB,GAAGA,IAAG,KAAK,MAAM,MAAM,GAAG,MAAM,IAAI,IAAI,IAAI,MAAM,IAAI,MAAM,KAAK,IAAI,SAAS,KAAK,CAAC,CAAC,OACrF;AACJ,sBAAQ,IAAI,KAAK,GAAG,KAAK,GAAG,GAAGA,IAAG,IAAI,MAAM,SAAS,OAAO,EAAE,CAAC,CAAC,KAAKA,IAAG,KAAK,MAAM,KAAK,OAAO,EAAE,CAAC,CAAC,KAAK,MAAM,OAAO,EAAE;AAAA,YACzH;AAAA,UACF;AACA,kBAAQ,IAAI;AAAA,QACd;AAEA,cAAM,UAAU;AAAA,UACd,GAAG,MAAM,MAAM,QAAQ,MAAM,WAAW,IAAI,KAAK,GAAG;AAAA,UACpD,cAAc,IAAIA,IAAG,IAAI,GAAG,WAAW,SAAS,gBAAgB,IAAI,KAAK,GAAG,EAAE,IAAI;AAAA,UAClF,gBAAgB,IAAIA,IAAG,OAAO,GAAG,aAAa,WAAW,kBAAkB,IAAI,KAAK,GAAG,EAAE,IAAI;AAAA,UAC7F,gBAAgB,KAAK,kBAAkB,IAAIA,IAAG,MAAM,WAAW,IAAI;AAAA,QACrE,EAAE,OAAO,OAAO,EAAE,KAAK,KAAK;AAE5B,gBAAQ,IAAI,OAAO;AAAA,MACrB;AAGA,UAAI,cAAc,GAAG;AACnB,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,UAAI,KAAK,iBAAiB,gBAAgB,GAAG;AAC3C,YAAI,CAAC,KAAK,MAAM;AACd,kBAAQ,MAAMA,IAAG,OAAO;AAAA,UAAa,aAAa,WAAW,kBAAkB,IAAI,KAAK,GAAG,0BAA0B,CAAC;AAAA,QACxH;AACA,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,UAAI,OAAO,SAAS,WAAW,KAAK,gBAAgB,aAAa;AAC/D,YAAI,CAAC,KAAK,MAAM;AACd,kBAAQ,MAAMA,IAAG,IAAI;AAAA,yBAA4B,aAAa,MAAM,WAAW,EAAE,CAAC;AAAA,QACpF;AACA,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAeL,eAAc;AAC/B,gBAAQ,MAAMK,IAAG,IAAI,uBAAuB,IAAI,KAAK,MAAM,IAAI,OAAO,EAAE,CAAC;AAAA,MAC3E,OAAO;AACL,gBAAQ,MAAMA,IAAG,IAAK,IAAc,OAAO,CAAC;AAAA,MAC9C;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF,CAAC;AASD,eAAe,YAAY,SAAoC;AAE7D,MAAI,CAAC,QAAQ,SAAS,GAAG,GAAG;AAC1B,WAAO,CAACJ,SAAQ,OAAO,CAAC;AAAA,EAC1B;AAGA,MAAI,QAAQ,SAAS,IAAI,GAAG;AAC1B,UAAM,IAAI,MAAM,gHAAgH;AAAA,EAClI;AAGA,QAAM,UAAU,KAAK,IAAI,QAAQ,YAAY,GAAG,GAAG,QAAQ,YAAY,IAAI,CAAC;AAC5E,QAAM,MAAM,WAAW,IAAI,QAAQ,MAAM,GAAG,OAAO,IAAI;AACvD,QAAM,OAAO,WAAW,IAAI,QAAQ,MAAM,UAAU,CAAC,IAAI;AAGzD,QAAM,QAAQ,IAAI,OAAO,MAAM,KAAK,QAAQ,OAAO,IAAI,IAAI,GAAG;AAE9D,QAAM,UAAU,MAAM,QAAQA,SAAQ,GAAG,GAAG,EAAE,eAAe,KAAK,CAAC;AACnE,SAAO,QACJ,OAAO,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,KAAK,EAAE,IAAI,CAAC,EAC9C,IAAI,CAAC,MAAMA,SAAQ,KAAK,EAAE,IAAI,CAAC,EAC/B,KAAK;AACV;AASA,SAAS,UAAU,MAAwB,MAA6C;AACtF,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,YAAY;AAChB,aAAW,OAAO,MAAM;AACtB,QAAI,KAAK,KAAK,CAAC,MAAM,EAAE,WAAW,IAAI,MAAM,EAAG;AAC/C,QAAI,KAAK,UAAU,uBAAuB;AACxC,kBAAY;AACZ;AAAA,IACF;AACA,SAAK,KAAK,GAAG;AAAA,EACf;AACA,OAAK,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AACvC,SAAO;AACT;AAKA,SAAS,oBAAoB,QAAqB,MAA6B;AAC7E,QAAM,SAAsB,CAAC;AAG7B,MAAI,CAAC,KAAK,SAAS,eAAe,GAAG;AACnC,UAAM,YAAY,oBAAI,IAOnB;AAEH,eAAW,KAAK,OAAO,cAAc,UAAU;AAC7C,YAAM,MAAM,GAAG,EAAE,QAAQ,KAAK,EAAE,QAAQ;AACxC,YAAM,WAAW,UAAU,IAAI,GAAG;AAClC,UAAI,UAAU;AACZ,iBAAS,QAAQ,KAAK,EAAE,MAAM;AAI9B,iBAAS,QAAQ,EAAE;AACnB,YAAI,UAAU,SAAS,MAAM,EAAE,IAAI,EAAG,UAAS,gBAAgB;AAC/D,YAAI,EAAE,cAAe,UAAS,gBAAgB;AAAA,MAChD,OAAO;AACL,kBAAU,IAAI,KAAK;AAAA,UACjB,UAAU,EAAE;AAAA,UACZ,SAAS,CAAC,EAAE,MAAM;AAAA,UAClB,SAAS,EAAE;AAAA,UACX,KAAK,EAAE;AAAA,UACP,OAAO,EAAE,QAAQ,CAAC,GAAG,MAAM,GAAG,qBAAqB;AAAA,UACnD,GAAI,EAAE,gBAAgB,EAAE,eAAe,KAAK,IAAI,CAAC;AAAA,QACnD,CAAC;AAAA,MACH;AAAA,IACF;AAEA,eAAW,CAAC,KAAK,GAAG,KAAK,WAAW;AAClC,YAAM,WAAW,IAAI,MAAM,IAAI,EAAE,CAAC;AAClC,aAAO,KAAK;AAAA,QACV,UAAU,IAAI;AAAA,QACd,UAAU,IAAI,QAAQ,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,IAAI,QAAQ,SAAS,IAAI,IAAI,IAAI,QAAQ,SAAS,CAAC,KAAK;AAAA,QACvG,MAAM;AAAA,QACN,SAAS,IAAI;AAAA,QACb,GAAI,IAAI,MAAM,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC;AAAA,QAClC,GAAI,IAAI,KAAK,SAAS,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAAA,QAC5C,GAAI,IAAI,gBAAgB,EAAE,eAAe,KAAK,IAAI,CAAC;AAAA,MACrD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,SAAS,MAAM,GAAG;AAC1B,eAAW,SAAS,OAAO,KAAK,QAAQ;AACtC,aAAO,KAAK;AAAA,QACV,UAAU,MAAM;AAAA,QAChB,UAAU;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,QAAQ,MAAM;AAAA,QACd,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,SAAS,OAAO,GAAG;AAC3B,eAAW,SAAS,OAAO,MAAM,QAAQ;AACvC,aAAO,KAAK;AAAA,QACV,UAAU,MAAM;AAAA,QAChB,UAAU;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,SAAS,eAAe,GAAG;AACnC,eAAW,SAAS,OAAO,cAAc,QAAQ;AAC/C,aAAO,KAAK;AAAA,QACV,UAAU,MAAM;AAAA,QAChB,UAAU;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,SAAS,QAAQ,GAAG;AAC5B,eAAW,SAAS,OAAO,OAAO,QAAQ;AACxC,aAAO,KAAK;AAAA,QACV,UAAU,MAAM;AAAA,QAChB,UAAU;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,SAAS,cAAc,GAAG;AAClC,eAAW,SAAS,OAAO,aAAa,QAAQ;AAC9C,aAAO,KAAK;AAAA,QACV,UAAU,MAAM;AAAA,QAChB,UAAU;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,SAAS,MAAM,GAAG;AAC1B,eAAW,SAAS,OAAO,KAAK,QAAQ;AACtC,aAAO,KAAK;AAAA,QACV,UAAU,MAAM;AAAA,QAChB,UAAU;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,SAAS,mBAAmB,GAAG;AACvC,eAAW,SAAS,OAAO,kBAAkB,QAAQ;AACnD,aAAO,KAAK;AAAA,QACV,UAAU,MAAM;AAAA,QAChB,UAAU;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,SAAS,UAAU,GAAG;AAC9B,eAAW,SAAS,OAAO,SAAS,QAAQ;AAC1C,aAAO,KAAK;AAAA,QACV,UAAU,MAAM;AAAA,QAChB,UAAU;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,SAAS,QAAQ,GAAG;AAC5B,eAAW,SAAS,OAAO,OAAO,QAAQ;AACxC,aAAO,KAAK;AAAA,QACV,UAAU,MAAM;AAAA,QAChB,UAAU;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,EAAE;AAC9C,SAAO,KAAK,CAAC,GAAG,MAAM,MAAM,EAAE,QAAQ,IAAI,MAAM,EAAE,QAAQ,CAAC;AAE3D,SAAO;AACT;;;Ab7ZA,IAAM,OAAOK,eAAc;AAAA,EACzB;AAAA,EACA,aAAa;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF,CAAC;AAED,QAAQ,IAAI;","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","../src/project-config.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\";\r\nimport pkg from \"../package.json\" with { type: \"json\" };\r\n\r\n/**\r\n * CLI identity. Both fields used to be hardcoded in index.ts and both had\r\n * drifted: `--version` reported 0.4.0 while the package was 0.3.4, and the\r\n * description claimed 12 clients while the engine shipped 15. Anyone filing a\r\n * bug would have quoted a version that never existed.\r\n *\r\n * Kept in its own module so it can be asserted directly. Testing it through the\r\n * binary means racing citty's process.exit() against the stdout flush.\r\n */\r\nexport const meta = {\r\n name: \"emailens\",\r\n version: pkg.version,\r\n description: `Email compatibility analysis CLI; preview how HTML emails render across ${EMAIL_CLIENTS.length} email clients.`,\r\n} as const;\r\n","{\r\n \"name\": \"@emailens/cli\",\r\n \"version\": \"0.5.0\",\r\n \"description\": \"CLI tool for email compatibility analysis: lint, preview and export HTML, MJML, Maizzle and React 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.11.0\",\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 \"mjml\",\r\n \"maizzle\",\r\n \"react-email\"\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\";\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);\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\" ? \"✗\" : w.severity === \"warning\" ? \"⚠\" : \"ℹ\";\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\">→ ${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 ?? \"\"} · ${client?.category ?? \"\"} · ${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 · ${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, \"&\")\r\n .replace(/</g, \"<\")\r\n .replace(/>/g, \">\")\r\n .replace(/\"/g, \""\");\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\", \"darkContrast\", \"mobileContrast\", \"design\"]);\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 + contrast + design\",\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,darkContrast,mobileContrast,design\",\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\" | \"darkContrast\" | \"mobileContrast\" | \"design\"> = [];\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 // Contrast findings are errors, not tips: text at 1:1 is invisible, not untidy.\r\n if (!skip.includes(\"darkContrast\")) {\r\n const n = report.darkContrast.length;\r\n const color = n === 0 ? pc.green : pc.red;\r\n table.push([\"Dark Mode Contrast\", color(`${n} issue${n === 1 ? \"\" : \"s\"}`)]);\r\n }\r\n\r\n if (!skip.includes(\"mobileContrast\")) {\r\n const n = report.mobileContrast.length;\r\n const color = n === 0 ? pc.green : pc.red;\r\n table.push([\"Mobile Contrast\", color(`${n} issue${n === 1 ? \"\" : \"s\"}`)]);\r\n }\r\n\r\n if (!skip.includes(\"design\")) {\r\n const { issues } = report.design;\r\n const color = issues.length === 0 ? pc.green : pc.yellow;\r\n table.push([\"Design Consistency\", 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 { applySeverities, loadProjectConfig } from \"../project-config.js\";\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 \"darkContrast\", \"mobileContrast\", \"design\",\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 // `.emailensrc`, the same file the editor extension reads. Without this\r\n // a rule demoted in the editor still fails the build, which is the worst\r\n // of both: the panel says it does not matter and CI says it does.\r\n const project = loadProjectConfig();\r\n if (project?.invalid.length && !args.json) {\r\n console.error(\r\n pc.yellow(\r\n `${project.source}: ignoring ${project.invalid.join(\", \")}, ` +\r\n `a severity must be \"error\", \"warning\", \"info\" or \"off\".`,\r\n ),\r\n );\r\n }\r\n\r\n // Parse --skip. A flag is an explicit choice for this invocation and\r\n // wins over the file; in the editor it is the other way round, because\r\n // an editor setting is ambient and the repo's file is the team's.\r\n const skip: Array<string> = [];\r\n if (!args.skip) {\r\n for (const s of project?.skip ?? []) {\r\n // Named rather than dropped: a team that believes spam checking is\r\n // off and finds it is not has been misled by their own config.\r\n if (VALID_SKIPS.has(s)) skip.push(s);\r\n else if (!args.json) {\r\n console.error(pc.yellow(`${project?.source}: unknown skip value \"${s}\", ignoring.`));\r\n }\r\n }\r\n }\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 = applySeverities(flattenToLintIssues(report, skip), project?.rules);\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\" | \"darkContrast\" | \"mobileContrast\" | \"design\";\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 // Contrast in the two renders a desktop light preview never shows. These are\r\n // flat arrays rather than a report with `.issues`, and their messages arrive\r\n // prefixed (\"Dark mode: …\"), which the category column already says.\r\n if (!skip.includes(\"darkContrast\")) {\r\n for (const issue of report.darkContrast) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"darkContrast\",\r\n rule: issue.rule,\r\n message: issue.message.replace(/^Dark mode: /, \"\"),\r\n ...(issue.loc ? { loc: issue.loc } : {}),\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"mobileContrast\")) {\r\n for (const issue of report.mobileContrast) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"mobileContrast\",\r\n rule: issue.rule,\r\n message: issue.message.replace(/^At mobile width: /, \"\"),\r\n ...(issue.loc ? { loc: issue.loc } : {}),\r\n });\r\n }\r\n }\r\n\r\n if (!skip.includes(\"design\")) {\r\n for (const issue of report.design.issues) {\r\n issues.push({\r\n severity: issue.severity,\r\n category: \"design\",\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 // 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","import { existsSync, readFileSync } from \"node:fs\";\r\nimport { dirname, join, parse } from \"node:path\";\r\n\r\n/**\r\n * `.emailensrc`, shared with the editor extension.\r\n *\r\n * A severity policy is a team decision, and a team decision that lives in one\r\n * person's editor settings is one CI disagrees with. The extension has read\r\n * this file since it shipped; the CLI reading the same keys is what makes the\r\n * two agree: a rule demoted in the editor and still failing the build is the\r\n * worst of both.\r\n *\r\n * Precedence here is the other way round from the editor's, deliberately. A\r\n * command-line flag is an explicit choice for one invocation and wins; an\r\n * editor setting is ambient, so the repo's file wins there.\r\n */\r\n\r\n/** What a rule may be turned into. `off` means not reported at all. */\r\nexport type RuleSeverity = \"error\" | \"warning\" | \"info\" | \"off\";\r\nexport type RuleSeverities = Record<string, RuleSeverity>;\r\n\r\nconst SEVERITIES: readonly string[] = [\"error\", \"warning\", \"info\", \"off\"];\r\n\r\nexport interface ProjectConfig {\r\n skip?: string[];\r\n rules?: RuleSeverities;\r\n /** Where it was read from, for error messages. */\r\n source?: string;\r\n /** Entries that were not usable, named rather than dropped in silence. */\r\n invalid: string[];\r\n}\r\n\r\n/**\r\n * Read a `rules` map, keeping what is usable and naming what is not.\r\n *\r\n * A misspelled severity is silently nothing otherwise, and a rule someone\r\n * believes is switched off but is not is worse than no setting at all.\r\n */\r\nexport function parseRules(value: unknown): { rules: RuleSeverities; invalid: string[] } {\r\n const rules: RuleSeverities = {};\r\n const invalid: string[] = [];\r\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { rules, invalid };\r\n\r\n for (const [rule, severity] of Object.entries(value as Record<string, unknown>)) {\r\n if (typeof severity === \"string\" && SEVERITIES.includes(severity)) {\r\n rules[rule] = severity as RuleSeverity;\r\n } else {\r\n invalid.push(`${rule}: ${JSON.stringify(severity)}`);\r\n }\r\n }\r\n return { rules, invalid };\r\n}\r\n\r\n/** Parse a config body that has already been read off disk. */\r\nexport function parseProjectConfig(\r\n source: string,\r\n from: \"emailensrc\" | \"package.json\",\r\n): ProjectConfig | undefined {\r\n let parsed: unknown;\r\n try {\r\n parsed = JSON.parse(source);\r\n } catch {\r\n // A malformed file is a reason to lint without it, not a reason to stop.\r\n return undefined;\r\n }\r\n if (!parsed || typeof parsed !== \"object\") return undefined;\r\n\r\n const body = from === \"package.json\" ? (parsed as Record<string, unknown>).emailens : parsed;\r\n if (!body || typeof body !== \"object\") return undefined;\r\n\r\n const config: ProjectConfig = { invalid: [] };\r\n const skip = (body as Record<string, unknown>).skip;\r\n if (Array.isArray(skip) && skip.every((s) => typeof s === \"string\")) config.skip = skip;\r\n\r\n const { rules, invalid } = parseRules((body as Record<string, unknown>).rules);\r\n if (Object.keys(rules).length) config.rules = rules;\r\n config.invalid = invalid;\r\n\r\n return config.skip || config.rules || invalid.length ? config : undefined;\r\n}\r\n\r\n/**\r\n * Find and read the nearest config, walking up from `from`.\r\n *\r\n * Upward, because `emailens lint emails/*.html` is usually run from the repo\r\n * root while the file being linted is not, and the config belongs to the\r\n * project rather than to the directory the shell happens to be in.\r\n */\r\nexport function loadProjectConfig(from: string = process.cwd()): ProjectConfig | undefined {\r\n const root = parse(from).root;\r\n for (let dir = from; ; dir = dirname(dir)) {\r\n for (const [name, kind] of [\r\n [\".emailensrc\", \"emailensrc\"],\r\n [\"package.json\", \"package.json\"],\r\n ] as const) {\r\n const path = join(dir, name);\r\n if (!existsSync(path)) continue;\r\n let body: string;\r\n try {\r\n body = readFileSync(path, \"utf-8\");\r\n } catch {\r\n continue;\r\n }\r\n const config = parseProjectConfig(body, kind);\r\n if (config) return { ...config, source: path };\r\n }\r\n if (dir === root) return undefined;\r\n }\r\n}\r\n\r\n/** Apply per-rule severities. `off` drops the issue entirely. */\r\nexport function applySeverities<T extends { rule: string; severity: \"error\" | \"warning\" | \"info\" }>(\r\n issues: T[],\r\n rules?: RuleSeverities,\r\n): T[] {\r\n if (!rules || !Object.keys(rules).length) return issues;\r\n const out: T[] = [];\r\n for (const issue of issues) {\r\n const severity = rules[issue.rule];\r\n if (severity === \"off\") continue;\r\n out.push(severity ? { ...issue, severity } : issue);\r\n }\r\n return out;\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","readFileSync","join","resolve","VALID_SKIPS","defineCommand","pc","s","compile","auditEmail","CompileError","defineCommand"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emailens/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI tool for email compatibility analysis
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "CLI tool for email compatibility analysis: lint, preview and export HTML, MJML, Maizzle and React Email rendering across 21 email clients.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"emailens": "./dist/index.js"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"prepublishOnly": "bun run build"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@emailens/engine": "^0.
|
|
30
|
+
"@emailens/engine": "^0.11.0",
|
|
31
31
|
"citty": "^0.1.6",
|
|
32
32
|
"cli-table3": "^0.6.5",
|
|
33
33
|
"ora": "^8.2.0",
|
|
@@ -55,7 +55,10 @@
|
|
|
55
55
|
"outlook",
|
|
56
56
|
"apple-mail",
|
|
57
57
|
"email-testing",
|
|
58
|
-
"dark-mode"
|
|
58
|
+
"dark-mode",
|
|
59
|
+
"mjml",
|
|
60
|
+
"maizzle",
|
|
61
|
+
"react-email"
|
|
59
62
|
],
|
|
60
63
|
"license": "MIT",
|
|
61
64
|
"repository": {
|