@stacksjs/ts-cloud 0.2.7 → 0.2.9
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
|
@@ -288,7 +288,7 @@ ${Object.entries($).filter(([W])=>!W.startsWith("@_")).map(([W,G])=>Z(W,G," ","
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
return request;
|
|
291
|
-
}`}};let H={Enabled:!0,DefaultRootObject:W,HttpVersion:"http2and3",IPV6Enabled:!0,PriceClass:"PriceClass_100",Origins:[{Id:`S3-${J}`,DomainName:{"Fn::GetAtt":["S3Bucket","RegionalDomainName"]},S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{"Fn::GetAtt":["CloudFrontOAC","Id"]}}],DefaultCacheBehavior:{TargetOriginId:`S3-${J}`,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:["GET","HEAD"],CachedMethods:["GET","HEAD"],Compress:!0,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6",FunctionAssociations:[{EventType:"viewer-request",FunctionARN:{"Fn::GetAtt":["UrlRewriteFunction","FunctionARN"]}}]},CustomErrorResponses:[{ErrorCode:403,ResponseCode:200,ResponsePagePath:`/${W}`,ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:404,ResponsePagePath:`/${G}`,ErrorCachingMinTTL:300}]};if(Y&&Q)H.Aliases=[Y],H.ViewerCertificate={AcmCertificateArn:Q,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"};else H.ViewerCertificate={CloudFrontDefaultCertificate:!0};if(U.CloudFrontDistribution={Type:"AWS::CloudFront::Distribution",DependsOn:["S3Bucket","CloudFrontOAC","UrlRewriteFunction"],Properties:{DistributionConfig:H}},F.DistributionId={Description:"CloudFront Distribution ID",Value:{Ref:"CloudFrontDistribution"}},F.DistributionDomain={Description:"CloudFront Distribution Domain",Value:{"Fn::GetAtt":["CloudFrontDistribution","DomainName"]}},U.S3BucketPolicy={Type:"AWS::S3::BucketPolicy",DependsOn:["S3Bucket","CloudFrontDistribution"],Properties:{Bucket:{Ref:"S3Bucket"},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":"arn:aws:s3:::${S3Bucket}/*"},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":"arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}"}}}}]}}},Y&&Z)U.DNSRecord={Type:"AWS::Route53::RecordSet",DependsOn:"CloudFrontDistribution",Properties:{HostedZoneId:Z,Name:Y,Type:"A",AliasTarget:{DNSName:{"Fn::GetAtt":["CloudFrontDistribution","DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}},U.DNSRecordIPv6={Type:"AWS::Route53::RecordSet",DependsOn:"CloudFrontDistribution",Properties:{HostedZoneId:Z,Name:Y,Type:"AAAA",AliasTarget:{DNSName:{"Fn::GetAtt":["CloudFrontDistribution","DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}},F.SiteUrl={Description:"Site URL",Value:{"Fn::Sub":"https://${DNSRecord}"}};return{AWSTemplateFormatVersion:"2010-09-09",Description:`Static site infrastructure for ${Y||J}`,Resources:U,Outputs:F}}async function f8($){if($.dnsProvider&&$.dnsProvider.provider!=="route53"){let w=$.domain||($.subdomain&&$.baseDomain?`${$.subdomain}.${$.baseDomain}`:void 0);if(!w)return{success:!1,stackName:$.stackName||`${$.siteName}-static-site`,bucket:$.bucket||`${$.siteName}-${Date.now()}`,message:"Domain is required when using external DNS provider"};return x6({siteName:$.siteName,region:$.region,domain:w,bucket:$.bucket,certificateArn:$.certificateArn,stackName:$.stackName,defaultRootObject:$.defaultRootObject,errorDocument:$.errorDocument,cacheControl:$.cacheControl,tags:$.tags,dnsProvider:$.dnsProvider})}let J=$.region||"us-east-1",Y="us-east-1",Q;if($.domain)Q=$.domain;else if($.subdomain&&$.baseDomain)Q=`${$.subdomain}.${$.baseDomain}`;let Z=$.bucket||(Q?Q.replace(/\./g,"-"):`${$.siteName}-${Date.now()}`),W=$.stackName||`${$.siteName}-static-site`,G=new F0(Y),U=new N0,F=new L0("us-east-1"),H=new h2("us-east-1"),B=$.hostedZoneId,K=$.certificateArn;if(Q&&!B){let w=await U.findHostedZoneForDomain(Q);if(w)B=w.Id.replace("/hostedzone/","");else return{success:!1,stackName:W,bucket:Z,message:`No Route53 hosted zone found for ${$.baseDomain||Q}. Please create one first.`}}if(Q&&!K&&B){let w=await F.findCertificateByDomain(Q);if(w&&w.Status==="ISSUED")K=w.CertificateArn;else K=(await H.requestAndValidate({domainName:Q,hostedZoneId:B,waitForValidation:!0,maxWaitMinutes:10})).certificateArn}let A=!1,O;try{let w=await G.describeStacks({stackName:W});if(w.Stacks.length>0){let S=w.Stacks[0],k=S.StackStatus;if(k==="DELETE_IN_PROGRESS")console.log("Previous stack is still being deleted, waiting..."),await G.waitForStack(W,"stack-delete-complete"),A=!1;else if(k==="DELETE_COMPLETE")A=!1;else A=!0,O=(S.Outputs||[]).find((I)=>I.OutputKey==="BucketName")?.OutputValue}}catch(w){if(w.message?.includes("does not exist")||w.code==="ValidationError")A=!1;else throw w}let E=O||Z;if(!A){let w=new H0(J),S=new K0,k=!1;if(Q){try{console.log(`Checking for existing CloudFront distribution for ${Q}...`);let x=await S.listDistributions();for(let I of x){let C=[];if(I.Aliases?.Items){if(Array.isArray(I.Aliases.Items))C=I.Aliases.Items;else if(typeof I.Aliases.Items==="object"){let f=I.Aliases.Items.CNAME;if(typeof f==="string")C=[f];else if(Array.isArray(f))C=f}}if(C.includes(Q)){k=!0,console.log(`Found existing CloudFront distribution ${I.Id} for ${Q}`);let d=(await S.getDistributionConfig(I.Id)).DistributionConfig?.Origins?.Items,r;if(d){let c=[];if(Array.isArray(d))c=d;else if(d.Origin)c=Array.isArray(d.Origin)?d.Origin:[d.Origin];else c=[d];for(let a of c){let J0=(a.DomainName||"").match(/^([^.]+)\.s3[\.-]/);if(J0){r=J0[1];break}}}if(r){let c=Q.replace(/\./g,"-");if(!r.startsWith(c)&&!r.includes($.siteName)){console.log(`Warning: Found distribution with mismatched bucket ${r}, skipping...`);continue}if(console.log(`Using existing S3 bucket: ${r}`),B&&I.DomainName)try{console.log(`Ensuring Route53 records exist for ${Q}...`),await U.createAliasRecord({HostedZoneId:B,Name:Q,Type:"A",TargetHostedZoneId:N0.CloudFrontHostedZoneId,TargetDNSName:I.DomainName,EvaluateTargetHealth:!1}),await U.createAliasRecord({HostedZoneId:B,Name:Q,Type:"AAAA",TargetHostedZoneId:N0.CloudFrontHostedZoneId,TargetDNSName:I.DomainName,EvaluateTargetHealth:!1}),console.log(`Route53 records ensured for ${Q}`)}catch(a){console.log(`Note: Could not update Route53 records: ${a.message}`)}return{success:!0,stackName:`existing-${I.Id}`,bucket:r,distributionId:I.Id,distributionDomain:I.DomainName,domain:Q,certificateArn:K,message:"Using existing CloudFront distribution"}}}}}catch{}if(B)try{let I=(await U.listResourceRecordSets({HostedZoneId:B})).ResourceRecordSets||[];for(let C of I)if(C.Name===`${Q}.`&&(C.Type==="A"||C.Type==="AAAA")){if(C.AliasTarget){console.log(`Found orphaned Route53 ${C.Type} record for ${Q}, cleaning up...`);try{await U.deleteRecord({HostedZoneId:B,RecordSet:C}),console.log(`Deleted orphaned Route53 ${C.Type} record for ${Q}`)}catch(f){console.log(`Note: Could not delete Route53 record: ${f.message}`)}}}}catch{}}if(!k)try{if((await w.headBucket(Z)).exists){console.log(`Found orphaned S3 bucket ${Z}, cleaning up...`);try{let I=w.emptyBucket(Z).then(()=>w.deleteBucket(Z)),C=new Promise((f,d)=>setTimeout(()=>d(Error("Bucket cleanup timeout")),30000));await Promise.race([I,C]),console.log(`Deleted orphaned S3 bucket ${Z}`)}catch(I){console.log(`Note: Could not clean up S3 bucket: ${I.message}`);let C=Date.now().toString(36);E=`${Z}-${C}`,console.log(`Using alternative bucket name: ${E}`)}}}catch{}}let j=v8({bucketName:E,domain:Q,certificateArn:K,hostedZoneId:B,defaultRootObject:$.defaultRootObject,errorDocument:$.errorDocument}),V=Object.entries($.tags||{}).map(([w,S])=>({Key:w,Value:S}));V.push({Key:"ManagedBy",Value:"ts-cloud"}),V.push({Key:"Application",Value:$.siteName});let X,_=!1;if(A){_=!0,console.log(`Updating CloudFormation stack: ${W}`),console.log(`Using existing bucket: ${E}`),console.log(`Domain: ${Q||"not specified"}`),console.log(`Certificate ARN: ${K||"not specified"}`);try{X=(await G.updateStack({stackName:W,templateBody:JSON.stringify(j),capabilities:["CAPABILITY_IAM"],tags:V})).StackId,console.log(`Update initiated, stack ID: ${X}`)}catch(w){if(w.message?.includes("No updates are to be performed")){let S=await G.describeStacks({stackName:W});X=S.Stacks[0].StackId;let k=S.Stacks[0]?.Outputs||[],x=(I)=>k.find((C)=>C.OutputKey===I)?.OutputValue;return{success:!0,stackId:X,stackName:W,bucket:x("BucketName")||E,distributionId:x("DistributionId"),distributionDomain:x("DistributionDomain"),domain:Q,certificateArn:K,message:"Static site infrastructure is already up to date"}}else throw w}}else console.log(`Creating CloudFormation stack: ${W}`),console.log(`Bucket name: ${E}`),console.log(`Domain: ${Q||"not specified"}`),console.log(`Certificate ARN: ${K||"not specified"}`),console.log("Stack does not exist, creating..."),X=(await G.createStack({stackName:W,templateBody:JSON.stringify(j),capabilities:["CAPABILITY_IAM"],tags:V,onFailure:"DELETE"})).StackId,console.log(`Create initiated, stack ID: ${X}`);console.log(`Waiting for stack to reach ${_?"stack-update-complete":"stack-create-complete"}...`);try{await G.waitForStack(W,_?"stack-update-complete":"stack-create-complete"),console.log("Stack operation completed successfully!")}catch(w){if(w.message?.includes("must be verified")||w.message?.includes("Access denied for operation")||w.message?.includes("failed")){console.log("CloudFormation deployment failed, trying direct API creation...");let S=new K0;if(Q)try{let k=await S.listDistributions();for(let x of k){let I=[];if(x.Aliases?.Items){if(Array.isArray(x.Aliases.Items))I=x.Aliases.Items;else if(typeof x.Aliases.Items==="object"){let C=x.Aliases.Items.CNAME;if(typeof C==="string")I=[C];else if(Array.isArray(C))I=C}}if(I.includes(Q)){console.log(`Found existing CloudFront distribution ${x.Id} with alias ${Q}`);let f=(await S.getDistributionConfig(x.Id)).DistributionConfig?.Origins?.Items,d;if(f){let r=[];if(Array.isArray(f))r=f;else if(f.Origin)r=Array.isArray(f.Origin)?f.Origin:[f.Origin];else r=[f];for(let c of r){let s=(c.DomainName||"").match(/^([^.]+)\.s3[\.-]/);if(s){d=s[1];break}}}if(d)return console.log(`Using existing S3 bucket: ${d}`),{success:!0,stackName:`existing-${x.Id}`,bucket:d,distributionId:x.Id,distributionDomain:x.DomainName,domain:Q,certificateArn:K,message:"Using existing CloudFront distribution (account verification pending for new distributions)"}}}}catch{}console.log("No existing infrastructure found, creating via direct API calls...");try{let k=new H0(J);if((await k.headBucket(E)).exists)console.log(`Using existing S3 bucket: ${E}`);else console.log(`Creating S3 bucket: ${E}...`),await k.createBucket(E);await k.putBucketWebsite(E,{IndexDocument:$.defaultRootObject||"index.html",ErrorDocument:$.errorDocument||"404.html"}),await k.putPublicAccessBlock(E,{BlockPublicAcls:!0,IgnorePublicAcls:!0,BlockPublicPolicy:!1,RestrictPublicBuckets:!1}),console.log(`S3 bucket ${E} configured`);let I=`OAC-${E}`;console.log(`Creating Origin Access Control: ${I}...`);let C=await S.findOrCreateOriginAccessControl(I);console.log(`Origin Access Control ${C.Id} ready`),console.log("Creating CloudFront distribution...");let f=await S.createDistributionForS3({bucketName:E,bucketRegion:J,originAccessControlId:C.Id,aliases:Q?[Q]:[],certificateArn:K,defaultRootObject:$.defaultRootObject||"index.html",comment:`Distribution for ${Q||E}`});console.log(`CloudFront distribution ${f.Id} created`),console.log("Updating S3 bucket policy...");let d=K0.getS3BucketPolicyForCloudFront(E,f.ARN);if(await k.putBucketPolicy(E,d),console.log("S3 bucket policy updated"),Q&&B){console.log(`Creating Route53 records for ${Q}...`);try{await U.createAliasRecord({HostedZoneId:B,Name:Q,Type:"A",TargetHostedZoneId:N0.CloudFrontHostedZoneId,TargetDNSName:f.DomainName,EvaluateTargetHealth:!1}),await U.createAliasRecord({HostedZoneId:B,Name:Q,Type:"AAAA",TargetHostedZoneId:N0.CloudFrontHostedZoneId,TargetDNSName:f.DomainName,EvaluateTargetHealth:!1}),console.log(`Route53 records created for ${Q}`)}catch(r){console.log(`Note: Could not create Route53 records: ${r.message}`)}}return{success:!0,stackName:`direct-${f.Id}`,bucket:E,distributionId:f.Id,distributionDomain:f.DomainName,domain:Q,certificateArn:K,message:"Static site infrastructure created via direct API calls"}}catch(k){return console.log(`Direct API creation failed: ${k.message}`),{success:!1,stackId:X,stackName:W,bucket:E,message:`Deployment failed: ${k.message}`}}}return{success:!1,stackId:X,stackName:W,bucket:E,message:`Stack deployment failed: ${w.message}`}}let N=(await G.describeStacks({stackName:W})).Stacks[0]?.Outputs||[],D=(w)=>N.find((S)=>S.OutputKey===w)?.OutputValue;return{success:!0,stackId:X,stackName:W,bucket:D("BucketName")||E,distributionId:D("DistributionId"),distributionDomain:D("DistributionDomain"),domain:Q,certificateArn:K,message:"Static site infrastructure deployed successfully"}}async function g8($){let{sourceDir:J,bucket:Y,region:Q,cacheControl:Z="max-age=31536000, public",onProgress:W}=$,G=new H0(Q),{readdir:U}=await import("node:fs/promises"),{join:F,relative:H}=await import("node:path"),{createHash:B}=await import("node:crypto");async function K(N){let D=[],w=await U(N,{withFileTypes:!0});for(let S of w){let k=F(N,S.name);if(S.isDirectory())D.push(...await K(k));else D.push(k)}return D}function A(N){let D=N.split(".").pop()?.toLowerCase();return{html:"text/html; charset=utf-8",css:"text/css; charset=utf-8",js:"application/javascript; charset=utf-8",json:"application/json; charset=utf-8",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",svg:"image/svg+xml",ico:"image/x-icon",webp:"image/webp",woff:"font/woff",woff2:"font/woff2",ttf:"font/ttf",xml:"application/xml",txt:"text/plain; charset=utf-8",sh:"text/plain; charset=utf-8",bash:"text/plain; charset=utf-8",ps1:"text/plain; charset=utf-8"}[D||""]||"application/octet-stream"}function O(N){return B("md5").update(N).digest("hex")}async function E(){let N=new Map,D;do{let w=await G.listObjects({bucket:Y,maxKeys:1000,continuationToken:D});for(let S of w.objects){let k=S.ETag?.replace(/"/g,"")||"";N.set(S.Key,k)}D=w.nextContinuationToken}while(D);return N}let j=await K(J),V=[],X=0,_=0,T=await E();for(let N of j){let D=H(J,N),w=A(N),S=N.endsWith(".html")?"max-age=3600, public":Z;try{let k=Buffer.from(await Bun.file(N).arrayBuffer()),x=O(k),I=T.get(D);if(I&&I===x){_++,W?.(X+_,j.length,D);continue}await G.putObject({bucket:Y,key:D,body:k,contentType:w,cacheControl:S}),X++,W?.(X+_,j.length,D)}catch(k){V.push(`Failed to upload ${D}: ${k.message}`)}}return{uploaded:X,skipped:_,errors:V}}async function u8($){return{invalidationId:(await new K0().invalidateAll($)).Id}}async function O4($,J="us-east-1"){let Y=new F0(J);try{let G=((await Y.describeStacks({stackName:$})).Stacks[0]?.Outputs||[]).find((U)=>U.OutputKey==="BucketName")?.OutputValue;if(G)await new H0(J).emptyBucket(G)}catch{}await Y.deleteStack($);let Q=await Y.waitForStackComplete($,60,1e4);return{success:Q.success||Q.status==="DELETE_COMPLETE",message:Q.success?"Static site deleted successfully":`Deletion failed: ${Q.status}`}}async function j4($){let{sourceDir:J,cleanBucket:Y=!1,onProgress:Q,...Z}=$;Q?.("infrastructure","Deploying CloudFormation stack...");let W=await f8(Z);if(!W.success)return W;if(Y){Q?.("clean","Cleaning old files from S3...");try{await new H0(Z.region||"us-east-1").emptyBucket(W.bucket)}catch(F){console.log(`Note: Could not clean bucket: ${F.message}`)}}Q?.("upload","Uploading files to S3...");let G=await g8({sourceDir:J,bucket:W.bucket,region:Z.region||"us-east-1",cacheControl:Z.cacheControl,onProgress:(F,H,B)=>{Q?.("upload",`${F}/${H}: ${B}`)}});if(G.errors.length>0)return{...W,success:!1,message:`Upload errors: ${G.errors.join(", ")}`,filesUploaded:G.uploaded};if(W.distributionId&&G.uploaded>0)Q?.("invalidate","Invalidating CloudFront cache..."),await u8(W.distributionId);Q?.("complete","Deployment complete!");let U=G.skipped>0?`Deployed ${G.uploaded} files (${G.skipped} unchanged)`:`Deployed ${G.uploaded} files successfully`;return{...W,filesUploaded:G.uploaded,filesSkipped:G.skipped,message:U}}var n2=_0(()=>{$1();A1();O1();x2();U1();C6()});function E4($){let{bucketName:J,domain:Y,aliases:Q,certificateArn:Z,defaultRootObject:W="index.html",errorDocument:G="404.html",passthroughUrls:U=!1}=$,F={},H={};if(F.S3Bucket={Type:"AWS::S3::Bucket",Properties:{BucketName:J,PublicAccessBlockConfiguration:{BlockPublicAcls:!0,BlockPublicPolicy:!1,IgnorePublicAcls:!0,RestrictPublicBuckets:!1},WebsiteConfiguration:{IndexDocument:W,ErrorDocument:G}}},H.BucketName={Description:"S3 Bucket Name",Value:{Ref:"S3Bucket"}},H.BucketArn={Description:"S3 Bucket ARN",Value:{"Fn::GetAtt":["S3Bucket","Arn"]}},F.CloudFrontOAC={Type:"AWS::CloudFront::OriginAccessControl",Properties:{OriginAccessControlConfig:{Name:`OAC-${J}`,Description:`OAC for ${J}`,OriginAccessControlOriginType:"s3",SigningBehavior:"always",SigningProtocol:"sigv4"}}},!U)F.UrlRewriteFunction={Type:"AWS::CloudFront::Function",Properties:{Name:{"Fn::Sub":"${AWS::StackName}-url-rewrite"},AutoPublish:!0,FunctionConfig:{Comment:"Append .html extension to URLs without extensions",Runtime:"cloudfront-js-2.0"},FunctionCode:`function handler(event) {
|
|
291
|
+
}`}};let H={Enabled:!0,DefaultRootObject:W,HttpVersion:"http2and3",IPV6Enabled:!0,PriceClass:"PriceClass_100",Origins:[{Id:`S3-${J}`,DomainName:{"Fn::GetAtt":["S3Bucket","RegionalDomainName"]},S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{"Fn::GetAtt":["CloudFrontOAC","Id"]}}],DefaultCacheBehavior:{TargetOriginId:`S3-${J}`,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:["GET","HEAD"],CachedMethods:["GET","HEAD"],Compress:!0,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6",FunctionAssociations:[{EventType:"viewer-request",FunctionARN:{"Fn::GetAtt":["UrlRewriteFunction","FunctionARN"]}}]},CustomErrorResponses:[{ErrorCode:403,ResponseCode:200,ResponsePagePath:`/${W}`,ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:404,ResponsePagePath:`/${G}`,ErrorCachingMinTTL:300}]};if(Y&&Q)H.Aliases=[Y],H.ViewerCertificate={AcmCertificateArn:Q,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"};else H.ViewerCertificate={CloudFrontDefaultCertificate:!0};if(U.CloudFrontDistribution={Type:"AWS::CloudFront::Distribution",DependsOn:["S3Bucket","CloudFrontOAC","UrlRewriteFunction"],Properties:{DistributionConfig:H}},F.DistributionId={Description:"CloudFront Distribution ID",Value:{Ref:"CloudFrontDistribution"}},F.DistributionDomain={Description:"CloudFront Distribution Domain",Value:{"Fn::GetAtt":["CloudFrontDistribution","DomainName"]}},U.S3BucketPolicy={Type:"AWS::S3::BucketPolicy",DependsOn:["S3Bucket","CloudFrontDistribution"],Properties:{Bucket:{Ref:"S3Bucket"},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":"arn:aws:s3:::${S3Bucket}/*"},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":"arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}"}}}}]}}},Y&&Z)U.DNSRecord={Type:"AWS::Route53::RecordSet",DependsOn:"CloudFrontDistribution",Properties:{HostedZoneId:Z,Name:Y,Type:"A",AliasTarget:{DNSName:{"Fn::GetAtt":["CloudFrontDistribution","DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}},U.DNSRecordIPv6={Type:"AWS::Route53::RecordSet",DependsOn:"CloudFrontDistribution",Properties:{HostedZoneId:Z,Name:Y,Type:"AAAA",AliasTarget:{DNSName:{"Fn::GetAtt":["CloudFrontDistribution","DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}},F.SiteUrl={Description:"Site URL",Value:{"Fn::Sub":"https://${DNSRecord}"}};return{AWSTemplateFormatVersion:"2010-09-09",Description:`Static site infrastructure for ${Y||J}`,Resources:U,Outputs:F}}async function f8($){if($.dnsProvider&&$.dnsProvider.provider!=="route53"){let w=$.domain||($.subdomain&&$.baseDomain?`${$.subdomain}.${$.baseDomain}`:void 0);if(!w)return{success:!1,stackName:$.stackName||`${$.siteName}-static-site`,bucket:$.bucket||`${$.siteName}-${Date.now()}`,message:"Domain is required when using external DNS provider"};return x6({siteName:$.siteName,region:$.region,domain:w,bucket:$.bucket,certificateArn:$.certificateArn,stackName:$.stackName,defaultRootObject:$.defaultRootObject,errorDocument:$.errorDocument,cacheControl:$.cacheControl,tags:$.tags,dnsProvider:$.dnsProvider})}let J=$.region||"us-east-1",Y="us-east-1",Q;if($.domain)Q=$.domain;else if($.subdomain&&$.baseDomain)Q=`${$.subdomain}.${$.baseDomain}`;let Z=$.bucket||(Q?Q.replace(/\./g,"-"):`${$.siteName}-${Date.now()}`),W=$.stackName||`${$.siteName}-static-site`,G=new F0(Y),U=new N0,F=new L0("us-east-1"),H=new h2("us-east-1"),B=$.hostedZoneId,K=$.certificateArn;if(Q&&!B){let w=await U.findHostedZoneForDomain(Q);if(w)B=w.Id.replace("/hostedzone/","");else return{success:!1,stackName:W,bucket:Z,message:`No Route53 hosted zone found for ${$.baseDomain||Q}. Please create one first.`}}if(Q&&!K&&B){let w=await F.findCertificateByDomain(Q);if(w&&w.Status==="ISSUED")K=w.CertificateArn;else K=(await H.requestAndValidate({domainName:Q,hostedZoneId:B,waitForValidation:!0,maxWaitMinutes:10})).certificateArn}let A=!1,O;try{let w=await G.describeStacks({stackName:W});if(w.Stacks.length>0){let S=w.Stacks[0],k=S.StackStatus;if(k==="DELETE_IN_PROGRESS")console.log("Previous stack is still being deleted, waiting..."),await G.waitForStack(W,"stack-delete-complete"),A=!1;else if(k==="DELETE_COMPLETE")A=!1;else A=!0,O=(S.Outputs||[]).find((I)=>I.OutputKey==="BucketName")?.OutputValue}}catch(w){if(w.message?.includes("does not exist")||w.code==="ValidationError")A=!1;else throw w}let E=O||Z;if(!A){let w=new H0(J),S=new K0,k=!1;if(Q){try{console.log(`Checking for existing CloudFront distribution for ${Q}...`);let x=await S.listDistributions();for(let I of x){let C=[];if(I.Aliases?.Items){if(Array.isArray(I.Aliases.Items))C=I.Aliases.Items;else if(typeof I.Aliases.Items==="object"){let f=I.Aliases.Items.CNAME;if(typeof f==="string")C=[f];else if(Array.isArray(f))C=f}}if(C.includes(Q)){k=!0,console.log(`Found existing CloudFront distribution ${I.Id} for ${Q}`);let d=(await S.getDistributionConfig(I.Id)).DistributionConfig?.Origins?.Items,r;if(d){let c=[];if(Array.isArray(d))c=d;else if(d.Origin)c=Array.isArray(d.Origin)?d.Origin:[d.Origin];else c=[d];for(let a of c){let J0=(a.DomainName||"").match(/^([^.]+)\.s3[\.-]/);if(J0){r=J0[1];break}}}if(r){let c=Q.replace(/\./g,"-");if(!r.startsWith(c)&&!r.includes($.siteName)){console.log(`Warning: Found distribution with mismatched bucket ${r}, skipping...`);continue}if(console.log(`Using existing S3 bucket: ${r}`),B&&I.DomainName)try{console.log(`Ensuring Route53 records exist for ${Q}...`),await U.createAliasRecord({HostedZoneId:B,Name:Q,Type:"A",TargetHostedZoneId:N0.CloudFrontHostedZoneId,TargetDNSName:I.DomainName,EvaluateTargetHealth:!1}),await U.createAliasRecord({HostedZoneId:B,Name:Q,Type:"AAAA",TargetHostedZoneId:N0.CloudFrontHostedZoneId,TargetDNSName:I.DomainName,EvaluateTargetHealth:!1}),console.log(`Route53 records ensured for ${Q}`)}catch(a){console.log(`Note: Could not update Route53 records: ${a.message}`)}return{success:!0,stackName:`existing-${I.Id}`,bucket:r,distributionId:I.Id,distributionDomain:I.DomainName,domain:Q,certificateArn:K,message:"Using existing CloudFront distribution"}}}}}catch{}if(B)try{let I=(await U.listResourceRecordSets({HostedZoneId:B})).ResourceRecordSets||[];for(let C of I)if(C.Name===`${Q}.`&&(C.Type==="A"||C.Type==="AAAA")){if(C.AliasTarget){console.log(`Found orphaned Route53 ${C.Type} record for ${Q}, cleaning up...`);try{await U.deleteRecord({HostedZoneId:B,RecordSet:C}),console.log(`Deleted orphaned Route53 ${C.Type} record for ${Q}`)}catch(f){console.log(`Note: Could not delete Route53 record: ${f.message}`)}}}}catch{}}if(!k)try{if((await w.headBucket(Z)).exists){console.log(`Found orphaned S3 bucket ${Z}, cleaning up...`);try{let I=w.emptyBucket(Z).then(()=>w.deleteBucket(Z)),C=new Promise((f,d)=>setTimeout(()=>d(Error("Bucket cleanup timeout")),30000));await Promise.race([I,C]),console.log(`Deleted orphaned S3 bucket ${Z}`)}catch(I){console.log(`Note: Could not clean up S3 bucket: ${I.message}`);let C=Date.now().toString(36);E=`${Z}-${C}`,console.log(`Using alternative bucket name: ${E}`)}}}catch{}}let j=v8({bucketName:E,domain:Q,certificateArn:K,hostedZoneId:B,defaultRootObject:$.defaultRootObject,errorDocument:$.errorDocument}),V=Object.entries($.tags||{}).map(([w,S])=>({Key:w,Value:S}));V.push({Key:"ManagedBy",Value:"ts-cloud"}),V.push({Key:"Application",Value:$.siteName});let X,_=!1;if(A){_=!0,console.log(`Updating CloudFormation stack: ${W}`),console.log(`Using existing bucket: ${E}`),console.log(`Domain: ${Q||"not specified"}`),console.log(`Certificate ARN: ${K||"not specified"}`);try{X=(await G.updateStack({stackName:W,templateBody:JSON.stringify(j),capabilities:["CAPABILITY_IAM"],tags:V})).StackId,console.log(`Update initiated, stack ID: ${X}`)}catch(w){if(w.message?.includes("No updates are to be performed")){let S=await G.describeStacks({stackName:W});X=S.Stacks[0].StackId;let k=S.Stacks[0]?.Outputs||[],x=(I)=>k.find((C)=>C.OutputKey===I)?.OutputValue;return{success:!0,stackId:X,stackName:W,bucket:x("BucketName")||E,distributionId:x("DistributionId"),distributionDomain:x("DistributionDomain"),domain:Q,certificateArn:K,message:"Static site infrastructure is already up to date"}}else throw w}}else console.log(`Creating CloudFormation stack: ${W}`),console.log(`Bucket name: ${E}`),console.log(`Domain: ${Q||"not specified"}`),console.log(`Certificate ARN: ${K||"not specified"}`),console.log("Stack does not exist, creating..."),X=(await G.createStack({stackName:W,templateBody:JSON.stringify(j),capabilities:["CAPABILITY_IAM"],tags:V,onFailure:"DELETE"})).StackId,console.log(`Create initiated, stack ID: ${X}`);console.log(`Waiting for stack to reach ${_?"stack-update-complete":"stack-create-complete"}...`);try{await G.waitForStack(W,_?"stack-update-complete":"stack-create-complete"),console.log("Stack operation completed successfully!")}catch(w){if(w.message?.includes("must be verified")||w.message?.includes("Access denied for operation")||w.message?.includes("failed")){console.log("CloudFormation deployment failed, trying direct API creation...");let S=new K0;if(Q)try{let k=await S.listDistributions();for(let x of k){let I=[];if(x.Aliases?.Items){if(Array.isArray(x.Aliases.Items))I=x.Aliases.Items;else if(typeof x.Aliases.Items==="object"){let C=x.Aliases.Items.CNAME;if(typeof C==="string")I=[C];else if(Array.isArray(C))I=C}}if(I.includes(Q)){console.log(`Found existing CloudFront distribution ${x.Id} with alias ${Q}`);let f=(await S.getDistributionConfig(x.Id)).DistributionConfig?.Origins?.Items,d;if(f){let r=[];if(Array.isArray(f))r=f;else if(f.Origin)r=Array.isArray(f.Origin)?f.Origin:[f.Origin];else r=[f];for(let c of r){let s=(c.DomainName||"").match(/^([^.]+)\.s3[\.-]/);if(s){d=s[1];break}}}if(d)return console.log(`Using existing S3 bucket: ${d}`),{success:!0,stackName:`existing-${x.Id}`,bucket:d,distributionId:x.Id,distributionDomain:x.DomainName,domain:Q,certificateArn:K,message:"Using existing CloudFront distribution (account verification pending for new distributions)"}}}}catch{}console.log("No existing infrastructure found, creating via direct API calls...");try{let k=new H0(J);if((await k.headBucket(E)).exists)console.log(`Using existing S3 bucket: ${E}`);else console.log(`Creating S3 bucket: ${E}...`),await k.createBucket(E);await k.putBucketWebsite(E,{IndexDocument:$.defaultRootObject||"index.html",ErrorDocument:$.errorDocument||"404.html"}),await k.putPublicAccessBlock(E,{BlockPublicAcls:!0,IgnorePublicAcls:!0,BlockPublicPolicy:!1,RestrictPublicBuckets:!1}),console.log(`S3 bucket ${E} configured`);let I=`OAC-${E}`;console.log(`Creating Origin Access Control: ${I}...`);let C=await S.findOrCreateOriginAccessControl(I);console.log(`Origin Access Control ${C.Id} ready`),console.log("Creating CloudFront distribution...");let f=await S.createDistributionForS3({bucketName:E,bucketRegion:J,originAccessControlId:C.Id,aliases:Q?[Q]:[],certificateArn:K,defaultRootObject:$.defaultRootObject||"index.html",comment:`Distribution for ${Q||E}`});console.log(`CloudFront distribution ${f.Id} created`),console.log("Updating S3 bucket policy...");let d=K0.getS3BucketPolicyForCloudFront(E,f.ARN);if(await k.putBucketPolicy(E,d),console.log("S3 bucket policy updated"),Q&&B){console.log(`Creating Route53 records for ${Q}...`);try{await U.createAliasRecord({HostedZoneId:B,Name:Q,Type:"A",TargetHostedZoneId:N0.CloudFrontHostedZoneId,TargetDNSName:f.DomainName,EvaluateTargetHealth:!1}),await U.createAliasRecord({HostedZoneId:B,Name:Q,Type:"AAAA",TargetHostedZoneId:N0.CloudFrontHostedZoneId,TargetDNSName:f.DomainName,EvaluateTargetHealth:!1}),console.log(`Route53 records created for ${Q}`)}catch(r){console.log(`Note: Could not create Route53 records: ${r.message}`)}}return{success:!0,stackName:`direct-${f.Id}`,bucket:E,distributionId:f.Id,distributionDomain:f.DomainName,domain:Q,certificateArn:K,message:"Static site infrastructure created via direct API calls"}}catch(k){return console.log(`Direct API creation failed: ${k.message}`),{success:!1,stackId:X,stackName:W,bucket:E,message:`Deployment failed: ${k.message}`}}}return{success:!1,stackId:X,stackName:W,bucket:E,message:`Stack deployment failed: ${w.message}`}}let N=(await G.describeStacks({stackName:W})).Stacks[0]?.Outputs||[],D=(w)=>N.find((S)=>S.OutputKey===w)?.OutputValue;return{success:!0,stackId:X,stackName:W,bucket:D("BucketName")||E,distributionId:D("DistributionId"),distributionDomain:D("DistributionDomain"),domain:Q,certificateArn:K,message:"Static site infrastructure deployed successfully"}}async function g8($){let{sourceDir:J,bucket:Y,region:Q,cacheControl:Z="max-age=31536000, public",onProgress:W}=$,G=new H0(Q),{readdir:U}=await import("node:fs/promises"),{join:F,relative:H}=await import("node:path"),{createHash:B}=await import("node:crypto");async function K(N){let D=[],w=await U(N,{withFileTypes:!0});for(let S of w){let k=F(N,S.name);if(S.isDirectory())D.push(...await K(k));else D.push(k)}return D}function A(N){let D=N.split(".").pop()?.toLowerCase();return{html:"text/html; charset=utf-8",css:"text/css; charset=utf-8",js:"application/javascript; charset=utf-8",json:"application/json; charset=utf-8",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",svg:"image/svg+xml",ico:"image/x-icon",webp:"image/webp",woff:"font/woff",woff2:"font/woff2",ttf:"font/ttf",xml:"application/xml",txt:"text/plain; charset=utf-8",sh:"text/plain; charset=utf-8",bash:"text/plain; charset=utf-8",ps1:"text/plain; charset=utf-8"}[D||""]||"application/octet-stream"}function O(N){return B("md5").update(N).digest("hex")}async function E(){let N=new Map,D;do{let w=await G.listObjects({bucket:Y,maxKeys:1000,continuationToken:D});for(let S of w.objects){let k=S.ETag?.replace(/"/g,"")||"";N.set(S.Key,k)}D=w.nextContinuationToken}while(D);return N}let j=await K(J),V=[],X=0,_=0,T=await E();for(let N of j){let D=H(J,N),w=A(N),S=N.endsWith(".html")?"max-age=3600, public":Z;try{let k=Buffer.from(await Bun.file(N).arrayBuffer()),x=O(k),I=T.get(D);if(I&&I===x){_++,W?.(X+_,j.length,D);continue}await G.putObject({bucket:Y,key:D,body:k,contentType:w,cacheControl:S}),X++,W?.(X+_,j.length,D)}catch(k){V.push(`Failed to upload ${D}: ${k.message}`)}}return{uploaded:X,skipped:_,errors:V}}async function u8($){return{invalidationId:(await new K0().invalidateAll($)).Id}}async function O4($,J="us-east-1"){let Y=new F0(J);try{let G=((await Y.describeStacks({stackName:$})).Stacks[0]?.Outputs||[]).find((U)=>U.OutputKey==="BucketName")?.OutputValue;if(G)await new H0(J).emptyBucket(G)}catch{}await Y.deleteStack($);let Q=await Y.waitForStackComplete($,60,1e4);return{success:Q.success||Q.status==="DELETE_COMPLETE",message:Q.success?"Static site deleted successfully":`Deletion failed: ${Q.status}`}}async function j4($){let{sourceDir:J,cleanBucket:Y=!1,onProgress:Q,...Z}=$;Q?.("infrastructure","Deploying CloudFormation stack...");let W=await f8(Z);if(!W.success)return W;if(Y){Q?.("clean","Cleaning old files from S3...");try{await new H0(Z.region||"us-east-1").emptyBucket(W.bucket)}catch(F){console.log(`Note: Could not clean bucket: ${F.message}`)}}Q?.("upload","Uploading files to S3...");let G=await g8({sourceDir:J,bucket:W.bucket,region:Z.region||"us-east-1",cacheControl:Z.cacheControl,onProgress:(F,H,B)=>{Q?.("upload",`${F}/${H}: ${B}`)}});if(G.errors.length>0)return{...W,success:!1,message:`Upload errors: ${G.errors.join(", ")}`,filesUploaded:G.uploaded};if(W.distributionId&&G.uploaded>0)Q?.("invalidate","Invalidating CloudFront cache..."),await u8(W.distributionId);Q?.("complete","Deployment complete!");let U=G.skipped>0?`Deployed ${G.uploaded} files (${G.skipped} unchanged)`:`Deployed ${G.uploaded} files successfully`;return{...W,filesUploaded:G.uploaded,filesSkipped:G.skipped,message:U}}var n2=_0(()=>{$1();A1();O1();x2();U1();C6()});function E4($){let{bucketName:J,domain:Y,aliases:Q,certificateArn:Z,defaultRootObject:W="index.html",errorDocument:G="404.html",passthroughUrls:U=!1,singlePageApp:F=!1}=$,H={},B={};if(H.S3Bucket={Type:"AWS::S3::Bucket",Properties:{BucketName:J,PublicAccessBlockConfiguration:{BlockPublicAcls:!0,BlockPublicPolicy:!1,IgnorePublicAcls:!0,RestrictPublicBuckets:!1},WebsiteConfiguration:{IndexDocument:W,ErrorDocument:G}}},B.BucketName={Description:"S3 Bucket Name",Value:{Ref:"S3Bucket"}},B.BucketArn={Description:"S3 Bucket ARN",Value:{"Fn::GetAtt":["S3Bucket","Arn"]}},H.CloudFrontOAC={Type:"AWS::CloudFront::OriginAccessControl",Properties:{OriginAccessControlConfig:{Name:`OAC-${J}`,Description:`OAC for ${J}`,OriginAccessControlOriginType:"s3",SigningBehavior:"always",SigningProtocol:"sigv4"}}},!U)H.UrlRewriteFunction={Type:"AWS::CloudFront::Function",Properties:{Name:{"Fn::Sub":"${AWS::StackName}-url-rewrite"},AutoPublish:!0,FunctionConfig:{Comment:"Append .html extension to URLs without extensions",Runtime:"cloudfront-js-2.0"},FunctionCode:`function handler(event) {
|
|
292
292
|
const request = event.request;
|
|
293
293
|
var uri = request.uri;
|
|
294
294
|
|
|
@@ -302,7 +302,7 @@ ${Object.entries($).filter(([W])=>!W.startsWith("@_")).map(([W,G])=>Z(W,G," ","
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
return request;
|
|
305
|
-
}`}};let
|
|
305
|
+
}`}};let K={Enabled:!0,DefaultRootObject:W,HttpVersion:"http2and3",IPV6Enabled:!0,PriceClass:"PriceClass_100",Origins:[{Id:`S3-${J}`,DomainName:{"Fn::GetAtt":["S3Bucket","RegionalDomainName"]},S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{"Fn::GetAtt":["CloudFrontOAC","Id"]}}],DefaultCacheBehavior:{TargetOriginId:`S3-${J}`,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:["GET","HEAD"],CachedMethods:["GET","HEAD"],Compress:!0,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6",...!U&&{FunctionAssociations:[{EventType:"viewer-request",FunctionARN:{"Fn::GetAtt":["UrlRewriteFunction","FunctionARN"]}}]}},CustomErrorResponses:F?[{ErrorCode:403,ResponseCode:200,ResponsePagePath:`/${W}`,ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:200,ResponsePagePath:`/${W}`,ErrorCachingMinTTL:300}]:[{ErrorCode:403,ResponseCode:404,ResponsePagePath:`/${G}`,ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:404,ResponsePagePath:`/${G}`,ErrorCachingMinTTL:300}]};if(Y&&Z)K.Aliases=Q&&Q.length>0?Q:[Y],K.ViewerCertificate={AcmCertificateArn:Z,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"};else K.ViewerCertificate={CloudFrontDefaultCertificate:!0};return H.CloudFrontDistribution={Type:"AWS::CloudFront::Distribution",DependsOn:U?["S3Bucket","CloudFrontOAC"]:["S3Bucket","CloudFrontOAC","UrlRewriteFunction"],Properties:{DistributionConfig:K}},B.DistributionId={Description:"CloudFront Distribution ID",Value:{Ref:"CloudFrontDistribution"}},B.DistributionDomain={Description:"CloudFront Distribution Domain",Value:{"Fn::GetAtt":["CloudFrontDistribution","DomainName"]}},H.S3BucketPolicy={Type:"AWS::S3::BucketPolicy",DependsOn:["S3Bucket","CloudFrontDistribution"],Properties:{Bucket:{Ref:"S3Bucket"},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":"arn:aws:s3:::${S3Bucket}/*"},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":"arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}"}}}}]}}},B.SiteUrl={Description:"Site URL",Value:Y?`https://${Y}`:{"Fn::Sub":"https://${CloudFrontDistribution.DomainName}"}},{AWSTemplateFormatVersion:"2010-09-09",Description:`Static site infrastructure for ${Y||J} (External DNS)`,Resources:H,Outputs:B}}async function x6($){let J=$.region||"us-east-1",Y="us-east-1",Q=$.domain,Z=$.bucket||Q.replace(/\./g,"-"),W=$.stackName||`${$.siteName}-static-site`,G=new F0("us-east-1"),U=new L0("us-east-1"),F=null;if(!$.skipDnsVerification){if(F=R0($.dnsProvider),console.log(`Verifying DNS provider can manage ${Q}...`),!await F.canManageDomain(Q))return{success:!1,stackName:W,bucket:Z,message:`DNS provider '${F.name}' cannot manage domain ${Q}. Please check your API credentials and domain ownership.`};console.log(`DNS provider '${F.name}' verified for ${Q}`)}else console.log(`Skipping DNS verification for ${Q}`);let H=$.certificateArn,A=Q.split(".").length===2?`www.${Q}`:void 0;if(!H){console.log(`Checking for existing SSL certificate for ${Q}...`);let x=await U.findCertificateByDomain(Q),I=!1;if(x&&x.Status==="ISSUED"){if(A&&x.SubjectAlternativeNames)I=x.SubjectAlternativeNames.includes(A)||x.SubjectAlternativeNames.some((C)=>C===`*.${Q}`);else I=!0;if(I)H=x.CertificateArn,console.log(`Found existing certificate with www coverage: ${H}`);else console.log(`Existing certificate doesn't cover ${A}, requesting new one...`)}if(!H){if(!F)return{success:!1,stackName:W,bucket:Z,message:`No DNS provider available to validate SSL certificate for ${Q}. Provide a certificateArn or enable DNS verification.`};console.log(`Requesting new SSL certificate for ${Q}${A?` (including ${A})`:""}...`);let f=await new f1(F,"us-east-1").findOrCreateCertificate({domainName:Q,subjectAlternativeNames:A?[A]:void 0,waitForValidation:!0,maxWaitMinutes:10});if(f.status!=="issued")return{success:!1,stackName:W,bucket:Z,message:`SSL certificate validation failed. Status: ${f.status}`};H=f.certificateArn,console.log(`Certificate issued: ${H}`)}}let O=!1,E;try{let x=await G.describeStacks({stackName:W});if(x.Stacks.length>0){let I=x.Stacks[0],C=I.StackStatus;if(C==="DELETE_IN_PROGRESS")console.log("Previous stack is still being deleted, waiting..."),await G.waitForStack(W,"stack-delete-complete"),O=!1;else if(C==="DELETE_COMPLETE")O=!1;else O=!0,E=(I.Outputs||[]).find((d)=>d.OutputKey==="BucketName")?.OutputValue}}catch(x){if(x.message?.includes("does not exist")||x.code==="ValidationError")O=!1;else throw x}let j=E||Z;if(!O){let x=new H0(J),I=new K0,C=!1;if(Q)try{console.log(`Checking for existing CloudFront distributions with alias ${Q}...`);let f=await I.listDistributions();for(let d of f){let r=[];if(d.Aliases?.Items){if(Array.isArray(d.Aliases.Items))r=d.Aliases.Items;else if(typeof d.Aliases.Items==="object"){let c=d.Aliases.Items.CNAME;if(typeof c==="string")r=[c];else if(Array.isArray(c))r=c}}if(r.includes(Q)){C=!0,console.log(`Found existing CloudFront distribution ${d.Id} with alias ${Q}`),console.log("Reusing existing infrastructure for updates...");let a=(await I.getDistributionConfig(d.Id)).DistributionConfig?.Origins?.Items,s;if(a){let J0=[];if(Array.isArray(a))J0=a;else if(a.Origin)J0=Array.isArray(a.Origin)?a.Origin:[a.Origin];else J0=[a];for(let O0 of J0){let X0=(O0.DomainName||"").match(/^([^.]+)\.s3[\.-]/);if(X0){s=X0[1];break}}}if(s)return console.log(`Using existing S3 bucket: ${s}`),{success:!0,stackName:`existing-${d.Id}`,bucket:s,distributionId:d.Id,distributionDomain:d.DomainName,domain:Q,certificateArn:H,message:"Using existing CloudFront distribution",_existingInfrastructure:!0}}}}catch{}if(!C)try{if((await x.headBucket(Z)).exists){console.log(`Found orphaned S3 bucket ${Z}, cleaning up...`);try{let d=x.emptyBucket(Z).then(()=>x.deleteBucket(Z)),r=new Promise((c,a)=>setTimeout(()=>a(Error("Bucket cleanup timeout")),30000));await Promise.race([d,r]),console.log(`Deleted orphaned S3 bucket ${Z}`)}catch(d){console.log(`Note: Could not clean up S3 bucket: ${d.message}`);let r=Date.now().toString(36);j=`${Z}-${r}`,console.log(`Using alternative bucket name: ${j}`)}}}catch{}}let X=E4({bucketName:j,domain:Q,aliases:A?[Q,A]:[Q],certificateArn:H,defaultRootObject:$.defaultRootObject,errorDocument:$.errorDocument,passthroughUrls:$.passthroughUrls,singlePageApp:$.singlePageApp}),_=Object.entries($.tags||{}).map(([x,I])=>({Key:x,Value:I}));if(_.push({Key:"ManagedBy",Value:"ts-cloud"}),_.push({Key:"Application",Value:$.siteName}),F)_.push({Key:"DnsProvider",Value:F.name});let T,N=!1;if(O){N=!0,console.log(`Updating CloudFormation stack: ${W}`);try{T=(await G.updateStack({stackName:W,templateBody:JSON.stringify(X),capabilities:["CAPABILITY_IAM"],tags:_})).StackId,console.log(`Update initiated, stack ID: ${T}`)}catch(x){if(x.message?.includes("No updates are to be performed")){let I=await G.describeStacks({stackName:W});T=I.Stacks[0].StackId;let C=I.Stacks[0]?.Outputs||[],f=(r)=>C.find((c)=>c.OutputKey===r)?.OutputValue,d=f("DistributionDomain");if(d)await h6(F,Q,d);return{success:!0,stackId:T,stackName:W,bucket:f("BucketName")||j,distributionId:f("DistributionId"),distributionDomain:d,domain:Q,certificateArn:H,message:"Static site infrastructure is already up to date"}}else throw x}}else console.log(`Creating CloudFormation stack: ${W}`),console.log(`Bucket name: ${j}`),console.log(`Domain: ${Q}`),console.log(`Certificate ARN: ${H}`),T=(await G.createStack({stackName:W,templateBody:JSON.stringify(X),capabilities:["CAPABILITY_IAM"],tags:_,onFailure:"DELETE"})).StackId,console.log(`Create initiated, stack ID: ${T}`);console.log(`Waiting for stack to reach ${N?"stack-update-complete":"stack-create-complete"}...`);try{await G.waitForStack(W,N?"stack-update-complete":"stack-create-complete"),console.log("Stack operation completed successfully!")}catch(x){if(x.message?.includes("must be verified")||x.message?.includes("Access denied for operation")){console.log("CloudFront account verification required - checking for existing infrastructure...");try{let C=new K0,f=await C.listDistributions();for(let d of f){let r=[];if(d.Aliases?.Items){if(Array.isArray(d.Aliases.Items))r=d.Aliases.Items;else if(typeof d.Aliases.Items==="object"){let c=d.Aliases.Items.CNAME;if(typeof c==="string")r=[c];else if(Array.isArray(c))r=c}}if(r.includes(Q)){console.log(`Found existing CloudFront distribution ${d.Id} with alias ${Q}`),console.log("Using existing infrastructure despite account verification requirement...");let a=(await C.getDistributionConfig(d.Id)).DistributionConfig?.Origins?.Items,s;if(a){let J0=[];if(Array.isArray(a))J0=a;else if(a.Origin)J0=Array.isArray(a.Origin)?a.Origin:[a.Origin];else J0=[a];for(let O0 of J0){let X0=(O0.DomainName||"").match(/^([^.]+)\.s3[\.-]/);if(X0){s=X0[1];break}}}if(s){console.log(`Using existing S3 bucket: ${s}`);let J0=d.DomainName;if(J0)await h6(F,Q,J0);return{success:!0,stackName:`existing-${d.Id}`,bucket:s,distributionId:d.Id,distributionDomain:d.DomainName,domain:Q,certificateArn:H,message:"Using existing CloudFront distribution (account verification pending for new distributions)"}}}}}catch{}return console.log(`
|
|
306
306
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
307
307
|
│ AWS ACCOUNT VERIFICATION REQUIRED │
|
|
308
308
|
├─────────────────────────────────────────────────────────────────────────────┤
|
|
@@ -360,7 +360,7 @@ Run \`${this.name??"cli"} --help\` for usage.`;Q1.stderr.write(`${W}${Z}${G}
|
|
|
360
360
|
`).length-1;this.output.write(t0.move(-999,$*-1))}render(){let $=K9(this._render(this)??"",B9.stdout.columns,{hard:!0,trim:!1});if($===this._prevFrame)return;if(this.state==="initial")this.output.write(t0.hide);else{let J=a7(this._prevFrame,$);if(this.restoreCursor(),J&&J?.length===1){let Y=J[0];this.output.write(t0.move(0,Y)),this.output.write(Y$.lines(1));let Q=$.split(`
|
|
361
361
|
`);this.output.write(Q[Y]),this._prevFrame=$,this.output.write(t0.move(0,Q.length-Y-1));return}if(J&&J?.length>1){let Y=J[0];this.output.write(t0.move(0,Y)),this.output.write(Y$.down());let Z=$.split(`
|
|
362
362
|
`).slice(Y);this.output.write(Z.join(`
|
|
363
|
-
`)),this._prevFrame=$;return}this.output.write(Y$.down())}if(this.output.write($),this.state==="initial")this.state="active";this._prevFrame=$}}function e7($,J){if($===void 0)return 0;if(J.length===0)return 0;let Q=J.findIndex((Z)=>Z.value===$);return Q!==-1?Q:0}function $J($,J){return(J.label??String(J.value)).toLowerCase().includes($.toLowerCase())}function JJ($,J){if(!J)return;if($)return J;return J[0]}class YJ extends z${filteredOptions;multiple;isNavigating=!1;selectedValues=[];focusedValue;#$=0;#Y="";#Q;#J;get cursor(){return this.#$}get userInputWithCursor(){if(!this.userInput)return B2.inverse(B2.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`${$}${B2.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??$J;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 Z=J.findIndex((W)=>W.value===Q);if(Z!==-1)this.toggleSelected(Q),this.#$=Z}this.focusedValue=this.options[this.#$]?.value,this.on("key",(Q,Z)=>this.#Z(Q,Z)),this.on("userInput",(Q)=>this.#W(Q))}_isActionKey($,J){return $==="\t"||this.multiple&&this.isNavigating&&J.name==="space"&&$!==void 0&&$!==""}#Z($,J){let Y=J.name==="up",Q=J.name==="down",Z=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(Z)this.value=JJ(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=[$]}#W($){if($!==this.#Y){this.#Y=$;let J=this.options;if($)this.filteredOptions=J.filter((Y)=>this.#Q($,Y));else this.filteredOptions=[...J];if(this.#$=e7(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 QJ extends z${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((Z)=>Z.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((Z)=>({...Z,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 ZJ=K7();var U0=($,J)=>ZJ?$:J,VQ=U0("◆","*"),XQ=U0("■","x"),_Q=U0("▲","x"),qQ=U0("◇","o"),LQ=U0("┌","T"),WJ=U0("│","|"),MQ=U0("└","—"),wQ=U0("●",">"),TQ=U0("○"," "),DQ=U0("◻","[•]"),NQ=U0("◼","[+]"),RQ=U0("◻","[ ]"),SQ=U0("▪","•"),PQ=U0("─","-"),xQ=U0("╮","+"),CQ=U0("├","+"),hQ=U0("╯","+"),IQ=U0("●","•"),kQ=U0("◆","*"),bQ=U0("▲","!"),yQ=U0("■","x");var vQ={light:U0("─","-"),heavy:U0("━","="),block:U0("█","#")};function GJ(){return`${B2.gray(WJ)} `}var fQ=GJ();var F$="0.2.
|
|
363
|
+
`)),this._prevFrame=$;return}this.output.write(Y$.down())}if(this.output.write($),this.state==="initial")this.state="active";this._prevFrame=$}}function e7($,J){if($===void 0)return 0;if(J.length===0)return 0;let Q=J.findIndex((Z)=>Z.value===$);return Q!==-1?Q:0}function $J($,J){return(J.label??String(J.value)).toLowerCase().includes($.toLowerCase())}function JJ($,J){if(!J)return;if($)return J;return J[0]}class YJ extends z${filteredOptions;multiple;isNavigating=!1;selectedValues=[];focusedValue;#$=0;#Y="";#Q;#J;get cursor(){return this.#$}get userInputWithCursor(){if(!this.userInput)return B2.inverse(B2.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`${$}${B2.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??$J;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 Z=J.findIndex((W)=>W.value===Q);if(Z!==-1)this.toggleSelected(Q),this.#$=Z}this.focusedValue=this.options[this.#$]?.value,this.on("key",(Q,Z)=>this.#Z(Q,Z)),this.on("userInput",(Q)=>this.#W(Q))}_isActionKey($,J){return $==="\t"||this.multiple&&this.isNavigating&&J.name==="space"&&$!==void 0&&$!==""}#Z($,J){let Y=J.name==="up",Q=J.name==="down",Z=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(Z)this.value=JJ(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=[$]}#W($){if($!==this.#Y){this.#Y=$;let J=this.options;if($)this.filteredOptions=J.filter((Y)=>this.#Q($,Y));else this.filteredOptions=[...J];if(this.#$=e7(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 QJ extends z${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((Z)=>Z.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((Z)=>({...Z,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 ZJ=K7();var U0=($,J)=>ZJ?$:J,VQ=U0("◆","*"),XQ=U0("■","x"),_Q=U0("▲","x"),qQ=U0("◇","o"),LQ=U0("┌","T"),WJ=U0("│","|"),MQ=U0("└","—"),wQ=U0("●",">"),TQ=U0("○"," "),DQ=U0("◻","[•]"),NQ=U0("◼","[+]"),RQ=U0("◻","[ ]"),SQ=U0("▪","•"),PQ=U0("─","-"),xQ=U0("╮","+"),CQ=U0("├","+"),hQ=U0("╯","+"),IQ=U0("●","•"),kQ=U0("◆","*"),bQ=U0("▲","!"),yQ=U0("■","x");var vQ={light:U0("─","-"),heavy:U0("━","="),block:U0("█","#")};function GJ(){return`${B2.gray(WJ)} `}var fQ=GJ();var F$="0.2.9";import{existsSync as j$}from"node:fs";import{mkdir as EJ,writeFile as I9}from"node:fs/promises";var e={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",gray:"\x1B[90m"};function q1($,J){return`${e[J]}${$}${e.reset}`}function P($){console.log(`${e.green}✓${e.reset} ${$}`)}function M($){console.error(`${e.red}✗${e.reset} ${$}`)}function y($){console.warn(`${e.yellow}⚠${e.reset} ${$}`)}var d0=y;function z($){console.log(`${e.blue}ℹ${e.reset} ${$}`)}function t($){console.log(`${e.cyan}→${e.reset} ${$}`)}function q($){console.log(`
|
|
364
364
|
${e.bright}${e.cyan}${$}${e.reset}
|
|
365
365
|
`)}var A$=!!(process.env.CI||process.env.GITHUB_ACTIONS||process.env.BUILDKITE||process.env.CIRCLECI||process.env.GITLAB_CI);class L{frames=["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"];interval=null;currentFrame=0;message;constructor($){this.message=$}get text(){return this.message}set text($){if(this.message=$,A$&&this.interval)console.log(` ${$}`)}start(){if(A$){console.log(` ${this.message}`),this.interval=!0;return}this.interval=setInterval(()=>{process.stdout.write(`\r${e.cyan}${this.frames[this.currentFrame]}${e.reset} ${this.message}`),this.currentFrame=(this.currentFrame+1)%this.frames.length},80)}succeed($){this.stop(),P($||this.message)}fail($){this.stop(),M($||this.message)}warn($){this.stop(),d0($||this.message)}stop(){if(this.interval){if(!A$)process.stdout.write("\r");if(typeof this.interval==="object")clearInterval(this.interval);this.interval=null}}}async function G0($,J){let Q=(await import("node:readline")).createInterface({input:process.stdin,output:process.stdout});return new Promise((Z)=>{let W=J?`${e.cyan}?${e.reset} ${$} ${e.gray}(${J})${e.reset}: `:`${e.cyan}?${e.reset} ${$}: `;Q.question(W,(G)=>{Q.close(),Z(G||J||"")})})}async function h($,J=!1){let Y=await G0(`${$} (y/n)`,J?"y":"n");return Y.toLowerCase()==="y"||Y.toLowerCase()==="yes"}async function O$($,J){console.log(`${e.cyan}?${e.reset} ${$}`),J.forEach((Z,W)=>{console.log(` ${e.gray}${W+1}.${e.reset} ${Z}`)});let Y=await G0("Select","1"),Q=Number.parseInt(Y)-1;if(Q>=0&&Q<J.length)return J[Q];return J[0]}function g($,J){let Y=$.map((Z,W)=>{let G=Math.max(...J.map((U)=>(U[W]||"").length));return Math.max(Z.length,G)}),Q=$.map((Z,W)=>Z.padEnd(Y[W])).join(" ");console.log(q1(Q,"bright")),console.log(q1("─".repeat(Q.length),"gray")),J.forEach((Z)=>{let W=Z.map((G,U)=>(G||"").padEnd(Y[U])).join(" ");console.log(W)})}function x1($,J="cyan"){let Y=$.split(`
|
|
366
366
|
`),Q=Math.max(...Y.map((W)=>W.length)),Z="─".repeat(Q+2);console.log(q1(`┌${Z}┐`,J)),Y.forEach((W)=>{console.log(q1(`│ ${W.padEnd(Q)} │`,J))}),console.log(q1(`└${Z}┘`,J))}async function P9(){return!0}async function x9(){try{let{AWSClient:$}=await Promise.resolve().then(() => (Z0(),_1));return await new $().request({service:"sts",region:"us-east-1",method:"POST",path:"/",body:new URLSearchParams({Action:"GetCallerIdentity",Version:"2011-06-15"}).toString()}),!0}catch{return!1}}async function C9(){try{let{AWSClient:$}=await Promise.resolve().then(() => (Z0(),_1)),Y=await new $().request({service:"sts",region:"us-east-1",method:"POST",path:"/",body:new URLSearchParams({Action:"GetCallerIdentity",Version:"2011-06-15"}).toString()});return Y.Account||Y.GetCallerIdentityResult?.Account||null}catch{return null}}async function h9(){try{let{AWSClient:$}=await Promise.resolve().then(() => (Z0(),_1)),Y=await new $().request({service:"ec2",region:"us-east-1",method:"POST",path:"/",body:new URLSearchParams({Action:"DescribeRegions",Version:"2016-11-15"}).toString()}),Q=[];if(Y.regionInfo){let Z=Array.isArray(Y.regionInfo)?Y.regionInfo:[Y.regionInfo];Q.push(...Z.map((W)=>W.regionName))}return Q.length>0?Q:S9()}catch{return S9()}}function S9(){return["us-east-1","us-east-2","us-west-1","us-west-2","eu-west-1","eu-central-1","ap-southeast-1","ap-northeast-1"]}function E$($){$.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(q("Initializing ts-cloud Project"),j$("cloud.config.ts")){if(!await h("cloud.config.ts already exists. Overwrite?",!1)){z("Initialization cancelled");return}}let Y=J?.name||await G0("Project name","my-app"),Q=J?.mode||await O$("Select deployment mode",["serverless","server","hybrid"]),Z=J?.region||await O$("Select AWS region",["us-east-1","us-west-2","eu-west-1","eu-central-1","ap-southeast-1"]),W=new L("Creating configuration file...");W.start();let G=`import { defineConfig } from '@ts-cloud/core'
|
package/dist/deploy/index.d.ts
CHANGED
|
@@ -30,6 +30,15 @@ export interface ExternalDnsStaticSiteConfig {
|
|
|
30
30
|
skipDnsVerification?: boolean;
|
|
31
31
|
/** When true, serves raw files without URL rewriting (for curl | bash install scripts) */
|
|
32
32
|
passthroughUrls?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* When true, missing files (S3 403/404) fall through to the index document
|
|
35
|
+
* with a 200 status — required for client-side-routed SPAs.
|
|
36
|
+
*
|
|
37
|
+
* Defaults to false: missing files return a real 404 with the error document.
|
|
38
|
+
* Multi-page static sites should leave this off so /favicon.ico, /robots.txt,
|
|
39
|
+
* /sitemap.xml etc. don't masquerade as the homepage.
|
|
40
|
+
*/
|
|
41
|
+
singlePageApp?: boolean;
|
|
33
42
|
}
|
|
34
43
|
export interface ExternalDnsDeployResult {
|
|
35
44
|
success: boolean;
|
|
@@ -55,6 +64,7 @@ export declare function generateExternalDnsStaticSiteTemplate(config: {
|
|
|
55
64
|
defaultRootObject?: string;
|
|
56
65
|
errorDocument?: string;
|
|
57
66
|
passthroughUrls?: boolean;
|
|
67
|
+
singlePageApp?: boolean;
|
|
58
68
|
}): object;
|
|
59
69
|
/**
|
|
60
70
|
* Deploy a static site to AWS with external DNS provider
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level static site deployer with smart defaults.
|
|
3
|
+
*
|
|
4
|
+
* Most callers don't need the full surface of
|
|
5
|
+
* `deployStaticSiteWithExternalDnsFull` — they want to point a build
|
|
6
|
+
* directory at a domain and let the helper sort out:
|
|
7
|
+
* - Porkbun DNS (default for non-Route53 setups)
|
|
8
|
+
* - Non-SPA error handling (so /favicon.ico, /robots.txt, etc. don't
|
|
9
|
+
* masquerade as the homepage)
|
|
10
|
+
* - AWS env-var validation up front
|
|
11
|
+
* - Sensible cache-control headers
|
|
12
|
+
*
|
|
13
|
+
* `deploySite` is the one-call entrypoint; the lower-level
|
|
14
|
+
* `deployStaticSiteWithExternalDnsFull` and `deployStaticSiteFull`
|
|
15
|
+
* remain available for callers that need the full surface.
|
|
16
|
+
*/
|
|
17
|
+
export type StaticSiteDnsProvider = 'porkbun' | 'godaddy' | {
|
|
18
|
+
provider: 'porkbun';
|
|
19
|
+
apiKey?: string;
|
|
20
|
+
secretKey?: string;
|
|
21
|
+
} | {
|
|
22
|
+
provider: 'godaddy';
|
|
23
|
+
apiKey?: string;
|
|
24
|
+
secretKey?: string;
|
|
25
|
+
environment?: 'production' | 'ote';
|
|
26
|
+
};
|
|
27
|
+
export interface DeploySiteConfig {
|
|
28
|
+
/** Site name used for AWS resource naming. */
|
|
29
|
+
siteName: string;
|
|
30
|
+
/** Apex or subdomain (e.g. "paweldregan.com"). */
|
|
31
|
+
domain: string;
|
|
32
|
+
/** AWS region for the S3 bucket. Defaults to "us-east-1". */
|
|
33
|
+
region?: string;
|
|
34
|
+
/** Output directory containing built files. Defaults to "dist". */
|
|
35
|
+
sourceDir?: string;
|
|
36
|
+
/** S3 bucket name (auto-generated from the domain if omitted). */
|
|
37
|
+
bucket?: string;
|
|
38
|
+
/** CloudFormation stack name (auto-generated if omitted). */
|
|
39
|
+
stackName?: string;
|
|
40
|
+
/** Default root object served at "/". Defaults to "index.html". */
|
|
41
|
+
defaultRootObject?: string;
|
|
42
|
+
/** Error document served on 403/404. Defaults to "404.html". */
|
|
43
|
+
errorDocument?: string;
|
|
44
|
+
/**
|
|
45
|
+
* DNS provider config. Defaults to Porkbun, reading PORKBUN_API_KEY /
|
|
46
|
+
* PORKBUN_SECRET_KEY from env. Pass `'godaddy'` or a full provider
|
|
47
|
+
* object to override.
|
|
48
|
+
*/
|
|
49
|
+
dnsProvider?: StaticSiteDnsProvider;
|
|
50
|
+
/** Pre-issued ACM cert ARN. Auto-created when omitted. */
|
|
51
|
+
certificateArn?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Cache-Control header for uploaded objects. Defaults to short-TTL
|
|
54
|
+
* (one hour) so HTML edits propagate quickly; long-lived assets can
|
|
55
|
+
* still set their own Cache-Control via S3 object metadata if needed.
|
|
56
|
+
*/
|
|
57
|
+
cacheControl?: string;
|
|
58
|
+
/**
|
|
59
|
+
* SPA mode. Defaults to false. With false, missing files return a
|
|
60
|
+
* real 404 with the error document. With true, missing files fall
|
|
61
|
+
* through to the index document with a 200 — required for
|
|
62
|
+
* client-side-routed SPAs but wrong for multi-page static sites.
|
|
63
|
+
*/
|
|
64
|
+
singlePageApp?: boolean;
|
|
65
|
+
/** Empty the bucket before uploading (default: false). */
|
|
66
|
+
cleanBucket?: boolean;
|
|
67
|
+
/** AWS resource tags. */
|
|
68
|
+
tags?: Record<string, string>;
|
|
69
|
+
/** Progress callback. */
|
|
70
|
+
onProgress?: (stage: string, detail?: string) => void;
|
|
71
|
+
}
|
|
72
|
+
export interface DeploySiteResult {
|
|
73
|
+
success: boolean;
|
|
74
|
+
domain?: string;
|
|
75
|
+
url?: string;
|
|
76
|
+
bucket?: string;
|
|
77
|
+
distributionId?: string;
|
|
78
|
+
distributionDomain?: string;
|
|
79
|
+
certificateArn?: string;
|
|
80
|
+
filesUploaded?: number;
|
|
81
|
+
filesSkipped?: number;
|
|
82
|
+
message?: string;
|
|
83
|
+
durationMs: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Deploy a static site to AWS (S3 + CloudFront + ACM) with DNS managed
|
|
87
|
+
* via Porkbun (or another supported external provider). Validates AWS +
|
|
88
|
+
* DNS-provider credentials up front, fills in opinionated defaults, and
|
|
89
|
+
* routes to the underlying CloudFormation flow.
|
|
90
|
+
*/
|
|
91
|
+
export declare function deploySite(config: DeploySiteConfig): Promise<DeploySiteResult>;
|
package/dist/index.js
CHANGED
|
@@ -7463,7 +7463,8 @@ function generateExternalDnsStaticSiteTemplate(config4) {
|
|
|
7463
7463
|
certificateArn,
|
|
7464
7464
|
defaultRootObject = "index.html",
|
|
7465
7465
|
errorDocument = "404.html",
|
|
7466
|
-
passthroughUrls = false
|
|
7466
|
+
passthroughUrls = false,
|
|
7467
|
+
singlePageApp = false
|
|
7467
7468
|
} = config4;
|
|
7468
7469
|
const resources = {};
|
|
7469
7470
|
const outputs = {};
|
|
@@ -7563,13 +7564,26 @@ function generateExternalDnsStaticSiteTemplate(config4) {
|
|
|
7563
7564
|
]
|
|
7564
7565
|
}
|
|
7565
7566
|
},
|
|
7566
|
-
CustomErrorResponses: [
|
|
7567
|
+
CustomErrorResponses: singlePageApp ? [
|
|
7567
7568
|
{
|
|
7568
7569
|
ErrorCode: 403,
|
|
7569
7570
|
ResponseCode: 200,
|
|
7570
7571
|
ResponsePagePath: `/${defaultRootObject}`,
|
|
7571
7572
|
ErrorCachingMinTTL: 300
|
|
7572
7573
|
},
|
|
7574
|
+
{
|
|
7575
|
+
ErrorCode: 404,
|
|
7576
|
+
ResponseCode: 200,
|
|
7577
|
+
ResponsePagePath: `/${defaultRootObject}`,
|
|
7578
|
+
ErrorCachingMinTTL: 300
|
|
7579
|
+
}
|
|
7580
|
+
] : [
|
|
7581
|
+
{
|
|
7582
|
+
ErrorCode: 403,
|
|
7583
|
+
ResponseCode: 404,
|
|
7584
|
+
ResponsePagePath: `/${errorDocument}`,
|
|
7585
|
+
ErrorCachingMinTTL: 300
|
|
7586
|
+
},
|
|
7573
7587
|
{
|
|
7574
7588
|
ErrorCode: 404,
|
|
7575
7589
|
ResponseCode: 404,
|
|
@@ -7839,7 +7853,8 @@ async function deployStaticSiteWithExternalDns(config4) {
|
|
|
7839
7853
|
certificateArn,
|
|
7840
7854
|
defaultRootObject: config4.defaultRootObject,
|
|
7841
7855
|
errorDocument: config4.errorDocument,
|
|
7842
|
-
passthroughUrls: config4.passthroughUrls
|
|
7856
|
+
passthroughUrls: config4.passthroughUrls,
|
|
7857
|
+
singlePageApp: config4.singlePageApp
|
|
7843
7858
|
});
|
|
7844
7859
|
const tags = Object.entries(config4.tags || {}).map(([Key, Value]) => ({ Key, Value }));
|
|
7845
7860
|
tags.push({ Key: "ManagedBy", Value: "ts-cloud" });
|
|
@@ -75326,6 +75341,8 @@ class AcmeClient {
|
|
|
75326
75341
|
init_static_site();
|
|
75327
75342
|
init_static_site_external_dns();
|
|
75328
75343
|
|
|
75344
|
+
// src/deploy/static-site-helper.ts
|
|
75345
|
+
init_static_site_external_dns();
|
|
75329
75346
|
// src/index.ts
|
|
75330
75347
|
init_dns();
|
|
75331
75348
|
export {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/ts-cloud",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.9",
|
|
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",
|
|
@@ -85,8 +85,8 @@
|
|
|
85
85
|
"test": "bun test"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@ts-cloud/aws-types": "0.2.
|
|
89
|
-
"@ts-cloud/core": "0.2.
|
|
88
|
+
"@ts-cloud/aws-types": "0.2.9",
|
|
89
|
+
"@ts-cloud/core": "0.2.9",
|
|
90
90
|
"@stacksjs/ts-xml": "^0.1.0"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|