1- import type { Config , RunnerResult } from 'lighthouse' ;
1+ import ansis from 'ansis' ;
2+ import type { Config , Result , RunnerResult } from 'lighthouse' ;
23import { runLighthouse } from 'lighthouse/cli/run.js' ;
34import path from 'node:path' ;
4- import type { AuditOutputs , RunnerFunction } from '@code-pushup/models' ;
5+ import type {
6+ AuditOutputs ,
7+ RunnerFunction ,
8+ TableColumnObject ,
9+ } from '@code-pushup/models' ;
510import {
611 addIndex ,
12+ asyncSequential ,
713 ensureDirectoryExists ,
8- formatAsciiLink ,
14+ formatAsciiTable ,
15+ formatReportScore ,
916 logger ,
1017 shouldExpandForUrls ,
1118 stringifyError ,
@@ -15,8 +22,8 @@ import { DEFAULT_CLI_FLAGS } from './constants.js';
1522import type { LighthouseCliFlags } from './types.js' ;
1623import {
1724 enrichFlags ,
25+ filterAuditOutputs ,
1826 getConfig ,
19- normalizeAuditOutputs ,
2027 toAuditOutputs ,
2128 withLocalTmpDir ,
2229} from './utils.js' ;
@@ -28,64 +35,118 @@ export function createRunnerFunction(
2835 return withLocalTmpDir ( async ( ) : Promise < AuditOutputs > => {
2936 const config = await getConfig ( flags ) ;
3037 const normalizationFlags = enrichFlags ( flags ) ;
31- const isSingleUrl = ! shouldExpandForUrls ( urls . length ) ;
38+ const urlsCount = urls . length ;
39+ const isSingleUrl = ! shouldExpandForUrls ( urlsCount ) ;
3240
33- const allResults = await urls . reduce ( async ( prev , url , index ) => {
34- const acc = await prev ;
35- try {
36- const enrichedFlags = isSingleUrl
37- ? normalizationFlags
38- : enrichFlags ( flags , index + 1 ) ;
41+ const allResults = await asyncSequential ( urls , ( url , urlIndex ) => {
42+ const enrichedFlags = isSingleUrl
43+ ? normalizationFlags
44+ : enrichFlags ( flags , urlIndex + 1 ) ;
45+ const step = { urlIndex, urlsCount } ;
46+ return runLighthouseForUrl ( url , enrichedFlags , config , step ) ;
47+ } ) ;
3948
40- const auditOutputs = await runLighthouseForUrl (
41- url ,
42- enrichedFlags ,
43- config ,
44- ) ;
45-
46- const processedOutputs = isSingleUrl
47- ? auditOutputs
48- : auditOutputs . map ( audit => ( {
49- ...audit ,
50- slug : addIndex ( audit . slug , index ) ,
51- } ) ) ;
52-
53- return [ ...acc , ...processedOutputs ] ;
54- } catch ( error ) {
55- logger . warn ( stringifyError ( error ) ) ;
56- return acc ;
57- }
58- } , Promise . resolve < AuditOutputs > ( [ ] ) ) ;
59-
60- if ( allResults . length === 0 ) {
49+ const collectedResults = allResults . filter ( res => res != null ) ;
50+ if ( collectedResults . length === 0 ) {
6151 throw new Error (
6252 isSingleUrl
6353 ? 'Lighthouse did not produce a result.'
6454 : 'Lighthouse failed to produce results for all URLs.' ,
6555 ) ;
6656 }
67- return normalizeAuditOutputs ( allResults , normalizationFlags ) ;
57+
58+ logResultsForAllUrls ( collectedResults ) ;
59+
60+ const auditOutputs : AuditOutputs = collectedResults . flatMap (
61+ res => res . auditOutputs ,
62+ ) ;
63+ return filterAuditOutputs ( auditOutputs , normalizationFlags ) ;
6864 } ) ;
6965}
7066
67+ type ResultForUrl = {
68+ url : string ;
69+ lhr : Result ;
70+ auditOutputs : AuditOutputs ;
71+ } ;
72+
7173async function runLighthouseForUrl (
7274 url : string ,
7375 flags : LighthouseOptions ,
7476 config : Config | undefined ,
75- ) : Promise < AuditOutputs > {
76- if ( flags . outputPath ) {
77- await ensureDirectoryExists ( path . dirname ( flags . outputPath ) ) ;
78- }
77+ step : { urlIndex : number ; urlsCount : number } ,
78+ ) : Promise < ResultForUrl | null > {
79+ const { urlIndex, urlsCount } = step ;
7980
80- const runnerResult : unknown = await runLighthouse ( url , flags , config ) ;
81+ const prefix = ansis . gray ( `[ ${ step . urlIndex + 1 } / ${ step . urlsCount } ]` ) ;
8182
82- if ( runnerResult == null ) {
83- throw new Error (
84- `Lighthouse did not produce a result for URL: ${ formatAsciiLink ( url ) } ` ,
83+ try {
84+ if ( flags . outputPath ) {
85+ await ensureDirectoryExists ( path . dirname ( flags . outputPath ) ) ;
86+ }
87+
88+ const lhr : Result = await logger . task (
89+ `${ prefix } Running lighthouse on ${ url } ` ,
90+ async ( ) => {
91+ const runnerResult : RunnerResult | undefined = await runLighthouse (
92+ url ,
93+ flags ,
94+ config ,
95+ ) ;
96+
97+ if ( runnerResult == null ) {
98+ throw new Error ( 'Lighthouse did not produce a result' ) ;
99+ }
100+
101+ return {
102+ message : `${ prefix } Completed lighthouse run on ${ url } ` ,
103+ result : runnerResult . lhr ,
104+ } ;
105+ } ,
85106 ) ;
107+
108+ const auditOutputs = toAuditOutputs ( Object . values ( lhr . audits ) , flags ) ;
109+ if ( shouldExpandForUrls ( urlsCount ) ) {
110+ return {
111+ url,
112+ lhr,
113+ auditOutputs : auditOutputs . map ( audit => ( {
114+ ...audit ,
115+ slug : addIndex ( audit . slug , urlIndex ) ,
116+ } ) ) ,
117+ } ;
118+ }
119+ return { url, lhr, auditOutputs } ;
120+ } catch ( error ) {
121+ logger . warn ( `Lighthouse run failed for ${ url } - ${ stringifyError ( error ) } ` ) ;
122+ return null ;
86123 }
124+ }
87125
88- const { lhr } = runnerResult as RunnerResult ;
126+ function logResultsForAllUrls ( results : ResultForUrl [ ] ) : void {
127+ const categoryNames = Object . fromEntries (
128+ results
129+ . flatMap ( res => Object . values ( res . lhr . categories ) )
130+ . map ( category => [ category . id , category . title ] ) ,
131+ ) ;
89132
90- return toAuditOutputs ( Object . values ( lhr . audits ) , flags ) ;
133+ logger . info (
134+ formatAsciiTable ( {
135+ columns : [
136+ { key : 'url' , label : 'URL' , align : 'left' } ,
137+ ...Object . entries ( categoryNames ) . map (
138+ ( [ key , label ] ) : TableColumnObject => ( { key, label, align : 'right' } ) ,
139+ ) ,
140+ ] ,
141+ rows : results . map ( ( { url, lhr } ) => ( {
142+ url,
143+ ...Object . fromEntries (
144+ Object . values ( lhr . categories ) . map ( category => [
145+ category . id ,
146+ category . score == null ? '-' : formatReportScore ( category . score ) ,
147+ ] ) ,
148+ ) ,
149+ } ) ) ,
150+ } ) ,
151+ ) ;
91152}
0 commit comments