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 ,
814 formatAsciiLink ,
15+ formatAsciiTable ,
16+ formatReportScore ,
917 logger ,
1018 shouldExpandForUrls ,
1119 stringifyError ,
@@ -15,8 +23,8 @@ import { DEFAULT_CLI_FLAGS } from './constants.js';
1523import type { LighthouseCliFlags } from './types.js' ;
1624import {
1725 enrichFlags ,
26+ filterAuditOutputs ,
1827 getConfig ,
19- normalizeAuditOutputs ,
2028 toAuditOutputs ,
2129 withLocalTmpDir ,
2230} from './utils.js' ;
@@ -28,64 +36,120 @@ export function createRunnerFunction(
2836 return withLocalTmpDir ( async ( ) : Promise < AuditOutputs > => {
2937 const config = await getConfig ( flags ) ;
3038 const normalizationFlags = enrichFlags ( flags ) ;
31- const isSingleUrl = ! shouldExpandForUrls ( urls . length ) ;
39+ const urlsCount = urls . length ;
40+ const isSingleUrl = ! shouldExpandForUrls ( urlsCount ) ;
3241
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 ) ;
42+ const allResults = await asyncSequential ( urls , ( url , urlIndex ) => {
43+ const enrichedFlags = isSingleUrl
44+ ? normalizationFlags
45+ : enrichFlags ( flags , urlIndex + 1 ) ;
46+ const step = { urlIndex, urlsCount } ;
47+ return runLighthouseForUrl ( url , enrichedFlags , config , step ) ;
48+ } ) ;
3949
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 ) {
50+ const collectedResults = allResults . filter ( res => res != null ) ;
51+ if ( collectedResults . length === 0 ) {
6152 throw new Error (
6253 isSingleUrl
6354 ? 'Lighthouse did not produce a result.'
6455 : 'Lighthouse failed to produce results for all URLs.' ,
6556 ) ;
6657 }
67- return normalizeAuditOutputs ( allResults , normalizationFlags ) ;
58+
59+ logResultsForAllUrls ( collectedResults ) ;
60+
61+ const auditOutputs : AuditOutputs = collectedResults . flatMap (
62+ res => res . auditOutputs ,
63+ ) ;
64+ return filterAuditOutputs ( auditOutputs , normalizationFlags ) ;
6865 } ) ;
6966}
7067
68+ type ResultForUrl = {
69+ url : string ;
70+ lhr : Result ;
71+ auditOutputs : AuditOutputs ;
72+ } ;
73+
7174async function runLighthouseForUrl (
7275 url : string ,
7376 flags : LighthouseOptions ,
7477 config : Config | undefined ,
75- ) : Promise < AuditOutputs > {
76- if ( flags . outputPath ) {
77- await ensureDirectoryExists ( path . dirname ( flags . outputPath ) ) ;
78- }
78+ step : { urlIndex : number ; urlsCount : number } ,
79+ ) : Promise < ResultForUrl | null > {
80+ const { urlIndex, urlsCount } = step ;
7981
80- const runnerResult : unknown = await runLighthouse ( url , flags , config ) ;
82+ const prefix = ansis . gray ( `[ ${ step . urlIndex + 1 } / ${ step . urlsCount } ]` ) ;
8183
82- if ( runnerResult == null ) {
83- throw new Error (
84- `Lighthouse did not produce a result for URL: ${ formatAsciiLink ( url ) } ` ,
84+ try {
85+ if ( flags . outputPath ) {
86+ await ensureDirectoryExists ( path . dirname ( flags . outputPath ) ) ;
87+ }
88+
89+ const lhr : Result = await logger . task (
90+ `${ prefix } Running lighthouse on ${ url } ` ,
91+ async ( ) => {
92+ const runnerResult : RunnerResult | undefined = await runLighthouse (
93+ url ,
94+ flags ,
95+ config ,
96+ ) ;
97+
98+ if ( runnerResult == null ) {
99+ throw new Error (
100+ `Lighthouse did not produce a result for URL: ${ formatAsciiLink ( url ) } ` ,
101+ ) ;
102+ }
103+
104+ return {
105+ message : `${ prefix } Completed lighthouse run on ${ url } ` ,
106+ result : runnerResult . lhr ,
107+ } ;
108+ } ,
85109 ) ;
110+
111+ const auditOutputs = toAuditOutputs ( Object . values ( lhr . audits ) , flags ) ;
112+ if ( shouldExpandForUrls ( urlsCount ) ) {
113+ return {
114+ url,
115+ lhr,
116+ auditOutputs : auditOutputs . map ( audit => ( {
117+ ...audit ,
118+ slug : addIndex ( audit . slug , urlIndex ) ,
119+ } ) ) ,
120+ } ;
121+ }
122+ return { url, lhr, auditOutputs } ;
123+ } catch ( error ) {
124+ logger . warn ( `Lighthouse run failed for ${ url } - ${ stringifyError ( error ) } ` ) ;
125+ return null ;
86126 }
127+ }
87128
88- const { lhr } = runnerResult as RunnerResult ;
129+ function logResultsForAllUrls ( results : ResultForUrl [ ] ) : void {
130+ const categoryNames = Object . fromEntries (
131+ results
132+ . flatMap ( res => Object . values ( res . lhr . categories ) )
133+ . map ( category => [ category . id , category . title ] ) ,
134+ ) ;
89135
90- return toAuditOutputs ( Object . values ( lhr . audits ) , flags ) ;
136+ logger . info (
137+ formatAsciiTable ( {
138+ columns : [
139+ { key : 'url' , label : 'URL' , align : 'left' } ,
140+ ...Object . entries ( categoryNames ) . map (
141+ ( [ key , label ] ) : TableColumnObject => ( { key, label, align : 'right' } ) ,
142+ ) ,
143+ ] ,
144+ rows : results . map ( ( { url, lhr } ) => ( {
145+ url,
146+ ...Object . fromEntries (
147+ Object . values ( lhr . categories ) . map ( category => [
148+ category . id ,
149+ category . score == null ? '-' : formatReportScore ( category . score ) ,
150+ ] ) ,
151+ ) ,
152+ } ) ) ,
153+ } ) ,
154+ ) ;
91155}
0 commit comments