@stacksjs/cloud 0.58.49 → 0.58.51

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/index.js CHANGED
@@ -406,6 +406,7 @@ var appEnv = config2.app.env === "local" ? "dev" : config2.app.env;
406
406
  var cloudName = `stacks-cloud-${appEnv}`;
407
407
  // src/cloud/index.ts
408
408
  import {Stack as Stack2} from "aws-cdk-lib";
409
+ import {config as config20} from "@stacksjs/config";
409
410
 
410
411
  // src/cloud/ai.ts
411
412
  import {Duration, CfnOutput as Output, aws_iam as iam, aws_lambda as lambda} from "aws-cdk-lib";
@@ -1347,7 +1348,7 @@ class CdnStack {
1347
1348
  }
1348
1349
  }
1349
1350
  shouldDeployApi() {
1350
- return config6.cloud.api;
1351
+ return config6.cloud.api?.deploy;
1351
1352
  }
1352
1353
  apiBehaviorOptions(scope, props) {
1353
1354
  const hostname = Fn.select(2, Fn.split("/", props.webServerUrl.url));
@@ -1472,7 +1473,6 @@ class CdnStack {
1472
1473
  if (this.shouldDeployApi()) {
1473
1474
  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", "_"];
1474
1475
  keysToRemove.forEach((key) => delete env2[key]);
1475
- behaviorOptions = this.apiBehaviorOptions(scope, props);
1476
1476
  }
1477
1477
  if (this.shouldDeployDocs() && !config6.app.docMode) {
1478
1478
  behaviorOptions = {
@@ -1960,34 +1960,14 @@ class NetworkStack {
1960
1960
  natGateways: 0,
1961
1961
  subnetConfiguration: [
1962
1962
  {
1963
- cidrMask: 21,
1964
- name: `${props.slug}-${props.appEnv}-public-subnet-1`,
1965
- subnetType: ec22.SubnetType.PUBLIC
1963
+ name: "public-subnet-1",
1964
+ subnetType: ec22.SubnetType.PUBLIC,
1965
+ cidrMask: 24
1966
1966
  },
1967
1967
  {
1968
- cidrMask: 21,
1969
- name: `${props.slug}-${props.appEnv}-public-subnet-2`,
1970
- subnetType: ec22.SubnetType.PUBLIC
1971
- },
1972
- {
1973
- cidrMask: 21,
1974
- name: `${props.slug}-${props.appEnv}-public-subnet-3`,
1975
- subnetType: ec22.SubnetType.PUBLIC
1976
- },
1977
- {
1978
- cidrMask: 21,
1979
- name: `${props.slug}-${props.appEnv}-private-subnet-1`,
1980
- subnetType: ec22.SubnetType.PRIVATE_ISOLATED
1981
- },
1982
- {
1983
- cidrMask: 21,
1984
- name: `${props.slug}-${props.appEnv}-private-subnet-2`,
1985
- subnetType: ec22.SubnetType.PRIVATE_ISOLATED
1986
- },
1987
- {
1988
- cidrMask: 21,
1989
- name: `${props.slug}-${props.appEnv}-private-subnet-3`,
1990
- subnetType: ec22.SubnetType.PRIVATE_ISOLATED
1968
+ name: "private-subnet-1",
1969
+ subnetType: ec22.SubnetType.PRIVATE_ISOLATED,
1970
+ cidrMask: 28
1991
1971
  }
1992
1972
  ]
1993
1973
  });
@@ -2337,11 +2317,14 @@ class PermissionsStack {
2337
2317
  }
2338
2318
 
2339
2319
  // src/cloud/compute.ts
2340
- import {Duration as Duration6, CfnOutput as Output6, aws_lambda as lambda5, aws_logs as logs, aws_secretsmanager as secretsmanager} from "aws-cdk-lib";
2320
+ import {Duration as Duration6, CfnOutput as Output6, RemovalPolicy as RemovalPolicy8, aws_ec2 as ec23, aws_ecs as ecs, aws_secretsmanager as secretsmanager} from "aws-cdk-lib";
2341
2321
  import {path as p6} from "@stacksjs/path";
2342
2322
  import {env as env6} from "@stacksjs/env";
2323
+ import {LogGroup} from "aws-cdk-lib/aws-logs";
2324
+ import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
2343
2325
 
2344
2326
  class ComputeStack {
2327
+ lb;
2345
2328
  apiServer;
2346
2329
  apiServerUrl;
2347
2330
  constructor(scope, props) {
@@ -2349,17 +2332,122 @@ class ComputeStack {
2349
2332
  const fileSystem = props.fileSystem;
2350
2333
  if (!fileSystem)
2351
2334
  throw new Error("The file system is missing. Please make sure it was created properly.");
2352
- this.apiServer = new lambda5.Function(scope, "WebServer", {
2353
- functionName: `${props.slug}-${props.appEnv}-web-server`,
2354
- description: "The web server for the Stacks application",
2355
- code: lambda5.Code.fromAssetImage(p6.frameworkPath("server")),
2356
- handler: lambda5.Handler.FROM_IMAGE,
2357
- runtime: lambda5.Runtime.FROM_IMAGE,
2335
+ const cluster = new ecs.Cluster(scope, "StacksCluster", {
2336
+ clusterName: `${props.slug}-${props.appEnv}-web-server-cluster`,
2337
+ vpc
2338
+ });
2339
+ const taskDefinition = new ecs.FargateTaskDefinition(scope, "TaskDefinition", {
2340
+ family: `${props.appName}-${props.appEnv}-api`,
2341
+ memoryLimitMiB: 512,
2342
+ cpu: 256,
2343
+ runtimePlatform: {
2344
+ cpuArchitecture: ecs.CpuArchitecture.ARM64
2345
+ }
2346
+ });
2347
+ const container = taskDefinition.addContainer("WebServerContainer", {
2348
+ containerName: `${props.appName}-${props.appEnv}-api`,
2349
+ image: ecs.ContainerImage.fromAsset(p6.frameworkPath("server")),
2350
+ logging: new ecs.AwsLogDriver({
2351
+ streamPrefix: `${props.appName}-${props.appEnv}-web`,
2352
+ logGroup: new LogGroup(scope, "StacksApiLogs", {
2353
+ logGroupName: "/ecs/stacks-api",
2354
+ removalPolicy: RemovalPolicy8.DESTROY
2355
+ })
2356
+ }),
2357
+ healthCheck: {
2358
+ command: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
2359
+ interval: Duration6.seconds(10),
2360
+ timeout: Duration6.seconds(5),
2361
+ retries: 3,
2362
+ startPeriod: Duration6.seconds(10)
2363
+ }
2364
+ });
2365
+ container.addPortMappings({
2366
+ containerPort: 3000,
2367
+ hostPort: 3000
2368
+ });
2369
+ const serviceSecurityGroup = new ec23.SecurityGroup(scope, "ServiceSecurityGroup", {
2370
+ securityGroupName: `${props.appName}-${props.appEnv}-api-service-sg`,
2358
2371
  vpc,
2359
- memorySize: 512,
2360
- timeout: Duration6.minutes(5),
2361
- logRetention: logs.RetentionDays.ONE_WEEK,
2362
- architecture: lambda5.Architecture.ARM_64
2372
+ description: "Stacks Security Group for API Service"
2373
+ });
2374
+ const publicLoadBalancerSG = new ec23.SecurityGroup(scope, "PublicLoadBalancerSG", {
2375
+ securityGroupName: `${props.appName}-${props.appEnv}-public-load-balancer-sg`,
2376
+ vpc,
2377
+ description: "Access to the public facing load balancer"
2378
+ });
2379
+ serviceSecurityGroup.addIngressRule(publicLoadBalancerSG, ec23.Port.allTraffic(), "Ingress from the public ALB");
2380
+ serviceSecurityGroup.addIngressRule(publicLoadBalancerSG, ec23.Port.allTraffic(), "Ingress from the public ALB");
2381
+ this.lb = new elbv2.ApplicationLoadBalancer(scope, "ApplicationLoadBalancer", {
2382
+ loadBalancerName: `${props.appName}-${props.appEnv}-alb`,
2383
+ vpc,
2384
+ vpcSubnets: {
2385
+ subnets: vpc.selectSubnets({
2386
+ subnetType: ec23.SubnetType.PUBLIC,
2387
+ onePerAz: true
2388
+ }).subnets
2389
+ },
2390
+ internetFacing: true,
2391
+ idleTimeout: Duration6.seconds(30),
2392
+ securityGroup: publicLoadBalancerSG
2393
+ });
2394
+ const serviceTargetGroup = new elbv2.ApplicationTargetGroup(scope, "ServiceTargetGroup", {
2395
+ vpc,
2396
+ targetType: elbv2.TargetType.IP,
2397
+ protocol: elbv2.ApplicationProtocol.HTTP,
2398
+ port: 3000,
2399
+ healthCheck: {
2400
+ interval: Duration6.seconds(6),
2401
+ path: "/",
2402
+ protocol: elbv2.Protocol.HTTP,
2403
+ timeout: Duration6.seconds(5),
2404
+ healthyThresholdCount: 2,
2405
+ unhealthyThresholdCount: 10
2406
+ }
2407
+ });
2408
+ const service = new ecs.FargateService(scope, "StacksApiService", {
2409
+ serviceName: `${props.appName}-${props.appEnv}-api-service`,
2410
+ cluster,
2411
+ taskDefinition,
2412
+ desiredCount: 2,
2413
+ assignPublicIp: true,
2414
+ maxHealthyPercent: 200,
2415
+ vpcSubnets: vpc.selectSubnets({
2416
+ subnetType: ec23.SubnetType.PUBLIC,
2417
+ onePerAz: true
2418
+ }),
2419
+ minHealthyPercent: 75,
2420
+ securityGroups: [serviceSecurityGroup]
2421
+ });
2422
+ service.attachToApplicationTargetGroup(serviceTargetGroup);
2423
+ publicLoadBalancerSG.addIngressRule(ec23.Peer.anyIpv4(), ec23.Port.allTraffic());
2424
+ this.lb.addListener("HttpListener", {
2425
+ port: 80,
2426
+ defaultAction: elbv2.ListenerAction.forward([serviceTargetGroup])
2427
+ });
2428
+ props.fileSystem.connections.allowFromAnyIpv4(ec23.Port.tcp(2049));
2429
+ const volumeName = `${props.slug}-${props.appEnv}-efs`;
2430
+ taskDefinition.addVolume({
2431
+ name: volumeName,
2432
+ efsVolumeConfiguration: {
2433
+ fileSystemId: props.fileSystem.fileSystemId
2434
+ }
2435
+ });
2436
+ container.addMountPoints({
2437
+ sourceVolume: volumeName,
2438
+ containerPath: "/mnt/efs",
2439
+ readOnly: false
2440
+ });
2441
+ const scaling = service.autoScaleTaskCount({ maxCapacity: 2 });
2442
+ scaling.scaleOnCpuUtilization("CpuScaling", {
2443
+ targetUtilizationPercent: 50,
2444
+ scaleInCooldown: Duration6.seconds(60),
2445
+ scaleOutCooldown: Duration6.seconds(60)
2446
+ });
2447
+ scaling.scaleOnMemoryUtilization("MemoryScaling", {
2448
+ targetUtilizationPercent: 60,
2449
+ scaleInCooldown: Duration6.seconds(60),
2450
+ scaleOutCooldown: Duration6.seconds(60)
2363
2451
  });
2364
2452
  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", "_"];
2365
2453
  keysToRemove.forEach((key) => delete env6[key]);
@@ -2371,23 +2459,16 @@ class ComputeStack {
2371
2459
  generateStringKey: Object.keys(env6).join(",").length.toString()
2372
2460
  }
2373
2461
  });
2374
- secrets.grantRead(this.apiServer);
2375
- this.apiServer.addEnvironment("SECRETS_ARN", secrets.secretArn);
2376
- this.apiServerUrl = new lambda5.FunctionUrl(scope, "StacksServerUrl", {
2377
- function: this.apiServer,
2378
- authType: lambda5.FunctionUrlAuthType.NONE,
2379
- cors: {
2380
- allowedOrigins: ["*"]
2381
- }
2382
- });
2462
+ secrets.grantRead(service.taskDefinition.executionRole);
2463
+ container.addEnvironment("SECRETS_ARN", secrets.secretArn);
2383
2464
  const apiPrefix = "api";
2384
2465
  new Output6(scope, "ApiUrl", {
2385
2466
  value: `https://${props.domain}/${apiPrefix}`,
2386
2467
  description: "The URL of the deployed application"
2387
2468
  });
2388
2469
  new Output6(scope, "ApiVanityUrl", {
2389
- value: this.apiServerUrl.url,
2390
- description: "The Vanity URL of the deployed application"
2470
+ value: `http://${this.lb.loadBalancerDnsName}`,
2471
+ description: "The Vanity URL / DNS name of the load balancer"
2391
2472
  });
2392
2473
  }
2393
2474
  }
@@ -2422,13 +2503,16 @@ class Cloud extends Stack2 {
2422
2503
  });
2423
2504
  new RedirectsStack(this, props);
2424
2505
  new PermissionsStack(this);
2425
- const api = new ComputeStack(this, {
2426
- ...props,
2427
- vpc: network2.vpc,
2428
- fileSystem: fileSystem.fileSystem,
2429
- zone: dns2.zone,
2430
- certificate: security2.certificate
2431
- });
2506
+ let api;
2507
+ if (config20.cloud.api?.deploy) {
2508
+ api = new ComputeStack(this, {
2509
+ ...props,
2510
+ vpc: network2.vpc,
2511
+ fileSystem: fileSystem.fileSystem,
2512
+ zone: dns2.zone,
2513
+ certificate: security2.certificate
2514
+ });
2515
+ }
2432
2516
  const ai2 = new AiStack(this, props);
2433
2517
  const cli4 = new CliStack(this, props);
2434
2518
  const cdn2 = new CdnStack(this, {
@@ -2439,11 +2523,11 @@ class Cloud extends Stack2 {
2439
2523
  firewall: security2.firewall,
2440
2524
  originRequestFunction: docs2.originRequestFunction,
2441
2525
  zone: dns2.zone,
2442
- webServer: api.apiServer,
2443
- webServerUrl: api.apiServerUrl,
2444
2526
  cliSetupUrl: cli4.cliSetupUrl,
2445
2527
  askAiUrl: ai2.askAiUrl,
2446
- summarizeAiUrl: ai2.summarizeAiUrl
2528
+ summarizeAiUrl: ai2.summarizeAiUrl,
2529
+ webServer: api?.apiServer,
2530
+ webServerUrl: api?.apiServerUrl
2447
2531
  });
2448
2532
  new DeploymentStack(this, {
2449
2533
  ...props,
@@ -2492,7 +2576,6 @@ var request = new Request;
2492
2576
  // /home/runner/work/stacks/stacks/storage/framework/core/router/src/server.ts
2493
2577
  import {extname} from "path";
2494
2578
  import {URL} from "url";
2495
- import {localUrl} from "@stacksjs/config";
2496
2579
  async function serverResponse(req) {
2497
2580
  console.log("serverResponse", req);
2498
2581
  const routesList = await route.getRoutes();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/cloud",
3
3
  "type": "module",
4
- "version": "0.58.49",
4
+ "version": "0.58.51",
5
5
  "description": "The Stacks cloud/serverless integration & implementation.",
6
6
  "author": "Chris Breuer",
7
7
  "license": "MIT",
@@ -55,20 +55,20 @@
55
55
  "prepublishOnly": "bun run build"
56
56
  },
57
57
  "peerDependencies": {
58
- "@aws-sdk/client-bedrock": "^3.503.1",
59
- "@aws-sdk/client-cloudformation": "^3.503.1",
60
- "@aws-sdk/client-cloudfront": "^3.503.1",
61
- "@aws-sdk/client-cloudwatch-logs": "^3.503.1",
62
- "@aws-sdk/client-ec2": "^3.503.1",
63
- "@aws-sdk/client-efs": "^3.503.1",
64
- "@aws-sdk/client-iam": "^3.503.1",
65
- "@aws-sdk/client-lambda": "^3.503.1",
66
- "@aws-sdk/client-route-53-domains": "^3.503.1",
67
- "@aws-sdk/client-s3": "^3.503.1",
68
- "@aws-sdk/client-ses": "^3.503.1",
69
- "@aws-sdk/client-sesv2": "^3.503.1",
70
- "@aws-sdk/client-ssm": "^3.503.1",
71
- "@aws-sdk/lib-dynamodb": "^3.503.1",
58
+ "@aws-sdk/client-bedrock": "^3.504.0",
59
+ "@aws-sdk/client-cloudformation": "^3.504.0",
60
+ "@aws-sdk/client-cloudfront": "^3.504.0",
61
+ "@aws-sdk/client-cloudwatch-logs": "^3.504.0",
62
+ "@aws-sdk/client-ec2": "^3.504.0",
63
+ "@aws-sdk/client-efs": "^3.504.0",
64
+ "@aws-sdk/client-iam": "^3.504.0",
65
+ "@aws-sdk/client-lambda": "^3.504.0",
66
+ "@aws-sdk/client-route-53-domains": "^3.504.0",
67
+ "@aws-sdk/client-s3": "^3.504.0",
68
+ "@aws-sdk/client-ses": "^3.504.0",
69
+ "@aws-sdk/client-sesv2": "^3.504.0",
70
+ "@aws-sdk/client-ssm": "^3.504.0",
71
+ "@aws-sdk/lib-dynamodb": "^3.506.0",
72
72
  "@stacksjs/config": "latest",
73
73
  "@stacksjs/env": "latest",
74
74
  "@stacksjs/logging": "latest",
@@ -80,20 +80,20 @@
80
80
  "@stacksjs/validation": "latest"
81
81
  },
82
82
  "dependencies": {
83
- "@aws-sdk/client-bedrock": "^3.503.1",
84
- "@aws-sdk/client-cloudformation": "^3.503.1",
85
- "@aws-sdk/client-cloudfront": "^3.503.1",
86
- "@aws-sdk/client-cloudwatch-logs": "^3.503.1",
87
- "@aws-sdk/client-dynamodb": "3.503.1",
88
- "@aws-sdk/client-ec2": "^3.503.1",
89
- "@aws-sdk/client-efs": "^3.503.1",
90
- "@aws-sdk/client-iam": "^3.503.1",
91
- "@aws-sdk/client-lambda": "^3.503.1",
92
- "@aws-sdk/client-route-53-domains": "^3.503.1",
93
- "@aws-sdk/client-s3": "^3.503.1",
94
- "@aws-sdk/client-ses": "^3.503.1",
95
- "@aws-sdk/client-sesv2": "^3.503.1",
96
- "@aws-sdk/client-ssm": "^3.503.1",
83
+ "@aws-sdk/client-bedrock": "^3.504.0",
84
+ "@aws-sdk/client-cloudformation": "^3.504.0",
85
+ "@aws-sdk/client-cloudfront": "^3.504.0",
86
+ "@aws-sdk/client-cloudwatch-logs": "^3.504.0",
87
+ "@aws-sdk/client-dynamodb": "3.506.0",
88
+ "@aws-sdk/client-ec2": "^3.504.0",
89
+ "@aws-sdk/client-efs": "^3.504.0",
90
+ "@aws-sdk/client-iam": "^3.504.0",
91
+ "@aws-sdk/client-lambda": "^3.504.0",
92
+ "@aws-sdk/client-route-53-domains": "^3.504.0",
93
+ "@aws-sdk/client-s3": "^3.504.0",
94
+ "@aws-sdk/client-ses": "^3.504.0",
95
+ "@aws-sdk/client-sesv2": "^3.504.0",
96
+ "@aws-sdk/client-ssm": "^3.504.0",
97
97
  "@stacksjs/config": "latest",
98
98
  "@stacksjs/dns": "latest",
99
99
  "@stacksjs/env": "latest",
@@ -103,13 +103,13 @@
103
103
  "@stacksjs/storage": "latest",
104
104
  "@stacksjs/utils": "latest",
105
105
  "@stacksjs/validation": "latest",
106
- "aws-cdk": "^2.124.0",
107
- "aws-cdk-lib": "^2.124.0",
106
+ "aws-cdk": "^2.126.0",
107
+ "aws-cdk-lib": "^2.126.0",
108
108
  "aws4fetch": "^1.0.17",
109
109
  "constructs": "^10.3.0"
110
110
  },
111
111
  "devDependencies": {
112
- "@oclif/plugin-plugins": "^4.1.23",
112
+ "@oclif/plugin-plugins": "^4.2.1",
113
113
  "@stacksjs/cli": "latest",
114
114
  "@stacksjs/development": "latest",
115
115
  "@stacksjs/env": "latest",
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "aws-sdk-layer",
3
- "version": "0.58.44",
3
+ "version": "0.58.49",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "aws-sdk-layer",
9
- "version": "0.58.44",
9
+ "version": "0.58.49",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
- "aws-sdk": "^2.1547.0"
12
+ "aws-sdk": "^2.1550.0"
13
13
  }
14
14
  },
15
15
  "node_modules/available-typed-arrays": {
@@ -24,9 +24,9 @@
24
24
  }
25
25
  },
26
26
  "node_modules/aws-sdk": {
27
- "version": "2.1547.0",
28
- "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1547.0.tgz",
29
- "integrity": "sha512-jk7u3KtDZ5F20k2X6D2FhndpLGkt3ZuNfRU5crp+fI6B/GFj/S91GJDoZh/Yw3rW+CemY1sFmdFT8ReA2G8WkA==",
27
+ "version": "2.1550.0",
28
+ "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1550.0.tgz",
29
+ "integrity": "sha512-abkbOeaL7iV085UqO8Y7/Ep7VYONK32chhKejhMbPYSqTp2YgNeqOSQfSaVZWeWCwqJxujEyoXFGTNgTt46D0g==",
30
30
  "dependencies": {
31
31
  "buffer": "4.9.2",
32
32
  "events": "1.1.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-sdk-layer",
3
- "version": "0.58.49",
3
+ "version": "0.58.51",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -10,6 +10,6 @@
10
10
  "test": "echo \"Error: no test specified\" && exit 1"
11
11
  },
12
12
  "dependencies": {
13
- "aws-sdk": "^2.1547.0"
13
+ "aws-sdk": "^2.1550.0"
14
14
  }
15
15
  }
package/src/cloud/cdn.ts CHANGED
@@ -16,8 +16,8 @@ export interface CdnStackProps extends NestedCloudProps {
16
16
  firewall: wafv2.CfnWebACL
17
17
  originRequestFunction: lambda.Function
18
18
  zone: route53.IHostedZone
19
- webServer: lambda.Function
20
- webServerUrl: lambda.FunctionUrl
19
+ webServer?: lambda.Function
20
+ webServerUrl?: lambda.FunctionUrl
21
21
  cliSetupUrl: lambda.FunctionUrl
22
22
  askAiUrl: lambda.FunctionUrl
23
23
  summarizeAiUrl: lambda.FunctionUrl
@@ -184,11 +184,11 @@ export class CdnStack {
184
184
  }
185
185
 
186
186
  shouldDeployApi() {
187
- return config.cloud.api
187
+ return config.cloud.api?.deploy
188
188
  }
189
189
 
190
190
  apiBehaviorOptions(scope: Construct, props: CdnStackProps): Record<string, cloudfront.BehaviorOptions> {
191
- const hostname = Fn.select(2, Fn.split('/', props.webServerUrl.url))
191
+ const hostname = Fn.select(2, Fn.split('/', props.webServerUrl!.url))
192
192
  const origin = (path: '/api' | '/api/*' = '/api') => {
193
193
  return new origins.HttpOrigin(hostname, {
194
194
  originPath: path,
@@ -326,7 +326,7 @@ export class CdnStack {
326
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
327
  keysToRemove.forEach(key => delete env[key as EnvKey])
328
328
 
329
- behaviorOptions = this.apiBehaviorOptions(scope, props)
329
+ // behaviorOptions = this.apiBehaviorOptions(scope, props)
330
330
  }
331
331
 
332
332
  // if docMode is used, we don't need to add a behavior for the docs
@@ -1,9 +1,11 @@
1
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'
2
+ import type { aws_certificatemanager as acm, aws_efs as efs, aws_lambda as lambda, aws_route53 as route53 } from 'aws-cdk-lib'
3
+ import { Duration, CfnOutput as Output, RemovalPolicy, aws_ec2 as ec2, aws_ecs as ecs, aws_secretsmanager as secretsmanager } from 'aws-cdk-lib'
4
4
  import type { Construct } from 'constructs'
5
5
  import { path as p } from '@stacksjs/path'
6
6
  import { env } from '@stacksjs/env'
7
+ import { LogGroup } from 'aws-cdk-lib/aws-logs'
8
+ import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'
7
9
  import type { NestedCloudProps } from '../types'
8
10
  import type { EnvKey } from '../../../../env'
9
11
 
@@ -15,6 +17,7 @@ export interface ComputeStackProps extends NestedCloudProps {
15
17
  }
16
18
 
17
19
  export class ComputeStack {
20
+ lb: elbv2.ApplicationLoadBalancer
18
21
  apiServer: lambda.Function
19
22
  apiServerUrl: lambda.FunctionUrl
20
23
 
@@ -25,22 +28,149 @@ export class ComputeStack {
25
28
  if (!fileSystem)
26
29
  throw new Error('The file system is missing. Please make sure it was created properly.')
27
30
 
28
- // const dockerImageAsset = new ecr_assets.DockerImageAsset(scope, 'ServerBuildImage', {
29
- // directory: p.cloudPath('src/server'),
30
- // })
31
+ const cluster = new ecs.Cluster(scope, 'StacksCluster', {
32
+ clusterName: `${props.slug}-${props.appEnv}-web-server-cluster`,
33
+ vpc,
34
+ })
35
+
36
+ const taskDefinition = new ecs.FargateTaskDefinition(scope, 'TaskDefinition', {
37
+ family: `${props.appName}-${props.appEnv}-api`,
38
+ memoryLimitMiB: 512, // Match your Lambda memory size
39
+ cpu: 256, // Choose an appropriate value
40
+ runtimePlatform: {
41
+ cpuArchitecture: ecs.CpuArchitecture.ARM64,
42
+ },
43
+ })
44
+
45
+ const container = taskDefinition.addContainer('WebServerContainer', {
46
+ containerName: `${props.appName}-${props.appEnv}-api`,
47
+ image: ecs.ContainerImage.fromAsset(p.frameworkPath('server')),
48
+ logging: new ecs.AwsLogDriver({
49
+ streamPrefix: `${props.appName}-${props.appEnv}-web`,
50
+ logGroup: new LogGroup(scope, 'StacksApiLogs', {
51
+ logGroupName: '/ecs/stacks-api',
52
+ removalPolicy: RemovalPolicy.DESTROY, // Automatically remove logs on stack deletion
53
+ }),
54
+ }),
55
+ healthCheck: {
56
+ command: ['CMD-SHELL', 'curl -f http://localhost:3000/health || exit 1'],
57
+ interval: Duration.seconds(10),
58
+ timeout: Duration.seconds(5),
59
+ retries: 3,
60
+ startPeriod: Duration.seconds(10),
61
+ },
62
+ })
63
+
64
+ container.addPortMappings({
65
+ containerPort: 3000,
66
+ hostPort: 3000,
67
+ })
68
+
69
+ const serviceSecurityGroup = new ec2.SecurityGroup(scope, 'ServiceSecurityGroup', {
70
+ securityGroupName: `${props.appName}-${props.appEnv}-api-service-sg`,
71
+ vpc,
72
+ description: 'Stacks Security Group for API Service',
73
+ })
74
+
75
+ const publicLoadBalancerSG = new ec2.SecurityGroup(scope, 'PublicLoadBalancerSG', {
76
+ securityGroupName: `${props.appName}-${props.appEnv}-public-load-balancer-sg`,
77
+ vpc,
78
+ description: 'Access to the public facing load balancer',
79
+ })
80
+
81
+ // Assuming serviceSecurityGroup and publicLoadBalancerSG are already defined
82
+ serviceSecurityGroup.addIngressRule(
83
+ publicLoadBalancerSG,
84
+ ec2.Port.allTraffic(),
85
+ 'Ingress from the public ALB',
86
+ )
87
+
88
+ // Assuming serviceSecurityGroup and publicLoadBalancerSG are already defined
89
+ serviceSecurityGroup.addIngressRule(publicLoadBalancerSG, ec2.Port.allTraffic(), 'Ingress from the public ALB')
90
+
91
+ this.lb = new elbv2.ApplicationLoadBalancer(scope, 'ApplicationLoadBalancer', {
92
+ loadBalancerName: `${props.appName}-${props.appEnv}-alb`,
93
+ vpc,
94
+ vpcSubnets: {
95
+ subnets: vpc.selectSubnets({
96
+ subnetType: ec2.SubnetType.PUBLIC,
97
+ onePerAz: true,
98
+ }).subnets,
99
+ },
100
+ internetFacing: true,
101
+ idleTimeout: Duration.seconds(30),
102
+ securityGroup: publicLoadBalancerSG,
103
+ })
31
104
 
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,
105
+ const serviceTargetGroup = new elbv2.ApplicationTargetGroup(scope, 'ServiceTargetGroup', {
106
+ // targetGroupName: `${props.appName}-${props.appEnv}-service-tg`,
38
107
  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'),
108
+ targetType: elbv2.TargetType.IP,
109
+ protocol: elbv2.ApplicationProtocol.HTTP,
110
+ port: 3000,
111
+ healthCheck: {
112
+ interval: Duration.seconds(6),
113
+ path: '/',
114
+ protocol: elbv2.Protocol.HTTP,
115
+ timeout: Duration.seconds(5),
116
+ healthyThresholdCount: 2,
117
+ unhealthyThresholdCount: 10,
118
+ },
119
+ })
120
+
121
+ const service = new ecs.FargateService(scope, 'StacksApiService', {
122
+ serviceName: `${props.appName}-${props.appEnv}-api-service`,
123
+ cluster,
124
+ taskDefinition,
125
+ desiredCount: 2,
126
+ assignPublicIp: true,
127
+ maxHealthyPercent: 200,
128
+ vpcSubnets: vpc.selectSubnets({
129
+ subnetType: ec2.SubnetType.PUBLIC,
130
+ onePerAz: true,
131
+ }),
132
+ minHealthyPercent: 75,
133
+ securityGroups: [serviceSecurityGroup],
134
+ })
135
+
136
+ service.attachToApplicationTargetGroup(serviceTargetGroup)
137
+ publicLoadBalancerSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.allTraffic())
138
+
139
+ this.lb.addListener('HttpListener', {
140
+ port: 80,
141
+ defaultAction: elbv2.ListenerAction.forward([serviceTargetGroup]),
142
+ })
143
+
144
+ props.fileSystem.connections.allowFromAnyIpv4(ec2.Port.tcp(2049)) // port 2049 (NFS) for EFS
145
+
146
+ const volumeName = `${props.slug}-${props.appEnv}-efs`
147
+ taskDefinition.addVolume({
148
+ name: volumeName,
149
+ efsVolumeConfiguration: {
150
+ fileSystemId: props.fileSystem.fileSystemId,
151
+ },
152
+ })
153
+
154
+ container.addMountPoints({
155
+ sourceVolume: volumeName,
156
+ containerPath: '/mnt/efs',
157
+ readOnly: false,
158
+ })
159
+
160
+ // Setup AutoScaling policy
161
+ // TODO: make this configurable in cloud.compute
162
+ const scaling = service.autoScaleTaskCount({ maxCapacity: 2 })
163
+
164
+ scaling.scaleOnCpuUtilization('CpuScaling', {
165
+ targetUtilizationPercent: 50,
166
+ scaleInCooldown: Duration.seconds(60),
167
+ scaleOutCooldown: Duration.seconds(60),
168
+ })
169
+
170
+ scaling.scaleOnMemoryUtilization('MemoryScaling', {
171
+ targetUtilizationPercent: 60,
172
+ scaleInCooldown: Duration.seconds(60),
173
+ scaleOutCooldown: Duration.seconds(60),
44
174
  })
45
175
 
46
176
  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', '_']
@@ -55,16 +185,8 @@ export class ComputeStack {
55
185
  },
56
186
  })
57
187
 
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
- })
188
+ secrets.grantRead(service.taskDefinition.executionRole!)
189
+ container.addEnvironment('SECRETS_ARN', secrets.secretArn)
68
190
 
69
191
  const apiPrefix = 'api'
70
192
  new Output(scope, 'ApiUrl', {
@@ -73,8 +195,8 @@ export class ComputeStack {
73
195
  })
74
196
 
75
197
  new Output(scope, 'ApiVanityUrl', {
76
- value: this.apiServerUrl.url,
77
- description: 'The Vanity URL of the deployed application',
198
+ value: `http://${this.lb.loadBalancerDnsName}`,
199
+ description: 'The Vanity URL / DNS name of the load balancer',
78
200
  })
79
201
  }
80
202
  }
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable no-new */
2
2
  import { Stack } from 'aws-cdk-lib'
3
3
  import type { Construct } from 'constructs'
4
+ import { config } from '@stacksjs/config'
4
5
  import type { CloudOptions } from '../types'
5
6
  import { AiStack } from './ai'
6
7
  import { CdnStack } from './cdn'
@@ -62,13 +63,16 @@ export class Cloud extends Stack {
62
63
 
63
64
  // new DashboardStack(this)
64
65
 
65
- const api = new ComputeStack(this, {
66
- ...props,
67
- vpc: network.vpc,
68
- fileSystem: fileSystem.fileSystem,
69
- zone: dns.zone,
70
- certificate: security.certificate,
71
- })
66
+ let api
67
+ if (config.cloud.api?.deploy) {
68
+ api = new ComputeStack(this, {
69
+ ...props,
70
+ vpc: network.vpc,
71
+ fileSystem: fileSystem.fileSystem,
72
+ zone: dns.zone,
73
+ certificate: security.certificate,
74
+ })
75
+ }
72
76
 
73
77
  const ai = new AiStack(this, props)
74
78
 
@@ -82,11 +86,11 @@ export class Cloud extends Stack {
82
86
  firewall: security.firewall,
83
87
  originRequestFunction: docs.originRequestFunction,
84
88
  zone: dns.zone,
85
- webServer: api.apiServer,
86
- webServerUrl: api.apiServerUrl,
87
89
  cliSetupUrl: cli.cliSetupUrl,
88
90
  askAiUrl: ai.askAiUrl,
89
91
  summarizeAiUrl: ai.summarizeAiUrl,
92
+ webServer: api?.apiServer,
93
+ webServerUrl: api?.apiServerUrl,
90
94
  })
91
95
 
92
96
  new DeploymentStack(this, {
@@ -17,34 +17,14 @@ export class NetworkStack {
17
17
  natGateways: 0,
18
18
  subnetConfiguration: [
19
19
  {
20
- cidrMask: 21,
21
- name: `${props.slug}-${props.appEnv}-public-subnet-1`,
20
+ name: 'public-subnet-1',
22
21
  subnetType: ec2.SubnetType.PUBLIC,
22
+ cidrMask: 24,
23
23
  },
24
24
  {
25
- cidrMask: 21,
26
- name: `${props.slug}-${props.appEnv}-public-subnet-2`,
27
- subnetType: ec2.SubnetType.PUBLIC,
28
- },
29
- {
30
- cidrMask: 21,
31
- name: `${props.slug}-${props.appEnv}-public-subnet-3`,
32
- subnetType: ec2.SubnetType.PUBLIC,
33
- },
34
- {
35
- cidrMask: 21,
36
- name: `${props.slug}-${props.appEnv}-private-subnet-1`,
37
- subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
38
- },
39
- {
40
- cidrMask: 21,
41
- name: `${props.slug}-${props.appEnv}-private-subnet-2`,
42
- subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
43
- },
44
- {
45
- cidrMask: 21,
46
- name: `${props.slug}-${props.appEnv}-private-subnet-3`,
25
+ name: 'private-subnet-1',
47
26
  subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
27
+ cidrMask: 28,
48
28
  },
49
29
  ],
50
30
  })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/router",
3
3
  "type": "module",
4
- "version": "0.58.49",
4
+ "version": "0.58.51",
5
5
  "description": "The Stacks framework router.",
6
6
  "author": "Chris Breuer",
7
7
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stacks-router-layer",
3
- "version": "0.58.49",
3
+ "version": "0.58.51",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -10,6 +10,6 @@
10
10
  "test": "echo \"Error: no test specified\" && exit 1"
11
11
  },
12
12
  "dependencies": {
13
- "@stacksjs/router": "^0.58.48"
13
+ "@stacksjs/router": "^0.58.51"
14
14
  }
15
15
  }