import { Tag } from "aws-sdk/clients/s3" ; import { createReadStream } from "fs" ; import { lookup } from "mime-types" ; import { prompt } from "inquirer" ; import { s3 } from "./aws-services" ; import { readRecursively } from "./fs-helper" ; import { logger } from "./logger" ; export const doesS3BucketExists = async ( bucketName ) => { try { logger . info ( `[S3] 🔍 Looking for bucket "${ bucketName }"...` ) ; await s3 . headBucket ( { Bucket : bucketName } ) . promise ( ) ; } catch ( error ) { if ( error . statusCode === 404 ) { logger . info ( `[S3] 😬 Bucket "${ bucketName }" not found...` ) ; return false ; } throw error ; } logger . info ( `[S3] 🔍 Bucket "${ bucketName }" found` ) ; return true ; } ; export const createBucket = async ( bucketName ) => { logger . info ( `[S3] ✏️ Creating "${ bucketName }" bucket...` ) ; try { await s3 . createBucket ( { Bucket : bucketName } ) . promise ( ) ; } catch ( error ) { if ( error . statusCode === 409 ) { throw new Error ( "[S3] It seems that a bucket already exists but in an unsupported region... You should delete it first." ) ; } throw error ; } } ; export const confirmBucketManagement = async ( bucketName ) => { logger . info ( `[S3] 🔍 Checking that tag "${ identifyingTag . Key }:${ identifyingTag . Value }" exists on bucket "${ bucketName }"...` ) ; try { const { TagSet } = await s3 . getBucketTagging ( { Bucket : bucketName } ) . promise ( ) ; const tag = TagSet . find ( _tag => _tag . Key === identifyingTag . Key && _tag . Value === identifyingTag . Value ) ; if ( tag ) { logger . info ( `[S3] 👍 Tag "${ identifyingTag . Key }:${ identifyingTag . Value }" found` ) ; return true ; } } catch ( error ) { if ( error . statusCode !== 404 ) { throw error ; } } const { continueUpdate } = await prompt ( [ { type : "confirm" , name : "continueUpdate" , message : `[S3] Bucket "${ bucketName }" is not yet managed by aws-spa. Would you like it to be modified (public access & website config) & managed by aws-spa?` , default : false } ] ) ; if ( continueUpdate ) { return true ; } throw new Error ( "You can use another domain name or delete the S3 bucket..." ) ; } ; export const tagBucket = async ( bucketName ) => { logger . info ( `[S3] ✏️ Tagging "${ bucketName }" bucket with "${ identifyingTag . Key }:${ identifyingTag . Value }"...` ) ; await s3 . putBucketTagging ( { Bucket : bucketName , Tagging : { TagSet : [ identifyingTag ] } } ) . promise ( ) ; } ; export const setBucketWebsite = ( bucketName ) => { logger . info ( `[S3] ✏️ Set bucket website with IndexDocument: "index.html" & ErrorDocument: "index.html" to "${ bucketName }"...` ) ; return s3 . putBucketWebsite ( { Bucket : bucketName , WebsiteConfiguration : { ErrorDocument : { Key : "index.html" } , IndexDocument : { Suffix : "index.html" } } } ) . promise ( ) ; } ; export const setBucketPolicy = ( bucketName ) => { logger . info ( `[S3] ✏️ Allow public read to "${ bucketName }"...` ) ; return s3 . putBucketPolicy ( { Bucket : bucketName , Policy : JSON . stringify ( { Statement : [ { Sid : "AllowPublicRead" , Effect : "Allow" , Principal : { AWS : "*" } , Action : "s3:GetObject" , Resource : `arn:aws:s3:::${ bucketName }/*` } ] } ) } ) . promise ( ) ; } ; export const identifyingTag = { Key : "managed-by-aws-spa" , Value : "v1" } ; export const syncToS3 = function ( folder , bucketName , cacheBustedPrefix , subfolder ? ) { logger . info ( `[S3] ✏️ Uploading "${ folder }" folder on "${ bucketName }"...` ) ; const filesToUpload = readRecursively ( folder ) ; return Promise . all ( filesToUpload . map ( file => { const filenameParts = file . split ( "." ) ; const key = file . replace ( `${ folder }/` , "" ) ; const prefix = subfolder ? `${ subfolder }/` : "" ; return s3 . putObject ( { Bucket : bucketName , Key : `${ prefix }${ key }` , Body : createReadStream ( file ) , CacheControl : getCacheControl ( key , cacheBustedPrefix ) , ContentType : lookup ( filenameParts [ filenameParts . length - 1 ] ) || "application/octet-stream" } ) . promise ( ) ; } ) ) ; } ; const getCacheControl = ( filename , cacheBustedPrefix ) => { if ( filename === "index.html" ) { return "public, must-revalidate, proxy-revalidate, max-age=0" ; } if ( cacheBustedPrefix && filename . startsWith ( cacheBustedPrefix ) ) { return "max-age=31536000" ; } return undefined ; } ;