1
1
'use strict' ;
2
2
3
+ const _ = require ( 'lodash' ) ;
3
4
const Nv = require ( '@pkgjs/nv' ) ;
4
5
5
- exports . detect = async ( meta ) => {
6
6
7
- const files = await meta . loadFolder ( '.github/workflows' ) ;
8
- const rawSet = new Set ( ) ;
7
+ const internals = { } ;
9
8
10
- if ( ! files . length ) {
11
- return ;
12
- }
13
9
14
- for ( const file of files ) {
10
+ internals . parseActionsSetupNode = function * ( workflow , file ) {
15
11
16
- if ( ! file . endsWith ( '.yaml' ) && ! file . endsWith ( '.yml' ) ) {
17
- continue ;
12
+ for ( const job of Object . values ( workflow . jobs ) ) {
13
+
14
+ const nodeSteps = job . steps . filter ( ( { uses } ) => uses && uses . startsWith ( 'actions/setup-node' ) ) ;
15
+ for ( const step of nodeSteps ) {
16
+ const nodeVersion = step . with && step . with [ 'node-version' ] ;
17
+
18
+ if ( ! nodeVersion ) {
19
+ // Docs say: "The node-version input is optional. If not supplied, the node version that is PATH will be used."
20
+ // Therefore we cannot reliably detect a specific version, but we do want to let the user know
21
+ yield 'not-set' ;
22
+ continue ;
23
+ }
24
+
25
+ const matrixMatch = nodeVersion . match ( / ^ \$ { { \s + m a t r i x .(?< matrixVarName > .* ) \s + } } $ / ) ;
26
+ if ( matrixMatch ) {
27
+ const matrix = job . strategy . matrix [ matrixMatch . groups . matrixVarName ] ;
28
+
29
+ yield * matrix ;
30
+ continue ;
31
+ }
32
+
33
+ const envMatch = nodeVersion . match ( / ^ \$ { { \s + e n v .(?< envVarName > .* ) \s + } } $ / ) ;
34
+ if ( envMatch ) {
35
+ const envValue = workflow . env [ envMatch . groups . envVarName ] ;
36
+
37
+ yield envValue ;
38
+ continue ;
39
+ }
40
+
41
+ yield nodeVersion ;
18
42
}
43
+ }
44
+ } ;
19
45
20
- const workflow = await meta . loadFile ( `.github/workflows/${ file } ` , { yaml : true } ) ;
21
46
22
- for ( const job of Object . values ( workflow . jobs ) ) {
47
+ internals . parseLjharbActions = function * ( workflow , file ) {
48
+
49
+ for ( const job of Object . values ( workflow . jobs ) ) {
50
+
51
+ const nodeSteps = job . steps . filter ( ( { uses } ) => {
52
+
53
+ if ( ! uses ) {
54
+ return false ;
55
+ }
56
+
57
+ return uses . startsWith ( 'ljharb/actions/node/run' ) || uses . startsWith ( 'ljharb/actions/node/install' ) ;
58
+ } ) ;
59
+
60
+ for ( const step of nodeSteps ) {
61
+ const nodeVersion = step . with && step . with [ 'node-version' ] ;
62
+
63
+ if ( ! nodeVersion ) {
64
+ yield 'lts/*' ; // @todo : find ref which tells us that this is so
65
+ continue ;
66
+ }
67
+
68
+ const matrixMatch = nodeVersion . match ( / ^ \$ { { \s + m a t r i x .(?< matrixVarName > .* ) \s + } } $ / ) ;
69
+ if ( matrixMatch ) {
23
70
24
- const nodeSteps = job . steps . filter ( ( { uses } ) => uses && uses . startsWith ( 'actions/setup-node' ) ) ;
25
- for ( const step of nodeSteps ) {
26
- const nodeVersion = step . with && step . with [ 'node-version' ] ;
71
+ if ( typeof job . strategy . matrix !== 'string' ) {
27
72
28
- if ( ! nodeVersion ) {
29
- // @todo - no node version defined - use default? what is the default?
73
+ const matrix = job . strategy . matrix [ matrixMatch . groups . matrixVarName ] ;
74
+
75
+ yield * matrix ;
30
76
continue ;
31
77
}
32
78
33
- const matrixMatch = nodeVersion . match ( / ^ \$ { { \s + m a t r i x .(?< matrixVarName > .* ) \s + } } $ / ) ;
34
- if ( matrixMatch ) {
35
- const matrix = job . strategy . matrix [ matrixMatch . groups . matrixVarName ] ;
79
+ const fromJsonMatch = job . strategy . matrix . match ( / ^ \$ { { \s + f r o m J s o n \( n e e d s \. (?< needJobName > .* ) \. o u t p u t s \. (?< needOutputName > .* ) \) \s + } } $ / ) ;
80
+ if ( fromJsonMatch ) {
81
+ const { needJobName, needOutputName } = fromJsonMatch . groups ;
82
+ const needJob = workflow . jobs [ needJobName ] ;
83
+ const needOutput = needJob . outputs [ needOutputName ] ;
84
+ const stepMatch = needOutput . match ( / ^ \$ { { \s + s t e p s \. (?< needStepName > .* ) \. o u t p u t s \. (?< needStepOutputName > .* ) \s + } } $ / ) ;
36
85
37
- for ( const version of matrix ) {
38
- rawSet . add ( version ) ;
86
+ if ( ! stepMatch ) {
87
+ throw new Error ( `Unable to parse need output: ${ needOutput } in ${ file } ` ) ;
39
88
}
40
89
41
- continue ;
42
- }
90
+ const { needStepName /*, needStepOutputName*/ } = stepMatch . groups ;
91
+ const needStep = needJob . steps . find ( ( { id } ) => id === needStepName ) ;
43
92
44
- const envMatch = nodeVersion . match ( / ^ \$ { { \s + e n v . (?< envVarName > . * ) \s + } } $ / ) ;
45
- if ( envMatch ) {
46
- rawSet . add ( workflow . env [ envMatch . groups . envVarName ] ) ;
93
+ if ( ! needStep || ! needStep . uses . startsWith ( 'ljharb/actions/node/matrix' ) ) {
94
+ throw new Error ( `Unrecognized action in ${ needOutput } in ${ file } ` ) ;
95
+ }
47
96
97
+ // @todo : with has more options - resolve to precise versions here and yield the full list
98
+ yield needStep . with . preset ;
48
99
continue ;
49
100
}
50
101
51
- rawSet . add ( nodeVersion ) ;
102
+ throw new Error ( `Unable to parse the job matrix: ${ job . strategy . matrix } in ${ file } ` ) ;
52
103
}
104
+
105
+ yield nodeVersion ;
106
+ }
107
+ }
108
+ } ;
109
+
110
+
111
+ exports . detect = async ( meta ) => {
112
+
113
+ const files = await meta . loadFolder ( '.github/workflows' ) ;
114
+ const rawSet = new Set ( ) ;
115
+ const byFileSets = { } ;
116
+
117
+ if ( ! files . length ) {
118
+ // explicitly return no `githubActions` - this is different to finding actions and detecting no Node.js versions
119
+ return ;
120
+ }
121
+
122
+ for ( const file of files ) {
123
+
124
+ if ( ! file . endsWith ( '.yaml' ) && ! file . endsWith ( '.yml' ) ) {
125
+ continue ;
126
+ }
127
+
128
+ const workflow = await meta . loadFile ( `.github/workflows/${ file } ` , { yaml : true } ) ;
129
+ byFileSets [ file ] = byFileSets [ file ] || new Set ( ) ;
130
+
131
+ for ( const version of internals . parseActionsSetupNode ( workflow , file ) ) {
132
+ rawSet . add ( version ) ;
133
+ byFileSets [ file ] . add ( version ) ;
134
+ }
135
+
136
+ for ( const version of internals . parseLjharbActions ( workflow , file ) ) {
137
+ rawSet . add ( version ) ;
138
+ byFileSets [ file ] . add ( version ) ;
53
139
}
54
140
}
55
141
56
142
const raw = [ ...rawSet ] ;
143
+ const byFile = _ . mapValues ( byFileSets , ( set ) => [ ...set ] ) ;
57
144
58
145
const resolved = { } ;
59
146
@@ -69,5 +156,5 @@ exports.detect = async (meta) => {
69
156
}
70
157
}
71
158
72
- return { githubActions : { raw, resolved } } ;
159
+ return { githubActions : { byFile , raw, resolved } } ;
73
160
} ;
0 commit comments