@@ -3,6 +3,8 @@ import { resolve, join } from 'path';
33import { intro , outro , log , spinner } from '@clack/prompts' ;
44import pc from 'picocolors' ;
55import { validateConfig , isPortableConfig , getCoreConfigKeys , getExtensionKeys } from '../core/config-validator.js' ;
6+ import { lintContent } from '../core/content-linter.js' ;
7+ import { checkLinks } from '../core/link-checker.js' ;
68
79/**
810 * Validate command - Validate docs-config.json
@@ -12,26 +14,49 @@ export async function validateCommand(options) {
1214 const inputPath = options . input ? resolve ( options . input ) : process . cwd ( ) ;
1315 const configPath = join ( inputPath , 'docs-config.json' ) ;
1416
17+ const runContent = options . content || options . all ;
18+ const runLinks = options . links || options . all ;
19+ const strict = options . strict || false ;
20+
1521 // Quick mode for CI - just exit with code
1622 if ( options . quiet ) {
23+ let hasErrors = false ;
24+
25+ // Config validation
1726 if ( ! existsSync ( configPath ) ) {
1827 process . exit ( 1 ) ;
1928 }
2029 try {
2130 const config = JSON . parse ( readFileSync ( configPath , 'utf-8' ) ) ;
2231 const result = validateConfig ( config , inputPath , { silent : true } ) ;
23- process . exit ( result . valid ? 0 : 1 ) ;
32+ if ( ! result . valid ) hasErrors = true ;
33+
34+ // Content linting
35+ if ( runContent ) {
36+ const lint = await lintContent ( inputPath , { config } ) ;
37+ const hasLintErrors = lint . issues . some ( i => i . severity === 'error' ) ;
38+ const hasLintWarnings = lint . issues . some ( i => i . severity === 'warning' ) ;
39+ if ( hasLintErrors || ( strict && hasLintWarnings ) ) hasErrors = true ;
40+ }
41+
42+ // Link checking
43+ if ( runLinks ) {
44+ const linkResult = await checkLinks ( inputPath ) ;
45+ if ( linkResult . brokenLinks . length > 0 ) hasErrors = true ;
46+ }
2447 } catch ( e ) {
25- process . exit ( 1 ) ;
48+ hasErrors = true ;
2649 }
50+
51+ process . exit ( hasErrors ? 1 : 0 ) ;
2752 }
2853
2954 console . clear ( ) ;
3055 intro ( pc . inverse ( pc . cyan ( ' Lito - Validate Configuration ' ) ) ) ;
3156
3257 const s = spinner ( ) ;
3358
34- // Check if config file exists
59+ // ── Config validation ──
3560 s . start ( 'Looking for docs-config.json...' ) ;
3661
3762 if ( ! existsSync ( configPath ) ) {
@@ -113,7 +138,89 @@ export async function validateCommand(options) {
113138 }
114139
115140 log . message ( '' ) ;
116- outro ( pc . green ( 'Validation complete!' ) ) ;
141+
142+ // Track if any checks failed
143+ let hasFailure = false ;
144+
145+ // ── Content linting ──
146+ if ( runContent ) {
147+ log . message ( pc . dim ( '─' . repeat ( 50 ) ) ) ;
148+ log . message ( '' ) ;
149+ s . start ( 'Linting documentation content...' ) ;
150+
151+ const lint = await lintContent ( inputPath , { config } ) ;
152+
153+ const errors = lint . issues . filter ( i => i . severity === 'error' ) ;
154+ const warnings = lint . issues . filter ( i => i . severity === 'warning' ) ;
155+
156+ if ( lint . issues . length === 0 ) {
157+ s . stop ( pc . green ( `Content is clean (${ lint . totalFiles } files checked)` ) ) ;
158+ } else {
159+ s . stop ( pc . yellow ( `Found ${ lint . issues . length } issue(s) in ${ lint . totalFiles } files` ) ) ;
160+ log . message ( '' ) ;
161+
162+ if ( errors . length > 0 ) {
163+ log . error ( pc . bold ( `Errors (${ errors . length } ):` ) ) ;
164+ for ( const issue of errors ) {
165+ log . error ( ` ${ pc . red ( '✗' ) } ${ pc . cyan ( issue . file ) } : ${ issue . message } ${ pc . dim ( `[${ issue . rule } ]` ) } ` ) ;
166+ }
167+ log . message ( '' ) ;
168+ hasFailure = true ;
169+ }
170+
171+ if ( warnings . length > 0 ) {
172+ log . warn ( pc . bold ( `Warnings (${ warnings . length } ):` ) ) ;
173+ for ( const issue of warnings ) {
174+ log . warn ( ` ${ pc . yellow ( '!' ) } ${ pc . cyan ( issue . file ) } : ${ issue . message } ${ pc . dim ( `[${ issue . rule } ]` ) } ` ) ;
175+ }
176+ log . message ( '' ) ;
177+ if ( strict ) hasFailure = true ;
178+ }
179+ }
180+ }
181+
182+ // ── Link checking ──
183+ if ( runLinks ) {
184+ log . message ( pc . dim ( '─' . repeat ( 50 ) ) ) ;
185+ log . message ( '' ) ;
186+ s . start ( 'Checking for broken links...' ) ;
187+
188+ const linkResult = await checkLinks ( inputPath ) ;
189+
190+ if ( linkResult . brokenLinks . length === 0 ) {
191+ s . stop ( pc . green ( `All ${ linkResult . totalLinks } links are valid (${ linkResult . checkedFiles } files)` ) ) ;
192+ } else {
193+ s . stop ( pc . yellow ( `Found ${ linkResult . brokenLinks . length } broken link(s)` ) ) ;
194+ log . message ( '' ) ;
195+
196+ // Group by file
197+ const byFile = new Map ( ) ;
198+ for ( const bl of linkResult . brokenLinks ) {
199+ if ( ! byFile . has ( bl . file ) ) byFile . set ( bl . file , [ ] ) ;
200+ byFile . get ( bl . file ) . push ( bl ) ;
201+ }
202+
203+ for ( const [ file , links ] of byFile ) {
204+ log . message ( ` ${ pc . bold ( pc . cyan ( file ) ) } ` ) ;
205+ for ( const bl of links ) {
206+ const label = bl . text ? ` (${ pc . dim ( bl . text ) } )` : '' ;
207+ log . message ( ` ${ pc . red ( '✗' ) } ${ bl . link } ${ label } ` ) ;
208+ if ( bl . suggestion ) {
209+ log . message ( ` ${ pc . dim ( 'Did you mean:' ) } ${ pc . green ( bl . suggestion ) } ` ) ;
210+ }
211+ }
212+ }
213+ log . message ( '' ) ;
214+ hasFailure = true ;
215+ }
216+ }
217+
218+ if ( hasFailure ) {
219+ outro ( pc . red ( 'Validation complete with errors' ) ) ;
220+ process . exit ( 1 ) ;
221+ } else {
222+ outro ( pc . green ( 'Validation complete!' ) ) ;
223+ }
117224 } catch ( error ) {
118225 log . error ( pc . red ( error . message ) ) ;
119226 if ( error . stack && ! options . quiet ) {
0 commit comments