2022-08-12 14:01:38 +10:00
import { readdir } from 'fs/promises' ;
2023-08-15 15:07:19 +02:00
import { pathExists , readFile } from 'fs-extra' ;
2023-06-24 01:03:41 +02:00
import { program } from 'commander' ;
import dedent from 'ts-dedent' ;
2023-08-15 15:07:19 +02:00
import chalk from 'chalk' ;
import yaml from 'yaml' ;
2023-11-14 17:15:47 +01:00
import { esMain } from './utils/esmain' ;
2022-12-02 14:24:55 +01:00
import {
allTemplates ,
templatesByCadence ,
type Cadence ,
type Template as TTemplate ,
type SkippableTask ,
2023-01-12 09:03:09 +01:00
} from '../code/lib/cli/src/sandbox-templates' ;
2023-03-29 14:52:14 +02:00
import { SANDBOX_DIRECTORY } from './utils/constants' ;
2022-08-12 14:01:38 +10:00
2023-03-29 14:52:14 +02:00
const sandboxDir = process . env . SANDBOX_ROOT || SANDBOX_DIRECTORY ;
2022-08-12 14:01:38 +10:00
2022-12-02 14:24:55 +01:00
type Template = Pick < TTemplate , 'inDevelopment' | 'skipTasks' > ;
2022-11-07 16:13:47 +01:00
export type TemplateKey = keyof typeof allTemplates ;
2022-08-12 14:01:38 +10:00
export type Templates = Record < TemplateKey , Template > ;
async function getDirectories ( source : string ) {
return ( await readdir ( source , { withFileTypes : true } ) )
. filter ( ( entry ) = > entry . isDirectory ( ) )
. map ( ( entry ) = > entry . name ) ;
}
export async function getTemplate (
cadence : Cadence ,
scriptName : string ,
{ index , total } : { index : number ; total : number }
) {
2022-08-12 16:20:03 +10:00
let potentialTemplateKeys : TemplateKey [ ] = [ ] ;
2022-08-12 15:36:16 +10:00
if ( await pathExists ( sandboxDir ) ) {
2022-08-12 14:01:38 +10:00
const sandboxes = await getDirectories ( sandboxDir ) ;
potentialTemplateKeys = sandboxes
. map ( ( dirName ) = > {
2022-11-07 16:13:47 +01:00
return Object . keys ( allTemplates ) . find (
2022-08-12 14:01:38 +10:00
( templateKey ) = > templateKey . replace ( '/' , '-' ) === dirName
) ;
} )
2022-10-05 15:55:57 +11:00
. filter ( Boolean ) as TemplateKey [ ] ;
2022-08-12 14:01:38 +10:00
}
if ( potentialTemplateKeys . length === 0 ) {
2022-11-07 16:13:47 +01:00
const cadenceTemplates = Object . entries ( allTemplates ) . filter ( ( [ key ] ) = >
templatesByCadence [ cadence ] . includes ( key as TemplateKey )
2022-08-12 14:01:38 +10:00
) ;
2022-10-05 15:55:57 +11:00
potentialTemplateKeys = cadenceTemplates . map ( ( [ k ] ) = > k ) as TemplateKey [ ] ;
2022-08-12 14:01:38 +10:00
}
2022-12-02 14:24:55 +01:00
potentialTemplateKeys = potentialTemplateKeys . filter ( ( t ) = > {
const currentTemplate = allTemplates [ t ] as Template ;
return (
currentTemplate . inDevelopment !== true &&
! currentTemplate . skipTasks ? . includes ( scriptName as SkippableTask )
) ;
} ) ;
2022-10-05 15:55:57 +11:00
2022-08-12 14:01:38 +10:00
if ( potentialTemplateKeys . length !== total ) {
2023-06-24 01:03:41 +02:00
throw new Error ( dedent ` Circle parallelism set incorrectly.
2022-08-12 14:01:38 +10:00
Parallelism is set to $ { total } , but there are $ {
potentialTemplateKeys . length
2023-06-27 12:57:17 +02:00
} templates to run for the "${scriptName}" task :
2023-06-24 01:03:41 +02:00
$ { potentialTemplateKeys . map ( ( v ) = > ` - ${ v } ` ) . join ( '\n' ) }
2023-08-16 12:25:32 +02:00
$ { await checkParallelism ( cadence ) }
2022-08-12 14:01:38 +10:00
` );
}
return potentialTemplateKeys [ index ] ;
}
2023-08-15 15:07:19 +02:00
const tasksMap = {
sandbox : 'create-sandboxes' ,
build : 'build-sandboxes' ,
chromatic : 'chromatic-sandboxes' ,
'e2e-tests' : 'e2e-production' ,
'e2e-tests-dev' : 'e2e-dev' ,
'test-runner' : 'test-runner-production' ,
2023-06-24 01:03:41 +02:00
// 'test-runner-dev', TODO: bring this back when the task is enabled again
2023-08-15 15:07:19 +02:00
bench : 'bench' ,
2023-08-16 12:25:32 +02:00
} as const ;
2023-08-15 15:07:19 +02:00
2023-08-16 12:25:32 +02:00
type TaskKey = keyof typeof tasksMap ;
const tasks = Object . keys ( tasksMap ) as TaskKey [ ] ;
2023-08-15 15:07:19 +02:00
const CONFIG_YML_FILE = '../.circleci/config.yml' ;
2023-08-16 12:25:32 +02:00
async function checkParallelism ( cadence? : Cadence , scriptName? : TaskKey ) {
2023-08-15 15:07:19 +02:00
const configYml = await readFile ( CONFIG_YML_FILE , 'utf-8' ) ;
const data = yaml . parse ( configYml ) ;
2023-06-24 01:03:41 +02:00
let potentialTemplateKeys : TemplateKey [ ] = [ ] ;
const cadences = cadence ? [ cadence ] : ( Object . keys ( templatesByCadence ) as Cadence [ ] ) ;
const scripts = scriptName ? [ scriptName ] : tasks ;
const summary = [ ] ;
2023-08-15 15:07:19 +02:00
let isIncorrect = false ;
2023-06-24 01:03:41 +02:00
cadences . forEach ( ( cad ) = > {
2023-12-18 14:06:31 -03:00
summary . push ( ` \ n ${ chalk . bold ( cad ) } ` ) ;
2023-06-24 01:03:41 +02:00
const cadenceTemplates = Object . entries ( allTemplates ) . filter ( ( [ key ] ) = >
templatesByCadence [ cad ] . includes ( key as TemplateKey )
) ;
potentialTemplateKeys = cadenceTemplates . map ( ( [ k ] ) = > k ) as TemplateKey [ ] ;
2023-08-16 12:25:32 +02:00
scripts . forEach ( ( script ) = > {
2023-06-24 01:03:41 +02:00
const templateKeysPerScript = potentialTemplateKeys . filter ( ( t ) = > {
const currentTemplate = allTemplates [ t ] as Template ;
2023-12-18 14:06:31 -03:00
2023-06-24 01:03:41 +02:00
return (
currentTemplate . inDevelopment !== true &&
! currentTemplate . skipTasks ? . includes ( script as SkippableTask )
) ;
} ) ;
2023-08-15 15:07:19 +02:00
const workflowJobsRaw : ( string | { [ key : string ] : any } ) [ ] = data . workflows [ cad ] . jobs ;
const workflowJobs = workflowJobsRaw
. filter ( ( item ) = > typeof item === 'object' && item !== null )
. reduce ( ( result , item ) = > Object . assign ( result , item ) , { } ) as Record < string , any > ;
if ( templateKeysPerScript . length > 0 && workflowJobs [ tasksMap [ script ] ] ) {
const currentParallelism = workflowJobs [ tasksMap [ script ] ] . parallelism || 2 ;
const newParallelism = templateKeysPerScript . length ;
if ( newParallelism !== currentParallelism ) {
summary . push (
` -- ❌ ${ tasksMap [ script ] } - parallelism: ${ currentParallelism } ${ chalk . bgRed (
` (should be ${ newParallelism } ) `
) } `
) ;
isIncorrect = true ;
} else {
summary . push (
` -- ✅ ${ tasksMap [ script ] } - parallelism: ${ templateKeysPerScript . length } ${
templateKeysPerScript . length === 2 ? ' (default)' : ''
} `
) ;
}
2023-06-24 01:03:41 +02:00
} else {
summary . push ( ` -- ${ script } - this script is fully skipped for this cadence. ` ) ;
}
} ) ;
} ) ;
2023-08-15 15:07:19 +02:00
if ( isIncorrect ) {
summary . unshift (
'The parellism count is incorrect for some jobs in .circleci/config.yml, you have to update them:'
) ;
throw new Error ( summary . concat ( '\n' ) . join ( '\n' ) ) ;
} else {
summary . unshift ( '✅ The parallelism count is correct for all jobs in .circleci/config.yml:' ) ;
console . log ( summary . concat ( '\n' ) . join ( '\n' ) ) ;
}
2023-12-18 14:06:31 -03:00
const inDevelopmentTemplates = Object . entries ( allTemplates )
. filter ( ( [ _ , t ] ) = > t . inDevelopment )
. map ( ( [ k ] ) = > k ) ;
if ( inDevelopmentTemplates . length > 0 ) {
console . log (
` 👇 Some templates were skipped as they are flagged to be in development. Please review if they should still contain this flag: \ n ${ inDevelopmentTemplates
. map ( ( k ) = > ` - ${ k } ` )
. join ( '\n' ) } `
) ;
}
2023-06-24 01:03:41 +02:00
}
2023-08-16 12:25:32 +02:00
type RunOptions = { cadence? : Cadence ; task? : TaskKey ; check : boolean } ;
2023-08-15 15:07:19 +02:00
async function run ( { cadence , task , check } : RunOptions ) {
if ( check ) {
2023-06-27 12:57:17 +02:00
if ( task && ! tasks . includes ( task ) ) {
2023-06-24 01:03:41 +02:00
throw new Error (
dedent ` The " ${ task } " task you provided is not valid. Valid tasks (found in .circleci/config.yml) are:
$ { tasks . map ( ( v ) = > ` - ${ v } ` ) . join ( '\n' ) } `
) ;
}
2023-08-15 15:07:19 +02:00
await checkParallelism ( cadence as Cadence , task ) ;
2023-06-24 01:03:41 +02:00
return ;
}
2022-08-12 14:01:38 +10:00
if ( ! cadence ) throw new Error ( 'Need to supply cadence to get template script' ) ;
const { CIRCLE_NODE_INDEX = 0 , CIRCLE_NODE_TOTAL = 1 } = process . env ;
console . log (
2023-06-24 01:03:41 +02:00
await getTemplate ( cadence as Cadence , task , {
2022-08-12 14:01:38 +10:00
index : + CIRCLE_NODE_INDEX ,
total : + CIRCLE_NODE_TOTAL ,
} )
) ;
}
2023-11-07 14:30:03 +01:00
if ( esMain ( import . meta . url ) ) {
2023-06-24 01:03:41 +02:00
program
. description ( 'Retrieve the template to run for a given cadence and task' )
. option ( '--cadence <cadence>' , 'Which cadence you want to run the script for' )
. option ( '--task <task>' , 'Which task you want to run the script for' )
2023-08-15 15:07:19 +02:00
. option (
'--check' ,
'Throws an error when the parallelism counts for tasks are incorrect' ,
false
) ;
2023-06-24 01:03:41 +02:00
program . parse ( process . argv ) ;
const options = program . opts ( ) as RunOptions ;
run ( options ) . catch ( ( err ) = > {
2022-08-12 15:37:20 +10:00
console . error ( err ) ;
process . exit ( 1 ) ;
} ) ;
2022-08-12 14:01:38 +10:00
}