@stacksjs/cloud 0.58.48 → 0.58.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +3 -2
  2. package/src/cloud/ai.ts +94 -0
  3. package/src/cloud/aws-sdk-layer/nodejs/package-lock.json +386 -0
  4. package/src/cloud/aws-sdk-layer/nodejs/package.json +15 -0
  5. package/src/cloud/cache.ts +0 -0
  6. package/src/cloud/cdn.ts +374 -0
  7. package/src/cloud/cli.ts +45 -0
  8. package/src/cloud/compute.ts +80 -0
  9. package/src/cloud/dashboard.ts +85 -0
  10. package/src/cloud/database.ts +0 -0
  11. package/src/cloud/deployment.ts +41 -0
  12. package/src/cloud/dns.ts +34 -0
  13. package/src/cloud/docs.ts +51 -0
  14. package/src/cloud/email.ts +339 -0
  15. package/src/cloud/file-system.ts +35 -0
  16. package/src/cloud/index.ts +99 -0
  17. package/src/cloud/jump-box.ts +48 -0
  18. package/src/cloud/lambda/ask/index.js +35 -0
  19. package/src/cloud/lambda/cli-setup/index.js +67 -0
  20. package/src/cloud/lambda/summarize/index.js +35 -0
  21. package/src/cloud/network.ts +52 -0
  22. package/src/cloud/package/README.md +59 -0
  23. package/src/cloud/package/package.json +63 -0
  24. package/src/cloud/permissions.ts +33 -0
  25. package/src/cloud/queue.ts +0 -0
  26. package/src/cloud/redirects.ts +45 -0
  27. package/src/cloud/router-layer/nodejs/package.json +15 -0
  28. package/src/cloud/search-engine.ts +108 -0
  29. package/src/cloud/security.ts +259 -0
  30. package/src/cloud/stacksjs-router-0.58.48.tgz +0 -0
  31. package/src/cloud/storage.ts +168 -0
  32. package/src/edge/origin-request.ts +49 -0
  33. package/src/helpers.ts +597 -0
  34. package/src/index.ts +3 -0
  35. package/src/runtime/README.md +116 -0
  36. package/src/runtime/bootstrap +3 -0
  37. package/src/runtime/example/lambda.ts +36 -0
  38. package/src/runtime/runtime.ts +830 -0
  39. package/src/runtime/scripts/build-layer.ts +104 -0
  40. package/src/runtime/scripts/publish-layer.ts +110 -0
  41. package/src/runtime/server.ts +39 -0
  42. package/src/types.ts +28 -0
