/**
 * Split the given CloudFormation resource specification up into a patch set, by service
 *
 * After splitting, only write out those fragments that are valid.(*)
 *
 * The reason for this is that some service updates sometimes contain mistakes,
 * and we want to be able to take partial upgrades. By splitting the spec, we can
 * take updates to some services while leaving updates to other services behind.
 *
 * (*) If `NO_VALIDATE` is set, all pieces are written out. In normal operation this
 *     should not be used.
 */ import * as path from 'path' ; import * as fs from 'fs-extra' ; import { writeSorted } from './patch-set' ; import { CfnSpec , CfnSpecValidator , formatErrorInContext } from './validate-cfn' ; async function main ( args ) { if ( args . length < 2 ) { throw new Error ( 'Usage: split-spec-by-service <SPECFILE> <DIRECTORY>' ) ; } const [ specFile , outDir ] = args ; log ( `Loading specification: ${ specFile }` ) ; const spec = await fs . readJson ( specFile ) ; log ( 'Splitting' ) ; const byService = { } ; for ( const [ propTypeName , propType ] of Object . entries ( spec . PropertyTypes ) ) { const svcName = serviceName ( propTypeName ) ; serviceSpec ( svcName ) . PropertyTypes [ propTypeName ] = propType ; } for ( const [ resTypeName , resType ] of Object . entries ( spec . ResourceTypes ) ) { const svcName = serviceName ( resTypeName ) ; serviceSpec ( svcName ) . ResourceTypes [ resTypeName ] = resType ; } log ( 'Writing' ) ; for ( const [ svcName , svcSpec ] of Object . entries ( byService ) ) { const errors = ! process . env . NO_VALIDATE ? CfnSpecValidator . validate ( svcSpec ) : [ ] ; if ( errors . length === 0 ) { const toWrite = { PropertyTypes : svcSpec . PropertyTypes , ResourceTypes : svcSpec . ResourceTypes , $version : svcSpec . ResourceSpecificationVersion , } ; await writeSorted ( path . join ( outDir , `000_${ svcName }.json` ) , toWrite ) ; } else { console . warn ( '=' . repeat ( 70 ) ) ; console . warn ( ' ' . repeat ( Math . floor ( 35 - svcName . length / 2 ) ) + svcName ) ; console . warn ( '=' . repeat ( 70 ) ) ; for ( const error of errors ) { console . warn ( formatErrorInContext ( error ) ) ; } } } await fs . writeJson ( path . join ( outDir , '001_Version.json' ) , { ResourceSpecificationVersion : spec . ResourceSpecificationVersion , } , { spaces : 2 } ) ; function serviceSpec ( svcName ) { if ( ! ( svcName in byService ) ) { byService [ svcName ] = { PropertyTypes : { } , ResourceTypes : { } , ResourceSpecificationVersion : spec . ResourceSpecificationVersion , } ; } return byService [ svcName ] ; } } function serviceName ( x ) { return x . split ( '::' ) . slice ( 0 , 2 ) . join ( '_' ) ; } function log ( x ) { console . log ( x ) ; } main ( process . argv . slice ( 2 ) ) . catch ( e => { console . error ( e ) ; process . exitCode = 1 ; } ) ;