@stacksjs/cloud 0.67.0 → 0.68.0

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.
Files changed (45) hide show
  1. package/dist/ai.d.ts +20 -0
  2. package/dist/cdn.d.ts +271 -0
  3. package/dist/cli.d.ts +12 -0
  4. package/dist/compute.d.ts +221 -0
  5. package/dist/database.d.ts +29 -0
  6. package/dist/deployment.d.ts +60 -0
  7. package/dist/dns.d.ts +34 -0
  8. package/dist/docs.d.ts +34 -0
  9. package/dist/email.d.ts +336 -0
  10. package/dist/file-system.d.ts +32 -0
  11. package/dist/helpers.d.ts +50 -50
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +18 -18
  14. package/dist/jump-box.d.ts +42 -0
  15. package/dist/network.d.ts +29 -0
  16. package/dist/origin-request.d.ts +5 -0
  17. package/dist/permissions.d.ts +25 -0
  18. package/dist/queue.d.ts +144 -0
  19. package/dist/redirects.d.ts +45 -0
  20. package/dist/security.d.ts +149 -0
  21. package/dist/storage.d.ts +189 -0
  22. package/dist/types.d.ts +26 -25
  23. package/package.json +18 -18
  24. package/dist/cloud/ai.d.ts +0 -6
  25. package/dist/cloud/cache.d.ts +0 -0
  26. package/dist/cloud/cdn.d.ts +0 -34
  27. package/dist/cloud/cli.d.ts +0 -6
  28. package/dist/cloud/compute.d.ts +0 -17
  29. package/dist/cloud/dashboard.d.ts +0 -0
  30. package/dist/cloud/database.d.ts +0 -11
  31. package/dist/cloud/deployment.d.ts +0 -17
  32. package/dist/cloud/dns.d.ts +0 -7
  33. package/dist/cloud/docs.d.ts +0 -8
  34. package/dist/cloud/email.d.ts +0 -10
  35. package/dist/cloud/file-system.d.ts +0 -12
  36. package/dist/cloud/index.d.ts +0 -42
  37. package/dist/cloud/jump-box.d.ts +0 -12
  38. package/dist/cloud/network.d.ts +0 -8
  39. package/dist/cloud/permissions.d.ts +0 -6
  40. package/dist/cloud/queue.d.ts +0 -22
  41. package/dist/cloud/redirects.d.ts +0 -8
  42. package/dist/cloud/search-engine.d.ts +0 -0
  43. package/dist/cloud/security.d.ts +0 -15
  44. package/dist/cloud/storage.d.ts +0 -21
  45. package/dist/edge/origin-request.d.ts +0 -1
