@stacksjs/ts-cloud 0.1.1

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 (117) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +321 -0
  3. package/bin/cli.ts +133 -0
  4. package/bin/commands/analytics.ts +328 -0
  5. package/bin/commands/api.ts +379 -0
  6. package/bin/commands/assets.ts +221 -0
  7. package/bin/commands/audit.ts +501 -0
  8. package/bin/commands/backup.ts +682 -0
  9. package/bin/commands/cache.ts +294 -0
  10. package/bin/commands/cdn.ts +281 -0
  11. package/bin/commands/config.ts +202 -0
  12. package/bin/commands/container.ts +105 -0
  13. package/bin/commands/cost.ts +208 -0
  14. package/bin/commands/database.ts +401 -0
  15. package/bin/commands/deploy.ts +674 -0
  16. package/bin/commands/domain.ts +397 -0
  17. package/bin/commands/email.ts +423 -0
  18. package/bin/commands/environment.ts +285 -0
  19. package/bin/commands/events.ts +424 -0
  20. package/bin/commands/firewall.ts +145 -0
  21. package/bin/commands/function.ts +116 -0
  22. package/bin/commands/generate.ts +280 -0
  23. package/bin/commands/git.ts +139 -0
  24. package/bin/commands/iam.ts +464 -0
  25. package/bin/commands/index.ts +48 -0
  26. package/bin/commands/init.ts +120 -0
  27. package/bin/commands/logs.ts +148 -0
  28. package/bin/commands/network.ts +579 -0
  29. package/bin/commands/notify.ts +489 -0
  30. package/bin/commands/queue.ts +407 -0
  31. package/bin/commands/scheduler.ts +370 -0
  32. package/bin/commands/secrets.ts +54 -0
  33. package/bin/commands/server.ts +629 -0
  34. package/bin/commands/shared.ts +97 -0
  35. package/bin/commands/ssl.ts +138 -0
  36. package/bin/commands/stack.ts +325 -0
  37. package/bin/commands/status.ts +385 -0
  38. package/bin/commands/storage.ts +450 -0
  39. package/bin/commands/team.ts +96 -0
  40. package/bin/commands/tunnel.ts +489 -0
  41. package/bin/commands/utils.ts +202 -0
  42. package/build.ts +15 -0
  43. package/cloud +2 -0
  44. package/package.json +99 -0
  45. package/src/aws/acm.ts +768 -0
  46. package/src/aws/application-autoscaling.ts +845 -0
  47. package/src/aws/bedrock.ts +4074 -0
  48. package/src/aws/client.ts +878 -0
  49. package/src/aws/cloudformation.ts +896 -0
  50. package/src/aws/cloudfront.ts +1531 -0
  51. package/src/aws/cloudwatch-logs.ts +154 -0
  52. package/src/aws/comprehend.ts +839 -0
  53. package/src/aws/connect.ts +1056 -0
  54. package/src/aws/deploy-imap.ts +384 -0
  55. package/src/aws/dynamodb.ts +340 -0
  56. package/src/aws/ec2.ts +1385 -0
  57. package/src/aws/ecr.ts +621 -0
  58. package/src/aws/ecs.ts +615 -0
  59. package/src/aws/elasticache.ts +301 -0
  60. package/src/aws/elbv2.ts +942 -0
  61. package/src/aws/email.ts +928 -0
  62. package/src/aws/eventbridge.ts +248 -0
  63. package/src/aws/iam.ts +1689 -0
  64. package/src/aws/imap-server.ts +2100 -0
  65. package/src/aws/index.ts +213 -0
  66. package/src/aws/kendra.ts +1097 -0
  67. package/src/aws/lambda.ts +786 -0
  68. package/src/aws/opensearch.ts +158 -0
  69. package/src/aws/personalize.ts +977 -0
  70. package/src/aws/polly.ts +559 -0
  71. package/src/aws/rds.ts +888 -0
  72. package/src/aws/rekognition.ts +846 -0
  73. package/src/aws/route53-domains.ts +359 -0
  74. package/src/aws/route53.ts +1046 -0
  75. package/src/aws/s3.ts +2318 -0
  76. package/src/aws/scheduler.ts +571 -0
  77. package/src/aws/secrets-manager.ts +769 -0
  78. package/src/aws/ses.ts +1081 -0
  79. package/src/aws/setup-phone.ts +104 -0
  80. package/src/aws/setup-sms.ts +580 -0
  81. package/src/aws/sms.ts +1735 -0
  82. package/src/aws/smtp-server.ts +531 -0
  83. package/src/aws/sns.ts +758 -0
  84. package/src/aws/sqs.ts +382 -0
  85. package/src/aws/ssm.ts +807 -0
  86. package/src/aws/sts.ts +92 -0
  87. package/src/aws/support.ts +391 -0
  88. package/src/aws/test-imap.ts +86 -0
  89. package/src/aws/textract.ts +780 -0
  90. package/src/aws/transcribe.ts +108 -0
  91. package/src/aws/translate.ts +641 -0
  92. package/src/aws/voice.ts +1379 -0
  93. package/src/config.ts +35 -0
  94. package/src/deploy/index.ts +7 -0
  95. package/src/deploy/static-site-external-dns.ts +906 -0
  96. package/src/deploy/static-site.ts +1125 -0
  97. package/src/dns/godaddy.ts +412 -0
  98. package/src/dns/index.ts +183 -0
  99. package/src/dns/porkbun.ts +362 -0
  100. package/src/dns/route53-adapter.ts +414 -0
  101. package/src/dns/types.ts +114 -0
  102. package/src/dns/validator.ts +369 -0
  103. package/src/generators/index.ts +5 -0
  104. package/src/generators/infrastructure.ts +1660 -0
  105. package/src/index.ts +163 -0
  106. package/src/push/apns.ts +452 -0
  107. package/src/push/fcm.ts +506 -0
  108. package/src/push/index.ts +58 -0
  109. package/src/ssl/acme-client.ts +478 -0
  110. package/src/ssl/index.ts +7 -0
  111. package/src/ssl/letsencrypt.ts +747 -0
  112. package/src/types.ts +2 -0
  113. package/src/utils/cli.ts +398 -0
  114. package/src/validation/index.ts +5 -0
  115. package/src/validation/template.ts +405 -0
  116. package/test/index.test.ts +128 -0
  117. package/tsconfig.json +18 -0
