@@ -11,42 +11,93 @@ async function run() {
1111 try {
1212 const allowlist = core . getInput ( 'allowlist' ) ;
1313 const isDryRun = core . getInput ( 'dry_run' ) === 'true' ;
14+ let hasError = false ;
15+
1416 const workflowsPath = process . env [ 'ZG_WORKFLOWS_PATH' ] || '.github/workflows' ;
15- const globber = await glob . create ( [ workflowsPath + '/*.yaml' , workflowsPath + '/*.yml' ] . join ( '\n' ) ) ;
16- let actionHasError = false ;
17+ const workflowsGlobber = await glob . create ( [
18+ workflowsPath + '/*.yaml' ,
19+ workflowsPath + '/*.yml'
20+ ] . join ( '\n' ) ) ;
1721
18- for await ( const file of globber . globGenerator ( ) ) {
22+ for await ( const file of workflowsGlobber . globGenerator ( ) ) {
1923 const basename = path . basename ( file ) ;
2024 const fileContents = fs . readFileSync ( file , 'utf8' ) ;
2125 const yamlContents = yaml . parse ( fileContents ) ;
22- const jobs = yamlContents [ 'jobs' ] ;
2326 let fileHasError = false ;
2427
25- if ( jobs === undefined ) {
28+ let jobs = getYamlAttribute ( yamlContents , 'jobs' ) ;
29+ if ( jobs === undefined || jobs === null ) {
2630 core . setFailed ( `The "${ basename } " workflow does not contain jobs.` ) ;
31+ break ;
2732 }
2833
2934 core . startGroup ( workflowsPath + '/' + basename ) ;
3035
3136 for ( const job in jobs ) {
32- const uses = jobs [ job ] [ 'uses' ] ;
33- const steps = jobs [ job ] [ 'steps' ] ;
37+ jobObject = jobs [ job ] ;
3438 let jobHasError = false ;
35-
36- if ( uses !== undefined ) {
37- jobHasError = runAssertions ( uses , allowlist , isDryRun ) ;
38- } else if ( steps !== undefined ) {
39- for ( const step of steps ) {
40- if ( ! jobHasError ) {
41- jobHasError = runAssertions ( step [ 'uses' ] , allowlist , isDryRun ) ;
39+ if ( jobObject === undefined || jobObject === null ) {
40+ core . warning ( `The "${ job } " job of the "${ basename } " workflow is undefined.` ) ;
41+ jobHasError = true ;
42+ } else {
43+ const uses = getYamlAttribute ( jobObject , "uses" ) ;
44+ const steps = getYamlAttribute ( jobObject , "steps" ) ;
45+ if ( uses !== undefined && uses !== null ) {
46+ jobHasError = runAssertions ( uses , allowlist , isDryRun ) ;
47+ } else if ( steps !== undefined && steps !== null ) {
48+ for ( const step of steps ) {
49+ if ( ! jobHasError ) {
50+ jobHasError = runAssertions ( step [ 'uses' ] , allowlist , isDryRun ) ;
51+ }
4252 }
53+ } else {
54+ core . warning ( `The "${ job } " job of the "${ basename } " workflow does not contain uses or steps.` ) ;
4355 }
44- } else {
45- core . warning ( `The "${ job } " job of the "${ basename } " workflow does not contain uses or steps.` ) ;
4656 }
4757
4858 if ( jobHasError ) {
49- actionHasError = true ;
59+ hasError = true ;
60+ fileHasError = true ;
61+ }
62+ }
63+
64+ if ( ! fileHasError ) {
65+ core . info ( 'No issues were found.' ) ;
66+ }
67+
68+ core . endGroup ( ) ;
69+ }
70+
71+ const actionsPath = process . env [ 'ZG_ACTIONS_PATH' ] || '.github/actions' ;
72+ const actionsGlobber = await glob . create ( [
73+ actionsPath + '/*/action.yaml' ,
74+ actionsPath + '/*/action.yml'
75+ ] . join ( '\n' ) ) ;
76+
77+ for await ( const file of actionsGlobber . globGenerator ( ) ) {
78+ const basename = path . basename ( path . dirname ( file ) ) ;
79+ const fileContents = fs . readFileSync ( file , 'utf8' ) ;
80+ const yamlContents = yaml . parse ( fileContents ) ;
81+ let fileHasError = false ;
82+
83+ let runs = getYamlAttribute ( yamlContents , 'runs' ) ;
84+ if ( runs === undefined || runs === null ) {
85+ core . setFailed ( `The "${ basename } " action does not contain runs.` ) ;
86+ break ;
87+ }
88+
89+ core . startGroup ( actionsPath + '/' + basename ) ;
90+
91+ let runHasError = false ;
92+ const steps = getYamlAttribute ( runs , 'steps' ) ;
93+ if ( steps !== undefined && steps !== null ) {
94+ for ( const step of steps ) {
95+ if ( ! runHasError ) {
96+ runHasError = runAssertions ( step [ 'uses' ] , allowlist , isDryRun ) ;
97+ }
98+ }
99+ if ( runHasError ) {
100+ hasError = true ;
50101 fileHasError = true ;
51102 }
52103 }
@@ -58,8 +109,8 @@ async function run() {
58109 core . endGroup ( ) ;
59110 }
60111
61- if ( ! isDryRun && actionHasError ) {
62- throw new Error ( 'At least one workflow contains an unpinned GitHub Action version.' ) ;
112+ if ( ! isDryRun && hasError ) {
113+ throw new Error ( 'At least one workflow or composite action contains an unpinned GitHub Action version.' ) ;
63114 }
64115 } catch ( error ) {
65116 core . setFailed ( error . message ) ;
@@ -68,6 +119,13 @@ async function run() {
68119
69120run ( ) ;
70121
122+ function getYamlAttribute ( yamlContents , attribute ) {
123+ if ( yamlContents && typeof yamlContents === 'object' && Object . prototype . hasOwnProperty . call ( yamlContents , attribute ) ) {
124+ return yamlContents [ attribute ] ;
125+ }
126+ return undefined ;
127+ }
128+
71129function assertUsesVersion ( uses ) {
72130 return typeof uses === 'string' && uses . includes ( '@' ) ;
73131}
@@ -89,7 +147,7 @@ function assertUsesAllowlist(uses, allowlist) {
89147 const isAllowed = allowlist . split ( / \r ? \n / ) . some ( ( allow ) => action . startsWith ( allow ) ) ;
90148
91149 if ( isAllowed ) {
92- core . info ( `${ action } matched allowlist — ignoring action.` )
150+ core . info ( `${ action } matched allowlist — ignoring action.` ) ;
93151 }
94152
95153 return isAllowed ;
0 commit comments