@likecoin/epubcheck-ts 0.3.9 → 0.4.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 +36 -23
- package/bin/epubcheck.js +41 -12
- package/bin/epubcheck.ts +44 -14
- package/dist/index.cjs +672 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +437 -400
- package/dist/index.d.ts +437 -400
- package/dist/index.js +672 -44
- package/dist/index.js.map +1 -1
- package/package.json +21 -5
package/README.md
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
# epubcheck-ts
|
|
2
2
|
|
|
3
|
-
A TypeScript
|
|
3
|
+
Validate EPUB files in Node.js and the browser. A TypeScript implementation of [EPUBCheck](https://github.com/w3c/epubcheck).
|
|
4
4
|
|
|
5
5
|
[](https://github.com/likecoin/epubcheck-ts/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/@likecoin/epubcheck-ts)
|
|
7
7
|
[](./LICENSE)
|
|
8
8
|
|
|
9
|
-
> **
|
|
9
|
+
> **Status**: 93% feature parity with Java EPUBCheck (1061 tests passing, 74 skipped). See [PROJECT_STATUS.md](./PROJECT_STATUS.md) for details. For full EPUB 3 conformance testing, use the official [Java EPUBCheck](https://github.com/w3c/epubcheck).
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **CLI and
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **Tree-shakable**: ESM with proper exports for optimal bundling
|
|
13
|
+
- **CLI and API** — Use as a CLI tool (`npx @likecoin/epubcheck-ts book.epub`) or import as a library
|
|
14
|
+
- **Browser support** — Works in Node.js 18+ and modern browsers via pure JS + WASM
|
|
15
|
+
- **No native dependencies** — No Java, no compilation — `npm install` and go
|
|
16
|
+
- **TypeScript** — Full type definitions included
|
|
17
|
+
- **Tree-shakable** — ESM with proper exports for minimal bundle impact
|
|
19
18
|
|
|
20
19
|
## Try it Online
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
Live demo at **[likecoin.github.io/epubcheck-ts](https://likecoin.github.io/epubcheck-ts/)** — validate EPUB files in the browser without uploading to any server.
|
|
23
22
|
|
|
24
23
|
## Installation
|
|
25
24
|
|
|
@@ -50,7 +49,13 @@ Options:
|
|
|
50
49
|
-j, --json <file> Output JSON report to file (use '-' for stdout)
|
|
51
50
|
-q, --quiet Suppress console output (errors only)
|
|
52
51
|
-p, --profile <name> Validation profile (default|dict|edupub|idx|preview)
|
|
52
|
+
-u, --usage Include usage messages (best practices)
|
|
53
|
+
-f, --fatal Show only fatal errors
|
|
54
|
+
-e, --error Show fatal errors and errors
|
|
55
|
+
--warn Show fatal errors, errors, and warnings
|
|
56
|
+
-c, --customMessages <file> Override message severities (TSV: ID<tab>SEVERITY)
|
|
53
57
|
-w, --fail-on-warnings Exit with code 1 if warnings are found
|
|
58
|
+
-l, --listChecks List all message IDs and severities
|
|
54
59
|
-v, --version Show version information
|
|
55
60
|
-h, --help Show this help message
|
|
56
61
|
```
|
|
@@ -68,9 +73,14 @@ epubcheck-ts book.epub --quiet --fail-on-warnings
|
|
|
68
73
|
|
|
69
74
|
# Validate with specific profile
|
|
70
75
|
epubcheck-ts dictionary.epub --profile dict
|
|
71
|
-
```
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
# Show only errors (hide warnings/info)
|
|
78
|
+
epubcheck-ts book.epub --error
|
|
79
|
+
|
|
80
|
+
# Enable suppressed accessibility checks
|
|
81
|
+
printf "ACC-004\tWARNING\nACC-005\tWARNING\n" > overrides.txt
|
|
82
|
+
epubcheck-ts book.epub -c overrides.txt
|
|
83
|
+
```
|
|
74
84
|
|
|
75
85
|
### ES Modules (recommended)
|
|
76
86
|
|
|
@@ -181,6 +191,9 @@ interface EpubCheckOptions {
|
|
|
181
191
|
|
|
182
192
|
/** Locale for messages (default: 'en') */
|
|
183
193
|
locale?: string;
|
|
194
|
+
|
|
195
|
+
/** Custom message severity overrides (message ID → severity) */
|
|
196
|
+
customMessages?: Map<string, MessageSeverity>;
|
|
184
197
|
}
|
|
185
198
|
```
|
|
186
199
|
|
|
@@ -266,20 +279,20 @@ This library is a TypeScript port of the Java-based [EPUBCheck](https://github.c
|
|
|
266
279
|
|
|
267
280
|
| Component | Status | Completeness | Notes |
|
|
268
281
|
|-----------|--------|--------------|-------|
|
|
269
|
-
| OCF Container |
|
|
270
|
-
| Package Document (OPF) | 🟢 Complete | ~
|
|
271
|
-
| Content Documents |
|
|
272
|
-
| Navigation Document | 🟢 Complete | ~
|
|
273
|
-
| Schema Validation | 🟡 Partial | ~
|
|
274
|
-
| CSS | 🟡 Partial | ~
|
|
275
|
-
| Cross-reference Validation | 🟢 Complete | ~
|
|
276
|
-
| Accessibility Checks |
|
|
277
|
-
| Media Overlays |
|
|
282
|
+
| OCF Container | 🟢 Complete | ~92% | ZIP structure, mimetype, container.xml, encryption.xml obfuscation |
|
|
283
|
+
| Package Document (OPF) | 🟢 Complete | ~92% | Metadata, manifest, spine, collections, Schematron-equivalent checks |
|
|
284
|
+
| Content Documents | 🟢 Complete | ~93% | XHTML structure, CSS url(), @import, SVG, entities, title, SSML, XML version |
|
|
285
|
+
| Navigation Document | 🟢 Complete | ~95% | Nav content model, landmarks, labels, reading order, hidden, nested-ol |
|
|
286
|
+
| Schema Validation | 🟡 Partial | ~55% | RelaxNG for OPF/container; XHTML/SVG disabled (libxml2 limitation) |
|
|
287
|
+
| CSS | 🟡 Partial | ~85% | @font-face, @import, url() extraction, position, forbidden properties, alt style tags |
|
|
288
|
+
| Cross-reference Validation | 🟢 Complete | ~92% | Reference tracking, fragments, fallbacks, remote resources, cross-document features |
|
|
289
|
+
| Accessibility Checks | 🟢 Complete | ~71% | 12/17 ACC checks: table, image alt, hyperlink, MathML, SVG, epub:type, OPF metadata |
|
|
290
|
+
| Media Overlays | 🟡 Partial | ~70% | SMIL structure, timing, audio, OPF metadata, duration validation |
|
|
278
291
|
| Media Validation | ❌ Not Started | 0% | Planned |
|
|
279
292
|
|
|
280
293
|
Legend: 🟢 Complete | 🟡 Partial | 🔴 Basic | ❌ Not Started
|
|
281
294
|
|
|
282
|
-
**Overall Progress: ~
|
|
295
|
+
**Overall Progress: ~93% of Java EPUBCheck features**
|
|
283
296
|
|
|
284
297
|
See [PROJECT_STATUS.md](./PROJECT_STATUS.md) for detailed comparison.
|
|
285
298
|
|
|
@@ -364,13 +377,13 @@ Legend: ✅ Implemented
|
|
|
364
377
|
| Aspect | epubcheck-ts | EPUBCheck (Java) |
|
|
365
378
|
|--------|--------------|------------------|
|
|
366
379
|
| Runtime | Node.js / Browser | JVM |
|
|
367
|
-
| Feature Parity | ~
|
|
380
|
+
| Feature Parity | ~93% | 100% |
|
|
368
381
|
| Bundle Size | ~450KB JS + ~1.6MB WASM | ~15MB |
|
|
369
382
|
| Installation | `npm install` | Download JAR |
|
|
370
383
|
| Integration | Native JS/TS | CLI or Java API |
|
|
371
384
|
| Performance | Comparable | Baseline |
|
|
372
385
|
|
|
373
|
-
|
|
386
|
+
See [PROJECT_STATUS.md](./PROJECT_STATUS.md) for detailed feature comparison.
|
|
374
387
|
|
|
375
388
|
## Contributing
|
|
376
389
|
|
package/bin/epubcheck.js
CHANGED
|
@@ -3,13 +3,17 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { basename } from "node:path";
|
|
5
5
|
const { EpubCheck, toJSONReport } = await import("../dist/index.js");
|
|
6
|
-
const VERSION = "0.
|
|
6
|
+
const VERSION = "0.4.0";
|
|
7
7
|
const { values, positionals } = parseArgs({
|
|
8
8
|
options: {
|
|
9
9
|
json: { type: "string", short: "j" },
|
|
10
10
|
quiet: { type: "boolean", short: "q", default: false },
|
|
11
11
|
profile: { type: "string", short: "p" },
|
|
12
12
|
usage: { type: "boolean", short: "u", default: false },
|
|
13
|
+
fatal: { type: "boolean", short: "f", default: false },
|
|
14
|
+
error: { type: "boolean", short: "e", default: false },
|
|
15
|
+
warn: { type: "boolean", default: false },
|
|
16
|
+
customMessages: { type: "string", short: "c" },
|
|
13
17
|
version: { type: "boolean", short: "v", default: false },
|
|
14
18
|
help: { type: "boolean", short: "h", default: false },
|
|
15
19
|
"fail-on-warnings": { type: "boolean", short: "w", default: false },
|
|
@@ -22,7 +26,7 @@ if (values.version) {
|
|
|
22
26
|
console.log(`EPUBCheck-TS v${VERSION}`);
|
|
23
27
|
console.log("TypeScript EPUB validator for Node.js and browsers");
|
|
24
28
|
console.log();
|
|
25
|
-
console.log("Note: This is ~
|
|
29
|
+
console.log("Note: This is ~93% feature-complete compared to Java EPUBCheck.");
|
|
26
30
|
console.log("For production validation: https://github.com/w3c/epubcheck");
|
|
27
31
|
process.exit(0);
|
|
28
32
|
}
|
|
@@ -45,6 +49,10 @@ Options:
|
|
|
45
49
|
-q, --quiet Suppress console output (errors only)
|
|
46
50
|
-p, --profile <name> Validation profile (default|dict|edupub|idx|preview)
|
|
47
51
|
-u, --usage Include usage messages (best practices)
|
|
52
|
+
-f, --fatal Show only fatal errors
|
|
53
|
+
-e, --error Show fatal errors and errors
|
|
54
|
+
--warn Show fatal errors, errors, and warnings
|
|
55
|
+
-c, --customMessages <file> Override message severities (TSV: ID\\tSEVERITY)
|
|
48
56
|
-w, --fail-on-warnings Exit with code 1 if warnings are found
|
|
49
57
|
-l, --listChecks List all message IDs and severities
|
|
50
58
|
-v, --version Show version information
|
|
@@ -61,8 +69,8 @@ Exit Codes:
|
|
|
61
69
|
1 Validation errors found (or warnings with --fail-on-warnings)
|
|
62
70
|
2 Runtime error (file not found, invalid arguments, etc.)
|
|
63
71
|
|
|
64
|
-
Note: This tool provides ~
|
|
65
|
-
Missing features:
|
|
72
|
+
Note: This tool provides ~93% coverage of Java EPUBCheck features.
|
|
73
|
+
Missing features: single-file/directory validation, advanced ARIA, XHTML/SVG schema.
|
|
66
74
|
For complete EPUB 3 conformance testing, use: https://github.com/w3c/epubcheck
|
|
67
75
|
|
|
68
76
|
Report issues: https://github.com/likecoin/epubcheck-ts/issues
|
|
@@ -90,10 +98,31 @@ async function main() {
|
|
|
90
98
|
if (values.usage) {
|
|
91
99
|
options.includeUsage = true;
|
|
92
100
|
}
|
|
101
|
+
if (typeof values.customMessages === "string") {
|
|
102
|
+
const { parseCustomMessages } = await import("../dist/index.js");
|
|
103
|
+
const cmContent = await readFile(values.customMessages, "utf-8");
|
|
104
|
+
options.customMessages = parseCustomMessages(cmContent);
|
|
105
|
+
}
|
|
93
106
|
const result = await EpubCheck.validate(epubData, options);
|
|
94
107
|
const elapsedMs = Date.now() - startTime;
|
|
108
|
+
const severityRank = {
|
|
109
|
+
fatal: 0,
|
|
110
|
+
error: 1,
|
|
111
|
+
warning: 2,
|
|
112
|
+
info: 3,
|
|
113
|
+
usage: 4
|
|
114
|
+
};
|
|
115
|
+
let maxRank = 4;
|
|
116
|
+
if (values.fatal) maxRank = 0;
|
|
117
|
+
else if (values.error) maxRank = 1;
|
|
118
|
+
else if (values.warn) maxRank = 2;
|
|
119
|
+
const isFiltered = values.fatal || values.error || values.warn;
|
|
120
|
+
const displayMessages = result.messages.filter(
|
|
121
|
+
(m) => severityRank[m.severity] <= maxRank
|
|
122
|
+
);
|
|
95
123
|
if (values.json !== void 0) {
|
|
96
|
-
const
|
|
124
|
+
const filteredResult = isFiltered ? { ...result, messages: displayMessages } : result;
|
|
125
|
+
const jsonContent = toJSONReport(filteredResult);
|
|
97
126
|
if (values.json === "-") {
|
|
98
127
|
if (values.quiet) {
|
|
99
128
|
console.log(jsonContent);
|
|
@@ -109,11 +138,11 @@ async function main() {
|
|
|
109
138
|
}
|
|
110
139
|
}
|
|
111
140
|
if (!values.quiet) {
|
|
112
|
-
const fatal =
|
|
113
|
-
const errors =
|
|
114
|
-
const warnings =
|
|
115
|
-
const info =
|
|
116
|
-
const usage =
|
|
141
|
+
const fatal = displayMessages.filter((m) => m.severity === "fatal");
|
|
142
|
+
const errors = displayMessages.filter((m) => m.severity === "error");
|
|
143
|
+
const warnings = displayMessages.filter((m) => m.severity === "warning");
|
|
144
|
+
const info = displayMessages.filter((m) => m.severity === "info");
|
|
145
|
+
const usage = displayMessages.filter((m) => m.severity === "usage");
|
|
117
146
|
const printMessages = (messages, color, label) => {
|
|
118
147
|
if (messages.length === 0) return;
|
|
119
148
|
for (const msg of messages) {
|
|
@@ -134,7 +163,7 @@ async function main() {
|
|
|
134
163
|
if (warnings.length > 0) {
|
|
135
164
|
printMessages(warnings, "\x1B[33m", "WARNING");
|
|
136
165
|
}
|
|
137
|
-
if (info.length > 0 &&
|
|
166
|
+
if (info.length > 0 && displayMessages.length < 20) {
|
|
138
167
|
printMessages(info, "\x1B[36m", "INFO");
|
|
139
168
|
}
|
|
140
169
|
if (usage.length > 0) {
|
|
@@ -159,7 +188,7 @@ async function main() {
|
|
|
159
188
|
console.log();
|
|
160
189
|
if (result.errorCount === 0 && result.fatalCount === 0) {
|
|
161
190
|
console.log(
|
|
162
|
-
"\x1B[90mNote: This validator provides ~
|
|
191
|
+
"\x1B[90mNote: This validator provides ~93% coverage of Java EPUBCheck.\x1B[0m"
|
|
163
192
|
);
|
|
164
193
|
console.log("\x1B[90mFor complete validation: https://github.com/w3c/epubcheck\x1B[0m");
|
|
165
194
|
console.log();
|
package/bin/epubcheck.ts
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
11
11
|
import { parseArgs } from 'node:util';
|
|
12
12
|
import { basename } from 'node:path';
|
|
13
|
-
import type { EpubCheckOptions, EPUBProfile, ValidationMessage } from '../src/types.js';
|
|
13
|
+
import type { EpubCheckOptions, EPUBProfile, Severity, ValidationMessage } from '../src/types.js';
|
|
14
14
|
|
|
15
15
|
// Dynamic import to support both ESM and CJS builds
|
|
16
16
|
const { EpubCheck, toJSONReport } = await import('../dist/index.js');
|
|
17
17
|
|
|
18
|
-
const VERSION = '0.
|
|
18
|
+
const VERSION = '0.4.0';
|
|
19
19
|
|
|
20
20
|
// Parse command line arguments
|
|
21
21
|
const { values, positionals } = parseArgs({
|
|
@@ -24,6 +24,10 @@ const { values, positionals } = parseArgs({
|
|
|
24
24
|
quiet: { type: 'boolean', short: 'q', default: false },
|
|
25
25
|
profile: { type: 'string', short: 'p' },
|
|
26
26
|
usage: { type: 'boolean', short: 'u', default: false },
|
|
27
|
+
fatal: { type: 'boolean', short: 'f', default: false },
|
|
28
|
+
error: { type: 'boolean', short: 'e', default: false },
|
|
29
|
+
warn: { type: 'boolean', default: false },
|
|
30
|
+
customMessages: { type: 'string', short: 'c' },
|
|
27
31
|
version: { type: 'boolean', short: 'v', default: false },
|
|
28
32
|
help: { type: 'boolean', short: 'h', default: false },
|
|
29
33
|
'fail-on-warnings': { type: 'boolean', short: 'w', default: false },
|
|
@@ -38,7 +42,7 @@ if (values.version) {
|
|
|
38
42
|
console.log(`EPUBCheck-TS v${VERSION}`);
|
|
39
43
|
console.log('TypeScript EPUB validator for Node.js and browsers');
|
|
40
44
|
console.log();
|
|
41
|
-
console.log('Note: This is ~
|
|
45
|
+
console.log('Note: This is ~93% feature-complete compared to Java EPUBCheck.');
|
|
42
46
|
console.log('For production validation: https://github.com/w3c/epubcheck');
|
|
43
47
|
process.exit(0);
|
|
44
48
|
}
|
|
@@ -65,6 +69,10 @@ Options:
|
|
|
65
69
|
-q, --quiet Suppress console output (errors only)
|
|
66
70
|
-p, --profile <name> Validation profile (default|dict|edupub|idx|preview)
|
|
67
71
|
-u, --usage Include usage messages (best practices)
|
|
72
|
+
-f, --fatal Show only fatal errors
|
|
73
|
+
-e, --error Show fatal errors and errors
|
|
74
|
+
--warn Show fatal errors, errors, and warnings
|
|
75
|
+
-c, --customMessages <file> Override message severities (TSV: ID\\tSEVERITY)
|
|
68
76
|
-w, --fail-on-warnings Exit with code 1 if warnings are found
|
|
69
77
|
-l, --listChecks List all message IDs and severities
|
|
70
78
|
-v, --version Show version information
|
|
@@ -81,8 +89,8 @@ Exit Codes:
|
|
|
81
89
|
1 Validation errors found (or warnings with --fail-on-warnings)
|
|
82
90
|
2 Runtime error (file not found, invalid arguments, etc.)
|
|
83
91
|
|
|
84
|
-
Note: This tool provides ~
|
|
85
|
-
Missing features:
|
|
92
|
+
Note: This tool provides ~93% coverage of Java EPUBCheck features.
|
|
93
|
+
Missing features: single-file/directory validation, advanced ARIA, XHTML/SVG schema.
|
|
86
94
|
For complete EPUB 3 conformance testing, use: https://github.com/w3c/epubcheck
|
|
87
95
|
|
|
88
96
|
Report issues: https://github.com/likecoin/epubcheck-ts/issues
|
|
@@ -118,12 +126,35 @@ async function main(): Promise<void> {
|
|
|
118
126
|
if (values.usage) {
|
|
119
127
|
options.includeUsage = true;
|
|
120
128
|
}
|
|
129
|
+
if (typeof values.customMessages === 'string') {
|
|
130
|
+
const { parseCustomMessages } = await import('../dist/index.js');
|
|
131
|
+
const cmContent = await readFile(values.customMessages, 'utf-8');
|
|
132
|
+
options.customMessages = parseCustomMessages(cmContent);
|
|
133
|
+
}
|
|
121
134
|
const result = await EpubCheck.validate(epubData, options);
|
|
122
135
|
const elapsedMs = Date.now() - startTime;
|
|
123
136
|
|
|
137
|
+
// Most restrictive severity flag wins (--fatal overrides --error overrides --warn)
|
|
138
|
+
const severityRank: Record<Severity, number> = {
|
|
139
|
+
fatal: 0,
|
|
140
|
+
error: 1,
|
|
141
|
+
warning: 2,
|
|
142
|
+
info: 3,
|
|
143
|
+
usage: 4,
|
|
144
|
+
};
|
|
145
|
+
let maxRank = 4;
|
|
146
|
+
if (values.fatal) maxRank = 0;
|
|
147
|
+
else if (values.error) maxRank = 1;
|
|
148
|
+
else if (values.warn) maxRank = 2;
|
|
149
|
+
const isFiltered = values.fatal || values.error || values.warn;
|
|
150
|
+
const displayMessages = result.messages.filter(
|
|
151
|
+
(m: ValidationMessage) => severityRank[m.severity] <= maxRank,
|
|
152
|
+
);
|
|
153
|
+
|
|
124
154
|
// Output JSON report if requested
|
|
125
155
|
if (values.json !== undefined) {
|
|
126
|
-
const
|
|
156
|
+
const filteredResult = isFiltered ? { ...result, messages: displayMessages } : result;
|
|
157
|
+
const jsonContent = toJSONReport(filteredResult); // Already stringified
|
|
127
158
|
|
|
128
159
|
if (values.json === '-') {
|
|
129
160
|
// Output to stdout - suppress other output
|
|
@@ -145,11 +176,11 @@ async function main(): Promise<void> {
|
|
|
145
176
|
// Console output (unless quiet mode)
|
|
146
177
|
if (!values.quiet) {
|
|
147
178
|
// Group messages by severity
|
|
148
|
-
const fatal =
|
|
149
|
-
const errors =
|
|
150
|
-
const warnings =
|
|
151
|
-
const info =
|
|
152
|
-
const usage =
|
|
179
|
+
const fatal = displayMessages.filter((m: ValidationMessage) => m.severity === 'fatal');
|
|
180
|
+
const errors = displayMessages.filter((m: ValidationMessage) => m.severity === 'error');
|
|
181
|
+
const warnings = displayMessages.filter((m: ValidationMessage) => m.severity === 'warning');
|
|
182
|
+
const info = displayMessages.filter((m: ValidationMessage) => m.severity === 'info');
|
|
183
|
+
const usage = displayMessages.filter((m: ValidationMessage) => m.severity === 'usage');
|
|
153
184
|
|
|
154
185
|
// Print messages with colors
|
|
155
186
|
const printMessages = (
|
|
@@ -180,8 +211,7 @@ async function main(): Promise<void> {
|
|
|
180
211
|
if (warnings.length > 0) {
|
|
181
212
|
printMessages(warnings, '\x1b[33m', 'WARNING');
|
|
182
213
|
}
|
|
183
|
-
if (info.length > 0 &&
|
|
184
|
-
// Only show info if total messages is small
|
|
214
|
+
if (info.length > 0 && displayMessages.length < 20) {
|
|
185
215
|
printMessages(info, '\x1b[36m', 'INFO');
|
|
186
216
|
}
|
|
187
217
|
if (usage.length > 0) {
|
|
@@ -210,7 +240,7 @@ async function main(): Promise<void> {
|
|
|
210
240
|
// Show limitation notice if there were no major errors
|
|
211
241
|
if (result.errorCount === 0 && result.fatalCount === 0) {
|
|
212
242
|
console.log(
|
|
213
|
-
'\x1b[90mNote: This validator provides ~
|
|
243
|
+
'\x1b[90mNote: This validator provides ~93% coverage of Java EPUBCheck.\x1b[0m',
|
|
214
244
|
);
|
|
215
245
|
console.log('\x1b[90mFor complete validation: https://github.com/w3c/epubcheck\x1b[0m');
|
|
216
246
|
console.log();
|