package/dist/ai.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { Construct } from 'constructs';
2
+ import type { NestedCloudProps } from '../types';
3
+
4
+ export declare interface AiStackProps extends NestedCloudProps {}
5
+ export declare class AiStack {
6
+ constructor(scope: Construct, props: AiStackProps) {
7
+ const bedrockAccessPolicy = new iam.PolicyStatement({
8
+ effect: iam.Effect.ALLOW,
9
+ actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'],
10
+ resources: config.ai.models?.map((model: string) => `arn:aws:bedrock:us-east-1::foundation-model/${model}`),
11
+ })
12
+
13
+ const bedrockAccessRole = new iam.Role(scope, 'BedrockAccessRole', {
14
+ assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
15
+ managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy')],
16
+ })
17
+
18
+ bedrockAccessRole.addToPolicy(bedrockAccessPolicy)
19
+ }
20
+ }
package/dist/cdn.d.ts ADDED
@@ -0,0 +1,271 @@
1
+ import type { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
2
+ import type { Construct } from 'constructs';
3
+ import type { NestedCloudProps } from '../types';
4
+
5
+ export declare interface CdnStackProps extends NestedCloudProps {
6
+ certificate: acm.Certificate
7
+ logBucket: s3.Bucket
8
+ publicBucket: s3.Bucket
9
+ docsBucket?: s3.Bucket
10
+ firewall: wafv2.CfnWebACL
11
+ originRequestFunction: lambda.Function
12
+ zone: route53.IHostedZone
13
+ webServer?: lambda.Function
14
+ webServerUrl?: lambda.FunctionUrl
15
+ lb?: ApplicationLoadBalancer
16
+ }
17
+ export declare class CdnStack {
18
+ mainDistribution: cloudfront.Distribution
19
+ docsDistribution: cloudfront.Distribution | undefined
20
+ cdnCachePolicy: cloudfront.CachePolicy
21
+ mainVanityUrl!: string
22
+ docsVanityUrl: string | undefined
23
+ realtimeLogConfig!: cloudfront.RealtimeLogConfig
24
+ props: CdnStackProps
25
+
26
+ constructor(scope: Construct, props: CdnStackProps) {
27
+ this.props = props
28
+
29
+ this.cdnCachePolicy = new cloudfront.CachePolicy(scope, 'CdnCachePolicy', {
30
+ comment: 'Stacks CDN Cache Policy',
31
+ cachePolicyName: `${props.slug}-${props.appEnv}-cdn-cache-policy`,
32
+ minTtl: config.cloud.cdn?.minTtl ? Duration.seconds(config.cloud.cdn.minTtl) : undefined,
33
+ defaultTtl: config.cloud.cdn?.defaultTtl ? Duration.seconds(config.cloud.cdn.defaultTtl) : undefined,
34
+ maxTtl: config.cloud.cdn?.maxTtl ? Duration.seconds(config.cloud.cdn.maxTtl) : undefined,
35
+ cookieBehavior: this.getCookieBehavior(config.cloud.cdn?.cookieBehavior),
36
+ })
37
+
38
+ const originAccessControl = new cloudfront.S3OriginAccessControl(scope, 'WebOAC', {
39
+ originAccessControlName: `${props.slug}-${props.appEnv}-web-oac-${props.timestamp}`,
40
+ description: 'Access from CloudFront to the frontend bucket.',
41
+ signing: cloudfront.Signing.SIGV4_NO_OVERRIDE,
42
+ })
43
+
44
+ if (config.app.docMode) {
45
+ this.mainDistribution = this.createDistribution(
46
+ scope,
47
+ props,
48
+ props.docsBucket as s3.Bucket,
49
+ this.createDocsOriginRequestFunction(scope),
50
+ props.domain,
51
+ 'MainCdn',
52
+ originAccessControl,
53
+ )
54
+ }
55
+ else {
56
+ this.mainDistribution = this.createDistribution(
57
+ scope,
58
+ props,
59
+ props.publicBucket,
60
+ props.originRequestFunction,
61
+ props.domain,
62
+ 'MainCdn',
63
+ originAccessControl,
64
+ )
65
+
66
+ if (props.docsBucket) {
67
+ this.docsDistribution = this.createDistribution(
68
+ scope,
69
+ props,
70
+ props.docsBucket,
71
+ this.createDocsOriginRequestFunction(scope),
72
+ `docs.${props.domain}`,
73
+ 'DocsCdn',
74
+ originAccessControl,
75
+ )
76
+ }
77
+ }
78
+
79
+ this.createRoute53Records(scope, props)
80
+
81
+ this.createOutputs(scope, props)
82
+ }
83
+
84
+ createDistribution(
85
+ scope: Construct,
86
+ props: CdnStackProps,
87
+ sourceBucket: s3.Bucket,
88
+ originRequestFunction: lambda.Function,
89
+ domainName: string,
90
+ id: string,
91
+ originAccessControl: cloudfront.S3OriginAccessControl,
92
+ ): cloudfront.Distribution {
93
+ return new cloudfront.Distribution(scope, id, {
94
+ domainNames: [domainName],
95
+ defaultRootObject: 'index.html',
96
+ comment: `CDN for ${domainName}`,
97
+ certificate: props.certificate,
98
+ enableLogging: true,
99
+ logBucket: props.logBucket,
100
+ httpVersion: cloudfront.HttpVersion.HTTP2_AND_3,
101
+ priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
102
+ enabled: true,
103
+ minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
104
+ webAclId: props.firewall.attrArn,
105
+ enableIpv6: true,
106
+ defaultBehavior: {
107
+ origin: new origins.S3StaticWebsiteOrigin(sourceBucket, {
108
+ originPath: '/',
109
+ originAccessControlId: originAccessControl.originAccessControlId,
110
+ }),
111
+ edgeLambdas: [
112
+ {
113
+ eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
114
+ functionVersion: originRequestFunction.currentVersion,
115
+ },
116
+ ],
117
+ compress: config.cloud.cdn?.compress,
118
+ allowedMethods: this.allowedMethods(),
119
+ cachedMethods: this.cachedMethods(),
120
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
121
+ cachePolicy: this.cdnCachePolicy,
122
+ },
123
+ errorResponses: [
124
+ {
125
+ httpStatus: 404,
126
+ responsePagePath: '/index.html',
127
+ responseHttpStatus: 200,
128
+ ttl: Duration.millis(0),
129
+ },
130
+ {
131
+ httpStatus: 403,
132
+ responsePagePath: '/index.html',
133
+ responseHttpStatus: 200,
134
+ ttl: Duration.millis(0),
135
+ },
136
+ ],
137
+ })
138
+ }
139
+
140
+ createRoute53Records(scope: Construct, props: CdnStackProps): void {
141
+ new route53.ARecord(scope, 'MainAliasRecord', {
142
+ recordName: props.domain,
143
+ zone: props.zone,
144
+ target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(this.mainDistribution)),
145
+ })
146
+
147
+ if (this.docsDistribution) {
148
+ new route53.ARecord(scope, 'DocsAliasRecord', {
149
+ recordName: `docs.${props.domain}`,
150
+ zone: props.zone,
151
+ target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(this.docsDistribution)),
152
+ })
153
+ }
154
+ }
155
+
156
+ createOutputs(scope: Construct, props: CdnStackProps): void {
157
+ new Output(scope, 'MainDistributionId', {
158
+ value: this.mainDistribution.distributionId,
159
+ })
160
+
161
+ new Output(scope, 'MainUrl', {
162
+ value: `https:
163
+ description: 'The URL of the deployed main application',
164
+ })
165
+
166
+ this.mainVanityUrl = `https:
167
+ new Output(scope, 'MainVanityUrl', {
168
+ value: this.mainVanityUrl,
169
+ description: 'The vanity URL of the deployed main application',
170
+ })
171
+
172
+ if (this.docsDistribution) {
173
+ new Output(scope, 'DocsDistributionId', {
174
+ value: this.docsDistribution.distributionId,
175
+ })
176
+
177
+ this.docsVanityUrl = `https:
178
+ new Output(scope, 'DocsAppVanityUrl', {
179
+ value: this.docsVanityUrl,
180
+ description: 'The vanity URL of the deployed docs application',
181
+ })
182
+ }
183
+ }
184
+
185
+ createDocsOriginRequestFunction(scope: Construct): lambda.Function {
186
+ const docsOriginRequestFunction = new lambda.Function(scope, 'DocsOriginRequestFunction', {
187
+ functionName: `${this.props.slug}-${this.props.appEnv}-docs-origin-request-function-${this.props.timestamp}`,
188
+ description: 'Custom origin request function for the docs',
189
+ runtime: lambda.Runtime.NODEJS_20_X,
190
+ handler: 'index.handler',
191
+ code: lambda.Code.fromInline(`
192
+ const config = {
193
+ suffix: '.html',
194
+ }
195
+
196
+ const regexSuffixless = /\\/[^/.]+$/
197
+ const regexTrailingSlash = /.+\\/$/
198
+
199
+ exports.handler = (event, context, callback) => {
200
+ const request = event.Records[0].cf.request;
201
+ const uri = request.uri;
202
+
203
+ if (uri === '/') {
204
+ request.uri = '/index.html';
205
+ callback(null, request);
206
+ return;
207
+ }
208
+
209
+ if (uri.match(regexSuffixless)) {
210
+ request.uri = uri + '.html';
211
+ callback(null, request);
212
+ return;
213
+ }
214
+
215
+ if (uri.match(regexTrailingSlash)) {
216
+ request.uri = uri.slice(0, -1) + '.html';
217
+ callback(null, request);
218
+ return;
219
+ }
220
+
221
+ callback(null, request);
222
+ };
223
+ `),
224
+ })
225
+
226
+ new lambda.CfnPermission(scope, 'DocsOriginRequestFunctionPermission', {
227
+ action: 'lambda:InvokeFunction',
228
+ principal: 'edgelambda.amazonaws.com',
229
+ functionName: docsOriginRequestFunction.functionName,
230
+ })
231
+
232
+ return docsOriginRequestFunction
233
+ }
234
+
235
+ getCookieBehavior(cookieBehavior: string | undefined): cloudfront.CacheCookieBehavior | undefined {
236
+ switch (cookieBehavior) {
237
+ case 'none':
238
+ return cloudfront.CacheCookieBehavior.none()
239
+ case 'all':
240
+ return cloudfront.CacheCookieBehavior.all()
241
+ case 'allowList':
242
+ return cloudfront.CacheCookieBehavior.allowList(...(config.cloud.cdn?.allowList.cookies || []))
243
+ default:
244
+ return undefined
245
+ }
246
+ }
247
+
248
+ allowedMethods(): cloudfront.AllowedMethods {
249
+ switch (config.cloud.cdn?.allowedMethods) {
250
+ case 'ALL':
251
+ return cloudfront.AllowedMethods.ALLOW_ALL
252
+ case 'GET_HEAD':
253
+ return cloudfront.AllowedMethods.ALLOW_GET_HEAD
254
+ case 'GET_HEAD_OPTIONS':
255
+ return cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS
256
+ default:
257
+ return cloudfront.AllowedMethods.ALLOW_ALL
258
+ }
259
+ }
260
+
261
+ cachedMethods(): cloudfront.CachedMethods {
262
+ switch (config.cloud.cdn?.cachedMethods) {
263
+ case 'GET_HEAD':
264
+ return cloudfront.CachedMethods.CACHE_GET_HEAD
265
+ case 'GET_HEAD_OPTIONS':
266
+ return cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS
267
+ default:
268
+ return cloudfront.CachedMethods.CACHE_GET_HEAD
269
+ }
270
+ }
271
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { Construct } from 'constructs';
2
+ import type { NestedCloudProps } from '../types';
3
+
4
+ export declare interface CliStackProps extends NestedCloudProps {}
5
+ export declare class CliStack {
6
+ constructor(scope: Construct, props: CliStackProps) {
7
+ new Output(scope, 'CliSetupUrl', {
8
+ value: `https:
9
+ description: 'URL to trigger the CLI setup function',
10
+ })
11
+ }
12
+ }
@@ -0,0 +1,221 @@
1
+ import type { Construct } from 'constructs';
2
+ import type { NestedCloudProps } from '../types';
3
+
4
+ export declare interface ComputeStackProps extends NestedCloudProps {
5
+ vpc: ec2.Vpc
6
+ fileSystem: efs.FileSystem
7
+ zone: route53.IHostedZone
8
+ certificate: acm.Certificate
9
+ }
10
+ export declare class ComputeStack {
11
+ lb: elbv2.ApplicationLoadBalancer
12
+ cluster: ecs.Cluster
13
+ taskDefinition: ecs.FargateTaskDefinition
14
+
15
+ constructor(scope: Construct, props: ComputeStackProps) {
16
+ const vpc = props.vpc
17
+ const fileSystem = props.fileSystem
18
+
19
+ if (!fileSystem)
20
+ throw new Error('The file system is missing. Please make sure it was created properly.')
21
+
22
+ this.cluster = new ecs.Cluster(scope, 'StacksCluster', {
23
+ clusterName: `${props.slug}-${props.appEnv}-web-server-cluster`,
24
+ vpc,
25
+ })
26
+
27
+ this.taskDefinition = new ecs.FargateTaskDefinition(scope, 'TaskDefinition', {
28
+ family: `${props.appName}-${props.appEnv}-api`,
29
+ memoryLimitMiB: 512,
30
+ cpu: 256,
31
+ runtimePlatform: {
32
+ cpuArchitecture: ecs.CpuArchitecture.ARM64,
33
+ },
34
+ })
35
+
36
+
37
+ const container = this.taskDefinition.addContainer('WebServerContainer', {
38
+ containerName: `${props.appName}-${props.appEnv}-api`,
39
+ image: ecs.ContainerImage.fromAsset(p.frameworkPath('server')),
40
+ logging: new ecs.AwsLogDriver({
41
+ streamPrefix: `${props.appName}-${props.appEnv}-web-server-logs`,
42
+ logGroup: new LogGroup(scope, 'StacksApiLogs', {
43
+ logGroupName: '/aws/ecs/stacks-api',
44
+ removalPolicy: RemovalPolicy.DESTROY,
45
+ }),
46
+ }),
47
+ healthCheck: {
48
+ command: ['CMD-SHELL', 'curl -f http:
49
+ interval: Duration.seconds(10),
50
+ timeout: Duration.seconds(5),
51
+ retries: 3,
52
+ startPeriod: Duration.seconds(10),
53
+ },
54
+ })
55
+
56
+ container.addPortMappings({
57
+ containerPort: 3000,
58
+ hostPort: 3000,
59
+ })
60
+
61
+ const serviceSecurityGroup = new ec2.SecurityGroup(scope, 'ServiceSecurityGroup', {
62
+ securityGroupName: `${props.appName}-${props.appEnv}-api-service-sg`,
63
+ vpc,
64
+ description: 'Stacks Security Group for API Service',
65
+ })
66
+
67
+ const publicLoadBalancerSG = new ec2.SecurityGroup(scope, 'PublicLoadBalancerSG', {
68
+ securityGroupName: `${props.appName}-${props.appEnv}-public-load-balancer-sg`,
69
+ vpc,
70
+ description: 'Access to the public facing load balancer',
71
+ })
72
+
73
+ serviceSecurityGroup.addIngressRule(publicLoadBalancerSG, ec2.Port.allTraffic(), 'Ingress from the public ALB')
74
+
75
+ this.lb = new elbv2.ApplicationLoadBalancer(scope, 'ApplicationLoadBalancer', {
76
+ http2Enabled: true,
77
+ loadBalancerName: `${props.appName}-${props.appEnv}-alb`,
78
+ vpc,
79
+ vpcSubnets: {
80
+ subnets: vpc.selectSubnets({
81
+ subnetType: ec2.SubnetType.PUBLIC,
82
+ onePerAz: true,
83
+ }).subnets,
84
+ },
85
+ internetFacing: true,
86
+ idleTimeout: Duration.seconds(30),
87
+ securityGroup: publicLoadBalancerSG,
88
+ })
89
+
90
+ new route53.ARecord(scope, 'ApiDomainAliasRecord', {
91
+ zone: props.zone,
92
+ recordName: 'api',
93
+ target: route53.RecordTarget.fromAlias(new route53Targets.LoadBalancerTarget(this.lb)),
94
+ })
95
+
96
+ const serviceTargetGroup = new elbv2.ApplicationTargetGroup(scope, 'ServiceTargetGroup', {
97
+ targetGroupName: `${props.appName}-${props.appEnv}-api-tg`,
98
+ vpc,
99
+ targetType: elbv2.TargetType.IP,
100
+ protocol: elbv2.ApplicationProtocol.HTTP,
101
+ port: 3000,
102
+ healthCheck: {
103
+ interval: Duration.seconds(6),
104
+ path: '/health',
105
+ protocol: elbv2.Protocol.HTTP,
106
+ timeout: Duration.seconds(5),
107
+ healthyThresholdCount: 2,
108
+ unhealthyThresholdCount: 10,
109
+ },
110
+ })
111
+
112
+ const service = new ecs.FargateService(scope, 'StacksApiService', {
113
+ serviceName: `${props.appName}-${props.appEnv}-api-service`,
114
+ cluster: this.cluster,
115
+ taskDefinition: this.taskDefinition,
116
+ desiredCount: 1,
117
+ assignPublicIp: true,
118
+ maxHealthyPercent: 200,
119
+ vpcSubnets: vpc.selectSubnets({
120
+ subnetType: ec2.SubnetType.PUBLIC,
121
+ onePerAz: true,
122
+ }),
123
+ minHealthyPercent: 75,
124
+ securityGroups: [serviceSecurityGroup],
125
+ })
126
+
127
+ service.attachToApplicationTargetGroup(serviceTargetGroup)
128
+ publicLoadBalancerSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.allTraffic())
129
+
130
+ this.lb.addListener('HttpsListener', {
131
+ port: 443,
132
+ certificates: [props.certificate],
133
+ defaultAction: elbv2.ListenerAction.forward([serviceTargetGroup]),
134
+ })
135
+
136
+ this.lb.addListener('HttpListener', {
137
+ port: 80,
138
+ defaultAction: elbv2.ListenerAction.forward([serviceTargetGroup]),
139
+ })
140
+
141
+ props.fileSystem.connections.allowFromAnyIpv4(ec2.Port.tcp(2049))
142
+
143
+ const volumeName = `${props.slug}-${props.appEnv}-efs`
144
+ this.taskDefinition.addVolume({
145
+ name: volumeName,
146
+ efsVolumeConfiguration: {
147
+ fileSystemId: props.fileSystem.fileSystemId,
148
+ },
149
+ })
150
+
151
+ container.addMountPoints({
152
+ sourceVolume: volumeName,
153
+ containerPath: '/mnt/efs',
154
+ readOnly: false,
155
+ })
156
+
157
+ const scaling = service.autoScaleTaskCount({ maxCapacity: 2 })
158
+
159
+ scaling.scaleOnCpuUtilization('CpuScaling', {
160
+ targetUtilizationPercent: 50,
161
+ scaleInCooldown: Duration.seconds(60),
162
+ scaleOutCooldown: Duration.seconds(60),
163
+ })
164
+
165
+ scaling.scaleOnMemoryUtilization('MemoryScaling', {
166
+ targetUtilizationPercent: 60,
167
+ scaleInCooldown: Duration.seconds(60),
168
+ scaleOutCooldown: Duration.seconds(60),
169
+ })
170
+
171
+ const keysToRemove = [
172
+ '_HANDLER',
173
+ '_X_AMZN_TRACE_ID',
174
+ 'AWS_REGION',
175
+ 'AWS_EXECUTION_ENV',
176
+ 'AWS_LAMBDA_FUNCTION_NAME',
177
+ 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE',
178
+ 'AWS_LAMBDA_FUNCTION_VERSION',
179
+ 'AWS_LAMBDA_INITIALIZATION_TYPE',
180
+ 'AWS_LAMBDA_LOG_GROUP_NAME',
181
+ 'AWS_LAMBDA_LOG_STREAM_NAME',
182
+ 'AWS_ACCESS_KEY',
183
+ 'AWS_ACCESS_KEY_ID',
184
+ 'AWS_SECRET_ACCESS_KEY',
185
+ 'AWS_SESSION_TOKEN',
186
+ 'AWS_LAMBDA_RUNTIME_API',
187
+ 'LAMBDA_TASK_ROOT',
188
+ 'LAMBDA_RUNTIME_DIR',
189
+ '_',
190
+ ]
191
+ keysToRemove.forEach(key => delete env[key as EnvKey])
192
+
193
+ const secrets = new secretsmanager.Secret(scope, 'StacksSecrets', {
194
+ secretName: `${props.slug}-${props.appEnv}-secrets`,
195
+ description: 'Secrets for the Stacks application',
196
+ generateSecretString: {
197
+ secretStringTemplate: JSON.stringify(env),
198
+ generateStringKey: Object.keys(env).join(',').length.toString(),
199
+ },
200
+ })
201
+
202
+ if (service.taskDefinition.executionRole) {
203
+ secrets.grantRead(service.taskDefinition.executionRole)
204
+ container.addEnvironment('SECRETS_ARN', secrets.secretArn)
205
+ }
206
+ else {
207
+ throw new Error('Service task execution role is undefined.')
208
+ }
209
+
210
+ const apiPrefix = 'api'
211
+ new Output(scope, 'ApiUrl', {
212
+ value: `https:
213
+ description: 'The URL of the deployed application',
214
+ })
215
+
216
+ new Output(scope, 'ApiVanityUrl', {
217
+ value: `http:
218
+ description: 'The Vanity URL / DNS name of the load balancer',
219
+ })
220
+ }
221
+ }
@@ -0,0 +1,29 @@
1
+ import type { Construct } from 'constructs';
2
+ import type { NestedCloudProps } from '../types';
3
+
4
+ export declare interface DatabaseStackProps extends NestedCloudProps {
5
+ vpc: ec2.Vpc
6
+ }
7
+ export declare class DatabaseStack {
8
+ database: dynamodb.Table
9
+
10
+ constructor(scope: Construct, props: DatabaseStackProps) {
11
+ this.database = new dynamodb.Table(scope, 'Database', {
12
+ tableName: `${props.slug}-${props.appEnv}-database`,
13
+ partitionKey: {
14
+ name: 'id',
15
+ type: dynamodb.AttributeType.STRING,
16
+ },
17
+ sortKey: {
18
+ name: 'sort',
19
+ type: dynamodb.AttributeType.STRING,
20
+ },
21
+ billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
22
+ readCapacity: 5,
23
+ writeCapacity: 5,
24
+ pointInTimeRecovery: true,
25
+ removalPolicy: RemovalPolicy.DESTROY,
26
+ encryption: dynamodb.TableEncryption.AWS_MANAGED,
27
+ })
28
+ }
29
+ }
@@ -0,0 +1,60 @@
1
+ import type { Construct } from 'constructs';
2
+ import type { NestedCloudProps } from '../types';
3
+
4
+ export declare interface DeploymentStackProps extends NestedCloudProps {
5
+ publicBucket: s3.Bucket
6
+ docsBucket?: s3.Bucket
7
+ privateBucket: s3.Bucket
8
+ mainDistribution: cloudfront.Distribution
9
+ docsDistribution?: cloudfront.Distribution
10
+ }
11
+ export declare class DeploymentStack {
12
+ privateSource: string
13
+ docsSource: string
14
+ publicSource: string
15
+
16
+ constructor(scope: Construct, props: DeploymentStackProps) {
17
+ this.privateSource = '../../private'
18
+ this.docsSource = '../docs/dist/'
19
+ this.publicSource = config.app.docMode === true ? this.docsSource : '../views/web/dist/'
20
+ const mainBucket = config.app.docMode === true ? props.docsBucket : props.publicBucket
21
+
22
+ new s3deploy.BucketDeployment(scope, 'Website', {
23
+ sources: [
24
+ s3deploy.Source.asset(this.publicSource, {
25
+ assetHash: websiteSourceHash(),
26
+ assetHashType: AssetHashType.CUSTOM,
27
+ }),
28
+ ],
29
+ destinationBucket: mainBucket as s3.Bucket,
30
+ distribution: props.mainDistribution,
31
+ })
32
+
33
+ if (this.shouldDeployDocs()) {
34
+ new s3deploy.BucketDeployment(scope, 'Docs', {
35
+ sources: [
36
+ s3deploy.Source.asset(this.docsSource, {
37
+ assetHash: docsSourceHash(),
38
+ assetHashType: AssetHashType.CUSTOM,
39
+ }),
40
+ ],
41
+ destinationBucket: props.docsBucket as s3.Bucket,
42
+ distribution: props.docsDistribution,
43
+ })
44
+ }
45
+
46
+ if (hasFiles(this.privateSource)) {
47
+ new s3deploy.BucketDeployment(scope, 'PrivateFiles', {
48
+ sources: [s3deploy.Source.asset(this.privateSource)],
49
+ destinationBucket: props.privateBucket,
50
+ })
51
+ }
52
+ else {
53
+ console.error(`The path ${this.privateSource} does not have any files`)
54
+ }
55
+ }
56
+
57
+ shouldDeployDocs(): boolean {
58
+ return hasFiles(p.projectPath('docs')) && !config.app.docMode
59
+ }
60
+ }
package/dist/dns.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import type { Construct } from 'constructs';
2
+ import type { NestedCloudProps } from '../types';
3
+
4
+ export declare class DnsStack {
5
+ zone: route53.IHostedZone
6
+
7
+ constructor(scope: Construct, props: NestedCloudProps) {
8
+ this.zone = route53.PublicHostedZone.fromLookup(scope, 'AppUrlHostedZone', {
9
+ domainName: props.domain,
10
+ })
11
+
12
+ const wwwBucket = new s3.Bucket(scope, 'WwwBucket', {
13
+ bucketName: `www.${props.domain}`,
14
+ websiteRedirect: {
15
+ hostName: props.domain,
16
+ protocol: s3.RedirectProtocol.HTTPS,
17
+ },
18
+ removalPolicy: RemovalPolicy.DESTROY,
19
+ autoDeleteObjects: true,
20
+ })
21
+
22
+ new route53.ARecord(scope, 'WwwAliasRecord', {
23
+ recordName: `www.${props.domain}`,
24
+ zone: this.zone,
25
+ target: route53.RecordTarget.fromAlias(new targets.BucketWebsiteTarget(wwwBucket)),
26
+ })
27
+
28
+ new route53.ARecord(scope, 'StoreAliasRecord', {
29
+ recordName: `store.${props.domain}`,
30
+ zone: this.zone,
31
+ target: route53.RecordTarget.fromIpAddresses('137.66.37.136'),
32
+ })
33
+ }
34
+ }
package/dist/docs.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import type { CfnResource } from 'aws-cdk-lib';
2
+ import type { Construct } from 'constructs';
3
+ import type { NestedCloudProps } from '../types';
4
+
5
+ export declare interface DocsStackProps extends NestedCloudProps {
6
+ }
7
+ export declare class DocsStack {
8
+ originRequestFunction: lambda.Function
9
+
10
+ constructor(scope: Construct, props: DocsStackProps) {
11
+ const docsPrefix = 'docs'
12
+
13
+ this.originRequestFunction = new lambda.Function(scope, 'OriginRequestFunction', {
14
+ functionName: `${props.slug}-${props.appEnv}-origin-request-${props.timestamp}`,
15
+ description: 'The Stacks Origin Request function that prettifies URLs by removing the .html extension',
16
+ runtime: lambda.Runtime.NODEJS_20_X,
17
+ handler: 'dist/origin-request.handler',
18
+ code: lambda.Code.fromAsset(p.cloudPath('dist.zip'), {
19
+ assetHash: originRequestFunctionHash(),
20
+ assetHashType: AssetHashType.CUSTOM,
21
+ }),
22
+ })
23
+
24
+ const cfnOriginRequestFunction = this.originRequestFunction.node.defaultChild as CfnResource
25
+ cfnOriginRequestFunction.applyRemovalPolicy(RemovalPolicy.RETAIN)
26
+
27
+ if (!config.app.docMode && storage.hasFiles(p.projectPath('docs'))) {
28
+ new Output(scope, 'DocsUrl', {
29
+ value: `https:
30
+ description: 'The URL of the deployed documentation',
31
+ })
32
+ }
33
+ }
34
+ }