1
1
import chalk from 'chalk' ;
2
- import { readFile } from 'fs/promises' ;
3
2
import { join } from 'path' ;
4
3
import {
4
+ Audit ,
5
+ AuditOutput ,
6
+ AuditOutputs ,
7
+ AuditReport ,
5
8
PluginConfig ,
6
9
PluginReport ,
7
10
auditOutputsSchema ,
@@ -10,20 +13,15 @@ import {
10
13
ProcessObserver ,
11
14
executeProcess ,
12
15
getProgressBar ,
16
+ readJsonFile ,
13
17
} from '@code-pushup/utils' ;
14
18
15
19
/**
16
20
* Error thrown when plugin output is invalid.
17
21
*/
18
- export class PluginOutputError extends Error {
19
- constructor ( pluginSlug : string , error ?: Error ) {
20
- super (
21
- `Plugin output of plugin with slug ${ pluginSlug } is invalid. \n Error: ${ error ?. message } ` ,
22
- ) ;
23
- if ( error ) {
24
- this . name = error . name ;
25
- this . stack = error . stack ;
26
- }
22
+ export class PluginOutputMissingAuditError extends Error {
23
+ constructor ( auditSlug : string ) {
24
+ super ( `Audit metadata not found for slug ${ auditSlug } ` ) ;
27
25
}
28
26
}
29
27
@@ -34,7 +32,7 @@ export class PluginOutputError extends Error {
34
32
* @param pluginConfig - {@link ProcessConfig} object with runner and meta
35
33
* @param observer - process {@link ProcessObserver}
36
34
* @returns {Promise<AuditOutput[]> } - audit outputs from plugin runner
37
- * @throws {PluginOutputError } - if plugin runner output is invalid
35
+ * @throws {PluginOutputMissingAuditError } - if plugin runner output is invalid
38
36
*
39
37
* @example
40
38
* // plugin execution
@@ -54,70 +52,61 @@ export async function executePlugin(
54
52
observer ?: ProcessObserver ,
55
53
) : Promise < PluginReport > {
56
54
const {
57
- slug ,
58
- title ,
59
- icon ,
55
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
56
+ runner : onlyUsedForRestingPluginMeta ,
57
+ audits : pluginConfigAudits ,
60
58
description,
61
59
docsUrl,
62
- version,
63
- packageName,
64
60
groups,
61
+ ...pluginMeta
65
62
} = pluginConfig ;
66
63
const { args, command } = pluginConfig . runner ;
67
64
68
- const { duration , date } = await executeProcess ( {
65
+ const { date , duration } = await executeProcess ( {
69
66
command,
70
67
args,
71
68
observer,
72
69
} ) ;
70
+ const executionMeta = { date, duration } ;
73
71
74
- try {
75
- const processOutputPath = join (
76
- process . cwd ( ) ,
77
- pluginConfig . runner . outputFile ,
78
- ) ;
72
+ const processOutputPath = join ( process . cwd ( ) , pluginConfig . runner . outputFile ) ;
73
+
74
+ // read process output from file system and parse it
75
+ let unknownAuditOutputs = await readJsonFile < Record < string , unknown > [ ] > (
76
+ processOutputPath ,
77
+ ) ;
79
78
80
- // read process output from file system and parse it
81
- const auditOutputs = auditOutputsSchema . parse (
82
- JSON . parse ( ( await readFile ( processOutputPath ) ) . toString ( ) ) ,
79
+ // parse transform unknownAuditOutputs to auditOutputs
80
+ if ( pluginConfig . runner ?. outputTransform ) {
81
+ unknownAuditOutputs = await pluginConfig . runner . outputTransform (
82
+ unknownAuditOutputs ,
83
83
) ;
84
+ }
85
+
86
+ // validate audit outputs
87
+ const auditOutputs = auditOutputsSchema . parse ( unknownAuditOutputs ) ;
88
+
89
+ // validate auditOutputs
90
+ auditOutputsCorrelateWithPluginOutput ( auditOutputs , pluginConfigAudits ) ;
84
91
85
- const audits = auditOutputs . map ( auditOutput => {
86
- const auditMetadata = pluginConfig . audits . find (
92
+ // enrich `AuditOutputs` to `AuditReport`
93
+ const audits : AuditReport [ ] = auditOutputs . map (
94
+ ( auditOutput : AuditOutput ) => ( {
95
+ ...auditOutput ,
96
+ ...( pluginConfigAudits . find (
87
97
audit => audit . slug === auditOutput . slug ,
88
- ) ;
89
- if ( ! auditMetadata ) {
90
- throw new PluginOutputError (
91
- slug ,
92
- new Error (
93
- `Audit metadata not found for slug ${ auditOutput . slug } from runner output` ,
94
- ) ,
95
- ) ;
96
- }
97
- return {
98
- ...auditOutput ,
99
- ...auditMetadata ,
100
- } ;
101
- } ) ;
102
-
103
- // @TODO consider just resting/spreading the values
104
- return {
105
- version,
106
- packageName,
107
- slug,
108
- title,
109
- icon,
110
- date,
111
- duration,
112
- audits,
113
- ...( description && { description } ) ,
114
- ...( docsUrl && { docsUrl } ) ,
115
- ...( groups && { groups } ) ,
116
- } satisfies PluginReport ;
117
- } catch ( error ) {
118
- const e = error as Error ;
119
- throw new PluginOutputError ( slug , e ) ;
120
- }
98
+ ) as Audit ) ,
99
+ } ) ,
100
+ ) ;
101
+
102
+ return {
103
+ ...pluginMeta ,
104
+ ...executionMeta ,
105
+ audits,
106
+ ...( description && { description } ) ,
107
+ ...( docsUrl && { docsUrl } ) ,
108
+ ...( groups && { groups } ) ,
109
+ } satisfies PluginReport ;
121
110
}
122
111
123
112
/**
@@ -165,3 +154,17 @@ export async function executePlugins(
165
154
166
155
return pluginsResult ;
167
156
}
157
+
158
+ function auditOutputsCorrelateWithPluginOutput (
159
+ auditOutputs : AuditOutputs ,
160
+ pluginConfigAudits : PluginConfig [ 'audits' ] ,
161
+ ) {
162
+ auditOutputs . forEach ( auditOutput => {
163
+ const auditMetadata = pluginConfigAudits . find (
164
+ audit => audit . slug === auditOutput . slug ,
165
+ ) ;
166
+ if ( ! auditMetadata ) {
167
+ throw new PluginOutputMissingAuditError ( auditOutput . slug ) ;
168
+ }
169
+ } ) ;
170
+ }
0 commit comments