@likecoin/epubcheck-ts 0.1.0 → 0.2.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 CHANGED
@@ -6,14 +6,21 @@ A TypeScript port of [EPUBCheck](https://github.com/w3c/epubcheck) - the officia
6
6
  [![npm](https://img.shields.io/npm/v/%40likecoin%2Fepubcheck-ts)](https://www.npmjs.com/package/@likecoin/epubcheck-ts)
7
7
  [![License](https://img.shields.io/npm/l/%40likecoin%2Fepubcheck-ts)](./LICENSE)
8
8
 
9
+ > **Note**: This library is primarily developed for internal use at [3ook.com](https://3ook.com/about) and is built with AI-assisted development. While it has comprehensive test coverage (208 tests) and ~65% feature parity with Java EPUBCheck, it may not be suitable for mission-critical production workloads. For production environments requiring full EPUB validation, consider using the official [Java EPUBCheck](https://github.com/w3c/epubcheck). Contributions and feedback are welcome!
10
+
9
11
  ## Features
10
12
 
13
+ - **CLI and programmatic API**: Use as a command-line tool or integrate into your application
11
14
  - **Cross-platform**: Works in Node.js (18+) and modern browsers
12
- - **Partial EPUB validation**: Currently ~35% of EPUBCheck feature parity
15
+ - **Partial EPUB validation**: Currently ~65% of EPUBCheck feature parity
13
16
  - **Zero native dependencies**: Pure JavaScript/WebAssembly, no compilation required
14
17
  - **TypeScript first**: Full type definitions included
15
18
  - **Tree-shakable**: ESM with proper exports for optimal bundling
16
19
 
20
+ ## Try it Online
21
+
22
+ Try the live demo at **[likecoin.github.io/epubcheck-ts](https://likecoin.github.io/epubcheck-ts/)** - validate your EPUB files directly in the browser without uploading to any server.
23
+
17
24
  ## Installation
18
25
 
19
26
  ```bash
@@ -22,6 +29,49 @@ npm install @likecoin/epubcheck-ts
22
29
 
23
30
  ## Quick Start
24
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
+
25
75
  ### ES Modules (recommended)
26
76
 
27
77
  ```typescript
@@ -214,19 +264,20 @@ This library is a TypeScript port of the Java-based [EPUBCheck](https://github.c
214
264
 
215
265
  | Component | Status | Completeness | Notes |
216
266
  |-----------|--------|--------------|-------|
217
- | OCF Container | 🟡 Partial | ~40% | ZIP structure, mimetype, container.xml |
218
- | Package Document (OPF) | 🟡 Partial | ~40% | Metadata, manifest, spine, fallback chains |
219
- | Content Documents | 🟡 Partial | ~25% | XML well-formedness, XHTML structure |
220
- | Navigation Document | 🟡 Partial | ~30% | Nav structure, NCX validation |
267
+ | OCF Container | 🟡 Partial | ~70% | ZIP structure, mimetype (uncompressed check), container.xml |
268
+ | Package Document (OPF) | 🟡 Partial | ~70% | Metadata, manifest, spine, collections, version/date validation |
269
+ | Content Documents | 🟡 Partial | ~70% | XHTML structure, script/MathML/SVG detection, link validation |
270
+ | Navigation Document | 🟡 Partial | ~40% | Nav structure, NCX validation, remote link validation |
221
271
  | Schema Validation | 🟡 Partial | ~70% | RelaxNG, XSD, Schematron working |
222
- | CSS | 🔴 Basic | ~5% | Parser available, validation minimal |
272
+ | CSS | 🟡 Partial | ~50% | @font-face, @import, media overlay classes, position warnings |
273
+ | Cross-reference Validation | 🟡 Partial | ~75% | Reference tracking, fragment validation, undeclared resources |
274
+ | Accessibility Checks | 🟡 Partial | ~75% | Empty links, image alt, SVG titles, MathML alttext |
223
275
  | Media Overlays | ❌ Not Started | 0% | Planned |
224
- | Cross-reference Validation | 🔴 Basic | ~15% | Basic reference tracking |
225
- | Accessibility Checks | ❌ Not Started | 0% | Alt text, etc. |
276
+ | Media Validation | Not Started | 0% | Planned |
226
277
 
227
278
  Legend: 🟢 Complete | 🟡 Partial | 🔴 Basic | ❌ Not Started
228
279
 
229
- **Overall Progress: ~35% of Java EPUBCheck features**
280
+ **Overall Progress: ~65% of Java EPUBCheck features**
230
281
 
231
282
  See [PROJECT_STATUS.md](./PROJECT_STATUS.md) for detailed comparison.
232
283
 
@@ -306,7 +357,7 @@ Legend: ✅ Implemented
306
357
  | Aspect | epubcheck-ts | EPUBCheck (Java) |
307
358
  |--------|--------------|------------------|
308
359
  | Runtime | Node.js / Browser | JVM |
309
- | Feature Parity | ~35% | 100% |
360
+ | Feature Parity | ~65% | 100% |
310
361
  | Bundle Size | ~55KB JS + ~1.5MB WASM | ~15MB |
311
362
  | Installation | `npm install` | Download JAR |
312
363
  | Integration | Native JS/TS | CLI or Java API |
@@ -332,6 +383,10 @@ This is an independent TypeScript implementation inspired by the Java-based [EPU
332
383
  - [DAISY Consortium](https://daisy.org/) - Maintainers of EPUBCheck
333
384
  - [libxml2-wasm](https://github.com/jameslan/libxml2-wasm) - WebAssembly XML processing
334
385
 
386
+ ## Built by 3ook.com
387
+
388
+ This project is built and maintained by the [3ook.com](https://3ook.com/about) team. 3ook is a Web3 eBook platform where authors can publish EPUB ebooks and readers can collect them as digital assets. If you're an author looking to publish your ebook, check out [3ook.com](https://3ook.com/about).
389
+
335
390
  ## Related Projects
336
391
 
337
392
  - [EPUBCheck](https://github.com/w3c/epubcheck) - Official Java validator
@@ -0,0 +1,170 @@
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.1";
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((m) => m.severity === "warning");
107
+ const info = result.messages.filter((m) => m.severity === "info");
108
+ const usage = result.messages.filter((m) => m.severity === "usage");
109
+ const printMessages = (messages, color, label) => {
110
+ if (messages.length === 0) return;
111
+ for (const msg of messages) {
112
+ const locationStr = msg.location ? ` (${msg.location.path}${msg.location.line !== void 0 ? `:${String(msg.location.line)}` : ""})` : "";
113
+ console.log(`${color}${label}${locationStr}: ${msg.message}\x1B[0m`);
114
+ if (msg.id) {
115
+ console.log(` \x1B[90mID: ${msg.id}\x1B[0m`);
116
+ }
117
+ }
118
+ console.log();
119
+ };
120
+ if (fatal.length > 0) {
121
+ printMessages(fatal, "\x1B[31m\x1B[1m", "FATAL");
122
+ }
123
+ if (errors.length > 0) {
124
+ printMessages(errors, "\x1B[31m", "ERROR");
125
+ }
126
+ if (warnings.length > 0) {
127
+ printMessages(warnings, "\x1B[33m", "WARNING");
128
+ }
129
+ if (info.length > 0 && result.messages.length < 20) {
130
+ printMessages(info, "\x1B[36m", "INFO");
131
+ }
132
+ if (usage.length > 0) {
133
+ printMessages(usage, "\x1B[90m", "USAGE");
134
+ }
135
+ console.log("\u2500".repeat(60));
136
+ const summaryColor = result.valid ? "\x1B[32m" : "\x1B[31m";
137
+ const summaryIcon = result.valid ? "\u2713" : "\u2717";
138
+ console.log(
139
+ `${summaryColor}${summaryIcon} ${result.valid ? "Valid EPUB" : "Invalid EPUB"}\x1B[0m`
140
+ );
141
+ console.log();
142
+ console.log(` Errors: ${String(result.errorCount + result.fatalCount)}`);
143
+ console.log(` Warnings: ${String(result.warningCount)}`);
144
+ if (info.length > 0) {
145
+ console.log(` Info: ${String(result.infoCount)}`);
146
+ }
147
+ if (usage.length > 0) {
148
+ console.log(` Usages: ${String(result.usageCount)}`);
149
+ }
150
+ console.log(` Time: ${String(elapsedMs)}ms`);
151
+ console.log();
152
+ if (result.errorCount === 0 && result.fatalCount === 0) {
153
+ console.log(
154
+ "\x1B[90mNote: This validator provides ~65% coverage of Java EPUBCheck.\x1B[0m"
155
+ );
156
+ console.log("\x1B[90mFor complete validation: https://github.com/w3c/epubcheck\x1B[0m");
157
+ console.log();
158
+ }
159
+ }
160
+ const shouldFail = result.errorCount > 0 || result.fatalCount > 0 || values["fail-on-warnings"] && result.warningCount > 0;
161
+ process.exit(shouldFail ? 1 : 0);
162
+ } catch (error) {
163
+ console.error("\x1B[31mError:\x1B[0m", error instanceof Error ? error.message : String(error));
164
+ if (error instanceof Error && error.stack && !values.quiet) {
165
+ console.error("\x1B[90m" + error.stack + "\x1B[0m");
166
+ }
167
+ process.exit(2);
168
+ }
169
+ }
170
+ void main();
@@ -0,0 +1,227 @@
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.1';
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) => m.severity === 'fatal');
141
+ const errors = result.messages.filter((m) => m.severity === 'error');
142
+ const warnings = result.messages.filter((m) => m.severity === 'warning');
143
+ const info = result.messages.filter((m) => m.severity === 'info');
144
+ const usage = result.messages.filter((m) => m.severity === 'usage');
145
+
146
+ // Print messages with colors
147
+ const printMessages = (
148
+ messages: typeof result.messages,
149
+ color: string,
150
+ label: string,
151
+ ): void => {
152
+ if (messages.length === 0) return;
153
+
154
+ for (const msg of messages) {
155
+ const locationStr = msg.location
156
+ ? ` (${msg.location.path}${msg.location.line !== undefined ? `:${String(msg.location.line)}` : ''})`
157
+ : '';
158
+ console.log(`${color}${label}${locationStr}: ${msg.message}\x1b[0m`);
159
+ if (msg.id) {
160
+ console.log(` \x1b[90mID: ${msg.id}\x1b[0m`);
161
+ }
162
+ }
163
+ console.log();
164
+ };
165
+
166
+ if (fatal.length > 0) {
167
+ printMessages(fatal, '\x1b[31m\x1b[1m', 'FATAL');
168
+ }
169
+ if (errors.length > 0) {
170
+ printMessages(errors, '\x1b[31m', 'ERROR');
171
+ }
172
+ if (warnings.length > 0) {
173
+ printMessages(warnings, '\x1b[33m', 'WARNING');
174
+ }
175
+ if (info.length > 0 && result.messages.length < 20) {
176
+ // Only show info if total messages is small
177
+ printMessages(info, '\x1b[36m', 'INFO');
178
+ }
179
+ if (usage.length > 0) {
180
+ printMessages(usage, '\x1b[90m', 'USAGE');
181
+ }
182
+
183
+ // Summary
184
+ console.log('─'.repeat(60));
185
+ const summaryColor = result.valid ? '\x1b[32m' : '\x1b[31m';
186
+ const summaryIcon = result.valid ? '✓' : '✗';
187
+ console.log(
188
+ `${summaryColor}${summaryIcon} ${result.valid ? 'Valid EPUB' : 'Invalid EPUB'}\x1b[0m`,
189
+ );
190
+ console.log();
191
+ console.log(` Errors: ${String(result.errorCount + result.fatalCount)}`);
192
+ console.log(` Warnings: ${String(result.warningCount)}`);
193
+ if (info.length > 0) {
194
+ console.log(` Info: ${String(result.infoCount)}`);
195
+ }
196
+ if (usage.length > 0) {
197
+ console.log(` Usages: ${String(result.usageCount)}`);
198
+ }
199
+ console.log(` Time: ${String(elapsedMs)}ms`);
200
+ console.log();
201
+
202
+ // Show limitation notice if there were no major errors
203
+ if (result.errorCount === 0 && result.fatalCount === 0) {
204
+ console.log(
205
+ '\x1b[90mNote: This validator provides ~65% coverage of Java EPUBCheck.\x1b[0m',
206
+ );
207
+ console.log('\x1b[90mFor complete validation: https://github.com/w3c/epubcheck\x1b[0m');
208
+ console.log();
209
+ }
210
+ }
211
+
212
+ // Determine exit code
213
+ const shouldFail =
214
+ result.errorCount > 0 ||
215
+ result.fatalCount > 0 ||
216
+ (values['fail-on-warnings'] && result.warningCount > 0);
217
+ process.exit(shouldFail ? 1 : 0);
218
+ } catch (error) {
219
+ console.error('\x1b[31mError:\x1b[0m', error instanceof Error ? error.message : String(error));
220
+ if (error instanceof Error && error.stack && !values.quiet) {
221
+ console.error('\x1b[90m' + error.stack + '\x1b[0m');
222
+ }
223
+ process.exit(2);
224
+ }
225
+ }
226
+
227
+ void main();