11'use strict' ;
22
3+ const _ = require ( 'lodash' ) ;
34const Nv = require ( '@pkgjs/nv' ) ;
45
5- exports . detect = async ( meta ) => {
66
7- const files = await meta . loadFolder ( '.github/workflows' ) ;
8- const rawSet = new Set ( ) ;
7+ const internals = { } ;
98
10- if ( ! files . length ) {
11- return ;
12- }
139
14- for ( const file of files ) {
10+ internals . parseActionsSetupNode = function * ( workflow , file ) {
1511
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 ;
1842 }
43+ }
44+ } ;
1945
20- const workflow = await meta . loadFile ( `.github/workflows/${ file } ` , { yaml : true } ) ;
2146
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 ) {
2370
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' ) {
2772
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 ;
3076 continue ;
3177 }
3278
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 + } } $ / ) ;
3685
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 } ` ) ;
3988 }
4089
41- continue ;
42- }
90+ const { needStepName /*, needStepOutputName*/ } = stepMatch . groups ;
91+ const needStep = needJob . steps . find ( ( { id } ) => id === needStepName ) ;
4392
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+ }
4796
97+ // @todo : with has more options - resolve to precise versions here and yield the full list
98+ yield needStep . with . preset ;
4899 continue ;
49100 }
50101
51- rawSet . add ( nodeVersion ) ;
102+ throw new Error ( `Unable to parse the job matrix: ${ job . strategy . matrix } in ${ file } ` ) ;
52103 }
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 ) ;
53139 }
54140 }
55141
56142 const raw = [ ...rawSet ] ;
143+ const byFile = _ . mapValues ( byFileSets , ( set ) => [ ...set ] ) ;
57144
58145 const resolved = { } ;
59146
@@ -69,5 +156,5 @@ exports.detect = async (meta) => {
69156 }
70157 }
71158
72- return { githubActions : { raw, resolved } } ;
159+ return { githubActions : { byFile , raw, resolved } } ;
73160} ;
0 commit comments