import * as fs from 'fs'; import * as cdk from 'aws-cdk-lib'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; import { Construct } from 'constructs'; import { getConfig } from './config'; export class ServiceStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const config = getConfig(); // Content bucket const siteBucket = s3.Bucket.fromBucketName(this, 'SiteBucket', cdk.Fn.importValue(config.importBucket)); const sources = [s3deploy.Source.asset('../dist')]; if (fs.existsSync('../storybook-static')) { sources.push(s3deploy.Source.asset('../storybook-static')); } // Atomic per-deploy directory: // - tagged builds reuse $CI_COMMIT_TAG so the dir matches the release tag // - otherwise a sortable canary-- id const timestamp = new Date().toISOString().replace(/\.\d+Z$/, '').replace(/:/g, ''); const deployId = process.env.CI_COMMIT_TAG || `canary-${timestamp}-${process.env.CI_COMMIT_SHORT_SHA || 'local'}`; const keyPrefix = `${config.projectName}/${deployId}/`; // Surfaced in the CI `cdk deploy` logs so the atomic deploy target is easy // to trace end-to-end (matches the deploy-id the import-map-updater lambda // will log when bundle.js lands). console.log( `[atomic-deploy] service=${config.projectName} deployId=${deployId} -> s3:///${keyPrefix} (bundle.js uploaded last)` ); // Long term immutable cache since bundles use versioning to bust the cache const cacheControl = [s3deploy.CacheControl.fromString('max-age=31536000,immutable')]; // bundle.js is uploaded LAST: the import-map-updater Lambda triggers on its // OBJECT_CREATED event. If it fired before chunks/sourcemaps were in S3 the // flipped import map would 404 on lazy-loaded modules. const assets = new s3deploy.BucketDeployment(this, 'DeployAssets', { sources, destinationBucket: siteBucket, destinationKeyPrefix: keyPrefix, exclude: ['bundle.js'], prune: false, cacheControl, }); const entry = new s3deploy.BucketDeployment(this, 'DeployEntry', { sources: [s3deploy.Source.asset('../dist')], destinationBucket: siteBucket, destinationKeyPrefix: keyPrefix, exclude: ['*'], include: ['bundle.js'], prune: false, cacheControl, }); entry.node.addDependency(assets); } }