@@ -0,0 +1,385 @@
1
+ import type { CLI } from '@stacksjs/clapp'
2
+ import type { Distribution } from '../../src/aws/cloudfront'
3
+ import type { CertificateDetail } from '../../src/aws/acm'
4
+ import * as cli from '../../src/utils/cli'
5
+ import { loadValidatedConfig } from './shared'
6
+
7
+ export function registerStatusCommands(app: CLI): void {
8
+ app
9
+ .command('status', 'Show overall infrastructure health dashboard')
10
+ .option('--region <region>', 'AWS region')
11
+ .action(async (options: { region?: string }) => {
12
+ cli.header('Infrastructure Status Dashboard')
13
+
14
+ try {
15
+ const config = await loadValidatedConfig()
16
+ const region = options.region || config.project.region || 'us-east-1'
17
+
18
+ cli.info(`Region: ${region}`)
19
+ cli.info(`Project: ${config.project.name || 'Unknown'}`)
20
+ cli.info('')
21
+
22
+ const checks: { name: string; status: string; details: string }[] = []
23
+
24
+ // Check EC2 Instances
25
+ const ec2Spinner = new cli.Spinner('Checking EC2 instances...')
26
+ ec2Spinner.start()
27
+ try {
28
+ const { EC2Client } = await import('../../src/aws/ec2')
29
+ const ec2 = new EC2Client(region)
30
+ const instances = await ec2.describeInstances({
31
+ Filters: [{ Name: 'instance-state-name', Values: ['running', 'pending', 'stopping', 'stopped'] }],
32
+ })
33
+
34
+ let running = 0
35
+ let stopped = 0
36
+ let total = 0
37
+
38
+ for (const reservation of instances.Reservations || []) {
39
+ for (const instance of reservation.Instances || []) {
40
+ total++
41
+ if (instance.State?.Name === 'running') running++
42
+ else if (instance.State?.Name === 'stopped') stopped++
43
+ }
44
+ }
45
+
46
+ ec2Spinner.succeed('EC2 instances checked')
47
+ checks.push({
48
+ name: 'EC2 Instances',
49
+ status: running > 0 ? 'OK' : (total > 0 ? 'WARN' : 'INFO'),
50
+ details: `${running} running, ${stopped} stopped, ${total} total`,
51
+ })
52
+ }
53
+ catch (error: any) {
54
+ ec2Spinner.fail('EC2 check failed')
55
+ checks.push({ name: 'EC2 Instances', status: 'ERROR', details: error.message })
56
+ }
57
+
58
+ // Check RDS Instances
59
+ const rdsSpinner = new cli.Spinner('Checking RDS instances...')
60
+ rdsSpinner.start()
61
+ try {
62
+ const { RDSClient } = await import('../../src/aws/rds')
63
+ const rds = new RDSClient(region)
64
+ const result = await rds.describeDBInstances()
65
+ const instances = result.DBInstances || []
66
+
67
+ const available = instances.filter(i => i.DBInstanceStatus === 'available').length
68
+
69
+ rdsSpinner.succeed('RDS instances checked')
70
+ checks.push({
71
+ name: 'RDS Databases',
72
+ status: instances.length > 0 && available === instances.length ? 'OK' : (instances.length > 0 ? 'WARN' : 'INFO'),
73
+ details: `${available} available, ${instances.length} total`,
74
+ })
75
+ }
76
+ catch (error: any) {
77
+ rdsSpinner.fail('RDS check failed')
78
+ checks.push({ name: 'RDS Databases', status: 'ERROR', details: error.message })
79
+ }
80
+
81
+ // Check Lambda Functions
82
+ const lambdaSpinner = new cli.Spinner('Checking Lambda functions...')
83
+ lambdaSpinner.start()
84
+ try {
85
+ const { LambdaClient } = await import('../../src/aws/lambda')
86
+ const lambda = new LambdaClient(region)
87
+ const result = await lambda.listFunctions()
88
+ const functions = result.Functions || []
89
+
90
+ lambdaSpinner.succeed('Lambda functions checked')
91
+ checks.push({
92
+ name: 'Lambda Functions',
93
+ status: 'OK',
94
+ details: `${functions.length} function(s)`,
95
+ })
96
+ }
97
+ catch (error: any) {
98
+ lambdaSpinner.fail('Lambda check failed')
99
+ checks.push({ name: 'Lambda Functions', status: 'ERROR', details: error.message })
100
+ }
101
+
102
+ // Check S3 Buckets
103
+ const s3Spinner = new cli.Spinner('Checking S3 buckets...')
104
+ s3Spinner.start()
105
+ try {
106
+ const { S3Client } = await import('../../src/aws/s3')
107
+ const s3 = new S3Client(region)
108
+ const result = await s3.listBuckets()
109
+ const buckets = result.Buckets || []
110
+
111
+ s3Spinner.succeed('S3 buckets checked')
112
+ checks.push({
113
+ name: 'S3 Buckets',
114
+ status: 'OK',
115
+ details: `${buckets.length} bucket(s)`,
116
+ })
117
+ }
118
+ catch (error: any) {
119
+ s3Spinner.fail('S3 check failed')
120
+ checks.push({ name: 'S3 Buckets', status: 'ERROR', details: error.message })
121
+ }
122
+
123
+ // Check CloudFront Distributions
124
+ const cfSpinner = new cli.Spinner('Checking CloudFront distributions...')
125
+ cfSpinner.start()
126
+ try {
127
+ const { CloudFrontClient } = await import('../../src/aws/cloudfront')
128
+ const cloudfront = new CloudFrontClient()
129
+ const distributions = await cloudfront.listDistributions()
130
+
131
+ const deployed = distributions.filter((d: Distribution) => d.Status === 'Deployed').length
132
+
133
+ cfSpinner.succeed('CloudFront checked')
134
+ checks.push({
135
+ name: 'CloudFront',
136
+ status: distributions.length > 0 && deployed === distributions.length ? 'OK' : (distributions.length > 0 ? 'WARN' : 'INFO'),
137
+ details: `${deployed} deployed, ${distributions.length} total`,
138
+ })
139
+ }
140
+ catch (error: any) {
141
+ cfSpinner.fail('CloudFront check failed')
142
+ checks.push({ name: 'CloudFront', status: 'ERROR', details: error.message })
143
+ }
144
+
145
+ // Check SQS Queues
146
+ const sqsSpinner = new cli.Spinner('Checking SQS queues...')
147
+ sqsSpinner.start()
148
+ try {
149
+ const { SQSClient } = await import('../../src/aws/sqs')
150
+ const sqs = new SQSClient(region)
151
+ const result = await sqs.listQueues()
152
+ const queues = result.QueueUrls || []
153
+
154
+ sqsSpinner.succeed('SQS queues checked')
155
+ checks.push({
156
+ name: 'SQS Queues',
157
+ status: 'OK',
158
+ details: `${queues.length} queue(s)`,
159
+ })
160
+ }
161
+ catch (error: any) {
162
+ sqsSpinner.fail('SQS check failed')
163
+ checks.push({ name: 'SQS Queues', status: 'ERROR', details: error.message })
164
+ }
165
+
166
+ // Check CloudFormation Stacks
167
+ const cfnSpinner = new cli.Spinner('Checking CloudFormation stacks...')
168
+ cfnSpinner.start()
169
+ try {
170
+ const { CloudFormationClient } = await import('../../src/aws/cloudformation')
171
+ const cfn = new CloudFormationClient(region)
172
+ const result = await cfn.listStacks([
173
+ 'CREATE_COMPLETE',
174
+ 'UPDATE_COMPLETE',
175
+ 'CREATE_IN_PROGRESS',
176
+ 'UPDATE_IN_PROGRESS',
177
+ 'ROLLBACK_COMPLETE',
178
+ 'UPDATE_ROLLBACK_COMPLETE',
179
+ ])
180
+ const stacks = result.StackSummaries || []
181
+
182
+ const healthy = stacks.filter(s =>
183
+ s.StackStatus === 'CREATE_COMPLETE' || s.StackStatus === 'UPDATE_COMPLETE',
184
+ ).length
185
+
186
+ const inProgress = stacks.filter(s =>
187
+ s.StackStatus?.includes('IN_PROGRESS'),
188
+ ).length
189
+
190
+ const failed = stacks.filter(s =>
191
+ s.StackStatus?.includes('ROLLBACK'),
192
+ ).length
193
+
194
+ cfnSpinner.succeed('CloudFormation checked')
195
+ checks.push({
196
+ name: 'CloudFormation',
197
+ status: failed > 0 ? 'WARN' : (inProgress > 0 ? 'INFO' : 'OK'),
198
+ details: `${healthy} healthy, ${inProgress} in progress, ${failed} rolled back`,
199
+ })
200
+ }
201
+ catch (error: any) {
202
+ cfnSpinner.fail('CloudFormation check failed')
203
+ checks.push({ name: 'CloudFormation', status: 'ERROR', details: error.message })
204
+ }
205
+
206
+ // Check ACM Certificates
207
+ const acmSpinner = new cli.Spinner('Checking SSL certificates...')
208
+ acmSpinner.start()
209
+ try {
210
+ const { ACMClient } = await import('../../src/aws/acm')
211
+ const acm = new ACMClient('us-east-1')
212
+ const result = await acm.listCertificates()
213
+ const certSummaries = result.CertificateSummaryList || []
214
+
215
+ // Get full details for each certificate to access Status and NotAfter
216
+ const certs: CertificateDetail[] = await Promise.all(
217
+ certSummaries.map(c => acm.describeCertificate({ CertificateArn: c.CertificateArn })),
218
+ )
219
+
220
+ const issued = certs.filter(c => c.Status === 'ISSUED').length
221
+ const pending = certs.filter(c => c.Status === 'PENDING_VALIDATION').length
222
+ const expiringSoon = certs.filter((c) => {
223
+ if (c.NotAfter) {
224
+ const daysUntilExpiry = (new Date(c.NotAfter).getTime() - Date.now()) / (1000 * 60 * 60 * 24)
225
+ return daysUntilExpiry < 30
226
+ }
227
+ return false
228
+ }).length
229
+
230
+ acmSpinner.succeed('SSL certificates checked')
231
+ checks.push({
232
+ name: 'SSL Certificates',
233
+ status: expiringSoon > 0 ? 'WARN' : (pending > 0 ? 'INFO' : 'OK'),
234
+ details: `${issued} issued, ${pending} pending${expiringSoon > 0 ? `, ${expiringSoon} expiring soon` : ''}`,
235
+ })
236
+ }
237
+ catch (error: any) {
238
+ acmSpinner.fail('ACM check failed')
239
+ checks.push({ name: 'SSL Certificates', status: 'ERROR', details: error.message })
240
+ }
241
+
242
+ // Display summary
243
+ cli.info('\n' + '='.repeat(60))
244
+ cli.info('HEALTH SUMMARY')
245
+ cli.info('='.repeat(60) + '\n')
246
+
247
+ for (const check of checks) {
248
+ let icon = ''
249
+ if (check.status === 'OK') {
250
+ icon = `${cli.colors.green}[OK]${cli.colors.reset}`
251
+ }
252
+ else if (check.status === 'WARN') {
253
+ icon = `${cli.colors.yellow}[WARN]${cli.colors.reset}`
254
+ }
255
+ else if (check.status === 'ERROR') {
256
+ icon = `${cli.colors.red}[ERROR]${cli.colors.reset}`
257
+ }
258
+ else {
259
+ icon = `${cli.colors.blue}[INFO]${cli.colors.reset}`
260
+ }
261
+
262
+ console.log(`${icon} ${check.name.padEnd(20)} ${check.details}`)
263
+ }
264
+
265
+ // Overall status
266
+ const hasErrors = checks.some(c => c.status === 'ERROR')
267
+ const hasWarnings = checks.some(c => c.status === 'WARN')
268
+
269
+ cli.info('')
270
+ if (hasErrors) {
271
+ cli.error('Some services have errors. Check the details above.')
272
+ }
273
+ else if (hasWarnings) {
274
+ cli.warn('Some services need attention. Check the warnings above.')
275
+ }
276
+ else {
277
+ cli.success('All services are healthy!')
278
+ }
279
+ }
280
+ catch (error: any) {
281
+ cli.error(`Failed to get status: ${error.message}`)
282
+ process.exit(1)
283
+ }
284
+ })
285
+
286
+ app
287
+ .command('status:costs', 'Show current month cost summary')
288
+ .option('--region <region>', 'AWS region')
289
+ .action(async (options: { region?: string }) => {
290
+ cli.header('Cost Summary')
291
+
292
+ try {
293
+ cli.info('Fetching cost data from AWS Cost Explorer...')
294
+ cli.info('')
295
+
296
+ // Get current month dates
297
+ const now = new Date()
298
+ const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
299
+ const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
300
+
301
+ cli.info(`Period: ${startOfMonth.toISOString().split('T')[0]} to ${endOfMonth.toISOString().split('T')[0]}`)
302
+ cli.info('')
303
+
304
+ // Note: Cost Explorer API requires special permissions
305
+ cli.info('Note: Cost data requires AWS Cost Explorer API access.')
306
+ cli.info('Run `cloud cost` for detailed cost analysis.')
307
+ }
308
+ catch (error: any) {
309
+ cli.error(`Failed to get cost summary: ${error.message}`)
310
+ process.exit(1)
311
+ }
312
+ })
313
+
314
+ app
315
+ .command('status:alarms', 'Show CloudWatch alarm status')
316
+ .option('--region <region>', 'AWS region')
317
+ .action(async (options: { region?: string }) => {
318
+ cli.header('CloudWatch Alarms')
319
+
320
+ try {
321
+ const config = await loadValidatedConfig()
322
+ const region = options.region || config.project.region || 'us-east-1'
323
+
324
+ const { AWSClient } = await import('../../src/aws/client')
325
+
326
+ class CloudWatchClient {
327
+ private client: InstanceType<typeof AWSClient>
328
+ private region: string
329
+
330
+ constructor(region: string) {
331
+ this.region = region
332
+ this.client = new AWSClient()
333
+ }
334
+
335
+ async describeAlarms() {
336
+ return this.client.request({
337
+ service: 'monitoring',
338
+ region: this.region,
339
+ method: 'POST',
340
+ path: '/',
341
+ headers: {
342
+ 'Content-Type': 'application/x-www-form-urlencoded',
343
+ },
344
+ body: 'Action=DescribeAlarms&Version=2010-08-01',
345
+ })
346
+ }
347
+ }
348
+
349
+ const cloudwatch = new CloudWatchClient(region)
350
+
351
+ const spinner = new cli.Spinner('Fetching alarms...')
352
+ spinner.start()
353
+
354
+ const result = await cloudwatch.describeAlarms()
355
+ const alarms = result.MetricAlarms || []
356
+
357
+ spinner.succeed(`Found ${alarms.length} alarm(s)`)
358
+
359
+ if (alarms.length === 0) {
360
+ cli.info('No CloudWatch alarms configured')
361
+ return
362
+ }
363
+
364
+ const alarming = alarms.filter((a: any) => a.StateValue === 'ALARM')
365
+ const ok = alarms.filter((a: any) => a.StateValue === 'OK')
366
+ const insufficient = alarms.filter((a: any) => a.StateValue === 'INSUFFICIENT_DATA')
367
+
368
+ cli.info('')
369
+ cli.info(`Alarming: ${alarming.length}`)
370
+ cli.info(`OK: ${ok.length}`)
371
+ cli.info(`Insufficient Data: ${insufficient.length}`)
372
+
373
+ if (alarming.length > 0) {
374
+ cli.info('\nAlarms in ALARM state:')
375
+ for (const alarm of alarming) {
376
+ cli.error(` - ${alarm.AlarmName}: ${alarm.AlarmDescription || 'No description'}`)
377
+ }
378
+ }
379
+ }
380
+ catch (error: any) {
381
+ cli.error(`Failed to get alarms: ${error.message}`)
382
+ process.exit(1)
383
+ }
384
+ })
385
+ }