@emailens/cli 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -188
- package/dist/index.js +142 -81
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { EMAIL_CLIENTS } from "@emailens/engine";
|
|
|
12
12
|
// package.json
|
|
13
13
|
var package_default = {
|
|
14
14
|
name: "@emailens/cli",
|
|
15
|
-
version: "0.
|
|
15
|
+
version: "0.7.1",
|
|
16
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: {
|
|
@@ -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.14.2",
|
|
43
43
|
citty: "^0.1.6",
|
|
44
44
|
"cli-table3": "^0.6.5",
|
|
45
45
|
ora: "^8.2.0",
|
|
@@ -99,7 +99,6 @@ import ora from "ora";
|
|
|
99
99
|
import {
|
|
100
100
|
analyzeEmail,
|
|
101
101
|
generateCompatibilityScore,
|
|
102
|
-
warningsForClient,
|
|
103
102
|
CompileError
|
|
104
103
|
} from "@emailens/engine";
|
|
105
104
|
|
|
@@ -153,7 +152,10 @@ Valid IDs: ${Array.from(validIds).join(", ")}`
|
|
|
153
152
|
}
|
|
154
153
|
return requested;
|
|
155
154
|
}
|
|
156
|
-
function
|
|
155
|
+
function isVueSfc(source) {
|
|
156
|
+
return /<template[\s>]/i.test(source) && /<\/template>/i.test(source);
|
|
157
|
+
}
|
|
158
|
+
function resolveFormat(formatFlag, filePath, source) {
|
|
157
159
|
if (formatFlag) {
|
|
158
160
|
if (!VALID_FORMATS.has(formatFlag)) {
|
|
159
161
|
throw new Error(
|
|
@@ -165,6 +167,7 @@ function resolveFormat(formatFlag, filePath) {
|
|
|
165
167
|
if (filePath && filePath !== "-") {
|
|
166
168
|
return detectFormat(filePath);
|
|
167
169
|
}
|
|
170
|
+
if (source && isVueSfc(source)) return "maizzle";
|
|
168
171
|
return "html";
|
|
169
172
|
}
|
|
170
173
|
function toFramework(format) {
|
|
@@ -174,6 +177,15 @@ function toFramework(format) {
|
|
|
174
177
|
function positionsApply(format) {
|
|
175
178
|
return format === "html";
|
|
176
179
|
}
|
|
180
|
+
var TARGETING_POLICY_ARG = {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Client-targeting policy: progressive (default), strict, or lenient"
|
|
183
|
+
};
|
|
184
|
+
function parseTargetingPolicy(raw) {
|
|
185
|
+
if (raw == null || raw === "") return void 0;
|
|
186
|
+
if (raw === "progressive" || raw === "strict" || raw === "lenient") return raw;
|
|
187
|
+
throw new Error(`Unknown targeting policy "${raw}". Valid: progressive, strict, lenient`);
|
|
188
|
+
}
|
|
177
189
|
|
|
178
190
|
// src/commands/analyze.ts
|
|
179
191
|
import { compile } from "@emailens/engine/compile";
|
|
@@ -184,6 +196,57 @@ import Table from "cli-table3";
|
|
|
184
196
|
import {
|
|
185
197
|
EMAIL_CLIENTS as EMAIL_CLIENTS3
|
|
186
198
|
} from "@emailens/engine";
|
|
199
|
+
|
|
200
|
+
// src/output/json.ts
|
|
201
|
+
function worstClientScore(scores) {
|
|
202
|
+
const values = Object.values(scores);
|
|
203
|
+
if (values.length === 0) return 0;
|
|
204
|
+
return Math.min(...values.map((s) => s.score));
|
|
205
|
+
}
|
|
206
|
+
function formatJsonOutput(opts) {
|
|
207
|
+
const overallScore = worstClientScore(opts.scores);
|
|
208
|
+
const output = {
|
|
209
|
+
overallScore,
|
|
210
|
+
scores: opts.scores,
|
|
211
|
+
warnings: opts.warnings.map((w) => ({
|
|
212
|
+
client: w.client,
|
|
213
|
+
property: w.property,
|
|
214
|
+
severity: w.severity,
|
|
215
|
+
message: w.message,
|
|
216
|
+
suggestion: w.suggestion,
|
|
217
|
+
fix: w.fix ? {
|
|
218
|
+
before: w.fix.before,
|
|
219
|
+
after: w.fix.after,
|
|
220
|
+
language: w.fix.language,
|
|
221
|
+
description: w.fix.description
|
|
222
|
+
} : void 0,
|
|
223
|
+
loc: w.loc,
|
|
224
|
+
locs: w.locs,
|
|
225
|
+
locsTruncated: w.locsTruncated
|
|
226
|
+
}))
|
|
227
|
+
};
|
|
228
|
+
if (opts.transforms) {
|
|
229
|
+
output.transforms = opts.transforms.map((t) => ({
|
|
230
|
+
clientId: t.clientId,
|
|
231
|
+
html: t.html
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
if (opts.darkMode) {
|
|
235
|
+
output.darkMode = {};
|
|
236
|
+
for (const [clientId, dm] of Object.entries(opts.darkMode)) {
|
|
237
|
+
output.darkMode[clientId] = {
|
|
238
|
+
html: dm.html,
|
|
239
|
+
warningCount: dm.warnings.length
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return output;
|
|
244
|
+
}
|
|
245
|
+
function printJson(opts) {
|
|
246
|
+
console.log(JSON.stringify(formatJsonOutput(opts), null, 2));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/output/terminal.ts
|
|
187
250
|
function colorScore(score) {
|
|
188
251
|
const str = String(score);
|
|
189
252
|
if (score >= 90) return pc.green(str);
|
|
@@ -242,8 +305,7 @@ function printScoreTable(scores, warnings, options) {
|
|
|
242
305
|
]);
|
|
243
306
|
}
|
|
244
307
|
console.log(table.toString());
|
|
245
|
-
const
|
|
246
|
-
const overall = scoreValues.length > 0 ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length) : 0;
|
|
308
|
+
const overall = worstClientScore(scores);
|
|
247
309
|
console.log();
|
|
248
310
|
console.log(` Overall: ${colorScore(overall)}/100`);
|
|
249
311
|
console.log();
|
|
@@ -284,51 +346,6 @@ function printWarnings(warnings, options) {
|
|
|
284
346
|
}
|
|
285
347
|
}
|
|
286
348
|
|
|
287
|
-
// src/output/json.ts
|
|
288
|
-
function formatJsonOutput(opts) {
|
|
289
|
-
const scoreValues = Object.values(opts.scores);
|
|
290
|
-
const overallScore = scoreValues.length > 0 ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length) : 0;
|
|
291
|
-
const output = {
|
|
292
|
-
overallScore,
|
|
293
|
-
scores: opts.scores,
|
|
294
|
-
warnings: opts.warnings.map((w) => ({
|
|
295
|
-
client: w.client,
|
|
296
|
-
property: w.property,
|
|
297
|
-
severity: w.severity,
|
|
298
|
-
message: w.message,
|
|
299
|
-
suggestion: w.suggestion,
|
|
300
|
-
fix: w.fix ? {
|
|
301
|
-
before: w.fix.before,
|
|
302
|
-
after: w.fix.after,
|
|
303
|
-
language: w.fix.language,
|
|
304
|
-
description: w.fix.description
|
|
305
|
-
} : void 0,
|
|
306
|
-
loc: w.loc,
|
|
307
|
-
locs: w.locs,
|
|
308
|
-
locsTruncated: w.locsTruncated
|
|
309
|
-
}))
|
|
310
|
-
};
|
|
311
|
-
if (opts.transforms) {
|
|
312
|
-
output.transforms = opts.transforms.map((t) => ({
|
|
313
|
-
clientId: t.clientId,
|
|
314
|
-
html: t.html
|
|
315
|
-
}));
|
|
316
|
-
}
|
|
317
|
-
if (opts.darkMode) {
|
|
318
|
-
output.darkMode = {};
|
|
319
|
-
for (const [clientId, dm] of Object.entries(opts.darkMode)) {
|
|
320
|
-
output.darkMode[clientId] = {
|
|
321
|
-
html: dm.html,
|
|
322
|
-
warningCount: dm.warnings.length
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
return output;
|
|
327
|
-
}
|
|
328
|
-
function printJson(opts) {
|
|
329
|
-
console.log(JSON.stringify(formatJsonOutput(opts), null, 2));
|
|
330
|
-
}
|
|
331
|
-
|
|
332
349
|
// src/commands/analyze.ts
|
|
333
350
|
var analyze_default = defineCommand({
|
|
334
351
|
meta: {
|
|
@@ -359,20 +376,25 @@ var analyze_default = defineCommand({
|
|
|
359
376
|
type: "boolean",
|
|
360
377
|
alias: "q",
|
|
361
378
|
description: "Suppress spinners and decorations"
|
|
362
|
-
}
|
|
379
|
+
},
|
|
380
|
+
targetingPolicy: TARGETING_POLICY_ARG
|
|
363
381
|
},
|
|
364
382
|
async run({ args }) {
|
|
365
383
|
const spinner = args.quiet || args.json ? null : ora();
|
|
366
384
|
try {
|
|
367
|
-
|
|
385
|
+
if (args.format) resolveFormat(args.format);
|
|
368
386
|
spinner?.start("Reading input...");
|
|
369
387
|
const source = await readInput(args.input);
|
|
370
388
|
spinner?.succeed("Input read");
|
|
389
|
+
const format = resolveFormat(args.format, args.input, source);
|
|
371
390
|
const html = await compile(source, format);
|
|
372
391
|
const clientIds = resolveClients(args.clients);
|
|
373
392
|
spinner?.start("Analyzing compatibility...");
|
|
374
393
|
const framework = toFramework(format);
|
|
375
|
-
const warnings = analyzeEmail(html, framework, {
|
|
394
|
+
const warnings = analyzeEmail(html, framework, {
|
|
395
|
+
positions: positionsApply(format),
|
|
396
|
+
targetingPolicy: parseTargetingPolicy(args.targetingPolicy)
|
|
397
|
+
});
|
|
376
398
|
const allScores = generateCompatibilityScore(warnings);
|
|
377
399
|
const clientSet = new Set(clientIds);
|
|
378
400
|
const scores = {};
|
|
@@ -381,7 +403,7 @@ var analyze_default = defineCommand({
|
|
|
381
403
|
scores[id] = data;
|
|
382
404
|
}
|
|
383
405
|
}
|
|
384
|
-
const filteredWarnings = clientIds.flatMap((id) =>
|
|
406
|
+
const filteredWarnings = clientIds.flatMap((id) => warnings.filter((w) => w.client === id));
|
|
385
407
|
spinner?.succeed("Analysis complete");
|
|
386
408
|
if (args.json) {
|
|
387
409
|
printJson({ scores, warnings: filteredWarnings });
|
|
@@ -408,7 +430,6 @@ import {
|
|
|
408
430
|
analyzeEmail as analyzeEmail2,
|
|
409
431
|
generateCompatibilityScore as generateCompatibilityScore2,
|
|
410
432
|
simulateDarkMode,
|
|
411
|
-
warningsForClient as warningsForClient2,
|
|
412
433
|
CompileError as CompileError2
|
|
413
434
|
} from "@emailens/engine";
|
|
414
435
|
import { compile as compile2 } from "@emailens/engine/compile";
|
|
@@ -455,15 +476,17 @@ var preview_default = defineCommand2({
|
|
|
455
476
|
type: "boolean",
|
|
456
477
|
alias: "q",
|
|
457
478
|
description: "Suppress spinners and decorations"
|
|
458
|
-
}
|
|
479
|
+
},
|
|
480
|
+
targetingPolicy: TARGETING_POLICY_ARG
|
|
459
481
|
},
|
|
460
482
|
async run({ args }) {
|
|
461
483
|
const spinner = args.quiet || args.json ? null : ora2();
|
|
462
484
|
try {
|
|
463
|
-
|
|
485
|
+
if (args.format) resolveFormat(args.format);
|
|
464
486
|
spinner?.start("Reading input...");
|
|
465
487
|
const source = await readInput(args.input);
|
|
466
488
|
spinner?.succeed("Input read");
|
|
489
|
+
const format = resolveFormat(args.format, args.input, source);
|
|
467
490
|
const html = await compile2(source, format);
|
|
468
491
|
const clientIds = resolveClients(args.clients);
|
|
469
492
|
const clientSet = new Set(clientIds);
|
|
@@ -473,7 +496,9 @@ var preview_default = defineCommand2({
|
|
|
473
496
|
spinner?.succeed(`Transformed for ${transforms.length} clients`);
|
|
474
497
|
spinner?.start("Analyzing compatibility...");
|
|
475
498
|
const framework = toFramework(format);
|
|
476
|
-
const warnings = analyzeEmail2(html, framework
|
|
499
|
+
const warnings = analyzeEmail2(html, framework, {
|
|
500
|
+
targetingPolicy: parseTargetingPolicy(args.targetingPolicy)
|
|
501
|
+
});
|
|
477
502
|
const allScores = generateCompatibilityScore2(warnings);
|
|
478
503
|
const scores = {};
|
|
479
504
|
for (const [id, data] of Object.entries(allScores)) {
|
|
@@ -481,7 +506,7 @@ var preview_default = defineCommand2({
|
|
|
481
506
|
scores[id] = data;
|
|
482
507
|
}
|
|
483
508
|
}
|
|
484
|
-
const filteredWarnings = clientIds.flatMap((id) =>
|
|
509
|
+
const filteredWarnings = clientIds.flatMap((id) => warnings.filter((w) => w.client === id));
|
|
485
510
|
spinner?.succeed("Analysis complete");
|
|
486
511
|
let darkMode;
|
|
487
512
|
if (args["dark-mode"]) {
|
|
@@ -593,7 +618,6 @@ import {
|
|
|
593
618
|
analyzeEmail as analyzeEmail3,
|
|
594
619
|
generateCompatibilityScore as generateCompatibilityScore3,
|
|
595
620
|
simulateDarkMode as simulateDarkMode2,
|
|
596
|
-
warningsForClient as warningsForClient3,
|
|
597
621
|
CompileError as CompileError3
|
|
598
622
|
} from "@emailens/engine";
|
|
599
623
|
import { compile as compile3 } from "@emailens/engine/compile";
|
|
@@ -602,8 +626,7 @@ import { compile as compile3 } from "@emailens/engine/compile";
|
|
|
602
626
|
import { EMAIL_CLIENTS as EMAIL_CLIENTS5 } from "@emailens/engine";
|
|
603
627
|
function generateHtmlReport(opts) {
|
|
604
628
|
const clientMap = new Map(EMAIL_CLIENTS5.map((c) => [c.id, c]));
|
|
605
|
-
const
|
|
606
|
-
const overallScore = scoreValues.length > 0 ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length) : 0;
|
|
629
|
+
const overallScore = worstClientScore(opts.scores);
|
|
607
630
|
const scoreColor = (s) => s >= 90 ? "#22c55e" : s >= 70 ? "#eab308" : "#ef4444";
|
|
608
631
|
const clientCards = Object.entries(opts.scores).sort(([, a], [, b]) => a.score - b.score).map(([clientId, { score, errors, warnings: warns, info }]) => {
|
|
609
632
|
const client = clientMap.get(clientId);
|
|
@@ -720,16 +743,18 @@ var export_default = defineCommand4({
|
|
|
720
743
|
type: "boolean",
|
|
721
744
|
alias: "q",
|
|
722
745
|
description: "Suppress spinners and decorations"
|
|
723
|
-
}
|
|
746
|
+
},
|
|
747
|
+
targetingPolicy: TARGETING_POLICY_ARG
|
|
724
748
|
},
|
|
725
749
|
async run({ args }) {
|
|
726
750
|
const spinner = args.quiet ? null : ora3();
|
|
727
751
|
const outDir = resolve(args.out ?? "./emailens-report");
|
|
728
752
|
try {
|
|
729
|
-
|
|
753
|
+
if (args.format) resolveFormat(args.format);
|
|
730
754
|
spinner?.start("Reading input...");
|
|
731
755
|
const source = await readInput(args.input);
|
|
732
756
|
spinner?.succeed("Input read");
|
|
757
|
+
const format = resolveFormat(args.format, args.input, source);
|
|
733
758
|
const html = await compile3(source, format);
|
|
734
759
|
const clientIds = resolveClients(args.clients);
|
|
735
760
|
const clientSet = new Set(clientIds);
|
|
@@ -739,7 +764,9 @@ var export_default = defineCommand4({
|
|
|
739
764
|
spinner?.succeed(`Transformed for ${transforms.length} clients`);
|
|
740
765
|
spinner?.start("Analyzing compatibility...");
|
|
741
766
|
const framework = toFramework(format);
|
|
742
|
-
const warnings = analyzeEmail3(html, framework
|
|
767
|
+
const warnings = analyzeEmail3(html, framework, {
|
|
768
|
+
targetingPolicy: parseTargetingPolicy(args.targetingPolicy)
|
|
769
|
+
});
|
|
743
770
|
const allScores = generateCompatibilityScore3(warnings);
|
|
744
771
|
const scores = {};
|
|
745
772
|
for (const [id, data] of Object.entries(allScores)) {
|
|
@@ -747,7 +774,7 @@ var export_default = defineCommand4({
|
|
|
747
774
|
scores[id] = data;
|
|
748
775
|
}
|
|
749
776
|
}
|
|
750
|
-
const filteredWarnings = clientIds.flatMap((id) =>
|
|
777
|
+
const filteredWarnings = clientIds.flatMap((id) => warnings.filter((w) => w.client === id));
|
|
751
778
|
spinner?.succeed("Analysis complete");
|
|
752
779
|
let darkMode;
|
|
753
780
|
if (args["dark-mode"]) {
|
|
@@ -875,19 +902,23 @@ var fix_default = defineCommand5({
|
|
|
875
902
|
"max-tokens": {
|
|
876
903
|
type: "string",
|
|
877
904
|
description: "Maximum input tokens for the prompt (default: 16000)"
|
|
878
|
-
}
|
|
905
|
+
},
|
|
906
|
+
targetingPolicy: TARGETING_POLICY_ARG
|
|
879
907
|
},
|
|
880
908
|
async run({ args }) {
|
|
881
909
|
const spinner = args.quiet || args.json ? null : ora4();
|
|
882
910
|
try {
|
|
883
|
-
|
|
884
|
-
const framework = toFramework(format);
|
|
911
|
+
if (args.format) resolveFormat(args.format);
|
|
885
912
|
spinner?.start("Reading input...");
|
|
886
913
|
const source = await readInput(args.input);
|
|
914
|
+
const format = resolveFormat(args.format, args.input, source);
|
|
915
|
+
const framework = toFramework(format);
|
|
887
916
|
const html = await compile4(source, format);
|
|
888
917
|
spinner?.succeed("Input read");
|
|
889
918
|
spinner?.start("Analyzing compatibility...");
|
|
890
|
-
const warnings = analyzeEmail4(html, framework
|
|
919
|
+
const warnings = analyzeEmail4(html, framework, {
|
|
920
|
+
targetingPolicy: parseTargetingPolicy(args.targetingPolicy)
|
|
921
|
+
});
|
|
891
922
|
const scores = generateCompatibilityScore4(warnings);
|
|
892
923
|
spinner?.succeed(`Found ${warnings.length} warnings`);
|
|
893
924
|
if (warnings.length === 0) {
|
|
@@ -1027,11 +1058,11 @@ import {
|
|
|
1027
1058
|
CompileError as CompileError5
|
|
1028
1059
|
} from "@emailens/engine";
|
|
1029
1060
|
import { compile as compile5 } from "@emailens/engine/compile";
|
|
1030
|
-
var VALID_SKIPS = /* @__PURE__ */ new Set(["spam", "links", "accessibility", "images", "compatibility", "overflow", "visual", "darkContrast", "mobileContrast", "design", "vml", "styleSurvival"]);
|
|
1061
|
+
var VALID_SKIPS = /* @__PURE__ */ new Set(["spam", "links", "accessibility", "images", "compatibility", "overflow", "visual", "darkContrast", "mobileContrast", "design", "vml", "styleSurvival", "targeting"]);
|
|
1031
1062
|
var audit_default = defineCommand6({
|
|
1032
1063
|
meta: {
|
|
1033
1064
|
name: "audit",
|
|
1034
|
-
description: "Full email audit: compatibility + spam + links + accessibility + images + overflow + visual + contrast + design + Outlook VML"
|
|
1065
|
+
description: "Full email audit: compatibility + spam + links + accessibility + images + overflow + visual + contrast + design + Outlook VML + client targeting"
|
|
1035
1066
|
},
|
|
1036
1067
|
args: {
|
|
1037
1068
|
input: {
|
|
@@ -1055,16 +1086,18 @@ var audit_default = defineCommand6({
|
|
|
1055
1086
|
},
|
|
1056
1087
|
skip: {
|
|
1057
1088
|
type: "string",
|
|
1058
|
-
description: "Comma-separated checks to skip: spam,links,accessibility,images,compatibility,overflow,visual,darkContrast,mobileContrast,design,vml"
|
|
1059
|
-
}
|
|
1089
|
+
description: "Comma-separated checks to skip: spam,links,accessibility,images,compatibility,overflow,visual,darkContrast,mobileContrast,design,vml,styleSurvival,targeting"
|
|
1090
|
+
},
|
|
1091
|
+
targetingPolicy: TARGETING_POLICY_ARG
|
|
1060
1092
|
},
|
|
1061
1093
|
async run({ args }) {
|
|
1062
1094
|
const spinner = args.quiet || args.json ? null : ora5();
|
|
1063
1095
|
try {
|
|
1064
|
-
|
|
1096
|
+
if (args.format) resolveFormat(args.format);
|
|
1065
1097
|
spinner?.start("Reading input...");
|
|
1066
1098
|
const source = await readInput(args.input);
|
|
1067
1099
|
spinner?.succeed("Input read");
|
|
1100
|
+
const format = resolveFormat(args.format, args.input, source);
|
|
1068
1101
|
const html = await compile5(source, format);
|
|
1069
1102
|
const skip = [];
|
|
1070
1103
|
if (args.skip) {
|
|
@@ -1077,7 +1110,8 @@ var audit_default = defineCommand6({
|
|
|
1077
1110
|
}
|
|
1078
1111
|
spinner?.start("Running full audit...");
|
|
1079
1112
|
const framework = toFramework(format);
|
|
1080
|
-
const
|
|
1113
|
+
const targetingPolicy = parseTargetingPolicy(args.targetingPolicy);
|
|
1114
|
+
const report = auditEmail(html, { framework, skip, targetingPolicy });
|
|
1081
1115
|
spinner?.succeed("Audit complete");
|
|
1082
1116
|
if (args.json) {
|
|
1083
1117
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -1162,6 +1196,14 @@ function printQualitySummary(report, skip, options) {
|
|
|
1162
1196
|
const color = issues.length === 0 ? pc4.green : pc4.red;
|
|
1163
1197
|
table.push(["Style Survival", color(`${issues.length} issue${issues.length === 1 ? "" : "s"}`)]);
|
|
1164
1198
|
}
|
|
1199
|
+
if (!skip.includes("targeting")) {
|
|
1200
|
+
const { detectedHacks, warnings } = report.targeting;
|
|
1201
|
+
const color = warnings.length === 0 ? pc4.green : pc4.yellow;
|
|
1202
|
+
table.push([
|
|
1203
|
+
"Client Targeting",
|
|
1204
|
+
`${detectedHacks.length} detected, ${color(`${warnings.length} warning${warnings.length === 1 ? "" : "s"}`)}`
|
|
1205
|
+
]);
|
|
1206
|
+
}
|
|
1165
1207
|
console.log(table.toString());
|
|
1166
1208
|
console.log();
|
|
1167
1209
|
}
|
|
@@ -1261,7 +1303,8 @@ var VALID_SKIPS2 = /* @__PURE__ */ new Set([
|
|
|
1261
1303
|
"mobileContrast",
|
|
1262
1304
|
"design",
|
|
1263
1305
|
"vml",
|
|
1264
|
-
"styleSurvival"
|
|
1306
|
+
"styleSurvival",
|
|
1307
|
+
"targeting"
|
|
1265
1308
|
]);
|
|
1266
1309
|
var lint_default = defineCommand7({
|
|
1267
1310
|
meta: {
|
|
@@ -1289,8 +1332,9 @@ var lint_default = defineCommand7({
|
|
|
1289
1332
|
},
|
|
1290
1333
|
skip: {
|
|
1291
1334
|
type: "string",
|
|
1292
|
-
description: "Comma-separated checks to skip: spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables,overflow,visual,vml"
|
|
1335
|
+
description: "Comma-separated checks to skip: spam,links,accessibility,images,compatibility,inboxPreview,size,templateVariables,overflow,visual,vml,styleSurvival,targeting"
|
|
1293
1336
|
},
|
|
1337
|
+
targetingPolicy: TARGETING_POLICY_ARG,
|
|
1294
1338
|
maxWarnings: {
|
|
1295
1339
|
type: "string",
|
|
1296
1340
|
description: "Fail if more than n warnings"
|
|
@@ -1298,6 +1342,7 @@ var lint_default = defineCommand7({
|
|
|
1298
1342
|
},
|
|
1299
1343
|
async run({ args }) {
|
|
1300
1344
|
try {
|
|
1345
|
+
if (args.format) resolveFormat(args.format);
|
|
1301
1346
|
const project = loadProjectConfig();
|
|
1302
1347
|
if (project?.invalid.length && !args.json) {
|
|
1303
1348
|
console.error(
|
|
@@ -1341,15 +1386,17 @@ var lint_default = defineCommand7({
|
|
|
1341
1386
|
let totalErrors = 0;
|
|
1342
1387
|
let totalWarnings = 0;
|
|
1343
1388
|
for (const file of files) {
|
|
1344
|
-
const format = resolveFormat(args.format, file);
|
|
1345
1389
|
const source = await readInput(file);
|
|
1390
|
+
const format = resolveFormat(args.format, file, source);
|
|
1346
1391
|
const html = await compile6(source, format);
|
|
1347
1392
|
const framework = toFramework(format);
|
|
1348
1393
|
const positions = positionsApply(format);
|
|
1394
|
+
const targetingPolicy = parseTargetingPolicy(args.targetingPolicy);
|
|
1349
1395
|
const report = auditEmail2(html, {
|
|
1350
1396
|
framework,
|
|
1351
1397
|
skip,
|
|
1352
|
-
positions
|
|
1398
|
+
positions,
|
|
1399
|
+
targetingPolicy
|
|
1353
1400
|
});
|
|
1354
1401
|
const issues = applySeverities(flattenToLintIssues(report, skip), project?.rules);
|
|
1355
1402
|
const errors = issues.filter((i) => i.severity === "error").length;
|
|
@@ -1624,6 +1671,20 @@ function flattenToLintIssues(report, skip) {
|
|
|
1624
1671
|
});
|
|
1625
1672
|
}
|
|
1626
1673
|
}
|
|
1674
|
+
if (!skip.includes("targeting")) {
|
|
1675
|
+
for (const issue of report.targeting.warnings) {
|
|
1676
|
+
issues.push({
|
|
1677
|
+
severity: issue.severity,
|
|
1678
|
+
category: "targeting",
|
|
1679
|
+
rule: issue.property,
|
|
1680
|
+
message: issue.message,
|
|
1681
|
+
detail: issue.suggestion,
|
|
1682
|
+
...issue.loc ? { loc: issue.loc } : {},
|
|
1683
|
+
...issue.locs?.length ? { locs: issue.locs } : {},
|
|
1684
|
+
...issue.locsTruncated ? { locsTruncated: true } : {}
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1627
1688
|
if (!skip.includes("design")) {
|
|
1628
1689
|
for (const issue of report.design.issues) {
|
|
1629
1690
|
issues.push({
|