2020-05-19 09:51:39 -07:00
# ! /usr/bin/env npx ts-node
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 > {
2020-05-19 09:51:39 -07:00
let workflowErrors : WorkflowWithErrors = {
id : workflowPath ,
2021-08-16 15:36:31 +05:30
name : null ,
2020-05-19 09:51:39 -07:00
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
}
2021-09-30 10:19:20 +05:30
}
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
})();