@stacksjs/cloud 0.58.47 → 0.58.49

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 (42) hide show
  1. package/package.json +15 -14
  2. package/src/cloud/ai.ts +94 -0
  3. package/src/cloud/aws-sdk-layer/nodejs/package-lock.json +386 -0
  4. package/src/cloud/aws-sdk-layer/nodejs/package.json +15 -0
  5. package/src/cloud/cache.ts +0 -0
  6. package/src/cloud/cdn.ts +374 -0
  7. package/src/cloud/cli.ts +45 -0
  8. package/src/cloud/compute.ts +80 -0
  9. package/src/cloud/dashboard.ts +85 -0
  10. package/src/cloud/database.ts +0 -0
  11. package/src/cloud/deployment.ts +41 -0
  12. package/src/cloud/dns.ts +34 -0
  13. package/src/cloud/docs.ts +51 -0
  14. package/src/cloud/email.ts +339 -0
  15. package/src/cloud/file-system.ts +35 -0
  16. package/src/cloud/index.ts +99 -0
  17. package/src/cloud/jump-box.ts +48 -0
  18. package/src/cloud/lambda/ask/index.js +35 -0
  19. package/src/cloud/lambda/cli-setup/index.js +67 -0
  20. package/src/cloud/lambda/summarize/index.js +35 -0
  21. package/src/cloud/network.ts +52 -0
  22. package/src/cloud/package/README.md +59 -0
  23. package/src/cloud/package/package.json +63 -0
  24. package/src/cloud/permissions.ts +33 -0
  25. package/src/cloud/queue.ts +0 -0
  26. package/src/cloud/redirects.ts +45 -0
  27. package/src/cloud/router-layer/nodejs/package.json +15 -0
  28. package/src/cloud/search-engine.ts +108 -0
  29. package/src/cloud/security.ts +259 -0
  30. package/src/cloud/stacksjs-router-0.58.48.tgz +0 -0
  31. package/src/cloud/storage.ts +168 -0
  32. package/src/edge/origin-request.ts +49 -0
  33. package/src/helpers.ts +597 -0
  34. package/src/index.ts +3 -0
  35. package/src/runtime/README.md +116 -0
  36. package/src/runtime/bootstrap +3 -0
  37. package/src/runtime/example/lambda.ts +36 -0
  38. package/src/runtime/runtime.ts +830 -0
  39. package/src/runtime/scripts/build-layer.ts +104 -0
  40. package/src/runtime/scripts/publish-layer.ts +110 -0
  41. package/src/runtime/server.ts +39 -0
  42. package/src/types.ts +28 -0
