2026-07-04 23:24:31 +08:00
#!/usr/bin/env node
const fs = require ( "fs" ) ;
const path = require ( "path" ) ;
const ROOT _DIR = path . resolve ( _ _dirname , ".." ) ;
const WORKSPACE _DIR = path . resolve ( ROOT _DIR , ".." ) ;
const WORKFLOWS _DIR = path . join ( WORKSPACE _DIR , "mike-workflows" ) ;
2026-07-20 03:04:47 +08:00
const WORKFLOW _COLLECTIONS = [
{ directory : "assistant-workflows" , type : "assistant" } ,
{ directory : "tabular-review-workflows" , type : "tabular" } ,
] ;
2026-07-04 23:24:31 +08:00
const BACKEND _OUT = path . join ( ROOT _DIR , "backend/src/lib/systemWorkflows.ts" ) ;
const LANDING _OUT = path . join ( ROOT _DIR , "landing/app/generated-workflows.ts" ) ;
function readText ( filePath ) {
return fs . readFileSync ( filePath , "utf8" ) ;
}
function readJson ( filePath ) {
try {
return JSON . parse ( readText ( filePath ) ) ;
} catch ( error ) {
throw new Error ( ` ${ relative ( filePath ) } is not valid JSON: ${ error . message } ` ) ;
}
}
function relative ( filePath ) {
return path . relative ( WORKSPACE _DIR , filePath ) ;
}
function fail ( message ) {
throw new Error ( message ) ;
}
2026-07-08 18:27:28 +08:00
function parseScalar ( value , label ) {
const trimmed = value . trim ( ) ;
if ( trimmed === "null" ) return null ;
if ( trimmed === "true" ) return true ;
if ( trimmed === "false" ) return false ;
if ( /^-?\d+$/ . test ( trimmed ) ) return Number . parseInt ( trimmed , 10 ) ;
if (
( trimmed . startsWith ( '"' ) && trimmed . endsWith ( '"' ) ) ||
( trimmed . startsWith ( "'" ) && trimmed . endsWith ( "'" ) )
) {
return trimmed . slice ( 1 , - 1 ) ;
}
if ( trimmed . startsWith ( "[" ) || trimmed . startsWith ( "{" ) ) {
try {
return JSON . parse ( trimmed ) ;
} catch ( error ) {
fail ( ` ${ label } is not valid inline JSON: ${ error . message } ` ) ;
}
}
return trimmed ;
}
function parseSimpleYaml ( source , label ) {
const lines = source . replace ( /\r\n/g , "\n" ) . split ( "\n" ) ;
const result = { } ;
for ( let i = 0 ; i < lines . length ; i ++ ) {
const line = lines [ i ] ;
if ( ! line . trim ( ) || line . trim ( ) . startsWith ( "#" ) ) continue ;
if ( line . startsWith ( " " ) ) {
fail ( ` ${ label } : ${ i + 1 } has unsupported indentation ` ) ;
}
const match = line . match ( /^([A-Za-z_][A-Za-z0-9_-]*):(.*)$/ ) ;
if ( ! match ) fail ( ` ${ label } : ${ i + 1 } is not valid frontmatter ` ) ;
const key = match [ 1 ] ;
const rawValue = match [ 2 ] . trim ( ) ;
if ( rawValue ) {
result [ key ] = parseScalar ( rawValue , ` ${ label } . ${ key } ` ) ;
continue ;
}
const scalarItems = [ ] ;
const objectItems = [ ] ;
2026-07-20 03:04:47 +08:00
const properties = { } ;
2026-07-08 18:27:28 +08:00
let mode = null ;
i ++ ;
for ( ; i < lines . length ; i ++ ) {
const child = lines [ i ] ;
if ( ! child . trim ( ) ) continue ;
if ( ! child . startsWith ( " " ) ) {
i -- ;
break ;
}
const listMatch = child . match ( /^ -(?:\s+(.*))?$/ ) ;
if ( listMatch ) {
const itemText = listMatch [ 1 ] ? . trim ( ) ? ? "" ;
if ( itemText . includes ( ":" ) ) {
mode ? ? = "objects" ;
if ( mode !== "objects" ) {
fail ( ` ${ label } . ${ key } mixes scalar and object list items ` ) ;
}
const object = { } ;
if ( itemText ) {
const itemMatch = itemText . match ( /^([A-Za-z_][A-Za-z0-9_-]*):(.*)$/ ) ;
if ( ! itemMatch ) {
fail ( ` ${ label } : ${ i + 1 } is not a valid object list item ` ) ;
}
object [ itemMatch [ 1 ] ] = parseScalar (
itemMatch [ 2 ] ,
` ${ label } . ${ key } . ${ itemMatch [ 1 ] } ` ,
) ;
}
objectItems . push ( object ) ;
continue ;
}
mode ? ? = "scalars" ;
if ( mode !== "scalars" ) {
fail ( ` ${ label } . ${ key } mixes object and scalar list items ` ) ;
}
scalarItems . push ( parseScalar ( itemText , ` ${ label } . ${ key } ` ) ) ;
continue ;
}
2026-07-20 03:04:47 +08:00
const childPropMatch = child . match ( /^ ([A-Za-z_][A-Za-z0-9_-]*):(.*)$/ ) ;
if ( childPropMatch ) {
mode ? ? = "properties" ;
if ( mode !== "properties" ) {
fail ( ` ${ label } . ${ key } mixes mapping and list values ` ) ;
}
properties [ childPropMatch [ 1 ] ] = parseScalar (
childPropMatch [ 2 ] ,
` ${ label } . ${ key } . ${ childPropMatch [ 1 ] } ` ,
) ;
continue ;
}
2026-07-08 18:27:28 +08:00
const propMatch = child . match ( /^ ([A-Za-z_][A-Za-z0-9_-]*):(.*)$/ ) ;
if ( ! propMatch || mode !== "objects" || objectItems . length === 0 ) {
fail ( ` ${ label } : ${ i + 1 } has unsupported frontmatter structure ` ) ;
}
objectItems [ objectItems . length - 1 ] [ propMatch [ 1 ] ] = parseScalar (
propMatch [ 2 ] ,
` ${ label } . ${ key } . ${ propMatch [ 1 ] } ` ,
) ;
}
2026-07-20 03:04:47 +08:00
result [ key ] =
mode === "objects"
? objectItems
: mode === "properties"
? properties
: scalarItems ;
2026-07-08 18:27:28 +08:00
}
return result ;
}
function readSkillFile ( filePath ) {
const text = readText ( filePath ) . replace ( /\r\n/g , "\n" ) ;
if ( ! text . startsWith ( "---\n" ) ) {
fail ( ` ${ relative ( filePath ) } must start with YAML frontmatter ` ) ;
}
const close = text . indexOf ( "\n---" , 4 ) ;
if ( close === - 1 ) {
fail ( ` ${ relative ( filePath ) } is missing closing YAML frontmatter marker ` ) ;
}
const afterClose = text . slice ( close + 4 ) ;
if ( afterClose && ! afterClose . startsWith ( "\n" ) ) {
fail ( ` ${ relative ( filePath ) } has invalid frontmatter closing marker ` ) ;
}
return {
metadata : parseSimpleYaml ( text . slice ( 4 , close ) , relative ( filePath ) ) ,
body : afterClose . replace ( /^\n/ , "" ) . trimEnd ( ) ,
fullText : text . trimEnd ( ) ,
} ;
}
2026-07-20 03:04:47 +08:00
function parseTableColumnsYaml ( filePath ) {
2026-07-08 18:27:28 +08:00
const lines = readText ( filePath ) . replace ( /\r\n/g , "\n" ) . split ( "\n" ) ;
const result = { columns _config : [ ] } ;
let i = 0 ;
while ( i < lines . length ) {
const line = lines [ i ] ;
if ( ! line . trim ( ) || line . trim ( ) . startsWith ( "#" ) ) {
i ++ ;
continue ;
}
const schemaMatch = line . match ( /^\$schema:\s*(.+)$/ ) ;
if ( schemaMatch ) {
result . $schema = parseScalar ( schemaMatch [ 1 ] , ` ${ relative ( filePath ) } . $ schema ` ) ;
i ++ ;
continue ;
}
2026-07-20 03:04:47 +08:00
if ( line !== "columns:" ) {
fail ( ` ${ relative ( filePath ) } : ${ i + 1 } is not valid table columns YAML ` ) ;
2026-07-08 18:27:28 +08:00
}
i ++ ;
break ;
}
let current = null ;
for ( ; i < lines . length ; i ++ ) {
const line = lines [ i ] ;
if ( ! line . trim ( ) ) continue ;
const itemMatch = line . match ( /^ - index:\s*(.+)$/ ) ;
if ( itemMatch ) {
current = {
index : parseScalar ( itemMatch [ 1 ] , ` ${ relative ( filePath ) } .columns_config.index ` ) ,
} ;
result . columns _config . push ( current ) ;
continue ;
}
if ( ! current ) {
fail ( ` ${ relative ( filePath ) } : ${ i + 1 } column entry must start with index ` ) ;
}
const propMatch = line . match ( /^ ([A-Za-z_][A-Za-z0-9_-]*):(.*)$/ ) ;
if ( ! propMatch ) {
fail ( ` ${ relative ( filePath ) } : ${ i + 1 } is not a valid column property ` ) ;
}
const key = propMatch [ 1 ] ;
const rawValue = propMatch [ 2 ] . trim ( ) ;
if ( key === "tags" && rawValue === "" ) {
const tags = [ ] ;
i ++ ;
for ( ; i < lines . length ; i ++ ) {
const tagMatch = lines [ i ] . match ( /^ -\s*(.+)$/ ) ;
if ( ! tagMatch ) {
i -- ;
break ;
}
tags . push ( parseScalar ( tagMatch [ 1 ] , ` ${ relative ( filePath ) } . ${ key } ` ) ) ;
}
current . tags = tags ;
continue ;
}
if ( rawValue === ">-" || rawValue === ">" || rawValue === "|-" || rawValue === "|" ) {
const parts = [ ] ;
i ++ ;
for ( ; i < lines . length ; i ++ ) {
if ( ! lines [ i ] . startsWith ( " " ) ) {
i -- ;
break ;
}
parts . push ( lines [ i ] . slice ( 6 ) ) ;
}
current [ key ] = rawValue . startsWith ( "|" )
? parts . join ( "\n" )
: parts . join ( " " ) . replace ( /\s+/g , " " ) . trim ( ) ;
continue ;
}
current [ key ] = parseScalar ( rawValue , ` ${ relative ( filePath ) } . ${ key } ` ) ;
}
return result ;
}
2026-07-04 23:24:31 +08:00
function assertString ( value , label ) {
if ( typeof value !== "string" || value . trim ( ) === "" ) {
fail ( ` ${ label } must be a non-empty string ` ) ;
}
}
function assertOptionalString ( value , label ) {
if ( value === undefined || value === null ) return ;
if ( typeof value !== "string" ) fail ( ` ${ label } must be a string ` ) ;
}
function assertOptionalStringArray ( value , label ) {
if ( value === undefined || value === null ) return ;
if ( ! Array . isArray ( value ) || value . some ( ( item ) => typeof item !== "string" ) ) {
fail ( ` ${ label } must be an array of strings ` ) ;
}
}
function normalizeContributors ( value , label ) {
if ( ! Array . isArray ( value ) || value . length === 0 ) {
fail ( ` ${ label } must be a non-empty array ` ) ;
}
return value . map ( ( contributor , index ) => {
const contributorLabel = ` ${ label } [ ${ index } ] ` ;
if ( ! contributor || typeof contributor !== "object" || Array . isArray ( contributor ) ) {
fail ( ` ${ contributorLabel } must be an object ` ) ;
}
assertString ( contributor . name , ` ${ contributorLabel } .name ` ) ;
assertOptionalString ( contributor . organisation , ` ${ contributorLabel } .organisation ` ) ;
assertOptionalString ( contributor . role , ` ${ contributorLabel } .role ` ) ;
assertOptionalString ( contributor . linkedin , ` ${ contributorLabel } .linkedin ` ) ;
return {
name : contributor . name . trim ( ) ,
organisation : contributor . organisation ? . trim ( ) || null ,
role : contributor . role ? . trim ( ) || null ,
linkedin : contributor . linkedin ? . trim ( ) || null ,
} ;
} ) ;
}
function assertColumnConfig ( columns , label ) {
if ( ! Array . isArray ( columns ) || columns . length === 0 ) {
fail ( ` ${ label } .columns_config must be a non-empty array ` ) ;
}
columns . forEach ( ( column , index ) => {
const columnLabel = ` ${ label } .columns_config[ ${ index } ] ` ;
if ( ! column || typeof column !== "object" || Array . isArray ( column ) ) {
fail ( ` ${ columnLabel } must be an object ` ) ;
}
if ( ! Number . isInteger ( column . index ) ) {
fail ( ` ${ columnLabel } .index must be an integer ` ) ;
}
assertString ( column . name , ` ${ columnLabel } .name ` ) ;
assertString ( column . prompt , ` ${ columnLabel } .prompt ` ) ;
assertOptionalString ( column . format , ` ${ columnLabel } .format ` ) ;
2026-07-08 18:27:28 +08:00
assertOptionalStringArray ( column . tags , ` ${ columnLabel } .tags ` ) ;
2026-07-04 23:24:31 +08:00
} ) ;
}
2026-07-20 03:04:47 +08:00
function readWorkflow ( category , workflowDir ) {
const slug = path . basename ( workflowDir ) ;
2026-07-04 23:24:31 +08:00
const metadataPath = path . join ( workflowDir , "metadata.json" ) ;
2026-07-08 18:27:28 +08:00
if ( fs . existsSync ( metadataPath ) ) {
fail ( ` ${ relative ( metadataPath ) } is no longer supported; use SKILL.md frontmatter ` ) ;
}
const skillPath = path . join ( workflowDir , "SKILL.md" ) ;
if ( ! fs . existsSync ( skillPath ) ) {
fail ( ` ${ relative ( skillPath ) } is required ` ) ;
2026-07-04 23:24:31 +08:00
}
2026-07-20 03:04:47 +08:00
const { metadata : frontmatter , body : skillMd , fullText : sourceSkillMd } =
readSkillFile ( skillPath ) ;
2026-07-08 18:27:28 +08:00
const label = ` ${ relative ( skillPath ) } frontmatter ` ;
2026-07-20 03:04:47 +08:00
const metadata = frontmatter . metadata ;
2026-07-04 23:24:31 +08:00
const id = ` builtin- ${ slug } ` ;
2026-07-20 03:04:47 +08:00
assertString ( frontmatter . name , ` ${ label } .name ` ) ;
if ( frontmatter . name !== slug ) {
2026-07-08 18:27:28 +08:00
fail ( ` ${ label } .name must match the folder name " ${ slug } " ` ) ;
}
2026-07-20 03:04:47 +08:00
assertString ( frontmatter . description , ` ${ label } .description ` ) ;
assertString ( frontmatter . license , ` ${ label } .license ` ) ;
if ( ! metadata || typeof metadata !== "object" || Array . isArray ( metadata ) ) {
fail ( ` ${ label } .metadata must be a mapping ` ) ;
}
assertString ( metadata . author , ` ${ label } .metadata.author ` ) ;
2026-07-04 23:24:31 +08:00
assertString ( metadata . language , ` ${ label } .language ` ) ;
assertString ( metadata . version , ` ${ label } .version ` ) ;
2026-07-20 03:04:47 +08:00
assertString ( metadata [ "mike-display-name" ] , ` ${ label } .metadata.mike-display-name ` ) ;
if ( metadata [ "mike-type" ] !== category ) {
fail ( ` ${ label } .metadata.mike-type must be " ${ category } " ` ) ;
}
if ( ! [ "system" , "add-on" ] . includes ( metadata [ "mike-availability" ] ) ) {
fail ( ` ${ label } .metadata.mike-availability must be "system" or "add-on" ` ) ;
}
assertString ( metadata . practice , ` ${ label } .metadata.practice ` ) ;
assertString ( metadata . jurisdictions , ` ${ label } .metadata.jurisdictions ` ) ;
const normalizedMetadata = {
title : metadata [ "mike-display-name" ] ,
description : frontmatter . description ,
type : metadata [ "mike-type" ] ,
contributors : [
{
name : metadata . author . trim ( ) ,
organisation : null ,
role : null ,
linkedin : null ,
} ,
] ,
language : metadata . language ,
version : metadata . version ,
practice : metadata . practice ,
jurisdictions : metadata . jurisdictions
. split ( "," )
. map ( ( item ) => item . trim ( ) )
. filter ( Boolean ) ,
} ;
2026-07-04 23:24:31 +08:00
if ( category === "assistant" ) {
2026-07-08 18:27:28 +08:00
if ( ! skillMd . trim ( ) ) {
fail ( ` ${ relative ( skillPath ) } must include instructions after frontmatter ` ) ;
2026-07-04 23:24:31 +08:00
}
2026-07-20 03:04:47 +08:00
const tableColumnsPath = path . join ( workflowDir , "table-columns.yaml" ) ;
if ( fs . existsSync ( tableColumnsPath ) ) {
fail ( ` ${ relative ( tableColumnsPath ) } is only supported for tabular workflows ` ) ;
2026-07-04 23:24:31 +08:00
}
return {
id ,
2026-07-20 03:04:47 +08:00
availability : metadata [ "mike-availability" ] ,
metadata : normalizedMetadata ,
2026-07-08 18:27:28 +08:00
skill _md : skillMd ,
source _skill _md : sourceSkillMd ,
2026-07-04 23:24:31 +08:00
columns _config : null ,
} ;
}
2026-07-20 03:04:47 +08:00
const tableColumnsPath = path . join ( workflowDir , "table-columns.yaml" ) ;
if ( ! fs . existsSync ( tableColumnsPath ) ) {
fail ( ` ${ relative ( tableColumnsPath ) } is required for tabular workflows ` ) ;
2026-07-04 23:24:31 +08:00
}
2026-07-20 03:04:47 +08:00
const tableConfig = parseTableColumnsYaml ( tableColumnsPath ) ;
const tableConfigLabel = relative ( tableColumnsPath ) ;
const expectedSchemaPath = path . join (
WORKFLOWS _DIR ,
"workflow-schema/table-columns.schema.yaml" ,
) ;
const actualSchemaPath = path . resolve ( workflowDir , tableConfig . $schema ? ? "" ) ;
if ( actualSchemaPath !== expectedSchemaPath ) {
fail ( ` ${ tableConfigLabel } . $ schema must point to workflow-schema/table-columns.schema.yaml ` ) ;
2026-07-04 23:24:31 +08:00
}
assertColumnConfig ( tableConfig . columns _config , tableConfigLabel ) ;
return {
id ,
2026-07-20 03:04:47 +08:00
availability : metadata [ "mike-availability" ] ,
metadata : normalizedMetadata ,
2026-07-08 18:27:28 +08:00
skill _md : skillMd || null ,
source _skill _md : sourceSkillMd ,
2026-07-04 23:24:31 +08:00
columns _config : tableConfig . columns _config ,
} ;
}
function loadWorkflows ( ) {
const workflows = [ ] ;
const seenIds = new Set ( ) ;
2026-07-20 03:04:47 +08:00
for ( const collection of WORKFLOW _COLLECTIONS ) {
const collectionDir = path . join ( WORKFLOWS _DIR , collection . directory ) ;
if ( ! fs . existsSync ( collectionDir ) ) continue ;
const workflowDirs = fs
. readdirSync ( collectionDir , { withFileTypes : true } )
. filter ( ( entry ) => entry . isDirectory ( ) && ! entry . name . startsWith ( "." ) )
. flatMap ( ( entry ) => {
const entryDir = path . join ( collectionDir , entry . name ) ;
if ( fs . existsSync ( path . join ( entryDir , "SKILL.md" ) ) ) return [ entryDir ] ;
if ( ! fs . existsSync ( path . join ( entryDir , "pack.json" ) ) ) return [ ] ;
return fs
. readdirSync ( entryDir , { withFileTypes : true } )
. filter (
( child ) =>
child . isDirectory ( ) &&
fs . existsSync ( path . join ( entryDir , child . name , "SKILL.md" ) ) ,
)
. map ( ( child ) => path . join ( entryDir , child . name ) ) ;
} )
. sort ( ( a , b ) => a . localeCompare ( b ) ) ;
for ( const workflowDir of workflowDirs ) {
const workflow = readWorkflow ( collection . type , workflowDir ) ;
if ( workflow . availability !== "system" ) continue ;
2026-07-04 23:24:31 +08:00
if ( seenIds . has ( workflow . id ) ) {
fail ( ` Duplicate workflow id: ${ workflow . id } ` ) ;
}
seenIds . add ( workflow . id ) ;
workflows . push ( workflow ) ;
}
}
2026-07-08 18:27:28 +08:00
return workflows . sort ( ( a , b ) => a . id . localeCompare ( b . id ) ) ;
2026-07-04 23:24:31 +08:00
}
function formatTs ( value ) {
return JSON . stringify ( value , null , 4 ) ;
}
function writeGeneratedFiles ( workflows ) {
2026-07-08 18:27:28 +08:00
const systemWorkflows = workflows . map ( ( workflow ) => ( {
user _id : null ,
is _system : true ,
created _at : "" ,
id : workflow . id ,
metadata : workflow . metadata ,
skill _md : workflow . skill _md ,
columns _config : workflow . columns _config ,
} ) ) ;
2026-07-04 23:24:31 +08:00
const systemAssistantWorkflows = workflows
2026-07-08 18:27:28 +08:00
. filter ( ( workflow ) => workflow . metadata . type === "assistant" )
2026-07-04 23:24:31 +08:00
. map ( ( workflow ) => ( {
id : workflow . id ,
2026-07-08 18:27:28 +08:00
title : workflow . metadata . title ,
skill _md : workflow . skill _md ,
2026-07-04 23:24:31 +08:00
} ) ) ;
const landingWorkflows = workflows . map ( ( workflow ) => ( {
id : workflow . id ,
2026-07-08 18:27:28 +08:00
metadata : workflow . metadata ,
skill _md : workflow . source _skill _md ,
2026-07-04 23:24:31 +08:00
columnCount : workflow . columns _config ? . length ? ? 0 ,
columns : workflow . columns _config ? ? [ ] ,
} ) ) ;
2026-07-08 18:27:28 +08:00
const backendText = ` // This file is generated by scripts/build-workflows.js. Do not edit it directly. \n \n export type SystemWorkflowContributor = { \n name: string; \n organisation: string | null; \n role: string | null; \n linkedin: string | null; \n }; \n \n export type SystemWorkflowMetadata = { \n title: string; \n description: string; \n type: "assistant" | "tabular"; \n contributors: SystemWorkflowContributor[]; \n language: string; \n version: string; \n practice: string | null; \n jurisdictions: string[] | null; \n }; \n \n export type SystemWorkflow = { \n id: string; \n user_id: null; \n is_system: true; \n created_at: string; \n metadata: SystemWorkflowMetadata; \n skill_md: string | null; \n columns_config: { index: number; name: string; format?: string; prompt: string; tags?: string[] }[] | null; \n }; \n \n export const SYSTEM_WORKFLOWS: SystemWorkflow[] = ${ formatTs ( systemWorkflows ) } ; \n \n export const SYSTEM_WORKFLOW_IDS = new Set(SYSTEM_WORKFLOWS.map((wf) => wf.id)); \n \n export const SYSTEM_ASSISTANT_WORKFLOWS: { id: string; title: string; skill_md: string }[] = ${ formatTs ( systemAssistantWorkflows ) } ; \n ` ;
2026-07-04 23:24:31 +08:00
const landingText = ` // This file is generated by scripts/build-workflows.js. Do not edit it directly. \n import type { LandingWorkflow } from "./workflow-browser"; \n \n export const LANDING_WORKFLOWS: LandingWorkflow[] = ${ formatTs ( landingWorkflows ) } ; \n ` ;
fs . writeFileSync ( BACKEND _OUT , backendText ) ;
if ( fs . existsSync ( path . dirname ( LANDING _OUT ) ) ) {
fs . writeFileSync ( LANDING _OUT , landingText ) ;
}
}
function main ( ) {
if ( ! fs . existsSync ( WORKFLOWS _DIR ) ) {
fail ( ` Workflow source directory not found: ${ relative ( WORKFLOWS _DIR ) } ` ) ;
}
2026-07-20 03:04:47 +08:00
for ( const collection of WORKFLOW _COLLECTIONS ) {
const collectionDir = path . join ( WORKFLOWS _DIR , collection . directory ) ;
if ( ! fs . existsSync ( collectionDir ) ) {
fail ( ` Workflow collection not found: ${ relative ( collectionDir ) } ` ) ;
}
2026-07-08 18:27:28 +08:00
}
2026-07-04 23:24:31 +08:00
const workflows = loadWorkflows ( ) ;
if ( workflows . length === 0 ) {
fail ( "No workflows found" ) ;
}
writeGeneratedFiles ( workflows ) ;
console . log ( ` Generated ${ workflows . length } system workflows. ` ) ;
}
try {
main ( ) ;
} catch ( error ) {
console . error ( error . message ) ;
process . exit ( 1 ) ;
}