@@ -19,6 +19,13 @@ interface ScenarioConfig {
1919 description : string ;
2020 /** Optional parameters passed to the StackSet (and through to stack instances) */
2121 parameterKeys ?: string [ ] ;
22+ /**
23+ * SAM-style scenario: source template lives at `scenarios/<name>/dist/template.yaml`
24+ * (produced by `sam package`) rather than the scenario root. The CI deploy job
25+ * runs `sam package` which uploads Lambda zips to the blueprints bucket and
26+ * writes the packaged template into dist/.
27+ */
28+ samStyle ?: boolean ;
2229}
2330
2431const SCENARIOS : ScenarioConfig [ ] = [
@@ -31,9 +38,14 @@ const SCENARIOS: ScenarioConfig[] = [
3138 { name : 'localgov-drupal' , description : 'NDX:Try LocalGov Drupal - AI-enhanced CMS for UK councils' } ,
3239 { name : 'simply-readable' , description : 'NDX:Try Simply Readable - Document Translation & Easy Read, built by Swindon Borough Council' } ,
3340 { name : 'localgov-ims' , description : 'NDX:Try LocalGov IMS - Income Management System with GOV.UK Pay' , parameterKeys : [ 'GovUkPayApiKey' ] } ,
34- { name : 'minute' , description : 'Minute AI - Meeting transcription and AI-powered minute generation' } ,
35- { name : 'fixmystreet' , description : 'NDX:Try FixMyStreet - Citizen problem reporting platform for UK councils' } ,
36- { name : 'paperless-ngx' , description : 'NDX:Try Paperless-ngx - Document archive with OCR, full-text search and Bedrock AI' } ,
41+ // TODO(orphan-import): minute, fixmystreet, paperless-ngx have ACTIVE
42+ // StackSets in the hub account NOT owned by IsbHubStack. CDK create fails
43+ // with AlreadyExists. Re-enable once imported via
44+ // `cloudformation create-change-set --change-set-type IMPORT`.
45+ // { name: 'minute', description: 'Minute AI - Meeting transcription and AI-powered minute generation' },
46+ // { name: 'fixmystreet', description: 'NDX:Try FixMyStreet - Citizen problem reporting platform for UK councils' },
47+ // { name: 'paperless-ngx', description: 'NDX:Try Paperless-ngx - Document archive with OCR, full-text search and Bedrock AI' },
48+ { name : 'ai-contact-centre' , description : 'NDX:Try AI Contact Centre - Amazon Connect with Lex, Bedrock RAG, multimodal photo describe, multi-intent triage, and multilingual support' , samStyle : true } ,
3749 { name : 'all-demo' , description : 'NDX:Try All Demo - Deploys all 7 scenarios as nested stacks' } ,
3850] ;
3951
@@ -68,23 +80,50 @@ export class IsbHubStack extends cdk.Stack {
6880
6981 // ========================================================================
7082 // TEMPLATE UPLOADS — one BucketDeployment per scenario
83+ //
84+ // Scenarios with no local template.yaml are skipped with a warning. CI is
85+ // expected to synthesize/package every scenario before this stack runs, so
86+ // a missing template in CI is a build error; locally it lets developers
87+ // iterate on hub config without first building every scenario.
7188 // ========================================================================
7289 const deployments : Record < string , s3deploy . BucketDeployment > = { } ;
90+ const skipped : string [ ] = [ ] ;
7391
7492 for ( const scenario of SCENARIOS ) {
7593 const pascalName = scenario . name
7694 . split ( '-' )
7795 . map ( s => s . charAt ( 0 ) . toUpperCase ( ) + s . slice ( 1 ) )
7896 . join ( '' ) ;
7997
98+ // SAM-style scenarios source from dist/ (produced by `sam package`); others
99+ // from the scenario root.
100+ const sourceDir = scenario . samStyle
101+ ? path . join ( __dirname , '..' , '..' , 'scenarios' , scenario . name , 'dist' )
102+ : path . join ( __dirname , '..' , '..' , 'scenarios' , scenario . name ) ;
103+
104+ const templatePath = path . join ( sourceDir , 'template.yaml' ) ;
105+ if ( ! fs . existsSync ( templatePath ) ) {
106+ if ( process . env . CI === 'true' ) {
107+ throw new Error ( `Missing template for scenario '${ scenario . name } ': ${ templatePath } . CI must synthesize this scenario before the hub stack runs.` ) ;
108+ }
109+ console . warn ( `[isb-hub] WARN: skipping scenario '${ scenario . name } ' — no template at ${ templatePath } (run the synth step locally first if you want it deployed)` ) ;
110+ skipped . push ( scenario . name ) ;
111+ continue ;
112+ }
113+
80114 const deployment = new s3deploy . BucketDeployment ( this , `${ pascalName } Templates` , {
81115 sources : [
82- s3deploy . Source . asset ( path . join ( __dirname , '..' , '..' , 'scenarios' , scenario . name ) , {
116+ s3deploy . Source . asset ( sourceDir , {
83117 exclude : [ '*' , '!template.yaml' ] ,
84118 } ) ,
85119 ] ,
86120 destinationBucket : bucket ,
87121 destinationKeyPrefix : `scenarios/${ scenario . name } ` ,
122+ // Don't delete sibling objects under the same prefix. SAM-style
123+ // scenarios upload Lambda zips to scenarios/<name>/assets/ via
124+ // `sam package` BEFORE this BucketDeployment runs; default prune:true
125+ // would `aws s3 sync --delete` and remove them.
126+ prune : scenario . samStyle ? false : undefined ,
88127 } ) ;
89128
90129 // Grant permissions on imported bucket (CDK can't auto-grant on imported resources)
@@ -174,12 +213,16 @@ export class IsbHubStack extends cdk.Stack {
174213 // STACKSETS — one per scenario, no stack instances (ISB manages those)
175214 // ========================================================================
176215 for ( const scenario of SCENARIOS ) {
216+ if ( skipped . includes ( scenario . name ) ) continue ;
217+
177218 const pascalName = scenario . name
178219 . split ( '-' )
179220 . map ( s => s . charAt ( 0 ) . toUpperCase ( ) + s . slice ( 1 ) )
180221 . join ( '' ) ;
181222
182- const templatePath = path . join ( __dirname , '..' , '..' , 'scenarios' , scenario . name , 'template.yaml' ) ;
223+ const templatePath = scenario . samStyle
224+ ? path . join ( __dirname , '..' , '..' , 'scenarios' , scenario . name , 'dist' , 'template.yaml' )
225+ : path . join ( __dirname , '..' , '..' , 'scenarios' , scenario . name , 'template.yaml' ) ;
183226 const templateContent = fs . readFileSync ( templatePath , 'utf8' ) ;
184227 const contentHash = crypto . createHash ( 'sha256' ) . update ( templateContent ) . digest ( 'hex' ) . substring ( 0 , 16 ) ;
185228
0 commit comments