@emailens/cli 0.3.2 → 0.3.4
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 +73 -2
- package/dist/chunk-7OCVIDC7.js +12 -0
- package/dist/chunk-7OCVIDC7.js.map +1 -0
- package/dist/index.js +296 -10
- package/dist/index.js.map +1 -1
- package/dist/{screenshots-PMZQU5V7.js → screenshots-UOLRXFAJ.js} +2 -1
- package/dist/{screenshots-PMZQU5V7.js.map → screenshots-UOLRXFAJ.js.map} +1 -1
- package/dist/{sdk-HQZAL655.js → sdk-IYJIYBYM.js} +2 -1
- package/dist/{sdk-HQZAL655.js.map → sdk-IYJIYBYM.js.map} +1 -1
- package/package.json +70 -70
package/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# @emailens/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@emailens/cli)
|
|
4
|
+
[](https://github.com/emailens/mcp)
|
|
5
|
+
|
|
6
|
+
CLI tool for email compatibility analysis — preview how HTML emails render across 15 email clients (Gmail, Outlook, Apple Mail, Yahoo, Samsung, Thunderbird, HEY, Superhuman).
|
|
7
|
+
|
|
8
|
+
> **Prefer AI?** Use the [MCP server](https://github.com/emailens/mcp) — same engine, works with Claude, Cursor, and any MCP client.
|
|
4
9
|
|
|
5
10
|
## Install
|
|
6
11
|
|
|
@@ -74,9 +79,75 @@ cat email.html | emailens fix - --format jsx # Pipe from stdin
|
|
|
74
79
|
| `--estimate` | | Only show token estimate without calling the AI |
|
|
75
80
|
| `--max-tokens` | | Maximum input tokens for the prompt (default: 16000) |
|
|
76
81
|
|
|
82
|
+
### `emailens lint <file|glob>`
|
|
83
|
+
|
|
84
|
+
CI/CD-friendly linting with structured exit codes. Flattens all audit checks (compatibility, content hygiene, links, accessibility, images, inbox preview, size, template variables) into a unified issue list.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
emailens lint email.html
|
|
88
|
+
emailens lint src/*.html
|
|
89
|
+
emailens lint email.html --json
|
|
90
|
+
emailens lint email.html --fail-on-warning
|
|
91
|
+
emailens lint email.html --skip spam,links
|
|
92
|
+
emailens lint email.html --max-warnings 5
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Flag | Alias | Description |
|
|
96
|
+
|------|-------|-------------|
|
|
97
|
+
| `--format` | `-f` | Input format: `html`, `jsx`, `mjml`, `maizzle` |
|
|
98
|
+
| `--json` | | Output as JSON |
|
|
99
|
+
| `--fail-on-warning` | | Exit 2 if warnings found |
|
|
100
|
+
| `--skip` | | Comma-separated checks to skip: `spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables` |
|
|
101
|
+
| `--max-warnings` | | Fail if more than n warnings |
|
|
102
|
+
|
|
103
|
+
**Exit codes:**
|
|
104
|
+
- `0` — clean
|
|
105
|
+
- `1` — errors found
|
|
106
|
+
- `2` — warnings only (with `--fail-on-warning` or `--max-warnings` exceeded)
|
|
107
|
+
|
|
108
|
+
**Output format:**
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
src/emails/welcome.html
|
|
112
|
+
error outlook-windows border-radius Not supported in Outlook Windows
|
|
113
|
+
warn spam caps-ratio 20%+ of words are ALL CAPS
|
|
114
|
+
|
|
115
|
+
src/emails/newsletter.html
|
|
116
|
+
pass No issues found
|
|
117
|
+
|
|
118
|
+
2 files | 1 error | 1 warning
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### CI / GitHub Actions
|
|
122
|
+
|
|
123
|
+
Drop this into `.github/workflows/email-lint.yml` to fail PRs that introduce broken email CSS, spam triggers, or accessibility regressions:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
name: Email lint
|
|
127
|
+
|
|
128
|
+
on:
|
|
129
|
+
pull_request:
|
|
130
|
+
paths:
|
|
131
|
+
- 'emails/**'
|
|
132
|
+
- 'src/emails/**'
|
|
133
|
+
|
|
134
|
+
jobs:
|
|
135
|
+
lint:
|
|
136
|
+
runs-on: ubuntu-latest
|
|
137
|
+
steps:
|
|
138
|
+
- uses: actions/checkout@v4
|
|
139
|
+
- uses: actions/setup-node@v4
|
|
140
|
+
with:
|
|
141
|
+
node-version: 22
|
|
142
|
+
- name: Lint emails
|
|
143
|
+
run: npx -y @emailens/cli lint 'emails/**/*.{html,tsx,mjml}' --fail-on-warning
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
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.
|
|
147
|
+
|
|
77
148
|
### `emailens clients`
|
|
78
149
|
|
|
79
|
-
List all
|
|
150
|
+
List all 15 supported email clients.
|
|
80
151
|
|
|
81
152
|
```bash
|
|
82
153
|
emailens clients
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
__require
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=chunk-7OCVIDC7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
__require
|
|
4
|
+
} from "./chunk-7OCVIDC7.js";
|
|
2
5
|
|
|
3
6
|
// src/index.ts
|
|
4
|
-
import { defineCommand as
|
|
7
|
+
import { defineCommand as defineCommand8, runMain } from "citty";
|
|
5
8
|
|
|
6
9
|
// src/commands/analyze.ts
|
|
7
10
|
import { defineCommand } from "citty";
|
|
@@ -24,7 +27,7 @@ async function readInput(pathOrStdin) {
|
|
|
24
27
|
return readFileSync("/dev/stdin", "utf-8");
|
|
25
28
|
} catch {
|
|
26
29
|
}
|
|
27
|
-
return new Promise((
|
|
30
|
+
return new Promise((resolve3, reject) => {
|
|
28
31
|
const chunks = [];
|
|
29
32
|
process.stdin.resume();
|
|
30
33
|
process.stdin.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
@@ -33,7 +36,7 @@ async function readInput(pathOrStdin) {
|
|
|
33
36
|
if (!data.trim()) {
|
|
34
37
|
reject(new Error("No input received from stdin. Pipe HTML or provide a file path."));
|
|
35
38
|
} else {
|
|
36
|
-
|
|
39
|
+
resolve3(data);
|
|
37
40
|
}
|
|
38
41
|
});
|
|
39
42
|
process.stdin.on("error", () => {
|
|
@@ -394,7 +397,7 @@ var preview_default = defineCommand2({
|
|
|
394
397
|
const outDir = args.out ?? "./emailens-screenshots";
|
|
395
398
|
spinner?.start("Capturing screenshots...");
|
|
396
399
|
try {
|
|
397
|
-
const { captureScreenshots } = await import("./screenshots-
|
|
400
|
+
const { captureScreenshots } = await import("./screenshots-UOLRXFAJ.js");
|
|
398
401
|
const results = await captureScreenshots(
|
|
399
402
|
transforms,
|
|
400
403
|
outDir,
|
|
@@ -566,6 +569,11 @@ function escapeHtml(str) {
|
|
|
566
569
|
}
|
|
567
570
|
|
|
568
571
|
// src/commands/export.ts
|
|
572
|
+
var toPlainText = null;
|
|
573
|
+
try {
|
|
574
|
+
({ toPlainText } = __require("@emailens/engine"));
|
|
575
|
+
} catch {
|
|
576
|
+
}
|
|
569
577
|
var export_default = defineCommand4({
|
|
570
578
|
meta: {
|
|
571
579
|
name: "export",
|
|
@@ -605,6 +613,10 @@ var export_default = defineCommand4({
|
|
|
605
613
|
type: "boolean",
|
|
606
614
|
description: "Export as JSON instead of HTML"
|
|
607
615
|
},
|
|
616
|
+
"plain-text": {
|
|
617
|
+
type: "boolean",
|
|
618
|
+
description: "Also export a plain text version (.txt) for multipart emails"
|
|
619
|
+
},
|
|
608
620
|
quiet: {
|
|
609
621
|
type: "boolean",
|
|
610
622
|
alias: "q",
|
|
@@ -650,7 +662,7 @@ var export_default = defineCommand4({
|
|
|
650
662
|
if (args.screenshots) {
|
|
651
663
|
spinner?.start("Capturing screenshots...");
|
|
652
664
|
try {
|
|
653
|
-
const { captureScreenshots } = await import("./screenshots-
|
|
665
|
+
const { captureScreenshots } = await import("./screenshots-UOLRXFAJ.js");
|
|
654
666
|
const screenshotDir = join(outDir, "screenshots");
|
|
655
667
|
const results = await captureScreenshots(
|
|
656
668
|
transforms,
|
|
@@ -684,6 +696,15 @@ var export_default = defineCommand4({
|
|
|
684
696
|
writeFileSync(htmlPath, htmlReport);
|
|
685
697
|
spinner?.succeed(`HTML report saved to ${htmlPath}`);
|
|
686
698
|
}
|
|
699
|
+
if (args["plain-text"] && toPlainText) {
|
|
700
|
+
spinner?.start("Generating plain text version...");
|
|
701
|
+
const plainText = toPlainText(html);
|
|
702
|
+
const txtPath = join(outDir, "email.txt");
|
|
703
|
+
writeFileSync(txtPath, plainText);
|
|
704
|
+
spinner?.succeed(`Plain text saved to ${txtPath}`);
|
|
705
|
+
} else if (args["plain-text"] && !toPlainText) {
|
|
706
|
+
spinner?.warn("Plain text export requires @emailens/engine >=0.8.6");
|
|
707
|
+
}
|
|
687
708
|
if (!args.quiet) {
|
|
688
709
|
console.log();
|
|
689
710
|
console.log(` Output: ${outDir}`);
|
|
@@ -840,7 +861,7 @@ var fix_default = defineCommand5({
|
|
|
840
861
|
spinner?.start("Generating AI fix...");
|
|
841
862
|
let Anthropic;
|
|
842
863
|
try {
|
|
843
|
-
Anthropic = (await import("./sdk-
|
|
864
|
+
Anthropic = (await import("./sdk-IYJIYBYM.js")).default;
|
|
844
865
|
} catch {
|
|
845
866
|
spinner?.fail("@anthropic-ai/sdk is required for the fix command");
|
|
846
867
|
console.error(pc3.dim("\n Install it with: npm install @anthropic-ai/sdk\n"));
|
|
@@ -990,7 +1011,7 @@ function printQualitySummary(report, skip, options) {
|
|
|
990
1011
|
if (!skip.includes("spam")) {
|
|
991
1012
|
const { score, level, issues } = report.spam;
|
|
992
1013
|
const color = score >= 90 ? pc4.green : score >= 70 ? pc4.yellow : pc4.red;
|
|
993
|
-
table.push(["
|
|
1014
|
+
table.push(["Content Hygiene", `${color(String(score))}/100 (${level}) \u2014 ${issues.length} issue${issues.length === 1 ? "" : "s"}`]);
|
|
994
1015
|
}
|
|
995
1016
|
if (!skip.includes("links")) {
|
|
996
1017
|
const { totalLinks, issues } = report.links;
|
|
@@ -1011,11 +1032,275 @@ function printQualitySummary(report, skip, options) {
|
|
|
1011
1032
|
console.log();
|
|
1012
1033
|
}
|
|
1013
1034
|
|
|
1035
|
+
// src/commands/lint.ts
|
|
1036
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
1037
|
+
import pc5 from "picocolors";
|
|
1038
|
+
import { compile as compile6 } from "@emailens/engine/compile";
|
|
1039
|
+
import {
|
|
1040
|
+
auditEmail as auditEmail2,
|
|
1041
|
+
CompileError as CompileError6
|
|
1042
|
+
} from "@emailens/engine";
|
|
1043
|
+
import { readdir } from "fs/promises";
|
|
1044
|
+
import { resolve as resolve2, relative } from "path";
|
|
1045
|
+
var VALID_SKIPS2 = /* @__PURE__ */ new Set([
|
|
1046
|
+
"spam",
|
|
1047
|
+
"links",
|
|
1048
|
+
"accessibility",
|
|
1049
|
+
"images",
|
|
1050
|
+
"compatibility",
|
|
1051
|
+
"inboxPreview",
|
|
1052
|
+
"size",
|
|
1053
|
+
"templateVariables"
|
|
1054
|
+
]);
|
|
1055
|
+
var lint_default = defineCommand7({
|
|
1056
|
+
meta: {
|
|
1057
|
+
name: "lint",
|
|
1058
|
+
description: "Lint HTML emails for compatibility, spam, accessibility, and more. CI/CD-friendly with structured exit codes."
|
|
1059
|
+
},
|
|
1060
|
+
args: {
|
|
1061
|
+
input: {
|
|
1062
|
+
type: "positional",
|
|
1063
|
+
description: "HTML file path or glob pattern",
|
|
1064
|
+
required: true
|
|
1065
|
+
},
|
|
1066
|
+
format: {
|
|
1067
|
+
type: "string",
|
|
1068
|
+
alias: "f",
|
|
1069
|
+
description: "Input format: html, jsx, mjml, maizzle"
|
|
1070
|
+
},
|
|
1071
|
+
json: {
|
|
1072
|
+
type: "boolean",
|
|
1073
|
+
description: "Output as JSON"
|
|
1074
|
+
},
|
|
1075
|
+
failOnWarning: {
|
|
1076
|
+
type: "boolean",
|
|
1077
|
+
description: "Exit 2 if warnings found"
|
|
1078
|
+
},
|
|
1079
|
+
skip: {
|
|
1080
|
+
type: "string",
|
|
1081
|
+
description: "Comma-separated checks to skip: spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables"
|
|
1082
|
+
},
|
|
1083
|
+
maxWarnings: {
|
|
1084
|
+
type: "string",
|
|
1085
|
+
description: "Fail if more than n warnings"
|
|
1086
|
+
}
|
|
1087
|
+
},
|
|
1088
|
+
async run({ args }) {
|
|
1089
|
+
try {
|
|
1090
|
+
const skip = [];
|
|
1091
|
+
if (args.skip) {
|
|
1092
|
+
for (const s of args.skip.split(",").map((s2) => s2.trim()).filter(Boolean)) {
|
|
1093
|
+
if (!VALID_SKIPS2.has(s)) {
|
|
1094
|
+
throw new Error(`Unknown skip value "${s}". Valid: ${Array.from(VALID_SKIPS2).join(", ")}`);
|
|
1095
|
+
}
|
|
1096
|
+
skip.push(s);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
const maxWarningsRaw = args.maxWarnings !== void 0 ? parseInt(args.maxWarnings, 10) : void 0;
|
|
1100
|
+
if (maxWarningsRaw !== void 0 && isNaN(maxWarningsRaw)) {
|
|
1101
|
+
throw new Error(`--maxWarnings must be a number, got: ${args.maxWarnings}`);
|
|
1102
|
+
}
|
|
1103
|
+
const maxWarnings = maxWarningsRaw ?? Infinity;
|
|
1104
|
+
const files = await resolveGlob(args.input);
|
|
1105
|
+
if (files.length === 0) {
|
|
1106
|
+
if (args.json) {
|
|
1107
|
+
console.log(JSON.stringify({ files: [], totalErrors: 0, totalWarnings: 0 }));
|
|
1108
|
+
} else {
|
|
1109
|
+
console.error(pc5.red(`No files found matching: ${args.input}`));
|
|
1110
|
+
}
|
|
1111
|
+
process.exit(1);
|
|
1112
|
+
}
|
|
1113
|
+
const results = [];
|
|
1114
|
+
let totalErrors = 0;
|
|
1115
|
+
let totalWarnings = 0;
|
|
1116
|
+
for (const file of files) {
|
|
1117
|
+
const format = resolveFormat(args.format, file);
|
|
1118
|
+
const source = await readInput(file);
|
|
1119
|
+
const html = await compile6(source, format, file);
|
|
1120
|
+
const framework = toFramework(format);
|
|
1121
|
+
const report = auditEmail2(html, {
|
|
1122
|
+
framework,
|
|
1123
|
+
skip
|
|
1124
|
+
});
|
|
1125
|
+
const issues = flattenToLintIssues(report, skip);
|
|
1126
|
+
const errors = issues.filter((i) => i.severity === "error").length;
|
|
1127
|
+
const warnings = issues.filter((i) => i.severity === "warning").length;
|
|
1128
|
+
results.push({ file: relative(process.cwd(), file), issues, errors, warnings });
|
|
1129
|
+
totalErrors += errors;
|
|
1130
|
+
totalWarnings += warnings;
|
|
1131
|
+
}
|
|
1132
|
+
if (args.json) {
|
|
1133
|
+
console.log(JSON.stringify({ files: results, totalErrors, totalWarnings }, null, 2));
|
|
1134
|
+
} else {
|
|
1135
|
+
for (const result of results) {
|
|
1136
|
+
console.log(pc5.underline(result.file));
|
|
1137
|
+
if (result.issues.length === 0) {
|
|
1138
|
+
console.log(` ${pc5.green("pass")} No issues found`);
|
|
1139
|
+
} else {
|
|
1140
|
+
for (const issue of result.issues) {
|
|
1141
|
+
const sev = issue.severity === "error" ? pc5.red("error") : issue.severity === "warning" ? pc5.yellow("warn ") : pc5.blue("info ");
|
|
1142
|
+
console.log(` ${sev} ${pc5.dim(issue.category.padEnd(18))} ${pc5.bold(issue.rule.padEnd(22))} ${issue.message}`);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
console.log();
|
|
1146
|
+
}
|
|
1147
|
+
const summary = [
|
|
1148
|
+
`${files.length} file${files.length === 1 ? "" : "s"}`,
|
|
1149
|
+
totalErrors > 0 ? pc5.red(`${totalErrors} error${totalErrors === 1 ? "" : "s"}`) : null,
|
|
1150
|
+
totalWarnings > 0 ? pc5.yellow(`${totalWarnings} warning${totalWarnings === 1 ? "" : "s"}`) : null,
|
|
1151
|
+
totalErrors === 0 && totalWarnings === 0 ? pc5.green("all clean") : null
|
|
1152
|
+
].filter(Boolean).join(" | ");
|
|
1153
|
+
console.log(summary);
|
|
1154
|
+
}
|
|
1155
|
+
if (totalErrors > 0) {
|
|
1156
|
+
process.exit(1);
|
|
1157
|
+
}
|
|
1158
|
+
if (args.failOnWarning && totalWarnings > 0) {
|
|
1159
|
+
if (!args.json) {
|
|
1160
|
+
console.error(pc5.yellow(`
|
|
1161
|
+
Failed: ${totalWarnings} warning${totalWarnings === 1 ? "" : "s"} found (--failOnWarning)`));
|
|
1162
|
+
}
|
|
1163
|
+
process.exit(2);
|
|
1164
|
+
}
|
|
1165
|
+
if (Number.isFinite(maxWarnings) && totalWarnings > maxWarnings) {
|
|
1166
|
+
if (!args.json) {
|
|
1167
|
+
console.error(pc5.red(`
|
|
1168
|
+
Exceeded max warnings: ${totalWarnings} > ${maxWarnings}`));
|
|
1169
|
+
}
|
|
1170
|
+
process.exit(2);
|
|
1171
|
+
}
|
|
1172
|
+
} catch (err) {
|
|
1173
|
+
if (err instanceof CompileError6) {
|
|
1174
|
+
console.error(pc5.red(`Compilation failed (${err.phase}): ${err.message}`));
|
|
1175
|
+
} else {
|
|
1176
|
+
console.error(pc5.red(err.message));
|
|
1177
|
+
}
|
|
1178
|
+
process.exit(1);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
async function resolveGlob(pattern) {
|
|
1183
|
+
if (!pattern.includes("*")) {
|
|
1184
|
+
return [resolve2(pattern)];
|
|
1185
|
+
}
|
|
1186
|
+
if (pattern.includes("**")) {
|
|
1187
|
+
throw new Error("Recursive glob (**) is not supported. Use a single-level wildcard (e.g., src/*.html) or list files explicitly.");
|
|
1188
|
+
}
|
|
1189
|
+
const lastSep = Math.max(pattern.lastIndexOf("/"), pattern.lastIndexOf("\\"));
|
|
1190
|
+
const dir = lastSep >= 0 ? pattern.slice(0, lastSep) : ".";
|
|
1191
|
+
const glob = lastSep >= 0 ? pattern.slice(lastSep + 1) : pattern;
|
|
1192
|
+
const regex = new RegExp("^" + glob.replace(/\*/g, ".*") + "$");
|
|
1193
|
+
const entries = await readdir(resolve2(dir), { withFileTypes: true });
|
|
1194
|
+
return entries.filter((e) => e.isFile() && regex.test(e.name)).map((e) => resolve2(dir, e.name)).sort();
|
|
1195
|
+
}
|
|
1196
|
+
function flattenToLintIssues(report, skip) {
|
|
1197
|
+
const issues = [];
|
|
1198
|
+
if (!skip.includes("compatibility")) {
|
|
1199
|
+
const seenProps = /* @__PURE__ */ new Map();
|
|
1200
|
+
for (const w of report.compatibility.warnings) {
|
|
1201
|
+
const key = `${w.property}\0${w.severity}`;
|
|
1202
|
+
const existing = seenProps.get(key);
|
|
1203
|
+
if (existing) {
|
|
1204
|
+
existing.clients.push(w.client);
|
|
1205
|
+
} else {
|
|
1206
|
+
seenProps.set(key, {
|
|
1207
|
+
severity: w.severity,
|
|
1208
|
+
clients: [w.client],
|
|
1209
|
+
message: w.message
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
for (const [key, val] of seenProps) {
|
|
1214
|
+
const property = key.split("\0")[0];
|
|
1215
|
+
issues.push({
|
|
1216
|
+
severity: val.severity,
|
|
1217
|
+
category: val.clients.slice(0, 3).join(",") + (val.clients.length > 3 ? `+${val.clients.length - 3}` : ""),
|
|
1218
|
+
rule: property,
|
|
1219
|
+
message: val.message
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
if (!skip.includes("spam")) {
|
|
1224
|
+
for (const issue of report.spam.issues) {
|
|
1225
|
+
issues.push({
|
|
1226
|
+
severity: issue.severity,
|
|
1227
|
+
category: "spam",
|
|
1228
|
+
rule: issue.rule,
|
|
1229
|
+
message: issue.message,
|
|
1230
|
+
detail: issue.detail
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if (!skip.includes("links")) {
|
|
1235
|
+
for (const issue of report.links.issues) {
|
|
1236
|
+
issues.push({
|
|
1237
|
+
severity: issue.severity,
|
|
1238
|
+
category: "links",
|
|
1239
|
+
rule: issue.rule,
|
|
1240
|
+
message: issue.message
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (!skip.includes("accessibility")) {
|
|
1245
|
+
for (const issue of report.accessibility.issues) {
|
|
1246
|
+
issues.push({
|
|
1247
|
+
severity: issue.severity,
|
|
1248
|
+
category: "accessibility",
|
|
1249
|
+
rule: issue.rule,
|
|
1250
|
+
message: issue.message
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (!skip.includes("images")) {
|
|
1255
|
+
for (const issue of report.images.issues) {
|
|
1256
|
+
issues.push({
|
|
1257
|
+
severity: issue.severity,
|
|
1258
|
+
category: "images",
|
|
1259
|
+
rule: issue.rule,
|
|
1260
|
+
message: issue.message
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
if (!skip.includes("inboxPreview")) {
|
|
1265
|
+
for (const issue of report.inboxPreview.issues) {
|
|
1266
|
+
issues.push({
|
|
1267
|
+
severity: issue.severity,
|
|
1268
|
+
category: "inboxPreview",
|
|
1269
|
+
rule: issue.rule,
|
|
1270
|
+
message: issue.message
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
if (!skip.includes("size")) {
|
|
1275
|
+
for (const issue of report.size.issues) {
|
|
1276
|
+
issues.push({
|
|
1277
|
+
severity: issue.severity,
|
|
1278
|
+
category: "size",
|
|
1279
|
+
rule: issue.rule,
|
|
1280
|
+
message: issue.message
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (!skip.includes("templateVariables")) {
|
|
1285
|
+
for (const issue of report.templateVariables.issues) {
|
|
1286
|
+
issues.push({
|
|
1287
|
+
severity: issue.severity,
|
|
1288
|
+
category: "templateVars",
|
|
1289
|
+
rule: issue.rule,
|
|
1290
|
+
message: issue.message
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
const order = { error: 0, warning: 1, info: 2 };
|
|
1295
|
+
issues.sort((a, b) => order[a.severity] - order[b.severity]);
|
|
1296
|
+
return issues;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1014
1299
|
// src/index.ts
|
|
1015
|
-
var main =
|
|
1300
|
+
var main = defineCommand8({
|
|
1016
1301
|
meta: {
|
|
1017
1302
|
name: "emailens",
|
|
1018
|
-
version: "0.
|
|
1303
|
+
version: "0.4.0",
|
|
1019
1304
|
description: "Email compatibility analysis CLI \u2014 preview how HTML emails render across 12 email clients."
|
|
1020
1305
|
},
|
|
1021
1306
|
subCommands: {
|
|
@@ -1024,7 +1309,8 @@ var main = defineCommand7({
|
|
|
1024
1309
|
clients: clients_default,
|
|
1025
1310
|
export: export_default,
|
|
1026
1311
|
fix: fix_default,
|
|
1027
|
-
audit: audit_default
|
|
1312
|
+
audit: audit_default,
|
|
1313
|
+
lint: lint_default
|
|
1028
1314
|
}
|
|
1029
1315
|
});
|
|
1030
1316
|
runMain(main);
|