@stacksjs/cloud 0.58.28 → 0.58.43

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 (2) hide show
  1. package/dist/index.js +3147 -0
  2. package/package.json +33 -33
package/dist/index.js ADDED
@@ -0,0 +1,3147 @@
1
+ // @bun
2
+ // src/helpers.ts
3
+ import {CloudFormation} from "@aws-sdk/client-cloudformation";
4
+ import {CloudWatchLogsClient, DeleteLogGroupCommand, DescribeLogGroupsCommand} from "@aws-sdk/client-cloudwatch-logs";
5
+ import {EC2, _InstanceType as InstanceType} from "@aws-sdk/client-ec2";
6
+ import {DescribeFileSystemsCommand, EFSClient} from "@aws-sdk/client-efs";
7
+ import {IAM} from "@aws-sdk/client-iam";
8
+ import {SSM} from "@aws-sdk/client-ssm";
9
+ import {Lambda} from "@aws-sdk/client-lambda";
10
+ import {ContactType, Route53Domains} from "@aws-sdk/client-route-53-domains";
11
+ import {ListBucketsCommand, S3} from "@aws-sdk/client-s3";
12
+ import {config as config2} from "@stacksjs/config";
13
+ import {err, handleError as handleError2, ok} from "@stacksjs/error-handling";
14
+ import {log} from "@stacksjs/logging";
15
+ import {path as p} from "@stacksjs/path";
16
+ import {rimraf} from "@stacksjs/utils";
17
+ import {slug} from "@stacksjs/strings";
18
+ async function getSecurityGroupId(securityGroupName) {
19
+ const ec2 = new EC2({ region: "us-east-1" });
20
+ const { SecurityGroups } = await ec2.describeSecurityGroups({
21
+ Filters: [{ Name: "group-name", Values: [securityGroupName] }]
22
+ });
23
+ if (!SecurityGroups)
24
+ return err(`Security group ${securityGroupName} not found`);
25
+ if (SecurityGroups[0])
26
+ return ok(SecurityGroups[0].GroupId);
27
+ return err(`Security group ${securityGroupName} not found`);
28
+ }
29
+ function purchaseDomain(domain, options) {
30
+ const route53domains = new Route53Domains({ region: "us-east-1" });
31
+ const contactType = options.contactType.toUpperCase();
32
+ const params = {
33
+ DomainName: domain,
34
+ DurationInYears: options.years || 1,
35
+ AutoRenew: options.autoRenew || true,
36
+ AdminContact: {
37
+ FirstName: options.adminFirstName,
38
+ LastName: options.adminLastName,
39
+ ContactType: contactType || ContactType.PERSON,
40
+ OrganizationName: options.adminOrganization,
41
+ AddressLine1: options.adminAddressLine1,
42
+ AddressLine2: options.adminAddressLine2,
43
+ City: options.adminCity,
44
+ State: options.adminState,
45
+ CountryCode: options.adminCountry,
46
+ ZipCode: options.adminZip.toString(),
47
+ PhoneNumber: options.adminPhone.toString().includes("+") ? options.adminPhone.toString() : `+${options.adminPhone.toString()}`,
48
+ Email: options.adminEmail
49
+ },
50
+ RegistrantContact: {
51
+ FirstName: options.registrantFirstName,
52
+ LastName: options.registrantLastName,
53
+ ContactType: contactType || ContactType.PERSON,
54
+ OrganizationName: options.registrantOrganization,
55
+ AddressLine1: options.registrantAddressLine1,
56
+ AddressLine2: options.registrantAddressLine2,
57
+ City: options.registrantCity,
58
+ State: options.registrantState,
59
+ CountryCode: options.registrantCountry,
60
+ ZipCode: options.registrantZip.toString(),
61
+ PhoneNumber: options.registrantPhone.toString().includes("+") ? options.registrantPhone.toString() : `+${options.registrantPhone.toString()}`,
62
+ Email: options.registrantEmail
63
+ },
64
+ TechContact: {
65
+ FirstName: options.techFirstName,
66
+ LastName: options.techLastName,
67
+ ContactType: contactType || ContactType.PERSON,
68
+ OrganizationName: options.techOrganization,
69
+ AddressLine1: options.techAddressLine1,
70
+ AddressLine2: options.techAddressLine2,
71
+ City: options.techCity,
72
+ State: options.techState,
73
+ CountryCode: options.techCountry,
74
+ ZipCode: options.techZip.toString(),
75
+ PhoneNumber: options.techPhone.toString().includes("+") ? options.techPhone.toString() : `+${options.techPhone.toString()}`,
76
+ Email: options.techEmail
77
+ },
78
+ PrivacyProtectAdminContact: options.privacyAdmin || options.privacy || true,
79
+ PrivacyProtectRegistrantContact: options.privacyRegistrant || options.privacy || true,
80
+ PrivacyProtectTechContact: options.privacyTech || options.privacy || true
81
+ };
82
+ try {
83
+ return ok(route53domains.registerDomain(params));
84
+ } catch (error) {
85
+ return err(error);
86
+ }
87
+ }
88
+ async function getJumpBoxInstanceId(name) {
89
+ if (!name)
90
+ name = `${cloudName}/JumpBox`;
91
+ const ec2 = new EC2({ region: "us-east-1" });
92
+ const data = await ec2.describeInstances({
93
+ Filters: [
94
+ {
95
+ Name: "tag:Name",
96
+ Values: [name]
97
+ }
98
+ ]
99
+ });
100
+ if (data.Reservations && data.Reservations[0] && data.Reservations[0].Instances && data.Reservations[0].Instances[0])
101
+ return data.Reservations[0].Instances[0].InstanceId;
102
+ return;
103
+ }
104
+ async function deleteEc2Instance(id, stackName) {
105
+ if (!stackName)
106
+ stackName = cloudName;
107
+ if (!id)
108
+ return err(`Instance ${id} not found`);
109
+ const ec2 = new EC2({ region: "us-east-1" });
110
+ await ec2.terminateInstances({ InstanceIds: [id] });
111
+ return ok(`Instance ${id} is being terminated`);
112
+ }
113
+ async function deleteJumpBox(stackName) {
114
+ if (!stackName)
115
+ stackName = cloudName;
116
+ const jumpBoxId = await getJumpBoxInstanceId();
117
+ if (!jumpBoxId)
118
+ return err("Jump-box not found");
119
+ return await deleteEc2Instance(jumpBoxId, stackName);
120
+ }
121
+ async function deleteIamUsers() {
122
+ const iam = new IAM({ region: "us-east-1" });
123
+ const data = await iam.listUsers({});
124
+ const teamName = slug(config2.team.name);
125
+ const users = data.Users?.filter((user) => {
126
+ const userNameLower = user.UserName?.toLowerCase();
127
+ return userNameLower !== "stacks" && userNameLower !== teamName.toLowerCase() && userNameLower?.includes(teamName.toLowerCase());
128
+ }) || [];
129
+ if (!users || users.length === 0)
130
+ return ok(`No Stacks IAM users found for team ${teamName}`);
131
+ const promises = users.map(async (user) => {
132
+ const userName = user.UserName || "";
133
+ log.info(`Deleting IAM user: ${userName}`);
134
+ const policies = await iam.listAttachedUserPolicies({ UserName: userName });
135
+ await Promise.all(policies.AttachedPolicies?.map((policy) => iam.detachUserPolicy({ UserName: userName, PolicyArn: policy.PolicyArn || "" })) || []);
136
+ const accessKeys = await iam.listAccessKeys({ UserName: userName });
137
+ await Promise.all(accessKeys.AccessKeyMetadata?.map((key) => iam.deleteAccessKey({ UserName: userName, AccessKeyId: key.AccessKeyId || "" })) || []);
138
+ return iam.deleteUser({ UserName: userName });
139
+ });
140
+ await Promise.all(promises).catch((error) => {
141
+ console.error(`Error deleting user: ${error}`);
142
+ return err(handleError2("Error deleting Stacks IAM users", error));
143
+ });
144
+ return ok(`Stacks IAM users deleted for team ${teamName}`);
145
+ }
146
+ async function deleteStacksBuckets() {
147
+ try {
148
+ const s3 = new S3({ region: "us-east-1" });
149
+ const data = await s3.listBuckets({});
150
+ const stacksBuckets = data.Buckets?.filter((bucket) => bucket.Name?.includes("stacks"));
151
+ if (!stacksBuckets)
152
+ return err("No stacks buckets found");
153
+ const promises = stacksBuckets.map(async (bucket) => {
154
+ const bucketName = bucket.Name || "";
155
+ log.info(`Deleting bucket ${bucketName}...`);
156
+ const objects = await s3.listObjectsV2({ Bucket: bucketName });
157
+ log.info(`Finished listing bucket ${bucketName} objects`);
158
+ if (objects.Contents) {
159
+ log.info("Deleting bucket objects...");
160
+ await Promise.all(objects.Contents.map((object) => s3.deleteObject({ Bucket: bucketName, Key: object.Key || "" }).catch((error) => handleError2(error))));
161
+ log.info(`Finished deleting objects from bucket ${bucketName}`);
162
+ }
163
+ log.info(`Deleting bucket ${bucketName} versions...`);
164
+ try {
165
+ const versions = await s3.listObjectVersions({ Bucket: bucketName });
166
+ if (versions.Versions) {
167
+ await Promise.all(versions.Versions.map((version) => s3.deleteObject({ Bucket: bucketName, Key: version.Key || "", VersionId: version.VersionId }))).catch((error) => handleError2(error));
168
+ log.info(`Finished deleting versions from bucket ${bucketName}`);
169
+ }
170
+ log.info(`Deleting bucket ${bucketName} delete markers...`);
171
+ if (versions.DeleteMarkers) {
172
+ await Promise.all(versions.DeleteMarkers.map((marker) => s3.deleteObject({ Bucket: bucketName, Key: marker.Key || "", VersionId: marker.VersionId }))).catch((error) => handleError2(error));
173
+ log.info(`Finished deleting delete markers from bucket ${bucketName}`);
174
+ }
175
+ const uploads = await s3.listMultipartUploads({ Bucket: bucketName });
176
+ if (uploads.Uploads) {
177
+ log.info("Aborting bucket multipart uploads...");
178
+ await Promise.all(uploads.Uploads.map((upload) => s3.abortMultipartUpload({ Bucket: bucketName, Key: upload.Key || "", UploadId: upload.UploadId }))).catch((error) => handleError2(error));
179
+ log.info(`Finished aborting multipart uploads from bucket ${bucketName}`);
180
+ }
181
+ await s3.deleteBucket({ Bucket: bucketName }).catch((error) => handleError2(error));
182
+ log.info(`Bucket ${bucketName} deleted`);
183
+ } catch (error) {
184
+ log.info(`Error listing bucket ${bucketName} versions`, error);
185
+ }
186
+ });
187
+ await Promise.all(promises).catch((error) => {
188
+ return err(handleError2("Error deleting stacks buckets", error));
189
+ });
190
+ return ok("Stacks buckets deleted");
191
+ } catch (error) {
192
+ return err(handleError2("Error deleting stacks buckets", error));
193
+ }
194
+ }
195
+ async function deleteStacksFunctions() {
196
+ const lambda = new Lambda({ region: "us-east-1" });
197
+ const data = await lambda.listFunctions({});
198
+ const stacksFunctions = data.Functions?.filter((func) => func.FunctionName?.includes("stacks")) || [];
199
+ if (!stacksFunctions || stacksFunctions.length === 0)
200
+ return ok("No stacks functions found");
201
+ const promises = stacksFunctions.map((func) => lambda.deleteFunction({ FunctionName: func.FunctionName || "" }));
202
+ await Promise.all(promises).catch((error) => {
203
+ if (error.message.includes("it is a replicated function")) {
204
+ log.info("Function is replicated, skipping...");
205
+ return ok("CloudFront is still deleting the some functions. Try again later.");
206
+ }
207
+ return err(handleError2("Error deleting stacks functions", error));
208
+ });
209
+ return ok("Stacks functions deleted");
210
+ }
211
+ async function deleteLogGroups() {
212
+ try {
213
+ const client = new CloudWatchLogsClient({ region: "us-east-1" });
214
+ const logGroups = await client.send(new DescribeLogGroupsCommand({}));
215
+ if (!logGroups?.logGroups)
216
+ return err("No log groups found");
217
+ for (const group of logGroups.logGroups) {
218
+ if (group.logGroupName?.includes("stacks"))
219
+ await client.send(new DeleteLogGroupCommand({ logGroupName: group.logGroupName }));
220
+ }
221
+ return ok("Log groups deleted");
222
+ } catch (error) {
223
+ return err(handleError2("Error deleting log groups", error));
224
+ }
225
+ }
226
+ async function deleteParameterStore() {
227
+ const ssm = new SSM({ region: "us-east-1" });
228
+ const data = await ssm.describeParameters({});
229
+ if (!data.Parameters)
230
+ return ok("No parameters found");
231
+ const stacksParameters = data.Parameters.filter((param) => param.Name?.includes("stacks")) || [];
232
+ if (!stacksParameters || stacksParameters.length === 0)
233
+ return ok("No stacks parameters found");
234
+ const promises = stacksParameters.map((param) => ssm.deleteParameter({ Name: param.Name || "" }));
235
+ await Promise.all(promises).catch((error) => {
236
+ return err(handleError2("Error deleting parameter store", error));
237
+ });
238
+ return ok("Parameter store deleted");
239
+ }
240
+ async function deleteCdkRemnants() {
241
+ try {
242
+ return ok(await rimraf([
243
+ p.cloudPath("cdk.out/"),
244
+ p.cloudPath("cdk.context.json")
245
+ ]));
246
+ } catch (error) {
247
+ return err(handleError2("Error deleting CDK remnants", error));
248
+ }
249
+ }
250
+ async function hasBeenDeployed() {
251
+ const s3 = new S3({ region: "us-east-1" });
252
+ try {
253
+ const response = await s3.send(new ListBucketsCommand({}));
254
+ return ok(response.Buckets?.some((bucket) => bucket.Name?.includes(config2.app.name?.toLocaleLowerCase() || "stacks")) || false);
255
+ } catch (error) {
256
+ return err(handleError2("Error checking if the app has been deployed", error));
257
+ }
258
+ }
259
+ async function getJumpBoxInstanceProfileName() {
260
+ const iam = new IAM({ region: "us-east-1" });
261
+ const data = await iam.listInstanceProfiles({});
262
+ const instanceProfile = data.InstanceProfiles?.find((profile) => profile.InstanceProfileName?.includes("JumpBox"));
263
+ if (!instanceProfile)
264
+ return err("Jump-box IAM instance profile not found");
265
+ return ok(instanceProfile?.InstanceProfileName);
266
+ }
267
+ async function addJumpBox(stackName) {
268
+ if (!stackName)
269
+ stackName = cloudName;
270
+ if (await getJumpBoxInstanceId())
271
+ return err("The jump\u2013box you are trying to add already exists. Please remove it & wait until it finished terminating.");
272
+ const ec2 = new EC2({ region: "us-east-1" });
273
+ const r = await getJumpBoxSecurityGroupName();
274
+ if (r.isErr())
275
+ return err(r.error);
276
+ if (!r.value)
277
+ return err("Security group not found when adding jump-box");
278
+ const result = await getSecurityGroupId(r.value);
279
+ let sgId;
280
+ if (result.isErr())
281
+ return err(result.error);
282
+ else
283
+ sgId = result.value;
284
+ if (!sgId)
285
+ return err("Security group not found when adding jump-box");
286
+ const client = new EFSClient({ region: "us-east-1" });
287
+ const command = new DescribeFileSystemsCommand({});
288
+ const data = await client.send(command);
289
+ const fileSystemName = `stacks-${config2.app.env}-efs`;
290
+ const fileSystem = data.FileSystems?.find((fs) => fs.Name === fileSystemName);
291
+ const fileSystemId = fileSystem?.FileSystemId;
292
+ if (!fileSystem || !fileSystemId)
293
+ return err(`EFS file system ${fileSystemName} not found`);
294
+ const userDataScript = `
295
+ #!/bin/bash
296
+ yum update -y
297
+ yum install -y amazon-efs-utils
298
+ yum install -y git
299
+ yum install -y https://s3.us-east-1.amazonaws.com/amazon-ssm-us-east-1/latest/linux_amd64/amazon-ssm-agent.rpm
300
+ mkdir /mnt/efs
301
+ mount -t efs ${fileSystemId}:/ /mnt/efs
302
+ git clone https://github.com/stacksjs/stacks.git /mnt/efs
303
+ `;
304
+ const base64UserData = btoa(userDataScript);
305
+ const res = await getJumpBoxInstanceProfileName();
306
+ if (res.isErr())
307
+ return err(res.error);
308
+ const jumpBoxInstanceProfileName = res.value;
309
+ if (!jumpBoxInstanceProfileName)
310
+ return err("Jump-box IAM instance profile not found");
311
+ const instance = await ec2.runInstances({
312
+ ImageId: "ami-03a6eaae9938c858c",
313
+ InstanceType: InstanceType.t2_micro,
314
+ MaxCount: 1,
315
+ MinCount: 1,
316
+ SecurityGroupIds: [sgId],
317
+ SubnetId: "subnet-004c5f196358b00f0",
318
+ TagSpecifications: [
319
+ {
320
+ ResourceType: "instance",
321
+ Tags: [
322
+ {
323
+ Key: "Name",
324
+ Value: `${cloudName}-jump-box`
325
+ }
326
+ ]
327
+ }
328
+ ],
329
+ UserData: base64UserData,
330
+ IamInstanceProfile: {
331
+ Name: jumpBoxInstanceProfileName
332
+ }
333
+ });
334
+ return instance.Instances && instance.Instances[0] ? ok(`Jump-box created with id ${instance.Instances[0].InstanceId}`) : err("Jump-box creation failed");
335
+ }
336
+ async function getJumpBoxSecurityGroupName() {
337
+ const jumpBoxId = await getJumpBoxInstanceId();
338
+ if (!jumpBoxId)
339
+ return err("Jump-box not found");
340
+ const ec2 = new EC2({ region: "us-east-1" });
341
+ const data = await ec2.describeInstances({ InstanceIds: [jumpBoxId] });
342
+ if (data.Reservations && data.Reservations[0] && data.Reservations[0].Instances && data.Reservations[0].Instances[0]) {
343
+ const instance = data.Reservations[0].Instances[0];
344
+ const securityGroups = instance.SecurityGroups;
345
+ if (securityGroups && securityGroups[0])
346
+ return ok(securityGroups[0].GroupName);
347
+ }
348
+ return err("Security group not found");
349
+ }
350
+ async function getSecurityGroupFromInstanceId(instanceId) {
351
+ const ec2 = new EC2({ region: "us-east-1" });
352
+ const data = await ec2.describeInstances({ InstanceIds: [instanceId] });
353
+ if (data.Reservations && data.Reservations[0] && data.Reservations[0].Instances && data.Reservations[0].Instances[0]) {
354
+ const instance = data.Reservations[0].Instances[0];
355
+ const securityGroups = instance.SecurityGroups;
356
+ if (securityGroups && securityGroups[0])
357
+ return securityGroups[0].GroupId;
358
+ }
359
+ return;
360
+ }
361
+ async function isFirstDeployment() {
362
+ const stackName = cloudName;
363
+ const cloudFormation = new CloudFormation;
364
+ const data = await cloudFormation.listStacks({ StackStatusFilter: ["CREATE_COMPLETE", "UPDATE_COMPLETE"] });
365
+ const isStacksCloudPresent = data.StackSummaries?.some((stack) => stack.StackName === stackName);
366
+ return !isStacksCloudPresent;
367
+ }
368
+ async function isFailedState() {
369
+ const cloudFormation = new CloudFormation;
370
+ const data = await cloudFormation.listStacks({ StackStatusFilter: ["CREATE_FAILED", "UPDATE_FAILED", "ROLLBACK_COMPLETE", "UPDATE_ROLLBACK_COMPLETE"] });
371
+ const isStacksCloudPresent = data.StackSummaries?.some((stack) => stack.StackName === cloudName);
372
+ return !isStacksCloudPresent;
373
+ }
374
+ async function getOrCreateTimestamp() {
375
+ const parameterName = `/stacks/timestamp`;
376
+ const ssm = new SSM({ region: "us-east-1" });
377
+ try {
378
+ const response = await ssm.getParameter({ Name: parameterName });
379
+ const timestamp = response.Parameter ? response.Parameter.Value : undefined;
380
+ if (!timestamp)
381
+ throw new Error("Timestamp parameter not found");
382
+ return timestamp;
383
+ } catch (error) {
384
+ const timestamp = new Date().getTime().toString();
385
+ log.debug(`Creating timestamp parameter ${parameterName} with value ${timestamp}`);
386
+ await ssm.putParameter({
387
+ Name: parameterName,
388
+ Value: timestamp,
389
+ Type: "String"
390
+ });
391
+ return timestamp;
392
+ }
393
+ }
394
+ var appEnv = config2.app.env === "local" ? "dev" : config2.app.env;
395
+ var cloudName = `stacks-cloud-${appEnv}`;
396
+ // src/cloud/index.ts
397
+ import {Stack as Stack2} from "aws-cdk-lib";
398
+
399
+ // src/cloud/ai.ts
400
+ import {Duration, CfnOutput as Output, aws_iam as iam, aws_lambda as lambda} from "aws-cdk-lib";
401
+ import {config as config4} from "@stacksjs/config";
402
+
403
+ class AiStack {
404
+ askAiUrl;
405
+ summarizeAiUrl;
406
+ constructor(scope, props) {
407
+ const awsSdkLayer = new lambda.LayerVersion(scope, "AwsSdkLayer", {
408
+ code: lambda.Code.fromAsset("src/cloud/aws-sdk-layer"),
409
+ compatibleRuntimes: [lambda.Runtime.NODEJS_20_X],
410
+ description: "Layer with aws-sdk"
411
+ });
412
+ const bedrockAccessPolicy = new iam.PolicyStatement({
413
+ effect: iam.Effect.ALLOW,
414
+ actions: [
415
+ "bedrock:InvokeModel",
416
+ "bedrock:InvokeModelWithResponseStream"
417
+ ],
418
+ resources: config4.ai.models?.map((model) => `arn:aws:bedrock:us-east-1::foundation-model/${model}`)
419
+ });
420
+ const bedrockAccessRole = new iam.Role(scope, "BedrockAccessRole", {
421
+ assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
422
+ managedPolicies: [
423
+ iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")
424
+ ]
425
+ });
426
+ bedrockAccessRole.addToPolicy(bedrockAccessPolicy);
427
+ const askAi = new lambda.Function(scope, "AskAiFunction", {
428
+ functionName: `${props.slug}-${props.appEnv}-ai-ask`,
429
+ description: "Lambda function to invoke the AI model",
430
+ runtime: lambda.Runtime.NODEJS_20_X,
431
+ handler: "index.handler",
432
+ code: lambda.Code.fromAsset("src/cloud/lambda/ask"),
433
+ layers: [awsSdkLayer],
434
+ role: bedrockAccessRole,
435
+ timeout: Duration.seconds(30)
436
+ });
437
+ this.askAiUrl = new lambda.FunctionUrl(scope, "AskAiFunctionUrl", {
438
+ function: askAi,
439
+ authType: lambda.FunctionUrlAuthType.NONE,
440
+ cors: {
441
+ allowedOrigins: ["*"]
442
+ }
443
+ });
444
+ const summarizeAi = new lambda.Function(scope, "SummarizeAiFunction", {
445
+ functionName: `${props.slug}-${props.appEnv}-ai-summarize`,
446
+ description: "Lambda function to summarize any given text",
447
+ runtime: lambda.Runtime.NODEJS_20_X,
448
+ handler: "index.handler",
449
+ code: lambda.Code.fromAsset("src/cloud/lambda/summarize"),
450
+ layers: [awsSdkLayer],
451
+ role: bedrockAccessRole,
452
+ timeout: Duration.seconds(30)
453
+ });
454
+ this.summarizeAiUrl = new lambda.FunctionUrl(scope, "SummarizeAiFunctionUrl", {
455
+ function: summarizeAi,
456
+ authType: lambda.FunctionUrlAuthType.NONE,
457
+ cors: {
458
+ allowedOrigins: ["*"]
459
+ }
460
+ });
461
+ new Output(scope, "AiVanityAskApiUrl", {
462
+ value: this.askAiUrl.url
463
+ });
464
+ new Output(scope, "AiVanitySummarizeApiUrl", {
465
+ value: this.summarizeAiUrl.url
466
+ });
467
+ new Output(scope, "AiAskApiUrl", {
468
+ value: `https://${props.domain}/ai/ask`
469
+ });
470
+ new Output(scope, "AiSummarizeApiUrl", {
471
+ value: `https://${props.domain}/ai/summary`
472
+ });
473
+ }
474
+ }
475
+
476
+ // src/cloud/cdn.ts
477
+ import {Duration as Duration2, Fn, CfnOutput as Output2, aws_cloudfront as cloudfront, aws_cloudfront_origins as origins, aws_route53 as route53, aws_route53_targets as targets} from "aws-cdk-lib";
478
+ import {config as config6} from "@stacksjs/config";
479
+
480
+ // /Users/chrisbreuer/Code/stacks/storage/framework/core/storage/dist/index.js
481
+ import {detectIndent, detectNewline} from "@stacksjs/strings";
482
+ import {dirname, join as join2, path as p2} from "@stacksjs/path";
483
+
484
+ // /Users/chrisbreuer/Code/stacks/storage/framework/core/arrays/dist/index.js
485
+ import {clamp} from "@stacksjs/utils";
486
+ var toArray = function(array) {
487
+ array = array ?? [];
488
+ return Array.isArray(array) ? array : [array];
489
+ };
490
+ var flatten = function(array) {
491
+ return toArray(array).flat(1);
492
+ };
493
+ var mergeArrayable = function(...args) {
494
+ return args.flatMap((i) => toArray(i));
495
+ };
496
+ var partition = function(array, ...filters) {
497
+ const result = Array.from({ length: filters.length + 1 }).fill(null).map(() => []);
498
+ array.forEach((e, idx, arr) => {
499
+ let i = 0;
500
+ for (const filter of filters) {
501
+ if (filter(e, idx, arr)) {
502
+ result[i].push(e);
503
+ return;
504
+ }
505
+ i += 1;
506
+ }
507
+ result[i].push(e);
508
+ });
509
+ return result;
510
+ };
511
+ var uniq = function(array) {
512
+ return Array.from(new Set(array));
513
+ };
514
+ var unique = function(array) {
515
+ return uniq(array);
516
+ };
517
+ var uniqueBy = function(array, equalFn) {
518
+ return array.reduce((acc, cur) => {
519
+ const index = acc.findIndex((item) => equalFn(cur, item));
520
+ if (index === -1)
521
+ acc.push(cur);
522
+ return acc;
523
+ }, []);
524
+ };
525
+ var last = function(array) {
526
+ return at(array, -1);
527
+ };
528
+ var remove = function(array, value) {
529
+ if (!array)
530
+ return false;
531
+ const index = array.indexOf(value);
532
+ if (index >= 0) {
533
+ array.splice(index, 1);
534
+ return true;
535
+ }
536
+ return false;
537
+ };
538
+ var at = function(array, index) {
539
+ const len = array.length;
540
+ if (!len)
541
+ return;
542
+ if (index < 0)
543
+ index += len;
544
+ return array[index];
545
+ };
546
+ var move = function(array, from, to) {
547
+ const len = array.length;
548
+ if (!len)
549
+ return [];
550
+ if (from < 0)
551
+ from += len;
552
+ if (to < 0)
553
+ to += len;
554
+ const item = array.splice(from, 1)[0];
555
+ array.splice(to, 0, item);
556
+ return array;
557
+ };
558
+ var clampArrayRange = function(arr, n) {
559
+ return clamp(n, 0, arr.length - 1);
560
+ };
561
+ var sample = function(arr, count) {
562
+ return Array.from({ length: count }, (_) => arr[Math.round(Math.random() * (arr.length - 1))]);
563
+ };
564
+ var shuffle = function(array) {
565
+ for (let i = array.length - 1;i > 0; i--) {
566
+ const j = Math.floor(Math.random() * (i + 1));
567
+ [array[i], array[j]] = [array[j], array[i]];
568
+ }
569
+ return array;
570
+ };
571
+ var average = function(arr) {
572
+ return sum(arr) / arr.length;
573
+ };
574
+ var avg = function(arr) {
575
+ return average(arr);
576
+ };
577
+ var median = function(arr) {
578
+ return arr[Math.floor(arr.length / 2)];
579
+ };
580
+ var mode = function(arr) {
581
+ return arr.sort((a, b) => arr.filter((v) => v === a).length - arr.filter((v) => v === b).length).pop();
582
+ };
583
+ var sum = function(array) {
584
+ return array.reduce((acc, cur) => acc + cur, 0);
585
+ };
586
+ var product = function(array) {
587
+ return array.reduce((acc, cur) => acc * cur, 1);
588
+ };
589
+ var min = function(array) {
590
+ return Math.min(...array);
591
+ };
592
+ var max = function(array) {
593
+ return Math.max(...array);
594
+ };
595
+ var range = function(array) {
596
+ return max(array) - min(array);
597
+ };
598
+ var variance = function(array) {
599
+ const mean = average(array);
600
+ return average(array.map((num) => (num - mean) ** 2));
601
+ };
602
+ var standardDeviation = function(array) {
603
+ return Math.sqrt(variance(array));
604
+ };
605
+ var zScore = function(array, num) {
606
+ return (num - average(array)) / standardDeviation(array);
607
+ };
608
+ var percentile = function(array, num) {
609
+ return array.filter((n) => n < num).length / array.length;
610
+ };
611
+ var interquartileRange = function(array) {
612
+ const q1 = median(array.slice(0, Math.floor(array.length / 2)));
613
+ const q3 = median(array.slice(Math.ceil(array.length / 2)));
614
+ return q3 - q1;
615
+ };
616
+ var covariance = function(array1, array2) {
617
+ const mean1 = average(array1);
618
+ const mean2 = average(array2);
619
+ return average(array1.map((num1, i) => (num1 - mean1) * (array2[i] - mean2)));
620
+ };
621
+ var contains = function(needle, haystack) {
622
+ return haystack.some((hay) => needle.includes(hay));
623
+ };
624
+ var containsAll = function(needles, haystack) {
625
+ return needles.every((needle) => contains(needle, haystack));
626
+ };
627
+ var containsAny = function(needles, haystack) {
628
+ return needles.some((needle) => contains(needle, haystack));
629
+ };
630
+ var containsNone = function(needles, haystack) {
631
+ return !containsAny(needles, haystack);
632
+ };
633
+ var containsOnly = function(needles, haystack) {
634
+ return containsAll(haystack, needles);
635
+ };
636
+ var doesNotContain = function(needle, haystack) {
637
+ return !contains(needle, haystack);
638
+ };
639
+ var __defProp = Object.defineProperty;
640
+ var __export = (target, all) => {
641
+ for (var name in all)
642
+ __defProp(target, name, {
643
+ get: all[name],
644
+ enumerable: true,
645
+ configurable: true,
646
+ set: (newValue) => all[name] = () => newValue
647
+ });
648
+ };
649
+ var exports_arr = {};
650
+ __export(exports_arr, {
651
+ zScore: () => {
652
+ {
653
+ return zScore;
654
+ }
655
+ },
656
+ variance: () => {
657
+ {
658
+ return variance;
659
+ }
660
+ },
661
+ uniqueBy: () => {
662
+ {
663
+ return uniqueBy;
664
+ }
665
+ },
666
+ unique: () => {
667
+ {
668
+ return unique;
669
+ }
670
+ },
671
+ uniq: () => {
672
+ {
673
+ return uniq;
674
+ }
675
+ },
676
+ toArray: () => {
677
+ {
678
+ return toArray;
679
+ }
680
+ },
681
+ sum: () => {
682
+ {
683
+ return sum;
684
+ }
685
+ },
686
+ standardDeviation: () => {
687
+ {
688
+ return standardDeviation;
689
+ }
690
+ },
691
+ shuffle: () => {
692
+ {
693
+ return shuffle;
694
+ }
695
+ },
696
+ sample: () => {
697
+ {
698
+ return sample;
699
+ }
700
+ },
701
+ remove: () => {
702
+ {
703
+ return remove;
704
+ }
705
+ },
706
+ range: () => {
707
+ {
708
+ return range;
709
+ }
710
+ },
711
+ product: () => {
712
+ {
713
+ return product;
714
+ }
715
+ },
716
+ percentile: () => {
717
+ {
718
+ return percentile;
719
+ }
720
+ },
721
+ partition: () => {
722
+ {
723
+ return partition;
724
+ }
725
+ },
726
+ move: () => {
727
+ {
728
+ return move;
729
+ }
730
+ },
731
+ mode: () => {
732
+ {
733
+ return mode;
734
+ }
735
+ },
736
+ min: () => {
737
+ {
738
+ return min;
739
+ }
740
+ },
741
+ mergeArrayable: () => {
742
+ {
743
+ return mergeArrayable;
744
+ }
745
+ },
746
+ median: () => {
747
+ {
748
+ return median;
749
+ }
750
+ },
751
+ max: () => {
752
+ {
753
+ return max;
754
+ }
755
+ },
756
+ last: () => {
757
+ {
758
+ return last;
759
+ }
760
+ },
761
+ interquartileRange: () => {
762
+ {
763
+ return interquartileRange;
764
+ }
765
+ },
766
+ flatten: () => {
767
+ {
768
+ return flatten;
769
+ }
770
+ },
771
+ doesNotContain: () => {
772
+ {
773
+ return doesNotContain;
774
+ }
775
+ },
776
+ covariance: () => {
777
+ {
778
+ return covariance;
779
+ }
780
+ },
781
+ containsOnly: () => {
782
+ {
783
+ return containsOnly;
784
+ }
785
+ },
786
+ containsNone: () => {
787
+ {
788
+ return containsNone;
789
+ }
790
+ },
791
+ containsAny: () => {
792
+ {
793
+ return containsAny;
794
+ }
795
+ },
796
+ containsAll: () => {
797
+ {
798
+ return containsAll;
799
+ }
800
+ },
801
+ contains: () => {
802
+ {
803
+ return contains;
804
+ }
805
+ },
806
+ clampArrayRange: () => {
807
+ {
808
+ return clampArrayRange;
809
+ }
810
+ },
811
+ avg: () => {
812
+ {
813
+ return avg;
814
+ }
815
+ },
816
+ average: () => {
817
+ {
818
+ return average;
819
+ }
820
+ },
821
+ at: () => {
822
+ {
823
+ return at;
824
+ }
825
+ }
826
+ });
827
+
828
+ // /Users/chrisbreuer/Code/stacks/storage/framework/core/storage/dist/index.js
829
+ import {mkdirSync, writeFileSync} from "fs";
830
+ import * as fs from "fs-extra";
831
+ import {pathExists as existsSync2} from "fs-extra";
832
+ async function exists(path3) {
833
+ return await existsSync2(path3);
834
+ }
835
+ async function readJsonFile(name, cwd) {
836
+ const file = await readTextFile(name, cwd);
837
+ const data = JSON.parse(file.data);
838
+ const indent = detectIndent(file.data).indent;
839
+ const newline = detectNewline(file.data);
840
+ return { ...file, data, indent, newline };
841
+ }
842
+ async function readPackageJson(name, cwd) {
843
+ const file = await readJsonFile(name, cwd);
844
+ return file.data;
845
+ }
846
+ async function writeFile(path22, data) {
847
+ if (typeof path22 === "string") {
848
+ const dirPath = dirname(path22);
849
+ if (!await existsSync2(dirPath))
850
+ await createFolder(dirPath);
851
+ return await Bun.write(Bun.file(path22), data);
852
+ }
853
+ return await Bun.write(path22, data);
854
+ }
855
+ async function writeJsonFile(file) {
856
+ let json = JSON.stringify(file.data, undefined, file.indent);
857
+ if (file.newline)
858
+ json += file.newline;
859
+ return writeTextFile({ ...file, data: json });
860
+ }
861
+ var readTextFile = function(name, cwd) {
862
+ return new Promise((resolve, reject) => {
863
+ let filePath;
864
+ if (cwd)
865
+ filePath = join2(cwd, name);
866
+ else
867
+ filePath = name;
868
+ fs.readFile(filePath, "utf8", (err2, text) => {
869
+ if (err2) {
870
+ reject(err2);
871
+ } else {
872
+ resolve({
873
+ path: filePath,
874
+ data: text
875
+ });
876
+ }
877
+ });
878
+ });
879
+ };
880
+ async function writeTextFile(file) {
881
+ return await Bun.write(file.path, file.data);
882
+ }
883
+ var isFile = function(path22) {
884
+ return fs.existsSync(path22);
885
+ };
886
+ var doesExist = function(path22) {
887
+ return !isFile(path22) || !isFolder(path22);
888
+ };
889
+ var doesNotExist = function(path22) {
890
+ return !isFile(path22) && !isFolder(path22);
891
+ };
892
+ var hasFiles = function(folder) {
893
+ try {
894
+ return fs.readdirSync(folder).length > 0;
895
+ } catch (err2) {
896
+ return false;
897
+ }
898
+ };
899
+ var hasComponents = function() {
900
+ return hasFiles(p2.componentsPath());
901
+ };
902
+ var hasFunctions = function() {
903
+ return hasFiles(p2.functionsPath());
904
+ };
905
+ var deleteFiles = function(dir, exclude = []) {
906
+ if (fs.existsSync(dir)) {
907
+ fs.readdirSync(dir).forEach((file) => {
908
+ const p22 = join2(dir, file);
909
+ if (fs.statSync(p22).isDirectory()) {
910
+ if (fs.readdirSync(p22).length === 0)
911
+ fs.rmSync(p22, { recursive: true, force: true });
912
+ else
913
+ deleteFiles(p22, exclude);
914
+ } else if (!contains(p22, exclude)) {
915
+ fs.rmSync(p22);
916
+ }
917
+ });
918
+ }
919
+ };
920
+ var getFiles = function(dir, exclude = []) {
921
+ let results = [];
922
+ const list = fs.readdirSync(dir);
923
+ list.forEach((file) => {
924
+ file = join2(dir, file);
925
+ const stat = fs.statSync(file);
926
+ if (stat && stat.isDirectory())
927
+ results = results.concat(getFiles(file, exclude));
928
+ else if (!contains(file, exclude))
929
+ results.push(file);
930
+ });
931
+ return results;
932
+ };
933
+ var put = function(path22, contents) {
934
+ const dirPath = dirname(path22);
935
+ if (!fs.existsSync(dirPath))
936
+ fs.mkdirSync(dirPath, { recursive: true });
937
+ fs.writeFileSync(path22, contents, "utf-8");
938
+ };
939
+ async function get(path22) {
940
+ return Bun.file(path22).text();
941
+ }
942
+ import {join as join3} from "@stacksjs/path";
943
+ var isFolder2 = function(path32) {
944
+ try {
945
+ return fs.statSync(path32).isDirectory();
946
+ } catch {
947
+ return false;
948
+ }
949
+ };
950
+ var isDirectory = function(path32) {
951
+ return isFolder2(path32);
952
+ };
953
+ var isDir = function(path32) {
954
+ return isFolder2(path32);
955
+ };
956
+ var doesFolderExist = function(path32) {
957
+ return fs.existsSync(path32);
958
+ };
959
+ var createFolder2 = function(dir) {
960
+ return new Promise((resolve, reject) => {
961
+ fs.mkdirs(dir, (err2) => {
962
+ if (err2)
963
+ reject(err2);
964
+ else
965
+ resolve();
966
+ });
967
+ });
968
+ };
969
+ var getFolders = function(dir) {
970
+ return fs.readdirSync(dir).filter((file) => {
971
+ return fs.statSync(join3(dir, file)).isDirectory();
972
+ });
973
+ };
974
+ import {createHash} from "crypto";
975
+ import {path as p22} from "@stacksjs/path";
976
+ import {fileURLToPath} from "url";
977
+ import {dirname as dirname2} from "@stacksjs/path";
978
+ var updateConfigFile = function(filePath, newConfig) {
979
+ return new Promise((resolve, reject) => {
980
+ const config5 = JSON.parse(fs.readFileSync(filePath, "utf8"));
981
+ for (const key in newConfig)
982
+ config5[key] = newConfig[key];
983
+ try {
984
+ fs.writeFileSync(filePath, JSON.stringify(config5, null, 2));
985
+ resolve();
986
+ } catch (error) {
987
+ reject(error);
988
+ }
989
+ });
990
+ };
991
+ import {join as join4} from "@stacksjs/path";
992
+ var copy = function(src, dest, exclude = []) {
993
+ if (Array.isArray(src)) {
994
+ src.forEach((file) => {
995
+ copy(file, dest, exclude);
996
+ });
997
+ } else {
998
+ if (fs.statSync(src).isDirectory())
999
+ copyFolder(src, dest, exclude);
1000
+ else
1001
+ copyFile(src, dest);
1002
+ }
1003
+ };
1004
+ var copyFile = function(src, dest) {
1005
+ fs.copyFileSync(src, dest);
1006
+ };
1007
+ var copyFolder = function(src, dest, exclude = []) {
1008
+ if (!fs.existsSync(dest))
1009
+ fs.mkdirSync(dest, { recursive: true });
1010
+ if (fs.existsSync(src)) {
1011
+ fs.readdirSync(src).forEach((file) => {
1012
+ if (!contains(join4(src, file), exclude)) {
1013
+ const srcPath = join4(src, file);
1014
+ const destPath = join4(dest, file);
1015
+ if (fs.statSync(srcPath).isDirectory())
1016
+ copyFolder(srcPath, destPath, exclude);
1017
+ else
1018
+ fs.copyFileSync(srcPath, destPath);
1019
+ }
1020
+ });
1021
+ }
1022
+ };
1023
+ import {default as default2} from "fast-glob";
1024
+ import {err as err2, ok as ok2} from "@stacksjs/error-handling";
1025
+ import {italic, log as log2} from "@stacksjs/cli";
1026
+ var deleteFolder = function(path62) {
1027
+ return new Promise((resolve, reject) => {
1028
+ try {
1029
+ if (isFolder2(path62)) {
1030
+ fs.rmSync(path62, { recursive: true, force: true });
1031
+ return resolve(ok2(`Deleted ${path62}`));
1032
+ }
1033
+ return resolve(ok2(`Path ${path62} was not a directory`));
1034
+ } catch (error) {
1035
+ return reject(err2(error));
1036
+ }
1037
+ });
1038
+ };
1039
+ async function isDirectoryEmpty(path62) {
1040
+ return new Promise((resolve, reject) => {
1041
+ try {
1042
+ if (fs.statSync(path62).isDirectory()) {
1043
+ if (fs.readdirSync(path62).length === 0)
1044
+ return resolve(ok2(true));
1045
+ else
1046
+ return resolve(ok2(false));
1047
+ }
1048
+ return resolve(ok2(false));
1049
+ } catch (error) {
1050
+ return reject(err2(error));
1051
+ }
1052
+ });
1053
+ }
1054
+ async function deleteEmptyFolder(path62) {
1055
+ return new Promise((resolve, reject) => {
1056
+ try {
1057
+ if (fs.statSync(path62).isDirectory()) {
1058
+ if (fs.readdirSync(path62).length === 0) {
1059
+ fs.rmSync(path62, { recursive: true, force: true });
1060
+ return resolve(ok2(`Deleted ${path62}`));
1061
+ } else {
1062
+ return resolve(ok2(`Path ${path62} was not empty`));
1063
+ }
1064
+ }
1065
+ return resolve(ok2(`Path ${path62} was not a directory`));
1066
+ } catch (error) {
1067
+ return reject(err2(error));
1068
+ }
1069
+ });
1070
+ }
1071
+ async function deleteEmptyFolders(dir) {
1072
+ try {
1073
+ if (!fs.existsSync(dir))
1074
+ return ok2(`Path ${dir} does not exist`);
1075
+ const files3 = fs.readdirSync(dir);
1076
+ for (const file of files3) {
1077
+ const p3 = join(dir, file);
1078
+ if (isFolder2(p3)) {
1079
+ if (fs.readdirSync(p3).length === 0)
1080
+ fs.rmSync(p3, { recursive: true, force: true });
1081
+ else
1082
+ await deleteEmptyFolders(p3);
1083
+ }
1084
+ }
1085
+ return ok2(`Deleted empty folders located in ${dir}`);
1086
+ } catch (error) {
1087
+ return err2(error);
1088
+ }
1089
+ }
1090
+ var deleteFile = function(path62) {
1091
+ return new Promise((resolve, reject) => {
1092
+ try {
1093
+ if (isFile(path62)) {
1094
+ fs.rmSync(path62, { recursive: true, force: true });
1095
+ return resolve(ok2(`Deleted ${path62}`));
1096
+ }
1097
+ return resolve(ok2(`Path ${path62} was not a file`));
1098
+ } catch (error) {
1099
+ return reject(err2(error));
1100
+ }
1101
+ });
1102
+ };
1103
+ async function deleteGlob(path62) {
1104
+ if (!path62.includes("*"))
1105
+ return err2(handleError(`Path ${path62} does not contain a glob`));
1106
+ const directories = await default2([path62], { onlyDirectories: true });
1107
+ for (const directory of directories) {
1108
+ const result = await deleteFolder(directory);
1109
+ if (result.isErr()) {
1110
+ log2.error(result.error);
1111
+ return result;
1112
+ }
1113
+ log2.info(`Deleted ${italic(directory)}`);
1114
+ }
1115
+ return ok2(`Deleted ${directories.length} directories`);
1116
+ }
1117
+ async function del(path62) {
1118
+ if (isFile(path62))
1119
+ return await deleteFile(path62);
1120
+ if (isFolder2(path62))
1121
+ return await deleteFolder(path62);
1122
+ if (path62.includes("*"))
1123
+ return await deleteGlob(path62);
1124
+ return err2(handleError(`Path ${path62} cannot be deleted due to an unhandled condition. Please report this issue.`));
1125
+ }
1126
+ import {runCommand} from "@stacksjs/cli";
1127
+ async function zip(from, to, options) {
1128
+ const toPath = to || "archive.zip";
1129
+ const fromPath = Array.isArray(from) ? from.join(" ") : from;
1130
+ if (Array.isArray(from))
1131
+ return runCommand(`zip -r ${toPath} ${fromPath}`, options);
1132
+ return runCommand(`zip -r ${to} ${from}`, options);
1133
+ }
1134
+ async function unzip(paths) {
1135
+ if (Array.isArray(paths))
1136
+ return runCommand(`unzip ${paths.join(" ")}`);
1137
+ return runCommand(`unzip ${paths}`);
1138
+ }
1139
+ var archive = function(paths) {
1140
+ return zip(paths);
1141
+ };
1142
+ var unarchive = function(paths) {
1143
+ return unzip(paths);
1144
+ };
1145
+ var compress = function(paths) {
1146
+ return zip(paths);
1147
+ };
1148
+ var decompress = function(paths) {
1149
+ return unzip(paths);
1150
+ };
1151
+ var gzipSync = function(data, options) {
1152
+ return Bun.gzipSync(data, options);
1153
+ };
1154
+ var gunzipSync = function(data) {
1155
+ return Bun.gunzipSync(data);
1156
+ };
1157
+ var deflateSync = function(data, options) {
1158
+ return Bun.deflateSync(data, options);
1159
+ };
1160
+ var inflateSync = function(data) {
1161
+ return Bun.inflateSync(data);
1162
+ };
1163
+ import {err as err22, ok as ok22} from "@stacksjs/error-handling";
1164
+ import {log as log22} from "@stacksjs/logging";
1165
+ import {path as path72} from "@stacksjs/path";
1166
+ async function move2(src, dest, options) {
1167
+ try {
1168
+ if (Array.isArray(src)) {
1169
+ const operations = src.map(async (file) => {
1170
+ const from2 = file;
1171
+ const to2 = path72.resolve(dest, path72.basename(file));
1172
+ const result2 = await rename(from2, to2, options);
1173
+ if (result2.isErr()) {
1174
+ log22.error(result2.error);
1175
+ return err22(handleError(result2.error.message, result2.error));
1176
+ }
1177
+ });
1178
+ await Promise.all(operations);
1179
+ return ok22({ message: "Files moved successfully" });
1180
+ }
1181
+ const from = src;
1182
+ const to = dest;
1183
+ const result = await rename(from, to, options);
1184
+ if (result.isErr()) {
1185
+ log22.error(result.error);
1186
+ return err22(handleError(result.error));
1187
+ }
1188
+ return ok22({ message: "File moved successfully" });
1189
+ } catch (error) {
1190
+ return err22(handleError(error));
1191
+ }
1192
+ }
1193
+ async function rename(from, to, options) {
1194
+ return new Promise((resolve, reject) => {
1195
+ try {
1196
+ const dir = path72.dirname(to);
1197
+ if (!fs.existsSync(dir))
1198
+ fs.mkdirSync(dir, { recursive: true });
1199
+ if (!fs.existsSync(from))
1200
+ return reject(err22(new Error(`File or directory does not exist: ${from}`)));
1201
+ if (fs.existsSync(to)) {
1202
+ if (!options?.overwrite)
1203
+ return reject(err22(new Error(`File or directory already exists: ${to}`)));
1204
+ fs.unlinkSync(to);
1205
+ }
1206
+ fs.renameSync(from, to);
1207
+ return resolve(ok22({ message: "File moved successfully" }));
1208
+ } catch (error) {
1209
+ if (error.code === "ENOENT")
1210
+ log22.error("File or directory does not exist\n\n", error);
1211
+ else
1212
+ log22.error(error);
1213
+ return reject(err22(new Error(error)));
1214
+ }
1215
+ });
1216
+ }
1217
+ var setVisibility = function() {
1218
+ return "wip";
1219
+ };
1220
+ var __defProp2 = Object.defineProperty;
1221
+ var __export2 = (target, all) => {
1222
+ for (var name in all)
1223
+ __defProp2(target, name, {
1224
+ get: all[name],
1225
+ enumerable: true,
1226
+ configurable: true,
1227
+ set: (newValue) => all[name] = () => newValue
1228
+ });
1229
+ };
1230
+ var files = {
1231
+ readJsonFile,
1232
+ readPackageJson,
1233
+ readTextFile,
1234
+ writeJsonFile,
1235
+ writeTextFile,
1236
+ isFile,
1237
+ hasFiles,
1238
+ hasComponents,
1239
+ hasFunctions,
1240
+ deleteFiles,
1241
+ getFiles,
1242
+ put,
1243
+ get
1244
+ };
1245
+ var folders = {
1246
+ isFolder: isFolder2,
1247
+ doesFolderExist,
1248
+ createFolder: createFolder2,
1249
+ getFolders
1250
+ };
1251
+ var __dirname2 = "/Users/chrisbreuer/Code/stacks/storage/framework/core/storage/src";
1252
+ var _dirname = typeof __dirname2 !== "undefined" ? __dirname2 : dirname2(fileURLToPath(import.meta.url));
1253
+ var helpers = {
1254
+ _dirname,
1255
+ updateConfigFile
1256
+ };
1257
+ var exports_storage = {};
1258
+ __export2(exports_storage, {
1259
+ zip: () => {
1260
+ {
1261
+ return zip;
1262
+ }
1263
+ },
1264
+ writeTextFile: () => {
1265
+ {
1266
+ return writeTextFile;
1267
+ }
1268
+ },
1269
+ writeJsonFile: () => {
1270
+ {
1271
+ return writeJsonFile;
1272
+ }
1273
+ },
1274
+ writeFileSync: () => {
1275
+ {
1276
+ return writeFileSync;
1277
+ }
1278
+ },
1279
+ writeFile: () => {
1280
+ {
1281
+ return writeFile;
1282
+ }
1283
+ },
1284
+ updateConfigFile: () => {
1285
+ {
1286
+ return updateConfigFile;
1287
+ }
1288
+ },
1289
+ unzip: () => {
1290
+ {
1291
+ return unzip;
1292
+ }
1293
+ },
1294
+ unarchive: () => {
1295
+ {
1296
+ return unarchive;
1297
+ }
1298
+ },
1299
+ setVisibility: () => {
1300
+ {
1301
+ return setVisibility;
1302
+ }
1303
+ },
1304
+ rename: () => {
1305
+ {
1306
+ return rename;
1307
+ }
1308
+ },
1309
+ readTextFile: () => {
1310
+ {
1311
+ return readTextFile;
1312
+ }
1313
+ },
1314
+ readPackageJson: () => {
1315
+ {
1316
+ return readPackageJson;
1317
+ }
1318
+ },
1319
+ readJsonFile: () => {
1320
+ {
1321
+ return readJsonFile;
1322
+ }
1323
+ },
1324
+ put: () => {
1325
+ {
1326
+ return put;
1327
+ }
1328
+ },
1329
+ move: () => {
1330
+ {
1331
+ return move2;
1332
+ }
1333
+ },
1334
+ mkdirSync: () => {
1335
+ {
1336
+ return mkdirSync;
1337
+ }
1338
+ },
1339
+ isFolder: () => {
1340
+ {
1341
+ return isFolder2;
1342
+ }
1343
+ },
1344
+ isFile: () => {
1345
+ {
1346
+ return isFile;
1347
+ }
1348
+ },
1349
+ isDirectoryEmpty: () => {
1350
+ {
1351
+ return isDirectoryEmpty;
1352
+ }
1353
+ },
1354
+ isDirectory: () => {
1355
+ {
1356
+ return isDirectory;
1357
+ }
1358
+ },
1359
+ isDir: () => {
1360
+ {
1361
+ return isDir;
1362
+ }
1363
+ },
1364
+ inflateSync: () => {
1365
+ {
1366
+ return inflateSync;
1367
+ }
1368
+ },
1369
+ helpers: () => {
1370
+ {
1371
+ return helpers;
1372
+ }
1373
+ },
1374
+ hasFunctions: () => {
1375
+ {
1376
+ return hasFunctions;
1377
+ }
1378
+ },
1379
+ hasFiles: () => {
1380
+ {
1381
+ return hasFiles;
1382
+ }
1383
+ },
1384
+ hasComponents: () => {
1385
+ {
1386
+ return hasComponents;
1387
+ }
1388
+ },
1389
+ gzipSync: () => {
1390
+ {
1391
+ return gzipSync;
1392
+ }
1393
+ },
1394
+ gunzipSync: () => {
1395
+ {
1396
+ return gunzipSync;
1397
+ }
1398
+ },
1399
+ getFolders: () => {
1400
+ {
1401
+ return getFolders;
1402
+ }
1403
+ },
1404
+ getFiles: () => {
1405
+ {
1406
+ return getFiles;
1407
+ }
1408
+ },
1409
+ get: () => {
1410
+ {
1411
+ return get;
1412
+ }
1413
+ },
1414
+ fs: () => {
1415
+ {
1416
+ return fs;
1417
+ }
1418
+ },
1419
+ folders: () => {
1420
+ {
1421
+ return folders;
1422
+ }
1423
+ },
1424
+ files: () => {
1425
+ {
1426
+ return files;
1427
+ }
1428
+ },
1429
+ existsSync: () => {
1430
+ {
1431
+ return existsSync2;
1432
+ }
1433
+ },
1434
+ exists: () => {
1435
+ {
1436
+ return exists;
1437
+ }
1438
+ },
1439
+ doesNotExist: () => {
1440
+ {
1441
+ return doesNotExist;
1442
+ }
1443
+ },
1444
+ doesFolderExist: () => {
1445
+ {
1446
+ return doesFolderExist;
1447
+ }
1448
+ },
1449
+ doesExist: () => {
1450
+ {
1451
+ return doesExist;
1452
+ }
1453
+ },
1454
+ deleteGlob: () => {
1455
+ {
1456
+ return deleteGlob;
1457
+ }
1458
+ },
1459
+ deleteFolder: () => {
1460
+ {
1461
+ return deleteFolder;
1462
+ }
1463
+ },
1464
+ deleteFiles: () => {
1465
+ {
1466
+ return deleteFiles;
1467
+ }
1468
+ },
1469
+ deleteFile: () => {
1470
+ {
1471
+ return deleteFile;
1472
+ }
1473
+ },
1474
+ deleteEmptyFolders: () => {
1475
+ {
1476
+ return deleteEmptyFolders;
1477
+ }
1478
+ },
1479
+ deleteEmptyFolder: () => {
1480
+ {
1481
+ return deleteEmptyFolder;
1482
+ }
1483
+ },
1484
+ del: () => {
1485
+ {
1486
+ return del;
1487
+ }
1488
+ },
1489
+ deflateSync: () => {
1490
+ {
1491
+ return deflateSync;
1492
+ }
1493
+ },
1494
+ decompress: () => {
1495
+ {
1496
+ return decompress;
1497
+ }
1498
+ },
1499
+ createFolder: () => {
1500
+ {
1501
+ return createFolder2;
1502
+ }
1503
+ },
1504
+ copyFolder: () => {
1505
+ {
1506
+ return copyFolder;
1507
+ }
1508
+ },
1509
+ copyFile: () => {
1510
+ {
1511
+ return copyFile;
1512
+ }
1513
+ },
1514
+ copy: () => {
1515
+ {
1516
+ return copy;
1517
+ }
1518
+ },
1519
+ compress: () => {
1520
+ {
1521
+ return compress;
1522
+ }
1523
+ },
1524
+ archive: () => {
1525
+ {
1526
+ return archive;
1527
+ }
1528
+ },
1529
+ _dirname: () => {
1530
+ {
1531
+ return _dirname;
1532
+ }
1533
+ }
1534
+ });
1535
+
1536
+ // src/cloud/cdn.ts
1537
+ import {path as p3} from "@stacksjs/path";
1538
+ import {env as env2} from "@stacksjs/env";
1539
+
1540
+ class CdnStack {
1541
+ distribution;
1542
+ originAccessIdentity;
1543
+ cdnCachePolicy;
1544
+ apiCachePolicy;
1545
+ vanityUrl;
1546
+ props;
1547
+ constructor(scope, props) {
1548
+ this.props = props;
1549
+ this.originAccessIdentity = new cloudfront.OriginAccessIdentity(scope, "OAI");
1550
+ this.cdnCachePolicy = new cloudfront.CachePolicy(scope, "CdnCachePolicy", {
1551
+ comment: "Stacks CDN Cache Policy",
1552
+ cachePolicyName: `${props.slug}-${props.appEnv}-cdn-cache-policy`,
1553
+ minTtl: config6.cloud.cdn?.minTtl ? Duration2.seconds(config6.cloud.cdn.minTtl) : undefined,
1554
+ defaultTtl: config6.cloud.cdn?.defaultTtl ? Duration2.seconds(config6.cloud.cdn.defaultTtl) : undefined,
1555
+ maxTtl: config6.cloud.cdn?.maxTtl ? Duration2.seconds(config6.cloud.cdn.maxTtl) : undefined,
1556
+ cookieBehavior: this.getCookieBehavior(config6.cloud.cdn?.cookieBehavior)
1557
+ });
1558
+ this.distribution = new cloudfront.Distribution(scope, "Cdn", {
1559
+ domainNames: [props.domain],
1560
+ defaultRootObject: "index.html",
1561
+ comment: `CDN for ${config6.app.url}`,
1562
+ certificate: props.certificate,
1563
+ enableLogging: true,
1564
+ logBucket: props.logBucket,
1565
+ httpVersion: cloudfront.HttpVersion.HTTP2_AND_3,
1566
+ priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
1567
+ enabled: true,
1568
+ minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
1569
+ webAclId: props.firewall.attrArn,
1570
+ enableIpv6: true,
1571
+ defaultBehavior: {
1572
+ origin: new origins.S3Origin(props.publicBucket, {
1573
+ originAccessIdentity: this.originAccessIdentity
1574
+ }),
1575
+ edgeLambdas: [
1576
+ {
1577
+ eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
1578
+ functionVersion: props.originRequestFunction.currentVersion
1579
+ }
1580
+ ],
1581
+ compress: config6.cloud.cdn?.compress,
1582
+ allowedMethods: this.allowedMethods(),
1583
+ cachedMethods: this.cachedMethods(),
1584
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
1585
+ cachePolicy: this.cdnCachePolicy
1586
+ },
1587
+ additionalBehaviors: this.additionalBehaviors(scope, props),
1588
+ errorResponses: [
1589
+ {
1590
+ httpStatus: 403,
1591
+ responsePagePath: "/index.html",
1592
+ responseHttpStatus: 200,
1593
+ ttl: Duration2.seconds(0)
1594
+ }
1595
+ ]
1596
+ });
1597
+ new route53.ARecord(scope, "AliasRecord", {
1598
+ recordName: props.domain,
1599
+ zone: props.zone,
1600
+ target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(this.distribution))
1601
+ });
1602
+ new Output2(scope, "DistributionId", {
1603
+ value: this.distribution.distributionId
1604
+ });
1605
+ new Output2(scope, "AppUrl", {
1606
+ value: `https://${props.domain}`,
1607
+ description: "The URL of the deployed application"
1608
+ });
1609
+ this.vanityUrl = `https://${this.distribution.domainName}`;
1610
+ new Output2(scope, "AppVanityUrl", {
1611
+ value: this.vanityUrl,
1612
+ description: "The vanity URL of the deployed application"
1613
+ });
1614
+ }
1615
+ getCookieBehavior(behavior) {
1616
+ switch (behavior) {
1617
+ case "all":
1618
+ return cloudfront.CacheCookieBehavior.all();
1619
+ case "none":
1620
+ return cloudfront.CacheCookieBehavior.none();
1621
+ case "allowList":
1622
+ return cloudfront.CacheCookieBehavior.allowList(...config6.cloud.cdn?.allowList.cookies || []);
1623
+ default:
1624
+ return;
1625
+ }
1626
+ }
1627
+ allowedMethods() {
1628
+ switch (config6.cloud.cdn?.allowedMethods) {
1629
+ case "ALL":
1630
+ return cloudfront.AllowedMethods.ALLOW_ALL;
1631
+ case "GET_HEAD":
1632
+ return cloudfront.AllowedMethods.ALLOW_GET_HEAD;
1633
+ case "GET_HEAD_OPTIONS":
1634
+ return cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS;
1635
+ default:
1636
+ return cloudfront.AllowedMethods.ALLOW_ALL;
1637
+ }
1638
+ }
1639
+ cachedMethods() {
1640
+ switch (config6.cloud.cdn?.cachedMethods) {
1641
+ case "GET_HEAD":
1642
+ return cloudfront.CachedMethods.CACHE_GET_HEAD;
1643
+ case "GET_HEAD_OPTIONS":
1644
+ return cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS;
1645
+ default:
1646
+ return cloudfront.CachedMethods.CACHE_GET_HEAD;
1647
+ }
1648
+ }
1649
+ allowedMethodsFromString(methods) {
1650
+ if (!methods)
1651
+ return cloudfront.AllowedMethods.ALLOW_ALL;
1652
+ switch (methods) {
1653
+ case "ALL":
1654
+ return cloudfront.AllowedMethods.ALLOW_ALL;
1655
+ case "GET_HEAD":
1656
+ return cloudfront.AllowedMethods.ALLOW_GET_HEAD;
1657
+ case "GET_HEAD_OPTIONS":
1658
+ return cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS;
1659
+ default:
1660
+ return cloudfront.AllowedMethods.ALLOW_ALL;
1661
+ }
1662
+ }
1663
+ cachedMethodsFromString(methods) {
1664
+ if (!methods)
1665
+ return cloudfront.CachedMethods.CACHE_GET_HEAD;
1666
+ switch (methods) {
1667
+ case "GET_HEAD":
1668
+ return cloudfront.CachedMethods.CACHE_GET_HEAD;
1669
+ case "GET_HEAD_OPTIONS":
1670
+ return cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS;
1671
+ default:
1672
+ return cloudfront.CachedMethods.CACHE_GET_HEAD;
1673
+ }
1674
+ }
1675
+ shouldDeployApi() {
1676
+ return config6.cloud.api;
1677
+ }
1678
+ apiBehaviorOptions(scope, props) {
1679
+ const hostname = Fn.select(2, Fn.split("/", props.webServerUrl.url));
1680
+ const origin = (path9 = "/api") => {
1681
+ return new origins.HttpOrigin(hostname, {
1682
+ originPath: path9,
1683
+ protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY
1684
+ });
1685
+ };
1686
+ return {
1687
+ "/api": {
1688
+ origin: origin(),
1689
+ compress: true,
1690
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
1691
+ cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS,
1692
+ cachePolicy: this.setApiCachePolicy(scope),
1693
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS
1694
+ },
1695
+ "/api/*": {
1696
+ origin: origin("/api/*"),
1697
+ compress: true,
1698
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
1699
+ cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS,
1700
+ cachePolicy: this.apiCachePolicy,
1701
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS
1702
+ }
1703
+ };
1704
+ }
1705
+ docsBehaviorOptions(props) {
1706
+ return {
1707
+ "/docs": {
1708
+ origin: new origins.S3Origin(props.publicBucket, {
1709
+ originAccessIdentity: this.originAccessIdentity,
1710
+ originPath: "/docs"
1711
+ }),
1712
+ compress: true,
1713
+ allowedMethods: this.allowedMethodsFromString(config6.cloud.cdn?.allowedMethods),
1714
+ cachedMethods: this.cachedMethodsFromString(config6.cloud.cdn?.cachedMethods),
1715
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
1716
+ cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED
1717
+ },
1718
+ "/docs/*": {
1719
+ origin: new origins.S3Origin(props.publicBucket, {
1720
+ originAccessIdentity: this.originAccessIdentity,
1721
+ originPath: "/docs"
1722
+ }),
1723
+ compress: true,
1724
+ allowedMethods: this.allowedMethodsFromString(config6.cloud.cdn?.allowedMethods),
1725
+ cachedMethods: this.cachedMethodsFromString(config6.cloud.cdn?.cachedMethods),
1726
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
1727
+ cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED
1728
+ }
1729
+ };
1730
+ }
1731
+ aiBehaviorOptions(scope, props) {
1732
+ const hostname = Fn.select(2, Fn.split("/", props.askAiUrl.url));
1733
+ const summaryHostname = Fn.select(2, Fn.split("/", props.summarizeAiUrl.url));
1734
+ const aiCachePolicy = new cloudfront.CachePolicy(scope, "AiCachePolicy", {
1735
+ comment: "Stacks AI Cache Policy",
1736
+ cachePolicyName: `${this.props.slug}-${this.props.appEnv}-ai-cache-policy`,
1737
+ defaultTtl: Duration2.seconds(0),
1738
+ cookieBehavior: cloudfront.CacheCookieBehavior.none(),
1739
+ headerBehavior: cloudfront.CacheHeaderBehavior.allowList("Accept", "x-api-key", "Authorization", "Content-Type"),
1740
+ queryStringBehavior: cloudfront.CacheQueryStringBehavior.all()
1741
+ });
1742
+ return {
1743
+ "/ai/ask": {
1744
+ origin: new origins.HttpOrigin(hostname, {
1745
+ originPath: "/ai",
1746
+ protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY
1747
+ }),
1748
+ compress: false,
1749
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
1750
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
1751
+ cachePolicy: aiCachePolicy
1752
+ },
1753
+ "/ai/summary": {
1754
+ origin: new origins.HttpOrigin(summaryHostname, {
1755
+ originPath: "/ai",
1756
+ protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY
1757
+ }),
1758
+ compress: false,
1759
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
1760
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
1761
+ cachePolicy: aiCachePolicy
1762
+ }
1763
+ };
1764
+ }
1765
+ cliSetupBehaviorOptions(scope, props) {
1766
+ const hostname = Fn.select(2, Fn.split("/", props.cliSetupUrl.url));
1767
+ return {
1768
+ "/install": {
1769
+ origin: new origins.HttpOrigin(hostname, {
1770
+ originPath: "/cli-setup",
1771
+ protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY
1772
+ }),
1773
+ compress: false,
1774
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
1775
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
1776
+ cachePolicy: new cloudfront.CachePolicy(scope, "CliSetupCachePolicy", {
1777
+ comment: "Stacks CLI Setup Cache Policy",
1778
+ cachePolicyName: `${this.props.slug}-${this.props.appEnv}-cli-setup-cache-policy`,
1779
+ defaultTtl: Duration2.seconds(0),
1780
+ cookieBehavior: cloudfront.CacheCookieBehavior.none(),
1781
+ headerBehavior: cloudfront.CacheHeaderBehavior.none(),
1782
+ queryStringBehavior: cloudfront.CacheQueryStringBehavior.none()
1783
+ })
1784
+ }
1785
+ };
1786
+ }
1787
+ shouldDeployAiEndpoints() {
1788
+ return config6.cloud.ai;
1789
+ }
1790
+ shouldDeployCliSetup() {
1791
+ return config6.cloud.cli;
1792
+ }
1793
+ shouldDeployDocs() {
1794
+ return hasFiles(p3.projectPath("docs"));
1795
+ }
1796
+ additionalBehaviors(scope, props) {
1797
+ let behaviorOptions = {};
1798
+ if (this.shouldDeployApi()) {
1799
+ 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", "_"];
1800
+ keysToRemove.forEach((key) => delete env2[key]);
1801
+ behaviorOptions = this.apiBehaviorOptions(scope, props);
1802
+ }
1803
+ if (this.shouldDeployDocs() && !config6.app.docMode) {
1804
+ behaviorOptions = {
1805
+ ...this.docsBehaviorOptions(props),
1806
+ ...behaviorOptions
1807
+ };
1808
+ }
1809
+ if (this.shouldDeployAiEndpoints()) {
1810
+ behaviorOptions = {
1811
+ ...this.aiBehaviorOptions(scope, props),
1812
+ ...behaviorOptions
1813
+ };
1814
+ }
1815
+ if (this.shouldDeployCliSetup()) {
1816
+ behaviorOptions = {
1817
+ ...this.cliSetupBehaviorOptions(scope, props),
1818
+ ...behaviorOptions
1819
+ };
1820
+ }
1821
+ return behaviorOptions;
1822
+ }
1823
+ setApiCachePolicy(scope) {
1824
+ if (this.apiCachePolicy)
1825
+ return this.apiCachePolicy;
1826
+ this.apiCachePolicy = new cloudfront.CachePolicy(scope, "ApiCachePolicy", {
1827
+ comment: "Stacks API Cache Policy",
1828
+ cachePolicyName: `${this.props.slug}-${this.props.appEnv}-api-cache-policy`,
1829
+ defaultTtl: Duration2.seconds(0),
1830
+ cookieBehavior: cloudfront.CacheCookieBehavior.none(),
1831
+ headerBehavior: cloudfront.CacheHeaderBehavior.allowList("Accept", "x-api-key", "Authorization", "Content-Type"),
1832
+ queryStringBehavior: cloudfront.CacheQueryStringBehavior.none()
1833
+ });
1834
+ return this.apiCachePolicy;
1835
+ }
1836
+ }
1837
+
1838
+ // src/cloud/cli.ts
1839
+ import {Duration as Duration3, CfnOutput as Output3, aws_lambda as lambda2} from "aws-cdk-lib";
1840
+
1841
+ class CliStack {
1842
+ cliSetupUrl;
1843
+ constructor(scope, props) {
1844
+ const cliSetupFunc = new lambda2.Function(scope, "CliSetupFunction", {
1845
+ functionName: `${props.slug}-${props.appEnv}-cli-setup`,
1846
+ description: "Lambda function that triggers setup script for a Stacks project",
1847
+ runtime: lambda2.Runtime.NODEJS_20_X,
1848
+ handler: "index.handler",
1849
+ code: lambda2.Code.fromAsset("src/cloud/lambda/cli-setup"),
1850
+ timeout: Duration3.seconds(30)
1851
+ });
1852
+ this.cliSetupUrl = new lambda2.FunctionUrl(scope, "CliSetupFunctionUrl", {
1853
+ function: cliSetupFunc,
1854
+ authType: lambda2.FunctionUrlAuthType.NONE,
1855
+ cors: {
1856
+ allowedOrigins: ["*"]
1857
+ }
1858
+ });
1859
+ new Output3(scope, "CliSetupVanityUrl", {
1860
+ value: `${this.cliSetupUrl.url}cli-setup`
1861
+ });
1862
+ new Output3(scope, "CliSetupUrl", {
1863
+ value: `https://${props.domain}/install`,
1864
+ description: "URL to trigger the CLI setup function"
1865
+ });
1866
+ }
1867
+ }
1868
+
1869
+ // src/cloud/dns.ts
1870
+ import {RemovalPolicy, aws_route53 as route532, aws_s3 as s3, aws_route53_targets as targets2} from "aws-cdk-lib";
1871
+
1872
+ class DnsStack {
1873
+ zone;
1874
+ constructor(scope, props) {
1875
+ this.zone = route532.PublicHostedZone.fromLookup(scope, "AppUrlHostedZone", {
1876
+ domainName: props.domain
1877
+ });
1878
+ const wwwBucket = new s3.Bucket(scope, "WwwBucket", {
1879
+ bucketName: `www.${props.domain}`,
1880
+ websiteRedirect: {
1881
+ hostName: props.domain,
1882
+ protocol: s3.RedirectProtocol.HTTPS
1883
+ },
1884
+ removalPolicy: RemovalPolicy.DESTROY,
1885
+ autoDeleteObjects: true
1886
+ });
1887
+ new route532.ARecord(scope, "WwwAliasRecord", {
1888
+ recordName: `www.${props.domain}`,
1889
+ zone: this.zone,
1890
+ target: route532.RecordTarget.fromAlias(new targets2.BucketWebsiteTarget(wwwBucket))
1891
+ });
1892
+ }
1893
+ }
1894
+
1895
+ // src/cloud/docs.ts
1896
+ import {AssetHashType, CfnOutput as Output4, RemovalPolicy as RemovalPolicy2, aws_lambda as lambda3} from "aws-cdk-lib";
1897
+ import {config as config8} from "@stacksjs/config";
1898
+ import {path as p4} from "@stacksjs/path";
1899
+ import {originRequestFunctionHash} from "@stacksjs/utils";
1900
+
1901
+ class DocsStack {
1902
+ originRequestFunction;
1903
+ constructor(scope, props) {
1904
+ const docsPrefix = config8.app.docMode ? "" : config8.docs.base;
1905
+ this.originRequestFunction = new lambda3.Function(scope, "OriginRequestFunction", {
1906
+ functionName: `${props.slug}-${props.appEnv}-origin-request-${props.timestamp}`,
1907
+ description: "The Stacks Origin Request function that prettifies URLs",
1908
+ runtime: lambda3.Runtime.NODEJS_18_X,
1909
+ handler: "dist/origin-request.handler",
1910
+ code: lambda3.Code.fromAsset(p4.corePath("cloud/dist.zip"), {
1911
+ assetHash: originRequestFunctionHash,
1912
+ assetHashType: AssetHashType.CUSTOM
1913
+ })
1914
+ });
1915
+ const cfnOriginRequestFunction = this.originRequestFunction.node.defaultChild;
1916
+ cfnOriginRequestFunction.applyRemovalPolicy(RemovalPolicy2.RETAIN);
1917
+ if (!config8.app.docMode && exports_storage.hasFiles(p4.projectPath("docs"))) {
1918
+ new Output4(scope, "DocsUrl", {
1919
+ value: `https://${props.domain}/${docsPrefix}`,
1920
+ description: "The URL of the deployed documentation"
1921
+ });
1922
+ }
1923
+ }
1924
+ }
1925
+
1926
+ // src/cloud/storage.ts
1927
+ import {RemovalPolicy as RemovalPolicy3, Tags, aws_backup as backup, aws_iam as iam2, aws_s3 as s32} from "aws-cdk-lib";
1928
+
1929
+ class StorageStack {
1930
+ publicBucket;
1931
+ privateBucket;
1932
+ logBucket;
1933
+ bucketPrefix;
1934
+ vault;
1935
+ backupPlan;
1936
+ backupRole;
1937
+ constructor(scope, props) {
1938
+ this.bucketPrefix = `${props.slug}-${props.appEnv}`;
1939
+ this.publicBucket = new s32.Bucket(scope, "PublicBucket", {
1940
+ bucketName: `${this.bucketPrefix}-public-${props.timestamp}`,
1941
+ versioned: true,
1942
+ autoDeleteObjects: true,
1943
+ removalPolicy: RemovalPolicy3.DESTROY,
1944
+ encryption: s32.BucketEncryption.S3_MANAGED
1945
+ });
1946
+ Tags.of(this.publicBucket).add("daily-backup", "true");
1947
+ this.privateBucket = new s32.Bucket(scope, "PrivateBucket", {
1948
+ bucketName: `${this.bucketPrefix}-private-${props.timestamp}`,
1949
+ versioned: true,
1950
+ removalPolicy: RemovalPolicy3.DESTROY,
1951
+ autoDeleteObjects: true,
1952
+ encryption: s32.BucketEncryption.S3_MANAGED,
1953
+ enforceSSL: true,
1954
+ publicReadAccess: false,
1955
+ blockPublicAccess: {
1956
+ blockPublicAcls: true,
1957
+ blockPublicPolicy: true,
1958
+ ignorePublicAcls: true,
1959
+ restrictPublicBuckets: true
1960
+ }
1961
+ });
1962
+ Tags.of(this.privateBucket).add("daily-backup", "true");
1963
+ this.logBucket = new s32.Bucket(scope, "LogsBucket", {
1964
+ bucketName: `${this.bucketPrefix}-logs-${props.timestamp}`,
1965
+ removalPolicy: RemovalPolicy3.RETAIN,
1966
+ blockPublicAccess: new s32.BlockPublicAccess({
1967
+ blockPublicAcls: false,
1968
+ ignorePublicAcls: true,
1969
+ blockPublicPolicy: true,
1970
+ restrictPublicBuckets: true
1971
+ }),
1972
+ objectOwnership: s32.ObjectOwnership.BUCKET_OWNER_PREFERRED
1973
+ });
1974
+ Tags.of(this.logBucket).add("daily-backup", "true");
1975
+ this.backupRole = this.createBackupRole(scope);
1976
+ this.vault = new backup.BackupVault(scope, "BackupVault", {
1977
+ backupVaultName: `${props.slug}-${props.appEnv}-daily-backup-vault`,
1978
+ encryptionKey: props.kmsKey,
1979
+ removalPolicy: RemovalPolicy3.DESTROY
1980
+ });
1981
+ this.backupPlan = backup.BackupPlan.daily35DayRetention(scope, "BackupPlan", this.vault);
1982
+ this.backupPlan.addSelection("Selection", {
1983
+ role: this.backupRole,
1984
+ resources: [backup.BackupResource.fromTag("daily-backup", "true")]
1985
+ });
1986
+ }
1987
+ createBackupRole(scope) {
1988
+ const backupRole = new iam2.Role(scope, "BackupRole", {
1989
+ assumedBy: new iam2.ServicePrincipal("backup.amazonaws.com")
1990
+ });
1991
+ backupRole.addToPolicy(new iam2.PolicyStatement({
1992
+ actions: [
1993
+ "s3:GetInventoryConfiguration",
1994
+ "s3:PutInventoryConfiguration",
1995
+ "s3:ListBucketVersions",
1996
+ "s3:ListBucket",
1997
+ "s3:GetBucketVersioning",
1998
+ "s3:GetBucketNotification",
1999
+ "s3:PutBucketNotification",
2000
+ "s3:GetBucketLocation",
2001
+ "s3:GetBucketTagging"
2002
+ ],
2003
+ resources: ["arn:aws:s3:::*"],
2004
+ sid: "S3BucketBackupPermissions"
2005
+ }));
2006
+ backupRole.addToPolicy(new iam2.PolicyStatement({
2007
+ actions: [
2008
+ "s3:GetObjectAcl",
2009
+ "s3:GetObject",
2010
+ "s3:GetObjectVersionTagging",
2011
+ "s3:GetObjectVersionAcl",
2012
+ "s3:GetObjectTagging",
2013
+ "s3:GetObjectVersion"
2014
+ ],
2015
+ resources: ["arn:aws:s3:::*/*"],
2016
+ sid: "S3ObjectBackupPermissions"
2017
+ }));
2018
+ backupRole.addToPolicy(new iam2.PolicyStatement({
2019
+ actions: ["s3:ListAllMyBuckets"],
2020
+ resources: ["*"],
2021
+ sid: "S3GlobalPermissions"
2022
+ }));
2023
+ backupRole.addToPolicy(new iam2.PolicyStatement({
2024
+ actions: ["kms:Decrypt", "kms:DescribeKey"],
2025
+ resources: ["*"],
2026
+ sid: "KMSBackupPermissions",
2027
+ conditions: {
2028
+ StringLike: {
2029
+ "kms:ViaService": "s3.*.amazonaws.com"
2030
+ }
2031
+ }
2032
+ }));
2033
+ backupRole.addToPolicy(new iam2.PolicyStatement({
2034
+ actions: [
2035
+ "events:DescribeRule",
2036
+ "events:EnableRule",
2037
+ "events:PutRule",
2038
+ "events:DeleteRule",
2039
+ "events:PutTargets",
2040
+ "events:RemoveTargets",
2041
+ "events:ListTargetsByRule",
2042
+ "events:DisableRule"
2043
+ ],
2044
+ resources: ["arn:aws:events:*:*:rule/AwsBackupManagedRule*"],
2045
+ sid: "EventsPermissions"
2046
+ }));
2047
+ backupRole.addToPolicy(new iam2.PolicyStatement({
2048
+ actions: ["cloudwatch:GetMetricData", "events:ListRules"],
2049
+ resources: ["*"],
2050
+ sid: "EventsMetricsGlobalPermissions"
2051
+ }));
2052
+ return backupRole;
2053
+ }
2054
+ }
2055
+
2056
+ // src/cloud/security.ts
2057
+ import {config as config10} from "@stacksjs/config";
2058
+ import {Duration as Duration4, RemovalPolicy as RemovalPolicy4, Tags as Tags2, aws_certificatemanager as acm, aws_kms as kms, aws_wafv2 as wafv2} from "aws-cdk-lib";
2059
+
2060
+ class SecurityStack {
2061
+ firewall;
2062
+ kmsKey;
2063
+ certificate;
2064
+ constructor(scope, props) {
2065
+ const firewallOptions = config10.cloud.firewall;
2066
+ if (!firewallOptions)
2067
+ throw new Error("No firewall options found in config");
2068
+ const options = {
2069
+ defaultAction: { allow: {} },
2070
+ scope: "CLOUDFRONT",
2071
+ visibilityConfig: {
2072
+ sampledRequestsEnabled: true,
2073
+ cloudWatchMetricsEnabled: true,
2074
+ metricName: "firewallMetric"
2075
+ },
2076
+ rules: this.getFirewallRules()
2077
+ };
2078
+ this.firewall = new wafv2.CfnWebACL(scope, "WebFirewall", options);
2079
+ Tags2.of(this.firewall).add("Name", "waf-cloudfront", { priority: 300 });
2080
+ Tags2.of(this.firewall).add("Purpose", "CloudFront", { priority: 300 });
2081
+ Tags2.of(this.firewall).add("CreatedBy", "CloudFormation", { priority: 300 });
2082
+ this.kmsKey = new kms.Key(scope, "EncryptionKey", {
2083
+ alias: "stacks-encryption-key",
2084
+ description: "KMS key for Stacks Cloud",
2085
+ enableKeyRotation: true,
2086
+ removalPolicy: RemovalPolicy4.DESTROY,
2087
+ pendingWindow: Duration4.days(30)
2088
+ });
2089
+ this.certificate = new acm.Certificate(scope, "Certificate", {
2090
+ domainName: props.domain,
2091
+ validation: acm.CertificateValidation.fromDns(props.zone),
2092
+ subjectAlternativeNames: [`www.${props.domain}`, `api.${props.domain}`]
2093
+ });
2094
+ }
2095
+ getFirewallRules() {
2096
+ const rules = [];
2097
+ const priorities = [];
2098
+ if (config10.security.firewall?.countryCodes?.length) {
2099
+ priorities.push(1);
2100
+ rules.push({
2101
+ name: "CountryRule",
2102
+ priority: priorities.length,
2103
+ statement: {
2104
+ geoMatchStatement: {
2105
+ countryCodes: config10.security.firewall.countryCodes
2106
+ }
2107
+ },
2108
+ action: {
2109
+ block: {}
2110
+ },
2111
+ visibilityConfig: {
2112
+ sampledRequestsEnabled: true,
2113
+ cloudWatchMetricsEnabled: true,
2114
+ metricName: "CountryRule"
2115
+ }
2116
+ });
2117
+ }
2118
+ if (config10.security.firewall?.ipAddresses?.length) {
2119
+ const ipSet = new wafv2.CfnIPSet(this, "IpSet", {
2120
+ name: "IpSet",
2121
+ description: "IP Set",
2122
+ scope: "CLOUDFRONT",
2123
+ addresses: config10.security.firewall.ipAddresses,
2124
+ ipAddressVersion: "IPV4"
2125
+ });
2126
+ priorities.push(1);
2127
+ rules.push({
2128
+ name: "IpAddressRule",
2129
+ priority: priorities.length,
2130
+ statement: {
2131
+ ipSetReferenceStatement: {
2132
+ arn: ipSet.attrArn
2133
+ }
2134
+ },
2135
+ action: {
2136
+ block: {}
2137
+ },
2138
+ visibilityConfig: {
2139
+ sampledRequestsEnabled: true,
2140
+ cloudWatchMetricsEnabled: true,
2141
+ metricName: "IpAddressRule"
2142
+ }
2143
+ });
2144
+ }
2145
+ if (config10.security.firewall?.httpHeaders?.length) {
2146
+ config10.security.firewall.httpHeaders.forEach((header, index) => {
2147
+ priorities.push(1);
2148
+ rules.push({
2149
+ name: `HttpHeaderRule${index}`,
2150
+ priority: priorities.length,
2151
+ statement: {
2152
+ byteMatchStatement: {
2153
+ fieldToMatch: {
2154
+ singleHeader: {
2155
+ name: header
2156
+ }
2157
+ },
2158
+ positionalConstraint: "EXACTLY",
2159
+ searchString: "true",
2160
+ textTransformations: [
2161
+ {
2162
+ priority: index,
2163
+ type: "NONE"
2164
+ }
2165
+ ]
2166
+ }
2167
+ },
2168
+ action: {
2169
+ block: {}
2170
+ },
2171
+ visibilityConfig: {
2172
+ sampledRequestsEnabled: true,
2173
+ cloudWatchMetricsEnabled: true,
2174
+ metricName: `HttpHeaderRule${index}`
2175
+ }
2176
+ });
2177
+ });
2178
+ }
2179
+ return rules;
2180
+ }
2181
+ }
2182
+
2183
+ // src/cloud/deployment.ts
2184
+ import {AssetHashType as AssetHashType2, aws_s3_deployment as s3deploy} from "aws-cdk-lib";
2185
+ import {config as config12} from "@stacksjs/config";
2186
+ import {websiteSourceHash} from "@stacksjs/utils";
2187
+
2188
+ class DeploymentStack {
2189
+ privateSource;
2190
+ docsSource;
2191
+ websiteSource;
2192
+ constructor(scope, props) {
2193
+ this.privateSource = "../../../private";
2194
+ this.docsSource = "../../docs/dist/";
2195
+ this.websiteSource = config12.app.docMode === true ? this.docsSource : "../../views/dist/";
2196
+ new s3deploy.BucketDeployment(scope, "Website", {
2197
+ sources: [s3deploy.Source.asset(this.websiteSource, {
2198
+ assetHash: websiteSourceHash,
2199
+ assetHashType: AssetHashType2.CUSTOM
2200
+ })],
2201
+ destinationBucket: props.publicBucket,
2202
+ distribution: props.cdn,
2203
+ distributionPaths: ["/*"]
2204
+ });
2205
+ new s3deploy.BucketDeployment(scope, "PrivateFiles", {
2206
+ sources: [s3deploy.Source.asset(this.privateSource)],
2207
+ destinationBucket: props.privateBucket
2208
+ });
2209
+ }
2210
+ }
2211
+
2212
+ // src/cloud/jump-box.ts
2213
+ import {CfnOutput as Output5, aws_ec2 as ec2, aws_iam as iam3} from "aws-cdk-lib";
2214
+
2215
+ class JumpBoxStack {
2216
+ jumpBox;
2217
+ constructor(scope, props) {
2218
+ const role = new iam3.Role(scope, "JumpBoxInstanceRole", {
2219
+ assumedBy: new iam3.ServicePrincipal("ec2.amazonaws.com"),
2220
+ managedPolicies: [
2221
+ iam3.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"),
2222
+ iam3.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy")
2223
+ ]
2224
+ });
2225
+ this.jumpBox = new ec2.Instance(scope, "JumpBox", {
2226
+ vpc: props.vpc,
2227
+ instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
2228
+ machineImage: new ec2.AmazonLinuxImage,
2229
+ role,
2230
+ userData: ec2.UserData.custom(`
2231
+ #!/bin/bash
2232
+ yum update -y
2233
+ yum install -y amazon-efs-utils
2234
+ yum install -y git
2235
+ yum install -y https://s3.us-east-1.amazonaws.com/amazon-ssm-us-east-1/latest/linux_amd64/amazon-ssm-agent.rpm
2236
+ mkdir /mnt/efs
2237
+ mount -t efs ${props.fileSystem.fileSystemId}:/ /mnt/efs
2238
+ git clone https://github.com/stacksjs/stacks.git /mnt/efs
2239
+ `)
2240
+ });
2241
+ new Output5(scope, "JumpBoxInstanceId", {
2242
+ value: this.jumpBox.instanceId,
2243
+ description: "The ID of the EC2 instance that can be used to SSH into the Stacks Cloud."
2244
+ });
2245
+ }
2246
+ }
2247
+
2248
+ // src/cloud/file-system.ts
2249
+ import {RemovalPolicy as RemovalPolicy5, aws_efs as efs} from "aws-cdk-lib";
2250
+
2251
+ class FileSystemStack {
2252
+ fileSystem;
2253
+ accessPoint;
2254
+ constructor(scope, props) {
2255
+ this.fileSystem = new efs.FileSystem(scope, "FileSystem", {
2256
+ fileSystemName: `${props.slug}-${props.appEnv}-efs`,
2257
+ vpc: props.vpc,
2258
+ removalPolicy: RemovalPolicy5.DESTROY,
2259
+ lifecyclePolicy: efs.LifecyclePolicy.AFTER_7_DAYS,
2260
+ performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
2261
+ throughputMode: efs.ThroughputMode.BURSTING,
2262
+ enableAutomaticBackups: true,
2263
+ encrypted: true
2264
+ });
2265
+ this.accessPoint = new efs.AccessPoint(scope, "FileSystemAccessPoint", {
2266
+ fileSystem: this.fileSystem,
2267
+ path: "/",
2268
+ posixUser: {
2269
+ uid: "1000",
2270
+ gid: "1000"
2271
+ }
2272
+ });
2273
+ }
2274
+ }
2275
+
2276
+ // src/cloud/network.ts
2277
+ import {aws_ec2 as ec22} from "aws-cdk-lib";
2278
+
2279
+ class NetworkStack {
2280
+ vpc;
2281
+ constructor(scope, props) {
2282
+ this.vpc = new ec22.Vpc(scope, "Network", {
2283
+ vpcName: `${props.slug}-${props.appEnv}-vpc`,
2284
+ ipAddresses: ec22.IpAddresses.cidr("10.0.0.0/16"),
2285
+ maxAzs: 3,
2286
+ natGateways: 0,
2287
+ subnetConfiguration: [
2288
+ {
2289
+ cidrMask: 21,
2290
+ name: `${props.slug}-${props.appEnv}-public-subnet-1`,
2291
+ subnetType: ec22.SubnetType.PUBLIC
2292
+ },
2293
+ {
2294
+ cidrMask: 21,
2295
+ name: `${props.slug}-${props.appEnv}-public-subnet-2`,
2296
+ subnetType: ec22.SubnetType.PUBLIC
2297
+ },
2298
+ {
2299
+ cidrMask: 21,
2300
+ name: `${props.slug}-${props.appEnv}-public-subnet-3`,
2301
+ subnetType: ec22.SubnetType.PUBLIC
2302
+ },
2303
+ {
2304
+ cidrMask: 21,
2305
+ name: `${props.slug}-${props.appEnv}-private-subnet-1`,
2306
+ subnetType: ec22.SubnetType.PRIVATE_ISOLATED
2307
+ },
2308
+ {
2309
+ cidrMask: 21,
2310
+ name: `${props.slug}-${props.appEnv}-private-subnet-2`,
2311
+ subnetType: ec22.SubnetType.PRIVATE_ISOLATED
2312
+ },
2313
+ {
2314
+ cidrMask: 21,
2315
+ name: `${props.slug}-${props.appEnv}-private-subnet-3`,
2316
+ subnetType: ec22.SubnetType.PRIVATE_ISOLATED
2317
+ }
2318
+ ]
2319
+ });
2320
+ }
2321
+ }
2322
+
2323
+ // src/cloud/redirects.ts
2324
+ import {config as config14} from "@stacksjs/config";
2325
+ import {RemovalPolicy as RemovalPolicy6, aws_route53 as route533, aws_s3 as s33} from "aws-cdk-lib";
2326
+
2327
+ class RedirectsStack {
2328
+ redirectZones = [];
2329
+ constructor(scope, props) {
2330
+ config14.dns.redirects?.forEach((redirect) => {
2331
+ const slug2 = redirect.split(".").map((part, index) => index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)).join("");
2332
+ const hostedZone = route533.HostedZone.fromLookup(scope, "HostedZone", { domainName: redirect });
2333
+ const redirectBucket = new s33.Bucket(scope, `RedirectBucket${slug2}`, {
2334
+ bucketName: `${redirect}-redirect`,
2335
+ websiteRedirect: {
2336
+ hostName: props.domain,
2337
+ protocol: s33.RedirectProtocol.HTTPS
2338
+ },
2339
+ removalPolicy: RemovalPolicy6.DESTROY,
2340
+ autoDeleteObjects: true
2341
+ });
2342
+ new route533.CnameRecord(scope, `RedirectRecord${slug2}`, {
2343
+ zone: hostedZone,
2344
+ recordName: "redirect",
2345
+ domainName: redirectBucket.bucketWebsiteDomainName
2346
+ });
2347
+ });
2348
+ config14.dns.redirects?.forEach((redirect) => {
2349
+ const slug2 = redirect.split(".").map((part, index) => index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)).join("");
2350
+ const hostedZone = route533.HostedZone.fromLookup(scope, `RedirectHostedZone${slug2}`, { domainName: redirect });
2351
+ this.redirectZones.push(hostedZone);
2352
+ });
2353
+ }
2354
+ }
2355
+
2356
+ // src/cloud/email.ts
2357
+ import {Duration as Duration5, RemovalPolicy as RemovalPolicy7, Stack, Tags as Tags3, aws_iam as iam4, aws_lambda as lambda4, aws_route53 as route534, aws_s3 as s34, aws_s3_notifications as s3n, aws_ses as ses} from "aws-cdk-lib";
2358
+ import {config as config16} from "@stacksjs/config";
2359
+
2360
+ class EmailStack {
2361
+ emailBucket;
2362
+ constructor(scope, props) {
2363
+ const bucketPrefix = `${props.slug}-${props.appEnv}`;
2364
+ this.emailBucket = new s34.Bucket(scope, "EmailBucket", {
2365
+ bucketName: `${bucketPrefix}-email-${props.timestamp}`,
2366
+ versioned: true,
2367
+ removalPolicy: RemovalPolicy7.DESTROY,
2368
+ autoDeleteObjects: true,
2369
+ encryption: s34.BucketEncryption.S3_MANAGED,
2370
+ lifecycleRules: [
2371
+ {
2372
+ id: "24h",
2373
+ enabled: true,
2374
+ expiration: Duration5.days(1),
2375
+ noncurrentVersionExpiration: Duration5.days(1),
2376
+ prefix: "today/"
2377
+ },
2378
+ {
2379
+ id: "Intelligent transition for Inbox",
2380
+ enabled: true,
2381
+ prefix: "inbox/",
2382
+ transitions: [
2383
+ {
2384
+ storageClass: s34.StorageClass.INTELLIGENT_TIERING,
2385
+ transitionAfter: Duration5.days(0)
2386
+ }
2387
+ ]
2388
+ },
2389
+ {
2390
+ id: "Intelligent transition for Sent",
2391
+ enabled: true,
2392
+ prefix: "sent/",
2393
+ transitions: [
2394
+ {
2395
+ storageClass: s34.StorageClass.INTELLIGENT_TIERING,
2396
+ transitionAfter: Duration5.days(0)
2397
+ }
2398
+ ]
2399
+ }
2400
+ ]
2401
+ });
2402
+ Tags3.of(this.emailBucket).add("daily-backup", "true");
2403
+ const sesPrincipal = new iam4.ServicePrincipal("ses.amazonaws.com");
2404
+ const ruleSetName = `${props.slug}-${props.appEnv}-email-receipt-rule-set`;
2405
+ const receiptRuleName = `${props.slug}-${props.appEnv}-email-receipt-rule`;
2406
+ const ruleSet = new ses.CfnReceiptRuleSet(scope, "SESReceiptRuleSet", {
2407
+ ruleSetName
2408
+ });
2409
+ this.emailBucket.addToResourcePolicy(new iam4.PolicyStatement({
2410
+ sid: "AllowSESPuts",
2411
+ effect: iam4.Effect.ALLOW,
2412
+ principals: [sesPrincipal],
2413
+ actions: [
2414
+ "s3:PutObject"
2415
+ ],
2416
+ resources: [
2417
+ `${this.emailBucket.bucketArn}/*`
2418
+ ],
2419
+ conditions: {
2420
+ StringEquals: {
2421
+ "aws:SourceAccount": Stack.of(scope).account
2422
+ },
2423
+ ArnLike: {
2424
+ "aws:SourceArn": `arn:aws:ses:${Stack.of(scope).region}:${Stack.of(scope).account}:receipt-rule-set/${ruleSetName}:receipt-rule/${receiptRuleName}`
2425
+ }
2426
+ }
2427
+ }));
2428
+ const receiptRule = new ses.CfnReceiptRule(scope, "SESReceiptRule", {
2429
+ ruleSetName: ruleSet.ref,
2430
+ rule: {
2431
+ name: receiptRuleName,
2432
+ enabled: true,
2433
+ actions: [
2434
+ {
2435
+ s3Action: {
2436
+ bucketName: this.emailBucket.bucketName,
2437
+ objectKeyPrefix: "tmp/email_in/"
2438
+ }
2439
+ }
2440
+ ],
2441
+ recipients: config16.email.mailboxes || [],
2442
+ scanEnabled: config16.email.server?.scan || true,
2443
+ tlsPolicy: "Require"
2444
+ }
2445
+ });
2446
+ receiptRule.node.addDependency(this.emailBucket);
2447
+ const iamGroup = new iam4.Group(scope, "IAMGroup", {
2448
+ groupName: `${props.slug}-${props.appEnv}-email-management-s3-group`
2449
+ });
2450
+ const listBucketsPolicyStatement = new iam4.PolicyStatement({
2451
+ effect: iam4.Effect.ALLOW,
2452
+ actions: ["s3:ListAllMyBuckets"],
2453
+ resources: ["*"]
2454
+ });
2455
+ const policyStatement = new iam4.PolicyStatement({
2456
+ effect: iam4.Effect.ALLOW,
2457
+ actions: [
2458
+ "s3:ListBucket",
2459
+ "s3:GetObject",
2460
+ "s3:PutObject",
2461
+ "s3:DeleteObject",
2462
+ "s3:GetObjectAcl",
2463
+ "s3:GetObjectVersionAcl",
2464
+ "s3:PutObjectAcl",
2465
+ "s3:PutObjectVersionAcl"
2466
+ ],
2467
+ resources: [
2468
+ this.emailBucket.bucketArn,
2469
+ `${this.emailBucket.bucketArn}/*`
2470
+ ]
2471
+ });
2472
+ const policy = new iam4.Policy(scope, "EmailAccessPolicy", {
2473
+ policyName: `${props.slug}-${props.appEnv}-email-management-s3-policy`,
2474
+ statements: [policyStatement, listBucketsPolicyStatement]
2475
+ });
2476
+ iamGroup.attachInlinePolicy(policy);
2477
+ const sesIdentity = new ses.CfnEmailIdentity(scope, "DomainIdentity", {
2478
+ emailIdentity: props.domain,
2479
+ dkimSigningAttributes: {
2480
+ nextSigningKeyLength: "RSA_2048_BIT"
2481
+ },
2482
+ dkimAttributes: {
2483
+ signingEnabled: true
2484
+ },
2485
+ mailFromAttributes: {
2486
+ behaviorOnMxFailure: "USE_DEFAULT_VALUE",
2487
+ mailFromDomain: `mail.${props.domain}`
2488
+ },
2489
+ feedbackAttributes: {
2490
+ emailForwardingEnabled: true
2491
+ }
2492
+ });
2493
+ new route534.CfnRecordSet(scope, "DkimRecord1", {
2494
+ hostedZoneName: `${props.zone.zoneName}.`,
2495
+ name: sesIdentity.attrDkimDnsTokenName1,
2496
+ type: "CNAME",
2497
+ resourceRecords: [sesIdentity.attrDkimDnsTokenValue1],
2498
+ ttl: "1800"
2499
+ });
2500
+ new route534.CfnRecordSet(scope, "DkimRecord2", {
2501
+ hostedZoneName: `${props.zone.zoneName}.`,
2502
+ name: sesIdentity.attrDkimDnsTokenName2,
2503
+ type: "CNAME",
2504
+ resourceRecords: [sesIdentity.attrDkimDnsTokenValue2],
2505
+ ttl: "1800"
2506
+ });
2507
+ new route534.CfnRecordSet(scope, "DkimRecord3", {
2508
+ hostedZoneName: `${props.zone.zoneName}.`,
2509
+ name: sesIdentity.attrDkimDnsTokenName3,
2510
+ type: "CNAME",
2511
+ resourceRecords: [sesIdentity.attrDkimDnsTokenValue3],
2512
+ ttl: "1800"
2513
+ });
2514
+ new route534.MxRecord(scope, "MxRecord", {
2515
+ zone: props.zone,
2516
+ recordName: "mail",
2517
+ values: [{
2518
+ priority: 10,
2519
+ hostName: "feedback-smtp.us-east-1.amazonses.com"
2520
+ }]
2521
+ });
2522
+ new route534.TxtRecord(scope, "TxtSpfRecord", {
2523
+ zone: props.zone,
2524
+ recordName: "mail",
2525
+ values: ["v=spf1 include:amazonses.com ~all"]
2526
+ });
2527
+ new route534.TxtRecord(scope, "TxtDmarcRecord", {
2528
+ zone: props.zone,
2529
+ recordName: "_dmarc",
2530
+ values: [`v=DMARC1;p=quarantine;pct=25;rua=mailto:dmarcreports@${props.domain}`]
2531
+ });
2532
+ const lambdaEmailOutboundRole = new iam4.Role(scope, "LambdaEmailOutboundRole", {
2533
+ roleName: `${props.slug}-${props.appEnv}-email-outbound`,
2534
+ assumedBy: new iam4.ServicePrincipal("lambda.amazonaws.com"),
2535
+ managedPolicies: [
2536
+ iam4.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")
2537
+ ]
2538
+ });
2539
+ const lambdaEmailOutbound = new lambda4.Function(scope, "LambdaEmailOutbound", {
2540
+ functionName: `${props.slug}-${props.appEnv}-email-outbound`,
2541
+ description: "Take the JSON and convert it in to an raw email.",
2542
+ code: lambda4.Code.fromInline("exports.handler = async (event) => {return true;};"),
2543
+ handler: "index.handler",
2544
+ memorySize: 256,
2545
+ runtime: lambda4.Runtime.NODEJS_18_X,
2546
+ timeout: Duration5.seconds(60),
2547
+ environment: {
2548
+ BUCKET: this.emailBucket.bucketName
2549
+ },
2550
+ role: lambdaEmailOutboundRole
2551
+ });
2552
+ lambdaEmailOutboundRole.addToPolicy(policyStatement);
2553
+ const sesPolicyStatement = new iam4.PolicyStatement({
2554
+ effect: iam4.Effect.ALLOW,
2555
+ actions: ["ses:SendRawEmail"],
2556
+ resources: ["*"]
2557
+ });
2558
+ lambdaEmailOutboundRole.addToPolicy(sesPolicyStatement);
2559
+ const lambdaEmailInboundRole = new iam4.Role(scope, "LambdaEmailInboundRole", {
2560
+ roleName: `${props.slug}-${props.appEnv}-email-inbound`,
2561
+ assumedBy: new iam4.ServicePrincipal("lambda.amazonaws.com"),
2562
+ managedPolicies: [
2563
+ iam4.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")
2564
+ ]
2565
+ });
2566
+ const lambdaEmailInbound = new lambda4.Function(scope, "LambdaEmailInbound", {
2567
+ functionName: `${props.slug}-${props.appEnv}-email-inbound`,
2568
+ description: "This Lambda organizes all the incoming emails based on the From and To field.",
2569
+ code: lambda4.Code.fromInline("exports.handler = async (event) => {return true;};"),
2570
+ handler: "index.handler",
2571
+ memorySize: 256,
2572
+ role: lambdaEmailInboundRole,
2573
+ runtime: lambda4.Runtime.NODEJS_18_X,
2574
+ timeout: Duration5.seconds(60),
2575
+ environment: {
2576
+ BUCKET: this.emailBucket.bucketName
2577
+ }
2578
+ });
2579
+ new lambda4.CfnPermission(scope, "S3InboundPermission", {
2580
+ action: "lambda:InvokeFunction",
2581
+ functionName: lambdaEmailInbound.functionName,
2582
+ principal: "s3.amazonaws.com"
2583
+ });
2584
+ const inboundS3PolicyStatement = new iam4.PolicyStatement({
2585
+ effect: iam4.Effect.ALLOW,
2586
+ actions: ["s3:*"],
2587
+ resources: [
2588
+ this.emailBucket.bucketArn,
2589
+ `${this.emailBucket.bucketArn}/*`
2590
+ ]
2591
+ });
2592
+ lambdaEmailInboundRole.addToPolicy(inboundS3PolicyStatement);
2593
+ const sesInboundPolicyStatement = new iam4.PolicyStatement({
2594
+ effect: iam4.Effect.ALLOW,
2595
+ actions: ["ses:ListIdentities"],
2596
+ resources: ["*"]
2597
+ });
2598
+ lambdaEmailInboundRole.addToPolicy(sesInboundPolicyStatement);
2599
+ const lambdaEmailConverterRole = new iam4.Role(scope, "LambdaEmailConverterRole", {
2600
+ roleName: `${props.slug}-${props.appEnv}-email-converter`,
2601
+ assumedBy: new iam4.ServicePrincipal("lambda.amazonaws.com"),
2602
+ managedPolicies: [
2603
+ iam4.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")
2604
+ ]
2605
+ });
2606
+ const lambdaEmailConverter = new lambda4.Function(scope, "LambdaEmailConverter", {
2607
+ functionName: `${props.slug}-${props.appEnv}-email-converter`,
2608
+ description: "This Lambda converts raw emails files in to HTML and text.",
2609
+ code: lambda4.Code.fromInline('exports.handler = async (event) => {console.log("hello world email converter");return true;};'),
2610
+ handler: "index.handler",
2611
+ memorySize: 256,
2612
+ role: lambdaEmailConverterRole,
2613
+ runtime: lambda4.Runtime.NODEJS_18_X,
2614
+ timeout: Duration5.seconds(60),
2615
+ environment: {
2616
+ BUCKET: this.emailBucket.bucketName
2617
+ }
2618
+ });
2619
+ const converterS3PolicyStatement = new iam4.PolicyStatement({
2620
+ effect: iam4.Effect.ALLOW,
2621
+ actions: ["s3:*"],
2622
+ resources: [
2623
+ this.emailBucket.bucketArn,
2624
+ `${this.emailBucket.bucketArn}/*`
2625
+ ]
2626
+ });
2627
+ new lambda4.CfnPermission(scope, "S3ConverterPermission", {
2628
+ action: "lambda:InvokeFunction",
2629
+ functionName: lambdaEmailConverter.functionName,
2630
+ principal: "s3.amazonaws.com"
2631
+ });
2632
+ lambdaEmailConverterRole.addToPolicy(converterS3PolicyStatement);
2633
+ this.emailBucket.addEventNotification(s34.EventType.OBJECT_CREATED_PUT, new s3n.LambdaDestination(lambdaEmailInbound), { prefix: "tmp/email_in/" });
2634
+ this.emailBucket.addEventNotification(s34.EventType.OBJECT_CREATED_PUT, new s3n.LambdaDestination(lambdaEmailOutbound), { prefix: "tmp/email_out/json/" });
2635
+ this.emailBucket.addEventNotification(s34.EventType.OBJECT_CREATED_COPY, new s3n.LambdaDestination(lambdaEmailConverter), { prefix: "sent/" });
2636
+ this.emailBucket.addEventNotification(s34.EventType.OBJECT_CREATED_COPY, new s3n.LambdaDestination(lambdaEmailConverter), { prefix: "inbox/" });
2637
+ this.emailBucket.addEventNotification(s34.EventType.OBJECT_CREATED_COPY, new s3n.LambdaDestination(lambdaEmailConverter), { prefix: "today/" });
2638
+ }
2639
+ }
2640
+
2641
+ // src/cloud/permissions.ts
2642
+ import {SecretValue, aws_iam as iam5} from "aws-cdk-lib";
2643
+ import {config as config18} from "@stacksjs/config";
2644
+ import {string} from "@stacksjs/strings";
2645
+ import {env as env4} from "@stacksjs/env";
2646
+
2647
+ class PermissionsStack {
2648
+ constructor(scope) {
2649
+ const teamName = config18.team.name;
2650
+ const users = config18.team.members;
2651
+ const password = env4.AWS_DEFAULT_PASSWORD || string.random();
2652
+ for (const name in users) {
2653
+ const id = `User${string.pascalCase(teamName)}${string.pascalCase(name)}`;
2654
+ const userName = string.slug(`${teamName}-${name}`);
2655
+ const user = new iam5.User(scope, id, {
2656
+ userName,
2657
+ password: SecretValue.unsafePlainText(password),
2658
+ passwordResetRequired: true
2659
+ });
2660
+ user.addManagedPolicy(iam5.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess"));
2661
+ }
2662
+ }
2663
+ }
2664
+
2665
+ // src/cloud/compute.ts
2666
+ import {Duration as Duration6, CfnOutput as Output6, aws_lambda as lambda5, aws_logs as logs, aws_secretsmanager as secretsmanager} from "aws-cdk-lib";
2667
+ import {path as p5} from "@stacksjs/path";
2668
+ import {env as env6} from "@stacksjs/env";
2669
+
2670
+ class ComputeStack {
2671
+ apiServer;
2672
+ apiServerUrl;
2673
+ constructor(scope, props) {
2674
+ const vpc = props.vpc;
2675
+ const fileSystem = props.fileSystem;
2676
+ if (!fileSystem)
2677
+ throw new Error("The file system is missing. Please make sure it was created properly.");
2678
+ this.apiServer = new lambda5.Function(scope, "WebServer", {
2679
+ functionName: `${props.slug}-${props.appEnv}-web-server`,
2680
+ description: "The web server for the Stacks application",
2681
+ code: lambda5.Code.fromAssetImage(p5.frameworkPath("server")),
2682
+ handler: lambda5.Handler.FROM_IMAGE,
2683
+ runtime: lambda5.Runtime.FROM_IMAGE,
2684
+ vpc,
2685
+ memorySize: 512,
2686
+ timeout: Duration6.minutes(5),
2687
+ logRetention: logs.RetentionDays.ONE_WEEK,
2688
+ architecture: lambda5.Architecture.ARM_64
2689
+ });
2690
+ 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", "_"];
2691
+ keysToRemove.forEach((key) => delete env6[key]);
2692
+ const secrets = new secretsmanager.Secret(scope, "StacksSecrets", {
2693
+ secretName: `${props.slug}-${props.appEnv}-secrets`,
2694
+ description: "Secrets for the Stacks application",
2695
+ generateSecretString: {
2696
+ secretStringTemplate: JSON.stringify(env6),
2697
+ generateStringKey: Object.keys(env6).join(",").length.toString()
2698
+ }
2699
+ });
2700
+ secrets.grantRead(this.apiServer);
2701
+ this.apiServer.addEnvironment("SECRETS_ARN", secrets.secretArn);
2702
+ this.apiServerUrl = new lambda5.FunctionUrl(scope, "StacksServerUrl", {
2703
+ function: this.apiServer,
2704
+ authType: lambda5.FunctionUrlAuthType.NONE,
2705
+ cors: {
2706
+ allowedOrigins: ["*"]
2707
+ }
2708
+ });
2709
+ const apiPrefix = "api";
2710
+ new Output6(scope, "ApiUrl", {
2711
+ value: `https://${props.domain}/${apiPrefix}`,
2712
+ description: "The URL of the deployed application"
2713
+ });
2714
+ new Output6(scope, "ApiVanityUrl", {
2715
+ value: this.apiServerUrl.url,
2716
+ description: "The Vanity URL of the deployed application"
2717
+ });
2718
+ }
2719
+ }
2720
+
2721
+ // src/cloud/index.ts
2722
+ class Cloud extends Stack2 {
2723
+ constructor(scope, id, props) {
2724
+ super(scope, id, props);
2725
+ const dns2 = new DnsStack(this, props);
2726
+ const security2 = new SecurityStack(this, {
2727
+ ...props,
2728
+ zone: dns2.zone
2729
+ });
2730
+ const storage4 = new StorageStack(this, {
2731
+ ...props,
2732
+ kmsKey: security2.kmsKey
2733
+ });
2734
+ const network2 = new NetworkStack(this, props);
2735
+ const fileSystem = new FileSystemStack(this, {
2736
+ ...props,
2737
+ vpc: network2.vpc
2738
+ });
2739
+ new JumpBoxStack(this, {
2740
+ ...props,
2741
+ vpc: network2.vpc,
2742
+ fileSystem: fileSystem.fileSystem
2743
+ });
2744
+ const docs2 = new DocsStack(this, props);
2745
+ new EmailStack(this, {
2746
+ ...props,
2747
+ zone: dns2.zone
2748
+ });
2749
+ new RedirectsStack(this, props);
2750
+ new PermissionsStack(this);
2751
+ const api = new ComputeStack(this, {
2752
+ ...props,
2753
+ vpc: network2.vpc,
2754
+ fileSystem: fileSystem.fileSystem,
2755
+ zone: dns2.zone,
2756
+ certificate: security2.certificate
2757
+ });
2758
+ const ai2 = new AiStack(this, props);
2759
+ const cli4 = new CliStack(this, props);
2760
+ const cdn2 = new CdnStack(this, {
2761
+ ...props,
2762
+ publicBucket: storage4.publicBucket,
2763
+ logBucket: storage4.logBucket,
2764
+ certificate: security2.certificate,
2765
+ firewall: security2.firewall,
2766
+ originRequestFunction: docs2.originRequestFunction,
2767
+ zone: dns2.zone,
2768
+ webServer: api.apiServer,
2769
+ webServerUrl: api.apiServerUrl,
2770
+ cliSetupUrl: cli4.cliSetupUrl,
2771
+ askAiUrl: ai2.askAiUrl,
2772
+ summarizeAiUrl: ai2.summarizeAiUrl
2773
+ });
2774
+ new DeploymentStack(this, {
2775
+ ...props,
2776
+ publicBucket: storage4.publicBucket,
2777
+ privateBucket: storage4.privateBucket,
2778
+ cdn: cdn2.distribution
2779
+ });
2780
+ }
2781
+ }
2782
+ // /Users/chrisbreuer/Code/stacks/storage/framework/core/router/dist/index.js
2783
+ import process2 from "process";
2784
+ async function importMiddlewares(directory) {
2785
+ return [directory];
2786
+ }
2787
+ import {extname as extname2} from "path";
2788
+ import {URL} from "url";
2789
+ import {localUrl} from "@stacksjs/config";
2790
+ async function serverResponse(req) {
2791
+ console.log("serverResponse", req);
2792
+ const routesList = await route.getRoutes();
2793
+ const url = new URL(req.url);
2794
+ const foundRoute = routesList.find((route2) => {
2795
+ const pattern = new RegExp(`^${route2.uri.replace(/:\w+/g, "\\w+")}$`);
2796
+ return pattern.test(url.pathname);
2797
+ });
2798
+ if (!foundRoute)
2799
+ return new Response("Not found", { status: 404 });
2800
+ addRouteParamsAndQuery(url, foundRoute);
2801
+ executeMiddleware(foundRoute);
2802
+ return execute(foundRoute, req, { statusCode: foundRoute?.statusCode });
2803
+ }
2804
+ var normalizeWindowsPath = function(input = "") {
2805
+ if (!input) {
2806
+ return input;
2807
+ }
2808
+ return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
2809
+ };
2810
+ var cwd = function() {
2811
+ if (typeof process !== "undefined" && typeof process.cwd === "function") {
2812
+ return process.cwd().replace(/\\/g, "/");
2813
+ }
2814
+ return "/";
2815
+ };
2816
+ var normalizeString = function(path11, allowAboveRoot) {
2817
+ let res = "";
2818
+ let lastSegmentLength = 0;
2819
+ let lastSlash = -1;
2820
+ let dots = 0;
2821
+ let char = null;
2822
+ for (let index = 0;index <= path11.length; ++index) {
2823
+ if (index < path11.length) {
2824
+ char = path11[index];
2825
+ } else if (char === "/") {
2826
+ break;
2827
+ } else {
2828
+ char = "/";
2829
+ }
2830
+ if (char === "/") {
2831
+ if (lastSlash === index - 1 || dots === 1)
2832
+ ;
2833
+ else if (dots === 2) {
2834
+ if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
2835
+ if (res.length > 2) {
2836
+ const lastSlashIndex = res.lastIndexOf("/");
2837
+ if (lastSlashIndex === -1) {
2838
+ res = "";
2839
+ lastSegmentLength = 0;
2840
+ } else {
2841
+ res = res.slice(0, lastSlashIndex);
2842
+ lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
2843
+ }
2844
+ lastSlash = index;
2845
+ dots = 0;
2846
+ continue;
2847
+ } else if (res.length > 0) {
2848
+ res = "";
2849
+ lastSegmentLength = 0;
2850
+ lastSlash = index;
2851
+ dots = 0;
2852
+ continue;
2853
+ }
2854
+ }
2855
+ if (allowAboveRoot) {
2856
+ res += res.length > 0 ? "/.." : "..";
2857
+ lastSegmentLength = 2;
2858
+ }
2859
+ } else {
2860
+ if (res.length > 0) {
2861
+ res += `/${path11.slice(lastSlash + 1, index)}`;
2862
+ } else {
2863
+ res = path11.slice(lastSlash + 1, index);
2864
+ }
2865
+ lastSegmentLength = index - lastSlash - 1;
2866
+ }
2867
+ lastSlash = index;
2868
+ dots = 0;
2869
+ } else if (char === "." && dots !== -1) {
2870
+ ++dots;
2871
+ } else {
2872
+ dots = -1;
2873
+ }
2874
+ }
2875
+ return res;
2876
+ };
2877
+ var _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
2878
+ var _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
2879
+ var resolve = function(...arguments_) {
2880
+ arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));
2881
+ let resolvedPath = "";
2882
+ let resolvedAbsolute = false;
2883
+ for (let index = arguments_.length - 1;index >= -1 && !resolvedAbsolute; index--) {
2884
+ const path11 = index >= 0 ? arguments_[index] : cwd();
2885
+ if (!path11 || path11.length === 0) {
2886
+ continue;
2887
+ }
2888
+ resolvedPath = `${path11}/${resolvedPath}`;
2889
+ resolvedAbsolute = isAbsolute(path11);
2890
+ }
2891
+ resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
2892
+ if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
2893
+ return `/${resolvedPath}`;
2894
+ }
2895
+ return resolvedPath.length > 0 ? resolvedPath : ".";
2896
+ };
2897
+ var isAbsolute = function(p6) {
2898
+ return _IS_ABSOLUTE_RE.test(p6);
2899
+ };
2900
+ var appPath = function(path22) {
2901
+ return projectPath(`app/${path22 || ""}`);
2902
+ };
2903
+ var projectPath = function(filePath = "") {
2904
+ let path22 = process2.cwd();
2905
+ while (path22.includes("storage"))
2906
+ path22 = resolve(path22, "..");
2907
+ return resolve(path22, filePath);
2908
+ };
2909
+ var middlewares = await importMiddlewares(appPath("middleware"));
2910
+
2911
+ class Request {
2912
+ query = {};
2913
+ params = null;
2914
+ addQuery(url) {
2915
+ this.query = Object.fromEntries(url.searchParams);
2916
+ }
2917
+ get(element) {
2918
+ return this.query[element];
2919
+ }
2920
+ all() {
2921
+ return this.query;
2922
+ }
2923
+ has(element) {
2924
+ return element in this.query;
2925
+ }
2926
+ isEmpty() {
2927
+ return Object.keys(this.query).length === 0;
2928
+ }
2929
+ extractParamsFromRoute(routePattern, pathname) {
2930
+ const pattern = new RegExp(`^${routePattern.replace(/:(\w+)/g, (match2, paramName) => `(?<${paramName}>\\w+)`)}$`);
2931
+ const match = pattern.exec(pathname);
2932
+ if (match?.groups)
2933
+ this.params = match?.groups;
2934
+ }
2935
+ getParams(key) {
2936
+ return this.params ? this.params[key] || null : null;
2937
+ }
2938
+ }
2939
+ var request = new Request;
2940
+ var addRouteParamsAndQuery = function(url, route2) {
2941
+ if (!isObjectNotEmpty(url.searchParams))
2942
+ request.addQuery(url);
2943
+ request.extractParamsFromRoute(route2.uri, url.pathname);
2944
+ };
2945
+ var executeMiddleware = function(route2) {
2946
+ const { middleware: middleware2 = null } = route2;
2947
+ if (middleware2 && middlewares && isObjectNotEmpty(middlewares)) {
2948
+ if (isString(middleware2)) {
2949
+ const middlewareItem = middlewares.find((middlewareItem2) => {
2950
+ return middlewareItem2.name === middleware2;
2951
+ });
2952
+ if (middlewareItem)
2953
+ middlewareItem.handle();
2954
+ } else {
2955
+ middleware2.forEach((m) => {
2956
+ const middlewareItem = middlewares.find((middlewareItem2) => {
2957
+ return middlewareItem2.name === m;
2958
+ });
2959
+ if (middlewareItem)
2960
+ middlewareItem.handle();
2961
+ });
2962
+ }
2963
+ }
2964
+ };
2965
+ var execute = function(route2, request3, { statusCode }) {
2966
+ if (!statusCode)
2967
+ statusCode = 200;
2968
+ if (route2?.method === "GET" && (statusCode === 301 || statusCode === 302)) {
2969
+ const callback = String(route2.callback);
2970
+ const response = Response.redirect(callback, statusCode);
2971
+ return noCache(response);
2972
+ }
2973
+ if (route2?.method !== request3.method)
2974
+ return new Response("Method not allowed", { status: 405 });
2975
+ if (isString(route2.callback) && extname2(route2.callback) === ".html") {
2976
+ try {
2977
+ const fileContent = Bun.file(route2.callback);
2978
+ return new Response(fileContent, { headers: { "Content-Type": "text/html" } });
2979
+ } catch (error) {
2980
+ return new Response("Error reading the HTML file", { status: 500 });
2981
+ }
2982
+ }
2983
+ if (isString(route2.callback))
2984
+ return new Response(route2.callback);
2985
+ if (isFunction(route2.callback)) {
2986
+ const result = route2.callback();
2987
+ return new Response(JSON.stringify(result));
2988
+ }
2989
+ if (isObject(route2.callback))
2990
+ return new Response(JSON.stringify(route2.callback));
2991
+ return new Response("Unknown callback type.", { status: 500 });
2992
+ };
2993
+ var noCache = function(response) {
2994
+ response.headers.set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
2995
+ response.headers.set("Pragma", "no-cache");
2996
+ response.headers.set("Expires", "0");
2997
+ return response;
2998
+ };
2999
+ var isString = function(val) {
3000
+ return typeof val === "string";
3001
+ };
3002
+ var isObjectNotEmpty = function(obj) {
3003
+ return Object.keys(obj).length > 0;
3004
+ };
3005
+ var isFunction = function(val) {
3006
+ return typeof val === "function";
3007
+ };
3008
+ var isObject = function(val) {
3009
+ return val !== null && typeof val === "object" && !Array.isArray(val);
3010
+ };
3011
+
3012
+ class Router {
3013
+ routes = [];
3014
+ addRoute(method, uri, callback, statusCode) {
3015
+ const name = uri.replace(/\//g, ".").replace(/:/g, "");
3016
+ const pattern = new RegExp(`^${uri.replace(/:[a-zA-Z]+/g, (_match) => {
3017
+ return "([a-zA-Z0-9-]+)";
3018
+ })}$`);
3019
+ let routeCallback;
3020
+ if (typeof callback === "string" || typeof callback === "object") {
3021
+ routeCallback = () => callback;
3022
+ } else {
3023
+ routeCallback = callback;
3024
+ }
3025
+ this.routes.push({
3026
+ name,
3027
+ method,
3028
+ url: uri,
3029
+ uri,
3030
+ callback: routeCallback,
3031
+ pattern,
3032
+ statusCode,
3033
+ paramNames: []
3034
+ });
3035
+ }
3036
+ get(path42, callback) {
3037
+ this.addRoute("GET", path42, callback, 200);
3038
+ return this;
3039
+ }
3040
+ post(path42, callback) {
3041
+ this.addRoute("POST", path42, callback, 201);
3042
+ return this;
3043
+ }
3044
+ view(path42, callback) {
3045
+ this.addRoute("GET", path42, callback, 200);
3046
+ return this;
3047
+ }
3048
+ redirect(path42, callback, _status) {
3049
+ this.addRoute("GET", path42, callback, 302);
3050
+ return this;
3051
+ }
3052
+ delete(path42, callback) {
3053
+ this.addRoute("DELETE", path42, callback, 204);
3054
+ return this;
3055
+ }
3056
+ patch(path42, callback) {
3057
+ this.addRoute("PATCH", path42, callback, 202);
3058
+ return this;
3059
+ }
3060
+ put(path42, callback) {
3061
+ this.addRoute("PUT", path42, callback, 202);
3062
+ return this;
3063
+ }
3064
+ group(options, callback) {
3065
+ let cb;
3066
+ if (typeof options === "function") {
3067
+ cb = options;
3068
+ options = {};
3069
+ } else {
3070
+ if (!callback)
3071
+ throw new Error("Missing callback function for route group.");
3072
+ cb = callback;
3073
+ }
3074
+ const { prefix = "", middleware: middleware2 = [] } = options;
3075
+ const originalRoutes = this.routes;
3076
+ this.routes = [];
3077
+ cb();
3078
+ this.routes.forEach((r) => {
3079
+ r.uri = `${prefix}${r.uri}`;
3080
+ if (middleware2.length)
3081
+ r.middleware = middleware2;
3082
+ originalRoutes.push(r);
3083
+ return this;
3084
+ });
3085
+ this.routes = originalRoutes;
3086
+ return this;
3087
+ }
3088
+ name(name) {
3089
+ this.routes[this.routes.length - 1].name = name;
3090
+ return this;
3091
+ }
3092
+ middleware(middleware2) {
3093
+ this.routes[this.routes.length - 1].middleware = middleware2;
3094
+ return this;
3095
+ }
3096
+ prefix(prefix) {
3097
+ this.routes[this.routes.length - 1].prefix = prefix;
3098
+ return this;
3099
+ }
3100
+ async getRoutes() {
3101
+ await import(projectPath("routes/api.ts"));
3102
+ return this.routes;
3103
+ }
3104
+ }
3105
+ var route = new Router;
3106
+
3107
+ // src/runtime/server.ts
3108
+ var server_default = {
3109
+ async fetch(request2, server) {
3110
+ console.log("Request", {
3111
+ url: request2.url,
3112
+ method: request2.method,
3113
+ headers: request2.headers.toJSON(),
3114
+ body: request2.body ? await request2.text() : null
3115
+ });
3116
+ if (server.upgrade(request2)) {
3117
+ console.log("WebSocket upgraded");
3118
+ return;
3119
+ }
3120
+ return serverResponse(request2);
3121
+ },
3122
+ websocket: {}
3123
+ };
3124
+ export {
3125
+ server_default as server,
3126
+ purchaseDomain,
3127
+ isFirstDeployment,
3128
+ isFailedState,
3129
+ hasBeenDeployed,
3130
+ getSecurityGroupId,
3131
+ getSecurityGroupFromInstanceId,
3132
+ getOrCreateTimestamp,
3133
+ getJumpBoxSecurityGroupName,
3134
+ getJumpBoxInstanceProfileName,
3135
+ getJumpBoxInstanceId,
3136
+ deleteStacksFunctions,
3137
+ deleteStacksBuckets,
3138
+ deleteParameterStore,
3139
+ deleteLogGroups,
3140
+ deleteJumpBox,
3141
+ deleteIamUsers,
3142
+ deleteEc2Instance,
3143
+ deleteCdkRemnants,
3144
+ addJumpBox,
3145
+ InstanceType,
3146
+ Cloud
3147
+ };