package/src/helpers.ts ADDED
@@ -0,0 +1,597 @@
1
+ import { CloudFormation } from '@aws-sdk/client-cloudformation'
2
+ import type { DescribeLogGroupsCommandOutput } from '@aws-sdk/client-cloudwatch-logs'
3
+ import { CloudWatchLogsClient, DeleteLogGroupCommand, DescribeLogGroupsCommand } from '@aws-sdk/client-cloudwatch-logs'
4
+ import { EC2, _InstanceType as InstanceType } from '@aws-sdk/client-ec2'
5
+ import { DescribeFileSystemsCommand, EFSClient } from '@aws-sdk/client-efs'
6
+ import { IAM } from '@aws-sdk/client-iam'
7
+ import { SSM } from '@aws-sdk/client-ssm'
8
+ import { Lambda } from '@aws-sdk/client-lambda'
9
+ import type { CountryCode } from '@aws-sdk/client-route-53-domains'
10
+ import { ContactType, Route53Domains } from '@aws-sdk/client-route-53-domains'
11
+ import { ListBucketsCommand, S3 } from '@aws-sdk/client-s3'
12
+ import { config } from '@stacksjs/config'
13
+ import { err, handleError, 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
+
19
+ const appEnv = config.app.env === 'local' ? 'dev' : config.app.env
20
+ const cloudName = `stacks-cloud-${appEnv}`
21
+
22
+ export { InstanceType }
23
+
24
+ export async function getSecurityGroupId(securityGroupName: string) {
25
+ const ec2 = new EC2({ region: 'us-east-1' })
26
+ const { SecurityGroups } = await ec2.describeSecurityGroups({
27
+ Filters: [{ Name: 'group-name', Values: [securityGroupName] }],
28
+ })
29
+
30
+ if (!SecurityGroups)
31
+ return err(`Security group ${securityGroupName} not found`)
32
+
33
+ if (SecurityGroups[0])
34
+ return ok(SecurityGroups[0].GroupId)
35
+
36
+ return err(`Security group ${securityGroupName} not found`)
37
+ }
38
+
39
+ export interface PurchaseOptions {
40
+ domain: string
41
+ years: number
42
+ privacy: boolean
43
+ autoRenew: boolean
44
+ adminFirstName: string
45
+ adminLastName: string
46
+ adminOrganization: string
47
+ adminAddressLine1: string
48
+ adminAddressLine2: string
49
+ adminCity: string
50
+ adminState: string
51
+ adminCountry: CountryCode
52
+ adminZip: string
53
+ adminPhone: string
54
+ adminEmail: string
55
+ techFirstName: string
56
+ techLastName: string
57
+ techOrganization: string
58
+ techAddressLine1: string
59
+ techAddressLine2: string
60
+ techCity: string
61
+ techState: string
62
+ techCountry: CountryCode
63
+ techZip: string
64
+ techPhone: string
65
+ techEmail: string
66
+ registrantFirstName: string
67
+ registrantLastName: string
68
+ registrantOrganization: string
69
+ registrantAddressLine1: string
70
+ registrantAddressLine2: string
71
+ registrantCity: string
72
+ registrantState: string
73
+ registrantCountry: CountryCode
74
+ registrantZip: string
75
+ registrantPhone: string
76
+ registrantEmail: string
77
+ privacyAdmin: boolean
78
+ privacyTech: boolean
79
+ privacyRegistrant: boolean
80
+ contactType: ContactType
81
+ verbose: boolean
82
+ }
83
+
84
+ export function purchaseDomain(domain: string, options: PurchaseOptions) {
85
+ const route53domains = new Route53Domains({ region: 'us-east-1' })
86
+ const contactType = options.contactType.toUpperCase() as ContactType
87
+
88
+ const params = {
89
+ DomainName: domain,
90
+ DurationInYears: options.years || 1,
91
+ AutoRenew: options.autoRenew || true,
92
+ AdminContact: {
93
+ FirstName: options.adminFirstName,
94
+ LastName: options.adminLastName,
95
+ ContactType: contactType || ContactType.PERSON,
96
+ OrganizationName: options.adminOrganization,
97
+ AddressLine1: options.adminAddressLine1,
98
+ AddressLine2: options.adminAddressLine2,
99
+ City: options.adminCity,
100
+ State: options.adminState,
101
+ CountryCode: options.adminCountry,
102
+ ZipCode: options.adminZip.toString(),
103
+ PhoneNumber: options.adminPhone.toString().includes('+') ? options.adminPhone.toString() : `+${options.adminPhone.toString()}`,
104
+ Email: options.adminEmail,
105
+ },
106
+ RegistrantContact: {
107
+ FirstName: options.registrantFirstName,
108
+ LastName: options.registrantLastName,
109
+ ContactType: contactType || ContactType.PERSON,
110
+ OrganizationName: options.registrantOrganization,
111
+ AddressLine1: options.registrantAddressLine1,
112
+ AddressLine2: options.registrantAddressLine2,
113
+ City: options.registrantCity,
114
+ State: options.registrantState,
115
+ CountryCode: options.registrantCountry,
116
+ ZipCode: options.registrantZip.toString(),
117
+ PhoneNumber: options.registrantPhone.toString().includes('+') ? options.registrantPhone.toString() : `+${options.registrantPhone.toString()}`,
118
+ Email: options.registrantEmail,
119
+ },
120
+ TechContact: {
121
+ FirstName: options.techFirstName,
122
+ LastName: options.techLastName,
123
+ ContactType: contactType || ContactType.PERSON,
124
+ OrganizationName: options.techOrganization,
125
+ AddressLine1: options.techAddressLine1,
126
+ AddressLine2: options.techAddressLine2,
127
+ City: options.techCity,
128
+ State: options.techState,
129
+ CountryCode: options.techCountry,
130
+ ZipCode: options.techZip.toString(),
131
+ PhoneNumber: options.techPhone.toString().includes('+') ? options.techPhone.toString() : `+${options.techPhone.toString()}`,
132
+ Email: options.techEmail,
133
+ },
134
+ PrivacyProtectAdminContact: options.privacyAdmin || options.privacy || true,
135
+ PrivacyProtectRegistrantContact: options.privacyRegistrant || options.privacy || true,
136
+ PrivacyProtectTechContact: options.privacyTech || options.privacy || true,
137
+ }
138
+
139
+ try {
140
+ return ok(route53domains.registerDomain(params))
141
+ }
142
+ catch (error: any) {
143
+ return err(error)
144
+ }
145
+ }
146
+
147
+ export async function getJumpBoxInstanceId(name?: string) {
148
+ if (!name)
149
+ name = `${cloudName}/JumpBox`
150
+
151
+ const ec2 = new EC2({ region: 'us-east-1' })
152
+ const data = await ec2.describeInstances({
153
+ Filters: [
154
+ {
155
+ Name: 'tag:Name',
156
+ Values: [name],
157
+ },
158
+ ],
159
+ })
160
+
161
+ if (data.Reservations && data.Reservations[0] && data.Reservations[0].Instances && data.Reservations[0].Instances[0])
162
+ return data.Reservations[0].Instances[0].InstanceId
163
+
164
+ return undefined
165
+ }
166
+
167
+ export async function deleteEc2Instance(id: string, stackName?: string) {
168
+ if (!stackName)
169
+ stackName = cloudName
170
+
171
+ if (!id)
172
+ return err(`Instance ${id} not found`)
173
+
174
+ const ec2 = new EC2({ region: 'us-east-1' })
175
+ await ec2.terminateInstances({ InstanceIds: [id] })
176
+
177
+ return ok(`Instance ${id} is being terminated`)
178
+ }
179
+
180
+ export async function deleteJumpBox(stackName?: string) {
181
+ if (!stackName)
182
+ stackName = cloudName
183
+
184
+ const jumpBoxId = await getJumpBoxInstanceId()
185
+
186
+ if (!jumpBoxId)
187
+ return err('Jump-box not found')
188
+
189
+ return await deleteEc2Instance(jumpBoxId, stackName)
190
+ }
191
+
192
+ export async function deleteIamUsers() {
193
+ const iam = new IAM({ region: 'us-east-1' })
194
+ const data = await iam.listUsers({})
195
+ const teamName = slug(config.team.name)
196
+ const users = data.Users?.filter((user) => {
197
+ const userNameLower = user.UserName?.toLowerCase()
198
+ return userNameLower !== 'stacks' && userNameLower !== teamName.toLowerCase() && userNameLower?.includes(teamName.toLowerCase())
199
+ }) || []
200
+
201
+ if (!users || users.length === 0)
202
+ return ok(`No Stacks IAM users found for team ${teamName}`)
203
+
204
+ const promises = users.map(async (user) => {
205
+ const userName = user.UserName || ''
206
+ log.info(`Deleting IAM user: ${userName}`)
207
+
208
+ // Get the list of policies attached to the user
209
+ const policies = await iam.listAttachedUserPolicies({ UserName: userName })
210
+
211
+ // Detach each policy
212
+ await Promise.all(policies.AttachedPolicies?.map(policy =>
213
+ iam.detachUserPolicy({ UserName: userName, PolicyArn: policy.PolicyArn || '' }),
214
+ ) || [])
215
+
216
+ // Get the list of access keys for the user
217
+ const accessKeys = await iam.listAccessKeys({ UserName: userName })
218
+
219
+ // Delete each access key
220
+ await Promise.all(accessKeys.AccessKeyMetadata?.map(key =>
221
+ iam.deleteAccessKey({ UserName: userName, AccessKeyId: key.AccessKeyId || '' }),
222
+ ) || [])
223
+
224
+ // Now delete the user
225
+ return iam.deleteUser({ UserName: userName })
226
+ })
227
+
228
+ await Promise.all(promises).catch((error: Error) => {
229
+ console.error(`Error deleting user: ${error}`)
230
+ return err(handleError('Error deleting Stacks IAM users', error))
231
+ })
232
+
233
+ return ok(`Stacks IAM users deleted for team ${teamName}`)
234
+ }
235
+
236
+ export async function deleteStacksBuckets() {
237
+ try {
238
+ const s3 = new S3({ region: 'us-east-1' })
239
+ const data = await s3.listBuckets({})
240
+ const stacksBuckets = data.Buckets?.filter(bucket => bucket.Name?.includes('stacks'))
241
+
242
+ if (!stacksBuckets)
243
+ return err('No stacks buckets found')
244
+
245
+ const promises = stacksBuckets.map(async (bucket) => {
246
+ const bucketName = bucket.Name || ''
247
+
248
+ // Delete the bucket
249
+ log.info(`Deleting bucket ${bucketName}...`)
250
+
251
+ // List all objects in the bucket
252
+ const objects = await s3.listObjectsV2({ Bucket: bucketName })
253
+ log.info(`Finished listing bucket ${bucketName} objects`)
254
+ // Delete all objects
255
+ if (objects.Contents) {
256
+ log.info('Deleting bucket objects...')
257
+ await Promise.all(objects.Contents.map(object =>
258
+ s3.deleteObject({ Bucket: bucketName, Key: object.Key || '' }).catch(error => handleError(error)),
259
+ ))
260
+ log.info(`Finished deleting objects from bucket ${bucketName}`)
261
+ }
262
+
263
+ log.info(`Deleting bucket ${bucketName} versions...`)
264
+ try {
265
+ const versions = await s3.listObjectVersions({ Bucket: bucketName })
266
+
267
+ if (versions.Versions) {
268
+ await Promise.all(versions.Versions.map(version =>
269
+ s3.deleteObject({ Bucket: bucketName, Key: version.Key || '', VersionId: version.VersionId }),
270
+ )).catch(error => handleError(error))
271
+ log.info(`Finished deleting versions from bucket ${bucketName}`)
272
+ }
273
+
274
+ // Delete all delete markers
275
+ log.info(`Deleting bucket ${bucketName} delete markers...`)
276
+ if (versions.DeleteMarkers) {
277
+ await Promise.all(versions.DeleteMarkers.map(marker =>
278
+ s3.deleteObject({ Bucket: bucketName, Key: marker.Key || '', VersionId: marker.VersionId }),
279
+ )).catch(error => handleError(error))
280
+ log.info(`Finished deleting delete markers from bucket ${bucketName}`)
281
+ }
282
+
283
+ // If the bucket has uncompleted multipart uploads, you need to abort them
284
+ const uploads = await s3.listMultipartUploads({ Bucket: bucketName })
285
+ if (uploads.Uploads) {
286
+ log.info('Aborting bucket multipart uploads...')
287
+ await Promise.all(uploads.Uploads.map(upload =>
288
+ s3.abortMultipartUpload({ Bucket: bucketName, Key: upload.Key || '', UploadId: upload.UploadId }),
289
+ )).catch(error => handleError(error))
290
+ log.info(`Finished aborting multipart uploads from bucket ${bucketName}`)
291
+ }
292
+
293
+ await s3.deleteBucket({ Bucket: bucketName }).catch(error => handleError(error))
294
+ log.info(`Bucket ${bucketName} deleted`)
295
+ }
296
+ catch (error) {
297
+ log.info(`Error listing bucket ${bucketName} versions`, error)
298
+ }
299
+ })
300
+
301
+ await Promise.all(promises).catch((error: Error) => {
302
+ return err(handleError('Error deleting stacks buckets', error))
303
+ })
304
+
305
+ return ok('Stacks buckets deleted')
306
+ }
307
+ catch (error) {
308
+ return err(handleError('Error deleting stacks buckets', error as Error))
309
+ }
310
+ }
311
+
312
+ export async function deleteStacksFunctions() {
313
+ const lambda = new Lambda({ region: 'us-east-1' })
314
+ const data = await lambda.listFunctions({})
315
+ const stacksFunctions = data.Functions?.filter(func => func.FunctionName?.includes('stacks')) || []
316
+
317
+ if (!stacksFunctions || stacksFunctions.length === 0)
318
+ return ok('No stacks functions found')
319
+
320
+ const promises = stacksFunctions.map(func => lambda.deleteFunction({ FunctionName: func.FunctionName || '' }))
321
+
322
+ await Promise.all(promises).catch((error: Error) => {
323
+ if (error.message.includes('it is a replicated function')) {
324
+ log.info('Function is replicated, skipping...')
325
+ return ok('CloudFront is still deleting the some functions. Try again later.')
326
+ }
327
+
328
+ return err(handleError('Error deleting stacks functions', error))
329
+ })
330
+
331
+ return ok('Stacks functions deleted')
332
+ }
333
+
334
+ export async function deleteLogGroups() {
335
+ try {
336
+ const client = new CloudWatchLogsClient({ region: 'us-east-1' })
337
+
338
+ const logGroups: DescribeLogGroupsCommandOutput = await client.send(new DescribeLogGroupsCommand({}))
339
+
340
+ if (!logGroups?.logGroups)
341
+ return err('No log groups found')
342
+
343
+ for (const group of logGroups.logGroups) {
344
+ if (group.logGroupName?.includes('stacks'))
345
+
346
+ await client.send(new DeleteLogGroupCommand({ logGroupName: group.logGroupName }))
347
+ }
348
+
349
+ return ok('Log groups deleted')
350
+ }
351
+ catch (error) {
352
+ return err(handleError('Error deleting log groups', error as Error))
353
+ }
354
+ }
355
+
356
+ export async function deleteParameterStore() {
357
+ const ssm = new SSM({ region: 'us-east-1' })
358
+ const data = await ssm.describeParameters({})
359
+
360
+ if (!data.Parameters)
361
+ return ok('No parameters found')
362
+
363
+ const stacksParameters = data.Parameters.filter(param => param.Name?.includes('stacks')) || []
364
+
365
+ if (!stacksParameters || stacksParameters.length === 0)
366
+ return ok('No stacks parameters found')
367
+
368
+ const promises = stacksParameters.map(param => ssm.deleteParameter({ Name: param.Name || '' }))
369
+
370
+ await Promise.all(promises).catch((error: Error) => {
371
+ return err(handleError('Error deleting parameter store', error))
372
+ })
373
+
374
+ return ok('Parameter store deleted')
375
+ }
376
+
377
+ export async function deleteCdkRemnants() {
378
+ try {
379
+ return ok(await rimraf([
380
+ p.cloudPath('cdk.out/'),
381
+ p.cloudPath('cdk.context.json'),
382
+ ]))
383
+ }
384
+ catch (error) {
385
+ return err(handleError('Error deleting CDK remnants', error as Error))
386
+ }
387
+ }
388
+
389
+ export async function hasBeenDeployed() {
390
+ const s3 = new S3({ region: 'us-east-1' })
391
+
392
+ try {
393
+ const response = await s3.send(new ListBucketsCommand({}))
394
+ return ok(response.Buckets?.some(bucket => bucket.Name?.includes(config.app.name?.toLocaleLowerCase() || 'stacks')) || false)
395
+ }
396
+ catch (error) {
397
+ return err(handleError('Error checking if the app has been deployed', error as Error))
398
+ }
399
+ }
400
+
401
+ export async function getJumpBoxInstanceProfileName() {
402
+ const iam = new IAM({ region: 'us-east-1' })
403
+ const data = await iam.listInstanceProfiles({})
404
+ const instanceProfile = data.InstanceProfiles?.find(profile => profile.InstanceProfileName?.includes('JumpBox'))
405
+
406
+ if (!instanceProfile)
407
+ return err('Jump-box IAM instance profile not found')
408
+
409
+ return ok(instanceProfile?.InstanceProfileName)
410
+ }
411
+
412
+ export async function addJumpBox(stackName?: string) {
413
+ if (!stackName)
414
+ stackName = cloudName
415
+
416
+ if (await getJumpBoxInstanceId())
417
+ return err('The jump–box you are trying to add already exists. Please remove it & wait until it finished terminating.')
418
+
419
+ const ec2 = new EC2({ region: 'us-east-1' })
420
+
421
+ const r = await getJumpBoxSecurityGroupName()
422
+
423
+ if (r.isErr())
424
+ return err(r.error)
425
+
426
+ if (!r.value)
427
+ return err('Security group not found when adding jump-box')
428
+
429
+ const result = await getSecurityGroupId(r.value)
430
+ let sgId: string | undefined
431
+
432
+ if (result.isErr())
433
+ return err(result.error)
434
+ else
435
+ sgId = result.value
436
+
437
+ if (!sgId)
438
+ return err('Security group not found when adding jump-box')
439
+
440
+ const client = new EFSClient({ region: 'us-east-1' })
441
+ const command = new DescribeFileSystemsCommand({})
442
+ const data = await client.send(command)
443
+
444
+ const fileSystemName = `stacks-${config.app.env}-efs`
445
+ const fileSystem = data.FileSystems?.find(fs => fs.Name === fileSystemName)
446
+ const fileSystemId = fileSystem?.FileSystemId
447
+
448
+ if (!fileSystem || !fileSystemId)
449
+ return err(`EFS file system ${fileSystemName} not found`)
450
+
451
+ const userDataScript = `
452
+ #!/bin/bash
453
+ yum update -y
454
+ yum install -y amazon-efs-utils
455
+ yum install -y git
456
+ yum install -y https://s3.us-east-1.amazonaws.com/amazon-ssm-us-east-1/latest/linux_amd64/amazon-ssm-agent.rpm
457
+ mkdir /mnt/efs
458
+ mount -t efs ${fileSystemId}:/ /mnt/efs
459
+ git clone https://github.com/stacksjs/stacks.git /mnt/efs
460
+ `
461
+
462
+ const base64UserData = btoa(userDataScript)
463
+ const res = await getJumpBoxInstanceProfileName()
464
+
465
+ if (res.isErr())
466
+ return err(res.error)
467
+
468
+ const jumpBoxInstanceProfileName: string | undefined = res.value
469
+ if (!jumpBoxInstanceProfileName)
470
+ return err('Jump-box IAM instance profile not found')
471
+
472
+ const instance = await ec2.runInstances({
473
+ ImageId: 'ami-03a6eaae9938c858c', // Amazon Linux 2023 AMI
474
+ // ImageId: new ec2.AmazonLinuxImage(),
475
+ InstanceType: InstanceType.t2_micro,
476
+ MaxCount: 1,
477
+ MinCount: 1,
478
+ SecurityGroupIds: [sgId],
479
+ SubnetId: 'subnet-004c5f196358b00f0',
480
+ TagSpecifications: [
481
+ {
482
+ ResourceType: 'instance',
483
+ Tags: [
484
+ {
485
+ Key: 'Name',
486
+ Value: `${cloudName}-jump-box`,
487
+ },
488
+ ],
489
+ },
490
+ ],
491
+ UserData: base64UserData,
492
+ IamInstanceProfile: {
493
+ Name: jumpBoxInstanceProfileName,
494
+ },
495
+ })
496
+
497
+ return instance.Instances && instance.Instances[0]
498
+ ? ok(`Jump-box created with id ${instance.Instances[0].InstanceId}`)
499
+ : err('Jump-box creation failed')
500
+ }
501
+
502
+ export async function getJumpBoxSecurityGroupName() {
503
+ const jumpBoxId = await getJumpBoxInstanceId()
504
+
505
+ if (!jumpBoxId)
506
+ return err('Jump-box not found')
507
+
508
+ const ec2 = new EC2({ region: 'us-east-1' })
509
+ const data = await ec2.describeInstances({ InstanceIds: [jumpBoxId] })
510
+
511
+ if (data.Reservations && data.Reservations[0] && data.Reservations[0].Instances && data.Reservations[0].Instances[0]) {
512
+ const instance = data.Reservations[0].Instances[0]
513
+ const securityGroups = instance.SecurityGroups
514
+
515
+ if (securityGroups && securityGroups[0])
516
+ return ok(securityGroups[0].GroupName)
517
+ }
518
+
519
+ return err('Security group not found')
520
+ }
521
+
522
+ export async function getSecurityGroupFromInstanceId(instanceId: string) {
523
+ const ec2 = new EC2({ region: 'us-east-1' })
524
+ const data = await ec2.describeInstances({ InstanceIds: [instanceId] })
525
+
526
+ if (data.Reservations && data.Reservations[0] && data.Reservations[0].Instances && data.Reservations[0].Instances[0]) {
527
+ const instance = data.Reservations[0].Instances[0]
528
+ const securityGroups = instance.SecurityGroups
529
+
530
+ if (securityGroups && securityGroups[0])
531
+ return securityGroups[0].GroupId // Returns the ID of the first security group
532
+ }
533
+
534
+ return undefined
535
+ }
536
+
537
+ export async function isFirstDeployment() {
538
+ const stackName = cloudName
539
+ const cloudFormation = new CloudFormation()
540
+ const data = await cloudFormation.listStacks({ StackStatusFilter: ['CREATE_COMPLETE', 'UPDATE_COMPLETE'] })
541
+ const isStacksCloudPresent = data.StackSummaries?.some(stack => stack.StackName === stackName)
542
+
543
+ return !isStacksCloudPresent
544
+ }
545
+
546
+ export async function isFailedState() {
547
+ const cloudFormation = new CloudFormation()
548
+ const data = await cloudFormation.listStacks({ StackStatusFilter: ['CREATE_FAILED', 'UPDATE_FAILED', 'ROLLBACK_COMPLETE', 'UPDATE_ROLLBACK_COMPLETE'] })
549
+ const isStacksCloudPresent = data.StackSummaries?.some(stack => stack.StackName === cloudName)
550
+
551
+ return !isStacksCloudPresent
552
+ }
553
+
554
+ export async function getOrCreateTimestamp(): Promise<string> {
555
+ const parameterName = `/stacks/timestamp`
556
+ const ssm = new SSM({ region: 'us-east-1' })
557
+
558
+ try {
559
+ const response = await ssm.getParameter({ Name: parameterName })
560
+ const timestamp = response.Parameter ? response.Parameter.Value : undefined
561
+
562
+ if (!timestamp)
563
+ throw new Error('Timestamp parameter not found')
564
+
565
+ return timestamp
566
+ }
567
+ catch (error: any) {
568
+ const timestamp = new Date().getTime().toString()
569
+ log.debug(`Creating timestamp parameter ${parameterName} with value ${timestamp}`)
570
+ await ssm.putParameter({
571
+ Name: parameterName,
572
+ Value: timestamp,
573
+ Type: 'String',
574
+ })
575
+
576
+ return timestamp
577
+ }
578
+ }
579
+
580
+ // function isProductionEnv(env: string) {
581
+ // return env === 'production' || env === 'prod'
582
+ // }
583
+
584
+ // export async function getExistingBucketNameByPrefix(prefix: string): Promise<string | undefined | null> {
585
+ // const s3 = new S3({ region: 'us-east-1' })
586
+
587
+ // try {
588
+ // const response = await s3.send(new ListBucketsCommand({}))
589
+ // const bucket = response.Buckets?.find(bucket => bucket.Name?.startsWith(prefix))
590
+
591
+ // return bucket ? bucket.Name : null
592
+ // }
593
+ // catch (error) {
594
+ // console.error('Error fetching buckets', error)
595
+ // return `${prefix}-${timestamp}`
596
+ // }
597
+ // }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './helpers'
2
+ export * from './cloud'
3
+ export { default as server } from './runtime/server'