1+ const fs = require ( "node:fs/promises" )
2+
3+ /* Github Actions values to use */
4+ const contributorHandle = process . env . GITHUB_HANDLE || ''
5+ const changedFilesStr = process . env ?. CHANGED_FILES || ''
6+ const canChangeAnyFiles = [ 'admin' , 'maintain' ] . includes ( process . env ?. GITHUB_PERMISSION_ROLE || '' )
7+
8+
9+ /* Helpers */
10+ const createFeedbacks = ( feedbacks = [ ] , mode = 'line' ) => {
11+ return ( message ) => {
12+ const feedback = mode == 'task' ? `- [ ] ${ message } ` : message
13+ feedbacks . push ( feedback )
14+ }
15+ }
16+
17+ /* ------------------------------------ - ----------------------------------- */
18+
19+ /** Establishes a state about the contribution */
20+ const getContributionState = ( ) => {
21+ const changedFiles = changedFilesStr . split ( " " ) || [ ]
22+ const requiredRegexp = / ( i n d e x .h t m l | s t y l e s .c s s | m e t a .j s o n ) $ /
23+ const fileCorrectness = changedFiles . reduce ( ( details , file ) => {
24+
25+ // Separates changed file to "incorrectFiles" or "correctFiles"
26+ const detailsProperty = requiredRegexp . test ( file ) ? details . correctFiles : details . incorrectFiles
27+ detailsProperty . push ( file )
28+ return details
29+ } , { incorrectFiles : [ ] , correctFiles : [ ] } )
30+
31+ const state = {
32+ changedFiles,
33+ ...fileCorrectness
34+ }
35+ return state
36+ }
37+
38+
39+
40+ /** Review HTML file - returning an array of feedbacks */
41+ const reviewHTMLFile = ( file , fileContent ) => {
42+ const feedbacks = [ ]
43+ if ( file . includes ( '.html' ) ) {
44+ const feedbackPush = createFeedbacks ( feedbacks , 'task' )
45+
46+ const isHTMLWithJS = / < ( .+ ) ? s c r i p t ( .+ ) ? > / . test ( fileContent )
47+ const hasValidStylesheet = / < ( .+ ) ? l i n k ( .+ ) ? h r e f = ( \. + ) ? s t y l e s .c s s > / gi. test ( fileContent )
48+ if ( isHTMLWithJS ) {
49+ feedbackPush ( 'Remove the JavaScript contained in your HTML file: JavaScript is not allowed' )
50+ }
51+ if ( ! hasValidStylesheet ) {
52+ const isMissingCSS = ! hasValidStylesheet && ! / \w .c s s / gi. test ( fileContent )
53+ const message = isMissingCSS
54+ ? 'Missing linked stylesheet file: link the CSS file to your HTML'
55+ : 'Remove the JavaScript contained in your HTML file: JavaScript is not allowed'
56+
57+ feedbackPush ( message )
58+ }
59+
60+ if ( feedbacks . length ) feedbacks . unshift ( '### HTML feedbacks' )
61+ return feedbacks
62+ }
63+
64+ }
65+
66+ /** Review CSS file - returning an array of feedbacks */
67+ const reviewCSSFile = ( file , fileContent ) => {
68+ const feedbacks = [ ]
69+ if ( ! file . includes ( '.css' ) ) return ;
70+ const feedbackPush = createFeedbacks ( feedbacks , 'task' )
71+
72+ // CSS animation(s) check
73+ const hasCSSAnimation = / @ k e y f r a m e s / gi. test ( fileContent )
74+ if ( ! fileContent ) {
75+ feedbackPush ( `Missing content, add the code for the file \`${ file } \`` )
76+
77+ } else if ( ! hasCSSAnimation ) {
78+ feedbackPush ( 'Add animation(s) using the "@keyframes" in your CSS' )
79+ }
80+
81+
82+
83+ if ( feedbacks . length ) feedbacks . unshift ( '### CSS feedbacks' )
84+ return feedbacks
85+ }
86+
87+ /** Review JSON file - returning an array of feedbacks */
88+ const reviewJSONFile = ( file , fileContent ) => {
89+ const feedbacks = [ ]
90+ if ( ! file . includes ( '.json' ) ) return ;
91+
92+ const feedbackPush = createFeedbacks ( feedbacks , 'task' )
93+
94+ // Meta checks
95+ const contributorRegexp = new RegExp ( contributorHandle , 'i' )
96+ const hasMetaArtNameValue = / a r t N a m e \s ? : \s ? \w + ./ . test ( fileContent )
97+ const hasMetaGithubHandleValue = / g i t h u b H a n d l e \s ? : \? \w + ./ . test ( fileContent )
98+ const hasCorrectMetaGithubHandle = contributorRegexp . test ( fileContent )
99+
100+
101+ ! hasMetaArtNameValue && feedbackPush ( 'Missing artName: add an "artName"' )
102+ if ( hasMetaGithubHandleValue && ! hasCorrectMetaGithubHandle ) {
103+ feedbackPush ( 'Unmatched github handler: adjust your `githubHandle`' )
104+ } else if ( ! hasMetaGithubHandleValue ) {
105+ feedbackPush ( 'Missing github handle: add your github handle in `githubHandle`' )
106+ }
107+
108+ if ( feedbacks . length ) feedbacks . unshift ( '### JSON feedbacks' )
109+ return feedbacks
110+ }
111+
112+
113+ const reviewContribution = ( contributionStates ) => {
114+ const feedbacks = [ ]
115+ const feedbackPush = createFeedbacks ( feedbacks , 'task' )
116+
117+ const { correctFiles, incorrectFiles } = contributionStates
118+ const allFiles = [ ...correctFiles , ...incorrectFiles ]
119+
120+ // Unecessary files
121+ const folderRegexp = new RegExp ( `^Art/${ contributorHandle } ` , 'i' )
122+
123+ // Handle incorrect files
124+ for ( const file of incorrectFiles ) {
125+ const hasValidFolderName = folderRegexp . test ( file )
126+
127+ if ( ! canChangeAnyFiles ) {
128+ // File(s) outside of the Art folder
129+ if ( ! hasValidFolderName ) {
130+ feedbackPush ( `Remove unnecessary file: \`${ file } \`` )
131+ } else { // Incorrect file names in Art contribution folder
132+ feedbackPush ( `Rename your file as recommended: \`${ file } \`` )
133+ }
134+ }
135+ }
136+
137+ if ( feedbacks . length ) feedbacks . unshift ( '### Other feedbacks' )
138+ return feedbacks
139+
140+ }
141+
142+ const checkContent = async ( contributionStates ) => {
143+ let feedbacks = [ ]
144+ const feedbackPush = createFeedbacks ( feedbacks , 'task' )
145+
146+ const { incorrectFiles, correctFiles } = contributionStates
147+ const changedFiles = [ ...incorrectFiles , ...correctFiles ]
148+ let HTMLReviews = [ ] , CSSReviews = [ ] , JSONReviews = [ ]
149+
150+ // Checks
151+ for ( const file of changedFiles ) {
152+ let fileContent = ''
153+ try {
154+ fileContent = await fs . readFile ( file , 'utf-8' )
155+ } catch {
156+ feedbackPush ( `File not found or unreadable: \`${ file } \`` )
157+ continue
158+ }
159+
160+ if ( ! fileContent ) {
161+ feedbackPush ( `Missing content, add the code for the file \`${ file } \`` )
162+ } else {
163+ HTMLReviews = reviewHTMLFile ( file , fileContent ) || HTMLReviews
164+ CSSReviews = reviewCSSFile ( file , fileContent ) || CSSReviews
165+ JSONReviews = reviewJSONFile ( file , fileContent ) || JSONReviews
166+ }
167+ }
168+ feedbacks = [
169+ ...feedbacks ,
170+ ...HTMLReviews ,
171+ ...CSSReviews ,
172+ ...JSONReviews ,
173+ ]
174+ const otherReviews = reviewContribution ( contributionStates )
175+ return ( [
176+ ...feedbacks ,
177+ ...otherReviews
178+ ] )
179+ }
180+
181+ const generateReviewMessage = ( feedbacks ) => {
182+ const messageLines = [ `Aloha @${ contributorHandle } 🙌 Thanks for your contribution!` ]
183+ const messagePush = createFeedbacks ( messageLines , 'line' )
184+
185+
186+ if ( feedbacks . length ) {
187+ messagePush ( "Before we could merge, please address the following:" )
188+ messagePush ( "## Feedbacks" )
189+
190+ const taskList = feedbacks . join ( "\n" )
191+ messagePush ( `${ taskList } ` )
192+ } else {
193+ messagePush ( "Seems to meet requirements, now awaiting for a maintener last validation." )
194+ }
195+
196+
197+ messageLines . push ( "Happy Coding! 🚀" )
198+ const messageReview = messageLines . join ( "\n" )
199+ return messageReview
200+ }
201+
202+ ; ( async ( ) => {
203+ const contributionState = getContributionState ( )
204+ const feedbacks = await checkContent ( contributionState )
205+ const PRFinalReview = generateReviewMessage ( feedbacks )
206+ console . info ( `\n\n${ PRFinalReview } \n\n` )
207+ } ) ( )
0 commit comments