@likecoin/epubcheck-ts 0.4.0 → 0.5.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/bin/epubcheck.js CHANGED
@@ -1,27 +1,55 @@
1
1
  #!/usr/bin/env node
2
- import { readFile, writeFile } from "node:fs/promises";
2
+ import { readFile, readdir, stat, writeFile } from "node:fs/promises";
3
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.4.0";
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
- 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" },
17
- version: { type: "boolean", short: "v", default: false },
18
- help: { type: "boolean", short: "h", default: false },
19
- "fail-on-warnings": { type: "boolean", short: "w", default: false },
20
- listChecks: { type: "boolean", short: "l", default: false }
21
- },
22
- allowPositionals: true,
23
- strict: false
24
- });
4
+ import { basename, join, relative, sep } from "node:path";
5
+ const { EpubCheck, EPUB_VERSIONS, toJSONReport } = await import("../dist/index.js");
6
+ const VERSION = "0.5.0";
7
+ const VALID_MODES = /* @__PURE__ */ new Set([
8
+ "exp",
9
+ "opf",
10
+ "xhtml",
11
+ "svg",
12
+ "nav",
13
+ "mo"
14
+ ]);
15
+ let values;
16
+ let positionals;
17
+ try {
18
+ ({ values, positionals } = parseArgs({
19
+ options: {
20
+ json: { type: "string", short: "j" },
21
+ quiet: { type: "boolean", short: "q", default: false },
22
+ profile: { type: "string", short: "p" },
23
+ mode: { type: "string", short: "m" },
24
+ "epub-version": { type: "string", short: "v" },
25
+ usage: { type: "boolean", short: "u", default: false },
26
+ fatal: { type: "boolean", short: "f", default: false },
27
+ error: { type: "boolean", short: "e", default: false },
28
+ warn: { type: "boolean", short: "w", default: false },
29
+ info: { type: "boolean", short: "i", default: false },
30
+ customMessages: { type: "string", short: "c" },
31
+ version: { type: "boolean", short: "V", default: false },
32
+ help: { type: "boolean", short: "h", default: false },
33
+ "fail-on-warnings": { type: "boolean", default: false },
34
+ failonwarnings: { type: "boolean", default: false },
35
+ listChecks: { type: "boolean", short: "l", default: false }
36
+ },
37
+ allowPositionals: true,
38
+ strict: true
39
+ }));
40
+ } catch (err) {
41
+ const message = err instanceof Error ? err.message : String(err);
42
+ console.error(`\x1B[31mError:\x1B[0m ${message}`);
43
+ const unsupportedJavaFlags = ["--out", "-o", "--xmp", "-x", "--save", "--locale"];
44
+ const matched = unsupportedJavaFlags.find((f) => message.includes(f));
45
+ if (matched) {
46
+ console.error(
47
+ `\x1B[90mNote: ${matched} is a Java EPUBCheck flag that epubcheck-ts does not yet support.\x1B[0m`
48
+ );
49
+ }
50
+ console.error("Run with --help for usage information");
51
+ process.exit(2);
52
+ }
25
53
  if (values.version) {
26
54
  console.log(`EPUBCheck-TS v${VERSION}`);
27
55
  console.log("TypeScript EPUB validator for Node.js and browsers");
@@ -39,62 +67,115 @@ if (values.listChecks) {
39
67
  if (values.help || positionals.length === 0) {
40
68
  console.log(`EPUBCheck-TS v${VERSION} - EPUB Validator
41
69
 
42
- Usage: epubcheck-ts <file.epub> [options]
70
+ Usage: epubcheck-ts <file> [options]
43
71
 
44
72
  Arguments:
45
- <file.epub> Path to EPUB file to validate
73
+ <file> Path to EPUB file, directory, or single file to validate
46
74
 
47
75
  Options:
48
76
  -j, --json <file> Output JSON report to file (use '-' for stdout)
49
77
  -q, --quiet Suppress console output (errors only)
50
78
  -p, --profile <name> Validation profile (default|dict|edupub|idx|preview)
79
+ -m, --mode <type> Validation mode: exp (expanded directory), opf, xhtml, svg, nav, mo
80
+ -v, --epub-version <ver> EPUB version for single-file mode (2|2.0|3|3.0|3.1|3.2|3.3)
51
81
  -u, --usage Include usage messages (best practices)
52
82
  -f, --fatal Show only fatal errors
53
83
  -e, --error Show fatal errors and errors
54
- --warn Show fatal errors, errors, and warnings
84
+ -w, --warn Show fatal errors, errors, and warnings
85
+ -i, --info Show fatal, error, warning, and info messages
55
86
  -c, --customMessages <file> Override message severities (TSV: ID\\tSEVERITY)
56
- -w, --fail-on-warnings Exit with code 1 if warnings are found
87
+ --fail-on-warnings Exit with code 1 if warnings are found
88
+ (also accepts --failonwarnings for Java compatibility)
57
89
  -l, --listChecks List all message IDs and severities
58
- -v, --version Show version information
90
+ -V, --version Show version information
59
91
  -h, --help Show this help message
60
92
 
93
+ Modes:
94
+ --mode exp Validate an expanded (unpacked) EPUB directory
95
+ --mode opf -v 3.0 Validate a standalone OPF package document
96
+ --mode xhtml -v 3.0 Validate a standalone XHTML content document
97
+ --mode svg -v 3.0 Validate a standalone SVG content document
98
+ --mode nav -v 3.0 Validate a standalone Navigation Document (EPUB 3 only)
99
+ --mode mo -v 3.0 Validate a standalone SMIL media overlay document
100
+
61
101
  Examples:
62
102
  epubcheck-ts book.epub
63
103
  epubcheck-ts book.epub --json report.json
64
104
  epubcheck-ts book.epub --quiet --fail-on-warnings
65
105
  epubcheck-ts book.epub --profile dict
106
+ epubcheck-ts ./unpacked-epub/ --mode exp
107
+ epubcheck-ts chapter.xhtml --mode xhtml -v 3.0
108
+ epubcheck-ts package.opf --mode opf -v 3.0
109
+ epubcheck-ts image.svg --mode svg -v 3.0
110
+ epubcheck-ts nav.xhtml --mode nav -v 3.0
111
+ epubcheck-ts overlay.smil --mode mo -v 3.0
66
112
 
67
113
  Exit Codes:
68
114
  0 No errors (or only warnings if --fail-on-warnings not set)
69
115
  1 Validation errors found (or warnings with --fail-on-warnings)
70
116
  2 Runtime error (file not found, invalid arguments, etc.)
71
117
 
72
- Note: This tool provides ~93% coverage of Java EPUBCheck features.
73
- Missing features: single-file/directory validation, advanced ARIA, XHTML/SVG schema.
74
- For complete EPUB 3 conformance testing, use: https://github.com/w3c/epubcheck
75
-
76
118
  Report issues: https://github.com/likecoin/epubcheck-ts/issues
77
119
  `);
78
120
  process.exit(0);
79
121
  }
122
+ async function readDirectoryFiles(dirPath) {
123
+ const files = /* @__PURE__ */ new Map();
124
+ async function walk(dir) {
125
+ const entries = await readdir(dir, { withFileTypes: true });
126
+ for (const entry of entries) {
127
+ const fullPath = join(dir, entry.name);
128
+ if (entry.isDirectory()) {
129
+ await walk(fullPath);
130
+ } else if (entry.isFile()) {
131
+ const relPath = relative(dirPath, fullPath).split(sep).join("/");
132
+ const data = await readFile(fullPath);
133
+ files.set(relPath, data);
134
+ }
135
+ }
136
+ }
137
+ await walk(dirPath);
138
+ return files;
139
+ }
80
140
  async function main() {
81
141
  const filePath = positionals[0];
82
142
  if (!filePath) {
83
- console.error("Error: No EPUB file specified");
143
+ console.error("Error: No file specified");
84
144
  console.error("Run with --help for usage information");
85
145
  process.exit(2);
86
146
  }
147
+ const mode = values.mode;
148
+ if (mode && !VALID_MODES.has(mode)) {
149
+ console.error(`Error: Invalid mode "${mode}". Valid modes: ${[...VALID_MODES].join(", ")}`);
150
+ process.exit(2);
151
+ }
152
+ const rawVersion = values["epub-version"];
153
+ const epubVersion = rawVersion === "2" ? "2.0" : rawVersion === "3" ? "3.0" : rawVersion;
154
+ if (epubVersion && !EPUB_VERSIONS.includes(epubVersion)) {
155
+ console.error(
156
+ `Error: Invalid EPUB version "${epubVersion}". Valid versions: ${EPUB_VERSIONS.join(", ")}`
157
+ );
158
+ process.exit(2);
159
+ }
160
+ if (mode && mode !== "exp" && !epubVersion) {
161
+ console.error(`Error: --epub-version (-v) is required when using --mode ${mode}`);
162
+ process.exit(2);
163
+ }
87
164
  try {
88
165
  if (!values.quiet) {
89
166
  console.log(`Validating: ${basename(filePath)}`);
90
167
  console.log();
91
168
  }
92
- const epubData = await readFile(filePath);
93
- const startTime = Date.now();
94
169
  const options = {};
95
170
  if (values.profile) {
96
171
  options.profile = values.profile;
97
172
  }
173
+ if (epubVersion) {
174
+ options.version = epubVersion;
175
+ }
176
+ if (mode) {
177
+ options.mode = mode;
178
+ }
98
179
  if (values.usage) {
99
180
  options.includeUsage = true;
100
181
  }
@@ -103,7 +184,28 @@ async function main() {
103
184
  const cmContent = await readFile(values.customMessages, "utf-8");
104
185
  options.customMessages = parseCustomMessages(cmContent);
105
186
  }
106
- const result = await EpubCheck.validate(epubData, options);
187
+ const startTime = Date.now();
188
+ let result;
189
+ let effectiveMode = mode;
190
+ if (!mode || mode === "exp") {
191
+ const fileStat = await stat(filePath);
192
+ if (fileStat.isDirectory()) {
193
+ effectiveMode = "exp";
194
+ } else if (mode === "exp") {
195
+ console.error("Error: --mode exp requires a directory path");
196
+ process.exit(2);
197
+ }
198
+ }
199
+ if (effectiveMode === "exp") {
200
+ const files = await readDirectoryFiles(filePath);
201
+ result = await EpubCheck.validateExpanded(files, options);
202
+ } else if (effectiveMode === "opf" || effectiveMode === "xhtml" || effectiveMode === "svg" || effectiveMode === "nav" || effectiveMode === "mo") {
203
+ const fileData = await readFile(filePath);
204
+ result = await EpubCheck.validateSingleFile(fileData, basename(filePath), options);
205
+ } else {
206
+ const epubData = await readFile(filePath);
207
+ result = await EpubCheck.validate(epubData, options, basename(filePath));
208
+ }
107
209
  const elapsedMs = Date.now() - startTime;
108
210
  const severityRank = {
109
211
  fatal: 0,
@@ -116,7 +218,8 @@ async function main() {
116
218
  if (values.fatal) maxRank = 0;
117
219
  else if (values.error) maxRank = 1;
118
220
  else if (values.warn) maxRank = 2;
119
- const isFiltered = values.fatal || values.error || values.warn;
221
+ else if (values.info) maxRank = 3;
222
+ const isFiltered = values.fatal || values.error || values.warn || values.info;
120
223
  const displayMessages = result.messages.filter(
121
224
  (m) => severityRank[m.severity] <= maxRank
122
225
  );
@@ -194,7 +297,8 @@ async function main() {
194
297
  console.log();
195
298
  }
196
299
  }
197
- const shouldFail = result.errorCount > 0 || result.fatalCount > 0 || values["fail-on-warnings"] && result.warningCount > 0;
300
+ const failOnWarnings = values["fail-on-warnings"] || values.failonwarnings;
301
+ const shouldFail = result.errorCount > 0 || result.fatalCount > 0 || failOnWarnings && result.warningCount > 0;
198
302
  process.exit(shouldFail ? 1 : 0);
199
303
  } catch (error) {
200
304
  console.error("\x1B[31mError:\x1B[0m", error instanceof Error ? error.message : String(error));
package/bin/epubcheck.ts CHANGED
@@ -7,35 +7,88 @@
7
7
  * https://github.com/w3c/epubcheck
8
8
  */
9
9
 
10
- import { readFile, writeFile } from 'node:fs/promises';
10
+ import { readFile, readdir, stat, writeFile } from 'node:fs/promises';
11
11
  import { parseArgs } from 'node:util';
12
- import { basename } from 'node:path';
13
- import type { EpubCheckOptions, EPUBProfile, Severity, ValidationMessage } from '../src/types.js';
12
+ import { basename, join, relative, sep } from 'node:path';
13
+ import type {
14
+ EpubCheckOptions,
15
+ EPUBProfile,
16
+ EPUBVersion,
17
+ Severity,
18
+ ValidationMessage,
19
+ ValidationMode,
20
+ } from '../src/types.js';
14
21
 
15
22
  // Dynamic import to support both ESM and CJS builds
16
- const { EpubCheck, toJSONReport } = await import('../dist/index.js');
17
-
18
- const VERSION = '0.4.0';
19
-
20
- // Parse command line arguments
21
- const { values, positionals } = parseArgs({
22
- options: {
23
- json: { type: 'string', short: 'j' },
24
- quiet: { type: 'boolean', short: 'q', default: false },
25
- profile: { type: 'string', short: 'p' },
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' },
31
- version: { type: 'boolean', short: 'v', default: false },
32
- help: { type: 'boolean', short: 'h', default: false },
33
- 'fail-on-warnings': { type: 'boolean', short: 'w', default: false },
34
- listChecks: { type: 'boolean', short: 'l', default: false },
35
- },
36
- allowPositionals: true,
37
- strict: false,
38
- });
23
+ const { EpubCheck, EPUB_VERSIONS, toJSONReport } = await import('../dist/index.js');
24
+
25
+ const VERSION = '0.5.0';
26
+ const VALID_MODES: ReadonlySet<ValidationMode> = new Set([
27
+ 'exp',
28
+ 'opf',
29
+ 'xhtml',
30
+ 'svg',
31
+ 'nav',
32
+ 'mo',
33
+ ]);
34
+
35
+ interface CliValues {
36
+ json?: string;
37
+ quiet: boolean;
38
+ profile?: string;
39
+ mode?: string;
40
+ 'epub-version'?: string;
41
+ usage: boolean;
42
+ fatal: boolean;
43
+ error: boolean;
44
+ warn: boolean;
45
+ info: boolean;
46
+ customMessages?: string;
47
+ version: boolean;
48
+ help: boolean;
49
+ 'fail-on-warnings': boolean;
50
+ failonwarnings: boolean;
51
+ listChecks: boolean;
52
+ }
53
+
54
+ let values: CliValues;
55
+ let positionals: string[];
56
+ try {
57
+ ({ values, positionals } = parseArgs({
58
+ options: {
59
+ json: { type: 'string', short: 'j' },
60
+ quiet: { type: 'boolean', short: 'q', default: false },
61
+ profile: { type: 'string', short: 'p' },
62
+ mode: { type: 'string', short: 'm' },
63
+ 'epub-version': { type: 'string', short: 'v' },
64
+ usage: { type: 'boolean', short: 'u', default: false },
65
+ fatal: { type: 'boolean', short: 'f', default: false },
66
+ error: { type: 'boolean', short: 'e', default: false },
67
+ warn: { type: 'boolean', short: 'w', default: false },
68
+ info: { type: 'boolean', short: 'i', default: false },
69
+ customMessages: { type: 'string', short: 'c' },
70
+ version: { type: 'boolean', short: 'V', default: false },
71
+ help: { type: 'boolean', short: 'h', default: false },
72
+ 'fail-on-warnings': { type: 'boolean', default: false },
73
+ failonwarnings: { type: 'boolean', default: false },
74
+ listChecks: { type: 'boolean', short: 'l', default: false },
75
+ },
76
+ allowPositionals: true,
77
+ strict: true,
78
+ }) as unknown as { values: CliValues; positionals: string[] });
79
+ } catch (err) {
80
+ const message = err instanceof Error ? err.message : String(err);
81
+ console.error(`\x1b[31mError:\x1b[0m ${message}`);
82
+ const unsupportedJavaFlags = ['--out', '-o', '--xmp', '-x', '--save', '--locale'];
83
+ const matched = unsupportedJavaFlags.find((f) => message.includes(f));
84
+ if (matched) {
85
+ console.error(
86
+ `\x1b[90mNote: ${matched} is a Java EPUBCheck flag that epubcheck-ts does not yet support.\x1b[0m`,
87
+ );
88
+ }
89
+ console.error('Run with --help for usage information');
90
+ process.exit(2);
91
+ }
39
92
 
40
93
  // Show version
41
94
  if (values.version) {
@@ -59,70 +112,131 @@ if (values.listChecks) {
59
112
  if (values.help || positionals.length === 0) {
60
113
  console.log(`EPUBCheck-TS v${VERSION} - EPUB Validator
61
114
 
62
- Usage: epubcheck-ts <file.epub> [options]
115
+ Usage: epubcheck-ts <file> [options]
63
116
 
64
117
  Arguments:
65
- <file.epub> Path to EPUB file to validate
118
+ <file> Path to EPUB file, directory, or single file to validate
66
119
 
67
120
  Options:
68
121
  -j, --json <file> Output JSON report to file (use '-' for stdout)
69
122
  -q, --quiet Suppress console output (errors only)
70
123
  -p, --profile <name> Validation profile (default|dict|edupub|idx|preview)
124
+ -m, --mode <type> Validation mode: exp (expanded directory), opf, xhtml, svg, nav, mo
125
+ -v, --epub-version <ver> EPUB version for single-file mode (2|2.0|3|3.0|3.1|3.2|3.3)
71
126
  -u, --usage Include usage messages (best practices)
72
127
  -f, --fatal Show only fatal errors
73
128
  -e, --error Show fatal errors and errors
74
- --warn Show fatal errors, errors, and warnings
129
+ -w, --warn Show fatal errors, errors, and warnings
130
+ -i, --info Show fatal, error, warning, and info messages
75
131
  -c, --customMessages <file> Override message severities (TSV: ID\\tSEVERITY)
76
- -w, --fail-on-warnings Exit with code 1 if warnings are found
132
+ --fail-on-warnings Exit with code 1 if warnings are found
133
+ (also accepts --failonwarnings for Java compatibility)
77
134
  -l, --listChecks List all message IDs and severities
78
- -v, --version Show version information
135
+ -V, --version Show version information
79
136
  -h, --help Show this help message
80
137
 
138
+ Modes:
139
+ --mode exp Validate an expanded (unpacked) EPUB directory
140
+ --mode opf -v 3.0 Validate a standalone OPF package document
141
+ --mode xhtml -v 3.0 Validate a standalone XHTML content document
142
+ --mode svg -v 3.0 Validate a standalone SVG content document
143
+ --mode nav -v 3.0 Validate a standalone Navigation Document (EPUB 3 only)
144
+ --mode mo -v 3.0 Validate a standalone SMIL media overlay document
145
+
81
146
  Examples:
82
147
  epubcheck-ts book.epub
83
148
  epubcheck-ts book.epub --json report.json
84
149
  epubcheck-ts book.epub --quiet --fail-on-warnings
85
150
  epubcheck-ts book.epub --profile dict
151
+ epubcheck-ts ./unpacked-epub/ --mode exp
152
+ epubcheck-ts chapter.xhtml --mode xhtml -v 3.0
153
+ epubcheck-ts package.opf --mode opf -v 3.0
154
+ epubcheck-ts image.svg --mode svg -v 3.0
155
+ epubcheck-ts nav.xhtml --mode nav -v 3.0
156
+ epubcheck-ts overlay.smil --mode mo -v 3.0
86
157
 
87
158
  Exit Codes:
88
159
  0 No errors (or only warnings if --fail-on-warnings not set)
89
160
  1 Validation errors found (or warnings with --fail-on-warnings)
90
161
  2 Runtime error (file not found, invalid arguments, etc.)
91
162
 
92
- Note: This tool provides ~93% coverage of Java EPUBCheck features.
93
- Missing features: single-file/directory validation, advanced ARIA, XHTML/SVG schema.
94
- For complete EPUB 3 conformance testing, use: https://github.com/w3c/epubcheck
95
-
96
163
  Report issues: https://github.com/likecoin/epubcheck-ts/issues
97
164
  `);
98
165
  process.exit(0);
99
166
  }
100
167
 
168
+ /**
169
+ * Recursively read all files in a directory into a Map
170
+ */
171
+ async function readDirectoryFiles(dirPath: string): Promise<Map<string, Uint8Array>> {
172
+ const files = new Map<string, Uint8Array>();
173
+
174
+ async function walk(dir: string): Promise<void> {
175
+ const entries = await readdir(dir, { withFileTypes: true });
176
+ for (const entry of entries) {
177
+ const fullPath = join(dir, entry.name);
178
+ if (entry.isDirectory()) {
179
+ await walk(fullPath);
180
+ } else if (entry.isFile()) {
181
+ const relPath = relative(dirPath, fullPath).split(sep).join('/');
182
+ const data = await readFile(fullPath);
183
+ files.set(relPath, data);
184
+ }
185
+ }
186
+ }
187
+
188
+ await walk(dirPath);
189
+ return files;
190
+ }
191
+
101
192
  // Main validation logic
102
193
  async function main(): Promise<void> {
103
194
  const filePath = positionals[0];
104
195
 
105
196
  if (!filePath) {
106
- console.error('Error: No EPUB file specified');
197
+ console.error('Error: No file specified');
107
198
  console.error('Run with --help for usage information');
108
199
  process.exit(2);
109
200
  }
110
201
 
202
+ const mode = values.mode as ValidationMode | undefined;
203
+ if (mode && !VALID_MODES.has(mode)) {
204
+ console.error(`Error: Invalid mode "${mode}". Valid modes: ${[...VALID_MODES].join(', ')}`);
205
+ process.exit(2);
206
+ }
207
+
208
+ const rawVersion = values['epub-version'];
209
+ const epubVersion = rawVersion === '2' ? '2.0' : rawVersion === '3' ? '3.0' : rawVersion;
210
+ if (epubVersion && !(EPUB_VERSIONS as readonly string[]).includes(epubVersion)) {
211
+ console.error(
212
+ `Error: Invalid EPUB version "${epubVersion}". Valid versions: ${EPUB_VERSIONS.join(', ')}`,
213
+ );
214
+ process.exit(2);
215
+ }
216
+
217
+ // Single-file modes require a version
218
+ if (mode && mode !== 'exp' && !epubVersion) {
219
+ console.error(`Error: --epub-version (-v) is required when using --mode ${mode}`);
220
+ process.exit(2);
221
+ }
222
+
111
223
  try {
112
- // Read EPUB file
113
224
  if (!values.quiet) {
114
225
  console.log(`Validating: ${basename(filePath)}`);
115
226
  console.log();
116
227
  }
117
228
 
118
- const epubData = await readFile(filePath);
119
-
120
- // Validate
121
- const startTime = Date.now();
229
+ // Build options
122
230
  const options: EpubCheckOptions = {};
123
231
  if (values.profile) {
124
232
  options.profile = values.profile as EPUBProfile;
125
233
  }
234
+ if (epubVersion) {
235
+ options.version = epubVersion as EPUBVersion;
236
+ }
237
+ if (mode) {
238
+ options.mode = mode;
239
+ }
126
240
  if (values.usage) {
127
241
  options.includeUsage = true;
128
242
  }
@@ -131,10 +245,41 @@ async function main(): Promise<void> {
131
245
  const cmContent = await readFile(values.customMessages, 'utf-8');
132
246
  options.customMessages = parseCustomMessages(cmContent);
133
247
  }
134
- const result = await EpubCheck.validate(epubData, options);
248
+
249
+ const startTime = Date.now();
250
+ let result;
251
+
252
+ // Determine effective mode (auto-detect directory as expanded)
253
+ let effectiveMode = mode;
254
+ if (!mode || mode === 'exp') {
255
+ const fileStat = await stat(filePath);
256
+ if (fileStat.isDirectory()) {
257
+ effectiveMode = 'exp';
258
+ } else if (mode === 'exp') {
259
+ console.error('Error: --mode exp requires a directory path');
260
+ process.exit(2);
261
+ }
262
+ }
263
+
264
+ if (effectiveMode === 'exp') {
265
+ const files = await readDirectoryFiles(filePath);
266
+ result = await EpubCheck.validateExpanded(files, options);
267
+ } else if (
268
+ effectiveMode === 'opf' ||
269
+ effectiveMode === 'xhtml' ||
270
+ effectiveMode === 'svg' ||
271
+ effectiveMode === 'nav' ||
272
+ effectiveMode === 'mo'
273
+ ) {
274
+ const fileData = await readFile(filePath);
275
+ result = await EpubCheck.validateSingleFile(fileData, basename(filePath), options);
276
+ } else {
277
+ const epubData = await readFile(filePath);
278
+ result = await EpubCheck.validate(epubData, options, basename(filePath));
279
+ }
135
280
  const elapsedMs = Date.now() - startTime;
136
281
 
137
- // Most restrictive severity flag wins (--fatal overrides --error overrides --warn)
282
+ // Most restrictive severity flag wins (--fatal > --error > --warn > --info)
138
283
  const severityRank: Record<Severity, number> = {
139
284
  fatal: 0,
140
285
  error: 1,
@@ -146,7 +291,8 @@ async function main(): Promise<void> {
146
291
  if (values.fatal) maxRank = 0;
147
292
  else if (values.error) maxRank = 1;
148
293
  else if (values.warn) maxRank = 2;
149
- const isFiltered = values.fatal || values.error || values.warn;
294
+ else if (values.info) maxRank = 3;
295
+ const isFiltered = values.fatal || values.error || values.warn || values.info;
150
296
  const displayMessages = result.messages.filter(
151
297
  (m: ValidationMessage) => severityRank[m.severity] <= maxRank,
152
298
  );
@@ -248,10 +394,9 @@ async function main(): Promise<void> {
248
394
  }
249
395
 
250
396
  // Determine exit code
397
+ const failOnWarnings = values['fail-on-warnings'] || values.failonwarnings;
251
398
  const shouldFail =
252
- result.errorCount > 0 ||
253
- result.fatalCount > 0 ||
254
- (values['fail-on-warnings'] && result.warningCount > 0);
399
+ result.errorCount > 0 || result.fatalCount > 0 || (failOnWarnings && result.warningCount > 0);
255
400
  process.exit(shouldFail ? 1 : 0);
256
401
  } catch (error) {
257
402
  console.error('\x1b[31mError:\x1b[0m', error instanceof Error ? error.message : String(error));