@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,489 @@
1
+ import type { CLI } from '@stacksjs/clapp'
2
+ import * as cli from '../../src/utils/cli'
3
+ import { SNSClient } from '../../src/aws/sns'
4
+ import { loadValidatedConfig } from './shared'
5
+
6
+ export function registerNotifyCommands(app: CLI): void {
7
+ app
8
+ .command('notify:topics', 'List all SNS topics')
9
+ .option('--region <region>', 'AWS region')
10
+ .action(async (options: { region?: string }) => {
11
+ cli.header('SNS Topics')
12
+
13
+ try {
14
+ const config = await loadValidatedConfig()
15
+ const region = options.region || config.project.region || 'us-east-1'
16
+ const sns = new SNSClient(region)
17
+
18
+ const spinner = new cli.Spinner('Fetching topics...')
19
+ spinner.start()
20
+
21
+ const result = await sns.listTopics()
22
+ const topics = result.Topics || []
23
+
24
+ spinner.succeed(`Found ${topics.length} topic(s)`)
25
+
26
+ if (topics.length === 0) {
27
+ cli.info('No SNS topics found')
28
+ cli.info('Use `cloud notify:create` to create a new topic')
29
+ return
30
+ }
31
+
32
+ cli.table(
33
+ ['Topic ARN', 'Name'],
34
+ topics.map(t => {
35
+ const name = t.TopicArn?.split(':').pop() || 'N/A'
36
+ return [t.TopicArn || 'N/A', name]
37
+ }),
38
+ )
39
+ }
40
+ catch (error: any) {
41
+ cli.error(`Failed to list topics: ${error.message}`)
42
+ process.exit(1)
43
+ }
44
+ })
45
+
46
+ app
47
+ .command('notify:create <name>', 'Create a new SNS topic')
48
+ .option('--region <region>', 'AWS region', { default: 'us-east-1' })
49
+ .option('--fifo', 'Create a FIFO topic')
50
+ .option('--display-name <name>', 'Display name for SMS subscriptions')
51
+ .action(async (name: string, options: { region: string; fifo?: boolean; displayName?: string }) => {
52
+ cli.header('Create SNS Topic')
53
+
54
+ try {
55
+ const sns = new SNSClient(options.region)
56
+
57
+ // FIFO topics must end with .fifo
58
+ const topicName = options.fifo && !name.endsWith('.fifo') ? `${name}.fifo` : name
59
+
60
+ cli.info(`Topic name: ${topicName}`)
61
+ cli.info(`Type: ${options.fifo ? 'FIFO' : 'Standard'}`)
62
+
63
+ const confirmed = await cli.confirm('\nCreate this topic?', true)
64
+ if (!confirmed) {
65
+ cli.info('Operation cancelled')
66
+ return
67
+ }
68
+
69
+ const spinner = new cli.Spinner('Creating topic...')
70
+ spinner.start()
71
+
72
+ const attributes: Record<string, string> = {}
73
+
74
+ if (options.fifo) {
75
+ attributes.FifoTopic = 'true'
76
+ attributes.ContentBasedDeduplication = 'true'
77
+ }
78
+
79
+ if (options.displayName) {
80
+ attributes.DisplayName = options.displayName
81
+ }
82
+
83
+ const result = await sns.createTopic({
84
+ Name: topicName,
85
+ Attributes: Object.keys(attributes).length > 0 ? attributes : undefined,
86
+ })
87
+
88
+ spinner.succeed('Topic created')
89
+
90
+ cli.success(`\nTopic ARN: ${result.TopicArn}`)
91
+ cli.info('\nTo subscribe:')
92
+ cli.info(` cloud notify:subscribe ${result.TopicArn} --email user@example.com`)
93
+ }
94
+ catch (error: any) {
95
+ cli.error(`Failed to create topic: ${error.message}`)
96
+ process.exit(1)
97
+ }
98
+ })
99
+
100
+ app
101
+ .command('notify:delete <topicArn>', 'Delete an SNS topic')
102
+ .option('--region <region>', 'AWS region', { default: 'us-east-1' })
103
+ .action(async (topicArn: string, options: { region: string }) => {
104
+ cli.header('Delete SNS Topic')
105
+
106
+ try {
107
+ const sns = new SNSClient(options.region)
108
+
109
+ cli.warn(`This will delete topic: ${topicArn}`)
110
+ cli.warn('All subscriptions will be removed.')
111
+
112
+ const confirmed = await cli.confirm('\nDelete this topic?', false)
113
+ if (!confirmed) {
114
+ cli.info('Operation cancelled')
115
+ return
116
+ }
117
+
118
+ const spinner = new cli.Spinner('Deleting topic...')
119
+ spinner.start()
120
+
121
+ await sns.deleteTopic(topicArn)
122
+
123
+ spinner.succeed('Topic deleted')
124
+ }
125
+ catch (error: any) {
126
+ cli.error(`Failed to delete topic: ${error.message}`)
127
+ process.exit(1)
128
+ }
129
+ })
130
+
131
+ app
132
+ .command('notify:subscribe <topicArn>', 'Subscribe to an SNS topic')
133
+ .option('--region <region>', 'AWS region', { default: 'us-east-1' })
134
+ .option('--email <address>', 'Email address')
135
+ .option('--sms <number>', 'Phone number (E.164 format)')
136
+ .option('--sqs <queueArn>', 'SQS queue ARN')
137
+ .option('--lambda <functionArn>', 'Lambda function ARN')
138
+ .option('--https <url>', 'HTTPS endpoint URL')
139
+ .option('--filter <json>', 'Filter policy JSON')
140
+ .action(async (topicArn: string, options: {
141
+ region: string
142
+ email?: string
143
+ sms?: string
144
+ sqs?: string
145
+ lambda?: string
146
+ https?: string
147
+ filter?: string
148
+ }) => {
149
+ cli.header('Subscribe to SNS Topic')
150
+
151
+ try {
152
+ const sns = new SNSClient(options.region)
153
+
154
+ let protocol: string
155
+ let endpoint: string
156
+
157
+ if (options.email) {
158
+ protocol = 'email'
159
+ endpoint = options.email
160
+ }
161
+ else if (options.sms) {
162
+ protocol = 'sms'
163
+ endpoint = options.sms
164
+ }
165
+ else if (options.sqs) {
166
+ protocol = 'sqs'
167
+ endpoint = options.sqs
168
+ }
169
+ else if (options.lambda) {
170
+ protocol = 'lambda'
171
+ endpoint = options.lambda
172
+ }
173
+ else if (options.https) {
174
+ protocol = 'https'
175
+ endpoint = options.https
176
+ }
177
+ else {
178
+ cli.error('Specify a subscription type: --email, --sms, --sqs, --lambda, or --https')
179
+ return
180
+ }
181
+
182
+ cli.info(`Topic: ${topicArn}`)
183
+ cli.info(`Protocol: ${protocol}`)
184
+ cli.info(`Endpoint: ${endpoint}`)
185
+
186
+ const confirmed = await cli.confirm('\nCreate this subscription?', true)
187
+ if (!confirmed) {
188
+ cli.info('Operation cancelled')
189
+ return
190
+ }
191
+
192
+ const spinner = new cli.Spinner('Creating subscription...')
193
+ spinner.start()
194
+
195
+ const params: any = {
196
+ TopicArn: topicArn,
197
+ Protocol: protocol,
198
+ Endpoint: endpoint,
199
+ }
200
+
201
+ if (options.filter) {
202
+ params.Attributes = {
203
+ FilterPolicy: options.filter,
204
+ }
205
+ }
206
+
207
+ const result = await sns.subscribe(params)
208
+
209
+ spinner.succeed('Subscription created')
210
+
211
+ if (result.SubscriptionArn === 'pending confirmation') {
212
+ cli.info('\nSubscription is pending confirmation.')
213
+ cli.info('The subscriber will receive a confirmation message.')
214
+ }
215
+ else {
216
+ cli.success(`\nSubscription ARN: ${result.SubscriptionArn}`)
217
+ }
218
+ }
219
+ catch (error: any) {
220
+ cli.error(`Failed to subscribe: ${error.message}`)
221
+ process.exit(1)
222
+ }
223
+ })
224
+
225
+ app
226
+ .command('notify:unsubscribe <subscriptionArn>', 'Unsubscribe from an SNS topic')
227
+ .option('--region <region>', 'AWS region', { default: 'us-east-1' })
228
+ .action(async (subscriptionArn: string, options: { region: string }) => {
229
+ cli.header('Unsubscribe from SNS Topic')
230
+
231
+ try {
232
+ const sns = new SNSClient(options.region)
233
+
234
+ cli.warn(`This will remove subscription: ${subscriptionArn}`)
235
+
236
+ const confirmed = await cli.confirm('\nUnsubscribe?', false)
237
+ if (!confirmed) {
238
+ cli.info('Operation cancelled')
239
+ return
240
+ }
241
+
242
+ const spinner = new cli.Spinner('Unsubscribing...')
243
+ spinner.start()
244
+
245
+ await sns.unsubscribe(subscriptionArn)
246
+
247
+ spinner.succeed('Unsubscribed')
248
+ }
249
+ catch (error: any) {
250
+ cli.error(`Failed to unsubscribe: ${error.message}`)
251
+ process.exit(1)
252
+ }
253
+ })
254
+
255
+ app
256
+ .command('notify:subscriptions <topicArn>', 'List subscriptions for a topic')
257
+ .option('--region <region>', 'AWS region', { default: 'us-east-1' })
258
+ .action(async (topicArn: string, options: { region: string }) => {
259
+ cli.header('Topic Subscriptions')
260
+
261
+ try {
262
+ const sns = new SNSClient(options.region)
263
+
264
+ const spinner = new cli.Spinner('Fetching subscriptions...')
265
+ spinner.start()
266
+
267
+ const result = await sns.listSubscriptionsByTopic(topicArn)
268
+ const subscriptions = result.Subscriptions || []
269
+
270
+ spinner.succeed(`Found ${subscriptions.length} subscription(s)`)
271
+
272
+ if (subscriptions.length === 0) {
273
+ cli.info('No subscriptions found')
274
+ return
275
+ }
276
+
277
+ cli.table(
278
+ ['Protocol', 'Endpoint', 'Status', 'Subscription ARN'],
279
+ subscriptions.map(s => [
280
+ s.Protocol || 'N/A',
281
+ s.Endpoint || 'N/A',
282
+ s.SubscriptionArn === 'PendingConfirmation' ? 'Pending' : 'Confirmed',
283
+ (s.SubscriptionArn || 'N/A').substring(0, 50),
284
+ ]),
285
+ )
286
+ }
287
+ catch (error: any) {
288
+ cli.error(`Failed to list subscriptions: ${error.message}`)
289
+ process.exit(1)
290
+ }
291
+ })
292
+
293
+ app
294
+ .command('notify:publish <topicArn>', 'Publish a message to an SNS topic')
295
+ .option('--region <region>', 'AWS region', { default: 'us-east-1' })
296
+ .option('--message <text>', 'Message body')
297
+ .option('--subject <text>', 'Message subject (for email)')
298
+ .option('--file <path>', 'Read message from file')
299
+ .option('--json', 'Send as JSON message structure')
300
+ .option('--group <id>', 'Message group ID (for FIFO topics)')
301
+ .option('--dedup <id>', 'Deduplication ID (for FIFO topics)')
302
+ .action(async (topicArn: string, options: {
303
+ region: string
304
+ message?: string
305
+ subject?: string
306
+ file?: string
307
+ json?: boolean
308
+ group?: string
309
+ dedup?: string
310
+ }) => {
311
+ cli.header('Publish to SNS Topic')
312
+
313
+ try {
314
+ const sns = new SNSClient(options.region)
315
+
316
+ let messageBody: string
317
+
318
+ if (options.file) {
319
+ const file = Bun.file(options.file)
320
+ messageBody = await file.text()
321
+ }
322
+ else if (options.message) {
323
+ messageBody = options.message
324
+ }
325
+ else {
326
+ messageBody = await cli.prompt('Message')
327
+ }
328
+
329
+ if (!messageBody) {
330
+ cli.error('Message is required')
331
+ return
332
+ }
333
+
334
+ cli.info(`Topic: ${topicArn}`)
335
+ cli.info(`Message length: ${messageBody.length} characters`)
336
+
337
+ const confirmed = await cli.confirm('\nPublish this message?', true)
338
+ if (!confirmed) {
339
+ cli.info('Operation cancelled')
340
+ return
341
+ }
342
+
343
+ const spinner = new cli.Spinner('Publishing message...')
344
+ spinner.start()
345
+
346
+ const params: any = {
347
+ TopicArn: topicArn,
348
+ Message: messageBody,
349
+ }
350
+
351
+ if (options.subject) {
352
+ params.Subject = options.subject
353
+ }
354
+
355
+ if (options.json) {
356
+ params.MessageStructure = 'json'
357
+ }
358
+
359
+ if (options.group) {
360
+ params.MessageGroupId = options.group
361
+ }
362
+
363
+ if (options.dedup) {
364
+ params.MessageDeduplicationId = options.dedup
365
+ }
366
+
367
+ const result = await sns.publish(params)
368
+
369
+ spinner.succeed('Message published')
370
+
371
+ cli.success(`\nMessage ID: ${result.MessageId}`)
372
+ }
373
+ catch (error: any) {
374
+ cli.error(`Failed to publish message: ${error.message}`)
375
+ process.exit(1)
376
+ }
377
+ })
378
+
379
+ app
380
+ .command('notify:sms <phoneNumber>', 'Send an SMS message directly')
381
+ .option('--region <region>', 'AWS region', { default: 'us-east-1' })
382
+ .option('--message <text>', 'Message body')
383
+ .option('--sender <id>', 'Sender ID or short code')
384
+ .option('--type <type>', 'Message type (Transactional or Promotional)', { default: 'Transactional' })
385
+ .action(async (phoneNumber: string, options: {
386
+ region: string
387
+ message?: string
388
+ sender?: string
389
+ type: string
390
+ }) => {
391
+ cli.header('Send SMS')
392
+
393
+ try {
394
+ const sns = new SNSClient(options.region)
395
+
396
+ const messageBody = options.message || await cli.prompt('Message')
397
+
398
+ if (!messageBody) {
399
+ cli.error('Message is required')
400
+ return
401
+ }
402
+
403
+ cli.info(`To: ${phoneNumber}`)
404
+ cli.info(`Message: ${messageBody}`)
405
+ cli.info(`Type: ${options.type}`)
406
+
407
+ const confirmed = await cli.confirm('\nSend this SMS?', true)
408
+ if (!confirmed) {
409
+ cli.info('Operation cancelled')
410
+ return
411
+ }
412
+
413
+ const spinner = new cli.Spinner('Sending SMS...')
414
+ spinner.start()
415
+
416
+ const params: any = {
417
+ PhoneNumber: phoneNumber,
418
+ Message: messageBody,
419
+ MessageAttributes: {
420
+ 'AWS.SNS.SMS.SMSType': {
421
+ DataType: 'String',
422
+ StringValue: options.type,
423
+ },
424
+ },
425
+ }
426
+
427
+ if (options.sender) {
428
+ params.MessageAttributes['AWS.SNS.SMS.SenderID'] = {
429
+ DataType: 'String',
430
+ StringValue: options.sender,
431
+ }
432
+ }
433
+
434
+ const result = await sns.publish(params)
435
+
436
+ spinner.succeed('SMS sent')
437
+
438
+ cli.success(`\nMessage ID: ${result.MessageId}`)
439
+ }
440
+ catch (error: any) {
441
+ cli.error(`Failed to send SMS: ${error.message}`)
442
+ process.exit(1)
443
+ }
444
+ })
445
+
446
+ app
447
+ .command('notify:topic:attributes <topicArn>', 'Show topic attributes')
448
+ .option('--region <region>', 'AWS region', { default: 'us-east-1' })
449
+ .action(async (topicArn: string, options: { region: string }) => {
450
+ cli.header('Topic Attributes')
451
+
452
+ try {
453
+ const sns = new SNSClient(options.region)
454
+
455
+ const spinner = new cli.Spinner('Fetching attributes...')
456
+ spinner.start()
457
+
458
+ const attrs = await sns.getTopicAttributes(topicArn)
459
+
460
+ spinner.succeed('Attributes loaded')
461
+
462
+ cli.info('\nTopic Information:')
463
+ cli.info(` ARN: ${topicArn}`)
464
+ cli.info(` Display Name: ${attrs.DisplayName || 'Not set'}`)
465
+ cli.info(` Owner: ${attrs.Owner || 'N/A'}`)
466
+
467
+ cli.info('\nSubscriptions:')
468
+ cli.info(` Confirmed: ${attrs.SubscriptionsConfirmed || 0}`)
469
+ cli.info(` Pending: ${attrs.SubscriptionsPending || 0}`)
470
+ cli.info(` Deleted: ${attrs.SubscriptionsDeleted || 0}`)
471
+
472
+ cli.info('\nSettings:')
473
+ cli.info(` FIFO: ${(attrs as any).FifoTopic === 'true' ? 'Yes' : 'No'}`)
474
+ cli.info(` Content Deduplication: ${(attrs as any).ContentBasedDeduplication === 'true' ? 'Yes' : 'No'}`)
475
+
476
+ if (attrs.EffectiveDeliveryPolicy) {
477
+ cli.info('\nDelivery Policy: Configured')
478
+ }
479
+
480
+ if (attrs.Policy) {
481
+ cli.info('\nAccess Policy: Configured')
482
+ }
483
+ }
484
+ catch (error: any) {
485
+ cli.error(`Failed to get attributes: ${error.message}`)
486
+ process.exit(1)
487
+ }
488
+ })
489
+ }