@stacksjs/ts-cloud 0.5.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/cli.js CHANGED
@@ -3639,7 +3639,7 @@ export const queue = __handlers.queue
3639
3639
  export const cli = __handlers.cli
3640
3640
  `}function tR(){return O9(pR(iR(import.meta.url)),"runtime","adapter.ts")}function FH($,J,Y){for(let Q of $??[])Y?.(`build: ${Q}`),fR(Q,{stdio:"inherit",cwd:J})}function CY($){return gR("sha256").update($).digest("hex")}function RZ($,J,Y){return`deployments/${$}/${J}/${Y}.zip`}async function N9($){let J=rR($.projectRoot??process.cwd()),{app:Y}=$;if(!$.skipBuild)FH(Y.build,J,$.onStep);let Q=Y.entry;if(!Q)throw Error("serverless app: `entry` is required to package a Node/Bun application");let G=nR(Q)?Q:O9(J,Q),Z=dR(O9(mR(),"tscloud-pkg-"));try{uR(tR(),O9(Z,"adapter.ts"));let W=O9(Z,"bootstrap.ts");lR(W,XH({entryImport:G,adapterImport:"./adapter"})),$.onStep?.("bundling application");let U=await Bun.build({entrypoints:[W],target:Y.kind==="bun"?"bun":"node",format:"esm",minify:!1,sourcemap:"none"});if(!U.success){let F=U.logs.map((B)=>String(B)).join(`
3641
3641
  `);throw Error(`serverless app bundle failed:
