@likecoin/epubcheck-ts 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,7 @@ A TypeScript port of [EPUBCheck](https://github.com/w3c/epubcheck) - the officia
10
10
 
11
11
  ## Features
12
12
 
13
+ - **CLI and programmatic API**: Use as a command-line tool or integrate into your application
13
14
  - **Cross-platform**: Works in Node.js (18+) and modern browsers
14
15
  - **Partial EPUB validation**: Currently ~65% of EPUBCheck feature parity
15
16
  - **Zero native dependencies**: Pure JavaScript/WebAssembly, no compilation required
@@ -28,6 +29,49 @@ npm install @likecoin/epubcheck-ts
28
29
 
29
30
  ## Quick Start
30
31
 
32
+ ### Command Line Interface (CLI)
33
+
34
+ **Quick validation:**
35
+ ```bash
36
+ npx @likecoin/epubcheck-ts book.epub
37
+ ```
38
+
39
+ **Or install globally:**
40
+ ```bash
41
+ npm install -g @likecoin/epubcheck-ts
42
+ epubcheck-ts book.epub
43
+ ```
44
+
45
+ **CLI Options:**
46
+ ```bash
47
+ epubcheck-ts <file.epub> [options]
48
+
49
+ Options:
50
+ -j, --json <file> Output JSON report to file (use '-' for stdout)
51
+ -q, --quiet Suppress console output (errors only)
52
+ -p, --profile <name> Validation profile (default|dict|edupub|idx|preview)
53
+ -w, --fail-on-warnings Exit with code 1 if warnings are found
54
+ -v, --version Show version information
55
+ -h, --help Show this help message
56
+ ```
57
+
58
+ **Examples:**
59
+ ```bash
60
+ # Basic validation
61
+ epubcheck-ts book.epub
62
+
63
+ # Generate JSON report
64
+ epubcheck-ts book.epub --json report.json
65
+
66
+ # Quiet mode for CI/CD
67
+ epubcheck-ts book.epub --quiet --fail-on-warnings
68
+
69
+ # Validate with specific profile
70
+ epubcheck-ts dictionary.epub --profile dict
71
+ ```
72
+
73
+ **Note:** This CLI provides ~65% coverage of Java EPUBCheck features. For complete EPUB 3 conformance testing, use the [official Java EPUBCheck](https://github.com/w3c/epubcheck).
74
+
31
75
  ### ES Modules (recommended)
32
76
 
33
77
  ```typescript
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { parseArgs } from "node:util";
4
+ import { basename } from "node:path";
5
+ const { EpubCheck, toJSONReport } = await import("../dist/index.js");
6
+ const VERSION = "0.2.2";
7
+ const { values, positionals } = parseArgs({
8
+ options: {
9
+ json: { type: "string", short: "j" },
10
+ quiet: { type: "boolean", short: "q", default: false },
11
+ profile: { type: "string", short: "p" },
12
+ usage: { type: "boolean", short: "u", default: false },
13
+ version: { type: "boolean", short: "v", default: false },
14
+ help: { type: "boolean", short: "h", default: false },
15
+ "fail-on-warnings": { type: "boolean", short: "w", default: false }
16
+ },
17
+ allowPositionals: true,
18
+ strict: false
19
+ });
20
+ if (values.version) {
21
+ console.log(`EPUBCheck-TS v${VERSION}`);
22
+ console.log("TypeScript EPUB validator for Node.js and browsers");
23
+ console.log();
24
+ console.log("Note: This is ~65% feature-complete compared to Java EPUBCheck.");
25
+ console.log("For production validation: https://github.com/w3c/epubcheck");
26
+ process.exit(0);
27
+ }
28
+ if (values.help || positionals.length === 0) {
29
+ console.log(`EPUBCheck-TS v${VERSION} - EPUB Validator
30
+
31
+ Usage: epubcheck-ts <file.epub> [options]
32
+
33
+ Arguments:
34
+ <file.epub> Path to EPUB file to validate
35
+
36
+ Options:
37
+ -j, --json <file> Output JSON report to file (use '-' for stdout)
38
+ -q, --quiet Suppress console output (errors only)
39
+ -p, --profile <name> Validation profile (default|dict|edupub|idx|preview)
40
+ -u, --usage Include usage messages (best practices)
41
+ -w, --fail-on-warnings Exit with code 1 if warnings are found
42
+ -v, --version Show version information
43
+ -h, --help Show this help message
44
+
45
+ Examples:
46
+ epubcheck-ts book.epub
47
+ epubcheck-ts book.epub --json report.json
48
+ epubcheck-ts book.epub --quiet --fail-on-warnings
49
+ epubcheck-ts book.epub --profile dict
50
+
51
+ Exit Codes:
52
+ 0 No errors (or only warnings if --fail-on-warnings not set)
53
+ 1 Validation errors found (or warnings with --fail-on-warnings)
54
+ 2 Runtime error (file not found, invalid arguments, etc.)
55
+
56
+ Note: This tool provides ~65% coverage of Java EPUBCheck features.
57
+ Missing features: Media Overlays, advanced ARIA checks, encryption/signatures.
58
+ For complete EPUB 3 conformance testing, use: https://github.com/w3c/epubcheck
59
+
60
+ Report issues: https://github.com/likecoin/epubcheck-ts/issues
61
+ `);
62
+ process.exit(0);
63
+ }
64
+ async function main() {
65
+ const filePath = positionals[0];
66
+ if (!filePath) {
67
+ console.error("Error: No EPUB file specified");
68
+ console.error("Run with --help for usage information");
69
+ process.exit(2);
70
+ }
71
+ try {
72
+ if (!values.quiet) {
73
+ console.log(`Validating: ${basename(filePath)}`);
74
+ console.log();
75
+ }
76
+ const epubData = await readFile(filePath);
77
+ const startTime = Date.now();
78
+ const options = {};
79
+ if (values.profile) {
80
+ options.profile = values.profile;
81
+ }
82
+ if (values.usage) {
83
+ options.includeUsage = true;
84
+ }
85
+ const result = await EpubCheck.validate(epubData, options);
86
+ const elapsedMs = Date.now() - startTime;
87
+ if (values.json !== void 0) {
88
+ const jsonContent = toJSONReport(result);
89
+ if (values.json === "-") {
90
+ if (values.quiet) {
91
+ console.log(jsonContent);
92
+ } else {
93
+ console.log("\nJSON Report:");
94
+ console.log(jsonContent);
95
+ }
96
+ } else if (typeof values.json === "string") {
97
+ await writeFile(values.json, jsonContent, "utf-8");
98
+ if (!values.quiet) {
99
+ console.log(`JSON report written to: ${values.json}`);
100
+ }
101
+ }
102
+ }
103
+ if (!values.quiet) {
104
+ const fatal = result.messages.filter((m) => m.severity === "fatal");
105
+ const errors = result.messages.filter((m) => m.severity === "error");
106
+ const warnings = result.messages.filter(
107
+ (m) => m.severity === "warning"
108
+ );
109
+ const info = result.messages.filter((m) => m.severity === "info");
110
+ const usage = result.messages.filter((m) => m.severity === "usage");
111
+ const printMessages = (messages, color, label) => {
112
+ if (messages.length === 0) return;
113
+ for (const msg of messages) {
114
+ const locationStr = msg.location ? ` (${msg.location.path}${msg.location.line !== void 0 ? `:${String(msg.location.line)}` : ""})` : "";
115
+ console.log(`${color}${label}${locationStr}: ${msg.message}\x1B[0m`);
116
+ if (msg.id) {
117
+ console.log(` \x1B[90mID: ${msg.id}\x1B[0m`);
118
+ }
119
+ }
120
+ console.log();
121
+ };
122
+ if (fatal.length > 0) {
123
+ printMessages(fatal, "\x1B[31m\x1B[1m", "FATAL");
124
+ }
125
+ if (errors.length > 0) {
126
+ printMessages(errors, "\x1B[31m", "ERROR");
127
+ }
128
+ if (warnings.length > 0) {
129
+ printMessages(warnings, "\x1B[33m", "WARNING");
130
+ }
131
+ if (info.length > 0 && result.messages.length < 20) {
132
+ printMessages(info, "\x1B[36m", "INFO");
133
+ }
134
+ if (usage.length > 0) {
135
+ printMessages(usage, "\x1B[90m", "USAGE");
136
+ }
137
+ console.log("\u2500".repeat(60));
138
+ const summaryColor = result.valid ? "\x1B[32m" : "\x1B[31m";
139
+ const summaryIcon = result.valid ? "\u2713" : "\u2717";
140
+ console.log(
141
+ `${summaryColor}${summaryIcon} ${result.valid ? "Valid EPUB" : "Invalid EPUB"}\x1B[0m`
142
+ );
143
+ console.log();
144
+ console.log(` Errors: ${String(result.errorCount + result.fatalCount)}`);
145
+ console.log(` Warnings: ${String(result.warningCount)}`);
146
+ if (info.length > 0) {
147
+ console.log(` Info: ${String(result.infoCount)}`);
148
+ }
149
+ if (usage.length > 0) {
150
+ console.log(` Usages: ${String(result.usageCount)}`);
151
+ }
152
+ console.log(` Time: ${String(elapsedMs)}ms`);
153
+ console.log();
154
+ if (result.errorCount === 0 && result.fatalCount === 0) {
155
+ console.log(
156
+ "\x1B[90mNote: This validator provides ~65% coverage of Java EPUBCheck.\x1B[0m"
157
+ );
158
+ console.log("\x1B[90mFor complete validation: https://github.com/w3c/epubcheck\x1B[0m");
159
+ console.log();
160
+ }
161
+ }
162
+ const shouldFail = result.errorCount > 0 || result.fatalCount > 0 || values["fail-on-warnings"] && result.warningCount > 0;
163
+ process.exit(shouldFail ? 1 : 0);
164
+ } catch (error) {
165
+ console.error("\x1B[31mError:\x1B[0m", error instanceof Error ? error.message : String(error));
166
+ if (error instanceof Error && error.stack && !values.quiet) {
167
+ console.error("\x1B[90m" + error.stack + "\x1B[0m");
168
+ }
169
+ process.exit(2);
170
+ }
171
+ }
172
+ void main();
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * EPUBCheck-TS CLI
4
+ *
5
+ * A minimalist command-line interface for EPUB validation.
6
+ * For full EPUBCheck features, use the official Java version:
7
+ * https://github.com/w3c/epubcheck
8
+ */
9
+
10
+ import { readFile, writeFile } from 'node:fs/promises';
11
+ import { parseArgs } from 'node:util';
12
+ import { basename } from 'node:path';
13
+
14
+ // Dynamic import to support both ESM and CJS builds
15
+ const { EpubCheck, toJSONReport } = await import('../dist/index.js');
16
+
17
+ const VERSION = '0.2.2';
18
+
19
+ // Parse command line arguments
20
+ const { values, positionals } = parseArgs({
21
+ options: {
22
+ json: { type: 'string', short: 'j' },
23
+ quiet: { type: 'boolean', short: 'q', default: false },
24
+ profile: { type: 'string', short: 'p' },
25
+ usage: { type: 'boolean', short: 'u', default: false },
26
+ version: { type: 'boolean', short: 'v', default: false },
27
+ help: { type: 'boolean', short: 'h', default: false },
28
+ 'fail-on-warnings': { type: 'boolean', short: 'w', default: false },
29
+ },
30
+ allowPositionals: true,
31
+ strict: false,
32
+ });
33
+
34
+ // Show version
35
+ if (values.version) {
36
+ console.log(`EPUBCheck-TS v${VERSION}`);
37
+ console.log('TypeScript EPUB validator for Node.js and browsers');
38
+ console.log();
39
+ console.log('Note: This is ~65% feature-complete compared to Java EPUBCheck.');
40
+ console.log('For production validation: https://github.com/w3c/epubcheck');
41
+ process.exit(0);
42
+ }
43
+
44
+ // Show help
45
+ if (values.help || positionals.length === 0) {
46
+ console.log(`EPUBCheck-TS v${VERSION} - EPUB Validator
47
+
48
+ Usage: epubcheck-ts <file.epub> [options]
49
+
50
+ Arguments:
51
+ <file.epub> Path to EPUB file to validate
52
+
53
+ Options:
54
+ -j, --json <file> Output JSON report to file (use '-' for stdout)
55
+ -q, --quiet Suppress console output (errors only)
56
+ -p, --profile <name> Validation profile (default|dict|edupub|idx|preview)
57
+ -u, --usage Include usage messages (best practices)
58
+ -w, --fail-on-warnings Exit with code 1 if warnings are found
59
+ -v, --version Show version information
60
+ -h, --help Show this help message
61
+
62
+ Examples:
63
+ epubcheck-ts book.epub
64
+ epubcheck-ts book.epub --json report.json
65
+ epubcheck-ts book.epub --quiet --fail-on-warnings
66
+ epubcheck-ts book.epub --profile dict
67
+
68
+ Exit Codes:
69
+ 0 No errors (or only warnings if --fail-on-warnings not set)
70
+ 1 Validation errors found (or warnings with --fail-on-warnings)
71
+ 2 Runtime error (file not found, invalid arguments, etc.)
72
+
73
+ Note: This tool provides ~65% coverage of Java EPUBCheck features.
74
+ Missing features: Media Overlays, advanced ARIA checks, encryption/signatures.
75
+ For complete EPUB 3 conformance testing, use: https://github.com/w3c/epubcheck
76
+
77
+ Report issues: https://github.com/likecoin/epubcheck-ts/issues
78
+ `);
79
+ process.exit(0);
80
+ }
81
+
82
+ // Main validation logic
83
+ async function main(): Promise<void> {
84
+ const filePath = positionals[0];
85
+
86
+ if (!filePath) {
87
+ console.error('Error: No EPUB file specified');
88
+ console.error('Run with --help for usage information');
89
+ process.exit(2);
90
+ }
91
+
92
+ try {
93
+ // Read EPUB file
94
+ if (!values.quiet) {
95
+ console.log(`Validating: ${basename(filePath)}`);
96
+ console.log();
97
+ }
98
+
99
+ const epubData = await readFile(filePath);
100
+
101
+ // Validate
102
+ const startTime = Date.now();
103
+ const options: {
104
+ profile?: 'default' | 'dict' | 'edupub' | 'idx' | 'preview';
105
+ includeUsage?: boolean;
106
+ } = {};
107
+ if (values.profile) {
108
+ options.profile = values.profile as 'default' | 'dict' | 'edupub' | 'idx' | 'preview';
109
+ }
110
+ if (values.usage) {
111
+ options.includeUsage = true;
112
+ }
113
+ const result = await EpubCheck.validate(epubData, options);
114
+ const elapsedMs = Date.now() - startTime;
115
+
116
+ // Output JSON report if requested
117
+ if (values.json !== undefined) {
118
+ const jsonContent = toJSONReport(result); // Already stringified
119
+
120
+ if (values.json === '-') {
121
+ // Output to stdout - suppress other output
122
+ if (values.quiet) {
123
+ console.log(jsonContent);
124
+ } else {
125
+ // If not quiet, output after other messages
126
+ console.log('\nJSON Report:');
127
+ console.log(jsonContent);
128
+ }
129
+ } else if (typeof values.json === 'string') {
130
+ await writeFile(values.json, jsonContent, 'utf-8');
131
+ if (!values.quiet) {
132
+ console.log(`JSON report written to: ${values.json}`);
133
+ }
134
+ }
135
+ }
136
+
137
+ // Console output (unless quiet mode)
138
+ if (!values.quiet) {
139
+ // Group messages by severity
140
+ const fatal = result.messages.filter((m: { severity: string }) => m.severity === 'fatal');
141
+ const errors = result.messages.filter((m: { severity: string }) => m.severity === 'error');
142
+ const warnings = result.messages.filter(
143
+ (m: { severity: string }) => m.severity === 'warning',
144
+ );
145
+ const info = result.messages.filter((m: { severity: string }) => m.severity === 'info');
146
+ const usage = result.messages.filter((m: { severity: string }) => m.severity === 'usage');
147
+
148
+ // Print messages with colors
149
+ const printMessages = (
150
+ messages: typeof result.messages,
151
+ color: string,
152
+ label: string,
153
+ ): void => {
154
+ if (messages.length === 0) return;
155
+
156
+ for (const msg of messages) {
157
+ const locationStr = msg.location
158
+ ? ` (${msg.location.path}${msg.location.line !== undefined ? `:${String(msg.location.line)}` : ''})`
159
+ : '';
160
+ console.log(`${color}${label}${locationStr}: ${msg.message}\x1b[0m`);
161
+ if (msg.id) {
162
+ console.log(` \x1b[90mID: ${msg.id}\x1b[0m`);
163
+ }
164
+ }
165
+ console.log();
166
+ };
167
+
168
+ if (fatal.length > 0) {
169
+ printMessages(fatal, '\x1b[31m\x1b[1m', 'FATAL');
170
+ }
171
+ if (errors.length > 0) {
172
+ printMessages(errors, '\x1b[31m', 'ERROR');
173
+ }
174
+ if (warnings.length > 0) {
175
+ printMessages(warnings, '\x1b[33m', 'WARNING');
176
+ }
177
+ if (info.length > 0 && result.messages.length < 20) {
178
+ // Only show info if total messages is small
179
+ printMessages(info, '\x1b[36m', 'INFO');
180
+ }
181
+ if (usage.length > 0) {
182
+ printMessages(usage, '\x1b[90m', 'USAGE');
183
+ }
184
+
185
+ // Summary
186
+ console.log('─'.repeat(60));
187
+ const summaryColor = result.valid ? '\x1b[32m' : '\x1b[31m';
188
+ const summaryIcon = result.valid ? '✓' : '✗';
189
+ console.log(
190
+ `${summaryColor}${summaryIcon} ${result.valid ? 'Valid EPUB' : 'Invalid EPUB'}\x1b[0m`,
191
+ );
192
+ console.log();
193
+ console.log(` Errors: ${String(result.errorCount + result.fatalCount)}`);
194
+ console.log(` Warnings: ${String(result.warningCount)}`);
195
+ if (info.length > 0) {
196
+ console.log(` Info: ${String(result.infoCount)}`);
197
+ }
198
+ if (usage.length > 0) {
199
+ console.log(` Usages: ${String(result.usageCount)}`);
200
+ }
201
+ console.log(` Time: ${String(elapsedMs)}ms`);
202
+ console.log();
203
+
204
+ // Show limitation notice if there were no major errors
205
+ if (result.errorCount === 0 && result.fatalCount === 0) {
206
+ console.log(
207
+ '\x1b[90mNote: This validator provides ~65% coverage of Java EPUBCheck.\x1b[0m',
208
+ );
209
+ console.log('\x1b[90mFor complete validation: https://github.com/w3c/epubcheck\x1b[0m');
210
+ console.log();
211
+ }
212
+ }
213
+
214
+ // Determine exit code
215
+ const shouldFail =
216
+ result.errorCount > 0 ||
217
+ result.fatalCount > 0 ||
218
+ (values['fail-on-warnings'] && result.warningCount > 0);
219
+ process.exit(shouldFail ? 1 : 0);
220
+ } catch (error) {
221
+ console.error('\x1b[31mError:\x1b[0m', error instanceof Error ? error.message : String(error));
222
+ if (error instanceof Error && error.stack && !values.quiet) {
223
+ console.error('\x1b[90m' + error.stack + '\x1b[0m');
224
+ }
225
+ process.exit(2);
226
+ }
227
+ }
228
+
229
+ void main();