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