gbu-accessibility-package 3.12.0 → 3.13.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/cli.js CHANGED
@@ -1,28 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- /**
4
- * Accessibility Fixer CLI
5
- * Command line interface for the accessibility fixer tool
6
- */
7
-
8
- const AccessibilityFixer = require('./lib/fixer.js');
3
+ const { AccessibilityChecker } = require('./index.js');
9
4
  const chalk = require('chalk');
10
- const path = require('path');
11
5
 
12
- // Parse command line arguments
13
6
  const args = process.argv.slice(2);
14
7
  const options = {
15
8
  directory: '.',
16
9
  language: 'ja',
17
- backupFiles: false, // Default to false for faster processing
18
- dryRun: false,
19
10
  help: false,
20
11
  version: false,
21
- cleanupOnly: false,
22
- comprehensive: false, // Keep for backward compatibility
12
+ dryRun: false,
13
+ comprehensive: false,
23
14
  altOnly: false,
24
15
  langOnly: false,
25
16
  roleOnly: false,
17
+ ariaLabelOnly: false,
26
18
  formsOnly: false,
27
19
  nestedOnly: false,
28
20
  buttonsOnly: false,
@@ -30,24 +22,42 @@ const options = {
30
22
  landmarksOnly: false,
31
23
  headingsOnly: false,
32
24
  dlOnly: false,
25
+ linksCheckOnly: false,
33
26
  brokenLinksOnly: false,
27
+ missingResourcesOnly: false,
28
+ gtmCheckOnly: false,
29
+ checkMetaOnly: false,
30
+ fullReport: false,
34
31
  unusedFilesOnly: false,
32
+ unusedFilesListOnly: false,
35
33
  deadCodeOnly: false,
36
34
  fileSizeOnly: false,
37
- // Enhanced alt options
35
+ listFile: 'unused-files-list.txt',
36
+ reportOutput: null,
38
37
  enhancedAlt: false,
39
- altCreativity: 'balanced', // conservative, balanced, creative
38
+ altCreativity: 'balanced',
40
39
  includeEmotions: false,
41
- strictAltChecking: false,
42
- // Advanced features options
43
- autoFixHeadings: false,
44
- fixDescriptionLists: true
40
+ strictAltChecking: false
41
+ };
42
+
43
+ const removedFlags = {
44
+ '--backup': 'Source file backups were removed because the package no longer edits files.',
45
+ '--no-backup': 'Backups are no longer needed because the package is check/report-only.',
46
+ '--cleanup-only': 'Cleanup mode was removed because it modified source files.',
47
+ '--fix-meta': 'Meta auto-fix was removed. Use --check-meta to review issues.',
48
+ '--meta-fix': 'Meta auto-fix was removed. Use --check-meta to review issues.',
49
+ '--delete-unused-files': 'File deletion was removed. Use --unused-files-list to export a review list instead.',
50
+ '--auto-fix-headings': 'Heading auto-fix was removed. Use --headings-only to review issues instead.'
45
51
  };
46
52
 
47
- // Parse arguments
48
- for (let i = 0; i < args.length; i++) {
53
+ for (let i = 0; i < args.length; i += 1) {
49
54
  const arg = args[i];
50
-
55
+
56
+ if (removedFlags[arg]) {
57
+ console.error(chalk.red(`❌ ${removedFlags[arg]}`));
58
+ process.exit(1);
59
+ }
60
+
51
61
  switch (arg) {
52
62
  case '--help':
53
63
  case '-h':
@@ -59,27 +69,20 @@ for (let i = 0; i < args.length; i++) {
59
69
  break;
60
70
  case '--directory':
61
71
  case '-d':
62
- options.directory = args[++i];
72
+ options.directory = args[i + 1];
73
+ i += 1;
63
74
  break;
64
75
  case '--language':
65
76
  case '-l':
66
- options.language = args[++i];
67
- break;
68
- case '--backup':
69
- options.backupFiles = true;
70
- break;
71
- case '--no-backup':
72
- options.backupFiles = false;
77
+ options.language = args[i + 1];
78
+ i += 1;
73
79
  break;
74
80
  case '--dry-run':
75
81
  options.dryRun = true;
76
82
  break;
77
- case '--cleanup-only':
78
- options.cleanupOnly = true;
79
- break;
80
83
  case '--comprehensive':
81
84
  case '--all':
82
- options.comprehensive = true; // Keep for backward compatibility
85
+ options.comprehensive = true;
83
86
  break;
84
87
  case '--alt-only':
85
88
  options.altOnly = true;
@@ -133,10 +136,6 @@ for (let i = 0; i < args.length; i++) {
133
136
  case '--meta-check':
134
137
  options.checkMetaOnly = true;
135
138
  break;
136
- case '--fix-meta':
137
- case '--meta-fix':
138
- options.fixMetaOnly = true;
139
- break;
140
139
  case '--full-report':
141
140
  case '--report':
142
141
  case '--excel-report':
@@ -144,11 +143,19 @@ for (let i = 0; i < args.length; i++) {
144
143
  break;
145
144
  case '-o':
146
145
  case '--output':
147
- options.reportOutput = args[++i];
146
+ options.reportOutput = args[i + 1];
147
+ i += 1;
148
148
  break;
149
149
  case '--unused-files':
150
150
  options.unusedFilesOnly = true;
151
151
  break;
152
+ case '--unused-files-list':
153
+ options.unusedFilesListOnly = true;
154
+ break;
155
+ case '--list-file':
156
+ options.listFile = args[i + 1];
157
+ i += 1;
158
+ break;
152
159
  case '--dead-code':
153
160
  options.deadCodeOnly = true;
154
161
  break;
@@ -156,17 +163,12 @@ for (let i = 0; i < args.length; i++) {
156
163
  case '--size-check':
157
164
  options.fileSizeOnly = true;
158
165
  break;
159
- case '--auto-fix-headings':
160
- options.autoFixHeadings = true;
161
- break;
162
- case '--no-fix-dl':
163
- options.fixDescriptionLists = false;
164
- break;
165
166
  case '--enhanced-alt':
166
167
  options.enhancedAlt = true;
167
168
  break;
168
169
  case '--alt-creativity':
169
- options.altCreativity = args[++i];
170
+ options.altCreativity = args[i + 1];
171
+ i += 1;
170
172
  break;
171
173
  case '--include-emotions':
172
174
  options.includeEmotions = true;
@@ -177,516 +179,276 @@ for (let i = 0; i < args.length; i++) {
177
179
  default:
178
180
  if (!arg.startsWith('-')) {
179
181
  options.directory = arg;
182
+ } else {
183
+ console.error(chalk.red(`❌ Unknown option: ${arg}`));
184
+ process.exit(1);
180
185
  }
181
186
  }
182
187
  }
183
188
 
184
- // Show help
189
+ function showHelp() {
190
+ console.log(chalk.blue(`
191
+ GBU Accessibility Checker
192
+
193
+ Usage: gbu-a11y [options] [directory]
194
+
195
+ This package now runs in check/report-only mode.
196
+ Source files are never modified.
197
+
198
+ Core options:
199
+ -d, --directory <path> Target directory (default: current directory)
200
+ -l, --language <lang> Language context for checks (default: ja)
201
+ --dry-run Optional legacy flag; check-only mode is always enabled
202
+ -h, --help Show this help message
203
+ -v, --version Show version number
204
+
205
+ Check modes:
206
+ --comprehensive, --all Run the default comprehensive check set
207
+ --alt-only Check alt text issues only
208
+ --lang-only Check HTML lang issues only
209
+ --role-only Check role attribute issues only
210
+ --aria-label-only Check aria-label issues only
211
+ --forms-only Check form label issues only
212
+ --nested-only Check nested interactive controls only
213
+ --buttons-only Check button naming issues only
214
+ --links-only Check accessible link name issues only
215
+ --landmarks-only Check landmark issues only
216
+ --headings-only Check heading structure only
217
+ --dl-only Check description list structure only
218
+ --links-check Check external links and local missing resources
219
+ --broken-links Check broken external links only
220
+ --404-resources Check missing local resources only
221
+ --gtm-check Check Google Tag Manager installation only
222
+ --check-meta Check meta tags and OGP issues only
223
+ --unused-files Check unused files only
224
+ --unused-files-list Export unused files to a text report
225
+ --dead-code Check dead CSS and JavaScript only
226
+ --file-size Check file sizes only
227
+ --full-report Generate the comprehensive Excel report
228
+ -o, --output <file> Custom output path for --full-report
229
+ --list-file <file> Custom output path/name for --unused-files-list
230
+
231
+ Enhanced alt checks:
232
+ --enhanced-alt Enable deeper alt text analysis
233
+ --alt-creativity <mode> conservative | balanced | creative
234
+ --include-emotions Include emotional/contextual alt suggestions
235
+ --strict-alt Enable stricter alt quality rules
236
+
237
+ Removed editing features:
238
+ --backup, --no-backup
239
+ --cleanup-only
240
+ --fix-meta, --meta-fix
241
+ --delete-unused-files
242
+ --auto-fix-headings
243
+
244
+ Examples:
245
+ gbu-a11y
246
+ gbu-a11y --alt-only ./src
247
+ gbu-a11y --links-check ./public
248
+ gbu-a11y --unused-files-list --list-file reports/unused-files.txt
249
+ gbu-a11y --full-report -o reports/accessibility-report.xlsx
250
+ `));
251
+ }
252
+
253
+ function printSummary(label, result) {
254
+ if (!result) {
255
+ return;
256
+ }
257
+
258
+ if (Array.isArray(result)) {
259
+ const issueCount = result.filter((item) => (
260
+ item.status === 'issue-found'
261
+ || item.status === 'fixed'
262
+ || (typeof item.issues === 'number' && item.issues > 0)
263
+ )).length;
264
+ const cleanCount = result.filter((item) => item.status === 'clean' || item.status === 'no-change').length;
265
+ const errorCount = result.filter((item) => item.status === 'error').length;
266
+
267
+ console.log(chalk.blue(`\n📌 ${label}`));
268
+ console.log(` Files with findings: ${issueCount}`);
269
+ console.log(` Clean files: ${cleanCount}`);
270
+ console.log(` Errors: ${errorCount}`);
271
+ return;
272
+ }
273
+
274
+ if (result.filePath) {
275
+ console.log(chalk.blue(`\n📌 ${label}`));
276
+ console.log(` Report: ${result.filePath}`);
277
+ return;
278
+ }
279
+
280
+ if (typeof result.unusedCount === 'number') {
281
+ console.log(chalk.blue(`\n📌 ${label}`));
282
+ console.log(` Unused files found: ${result.unusedCount}`);
283
+ return;
284
+ }
285
+
286
+ if (typeof result.deletedCount === 'number') {
287
+ console.log(chalk.blue(`\n📌 ${label}`));
288
+ console.log(` Files flagged from list: ${result.deletedCount}`);
289
+ console.log(' Note: preview only, no files were deleted.');
290
+ return;
291
+ }
292
+
293
+ if (result.brokenLinks || result.missingResources) {
294
+ printSummary(`${label} - Broken Links`, result.brokenLinks);
295
+ printSummary(`${label} - Missing Resources`, result.missingResources);
296
+ return;
297
+ }
298
+
299
+ const nestedEntries = Object.entries(result).filter(([, value]) => Array.isArray(value));
300
+ if (nestedEntries.length > 0) {
301
+ console.log(chalk.blue(`\n📌 ${label}`));
302
+
303
+ for (const [key, value] of nestedEntries) {
304
+ const issueCount = value.filter((item) => (
305
+ item.status === 'issue-found'
306
+ || item.status === 'fixed'
307
+ || (typeof item.issues === 'number' && item.issues > 0)
308
+ )).length;
309
+ const errorCount = value.filter((item) => item.status === 'error').length;
310
+ console.log(` ${key}: ${issueCount} files with findings, ${errorCount} errors`);
311
+ }
312
+ }
313
+ }
314
+
185
315
  if (options.version) {
186
316
  const packageJson = require('./package.json');
187
- console.log(chalk.blue(`🔧 GBU Accessibility Package v${packageJson.version}`));
317
+ console.log(`GBU Accessibility Checker v${packageJson.version}`);
188
318
  process.exit(0);
189
319
  }
190
320
 
191
321
  if (options.help) {
192
- console.log(chalk.blue(`
193
- 🔧 Accessibility Fixer CLI
194
-
195
- Usage: node cli.js [options] [directory]
196
-
197
- Options:
198
- -d, --directory <path> Target directory (default: current directory)
199
- -l, --language <lang> Language for lang attribute (default: ja)
200
- --backup Create backup files
201
- --no-backup Don't create backup files (default)
202
- --dry-run Preview changes without applying
203
- --comprehensive, --all Run comprehensive fixes (same as default)
204
- --cleanup-only Only cleanup duplicate role attributes
205
- --alt-only Fix alt attributes + cleanup
206
- --lang-only Fix HTML lang attributes + cleanup
207
- --role-only Fix role attributes + cleanup
208
- --aria-label-only Fix aria-label attributes + cleanup
209
- --forms-only Fix form labels + cleanup
210
- --buttons-only Fix button names + cleanup
211
- --links-only Fix link names + cleanup
212
- --landmarks-only Fix landmarks + cleanup
213
- --headings-only Analyze heading structure (no auto-fix)
214
- --links-check Check for broken links and 404 resources (no auto-fix)
215
- --broken-links Check for broken external links only (no auto-fix)
216
- --404-resources Check for missing local resources only (no auto-fix)
217
- --gtm-check Check Google Tag Manager installation (no auto-fix)
218
- --check-meta Check meta tags and Open Graph Protocol (no auto-fix)
219
- --fix-meta Auto-fix missing meta tags and OGP tags
220
- --full-report Generate comprehensive Excel report (all checks)
221
- -o, --output <file> Output path for Excel report (use with --full-report)
222
- --unused-files Check for unused files in project (no auto-fix)
223
- --dead-code Check for dead code in CSS and JavaScript (no auto-fix)
224
- --file-size, --size-check Check file sizes and suggest optimizations (no auto-fix)
225
- --enhanced-alt Use enhanced alt attribute analysis and generation
226
- --alt-creativity <mode> Alt text creativity: conservative, balanced, creative (default: balanced)
227
- --include-emotions Include emotional descriptors in alt text
228
- --strict-alt Enable strict alt attribute quality checking
229
- -h, --help Show this help message
230
- -v, --version Show version number
231
-
232
- Enhanced Alt Features:
233
- --enhanced-alt Comprehensive alt attribute analysis with:
234
- • Image type classification (decorative, functional, complex, etc.)
235
- • Content quality checking (length, redundancy, generic text)
236
- • Context-aware alt text generation
237
- • Multi-language vocabulary support
238
- • Brand and emotional context integration
239
- • Technical image description (charts, graphs)
240
-
241
- Alt Creativity Modes:
242
- conservative Simple, factual descriptions
243
- balanced Context-aware with moderate creativity (default)
244
- creative Rich descriptions with emotions and brand context
245
-
246
- Examples:
247
- node cli.js # Comprehensive fixes (no backup by default)
248
- node cli.js --comprehensive # Comprehensive fixes (same as default)
249
- node cli.js --alt-only # Fix alt attributes + cleanup
250
- node cli.js --forms-only # Fix form labels + cleanup
251
- node cli.js --buttons-only # Fix button names + cleanup
252
- node cli.js --links-only # Fix link names + cleanup
253
- node cli.js --landmarks-only # Fix landmarks + cleanup
254
- node cli.js --headings-only # Analyze heading structure only
255
- node cli.js --links-check # Check for broken links and 404s
256
- node cli.js --broken-links # Check for broken external links only
257
- node cli.js --404-resources # Check for missing local resources only
258
- node cli.js --gtm-check # Check Google Tag Manager installation
259
- node cli.js --check-meta # Check meta tags and Open Graph Protocol
260
- node cli.js --fix-meta # Auto-fix missing meta tags and OGP
261
- node cli.js --fix-meta --dry-run # Preview meta tag fixes
262
- node cli.js --full-report # Generate comprehensive Excel report
263
- node cli.js --full-report ./project -o report.xlsx # Custom output path
264
- node cli.js --unused-files # Check for unused files in project
265
- node cli.js --dead-code # Check for dead CSS and JavaScript code
266
- node cli.js --file-size # Check file sizes and suggest optimizations
267
- node cli.js --cleanup-only # Only cleanup duplicate roles
268
- node cli.js ./src # Fix src directory (comprehensive)
269
- node cli.js -l en --dry-run ./dist # Preview comprehensive fixes in English
270
- node cli.js --backup ./public # Comprehensive fixes with backups
271
- node cli.js --enhanced-alt # Use enhanced alt attribute analysis
272
- node cli.js --enhanced-alt --alt-creativity creative # Creative alt text generation
273
- node cli.js --enhanced-alt --include-emotions # Include emotional context
274
- node cli.js --strict-alt --enhanced-alt # Strict quality checking
275
-
276
- Features:
277
- ✅ Alt attributes for images
278
- ✅ Lang attributes for HTML
279
- ✅ Role attributes for accessibility
280
- ✅ Context-aware text generation
281
- ✅ Automatic backups
282
- `));
322
+ showHelp();
283
323
  process.exit(0);
284
324
  }
285
325
 
326
+ const selectedModes = [
327
+ 'comprehensive',
328
+ 'altOnly',
329
+ 'langOnly',
330
+ 'roleOnly',
331
+ 'ariaLabelOnly',
332
+ 'formsOnly',
333
+ 'nestedOnly',
334
+ 'buttonsOnly',
335
+ 'linksOnly',
336
+ 'landmarksOnly',
337
+ 'headingsOnly',
338
+ 'dlOnly',
339
+ 'linksCheckOnly',
340
+ 'brokenLinksOnly',
341
+ 'missingResourcesOnly',
342
+ 'gtmCheckOnly',
343
+ 'checkMetaOnly',
344
+ 'fullReport',
345
+ 'unusedFilesOnly',
346
+ 'unusedFilesListOnly',
347
+ 'deadCodeOnly',
348
+ 'fileSizeOnly'
349
+ ].filter((key) => options[key]);
350
+
351
+ if (selectedModes.length > 1) {
352
+ console.error(chalk.red('❌ Please select only one check mode at a time.'));
353
+ process.exit(1);
354
+ }
355
+
356
+ const checker = new AccessibilityChecker({
357
+ language: options.language,
358
+ enhancedAltMode: options.enhancedAlt,
359
+ altCreativity: options.altCreativity,
360
+ includeEmotions: options.includeEmotions,
361
+ strictAltChecking: options.strictAltChecking
362
+ });
363
+
364
+ async function run() {
365
+ console.log(chalk.blue('🔍 GBU Accessibility Checker'));
366
+ console.log(chalk.gray(`Target: ${options.directory}`));
367
+ console.log(chalk.gray('Mode: check/report only (source files remain unchanged)'));
286
368
 
287
- // Helper function to show completion message with backup info
288
- function showCompletionMessage(options, mode = 'sửa lỗi') {
289
369
  if (options.dryRun) {
290
- console.log(chalk.cyan('\n💡 Đây chế độ xem trước. Sử dụng không có --dry-run để áp dụng thay đổi.'));
291
- } else {
292
- console.log(chalk.green(`\n🎉 ${mode} hoàn tất thành công!`));
293
- if (options.backupFiles) {
294
- console.log(chalk.gray(' 📁 Đã tạo file backup với đuôi .backup'));
295
- console.log(chalk.gray(' 💡 Sử dụng --no-backup để tắt backup trong các lần chạy sau'));
296
- } else {
297
- console.log(chalk.blue(' ⚡ Không tạo file backup (mặc định để xử lý nhanh hơn)'));
298
- console.log(chalk.gray(' 💡 Sử dụng --backup để bật tính năng backup để an toàn'));
299
- }
370
+ console.log(chalk.gray('Note: --dry-run is now redundant because check-only mode is always enabled.'));
300
371
  }
301
- }
302
372
 
303
- // Main function
304
- async function main() {
305
- console.log(chalk.blue('🚀 Đang khởi động Accessibility Fixer...'));
306
- console.log(chalk.gray(`Thư mục: ${path.resolve(options.directory)}`));
307
- console.log(chalk.gray(`Ngôn ngữ: ${options.language}`));
308
- console.log(chalk.gray(`Backup: ${options.backupFiles ? 'Có' : 'Không'}`));
309
- console.log(chalk.gray(`Chế độ: ${options.dryRun ? 'Xem trước (Preview)' : 'Áp dụng thay đổi'}`));
310
373
  console.log('');
311
374
 
312
- const fixer = new AccessibilityFixer({
313
- language: options.language,
314
- backupFiles: options.backupFiles,
315
- dryRun: options.dryRun,
316
- enhancedAltMode: options.enhancedAlt,
317
- altCreativity: options.altCreativity,
318
- includeEmotions: options.includeEmotions,
319
- strictAltChecking: options.strictAltChecking,
320
- autoFixHeadings: options.autoFixHeadings,
321
- fixDescriptionLists: options.fixDescriptionLists
322
- });
323
-
324
- try {
325
- // Handle Full Report mode first
326
- if (options.fullReport) {
327
- console.log(chalk.blue('📊 Đang tạo báo cáo toàn diện...'));
328
- await fixer.generateFullReport(options.directory, options.reportOutput);
329
- return;
330
- }
331
-
332
- // Handle different modes - All modes now include cleanup
333
- if (options.cleanupOnly || options.altOnly || options.langOnly || options.roleOnly || options.ariaLabelOnly ||
334
- options.formsOnly || options.nestedOnly || options.buttonsOnly || options.linksOnly || options.landmarksOnly ||
335
- options.headingsOnly || options.dlOnly || options.linksCheckOnly || options.brokenLinksOnly || options.missingResourcesOnly || options.gtmCheckOnly || options.checkMetaOnly || options.fixMetaOnly || options.unusedFilesOnly || options.deadCodeOnly || options.fileSizeOnly) {
336
- // Individual modes - handle each separately, then run cleanup
337
- } else {
338
- // Default mode: Run comprehensive fix (all fixes including cleanup)
339
- console.log(chalk.blue('🎯 Đang chạy sửa lỗi accessibility toàn diện...'));
340
- const results = await fixer.fixAllAccessibilityIssues(options.directory);
341
-
342
- // Results already logged in the method
343
- return;
344
- }
345
-
346
- // Individual modes
347
- if (options.cleanupOnly) {
348
- // Only cleanup duplicate roles
349
- console.log(chalk.blue('🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
350
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
351
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
352
-
353
- console.log(chalk.green(`\n✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
354
-
355
- showCompletionMessage(options, 'Dọn dẹp');
356
- return;
357
-
358
- } else if (options.cleanupOnly) {
359
- // Only cleanup duplicate roles
360
- console.log(chalk.blue('🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
361
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
362
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
363
-
364
- console.log(chalk.green(`\n✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
365
-
366
- showCompletionMessage(options, 'Dọn dẹp');
367
- return;
368
-
369
- } else if (options.altOnly) {
370
- // Fix alt attributes + cleanup
371
- console.log(chalk.blue('🖼️ Đang sửa thuộc tính alt + dọn dẹp...'));
372
- const altResults = await fixer.fixEmptyAltAttributes(options.directory);
373
- const altFixed = altResults.filter(r => r.status === 'fixed').length;
374
- const totalAltIssues = altResults.reduce((sum, r) => sum + (r.issues || 0), 0);
375
-
376
- console.log(chalk.green(`\n✅ Đã sửa thuộc tính alt trong ${altFixed} file (${totalAltIssues} vấn đề)`));
377
-
378
- // Run cleanup
379
- console.log(chalk.blue('\n🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
380
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
381
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
382
- console.log(chalk.green(`✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
383
-
384
- showCompletionMessage(options, 'Sửa thuộc tính alt + dọn dẹp');
385
- return;
386
-
387
- } else if (options.langOnly) {
388
- // Fix lang attributes + cleanup
389
- console.log(chalk.blue('📝 Đang sửa thuộc tính HTML lang + dọn dẹp...'));
390
- const langResults = await fixer.fixHtmlLang(options.directory);
391
- const langFixed = langResults.filter(r => r.status === 'fixed').length;
392
-
393
- console.log(chalk.green(`\n✅ Đã sửa thuộc tính lang trong ${langFixed} file`));
394
-
395
- // Run cleanup
396
- console.log(chalk.blue('\n🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
397
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
398
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
399
- console.log(chalk.green(`✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
400
-
401
- showCompletionMessage(options, 'Sửa thuộc tính lang + dọn dẹp');
402
- return;
403
-
404
- } else if (options.roleOnly) {
405
- // Fix role attributes + cleanup
406
- console.log(chalk.blue('🎭 Đang sửa thuộc tính role + dọn dẹp...'));
407
- const roleResults = await fixer.fixRoleAttributes(options.directory);
408
- const roleFixed = roleResults.filter(r => r.status === 'fixed').length;
409
- const totalRoleIssues = roleResults.reduce((sum, r) => sum + (r.issues || 0), 0);
410
-
411
- console.log(chalk.green(`\n✅ Đã sửa thuộc tính role trong ${roleFixed} file (${totalRoleIssues} vấn đề)`));
412
-
413
- // Run cleanup
414
- console.log(chalk.blue('\n🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
415
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
416
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
417
- console.log(chalk.green(`✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
418
-
419
- showCompletionMessage(options, 'Sửa thuộc tính role + dọn dẹp');
420
- return;
421
-
422
- } else if (options.ariaLabelOnly) {
423
- // Fix aria-label attributes + cleanup
424
- console.log(chalk.blue('🏷️ Đang sửa thuộc tính aria-label + dọn dẹp...'));
425
- const ariaResults = await fixer.fixAriaLabels(options.directory);
426
- const ariaFixed = ariaResults.filter(r => r.status === 'processed' && r.changes > 0).length;
427
- const totalAriaIssues = ariaResults.reduce((sum, r) => sum + (r.changes || 0), 0);
428
-
429
- console.log(chalk.green(`\n✅ Đã sửa thuộc tính aria-label trong ${ariaFixed} file (${totalAriaIssues} vấn đề)`));
430
-
431
- // Run cleanup
432
- console.log(chalk.blue('\n🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
433
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
434
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
435
- console.log(chalk.green(`✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
436
-
437
- showCompletionMessage(options, 'Sửa thuộc tính aria-label + dọn dẹp');
438
- return;
439
-
440
- } else if (options.formsOnly) {
441
- // Fix form labels + cleanup
442
- console.log(chalk.blue('📋 Đang sửa nhãn form + dọn dẹp...'));
443
- const formResults = await fixer.fixFormLabels(options.directory);
444
- const formFixed = formResults.filter(r => r.status === 'fixed').length;
445
- const totalFormIssues = formResults.reduce((sum, r) => sum + (r.issues || 0), 0);
446
-
447
- console.log(chalk.green(`\n✅ Đã sửa nhãn form trong ${formFixed} file (${totalFormIssues} vấn đề)`));
448
-
449
- // Run cleanup
450
- console.log(chalk.blue('\n🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
451
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
452
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
453
- console.log(chalk.green(`✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
454
-
455
- showCompletionMessage(options, 'Sửa nhãn form + dọn dẹp');
456
- return;
457
-
458
- } else if (options.nestedOnly) {
459
- // Fix nested interactive controls + cleanup
460
- console.log(chalk.blue('🎯 Đang sửa các control tương tác lồng nhau + dọn dẹp...'));
461
- const nestedResults = await fixer.fixNestedInteractiveControls(options.directory);
462
- const nestedFixed = nestedResults.filter(r => r.status === 'fixed').length;
463
- const totalNestedIssues = nestedResults.reduce((sum, r) => sum + (r.issues || 0), 0);
464
-
465
- console.log(chalk.green(`\n✅ Đã sửa các control tương tác lồng nhau trong ${nestedFixed} file (${totalNestedIssues} vấn đề)`));
466
-
467
- // Run cleanup
468
- console.log(chalk.blue('\n🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
469
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
470
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
471
- console.log(chalk.green(`✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
472
-
473
- showCompletionMessage(options, 'Sửa các control tương tác lồng nhau + dọn dẹp');
474
- return;
475
-
476
- } else if (options.buttonsOnly) {
477
- // Fix button names + cleanup
478
- console.log(chalk.blue('🔘 Đang sửa tên button + dọn dẹp...'));
479
- const buttonResults = await fixer.fixButtonNames(options.directory);
480
- const buttonFixed = buttonResults.filter(r => r.status === 'fixed').length;
481
- const totalButtonIssues = buttonResults.reduce((sum, r) => sum + (r.issues || 0), 0);
482
-
483
- console.log(chalk.green(`\n✅ Đã sửa tên button trong ${buttonFixed} file (${totalButtonIssues} vấn đề)`));
484
-
485
- // Run cleanup
486
- console.log(chalk.blue('\n🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
487
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
488
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
489
- console.log(chalk.green(`✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
490
-
491
- showCompletionMessage(options, 'Sửa tên button + dọn dẹp');
492
- return;
493
-
494
- } else if (options.linksOnly) {
495
- // Fix link names + cleanup
496
- console.log(chalk.blue('🔗 Đang sửa tên link + dọn dẹp...'));
497
- const linkResults = await fixer.fixLinkNames(options.directory);
498
- const linkFixed = linkResults.filter(r => r.status === 'fixed').length;
499
- const totalLinkIssues = linkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
500
-
501
- console.log(chalk.green(`\n✅ Đã sửa tên link trong ${linkFixed} file (${totalLinkIssues} vấn đề)`));
502
-
503
- // Run cleanup
504
- console.log(chalk.blue('\n🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
505
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
506
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
507
- console.log(chalk.green(`✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
508
-
509
- showCompletionMessage(options, 'Sửa tên link + dọn dẹp');
510
- return;
511
-
512
- } else if (options.landmarksOnly) {
513
- // Fix landmarks + cleanup
514
- console.log(chalk.blue('🏛️ Đang sửa landmark + dọn dẹp...'));
515
- const landmarkResults = await fixer.fixLandmarks(options.directory);
516
- const landmarkFixed = landmarkResults.filter(r => r.status === 'fixed').length;
517
- const totalLandmarkIssues = landmarkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
518
-
519
- console.log(chalk.green(`\n✅ Đã sửa landmark trong ${landmarkFixed} file (${totalLandmarkIssues} vấn đề)`));
520
-
521
- // Run cleanup
522
- console.log(chalk.blue('\n🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
523
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
524
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
525
- console.log(chalk.green(`✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
526
-
527
- showCompletionMessage(options, 'Sửa landmark + dọn dẹp');
528
- return;
529
-
530
- } else if (options.headingsOnly) {
531
- // Fix heading structure + cleanup
532
- console.log(chalk.blue('📑 Đang sửa cấu trúc heading + dọn dẹp...'));
533
- const headingResults = await fixer.fixHeadingStructure(options.directory);
534
- const headingFixed = headingResults.filter(r => r.status === 'fixed').length;
535
- const totalHeadingIssues = headingResults.reduce((sum, r) => sum + (r.issues || 0), 0);
536
- const totalHeadingFixes = headingResults.reduce((sum, r) => sum + (r.fixes || 0), 0);
537
-
538
- console.log(chalk.green(`\n✅ Đã xử lý heading trong ${headingResults.length} file (${totalHeadingIssues} vấn đề tìm thấy)`));
539
- if (options.autoFixHeadings) {
540
- console.log(chalk.green(`✅ Đã sửa ${totalHeadingFixes} vấn đề heading tự động`));
541
- } else {
542
- console.log(chalk.gray('💡 Sử dụng --auto-fix-headings để bật tính năng tự động sửa heading'));
543
- }
544
-
545
- // Run cleanup
546
- console.log(chalk.blue('\n🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
547
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
548
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
549
- console.log(chalk.green(`✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
550
-
551
- showCompletionMessage(options, 'Sửa cấu trúc heading + dọn dẹp');
552
- return;
553
-
554
- } else if (options.dlOnly) {
555
- // Fix description lists + cleanup
556
- console.log(chalk.blue('📋 Đang sửa danh sách mô tả + dọn dẹp...'));
557
- const dlResults = await fixer.fixDescriptionLists(options.directory);
558
- const dlFixed = dlResults.filter(r => r.status === 'fixed').length;
559
- const totalDlIssues = dlResults.reduce((sum, r) => sum + (r.issues || 0), 0);
560
-
561
- console.log(chalk.green(`\n✅ Đã sửa danh sách mô tả trong ${dlFixed} file (${totalDlIssues} vấn đề)`));
562
-
563
- // Run cleanup
564
- console.log(chalk.blue('\n🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
565
- const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
566
- const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
567
- console.log(chalk.green(`✅ Đã dọn dẹp role trùng lặp trong ${cleanupFixed} file`));
568
-
569
- showCompletionMessage(options, 'Sửa danh sách mô tả + dọn dẹp');
570
- return;
571
-
572
- } else if (options.linksCheckOnly) {
573
- // Check broken links and 404 resources (backward compatibility)
574
- console.log(chalk.blue('🔗 Đang kiểm tra link và tài nguyên toàn diện...'));
575
- const linkResults = await fixer.checkBrokenLinks(options.directory);
576
- const resourceResults = await fixer.check404Resources(options.directory);
577
- const totalLinkIssues = linkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
578
- const totalResourceIssues = resourceResults.reduce((sum, r) => sum + (r.issues || 0), 0);
579
-
580
- console.log(chalk.green(`\n✅ Đã kiểm tra link trong ${linkResults.length} file (${totalLinkIssues} vấn đề link)`));
581
- console.log(chalk.green(`✅ Đã kiểm tra tài nguyên trong ${resourceResults.length} file (${totalResourceIssues} vấn đề tài nguyên)`));
582
- console.log(chalk.gray('💡 Vấn đề về link và tài nguyên cần xem xét thủ công và không thể tự động sửa'));
583
-
584
- showCompletionMessage(options, 'Kiểm tra link và tài nguyên');
585
- return;
586
-
587
- } else if (options.brokenLinksOnly) {
588
- // Check broken external links only
589
- console.log(chalk.blue('🔗 Đang kiểm tra link bên ngoài bị lỗi...'));
590
- const linkResults = await fixer.checkBrokenLinks(options.directory);
591
- const totalBrokenLinks = linkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
592
-
593
- console.log(chalk.green(`\n✅ Đã kiểm tra link bên ngoài trong ${linkResults.length} file (${totalBrokenLinks} vấn đề)`));
594
- console.log(chalk.gray('💡 Vấn đề link bị lỗi cần xem xét thủ công và không thể tự động sửa'));
595
-
596
- showCompletionMessage(options, 'Kiểm tra link bị lỗi');
597
- return;
598
-
599
- } else if (options.missingResourcesOnly) {
600
- // Check 404 resources only (missing local files)
601
- console.log(chalk.blue('📁 Đang kiểm tra tài nguyên thiếu...'));
602
- const resourceResults = await fixer.check404Resources(options.directory);
603
- const totalMissingResources = resourceResults.reduce((sum, r) => sum + (r.issues || 0), 0);
604
-
605
- console.log(chalk.green(`\n✅ Đã kiểm tra tài nguyên cục bộ trong ${resourceResults.length} file (${totalMissingResources} vấn đề)`));
606
- console.log(chalk.gray('💡 Vấn đề tài nguyên thiếu cần xem xét thủ công và không thể tự động sửa'));
607
-
608
- showCompletionMessage(options, 'Kiểm tra tài nguyên thiếu');
609
- return;
610
-
611
- } else if (options.gtmCheckOnly) {
612
- // Check Google Tag Manager installation only (no fixes)
613
- console.log(chalk.blue('🏷️ Đang kiểm tra Google Tag Manager...'));
614
- const gtmResults = await fixer.checkGoogleTagManager(options.directory);
615
- const filesWithGTM = gtmResults.filter(r => r.gtmAnalysis?.hasGTM).length;
616
- const filesWithIssues = gtmResults.filter(r => r.gtmAnalysis?.issues?.length > 0).length;
617
-
618
- console.log(chalk.green(`\n✅ Phân tích hoàn tất: Tìm thấy ${filesWithGTM} file có GTM`));
619
- if (filesWithIssues > 0) {
620
- console.log(chalk.yellow(`⚠️ ${filesWithIssues} file có vấn đề về cài đặt GTM`));
621
- }
622
- console.log(chalk.gray('💡 GTM cần có cả <script> trong <head> và <noscript> sau <body>'));
623
-
624
- showCompletionMessage(options, 'Kiểm tra GTM');
625
- return;
626
-
627
- } else if (options.checkMetaOnly) {
628
- // Check meta tags only (no fixes)
629
- console.log(chalk.blue('🏷️ Đang kiểm tra meta tags và Open Graph Protocol...'));
630
- await fixer.checkMetaTags(options.directory);
631
-
632
- showCompletionMessage(options, 'Kiểm tra meta tags');
633
- return;
634
-
635
- } else if (options.fixMetaOnly) {
636
- // Fix meta tags
637
- console.log(chalk.blue('🔧 Đang tự động sửa meta tags...'));
638
- await fixer.fixMetaTags(options.directory, { dryRun: options.dryRun, backup: options.backupFiles });
639
-
640
- showCompletionMessage(options, 'Sửa meta tags');
641
- return;
642
-
643
- } else if (options.unusedFilesOnly) {
644
- // Check unused files only (no fixes, no cleanup)
645
- console.log(chalk.blue('🗂️ Đang kiểm tra file không sử dụng...'));
646
- const unusedResults = await fixer.checkUnusedFiles(options.directory);
647
- const totalUnusedFiles = unusedResults.unusedCount;
648
-
649
- if (totalUnusedFiles === 0) {
650
- console.log(chalk.green(`\n✅ Không tìm thấy file không sử dụng! Tất cả ${unusedResults.totalFiles} file đều được tham chiếu đúng cách.`));
651
- } else {
652
- console.log(chalk.green(`\n✅ Phân tích hoàn tất: Tìm thấy ${totalUnusedFiles} file không sử dụng trong tổng số ${unusedResults.totalFiles} file`));
653
- console.log(chalk.gray(`📊 ${unusedResults.referencedFiles} file được tham chiếu, ${totalUnusedFiles} file có thể không sử dụng`));
654
- }
655
- console.log(chalk.gray('💡 Phát hiện file không sử dụng dựa trên heuristic - khuyến nghị xem xét thủ công'));
656
-
657
- showCompletionMessage(options, 'Kiểm tra file không sử dụng');
658
- return;
659
-
660
- } else if (options.deadCodeOnly) {
661
- // Check dead code only (no fixes, no cleanup)
662
- console.log(chalk.blue('☠️ Đang kiểm tra mã không sử dụng...'));
663
- const deadCodeResults = await fixer.checkDeadCode(options.directory);
664
- const totalDeadCode = deadCodeResults.length;
665
-
666
- console.log(chalk.green(`\n✅ Đã kiểm tra mã không sử dụng (${totalDeadCode} vấn đề tiềm ẩn)`));
667
- console.log(chalk.gray('💡 Phân tích mã không sử dụng dựa trên heuristic - khuyến nghị xem xét thủ công'));
668
-
669
- showCompletionMessage(options, 'Kiểm tra mã không sử dụng');
670
- return;
671
-
672
- } else if (options.fileSizeOnly) {
673
- // Check file sizes only (no fixes, no cleanup)
674
- console.log(chalk.blue('📏 Đang phân tích kích thước file...'));
675
- const sizeResults = await fixer.checkFileSizes(options.directory);
676
- const totalLargeFiles = sizeResults.largeFiles.length;
677
-
678
- console.log(chalk.green(`\n✅ Đã phân tích ${sizeResults.totalFiles} file (${totalLargeFiles} file có kích thước lớn)`));
679
- console.log(chalk.gray('💡 Phân tích kích thước file dựa trên best practices phổ biến'));
680
-
681
- showCompletionMessage(options, 'Phân tích kích thước file');
682
- return;
683
- }
375
+ let result;
376
+ let label;
684
377
 
685
- } catch (error) {
686
- console.error(chalk.red('❌ Đã xảy ra lỗi:'), error.message);
687
- process.exit(1);
378
+ if (options.altOnly) {
379
+ label = 'Alt Text Check';
380
+ result = await checker.checkAltText(options.directory);
381
+ } else if (options.langOnly) {
382
+ label = 'Lang Check';
383
+ result = await checker.checkLang(options.directory);
384
+ } else if (options.roleOnly) {
385
+ label = 'Role Check';
386
+ result = await checker.checkRoles(options.directory);
387
+ } else if (options.ariaLabelOnly) {
388
+ label = 'Aria Label Check';
389
+ result = await checker.checkAriaLabels(options.directory);
390
+ } else if (options.formsOnly) {
391
+ label = 'Form Label Check';
392
+ result = await checker.checkForms(options.directory);
393
+ } else if (options.nestedOnly) {
394
+ label = 'Nested Control Check';
395
+ result = await checker.checkNestedControls(options.directory);
396
+ } else if (options.buttonsOnly) {
397
+ label = 'Button Check';
398
+ result = await checker.checkButtons(options.directory);
399
+ } else if (options.linksOnly) {
400
+ label = 'Accessible Link Name Check';
401
+ result = await checker.checkLinks(options.directory);
402
+ } else if (options.landmarksOnly) {
403
+ label = 'Landmark Check';
404
+ result = await checker.checkLandmarks(options.directory);
405
+ } else if (options.headingsOnly) {
406
+ label = 'Heading Check';
407
+ result = await checker.checkHeadings(options.directory);
408
+ } else if (options.dlOnly) {
409
+ label = 'Description List Check';
410
+ result = await checker.checkDescriptionLists(options.directory);
411
+ } else if (options.linksCheckOnly) {
412
+ label = 'Link and Resource Check';
413
+ result = await checker.checkLinksAndResources(options.directory);
414
+ } else if (options.brokenLinksOnly) {
415
+ label = 'Broken External Link Check';
416
+ result = await checker.checkBrokenLinks(options.directory);
417
+ } else if (options.missingResourcesOnly) {
418
+ label = 'Missing Resource Check';
419
+ result = await checker.check404Resources(options.directory);
420
+ } else if (options.gtmCheckOnly) {
421
+ label = 'GTM Check';
422
+ result = await checker.checkGoogleTagManager(options.directory);
423
+ } else if (options.checkMetaOnly) {
424
+ label = 'Meta Tag Check';
425
+ result = await checker.checkMetaTags(options.directory);
426
+ } else if (options.fullReport) {
427
+ label = 'Full Report';
428
+ result = await checker.generateFullReport(options.directory, options.reportOutput);
429
+ } else if (options.unusedFilesOnly) {
430
+ label = 'Unused File Check';
431
+ result = await checker.checkUnusedFiles(options.directory);
432
+ } else if (options.unusedFilesListOnly) {
433
+ label = 'Unused File List Export';
434
+ result = await checker.generateUnusedFilesList(options.directory, options.listFile);
435
+ } else if (options.deadCodeOnly) {
436
+ label = 'Dead Code Check';
437
+ result = await checker.checkDeadCode(options.directory);
438
+ } else if (options.fileSizeOnly) {
439
+ label = 'File Size Check';
440
+ result = await checker.checkFileSizes(options.directory);
441
+ } else {
442
+ label = 'Comprehensive Check';
443
+ result = await checker.runComprehensiveChecks(options.directory);
688
444
  }
445
+
446
+ printSummary(label, result);
447
+
448
+ console.log(chalk.green('\n✅ Check completed.'));
689
449
  }
690
450
 
691
- // Run the CLI
692
- main();
451
+ run().catch((error) => {
452
+ console.error(chalk.red(`\n❌ ${error.message}`));
453
+ process.exit(1);
454
+ });