@likecoin/epubcheck-ts 0.2.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 +44 -0
- package/bin/epubcheck.js +170 -0
- package/bin/epubcheck.ts +227 -0
- package/dist/index.cjs +21 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +21 -9
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
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
|
package/bin/epubcheck.js
ADDED
|
@@ -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();
|
package/bin/epubcheck.ts
ADDED
|
@@ -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();
|
package/dist/index.cjs
CHANGED
|
@@ -1380,6 +1380,7 @@ var OCFValidator = class {
|
|
|
1380
1380
|
"metadata.xml"
|
|
1381
1381
|
]);
|
|
1382
1382
|
for (const file of metaInfFiles) {
|
|
1383
|
+
if (file === "META-INF/") continue;
|
|
1383
1384
|
const filename = file.replace("META-INF/", "");
|
|
1384
1385
|
if (!allowedFiles.has(filename)) {
|
|
1385
1386
|
messages.push({
|
|
@@ -1397,6 +1398,7 @@ var OCFValidator = class {
|
|
|
1397
1398
|
validateFilenames(zip, messages) {
|
|
1398
1399
|
for (const path of zip.paths) {
|
|
1399
1400
|
if (path === "mimetype") continue;
|
|
1401
|
+
if (path.endsWith("/")) continue;
|
|
1400
1402
|
const filename = path.includes("/") ? path.split("/").pop() ?? path : path;
|
|
1401
1403
|
if (filename === "" || filename === "." || filename === "..") {
|
|
1402
1404
|
messages.push({
|
|
@@ -2220,12 +2222,13 @@ var OPFValidator = class {
|
|
|
2220
2222
|
}
|
|
2221
2223
|
declaredPaths.add(opfPath);
|
|
2222
2224
|
for (const filePath of context.files.keys()) {
|
|
2225
|
+
if (filePath.endsWith("/")) continue;
|
|
2223
2226
|
if (filePath.startsWith("META-INF/")) continue;
|
|
2224
2227
|
if (filePath === "mimetype") continue;
|
|
2225
2228
|
if (declaredPaths.has(filePath)) continue;
|
|
2226
2229
|
context.messages.push({
|
|
2227
2230
|
id: "RSC-008",
|
|
2228
|
-
severity: "
|
|
2231
|
+
severity: "error",
|
|
2229
2232
|
message: `File in container is not declared in manifest: ${filePath}`,
|
|
2230
2233
|
location: { path: filePath }
|
|
2231
2234
|
});
|
|
@@ -3309,7 +3312,16 @@ var EpubCheck = class _EpubCheck {
|
|
|
3309
3312
|
});
|
|
3310
3313
|
}
|
|
3311
3314
|
const elapsedMs = performance.now() - startTime;
|
|
3312
|
-
|
|
3315
|
+
const filteredMessages = context.messages.filter((msg) => {
|
|
3316
|
+
if (!this.options.includeUsage && msg.severity === "usage") {
|
|
3317
|
+
return false;
|
|
3318
|
+
}
|
|
3319
|
+
if (!this.options.includeInfo && msg.severity === "info") {
|
|
3320
|
+
return false;
|
|
3321
|
+
}
|
|
3322
|
+
return true;
|
|
3323
|
+
});
|
|
3324
|
+
return buildReport(filteredMessages, context.version, elapsedMs);
|
|
3313
3325
|
}
|
|
3314
3326
|
/**
|
|
3315
3327
|
* Static method to validate an EPUB file with default options
|
|
@@ -3353,20 +3365,20 @@ var EpubCheck = class _EpubCheck {
|
|
|
3353
3365
|
const ncxContent = new TextDecoder().decode(ncxData);
|
|
3354
3366
|
const ncxValidator = new NCXValidator();
|
|
3355
3367
|
ncxValidator.validate(context, ncxContent, ncxPath);
|
|
3356
|
-
if (context.ncxUid) {
|
|
3357
|
-
const
|
|
3358
|
-
|
|
3368
|
+
if (context.ncxUid && context.packageDocument.uniqueIdentifier) {
|
|
3369
|
+
const uniqueIdRef = context.packageDocument.uniqueIdentifier;
|
|
3370
|
+
const matchingIdentifier = context.packageDocument.dcElements.find(
|
|
3371
|
+
(dc) => dc.name === "identifier" && dc.id === uniqueIdRef
|
|
3359
3372
|
);
|
|
3360
|
-
|
|
3361
|
-
const opfUid =
|
|
3373
|
+
if (matchingIdentifier) {
|
|
3374
|
+
const opfUid = matchingIdentifier.value.trim();
|
|
3362
3375
|
if (context.ncxUid !== opfUid) {
|
|
3363
3376
|
context.messages.push({
|
|
3364
3377
|
id: "NCX-001",
|
|
3365
|
-
severity: "
|
|
3378
|
+
severity: "error",
|
|
3366
3379
|
message: `NCX uid "${context.ncxUid}" does not match OPF unique identifier "${opfUid}"`,
|
|
3367
3380
|
location: { path: ncxPath }
|
|
3368
3381
|
});
|
|
3369
|
-
break;
|
|
3370
3382
|
}
|
|
3371
3383
|
}
|
|
3372
3384
|
}
|