@@ -0,0 +1,374 @@
1
+ /* eslint-disable no-new */
2
+ import type { aws_certificatemanager as acm, aws_lambda as lambda, aws_s3 as s3, aws_wafv2 as wafv2 } from 'aws-cdk-lib'
3
+ import { Duration, Fn, CfnOutput as Output, aws_cloudfront as cloudfront, aws_cloudfront_origins as origins, aws_route53 as route53, aws_route53_targets as targets } from 'aws-cdk-lib'
4
+ import type { Construct } from 'constructs'
5
+ import { config } from '@stacksjs/config'
6
+ import { hasFiles } from '@stacksjs/storage'
7
+ import { path as p } from '@stacksjs/path'
8
+ import { env } from '@stacksjs/env'
9
+ import type { NestedCloudProps } from '../types'
10
+ import type { EnvKey } from '../../../../env'
11
+
12
+ export interface CdnStackProps extends NestedCloudProps {
13
+ certificate: acm.Certificate
14
+ logBucket: s3.Bucket
15
+ publicBucket: s3.Bucket
16
+ firewall: wafv2.CfnWebACL
17
+ originRequestFunction: lambda.Function
18
+ zone: route53.IHostedZone
19
+ webServer: lambda.Function
20
+ webServerUrl: lambda.FunctionUrl
21
+ cliSetupUrl: lambda.FunctionUrl
22
+ askAiUrl: lambda.FunctionUrl
23
+ summarizeAiUrl: lambda.FunctionUrl
24
+ }
25
+
26
+ export class CdnStack {
27
+ distribution: cloudfront.Distribution
28
+ originAccessIdentity: cloudfront.OriginAccessIdentity
29
+ cdnCachePolicy: cloudfront.CachePolicy
30
+ apiCachePolicy: cloudfront.CachePolicy | undefined
31
+ vanityUrl: string
32
+ props: CdnStackProps
33
+
34
+ constructor(scope: Construct, props: CdnStackProps) {
35
+ this.props = props
36
+
37
+ this.originAccessIdentity = new cloudfront.OriginAccessIdentity(scope, 'OAI')
38
+
39
+ this.cdnCachePolicy = new cloudfront.CachePolicy(scope, 'CdnCachePolicy', {
40
+ comment: 'Stacks CDN Cache Policy',
41
+ cachePolicyName: `${props.slug}-${props.appEnv}-cdn-cache-policy`,
42
+ minTtl: config.cloud.cdn?.minTtl ? Duration.seconds(config.cloud.cdn.minTtl) : undefined,
43
+ defaultTtl: config.cloud.cdn?.defaultTtl ? Duration.seconds(config.cloud.cdn.defaultTtl) : undefined,
44
+ maxTtl: config.cloud.cdn?.maxTtl ? Duration.seconds(config.cloud.cdn.maxTtl) : undefined,
45
+ cookieBehavior: this.getCookieBehavior(config.cloud.cdn?.cookieBehavior),
46
+ })
47
+
48
+ // the actual CDN distribution
49
+ this.distribution = new cloudfront.Distribution(scope, 'Cdn', {
50
+ domainNames: [props.domain],
51
+ defaultRootObject: 'index.html',
52
+ comment: `CDN for ${config.app.url}`,
53
+ certificate: props.certificate,
54
+ enableLogging: true,
55
+ logBucket: props.logBucket,
56
+ httpVersion: cloudfront.HttpVersion.HTTP2_AND_3,
57
+ priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
58
+ enabled: true,
59
+ minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
60
+ webAclId: props.firewall.attrArn,
61
+ enableIpv6: true,
62
+
63
+ defaultBehavior: {
64
+ origin: new origins.S3Origin(props.publicBucket, {
65
+ originAccessIdentity: this.originAccessIdentity,
66
+ }),
67
+ edgeLambdas: [
68
+ {
69
+ eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
70
+ functionVersion: props.originRequestFunction.currentVersion,
71
+ },
72
+ ],
73
+ compress: config.cloud.cdn?.compress,
74
+ allowedMethods: this.allowedMethods(),
75
+ cachedMethods: this.cachedMethods(),
76
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
77
+ cachePolicy: this.cdnCachePolicy,
78
+ },
79
+
80
+ additionalBehaviors: this.additionalBehaviors(scope, props),
81
+
82
+ // Add custom error responses
83
+ errorResponses: [
84
+ {
85
+ httpStatus: 403,
86
+ responsePagePath: '/index.html',
87
+ responseHttpStatus: 200,
88
+ ttl: Duration.seconds(0),
89
+ },
90
+ ],
91
+ })
92
+
93
+ new route53.ARecord(scope, 'AliasRecord', {
94
+ recordName: props.domain,
95
+ zone: props.zone,
96
+ target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(this.distribution)),
97
+ })
98
+
99
+ new Output(scope, 'DistributionId', {
100
+ value: this.distribution.distributionId,
101
+ })
102
+
103
+ new Output(scope, 'AppUrl', {
104
+ value: `https://${props.domain}`,
105
+ description: 'The URL of the deployed application',
106
+ })
107
+
108
+ this.vanityUrl = `https://${this.distribution.domainName}`
109
+ new Output(scope, 'AppVanityUrl', {
110
+ value: this.vanityUrl,
111
+ description: 'The vanity URL of the deployed application',
112
+ })
113
+
114
+ // this.originAccessIdentity = originAccessIdentity
115
+ // this.cdnCachePolicy = cdnCachePolicy
116
+ }
117
+
118
+ getCookieBehavior(behavior: string | undefined): cloudfront.CacheCookieBehavior | undefined {
119
+ switch (behavior) {
120
+ case 'all':
121
+ return cloudfront.CacheCookieBehavior.all()
122
+ case 'none':
123
+ return cloudfront.CacheCookieBehavior.none()
124
+ case 'allowList':
125
+ // If you have a list of cookies, replace `myCookie` with your cookie
126
+ return cloudfront.CacheCookieBehavior.allowList(...config.cloud.cdn?.allowList.cookies || [])
127
+ default:
128
+ return undefined
129
+ }
130
+ }
131
+
132
+ allowedMethods(): cloudfront.AllowedMethods {
133
+ switch (config.cloud.cdn?.allowedMethods) {
134
+ case 'ALL':
135
+ return cloudfront.AllowedMethods.ALLOW_ALL
136
+ case 'GET_HEAD':
137
+ return cloudfront.AllowedMethods.ALLOW_GET_HEAD
138
+ case 'GET_HEAD_OPTIONS':
139
+ return cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS
140
+ default:
141
+ return cloudfront.AllowedMethods.ALLOW_ALL
142
+ }
143
+ }
144
+
145
+ cachedMethods(): cloudfront.CachedMethods {
146
+ switch (config.cloud.cdn?.cachedMethods) {
147
+ case 'GET_HEAD':
148
+ return cloudfront.CachedMethods.CACHE_GET_HEAD
149
+ case 'GET_HEAD_OPTIONS':
150
+ return cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS
151
+ default:
152
+ return cloudfront.CachedMethods.CACHE_GET_HEAD
153
+ }
154
+ }
155
+
156
+ allowedMethodsFromString(methods?: 'ALL' | 'GET_HEAD' | 'GET_HEAD_OPTIONS'): cloudfront.AllowedMethods {
157
+ if (!methods)
158
+ return cloudfront.AllowedMethods.ALLOW_ALL
159
+
160
+ switch (methods) {
161
+ case 'ALL':
162
+ return cloudfront.AllowedMethods.ALLOW_ALL
163
+ case 'GET_HEAD':
164
+ return cloudfront.AllowedMethods.ALLOW_GET_HEAD
165
+ case 'GET_HEAD_OPTIONS':
166
+ return cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS
167
+ default:
168
+ return cloudfront.AllowedMethods.ALLOW_ALL
169
+ }
170
+ }
171
+
172
+ cachedMethodsFromString(methods?: 'GET_HEAD' | 'GET_HEAD_OPTIONS'): cloudfront.CachedMethods {
173
+ if (!methods)
174
+ return cloudfront.CachedMethods.CACHE_GET_HEAD
175
+
176
+ switch (methods) {
177
+ case 'GET_HEAD':
178
+ return cloudfront.CachedMethods.CACHE_GET_HEAD
179
+ case 'GET_HEAD_OPTIONS':
180
+ return cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS
181
+ default:
182
+ return cloudfront.CachedMethods.CACHE_GET_HEAD
183
+ }
184
+ }
185
+
186
+ shouldDeployApi() {
187
+ return config.cloud.api
188
+ }
189
+
190
+ apiBehaviorOptions(scope: Construct, props: CdnStackProps): Record<string, cloudfront.BehaviorOptions> {
191
+ const hostname = Fn.select(2, Fn.split('/', props.webServerUrl.url))
192
+ const origin = (path: '/api' | '/api/*' = '/api') => {
193
+ return new origins.HttpOrigin(hostname, {
194
+ originPath: path,
195
+ protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
196
+ })
197
+ }
198
+
199
+ return {
200
+ '/api': {
201
+ origin: origin(),
202
+ compress: true,
203
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
204
+ cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS,
205
+ cachePolicy: this.setApiCachePolicy(scope),
206
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
207
+ },
208
+ '/api/*': {
209
+ origin: origin('/api/*'),
210
+ compress: true,
211
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
212
+ cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS,
213
+ cachePolicy: this.apiCachePolicy,
214
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
215
+ },
216
+ }
217
+ }
218
+
219
+ docsBehaviorOptions(props: CdnStackProps): Record<string, cloudfront.BehaviorOptions> {
220
+ return {
221
+ '/docs': {
222
+ origin: new origins.S3Origin(props.publicBucket, {
223
+ originAccessIdentity: this.originAccessIdentity,
224
+ originPath: '/docs',
225
+ }),
226
+ compress: true,
227
+ allowedMethods: this.allowedMethodsFromString(config.cloud.cdn?.allowedMethods),
228
+ cachedMethods: this.cachedMethodsFromString(config.cloud.cdn?.cachedMethods),
229
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
230
+ cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
231
+ },
232
+ '/docs/*': {
233
+ origin: new origins.S3Origin(props.publicBucket, {
234
+ originAccessIdentity: this.originAccessIdentity,
235
+ originPath: '/docs',
236
+ }),
237
+ compress: true,
238
+ allowedMethods: this.allowedMethodsFromString(config.cloud.cdn?.allowedMethods),
239
+ cachedMethods: this.cachedMethodsFromString(config.cloud.cdn?.cachedMethods),
240
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
241
+ cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
242
+ },
243
+ }
244
+ }
245
+
246
+ aiBehaviorOptions(scope: Construct, props: CdnStackProps): Record<string, cloudfront.BehaviorOptions> {
247
+ const hostname = Fn.select(2, Fn.split('/', props.askAiUrl.url))
248
+ const summaryHostname = Fn.select(2, Fn.split('/', props.summarizeAiUrl.url))
249
+
250
+ const aiCachePolicy = new cloudfront.CachePolicy(scope, 'AiCachePolicy', {
251
+ comment: 'Stacks AI Cache Policy',
252
+ cachePolicyName: `${this.props.slug}-${this.props.appEnv}-ai-cache-policy`,
253
+ defaultTtl: Duration.seconds(0),
254
+ // minTtl: config.cloud.cdn?.minTtl ? Duration.seconds(config.cloud.cdn.minTtl) : undefined,
255
+ cookieBehavior: cloudfront.CacheCookieBehavior.none(),
256
+ headerBehavior: cloudfront.CacheHeaderBehavior.allowList('Accept', 'x-api-key', 'Authorization', 'Content-Type'),
257
+ queryStringBehavior: cloudfront.CacheQueryStringBehavior.all(),
258
+ })
259
+
260
+ return {
261
+ '/ai/ask': {
262
+ origin: new origins.HttpOrigin(hostname, {
263
+ originPath: '/ai',
264
+ protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
265
+ }),
266
+ compress: false,
267
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
268
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
269
+ cachePolicy: aiCachePolicy,
270
+ },
271
+ '/ai/summary': {
272
+ origin: new origins.HttpOrigin(summaryHostname, {
273
+ originPath: '/ai',
274
+ protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
275
+ }),
276
+ compress: false,
277
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
278
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
279
+ cachePolicy: aiCachePolicy,
280
+ },
281
+ }
282
+ }
283
+
284
+ cliSetupBehaviorOptions(scope: Construct, props: CdnStackProps): Record<string, cloudfront.BehaviorOptions> {
285
+ const hostname = Fn.select(2, Fn.split('/', props.cliSetupUrl.url))
286
+
287
+ return {
288
+ '/install': {
289
+ origin: new origins.HttpOrigin(hostname, {
290
+ // origin: new origins.HttpOrigin('tipevv3dfx35fb7ptyq7nrxtga0qkcgc.lambda-url.us-east-1.on.aws', {
291
+ originPath: '/cli-setup',
292
+ protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
293
+ }),
294
+ compress: false,
295
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
296
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
297
+ cachePolicy: new cloudfront.CachePolicy(scope, 'CliSetupCachePolicy', {
298
+ comment: 'Stacks CLI Setup Cache Policy',
299
+ cachePolicyName: `${this.props.slug}-${this.props.appEnv}-cli-setup-cache-policy`,
300
+ defaultTtl: Duration.seconds(0),
301
+ // minTtl: config.cloud.cdn?.minTtl ? Duration.seconds(config.cloud.cdn.minTtl) : undefined,
302
+ cookieBehavior: cloudfront.CacheCookieBehavior.none(),
303
+ headerBehavior: cloudfront.CacheHeaderBehavior.none(),
304
+ queryStringBehavior: cloudfront.CacheQueryStringBehavior.none(),
305
+ }),
306
+ },
307
+ }
308
+ }
309
+
310
+ shouldDeployAiEndpoints() {
311
+ return config.cloud.ai
312
+ }
313
+
314
+ shouldDeployCliSetup() {
315
+ return config.cloud.cli
316
+ }
317
+
318
+ shouldDeployDocs() {
319
+ return hasFiles(p.projectPath('docs'))
320
+ }
321
+
322
+ additionalBehaviors(scope: Construct, props: CdnStackProps): Record<string, cloudfront.BehaviorOptions> {
323
+ let behaviorOptions: Record<string, cloudfront.BehaviorOptions> = {}
324
+
325
+ if (this.shouldDeployApi()) {
326
+ const keysToRemove = ['_HANDLER', '_X_AMZN_TRACE_ID', 'AWS_REGION', 'AWS_EXECUTION_ENV', 'AWS_LAMBDA_FUNCTION_NAME', 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE', 'AWS_LAMBDA_FUNCTION_VERSION', 'AWS_LAMBDA_INITIALIZATION_TYPE', 'AWS_LAMBDA_LOG_GROUP_NAME', 'AWS_LAMBDA_LOG_STREAM_NAME', 'AWS_ACCESS_KEY', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'AWS_LAMBDA_RUNTIME_API', 'LAMBDA_TASK_ROOT', 'LAMBDA_RUNTIME_DIR', '_']
327
+ keysToRemove.forEach(key => delete env[key as EnvKey])
328
+
329
+ behaviorOptions = this.apiBehaviorOptions(scope, props)
330
+ }
331
+
332
+ // if docMode is used, we don't need to add a behavior for the docs
333
+ // because the docs will be the root of the site
334
+ if (this.shouldDeployDocs() && !config.app.docMode) {
335
+ behaviorOptions = {
336
+ ...this.docsBehaviorOptions(props),
337
+ ...behaviorOptions,
338
+ }
339
+ }
340
+
341
+ if (this.shouldDeployAiEndpoints()) {
342
+ behaviorOptions = {
343
+ ...this.aiBehaviorOptions(scope, props),
344
+ ...behaviorOptions,
345
+ }
346
+ }
347
+
348
+ if (this.shouldDeployCliSetup()) {
349
+ behaviorOptions = {
350
+ ...this.cliSetupBehaviorOptions(scope, props),
351
+ ...behaviorOptions,
352
+ }
353
+ }
354
+
355
+ return behaviorOptions
356
+ }
357
+
358
+ setApiCachePolicy(scope: Construct) {
359
+ if (this.apiCachePolicy)
360
+ return this.apiCachePolicy
361
+
362
+ this.apiCachePolicy = new cloudfront.CachePolicy(scope, 'ApiCachePolicy', {
363
+ comment: 'Stacks API Cache Policy',
364
+ cachePolicyName: `${this.props.slug}-${this.props.appEnv}-api-cache-policy`,
365
+ defaultTtl: Duration.seconds(0),
366
+ // minTtl: config.cloud.cdn?.minTtl ? Duration.seconds(config.cloud.cdn.minTtl) : undefined,
367
+ cookieBehavior: cloudfront.CacheCookieBehavior.none(),
368
+ headerBehavior: cloudfront.CacheHeaderBehavior.allowList('Accept', 'x-api-key', 'Authorization', 'Content-Type'),
369
+ queryStringBehavior: cloudfront.CacheQueryStringBehavior.none(),
370
+ })
371
+
372
+ return this.apiCachePolicy
373
+ }
374
+ }
@@ -0,0 +1,45 @@
1
+ /* eslint-disable no-new */
2
+ import { Duration, CfnOutput as Output, aws_lambda as lambda } from 'aws-cdk-lib'
3
+ import type { Construct } from 'constructs'
4
+ import type { NestedCloudProps } from '../types'
5
+
6
+ export interface CliStackProps extends NestedCloudProps {
7
+ }
8
+
9
+ export class CliStack {
10
+ cliSetupUrl: lambda.FunctionUrl
11
+
12
+ constructor(scope: Construct, props: CliStackProps) {
13
+ const cliSetupFunc = new lambda.Function(scope, 'CliSetupFunction', {
14
+ functionName: `${props.slug}-${props.appEnv}-cli-setup`,
15
+ description: 'Lambda function that triggers setup script for a Stacks project',
16
+ runtime: lambda.Runtime.NODEJS_20_X,
17
+ handler: 'index.handler',
18
+ code: lambda.Code.fromAsset('src/cloud/lambda/cli-setup'), // path relative to the cloud root package dir
19
+ timeout: Duration.seconds(30),
20
+ })
21
+
22
+ // create a Lambda.FunctionUrl to be used in the CloudFront OriginRequestPolicy
23
+ // this will be used to trigger the function
24
+ this.cliSetupUrl = new lambda.FunctionUrl(scope, 'CliSetupFunctionUrl', {
25
+ function: cliSetupFunc,
26
+ authType: lambda.FunctionUrlAuthType.NONE,
27
+ cors: {
28
+ allowedOrigins: ['*'],
29
+ },
30
+ })
31
+
32
+ new Output(scope, 'CliSetupVanityUrl', {
33
+ value: `${this.cliSetupUrl.url}cli-setup`,
34
+ })
35
+
36
+ new Output(scope, 'CliSetupUrl', {
37
+ value: `https://${props.domain}/install`,
38
+ description: 'URL to trigger the CLI setup function',
39
+ })
40
+
41
+ // once deployed, need to create logic in the cdn origin request to check if the request is for the cli
42
+ // if it is, then we need the to use the function url as the origin
43
+ // if it is not, then we need don't adjust the origin
44
+ }
45
+ }
@@ -0,0 +1,80 @@
1
+ /* eslint-disable no-new */
2
+ import type { aws_certificatemanager as acm, aws_ec2 as ec2, aws_efs as efs, aws_route53 as route53 } from 'aws-cdk-lib'
3
+ import { Duration, CfnOutput as Output, aws_lambda as lambda, aws_logs as logs, aws_secretsmanager as secretsmanager } from 'aws-cdk-lib'
4
+ import type { Construct } from 'constructs'
5
+ import { path as p } from '@stacksjs/path'
6
+ import { env } from '@stacksjs/env'
7
+ import type { NestedCloudProps } from '../types'
8
+ import type { EnvKey } from '../../../../env'
9
+
10
+ export interface ComputeStackProps extends NestedCloudProps {
11
+ vpc: ec2.Vpc
12
+ fileSystem: efs.FileSystem
13
+ zone: route53.IHostedZone
14
+ certificate: acm.Certificate
15
+ }
16
+
17
+ export class ComputeStack {
18
+ apiServer: lambda.Function
19
+ apiServerUrl: lambda.FunctionUrl
20
+
21
+ constructor(scope: Construct, props: ComputeStackProps) {
22
+ const vpc = props.vpc
23
+ const fileSystem = props.fileSystem
24
+
25
+ if (!fileSystem)
26
+ throw new Error('The file system is missing. Please make sure it was created properly.')
27
+
28
+ // const dockerImageAsset = new ecr_assets.DockerImageAsset(scope, 'ServerBuildImage', {
29
+ // directory: p.cloudPath('src/server'),
30
+ // })
31
+
32
+ this.apiServer = new lambda.Function(scope, 'WebServer', {
33
+ functionName: `${props.slug}-${props.appEnv}-web-server`,
34
+ description: 'The web server for the Stacks application',
35
+ code: lambda.Code.fromAssetImage(p.frameworkPath('server')),
36
+ handler: lambda.Handler.FROM_IMAGE,
37
+ runtime: lambda.Runtime.FROM_IMAGE,
38
+ vpc,
39
+ memorySize: 512, // replace with your actual memory size
40
+ timeout: Duration.minutes(5), // replace with your actual timeout
41
+ logRetention: logs.RetentionDays.ONE_WEEK,
42
+ architecture: lambda.Architecture.ARM_64,
43
+ // filesystem: lambda.FileSystem.fromEfsAccessPoint(props.accessPoint, '/mnt/efs'),
44
+ })
45
+
46
+ const keysToRemove = ['_HANDLER', '_X_AMZN_TRACE_ID', 'AWS_REGION', 'AWS_EXECUTION_ENV', 'AWS_LAMBDA_FUNCTION_NAME', 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE', 'AWS_LAMBDA_FUNCTION_VERSION', 'AWS_LAMBDA_INITIALIZATION_TYPE', 'AWS_LAMBDA_LOG_GROUP_NAME', 'AWS_LAMBDA_LOG_STREAM_NAME', 'AWS_ACCESS_KEY', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'AWS_LAMBDA_RUNTIME_API', 'LAMBDA_TASK_ROOT', 'LAMBDA_RUNTIME_DIR', '_']
47
+ keysToRemove.forEach(key => delete env[key as EnvKey])
48
+
49
+ const secrets = new secretsmanager.Secret(scope, 'StacksSecrets', {
50
+ secretName: `${props.slug}-${props.appEnv}-secrets`,
51
+ description: 'Secrets for the Stacks application',
52
+ generateSecretString: {
53
+ secretStringTemplate: JSON.stringify(env),
54
+ generateStringKey: Object.keys(env).join(',').length.toString(),
55
+ },
56
+ })
57
+
58
+ secrets.grantRead(this.apiServer)
59
+ this.apiServer.addEnvironment('SECRETS_ARN', secrets.secretArn)
60
+
61
+ this.apiServerUrl = new lambda.FunctionUrl(scope, 'StacksServerUrl', {
62
+ function: this.apiServer,
63
+ authType: lambda.FunctionUrlAuthType.NONE, // becomes a public API
64
+ cors: {
65
+ allowedOrigins: ['*'],
66
+ },
67
+ })
68
+
69
+ const apiPrefix = 'api'
70
+ new Output(scope, 'ApiUrl', {
71
+ value: `https://${props.domain}/${apiPrefix}`,
72
+ description: 'The URL of the deployed application',
73
+ })
74
+
75
+ new Output(scope, 'ApiVanityUrl', {
76
+ value: this.apiServerUrl.url,
77
+ description: 'The Vanity URL of the deployed application',
78
+ })
79
+ }
80
+ }
@@ -0,0 +1,85 @@
1
+ /* eslint-disable no-new */
2
+ // TODO: finish this cloudwatch dashboard
3
+ // import type { aws_lambda as lambda } from 'aws-cdk-lib'
4
+ import { Aws, CfnOutput as Output, aws_cloudwatch as cloudwatch } from 'aws-cdk-lib'
5
+ import type { Construct } from 'constructs'
6
+ import type { NestedCloudProps } from '../types'
7
+
8
+ export interface DashboardStackProps extends NestedCloudProps {
9
+ dashboardName?: string
10
+ }
11
+
12
+ export class DashboardStack {
13
+ // lambdaFunction: lambda.Function
14
+ dashboard: cloudwatch.Dashboard
15
+
16
+ constructor(scope: Construct, props: DashboardStackProps) {
17
+ const dashboardName = props.dashboardName || 'StacksDashboard'
18
+
19
+ // Create Sample Lambda Function which will create metrics
20
+ // this.lambdaFunction = new Function(this, 'SampleLambda', {
21
+ // handler: 'lambda-handler.handler',
22
+ // runtime: Runtime.PYTHON_3_7,
23
+ // code: new AssetCode(`./lambda`),
24
+ // memorySize: 512,
25
+ // timeout: Duration.seconds(10),
26
+ // })
27
+
28
+ // Create CloudWatch Dashboard
29
+ this.dashboard = new cloudwatch.Dashboard(scope, 'SampleLambdaDashboard', {
30
+ dashboardName,
31
+ })
32
+
33
+ // Create Title for Dashboard
34
+ this.dashboard.addWidgets(new cloudwatch.TextWidget({
35
+ markdown: `# Dashboard: ${this.lambdaFunction.functionName}`,
36
+ height: 1,
37
+ width: 24,
38
+ }))
39
+
40
+ // Create CloudWatch Dashboard Widgets: Errors, Invocations, Duration, Throttles
41
+ this.dashboard.addWidgets(new cloudwatch.GraphWidget({
42
+ title: 'Invocations',
43
+ left: [this.lambdaFunction.metricInvocations()],
44
+ width: 24,
45
+ }))
46
+
47
+ this.dashboard.addWidgets(new cloudwatch.GraphWidget({
48
+ title: 'Errors',
49
+ left: [this.lambdaFunction.metricErrors()],
50
+ width: 24,
51
+ }))
52
+
53
+ this.dashboard.addWidgets(new cloudwatch.GraphWidget({
54
+ title: 'Duration',
55
+ left: [this.lambdaFunction.metricDuration()],
56
+ width: 24,
57
+ }))
58
+
59
+ this.dashboard.addWidgets(new cloudwatch.GraphWidget({
60
+ title: 'Throttles',
61
+ left: [this.lambdaFunction.metricThrottles()],
62
+ width: 24,
63
+ }))
64
+
65
+ // Create Widget to show last 20 Log Entries
66
+ this.dashboard.addWidgets(new cloudwatch.LogQueryWidget({
67
+ logGroupNames: [this.lambdaFunction.logGroup.logGroupName],
68
+ queryLines: [
69
+ 'fields @timestamp, @message',
70
+ 'sort @timestamp desc',
71
+ 'limit 20',
72
+ ],
73
+ width: 24,
74
+ }))
75
+
76
+ // Generate Output
77
+ const cloudwatchDashboardURL = `https://${Aws.REGION}.console.aws.amazon.com/cloudwatch/home?region=${Aws.REGION}#dashboards:name=${dashboardName}`
78
+
79
+ new Output(scope, 'DashboardOutput', {
80
+ value: cloudwatchDashboardURL,
81
+ description: 'The CloudWatch Dashboard URL',
82
+ exportName: 'StacksDashboardURL',
83
+ })
84
+ }
85
+ }
File without changes
@@ -0,0 +1,41 @@
1
+ /* eslint-disable no-new */
2
+ import type { aws_cloudfront as cloudfront, aws_s3 as s3 } from 'aws-cdk-lib'
3
+ import { AssetHashType, aws_s3_deployment as s3deploy } from 'aws-cdk-lib'
4
+ import { config } from '@stacksjs/config'
5
+ import { websiteSourceHash } from '@stacksjs/utils'
6
+ import type { Construct } from 'constructs'
7
+ import type { NestedCloudProps } from '../types'
8
+
9
+ export interface DeploymentStackProps extends NestedCloudProps {
10
+ publicBucket: s3.Bucket
11
+ privateBucket: s3.Bucket
12
+ cdn: cloudfront.Distribution
13
+ }
14
+
15
+ export class DeploymentStack {
16
+ privateSource: string
17
+ docsSource: string
18
+ websiteSource: string
19
+
20
+ constructor(scope: Construct, props: DeploymentStackProps) {
21
+ // following paths are relative to where the command is run from
22
+ this.privateSource = '../../../private'
23
+ this.docsSource = '../../docs/dist/'
24
+ this.websiteSource = config.app.docMode === true ? this.docsSource : '../../views/dist/'
25
+
26
+ new s3deploy.BucketDeployment(scope, 'Website', {
27
+ sources: [s3deploy.Source.asset(this.websiteSource, {
28
+ assetHash: websiteSourceHash,
29
+ assetHashType: AssetHashType.CUSTOM,
30
+ })],
31
+ destinationBucket: props.publicBucket,
32
+ distribution: props.cdn,
33
+ distributionPaths: ['/*'],
34
+ })
35
+
36
+ new s3deploy.BucketDeployment(scope, 'PrivateFiles', {
37
+ sources: [s3deploy.Source.asset(this.privateSource)],
38
+ destinationBucket: props.privateBucket,
39
+ })
40
+ }
41
+ }
@@ -0,0 +1,34 @@
1
+ /* eslint-disable no-new */
2
+ import { RemovalPolicy, aws_route53 as route53, aws_s3 as s3, aws_route53_targets as targets } from 'aws-cdk-lib'
3
+ import type { Construct } from 'constructs'
4
+ import type { NestedCloudProps } from '../types'
5
+
6
+ export class DnsStack {
7
+ zone: route53.IHostedZone
8
+
9
+ constructor(scope: Construct, props: NestedCloudProps) {
10
+ // lets see if the zone already exists because Buddy should have created it already
11
+ this.zone = route53.PublicHostedZone.fromLookup(scope, 'AppUrlHostedZone', {
12
+ domainName: props.domain,
13
+ })
14
+
15
+ // setup the www redirect
16
+ // Create a bucket for www.yourdomain.com and configure it to redirect to yourdomain.com
17
+ const wwwBucket = new s3.Bucket(scope, 'WwwBucket', {
18
+ bucketName: `www.${props.domain}`,
19
+ websiteRedirect: {
20
+ hostName: props.domain,
21
+ protocol: s3.RedirectProtocol.HTTPS,
22
+ },
23
+ removalPolicy: RemovalPolicy.DESTROY,
24
+ autoDeleteObjects: true,
25
+ })
26
+
27
+ // Create a Route53 record for www.yourdomain.com
28
+ new route53.ARecord(scope, 'WwwAliasRecord', {
29
+ recordName: `www.${props.domain}`,
30
+ zone: this.zone,
31
+ target: route53.RecordTarget.fromAlias(new targets.BucketWebsiteTarget(wwwBucket)),
32
+ })
33
+ }
34
+ }