2020-05-19 09:51:39 -07:00
# ! / u s r / b i n / e n v n p x t s - n o d e
import { promises as fs } from "fs" ;
import { safeLoad } from "js-yaml" ;
2022-02-14 10:11:33 +00:00
import { basename , extname , join , dirname } from "path" ;
2020-05-19 09:51:39 -07:00
import { Validator as validator } from "jsonschema" ;
2020-05-19 10:04:07 -07:00
import { endGroup , error , info , setFailed , startGroup } from '@actions/core' ;
2020-05-19 09:51:39 -07:00
interface WorkflowWithErrors {
id : string ;
2021-08-16 15:36:31 +05:30
name : string ;
2020-05-19 09:51:39 -07:00
errors : string [ ] ;
}
interface WorkflowProperties {
name : string ;
description : string ;
2022-03-04 06:28:56 +00:00
creator : string ;
2020-05-19 09:51:39 -07:00
iconName : string ;
categories : string [ ] ;
}
const propertiesSchema = {
type : "object" ,
properties : {
2021-08-16 15:36:31 +05:30
name : { type : "string" , required : true , "minLength" : 1 } ,
2020-05-19 09:51:39 -07:00
description : { type : "string" , required : true } ,
2020-06-02 13:40:45 -07:00
creator : { type : "string" , required : false } ,
2020-05-19 09:51:39 -07:00
iconName : { type : "string" , required : true } ,
categories : {
anyOf : [
{
type : "array" ,
items : { type : "string" }
} ,
{
type : "null" ,
}
] ,
required : true
} ,
}
}
2021-08-16 15:36:31 +05:30
2022-05-02 06:08:29 +00:00
async function checkWorkflows ( folders : string [ ] , allowed_categories : object [ ] ) : Promise < WorkflowWithErrors [ ] > {
2020-05-19 09:51:39 -07:00
const result : WorkflowWithErrors [ ] = [ ]
2021-08-17 10:09:53 +05:30
const workflow_template_names = new Set ( )
2020-05-19 09:51:39 -07:00
for ( const folder of folders ) {
const dir = await fs . readdir ( folder , {
withFileTypes : true ,
} ) ;
for ( const e of dir ) {
2021-09-28 09:37:43 +01:00
if ( e . isFile ( ) && [ ".yml" , ".yaml" ] . includes ( extname ( e . name ) ) ) {
2020-05-19 09:51:39 -07:00
const fileType = basename ( e . name , extname ( e . name ) )
const workflowFilePath = join ( folder , e . name ) ;
const propertiesFilePath = join ( folder , "properties" , ` ${ fileType } .properties.json ` )
2022-05-02 06:08:29 +00:00
const workflowWithErrors = await checkWorkflow ( workflowFilePath , propertiesFilePath , allowed_categories ) ;
2021-08-17 10:09:53 +05:30
if ( workflowWithErrors . name && workflow_template_names . size == workflow_template_names . add ( workflowWithErrors . name ) . size ) {
workflowWithErrors . errors . push ( ` Workflow template name " ${ workflowWithErrors . name } " already exists ` )
2021-08-16 15:36:31 +05:30
}
if ( workflowWithErrors . errors . length > 0 ) {
result . push ( workflowWithErrors )
2020-05-19 09:51:39 -07:00
}
}
}
}
return result ;
}
2022-05-02 06:08:29 +00:00
async function checkWorkflow ( workflowPath : string , propertiesPath : string , allowed_categories : object [ ] ) : Promise < WorkflowWithErrors > {
2021-06-17 14:28:48 +05:30
let workflowErrors : WorkflowWithErrors = {
id : workflowPath ,
2021-08-16 15:36:31 +05:30
name : null ,
2021-06-17 14:28:48 +05:30
errors : [ ]
}
try {
const workflowFileContent = await fs . readFile ( workflowPath , "utf8" ) ;
2021-08-16 15:36:31 +05:30
safeLoad ( workflowFileContent ) ; // Validate yaml parses without error
const propertiesFileContent = await fs . readFile ( propertiesPath , "utf8" )
const properties : WorkflowProperties = JSON . parse ( propertiesFileContent )
2021-08-17 10:28:22 +05:30
if ( properties . name && properties . name . trim ( ) . length > 0 ) {
2021-08-16 15:36:31 +05:30
workflowErrors . name = properties . name
}
let v = new validator ( ) ;
const res = v . validate ( properties , propertiesSchema )
workflowErrors . errors = res . errors . map ( e = > e . toString ( ) )
2021-06-17 14:28:48 +05:30
2021-08-16 15:36:31 +05:30
if ( properties . iconName ) {
2021-08-16 16:48:34 +05:30
if ( ! /^octicon\s+/ . test ( properties . iconName ) ) {
2021-08-16 15:36:31 +05:30
try {
await fs . access ( ` ../../icons/ ${ properties . iconName } .svg ` )
} catch ( e ) {
workflowErrors . errors . push ( ` No icon named ${ properties . iconName } found ` )
}
}
else {
2021-08-16 16:48:34 +05:30
let iconName = properties . iconName . match ( /^octicon\s+(.*)/ )
if ( ! iconName || iconName [ 1 ] . split ( "." ) [ 0 ] . length <= 0 ) {
2021-08-16 15:36:31 +05:30
workflowErrors . errors . push ( ` No icon named ${ properties . iconName } found ` )
}
}
}
2022-05-02 06:08:29 +00:00
var path = dirname ( workflowPath )
2022-05-02 06:32:43 +00:00
var folder_categories = allowed_categories . find ( category = > category [ "path" ] == path ) [ "categories" ]
2022-05-02 06:08:29 +00:00
if ( ! workflowPath . endsWith ( "blank.yml" ) ) {
2022-02-24 10:26:04 +00:00
if ( ! properties . categories || properties . categories . length == 0 ) {
workflowErrors . errors . push ( ` Workflow categories cannot be null or empty ` )
2022-05-02 06:08:29 +00:00
}
2022-05-02 06:32:43 +00:00
else if ( ! folder_categories . some ( category = > properties . categories [ 0 ] . toLowerCase ( ) == category . toLowerCase ( ) ) ) {
2022-05-03 11:00:55 +05:30
workflowErrors . errors . push ( ` The first category in properties.json categories for workflow in ${ basename ( path ) } folder must be one of " ${ folder_categories } . Either move the workflow to an appropriate directory or change the category." ` )
2022-02-24 10:26:04 +00:00
}
2022-02-14 10:11:33 +00:00
}
2022-03-04 06:28:56 +00:00
2022-05-02 13:22:36 +00:00
if ( basename ( path ) . toLowerCase ( ) == 'deployments' && ! properties . creator ) {
2022-03-04 06:28:56 +00:00
workflowErrors . errors . push ( ` The "creator" in properties.json must be present. ` )
}
2020-05-19 09:51:39 -07:00
} catch ( e ) {
workflowErrors . errors . push ( e . toString ( ) )
}
return workflowErrors ;
}
( async function main() {
try {
const settings = require ( "./settings.json" ) ;
const erroredWorkflows = await checkWorkflows (
2022-05-02 06:08:29 +00:00
settings . folders , settings . allowed_categories
2020-05-19 09:51:39 -07:00
)
if ( erroredWorkflows . length > 0 ) {
2020-05-19 10:04:07 -07:00
startGroup ( ` 😟 - Found ${ erroredWorkflows . length } workflows with errors: ` ) ;
2020-05-19 09:51:39 -07:00
erroredWorkflows . forEach ( erroredWorkflow = > {
error ( ` Errors in ${ erroredWorkflow . id } - ${ erroredWorkflow . errors . map ( e = > e . toString ( ) ) . join ( ", " ) } ` )
} )
endGroup ( ) ;
setFailed ( ` Found ${ erroredWorkflows . length } workflows with errors ` ) ;
2020-05-19 10:04:07 -07:00
} else {
info ( "🎉🤘 - Found no workflows with errors!" )
2020-05-19 09:51:39 -07:00
}
} catch ( e ) {
error ( ` Unhandled error while syncing workflows: ${ e } ` ) ;
setFailed ( ` Unhandled error ` )
}
2021-08-16 15:42:50 +05:30
} ) ( ) ;