3642
- ${F}`)}let z=U.outputs[0],_=Buffer.from(await z.arrayBuffer()),H="index",X=E8([{name:`${H}.mjs`,data:_}]);return{zip:X,bundle:_,sha256:CY(X),handlerFile:H,handlers:{http:Y.handlers?.http??`${H}.http`,queue:Y.handlers?.queue??`${H}.queue`,cli:Y.handlers?.cli??`${H}.cli`},bundleBytes:_.length}}finally{cR(Z,{recursive:!0,force:!0})}}function aR($){if(!$)return"22";let J=String($).match(/(\d+)/);return J?J[1]:"22"}function IY($){let J=$.kind??"node";if(J==="php")return{lambdaRuntime:"provided.al2023",kind:"php",version:$.phpVersion??"8.3",usesLayer:!0,layerKind:"php",layerEnvVar:"TSCLOUD_PHP_LAYER_ARN"};if(J==="bun")return{lambdaRuntime:"provided.al2023",kind:"bun",version:$.runtimeVersion??"latest",usesLayer:!0,layerKind:"bun",layerEnvVar:"TSCLOUD_BUN_LAYER_ARN"};let Y=aR($.runtimeVersion);if($.runtime!=="provided.al2023"&&OH.includes(Y))return{lambdaRuntime:$.runtime&&$.runtime.startsWith("nodejs")?$.runtime:`nodejs${Y}.x`,kind:"node",version:Y,usesLayer:!1};return{lambdaRuntime:"provided.al2023",kind:"node",version:Y,usesLayer:!0,layerKind:"node",layerEnvVar:"TSCLOUD_NODE_LAYER_ARN"}}function BH($,J,Y){if($.queues===!1)return[];if($.queues===void 0||$.queues===!0)return[`${J}-${Y}-default`];return $.queues.map((Q)=>{let G=typeof Q==="string"?Q:Object.keys(Q)[0];return`${J}-${Y}-${G}`})}function DZ($){let{app:J,environment:Y,handlers:Q}=$,G=$.config.project.slug,Z=IY(J).lambdaRuntime,W=J.architecture??"x86_64",U=$.config.project.region,z={http:`${G}-${Y}-http`,queue:`${G}-${Y}-queue`,cli:`${G}-${Y}-cli`},_=BH(J,G,Y),H=_.length>0,X=J.packaging==="image",F=(J.scheduler??"on")!=="off",B=(J.cache?.driver??"dynamodb")==="dynamodb",K=Boolean(J.assets),V=RY(G,Y),L=J.tmpStorage??512,A={},E={},q=[{PolicyName:"tscloud-serverless-app",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],Resource:j.sub("arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*")},{Effect:"Allow",Action:["lambda:InvokeFunction"],Resource:j.sub(`arn:aws:lambda:\${AWS::Region}:\${AWS::AccountId}:function:${G}-${Y}-*`)},{Effect:"Allow",Action:["secretsmanager:GetSecretValue","ssm:GetParameter","ssm:GetParameters","ssm:GetParametersByPath"],Resource:[j.sub(`arn:aws:secretsmanager:\${AWS::Region}:\${AWS::AccountId}:secret:${G}/${Y}/*`),j.sub(`arn:aws:ssm:\${AWS::Region}:\${AWS::AccountId}:parameter/${G}/${Y}/*`)]},{Effect:"Allow",Action:["s3:GetObject","s3:PutObject","s3:DeleteObject","s3:ListBucket"],Resource:[j.sub(`arn:aws:s3:::${V}`),j.sub(`arn:aws:s3:::${V}/*`),...J.storage?.bucket?[j.sub(`arn:aws:s3:::${J.storage.bucket}`),j.sub(`arn:aws:s3:::${J.storage.bucket}/*`)]:[]]}]}}];if(H)q[0].PolicyDocument.Statement.push({Effect:"Allow",Action:["sqs:SendMessage","sqs:ReceiveMessage","sqs:DeleteMessage","sqs:GetQueueAttributes","sqs:GetQueueUrl"],Resource:j.sub(`arn:aws:sqs:\${AWS::Region}:\${AWS::AccountId}:${G}-${Y}-*`)});if(B)q[0].PolicyDocument.Statement.push({Effect:"Allow",Action:["dynamodb:GetItem","dynamodb:PutItem","dynamodb:UpdateItem","dynamodb:DeleteItem","dynamodb:Query","dynamodb:Scan","dynamodb:BatchGetItem","dynamodb:BatchWriteItem"],Resource:j.sub(`arn:aws:dynamodb:\${AWS::Region}:\${AWS::AccountId}:table/${G}-${Y}-cache*`)});let M=["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"];if(J.vpc?.subnets?.length)M.push("arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole");A.AppRole={Type:"AWS::IAM::Role",Properties:{RoleName:`${G}-${Y}-app-role`,AssumeRolePolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"lambda.amazonaws.com"},Action:"sts:AssumeRole"}]},ManagedPolicyArns:M,Policies:q}};let w=(a)=>({TSCLOUD_LAMBDA_MODE:a,TSCLOUD_ENV:Y,...J.octane?{TSCLOUD_OCTANE:"1"}:{},...J.scheduler==="sub-minute"?{TSCLOUD_SCHEDULER:"sub-minute"}:{},...B?{TSCLOUD_CACHE_TABLE:`${G}-${Y}-cache`}:{},...H?{TSCLOUD_QUEUE:_[0]}:{},...J.env??{}}),T=Boolean(J.efs),R=typeof J.efs==="object"?J.efs:{},I=R.mountPath??"/mnt/local",N=T&&!R.accessPointArn,C=R.accessPointArn??(N?j.getAtt("EfsAccessPoint","Arn"):void 0),x=J.vpc?.subnets??[],k=x.length>0,h=J.cache?.driver==="elasticache"||J.database?.connection==="aurora-serverless"||Boolean(J.rdsProxy)||T;if(h&&!k)throw Error("serverless app: elasticache / aurora-serverless / rdsProxy / efs require app.vpc.subnets (private subnets) to be set.");let p=k?{VpcConfig:{SubnetIds:x,SecurityGroupIds:[...J.vpc?.securityGroups??[],...h?[j.getAtt("DataSecurityGroup","GroupId")]:[]]}}:{},g=N?x.map((a,H0)=>`EfsMountTarget${H0}`):[],c=T?{FileSystemConfigs:[{Arn:C,LocalMountPath:I}]}:{};if(T)q[0].PolicyDocument.Statement.push({Effect:"Allow",Action:["elasticfilesystem:ClientMount","elasticfilesystem:ClientWrite","elasticfilesystem:ClientRootAccess","elasticfilesystem:DescribeMountTargets"],Resource:"*"});function $0(a,H0,j0,K0,G1,Y2,Q2,TQ=L){A[`${a}LogGroup`]={Type:"AWS::Logs::LogGroup",Properties:{LogGroupName:`/aws/lambda/${H0}`,RetentionInDays:14}};let RQ=X?{PackageType:"Image",Code:{ImageUri:j.ref("ImageUri")},...J.kind==="php"?{}:{ImageConfig:{Command:[j0]}}}:{Runtime:Z,Handler:j0,Code:{S3Bucket:j.ref("ArtifactBucket"),S3Key:j.ref("ArtifactKey")},...$.runtimeLayers?.length?{Layers:$.runtimeLayers}:{}};A[a]={Type:"AWS::Lambda::Function",DependsOn:[`${a}LogGroup`,...g],Properties:{FunctionName:H0,Architectures:[W],MemorySize:G1,Timeout:Y2,Role:j.getAtt("AppRole","Arn"),Environment:{Variables:w(K0)},EphemeralStorage:{Size:TQ},...RQ,...Q2!==void 0?{ReservedConcurrentExecutions:Q2}:{},...p,...c}}}if($0("HttpFunction",z.http,Q.http,"http",J.memory??1024,J.timeout??28,J.concurrency,L),$0("CliFunction",z.cli,Q.cli,"cli",J.cliMemory??1024,J.cliTimeout??900,void 0,J.cliTmpStorage??L),H)$0("QueueFunction",z.queue,Q.queue,"queue",J.queueMemory??1024,J.queueTimeout??120,void 0,J.queueTmpStorage??L);A.HttpApi={Type:"AWS::ApiGatewayV2::Api",Properties:{Name:`${G}-${Y}`,ProtocolType:"HTTP"}},A.HttpIntegration={Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:j.ref("HttpApi"),IntegrationType:"AWS_PROXY",IntegrationUri:j.getAtt("HttpFunction","Arn"),PayloadFormatVersion:"2.0"}},A.HttpRoute={Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:j.ref("HttpApi"),RouteKey:"$default",Target:j.join("/",["integrations",j.ref("HttpIntegration")])}},A.HttpStage={Type:"AWS::ApiGatewayV2::Stage",Properties:{ApiId:j.ref("HttpApi"),StageName:"$default",AutoDeploy:!0}},A.HttpPermission={Type:"AWS::Lambda::Permission",Properties:{FunctionName:j.ref("HttpFunction"),Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:j.sub("arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${HttpApi}/*/*")}},E.HttpApiEndpoint={Description:"HTTP API endpoint",Value:j.getAtt("HttpApi","ApiEndpoint")},E.HttpApiId={Description:"HTTP API id",Value:j.ref("HttpApi")};let i=(Array.isArray(J.domain)?J.domain:J.domain?[J.domain]:[]).filter(Boolean);if(i.length){if(!J.certificateArn&&!J.hostedZoneId)throw Error("serverless app: a custom `domain` needs either `certificateArn` (pre-issued, regional) or `hostedZoneId` (to auto-issue + validate an ACM cert).");let a=J.certificateArn;if(!a)A.HttpCertificate={Type:"AWS::CertificateManager::Certificate",Properties:{DomainName:i[0],...i.length>1?{SubjectAlternativeNames:i.slice(1)}:{},ValidationMethod:"DNS",DomainValidationOptions:i.map((H0)=>({DomainName:H0,HostedZoneId:J.hostedZoneId}))}},a=j.ref("HttpCertificate");i.forEach((H0,j0)=>{let K0=`HttpDomain${j0}`;if(A[K0]={Type:"AWS::ApiGatewayV2::DomainName",Properties:{DomainName:H0,DomainNameConfigurations:[{CertificateArn:a,EndpointType:"REGIONAL"}]}},A[`HttpApiMapping${j0}`]={Type:"AWS::ApiGatewayV2::ApiMapping",DependsOn:["HttpStage"],Properties:{ApiId:j.ref("HttpApi"),DomainName:j.ref(K0),Stage:"$default"}},J.hostedZoneId)A[`HttpDomainRecord${j0}`]={Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:J.hostedZoneId,Name:H0,Type:"A",AliasTarget:{DNSName:j.getAtt(K0,"RegionalDomainName"),HostedZoneId:j.getAtt(K0,"RegionalHostedZoneId")}}};E[`CustomDomain${j0}`]={Description:`Custom domain ${H0}`,Value:H0},E[`CustomDomainTarget${j0}`]={Description:`Point ${H0} (CNAME/alias) at this APIGW regional domain`,Value:j.getAtt(K0,"RegionalDomainName")}})}if(H)A.AppQueueDlq={Type:"AWS::SQS::Queue",Properties:{QueueName:`${G}-${Y}-dlq`,MessageRetentionPeriod:1209600}},_.forEach((a,H0)=>{let j0=`AppQueue${H0}`;A[j0]={Type:"AWS::SQS::Queue",Properties:{QueueName:a,VisibilityTimeout:Math.max(J.queueTimeout??120,J.queueTimeout??120),RedrivePolicy:{deadLetterTargetArn:j.getAtt("AppQueueDlq","Arn"),maxReceiveCount:J.queueTries??3}}},A[`${j0}Mapping`]={Type:"AWS::Lambda::EventSourceMapping",Properties:{EventSourceArn:j.getAtt(j0,"Arn"),FunctionName:j.ref("QueueFunction"),BatchSize:1,FunctionResponseTypes:["ReportBatchItemFailures"],...J.queueConcurrency?{ScalingConfig:{MaximumConcurrency:Math.max(2,J.queueConcurrency)}}:{}}},E[`QueueUrl${H0}`]={Description:`Queue URL: ${a}`,Value:j.ref(j0)}});if(F)A.SchedulerRule={Type:"AWS::Events::Rule",Properties:{Name:`${G}-${Y}-scheduler`,ScheduleExpression:"rate(1 minute)",State:"ENABLED",Targets:[{Id:"cli",Arn:j.getAtt("CliFunction","Arn"),Input:JSON.stringify({command:"schedule:run"})}]}},A.SchedulerPermission={Type:"AWS::Lambda::Permission",Properties:{FunctionName:j.ref("CliFunction"),Action:"lambda:InvokeFunction",Principal:"events.amazonaws.com",SourceArn:j.getAtt("SchedulerRule","Arn")}};if(J.warm&&J.warm>0){let H0=Math.ceil(J.warm/5),j0=0;for(let K0=0;K0<H0;K0++){let G1=Math.min(5,J.warm-j0);A[`WarmerRule${K0}`]={Type:"AWS::Events::Rule",Properties:{Name:`${G}-${Y}-warmer-${K0}`,ScheduleExpression:"rate(5 minutes)",State:"ENABLED",Targets:Array.from({length:G1},(Y2,Q2)=>({Id:`warm-${K0}-${Q2}`,Arn:j.getAtt("HttpFunction","Arn"),Input:JSON.stringify({warmer:!0})}))}},j0+=G1}A.WarmerPermission={Type:"AWS::Lambda::Permission",Properties:{FunctionName:j.ref("HttpFunction"),Action:"lambda:InvokeFunction",Principal:"events.amazonaws.com",SourceArn:j.sub(`arn:aws:events:\${AWS::Region}:\${AWS::AccountId}:rule/${G}-${Y}-warmer-*`)}}}if(B)A.CacheTable={Type:"AWS::DynamoDB::Table",Properties:{TableName:`${G}-${Y}-cache`,BillingMode:"PAY_PER_REQUEST",AttributeDefinitions:[{AttributeName:"key",AttributeType:"S"}],KeySchema:[{AttributeName:"key",KeyType:"HASH"}],TimeToLiveSpecification:{AttributeName:"expires_at",Enabled:!0}}},E.CacheTableName={Description:"DynamoDB cache table",Value:j.ref("CacheTable")};if(K){if(A.AssetsBucket={Type:"AWS::S3::Bucket",Properties:{BucketName:V,PublicAccessBlockConfiguration:{BlockPublicAcls:!0,BlockPublicPolicy:!0,IgnorePublicAcls:!0,RestrictPublicBuckets:!0}}},A.AssetsOAC={Type:"AWS::CloudFront::OriginAccessControl",Properties:{OriginAccessControlConfig:{Name:`${G}-${Y}-assets-oac`,OriginAccessControlOriginType:"s3",SigningBehavior:"always",SigningProtocol:"sigv4"}}},A.AssetsDistribution={Type:"AWS::CloudFront::Distribution",Properties:{DistributionConfig:{Enabled:!0,DefaultCacheBehavior:{TargetOriginId:"assets",ViewerProtocolPolicy:"redirect-to-https",Compress:!0,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6"},Origins:[{Id:"assets",DomainName:j.getAtt("AssetsBucket","RegionalDomainName"),OriginAccessControlId:j.ref("AssetsOAC"),S3OriginConfig:{OriginAccessIdentity:""}}],...J.assetDomain?{Aliases:[J.assetDomain],ViewerCertificate:{AcmCertificateArn:J.assetCertificateArn,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}}:{}}}},J.assetDomain){if(!J.assetCertificateArn)throw Error("serverless app: `assetDomain` requires `assetCertificateArn` (a us-east-1 ACM cert — CloudFront only accepts certs from us-east-1).");if(J.hostedZoneId)A.AssetsDomainRecord={Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:J.hostedZoneId,Name:J.assetDomain,Type:"A",AliasTarget:{DNSName:j.getAtt("AssetsDistribution","DomainName"),HostedZoneId:"Z2FDTNDATAQYW2"}}};E.AssetDomain={Description:"Custom asset CDN host",Value:J.assetDomain}}A.AssetsBucketPolicy={Type:"AWS::S3::BucketPolicy",Properties:{Bucket:j.ref("AssetsBucket"),PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:j.sub(`arn:aws:s3:::${V}/*`),Condition:{StringEquals:{"AWS:SourceArn":j.sub("arn:aws:cloudfront::${AWS::AccountId}:distribution/${AssetsDistribution}")}}}]}}},E.AssetsBucketName={Description:"Assets bucket",Value:j.ref("AssetsBucket")},E.AssetsCdnDomain={Description:"Assets CloudFront domain",Value:j.getAtt("AssetsDistribution","DomainName")}}if(J.firewall?.enabled){let a=[],H0=0;if(J.firewall.rateLimit)a.push({Name:"rate-limit",Priority:H0++,Action:{Block:{}},Statement:{RateBasedStatement:{Limit:J.firewall.rateLimit,AggregateKeyType:"IP"}},VisibilityConfig:{SampledRequestsEnabled:!0,CloudWatchMetricsEnabled:!0,MetricName:`${G}-${Y}-rate`}});let j0={sqlInjection:"AWSManagedRulesSQLiRuleSet",xss:"AWSManagedRulesCommonRuleSet",common:"AWSManagedRulesCommonRuleSet",botControl:"AWSManagedRulesBotControlRuleSet",ipReputation:"AWSManagedRulesAmazonIpReputationList"};for(let K0 of J.firewall.rules??[]){let G1=j0[K0];if(!G1)continue;a.push({Name:`managed-${K0}`,Priority:H0++,OverrideAction:{None:{}},Statement:{ManagedRuleGroupStatement:{VendorName:"AWS",Name:G1}},VisibilityConfig:{SampledRequestsEnabled:!0,CloudWatchMetricsEnabled:!0,MetricName:`${G}-${Y}-${K0}`}})}A.WebAcl={Type:"AWS::WAFv2::WebACL",Properties:{Name:`${G}-${Y}-waf`,Scope:"REGIONAL",DefaultAction:{Allow:{}},Rules:a,VisibilityConfig:{SampledRequestsEnabled:!0,CloudWatchMetricsEnabled:!0,MetricName:`${G}-${Y}-waf`}}},A.WebAclAssociation={Type:"AWS::WAFv2::WebACLAssociation",DependsOn:["HttpStage"],Properties:{ResourceArn:j.sub("arn:aws:apigateway:${AWS::Region}::/apis/${HttpApi}/stages/$default"),WebACLArn:j.getAtt("WebAcl","Arn")}}}if(k&&h){if(!J.vpc?.id)throw Error("serverless app: data services (elasticache / aurora-serverless / rdsProxy / efs) need app.vpc.id (the VPC id) so the managed security group is created in the right VPC.");A.DataSecurityGroup={Type:"AWS::EC2::SecurityGroup",Properties:{VpcId:J.vpc.id,GroupDescription:`${G}-${Y} serverless data access`,SecurityGroupIngress:[{IpProtocol:"-1",CidrIp:"10.0.0.0/8"}]}}}if(N)A.EfsFileSystem={Type:"AWS::EFS::FileSystem",Properties:{Encrypted:!0,FileSystemTags:[{Key:"Name",Value:`${G}-${Y}-efs`}]}},x.forEach((a,H0)=>{A[`EfsMountTarget${H0}`]={Type:"AWS::EFS::MountTarget",Properties:{FileSystemId:j.ref("EfsFileSystem"),SubnetId:a,SecurityGroups:[j.getAtt("DataSecurityGroup","GroupId")]}}}),A.EfsAccessPoint={Type:"AWS::EFS::AccessPoint",Properties:{FileSystemId:j.ref("EfsFileSystem"),PosixUser:{Uid:1001,Gid:1001},RootDirectory:{Path:"/lambda",CreationInfo:{OwnerUid:1001,OwnerGid:1001,Permissions:"0755"}}}},E.EfsFileSystemId={Description:"EFS file system id",Value:j.ref("EfsFileSystem")};if(J.cache?.driver==="elasticache")A.CacheSubnetGroup={Type:"AWS::ElastiCache::SubnetGroup",Properties:{Description:`${G}-${Y} cache subnets`,SubnetIds:x}},A.CacheCluster={Type:"AWS::ElastiCache::ReplicationGroup",Properties:{ReplicationGroupId:`${G}-${Y}-cache`,ReplicationGroupDescription:`${G}-${Y} redis`,Engine:"redis",CacheNodeType:"cache.t4g.micro",NumCacheClusters:1,AutomaticFailoverEnabled:!1,CacheSubnetGroupName:j.ref("CacheSubnetGroup"),SecurityGroupIds:[j.getAtt("DataSecurityGroup","GroupId")],TransitEncryptionEnabled:!1}},E.CacheEndpoint={Description:"Redis primary endpoint",Value:j.getAtt("CacheCluster","PrimaryEndPoint.Address")};if(J.database?.connection==="aurora-serverless")A.DbSubnetGroup={Type:"AWS::RDS::DBSubnetGroup",Properties:{DBSubnetGroupDescription:`${G}-${Y} db subnets`,SubnetIds:x}},A.DbSecret={Type:"AWS::SecretsManager::Secret",Properties:{Name:`${G}/${Y}/db`,GenerateSecretString:{SecretStringTemplate:JSON.stringify({username:"app"}),GenerateStringKey:"password",PasswordLength:32,ExcludePunctuation:!0}}},A.DbCluster={Type:"AWS::RDS::DBCluster",Properties:{Engine:"aurora-mysql",EngineMode:"provisioned",DBClusterIdentifier:`${G}-${Y}-db`,DatabaseName:"app",MasterUsername:j.sub("{{resolve:secretsmanager:${DbSecret}:SecretString:username}}"),MasterUserPassword:j.sub("{{resolve:secretsmanager:${DbSecret}:SecretString:password}}"),ServerlessV2ScalingConfiguration:{MinCapacity:0.5,MaxCapacity:4},DBSubnetGroupName:j.ref("DbSubnetGroup"),VpcSecurityGroupIds:[j.getAtt("DataSecurityGroup","GroupId")]}},A.DbInstance={Type:"AWS::RDS::DBInstance",Properties:{Engine:"aurora-mysql",DBInstanceClass:"db.serverless",DBClusterIdentifier:j.ref("DbCluster")}},E.DbEndpoint={Description:"Aurora cluster endpoint",Value:j.getAtt("DbCluster","Endpoint.Address")};if(J.rdsProxy&&J.database?.connection==="aurora-serverless")A.DbProxyRole={Type:"AWS::IAM::Role",Properties:{AssumeRolePolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"rds.amazonaws.com"},Action:"sts:AssumeRole"}]},Policies:[{PolicyName:"read-db-secret",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["secretsmanager:GetSecretValue"],Resource:j.ref("DbSecret")}]}}]}},A.DbProxy={Type:"AWS::RDS::DBProxy",Properties:{DBProxyName:typeof J.rdsProxy==="object"&&J.rdsProxy.name?J.rdsProxy.name:`${G}-${Y}-proxy`,EngineFamily:"MYSQL",RoleArn:j.getAtt("DbProxyRole","Arn"),Auth:[{AuthScheme:"SECRETS",SecretArn:j.ref("DbSecret"),IAMAuth:"DISABLED"}],VpcSubnetIds:x,VpcSecurityGroupIds:[j.getAtt("DataSecurityGroup","GroupId")],RequireTLS:!1}},A.DbProxyTargetGroup={Type:"AWS::RDS::DBProxyTargetGroup",DependsOn:["DbInstance"],Properties:{DBProxyName:j.ref("DbProxy"),TargetGroupName:"default",DBClusterIdentifiers:[j.ref("DbCluster")]}},E.DbProxyEndpoint={Description:"RDS Proxy endpoint",Value:j.getAtt("DbProxy","Endpoint")};if(E.HttpFunctionName={Description:"HTTP function name",Value:j.ref("HttpFunction")},E.CliFunctionName={Description:"CLI function name",Value:j.ref("CliFunction")},H)E.QueueFunctionName={Description:"Queue function name",Value:j.ref("QueueFunction")};let t={AWSTemplateFormatVersion:"2010-09-09",Description:`Serverless application for ${$.config.project.name} (${G}-${Y})`,Parameters:X?{ImageUri:{Type:"String",Description:"ECR image URI of the deployment artifact"}}:{ArtifactBucket:{Type:"String",Description:"S3 bucket holding the deployment artifact"},ArtifactKey:{Type:"String",Description:"S3 key of the deployment artifact (zip)"}},Resources:A,Outputs:E},G0={};for(let a of Object.values(A))G0[a.Type]=(G0[a.Type]??0)+1;return{template:t,functionNames:z,queueNames:_,resourceSummary:G0}}function KH($){let J=`php${$.replace(".","")}`;return VH.map((Y)=>`${J}-php-${Y}`)}function NZ($,J){let Y=`php${$.replace(".","")}`,Q=KH($).join(" \\\n "),G=`/opt/remi/${Y}/root`,Z=`/etc/opt/remi/${Y}`;return`${J?`FROM almalinux:9 AS ${J}`:"FROM almalinux:9"}
3642
+ ${F}`)}let z=U.outputs[0],_=Buffer.from(await z.arrayBuffer()),H="index",X=E8([{name:`${H}.mjs`,data:_}]);return{zip:X,bundle:_,sha256:CY(X),handlerFile:H,handlers:{http:Y.handlers?.http??`${H}.http`,queue:Y.handlers?.queue??`${H}.queue`,cli:Y.handlers?.cli??`${H}.cli`},bundleBytes:_.length}}finally{cR(Z,{recursive:!0,force:!0})}}function aR($){if(!$)return"22";let J=String($).match(/(\d+)/);return J?J[1]:"22"}function IY($){let J=$.kind??"node";if(J==="php")return{lambdaRuntime:"provided.al2023",kind:"php",version:$.phpVersion??"8.3",usesLayer:!0,layerKind:"php",layerEnvVar:"TSCLOUD_PHP_LAYER_ARN"};if(J==="bun")return{lambdaRuntime:"provided.al2023",kind:"bun",version:$.runtimeVersion??"latest",usesLayer:!0,layerKind:"bun",layerEnvVar:"TSCLOUD_BUN_LAYER_ARN"};let Y=aR($.runtimeVersion);if($.runtime!=="provided.al2023"&&OH.includes(Y))return{lambdaRuntime:$.runtime&&$.runtime.startsWith("nodejs")?$.runtime:`nodejs${Y}.x`,kind:"node",version:Y,usesLayer:!1};return{lambdaRuntime:"provided.al2023",kind:"node",version:Y,usesLayer:!0,layerKind:"node",layerEnvVar:"TSCLOUD_NODE_LAYER_ARN"}}function BH($,J,Y){if($.queues===!1)return[];if($.queues===void 0||$.queues===!0)return[`${J}-${Y}-default`];return $.queues.map((Q)=>{let G=typeof Q==="string"?Q:Object.keys(Q)[0];return`${J}-${Y}-${G}`})}function DZ($){let{app:J,environment:Y,handlers:Q}=$,G=$.config.project.slug,Z=IY(J).lambdaRuntime,W=J.architecture??"x86_64",U=$.config.project.region,z={http:`${G}-${Y}-http`,queue:`${G}-${Y}-queue`,cli:`${G}-${Y}-cli`},_=BH(J,G,Y),H=_.length>0,X=J.packaging==="image",F=(J.scheduler??"on")!=="off",B=(J.cache?.driver??"dynamodb")==="dynamodb",K=Boolean(J.assets),V=RY(G,Y),L=J.tmpStorage??512,A={},E={},q=[{PolicyName:"tscloud-serverless-app",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],Resource:j.sub("arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*")},{Effect:"Allow",Action:["lambda:InvokeFunction"],Resource:j.sub(`arn:aws:lambda:\${AWS::Region}:\${AWS::AccountId}:function:${G}-${Y}-*`)},{Effect:"Allow",Action:["secretsmanager:GetSecretValue","ssm:GetParameter","ssm:GetParameters","ssm:GetParametersByPath"],Resource:[j.sub(`arn:aws:secretsmanager:\${AWS::Region}:\${AWS::AccountId}:secret:${G}/${Y}/*`),j.sub(`arn:aws:ssm:\${AWS::Region}:\${AWS::AccountId}:parameter/${G}/${Y}/*`)]},{Effect:"Allow",Action:["s3:GetObject","s3:PutObject","s3:DeleteObject","s3:ListBucket"],Resource:[j.sub(`arn:aws:s3:::${V}`),j.sub(`arn:aws:s3:::${V}/*`),...J.storage?.bucket?[j.sub(`arn:aws:s3:::${J.storage.bucket}`),j.sub(`arn:aws:s3:::${J.storage.bucket}/*`)]:[]]}]}}];if(H)q[0].PolicyDocument.Statement.push({Effect:"Allow",Action:["sqs:SendMessage","sqs:ReceiveMessage","sqs:DeleteMessage","sqs:GetQueueAttributes","sqs:GetQueueUrl"],Resource:j.sub(`arn:aws:sqs:\${AWS::Region}:\${AWS::AccountId}:${G}-${Y}-*`)});if(B)q[0].PolicyDocument.Statement.push({Effect:"Allow",Action:["dynamodb:GetItem","dynamodb:PutItem","dynamodb:UpdateItem","dynamodb:DeleteItem","dynamodb:Query","dynamodb:Scan","dynamodb:BatchGetItem","dynamodb:BatchWriteItem"],Resource:j.sub(`arn:aws:dynamodb:\${AWS::Region}:\${AWS::AccountId}:table/${G}-${Y}-cache*`)});let M=["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"];if(J.vpc?.subnets?.length)M.push("arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole");A.AppRole={Type:"AWS::IAM::Role",Properties:{RoleName:`${G}-${Y}-app-role`,AssumeRolePolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"lambda.amazonaws.com"},Action:"sts:AssumeRole"}]},ManagedPolicyArns:M,Policies:q}};let w=(a)=>({TSCLOUD_LAMBDA_MODE:a,TSCLOUD_ENV:Y,...J.octane?{TSCLOUD_OCTANE:"1"}:{},...J.scheduler==="sub-minute"?{TSCLOUD_SCHEDULER:"sub-minute"}:{},...B?{TSCLOUD_CACHE_TABLE:`${G}-${Y}-cache`}:{},...H?{TSCLOUD_QUEUE:_[0]}:{},...J.env??{}}),T=Boolean(J.efs),R=typeof J.efs==="object"?J.efs:{},I=R.mountPath??"/mnt/local",N=T&&!R.accessPointArn,C=R.accessPointArn??(N?j.getAtt("EfsAccessPoint","Arn"):void 0),x=J.vpc?.subnets??[],k=x.length>0,h=J.cache?.driver==="elasticache"||J.database?.connection==="aurora-serverless"||Boolean(J.rdsProxy)||T;if(h&&!k)throw Error("serverless app: elasticache / aurora-serverless / rdsProxy / efs require app.vpc.subnets (private subnets) to be set.");let p=k?{VpcConfig:{SubnetIds:x,SecurityGroupIds:[...J.vpc?.securityGroups??[],...h?[j.getAtt("DataSecurityGroup","GroupId")]:[]]}}:{},g=N?x.map((a,H0)=>`EfsMountTarget${H0}`):[],c=T?{FileSystemConfigs:[{Arn:C,LocalMountPath:I}]}:{};if(T)q[0].PolicyDocument.Statement.push({Effect:"Allow",Action:["elasticfilesystem:ClientMount","elasticfilesystem:ClientWrite","elasticfilesystem:ClientRootAccess","elasticfilesystem:DescribeMountTargets"],Resource:"*"});function $0(a,H0,j0,K0,G1,Y2,Q2,TQ=L){A[`${a}LogGroup`]={Type:"AWS::Logs::LogGroup",Properties:{LogGroupName:`/aws/lambda/${H0}`,RetentionInDays:14}};let RQ=X?{PackageType:"Image",Code:{ImageUri:j.ref("ImageUri")},...J.kind==="php"?{}:{ImageConfig:{Command:[j0]}}}:{Runtime:Z,Handler:j0,Code:{S3Bucket:j.ref("ArtifactBucket"),S3Key:j.ref("ArtifactKey")},...$.runtimeLayers?.length?{Layers:$.runtimeLayers}:{}};A[a]={Type:"AWS::Lambda::Function",DependsOn:[`${a}LogGroup`,...g],Properties:{FunctionName:H0,Architectures:[W],MemorySize:G1,Timeout:Y2,Role:j.getAtt("AppRole","Arn"),Environment:{Variables:w(K0)},EphemeralStorage:{Size:TQ},...RQ,...Q2!==void 0?{ReservedConcurrentExecutions:Q2}:{},...p,...c}}}if($0("HttpFunction",z.http,Q.http,"http",J.memory??1024,J.timeout??28,J.concurrency,L),$0("CliFunction",z.cli,Q.cli,"cli",J.cliMemory??1024,J.cliTimeout??900,void 0,J.cliTmpStorage??L),H)$0("QueueFunction",z.queue,Q.queue,"queue",J.queueMemory??1024,J.queueTimeout??120,void 0,J.queueTmpStorage??L);A.HttpApi={Type:"AWS::ApiGatewayV2::Api",Properties:{Name:`${G}-${Y}`,ProtocolType:"HTTP"}},A.HttpIntegration={Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:j.ref("HttpApi"),IntegrationType:"AWS_PROXY",IntegrationUri:j.getAtt("HttpFunction","Arn"),PayloadFormatVersion:"2.0"}},A.HttpRoute={Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:j.ref("HttpApi"),RouteKey:"$default",Target:j.join("/",["integrations",j.ref("HttpIntegration")])}},A.HttpStage={Type:"AWS::ApiGatewayV2::Stage",Properties:{ApiId:j.ref("HttpApi"),StageName:"$default",AutoDeploy:!0}},A.HttpPermission={Type:"AWS::Lambda::Permission",Properties:{FunctionName:j.ref("HttpFunction"),Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:j.sub("arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${HttpApi}/*/*")}},E.HttpApiEndpoint={Description:"HTTP API endpoint",Value:j.getAtt("HttpApi","ApiEndpoint")},E.HttpApiId={Description:"HTTP API id",Value:j.ref("HttpApi")};let i=(Array.isArray(J.domain)?J.domain:J.domain?[J.domain]:[]).filter(Boolean);if(i.length){if(!J.certificateArn&&!J.hostedZoneId)throw Error("serverless app: a custom `domain` needs either `certificateArn` (pre-issued, regional) or `hostedZoneId` (to auto-issue + validate an ACM cert).");let a=J.certificateArn;if(!a)A.HttpCertificate={Type:"AWS::CertificateManager::Certificate",Properties:{DomainName:i[0],...i.length>1?{SubjectAlternativeNames:i.slice(1)}:{},ValidationMethod:"DNS",DomainValidationOptions:i.map((H0)=>({DomainName:H0,HostedZoneId:J.hostedZoneId}))}},a=j.ref("HttpCertificate");i.forEach((H0,j0)=>{let K0=`HttpDomain${j0}`;if(A[K0]={Type:"AWS::ApiGatewayV2::DomainName",Properties:{DomainName:H0,DomainNameConfigurations:[{CertificateArn:a,EndpointType:"REGIONAL"}]}},A[`HttpApiMapping${j0}`]={Type:"AWS::ApiGatewayV2::ApiMapping",DependsOn:["HttpStage"],Properties:{ApiId:j.ref("HttpApi"),DomainName:j.ref(K0),Stage:"$default"}},J.hostedZoneId)A[`HttpDomainRecord${j0}`]={Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:J.hostedZoneId,Name:H0,Type:"A",AliasTarget:{DNSName:j.getAtt(K0,"RegionalDomainName"),HostedZoneId:j.getAtt(K0,"RegionalHostedZoneId")}}};E[`CustomDomain${j0}`]={Description:`Custom domain ${H0}`,Value:H0},E[`CustomDomainTarget${j0}`]={Description:`Point ${H0} (CNAME/alias) at this APIGW regional domain`,Value:j.getAtt(K0,"RegionalDomainName")}})}if(H)A.AppQueueDlq={Type:"AWS::SQS::Queue",Properties:{QueueName:`${G}-${Y}-dlq`,MessageRetentionPeriod:1209600}},_.forEach((a,H0)=>{let j0=`AppQueue${H0}`;A[j0]={Type:"AWS::SQS::Queue",Properties:{QueueName:a,VisibilityTimeout:Math.max(J.queueTimeout??120,J.queueTimeout??120),RedrivePolicy:{deadLetterTargetArn:j.getAtt("AppQueueDlq","Arn"),maxReceiveCount:J.queueTries??3}}},A[`${j0}Mapping`]={Type:"AWS::Lambda::EventSourceMapping",Properties:{EventSourceArn:j.getAtt(j0,"Arn"),FunctionName:j.ref("QueueFunction"),BatchSize:1,FunctionResponseTypes:["ReportBatchItemFailures"],...J.queueConcurrency?{ScalingConfig:{MaximumConcurrency:Math.max(2,J.queueConcurrency)}}:{}}},E[`QueueUrl${H0}`]={Description:`Queue URL: ${a}`,Value:j.ref(j0)}});if(F)A.SchedulerRule={Type:"AWS::Events::Rule",Properties:{Name:`${G}-${Y}-scheduler`,ScheduleExpression:"rate(1 minute)",State:"ENABLED",Targets:[{Id:"cli",Arn:j.getAtt("CliFunction","Arn"),Input:JSON.stringify({command:"schedule:run"})}]}},A.SchedulerPermission={Type:"AWS::Lambda::Permission",Properties:{FunctionName:j.ref("CliFunction"),Action:"lambda:InvokeFunction",Principal:"events.amazonaws.com",SourceArn:j.getAtt("SchedulerRule","Arn")}};if(J.warm&&J.warm>0){let H0=Math.ceil(J.warm/5),j0=0;for(let K0=0;K0<H0;K0++){let G1=Math.min(5,J.warm-j0);A[`WarmerRule${K0}`]={Type:"AWS::Events::Rule",Properties:{Name:`${G}-${Y}-warmer-${K0}`,ScheduleExpression:"rate(5 minutes)",State:"ENABLED",Targets:Array.from({length:G1},(Y2,Q2)=>({Id:`warm-${K0}-${Q2}`,Arn:j.getAtt("HttpFunction","Arn"),Input:JSON.stringify({warmer:!0})}))}},j0+=G1}A.WarmerPermission={Type:"AWS::Lambda::Permission",Properties:{FunctionName:j.ref("HttpFunction"),Action:"lambda:InvokeFunction",Principal:"events.amazonaws.com",SourceArn:j.sub(`arn:aws:events:\${AWS::Region}:\${AWS::AccountId}:rule/${G}-${Y}-warmer-*`)}}}if(B)A.CacheTable={Type:"AWS::DynamoDB::Table",Properties:{TableName:`${G}-${Y}-cache`,BillingMode:"PAY_PER_REQUEST",AttributeDefinitions:[{AttributeName:"key",AttributeType:"S"}],KeySchema:[{AttributeName:"key",KeyType:"HASH"}],TimeToLiveSpecification:{AttributeName:"expires_at",Enabled:!0}}},E.CacheTableName={Description:"DynamoDB cache table",Value:j.ref("CacheTable")};if(K){if(A.AssetsBucket={Type:"AWS::S3::Bucket",Properties:{BucketName:V,PublicAccessBlockConfiguration:{BlockPublicAcls:!0,BlockPublicPolicy:!0,IgnorePublicAcls:!0,RestrictPublicBuckets:!0}}},A.AssetsOAC={Type:"AWS::CloudFront::OriginAccessControl",Properties:{OriginAccessControlConfig:{Name:`${G}-${Y}-assets-oac`,OriginAccessControlOriginType:"s3",SigningBehavior:"always",SigningProtocol:"sigv4"}}},A.AssetsDistribution={Type:"AWS::CloudFront::Distribution",Properties:{DistributionConfig:{Enabled:!0,DefaultCacheBehavior:{TargetOriginId:"assets",ViewerProtocolPolicy:"redirect-to-https",Compress:!0,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6"},Origins:[{Id:"assets",DomainName:j.getAtt("AssetsBucket","RegionalDomainName"),OriginAccessControlId:j.ref("AssetsOAC"),S3OriginConfig:{OriginAccessIdentity:""}}],...J.assetDomain?{Aliases:[J.assetDomain],ViewerCertificate:{AcmCertificateArn:J.assetCertificateArn??j.ref("AssetsCertificate"),SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}}:{}}}},J.assetDomain){if(!J.assetCertificateArn){if(!J.hostedZoneId)throw Error("serverless app: `assetDomain` requires either `assetCertificateArn` (a us-east-1 ACM cert) or `hostedZoneId` (to auto-issue + validate one). CloudFront only accepts certs from us-east-1.");if(U!=="us-east-1")throw Error(`serverless app: auto-issuing an asset-domain cert needs a us-east-1 app (CloudFront certs must be us-east-1); this app is in ${U}. Supply a pre-issued us-east-1 \`assetCertificateArn\` instead.`);A.AssetsCertificate={Type:"AWS::CertificateManager::Certificate",Properties:{DomainName:J.assetDomain,ValidationMethod:"DNS",DomainValidationOptions:[{DomainName:J.assetDomain,HostedZoneId:J.hostedZoneId}]}}}if(J.hostedZoneId)A.AssetsDomainRecord={Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:J.hostedZoneId,Name:J.assetDomain,Type:"A",AliasTarget:{DNSName:j.getAtt("AssetsDistribution","DomainName"),HostedZoneId:"Z2FDTNDATAQYW2"}}};E.AssetDomain={Description:"Custom asset CDN host",Value:J.assetDomain}}A.AssetsBucketPolicy={Type:"AWS::S3::BucketPolicy",Properties:{Bucket:j.ref("AssetsBucket"),PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:j.sub(`arn:aws:s3:::${V}/*`),Condition:{StringEquals:{"AWS:SourceArn":j.sub("arn:aws:cloudfront::${AWS::AccountId}:distribution/${AssetsDistribution}")}}}]}}},E.AssetsBucketName={Description:"Assets bucket",Value:j.ref("AssetsBucket")},E.AssetsCdnDomain={Description:"Assets CloudFront domain",Value:j.getAtt("AssetsDistribution","DomainName")}}if(J.firewall?.enabled){let a=[],H0=0;if(J.firewall.rateLimit)a.push({Name:"rate-limit",Priority:H0++,Action:{Block:{}},Statement:{RateBasedStatement:{Limit:J.firewall.rateLimit,AggregateKeyType:"IP"}},VisibilityConfig:{SampledRequestsEnabled:!0,CloudWatchMetricsEnabled:!0,MetricName:`${G}-${Y}-rate`}});let j0={sqlInjection:"AWSManagedRulesSQLiRuleSet",xss:"AWSManagedRulesCommonRuleSet",common:"AWSManagedRulesCommonRuleSet",botControl:"AWSManagedRulesBotControlRuleSet",ipReputation:"AWSManagedRulesAmazonIpReputationList"};for(let K0 of J.firewall.rules??[]){let G1=j0[K0];if(!G1)continue;a.push({Name:`managed-${K0}`,Priority:H0++,OverrideAction:{None:{}},Statement:{ManagedRuleGroupStatement:{VendorName:"AWS",Name:G1}},VisibilityConfig:{SampledRequestsEnabled:!0,CloudWatchMetricsEnabled:!0,MetricName:`${G}-${Y}-${K0}`}})}A.WebAcl={Type:"AWS::WAFv2::WebACL",Properties:{Name:`${G}-${Y}-waf`,Scope:"REGIONAL",DefaultAction:{Allow:{}},Rules:a,VisibilityConfig:{SampledRequestsEnabled:!0,CloudWatchMetricsEnabled:!0,MetricName:`${G}-${Y}-waf`}}},A.WebAclAssociation={Type:"AWS::WAFv2::WebACLAssociation",DependsOn:["HttpStage"],Properties:{ResourceArn:j.sub("arn:aws:apigateway:${AWS::Region}::/apis/${HttpApi}/stages/$default"),WebACLArn:j.getAtt("WebAcl","Arn")}}}if(k&&h){if(!J.vpc?.id)throw Error("serverless app: data services (elasticache / aurora-serverless / rdsProxy / efs) need app.vpc.id (the VPC id) so the managed security group is created in the right VPC.");A.DataSecurityGroup={Type:"AWS::EC2::SecurityGroup",Properties:{VpcId:J.vpc.id,GroupDescription:`${G}-${Y} serverless data access`,SecurityGroupIngress:[{IpProtocol:"-1",CidrIp:"10.0.0.0/8"}]}}}if(N)A.EfsFileSystem={Type:"AWS::EFS::FileSystem",Properties:{Encrypted:!0,FileSystemTags:[{Key:"Name",Value:`${G}-${Y}-efs`}]}},x.forEach((a,H0)=>{A[`EfsMountTarget${H0}`]={Type:"AWS::EFS::MountTarget",Properties:{FileSystemId:j.ref("EfsFileSystem"),SubnetId:a,SecurityGroups:[j.getAtt("DataSecurityGroup","GroupId")]}}}),A.EfsAccessPoint={Type:"AWS::EFS::AccessPoint",Properties:{FileSystemId:j.ref("EfsFileSystem"),PosixUser:{Uid:1001,Gid:1001},RootDirectory:{Path:"/lambda",CreationInfo:{OwnerUid:1001,OwnerGid:1001,Permissions:"0755"}}}},E.EfsFileSystemId={Description:"EFS file system id",Value:j.ref("EfsFileSystem")};if(J.cache?.driver==="elasticache")A.CacheSubnetGroup={Type:"AWS::ElastiCache::SubnetGroup",Properties:{Description:`${G}-${Y} cache subnets`,SubnetIds:x}},A.CacheCluster={Type:"AWS::ElastiCache::ReplicationGroup",Properties:{ReplicationGroupId:`${G}-${Y}-cache`,ReplicationGroupDescription:`${G}-${Y} redis`,Engine:"redis",CacheNodeType:"cache.t4g.micro",NumCacheClusters:1,AutomaticFailoverEnabled:!1,CacheSubnetGroupName:j.ref("CacheSubnetGroup"),SecurityGroupIds:[j.getAtt("DataSecurityGroup","GroupId")],TransitEncryptionEnabled:!1}},E.CacheEndpoint={Description:"Redis primary endpoint",Value:j.getAtt("CacheCluster","PrimaryEndPoint.Address")};if(J.database?.connection==="aurora-serverless")A.DbSubnetGroup={Type:"AWS::RDS::DBSubnetGroup",Properties:{DBSubnetGroupDescription:`${G}-${Y} db subnets`,SubnetIds:x}},A.DbSecret={Type:"AWS::SecretsManager::Secret",Properties:{Name:`${G}/${Y}/db`,GenerateSecretString:{SecretStringTemplate:JSON.stringify({username:"app"}),GenerateStringKey:"password",PasswordLength:32,ExcludePunctuation:!0}}},A.DbCluster={Type:"AWS::RDS::DBCluster",Properties:{Engine:"aurora-mysql",EngineMode:"provisioned",DBClusterIdentifier:`${G}-${Y}-db`,DatabaseName:"app",MasterUsername:j.sub("{{resolve:secretsmanager:${DbSecret}:SecretString:username}}"),MasterUserPassword:j.sub("{{resolve:secretsmanager:${DbSecret}:SecretString:password}}"),ServerlessV2ScalingConfiguration:{MinCapacity:0.5,MaxCapacity:4},DBSubnetGroupName:j.ref("DbSubnetGroup"),VpcSecurityGroupIds:[j.getAtt("DataSecurityGroup","GroupId")]}},A.DbInstance={Type:"AWS::RDS::DBInstance",Properties:{Engine:"aurora-mysql",DBInstanceClass:"db.serverless",DBClusterIdentifier:j.ref("DbCluster")}},E.DbEndpoint={Description:"Aurora cluster endpoint",Value:j.getAtt("DbCluster","Endpoint.Address")};if(J.rdsProxy&&J.database?.connection==="aurora-serverless")A.DbProxyRole={Type:"AWS::IAM::Role",Properties:{AssumeRolePolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"rds.amazonaws.com"},Action:"sts:AssumeRole"}]},Policies:[{PolicyName:"read-db-secret",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["secretsmanager:GetSecretValue"],Resource:j.ref("DbSecret")}]}}]}},A.DbProxy={Type:"AWS::RDS::DBProxy",Properties:{DBProxyName:typeof J.rdsProxy==="object"&&J.rdsProxy.name?J.rdsProxy.name:`${G}-${Y}-proxy`,EngineFamily:"MYSQL",RoleArn:j.getAtt("DbProxyRole","Arn"),Auth:[{AuthScheme:"SECRETS",SecretArn:j.ref("DbSecret"),IAMAuth:"DISABLED"}],VpcSubnetIds:x,VpcSecurityGroupIds:[j.getAtt("DataSecurityGroup","GroupId")],RequireTLS:!1}},A.DbProxyTargetGroup={Type:"AWS::RDS::DBProxyTargetGroup",DependsOn:["DbInstance"],Properties:{DBProxyName:j.ref("DbProxy"),TargetGroupName:"default",DBClusterIdentifiers:[j.ref("DbCluster")]}},E.DbProxyEndpoint={Description:"RDS Proxy endpoint",Value:j.getAtt("DbProxy","Endpoint")};if(E.HttpFunctionName={Description:"HTTP function name",Value:j.ref("HttpFunction")},E.CliFunctionName={Description:"CLI function name",Value:j.ref("CliFunction")},H)E.QueueFunctionName={Description:"Queue function name",Value:j.ref("QueueFunction")};let t={AWSTemplateFormatVersion:"2010-09-09",Description:`Serverless application for ${$.config.project.name} (${G}-${Y})`,Parameters:X?{ImageUri:{Type:"String",Description:"ECR image URI of the deployment artifact"}}:{ArtifactBucket:{Type:"String",Description:"S3 bucket holding the deployment artifact"},ArtifactKey:{Type:"String",Description:"S3 key of the deployment artifact (zip)"}},Resources:A,Outputs:E},G0={};for(let a of Object.values(A))G0[a.Type]=(G0[a.Type]??0)+1;return{template:t,functionNames:z,queueNames:_,resourceSummary:G0}}function KH($){let J=`php${$.replace(".","")}`;return VH.map((Y)=>`${J}-php-${Y}`)}function NZ($,J){let Y=`php${$.replace(".","")}`,Q=KH($).join(" \\\n "),G=`/opt/remi/${Y}/root`,Z=`/etc/opt/remi/${Y}`;return`${J?`FROM almalinux:9 AS ${J}`:"FROM almalinux:9"}
3643
3643
 
3644
3644
  # Build PHP on AlmaLinux 9 (where Remi's version-isolated SCL packages install
3645
3645
  # cleanly — Remi's release RPM rejects Amazon Linux 2023). AlmaLinux 9 shares
@@ -13562,7 +13562,7 @@ Run \`${this.name??"cli"} --help\` for usage.`;G2.stderr.write(`${Z}${G}${W}
13562
13562
  `).length-1;this.output.write(C$.move(-999,$*-1))}render(){let $=Y4(this._render(this)??"",J4.stdout.columns,{hard:!0,trim:!1});if($===this._prevFrame)return;if(this.state==="initial")this.output.write(C$.hide);else{let J=nL(this._prevFrame,$);if(this.restoreCursor(),J&&J?.length===1){let Y=J[0];this.output.write(C$.move(0,Y)),this.output.write(PQ.lines(1));let Q=$.split(`
13563
13563
  `);this.output.write(Q[Y]),this._prevFrame=$,this.output.write(C$.move(0,Q.length-Y-1));return}if(J&&J?.length>1){let Y=J[0];this.output.write(C$.move(0,Y)),this.output.write(PQ.down());let G=$.split(`
13564
13564
  `).slice(Y);this.output.write(G.join(`
13565
- `)),this._prevFrame=$;return}this.output.write(PQ.down())}if(this.output.write($),this.state==="initial")this.state="active";this._prevFrame=$}}function sL($,J){if($===void 0)return 0;if(J.length===0)return 0;let Q=J.findIndex((G)=>G.value===$);return Q!==-1?Q:0}function eL($,J){return(J.label??String(J.value)).toLowerCase().includes($.toLowerCase())}function $A($,J){if(!J)return;if($)return J;return J[0]}class JA extends hQ{filteredOptions;multiple;isNavigating=!1;selectedValues=[];focusedValue;#$=0;#Y="";#Q;#J;get cursor(){return this.#$}get userInputWithCursor(){if(!this.userInput)return jJ.inverse(jJ.hidden("_"));if(this._cursor>=this.userInput.length)return`${this.userInput}█`;let $=this.userInput.slice(0,this._cursor),[J,...Y]=this.userInput.slice(this._cursor);return`${$}${jJ.inverse(J)}${Y.join("")}`}get options(){if(typeof this.#J==="function")return this.#J();return this.#J}constructor($){super($);this.#J=$.options;let J=this.options;this.filteredOptions=[...J],this.multiple=$.multiple===!0,this.#Q=$.filter??eL;let Y;if($.initialValue&&Array.isArray($.initialValue))if(this.multiple)Y=$.initialValue;else Y=$.initialValue.slice(0,1);else if(!this.multiple&&this.options.length>0)Y=[this.options[0].value];if(Y)for(let Q of Y){let G=J.findIndex((Z)=>Z.value===Q);if(G!==-1)this.toggleSelected(Q),this.#$=G}this.focusedValue=this.options[this.#$]?.value,this.on("key",(Q,G)=>this.#G(Q,G)),this.on("userInput",(Q)=>this.#Z(Q))}_isActionKey($,J){return $==="\t"||this.multiple&&this.isNavigating&&J.name==="space"&&$!==void 0&&$!==""}#G($,J){let Y=J.name==="up",Q=J.name==="down",G=J.name==="return";if(Y||Q){if(this.#$=Math.max(0,Math.min(this.#$+(Y?-1:1),this.filteredOptions.length-1)),this.focusedValue=this.filteredOptions[this.#$]?.value,!this.multiple)this.selectedValues=[this.focusedValue];this.isNavigating=!0}else if(G)this.value=$A(this.multiple,this.selectedValues);else if(this.multiple)if(this.focusedValue!==void 0&&(J.name==="tab"||this.isNavigating&&J.name==="space"))this.toggleSelected(this.focusedValue);else this.isNavigating=!1;else{if(this.focusedValue)this.selectedValues=[this.focusedValue];this.isNavigating=!1}}deselectAll(){this.selectedValues=[]}toggleSelected($){if(this.filteredOptions.length===0)return;if(this.multiple)if(this.selectedValues.includes($))this.selectedValues=this.selectedValues.filter((J)=>J!==$);else this.selectedValues=[...this.selectedValues,$];else this.selectedValues=[$]}#Z($){if($!==this.#Y){this.#Y=$;let J=this.options;if($)this.filteredOptions=J.filter((Y)=>this.#Q($,Y));else this.filteredOptions=[...J];if(this.#$=sL(this.focusedValue,this.filteredOptions),this.focusedValue=this.filteredOptions[this.#$]?.value,!this.multiple)if(this.focusedValue!==void 0)this.toggleSelected(this.focusedValue);else this.deselectAll()}}}class YA extends hQ{options;cursor=0;#$;getGroupItems($){return this.options.filter((J)=>J.group===$)}isGroupSelected($){let J=this.getGroupItems($),Y=this.value;if(Y===void 0)return!1;return J.every((Q)=>Y.includes(Q.value))}toggleValue(){let $=this.options[this.cursor];if(this.value===void 0)this.value=[];if($.group===!0){let J=$.value,Y=this.getGroupItems(J);if(this.isGroupSelected(J))this.value=this.value.filter((Q)=>Y.findIndex((G)=>G.value===Q)===-1);else this.value=[...this.value,...Y.map((Q)=>Q.value)];this.value=Array.from(new Set(this.value))}else{let J=this.value.includes($.value);this.value=J?this.value.filter((Y)=>Y!==$.value):[...this.value,$.value]}}constructor($){super($,!1);let{options:J}=$;this.#$=$.selectableGroups!==!1,this.options=Object.entries(J).flatMap(([Y,Q])=>[{value:Y,group:!0,label:Y},...Q.map((G)=>({...G,group:Y}))]),this.value=[...$.initialValues??[]],this.cursor=Math.max(this.options.findIndex(({value:Y})=>Y===$.cursorAt),this.#$?0:1),this.on("cursor",(Y)=>{switch(Y){case"left":case"up":{this.cursor=this.cursor===0?this.options.length-1:this.cursor-1;let Q=this.options[this.cursor]?.group===!0;if(!this.#$&&Q)this.cursor=this.cursor===0?this.options.length-1:this.cursor-1;break}case"down":case"right":{this.cursor=this.cursor===this.options.length-1?0:this.cursor+1;let Q=this.options[this.cursor]?.group===!0;if(!this.#$&&Q)this.cursor=this.cursor===this.options.length-1?0:this.cursor+1;break}case"space":this.toggleValue();break}})}}var QA=XL();var h0=($,J)=>QA?$:J,Vh=h0("◆","*"),Kh=h0("■","x"),Lh=h0("▲","x"),Ah=h0("◇","o"),Eh=h0("┌","T"),GA=h0("│","|"),qh=h0("└","—"),jh=h0("●",">"),Mh=h0("○"," "),wh=h0("◻","[•]"),Th=h0("◼","[+]"),Rh=h0("◻","[ ]"),Dh=h0("▪","•"),Nh=h0("─","-"),Sh=h0("╮","+"),Ph=h0("├","+"),Ch=h0("╯","+"),Ih=h0("●","•"),xh=h0("◆","*"),kh=h0("▲","!"),bh=h0("■","x");var hh={light:h0("─","-"),heavy:h0("━","="),block:h0("█","#")};function ZA(){return`${jJ.gray(GA)} `}var yh=ZA();var yQ="0.5.4";X0();import{existsSync as nQ}from"node:fs";import{mkdir as KA,writeFile as q4}from"node:fs/promises";function rQ($){$.command("init","Initialize a new ts-cloud project").option("--mode <mode>","Deployment mode: server, serverless, or hybrid").option("--name <name>","Project name").option("--region <region>","AWS Region").action(async(J)=>{if(D("Initializing ts-cloud Project"),nQ("cloud.config.ts")){if(!await d("cloud.config.ts already exists. Overwrite?",!1)){O("Initialization cancelled");return}}let Y=J?.name||await I0("Project name","my-app"),Q=J?.mode||await TJ("Select deployment mode",["serverless","server","hybrid"]),G=J?.region||await TJ("Select AWS region",["us-east-1","us-west-2","eu-west-1","eu-central-1","ap-southeast-1"]),Z=new P("Creating configuration file...");Z.start();let W=`import { defineConfig } from '@ts-cloud/core'
13565
+ `)),this._prevFrame=$;return}this.output.write(PQ.down())}if(this.output.write($),this.state==="initial")this.state="active";this._prevFrame=$}}function sL($,J){if($===void 0)return 0;if(J.length===0)return 0;let Q=J.findIndex((G)=>G.value===$);return Q!==-1?Q:0}function eL($,J){return(J.label??String(J.value)).toLowerCase().includes($.toLowerCase())}function $A($,J){if(!J)return;if($)return J;return J[0]}class JA extends hQ{filteredOptions;multiple;isNavigating=!1;selectedValues=[];focusedValue;#$=0;#Y="";#Q;#J;get cursor(){return this.#$}get userInputWithCursor(){if(!this.userInput)return jJ.inverse(jJ.hidden("_"));if(this._cursor>=this.userInput.length)return`${this.userInput}█`;let $=this.userInput.slice(0,this._cursor),[J,...Y]=this.userInput.slice(this._cursor);return`${$}${jJ.inverse(J)}${Y.join("")}`}get options(){if(typeof this.#J==="function")return this.#J();return this.#J}constructor($){super($);this.#J=$.options;let J=this.options;this.filteredOptions=[...J],this.multiple=$.multiple===!0,this.#Q=$.filter??eL;let Y;if($.initialValue&&Array.isArray($.initialValue))if(this.multiple)Y=$.initialValue;else Y=$.initialValue.slice(0,1);else if(!this.multiple&&this.options.length>0)Y=[this.options[0].value];if(Y)for(let Q of Y){let G=J.findIndex((Z)=>Z.value===Q);if(G!==-1)this.toggleSelected(Q),this.#$=G}this.focusedValue=this.options[this.#$]?.value,this.on("key",(Q,G)=>this.#G(Q,G)),this.on("userInput",(Q)=>this.#Z(Q))}_isActionKey($,J){return $==="\t"||this.multiple&&this.isNavigating&&J.name==="space"&&$!==void 0&&$!==""}#G($,J){let Y=J.name==="up",Q=J.name==="down",G=J.name==="return";if(Y||Q){if(this.#$=Math.max(0,Math.min(this.#$+(Y?-1:1),this.filteredOptions.length-1)),this.focusedValue=this.filteredOptions[this.#$]?.value,!this.multiple)this.selectedValues=[this.focusedValue];this.isNavigating=!0}else if(G)this.value=$A(this.multiple,this.selectedValues);else if(this.multiple)if(this.focusedValue!==void 0&&(J.name==="tab"||this.isNavigating&&J.name==="space"))this.toggleSelected(this.focusedValue);else this.isNavigating=!1;else{if(this.focusedValue)this.selectedValues=[this.focusedValue];this.isNavigating=!1}}deselectAll(){this.selectedValues=[]}toggleSelected($){if(this.filteredOptions.length===0)return;if(this.multiple)if(this.selectedValues.includes($))this.selectedValues=this.selectedValues.filter((J)=>J!==$);else this.selectedValues=[...this.selectedValues,$];else this.selectedValues=[$]}#Z($){if($!==this.#Y){this.#Y=$;let J=this.options;if($)this.filteredOptions=J.filter((Y)=>this.#Q($,Y));else this.filteredOptions=[...J];if(this.#$=sL(this.focusedValue,this.filteredOptions),this.focusedValue=this.filteredOptions[this.#$]?.value,!this.multiple)if(this.focusedValue!==void 0)this.toggleSelected(this.focusedValue);else this.deselectAll()}}}class YA extends hQ{options;cursor=0;#$;getGroupItems($){return this.options.filter((J)=>J.group===$)}isGroupSelected($){let J=this.getGroupItems($),Y=this.value;if(Y===void 0)return!1;return J.every((Q)=>Y.includes(Q.value))}toggleValue(){let $=this.options[this.cursor];if(this.value===void 0)this.value=[];if($.group===!0){let J=$.value,Y=this.getGroupItems(J);if(this.isGroupSelected(J))this.value=this.value.filter((Q)=>Y.findIndex((G)=>G.value===Q)===-1);else this.value=[...this.value,...Y.map((Q)=>Q.value)];this.value=Array.from(new Set(this.value))}else{let J=this.value.includes($.value);this.value=J?this.value.filter((Y)=>Y!==$.value):[...this.value,$.value]}}constructor($){super($,!1);let{options:J}=$;this.#$=$.selectableGroups!==!1,this.options=Object.entries(J).flatMap(([Y,Q])=>[{value:Y,group:!0,label:Y},...Q.map((G)=>({...G,group:Y}))]),this.value=[...$.initialValues??[]],this.cursor=Math.max(this.options.findIndex(({value:Y})=>Y===$.cursorAt),this.#$?0:1),this.on("cursor",(Y)=>{switch(Y){case"left":case"up":{this.cursor=this.cursor===0?this.options.length-1:this.cursor-1;let Q=this.options[this.cursor]?.group===!0;if(!this.#$&&Q)this.cursor=this.cursor===0?this.options.length-1:this.cursor-1;break}case"down":case"right":{this.cursor=this.cursor===this.options.length-1?0:this.cursor+1;let Q=this.options[this.cursor]?.group===!0;if(!this.#$&&Q)this.cursor=this.cursor===this.options.length-1?0:this.cursor+1;break}case"space":this.toggleValue();break}})}}var QA=XL();var h0=($,J)=>QA?$:J,Vh=h0("◆","*"),Kh=h0("■","x"),Lh=h0("▲","x"),Ah=h0("◇","o"),Eh=h0("┌","T"),GA=h0("│","|"),qh=h0("└","—"),jh=h0("●",">"),Mh=h0("○"," "),wh=h0("◻","[•]"),Th=h0("◼","[+]"),Rh=h0("◻","[ ]"),Dh=h0("▪","•"),Nh=h0("─","-"),Sh=h0("╮","+"),Ph=h0("├","+"),Ch=h0("╯","+"),Ih=h0("●","•"),xh=h0("◆","*"),kh=h0("▲","!"),bh=h0("■","x");var hh={light:h0("─","-"),heavy:h0("━","="),block:h0("█","#")};function ZA(){return`${jJ.gray(GA)} `}var yh=ZA();var yQ="0.5.5";X0();import{existsSync as nQ}from"node:fs";import{mkdir as KA,writeFile as q4}from"node:fs/promises";function rQ($){$.command("init","Initialize a new ts-cloud project").option("--mode <mode>","Deployment mode: server, serverless, or hybrid").option("--name <name>","Project name").option("--region <region>","AWS Region").action(async(J)=>{if(D("Initializing ts-cloud Project"),nQ("cloud.config.ts")){if(!await d("cloud.config.ts already exists. Overwrite?",!1)){O("Initialization cancelled");return}}let Y=J?.name||await I0("Project name","my-app"),Q=J?.mode||await TJ("Select deployment mode",["serverless","server","hybrid"]),G=J?.region||await TJ("Select AWS region",["us-east-1","us-west-2","eu-west-1","eu-central-1","ap-southeast-1"]),Z=new P("Creating configuration file...");Z.start();let W=`import { defineConfig } from '@ts-cloud/core'
13566
13566
 
13567
13567
  export default defineConfig({
13568
13568
  project: {
package/dist/index.js CHANGED
@@ -26262,7 +26262,7 @@ function composeServerlessAppTemplate(opts) {
26262
26262
  ...app.assetDomain ? {
26263
26263
  Aliases: [app.assetDomain],
26264
26264
  ViewerCertificate: {
26265
- AcmCertificateArn: app.assetCertificateArn,
26265
+ AcmCertificateArn: app.assetCertificateArn ?? Fn2.ref("AssetsCertificate"),
26266
26266
  SslSupportMethod: "sni-only",
26267
26267
  MinimumProtocolVersion: "TLSv1.2_2021"
26268
26268
  }
@@ -26271,8 +26271,20 @@ function composeServerlessAppTemplate(opts) {
26271
26271
  }
26272
26272
  };
26273
26273
  if (app.assetDomain) {
26274
- if (!app.assetCertificateArn)
26275
- throw new Error("serverless app: `assetDomain` requires `assetCertificateArn` (a us-east-1 ACM cert — CloudFront only accepts certs from us-east-1).");
26274
+ if (!app.assetCertificateArn) {
26275
+ if (!app.hostedZoneId)
26276
+ throw new Error("serverless app: `assetDomain` requires either `assetCertificateArn` (a us-east-1 ACM cert) or `hostedZoneId` (to auto-issue + validate one). CloudFront only accepts certs from us-east-1.");
26277
+ if (region !== "us-east-1")
26278
+ throw new Error(`serverless app: auto-issuing an asset-domain cert needs a us-east-1 app (CloudFront certs must be us-east-1); this app is in ${region}. Supply a pre-issued us-east-1 \`assetCertificateArn\` instead.`);
26279
+ resources.AssetsCertificate = {
26280
+ Type: "AWS::CertificateManager::Certificate",
26281
+ Properties: {
26282
+ DomainName: app.assetDomain,
26283
+ ValidationMethod: "DNS",
26284
+ DomainValidationOptions: [{ DomainName: app.assetDomain, HostedZoneId: app.hostedZoneId }]
26285
+ }
26286
+ };
26287
+ }
26276
26288
  if (app.hostedZoneId) {
26277
26289
  resources.AssetsDomainRecord = {
26278
26290
  Type: "AWS::Route53::RecordSet",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/ts-cloud",
3
3
  "type": "module",
4
- "version": "0.5.4",
4
+ "version": "0.5.5",
5
5
  "description": "A lightweight, performant infrastructure-as-code library and CLI for deploying both server-based (EC2) and serverless applications.",
6
6
  "author": "Chris Breuer <chris@stacksjs.com>",
7
7
  "license": "MIT",
@@ -89,8 +89,8 @@
89
89
  "test": "bun test"
90
90
  },
91
91
  "dependencies": {
92
- "@ts-cloud/aws-types": "0.5.4",
93
- "@ts-cloud/core": "0.5.4",
92
+ "@ts-cloud/aws-types": "0.5.5",
93
+ "@ts-cloud/core": "0.5.5",
94
94
  "@stacksjs/ts-xml": "^0.1.0"
95
95
  },
96
96
  "devDependencies": {