@stacksjs/ts-cloud 0.1.2 → 0.1.5

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 (187) hide show
  1. package/README.md +98 -13
  2. package/dist/aws/acm.d.ts +129 -0
  3. package/dist/aws/application-autoscaling.d.ts +282 -0
  4. package/dist/aws/bedrock.d.ts +2292 -0
  5. package/dist/aws/client.d.ts +79 -0
  6. package/dist/aws/cloudformation.d.ts +105 -0
  7. package/dist/aws/cloudfront.d.ts +265 -0
  8. package/dist/aws/cloudwatch-logs.d.ts +48 -0
  9. package/dist/aws/comprehend.d.ts +505 -0
  10. package/dist/aws/connect.d.ts +377 -0
  11. package/dist/aws/deploy-imap.d.ts +14 -0
  12. package/dist/aws/dynamodb.d.ts +176 -0
  13. package/dist/aws/ec2.d.ts +272 -0
  14. package/dist/aws/ecr.d.ts +149 -0
  15. package/dist/aws/ecs.d.ts +162 -0
  16. package/dist/aws/elasticache.d.ts +71 -0
  17. package/dist/aws/elbv2.d.ts +248 -0
  18. package/dist/aws/email.d.ts +175 -0
  19. package/dist/aws/eventbridge.d.ts +142 -0
  20. package/dist/aws/iam.d.ts +638 -0
  21. package/dist/aws/imap-server.d.ts +119 -0
  22. package/{src/aws/index.ts → dist/aws/index.d.ts} +62 -83
  23. package/{src/aws/kendra.ts → dist/aws/kendra.d.ts} +71 -386
  24. package/dist/aws/lambda.d.ts +232 -0
  25. package/dist/aws/opensearch.d.ts +87 -0
  26. package/dist/aws/personalize.d.ts +516 -0
  27. package/dist/aws/polly.d.ts +214 -0
  28. package/dist/aws/rds.d.ts +240 -0
  29. package/dist/aws/rekognition.d.ts +543 -0
  30. package/dist/aws/route53-domains.d.ts +113 -0
  31. package/dist/aws/route53.d.ts +215 -0
  32. package/dist/aws/s3.d.ts +212 -0
  33. package/dist/aws/scheduler.d.ts +140 -0
  34. package/dist/aws/secrets-manager.d.ts +170 -0
  35. package/dist/aws/ses.d.ts +288 -0
  36. package/dist/aws/setup-phone.d.ts +0 -0
  37. package/dist/aws/setup-sms.d.ts +115 -0
  38. package/dist/aws/sms.d.ts +304 -0
  39. package/dist/aws/smtp-server.d.ts +61 -0
  40. package/dist/aws/sns.d.ts +117 -0
  41. package/dist/aws/sqs.d.ts +65 -0
  42. package/dist/aws/ssm.d.ts +179 -0
  43. package/dist/aws/sts.d.ts +15 -0
  44. package/dist/aws/support.d.ts +104 -0
  45. package/dist/aws/test-imap.d.ts +0 -0
  46. package/dist/aws/textract.d.ts +403 -0
  47. package/dist/aws/transcribe.d.ts +60 -0
  48. package/dist/aws/translate.d.ts +358 -0
  49. package/dist/aws/voice.d.ts +219 -0
  50. package/dist/bin/cli.js +1724 -0
  51. package/dist/config.d.ts +7 -0
  52. package/dist/deploy/index.d.ts +2 -0
  53. package/dist/deploy/static-site-external-dns.d.ts +51 -0
  54. package/dist/deploy/static-site.d.ts +71 -0
  55. package/dist/dns/cloudflare.d.ts +52 -0
  56. package/dist/dns/godaddy.d.ts +38 -0
  57. package/dist/dns/index.d.ts +45 -0
  58. package/dist/dns/porkbun.d.ts +18 -0
  59. package/dist/dns/route53-adapter.d.ts +38 -0
  60. package/{src/dns/types.ts → dist/dns/types.d.ts} +26 -63
  61. package/dist/dns/validator.d.ts +78 -0
  62. package/dist/generators/index.d.ts +1 -0
  63. package/dist/generators/infrastructure.d.ts +30 -0
  64. package/{src/index.ts → dist/index.d.ts} +70 -93
  65. package/dist/index.js +7881 -0
  66. package/dist/push/apns.d.ts +60 -0
  67. package/dist/push/fcm.d.ts +117 -0
  68. package/dist/push/index.d.ts +14 -0
  69. package/dist/security/pre-deploy-scanner.d.ts +69 -0
  70. package/dist/ssl/acme-client.d.ts +67 -0
  71. package/dist/ssl/index.d.ts +2 -0
  72. package/dist/ssl/letsencrypt.d.ts +48 -0
  73. package/dist/types.d.ts +1 -0
  74. package/dist/utils/cli.d.ts +123 -0
  75. package/dist/validation/index.d.ts +1 -0
  76. package/dist/validation/template.d.ts +23 -0
  77. package/package.json +8 -8
  78. package/bin/cli.ts +0 -133
  79. package/bin/commands/analytics.ts +0 -328
  80. package/bin/commands/api.ts +0 -379
  81. package/bin/commands/assets.ts +0 -221
  82. package/bin/commands/audit.ts +0 -501
  83. package/bin/commands/backup.ts +0 -682
  84. package/bin/commands/cache.ts +0 -294
  85. package/bin/commands/cdn.ts +0 -281
  86. package/bin/commands/config.ts +0 -202
  87. package/bin/commands/container.ts +0 -105
  88. package/bin/commands/cost.ts +0 -208
  89. package/bin/commands/database.ts +0 -401
  90. package/bin/commands/deploy.ts +0 -674
  91. package/bin/commands/domain.ts +0 -397
  92. package/bin/commands/email.ts +0 -423
  93. package/bin/commands/environment.ts +0 -285
  94. package/bin/commands/events.ts +0 -424
  95. package/bin/commands/firewall.ts +0 -145
  96. package/bin/commands/function.ts +0 -116
  97. package/bin/commands/generate.ts +0 -280
  98. package/bin/commands/git.ts +0 -139
  99. package/bin/commands/iam.ts +0 -464
  100. package/bin/commands/index.ts +0 -48
  101. package/bin/commands/init.ts +0 -120
  102. package/bin/commands/logs.ts +0 -148
  103. package/bin/commands/network.ts +0 -579
  104. package/bin/commands/notify.ts +0 -489
  105. package/bin/commands/queue.ts +0 -407
  106. package/bin/commands/scheduler.ts +0 -370
  107. package/bin/commands/secrets.ts +0 -54
  108. package/bin/commands/server.ts +0 -629
  109. package/bin/commands/shared.ts +0 -97
  110. package/bin/commands/ssl.ts +0 -138
  111. package/bin/commands/stack.ts +0 -325
  112. package/bin/commands/status.ts +0 -385
  113. package/bin/commands/storage.ts +0 -450
  114. package/bin/commands/team.ts +0 -96
  115. package/bin/commands/tunnel.ts +0 -489
  116. package/bin/commands/utils.ts +0 -202
  117. package/build.ts +0 -15
  118. package/cloud +0 -2
  119. package/src/aws/acm.ts +0 -768
  120. package/src/aws/application-autoscaling.ts +0 -845
  121. package/src/aws/bedrock.ts +0 -4074
  122. package/src/aws/client.ts +0 -878
  123. package/src/aws/cloudformation.ts +0 -896
  124. package/src/aws/cloudfront.ts +0 -1531
  125. package/src/aws/cloudwatch-logs.ts +0 -154
  126. package/src/aws/comprehend.ts +0 -839
  127. package/src/aws/connect.ts +0 -1056
  128. package/src/aws/deploy-imap.ts +0 -384
  129. package/src/aws/dynamodb.ts +0 -340
  130. package/src/aws/ec2.ts +0 -1385
  131. package/src/aws/ecr.ts +0 -621
  132. package/src/aws/ecs.ts +0 -615
  133. package/src/aws/elasticache.ts +0 -301
  134. package/src/aws/elbv2.ts +0 -942
  135. package/src/aws/email.ts +0 -928
  136. package/src/aws/eventbridge.ts +0 -248
  137. package/src/aws/iam.ts +0 -1689
  138. package/src/aws/imap-server.ts +0 -2100
  139. package/src/aws/lambda.ts +0 -786
  140. package/src/aws/opensearch.ts +0 -158
  141. package/src/aws/personalize.ts +0 -977
  142. package/src/aws/polly.ts +0 -559
  143. package/src/aws/rds.ts +0 -888
  144. package/src/aws/rekognition.ts +0 -846
  145. package/src/aws/route53-domains.ts +0 -359
  146. package/src/aws/route53.ts +0 -1046
  147. package/src/aws/s3.ts +0 -2318
  148. package/src/aws/scheduler.ts +0 -571
  149. package/src/aws/secrets-manager.ts +0 -769
  150. package/src/aws/ses.ts +0 -1081
  151. package/src/aws/setup-phone.ts +0 -104
  152. package/src/aws/setup-sms.ts +0 -580
  153. package/src/aws/sms.ts +0 -1735
  154. package/src/aws/smtp-server.ts +0 -531
  155. package/src/aws/sns.ts +0 -758
  156. package/src/aws/sqs.ts +0 -382
  157. package/src/aws/ssm.ts +0 -807
  158. package/src/aws/sts.ts +0 -92
  159. package/src/aws/support.ts +0 -391
  160. package/src/aws/test-imap.ts +0 -86
  161. package/src/aws/textract.ts +0 -780
  162. package/src/aws/transcribe.ts +0 -108
  163. package/src/aws/translate.ts +0 -641
  164. package/src/aws/voice.ts +0 -1379
  165. package/src/config.ts +0 -35
  166. package/src/deploy/index.ts +0 -7
  167. package/src/deploy/static-site-external-dns.ts +0 -906
  168. package/src/deploy/static-site.ts +0 -1125
  169. package/src/dns/godaddy.ts +0 -412
  170. package/src/dns/index.ts +0 -183
  171. package/src/dns/porkbun.ts +0 -362
  172. package/src/dns/route53-adapter.ts +0 -414
  173. package/src/dns/validator.ts +0 -369
  174. package/src/generators/index.ts +0 -5
  175. package/src/generators/infrastructure.ts +0 -1660
  176. package/src/push/apns.ts +0 -452
  177. package/src/push/fcm.ts +0 -506
  178. package/src/push/index.ts +0 -58
  179. package/src/ssl/acme-client.ts +0 -478
  180. package/src/ssl/index.ts +0 -7
  181. package/src/ssl/letsencrypt.ts +0 -747
  182. package/src/types.ts +0 -2
  183. package/src/utils/cli.ts +0 -398
  184. package/src/validation/index.ts +0 -5
  185. package/src/validation/template.ts +0 -405
  186. package/test/index.test.ts +0 -128
  187. package/tsconfig.json +0 -18
@@ -1,104 +0,0 @@
1
- import { ConnectClient } from './connect'
2
-
3
- const connect = new ConnectClient('us-east-1')
4
- const instanceId = '7deb6488-8555-4304-b893-d6c461449eda'
5
-
6
- async function main() {
7
- // First, check which phone number we have
8
- console.log('Checking claimed phone numbers...')
9
- const phones = await connect.listPhoneNumbers({ InstanceId: instanceId })
10
- console.log('Claimed phone numbers:')
11
- for (const p of phones.ListPhoneNumbersSummaryList || []) {
12
- console.log(' ', p.PhoneNumber, '(ID:', p.PhoneNumberId + ')')
13
- }
14
-
15
- // Use the first phone number
16
- const phoneNumber = phones.ListPhoneNumbersSummaryList?.[0]
17
- if (!phoneNumber) {
18
- console.log('No phone numbers found!')
19
- process.exit(1)
20
- }
21
-
22
- // Create a contact flow with the correct format for call forwarding
23
- console.log('\nCreating contact flow with call forwarding...')
24
-
25
- // Use the correct format for TransferToPhoneNumber
26
- // According to AWS documentation, the action type is "TransferToPhoneNumber"
27
- const forwardingFlow = {
28
- Version: '2019-10-30',
29
- StartAction: 'transfer-to-phone',
30
- Metadata: {
31
- entryPointPosition: { x: 40, y: 40 },
32
- ActionMetadata: {
33
- 'transfer-to-phone': { position: { x: 190, y: 40 } },
34
- 'end-call': { position: { x: 440, y: 40 } },
35
- },
36
- },
37
- Actions: [
38
- {
39
- Identifier: 'transfer-to-phone',
40
- Type: 'TransferToPhoneNumber',
41
- Parameters: {
42
- PhoneNumber: '+18088218241',
43
- ContactFlowId: '', // Will use instance default
44
- },
45
- Transitions: {
46
- NextAction: 'end-call',
47
- Errors: [
48
- { ErrorType: 'NoMatchingError', NextAction: 'end-call' },
49
- { ErrorType: 'CallFailed', NextAction: 'end-call' },
50
- { ErrorType: 'ConnectionTimeLimitExceeded', NextAction: 'end-call' },
51
- ],
52
- },
53
- },
54
- {
55
- Identifier: 'end-call',
56
- Type: 'DisconnectParticipant',
57
- Parameters: {},
58
- Transitions: {},
59
- },
60
- ],
61
- }
62
-
63
- try {
64
- const flowResult = await connect.createContactFlow({
65
- InstanceId: instanceId,
66
- Name: 'Stacks Call Forwarding',
67
- Type: 'CONTACT_FLOW',
68
- Content: JSON.stringify(forwardingFlow),
69
- Description: 'Forwards all calls to +18088218241',
70
- })
71
- console.log('Contact flow created!')
72
- console.log('Flow ID:', flowResult.ContactFlowId)
73
- console.log('Flow ARN:', flowResult.ContactFlowArn)
74
-
75
- // Associate the phone number with the contact flow
76
- console.log('\nAssociating phone number with contact flow...')
77
- await connect.associatePhoneNumberContactFlow({
78
- PhoneNumberId: phoneNumber.PhoneNumberId!,
79
- InstanceId: instanceId,
80
- ContactFlowId: flowResult.ContactFlowId!,
81
- })
82
- console.log('Phone number associated with contact flow!')
83
-
84
- // Format the phone number
85
- const num = phoneNumber.PhoneNumber!
86
- const formatted = `+1 (${num.slice(2, 5)}) ${num.slice(5, 8)}-${num.slice(8)}`
87
-
88
- console.log('\n========================================')
89
- console.log(' STACKS PHONE NUMBER IS READY!')
90
- console.log('========================================')
91
- console.log('')
92
- console.log(' Call: ' + formatted)
93
- console.log(' Raw: ' + num)
94
- console.log(' Forwards to: +1 (808) 821-8241')
95
- console.log(' Hours: 11:30 AM - 8:00 PM PST')
96
- console.log('')
97
- console.log('========================================')
98
- }
99
- catch (e: any) {
100
- console.log('Error:', e.message)
101
- }
102
- }
103
-
104
- main()
@@ -1,580 +0,0 @@
1
- /**
2
- * SMS Setup Automation Module
3
- * Handles complete SMS infrastructure setup during deploy
4
- *
5
- * This module automates:
6
- * - S3 inbox setup for incoming messages
7
- * - SNS topics for two-way messaging
8
- * - Spending limit management
9
- * - Sandbox exit requests via AWS Support
10
- * - Delivery receipt configuration
11
- *
12
- * Note: Phone number provisioning requires AWS End User Messaging console.
13
- * SNS uses a shared pool for sending unless you configure an origination number.
14
- */
15
-
16
- import { SNSClient } from './sns'
17
- import { S3Client } from './s3'
18
- import { IAMClient } from './iam'
19
- import { LambdaClient } from './lambda'
20
- import { SupportClient, SupportTemplates } from './support'
21
- import { AWSClient } from './client'
22
-
23
- export interface SmsSetupConfig {
24
- region?: string
25
- // Account identifier (used for naming resources)
26
- accountName?: string
27
- // AWS account ID
28
- accountId?: string
29
- // S3 inbox configuration
30
- inbox?: {
31
- enabled?: boolean
32
- bucket?: string
33
- prefix?: string
34
- // Create lifecycle rules for retention
35
- retentionDays?: number
36
- }
37
- // SNS topic for incoming SMS
38
- twoWay?: {
39
- enabled?: boolean
40
- topicName?: string
41
- }
42
- // Spending configuration
43
- spending?: {
44
- monthlyLimit?: number
45
- // Auto-request increase via AWS Support
46
- autoRequestIncrease?: boolean
47
- }
48
- // Sandbox configuration
49
- sandbox?: {
50
- // Auto-request sandbox exit via AWS Support
51
- autoRequestExit?: boolean
52
- // Company details for support ticket
53
- companyName?: string
54
- // Use case description for support ticket
55
- useCase?: string
56
- // Expected monthly SMS volume
57
- expectedMonthlyVolume?: number
58
- // Website URL
59
- websiteUrl?: string
60
- }
61
- // Delivery receipts
62
- deliveryReceipts?: {
63
- enabled?: boolean
64
- // SNS topic for delivery receipts
65
- topicName?: string
66
- // Store receipts in S3
67
- s3Bucket?: string
68
- s3Prefix?: string
69
- }
70
- // Lambda function for processing incoming SMS
71
- inboxLambda?: {
72
- enabled?: boolean
73
- functionName?: string
74
- // Code location (for creating new Lambda)
75
- codeS3Bucket?: string
76
- codeS3Key?: string
77
- }
78
- }
79
-
80
- export interface SmsSetupResult {
81
- success: boolean
82
- inboxBucket?: string
83
- inboxPrefix?: string
84
- twoWayTopicArn?: string
85
- deliveryReceiptTopicArn?: string
86
- inboxLambdaArn?: string
87
- spendingLimit?: number
88
- sandboxStatus?: 'IN_SANDBOX' | 'OUT_OF_SANDBOX' | 'EXIT_REQUESTED'
89
- supportCaseId?: string
90
- errors: string[]
91
- warnings: string[]
92
- }
93
-
94
- /**
95
- * Set up complete SMS infrastructure
96
- * Called automatically during `buddy deploy` when SMS is enabled
97
- */
98
- export async function setupSmsInfrastructure(config: SmsSetupConfig): Promise<SmsSetupResult> {
99
- const region = config.region || 'us-east-1'
100
- const result: SmsSetupResult = {
101
- success: true,
102
- errors: [],
103
- warnings: [],
104
- }
105
-
106
- const sns = new SNSClient(region)
107
- const s3 = new S3Client(region)
108
- const support = new SupportClient(region)
109
- const awsClient = new AWSClient()
110
-
111
- console.log('Setting up SMS infrastructure...')
112
-
113
- // 1. Check current SMS status
114
- console.log(' Checking SMS account status...')
115
- try {
116
- const accountStatus = await checkSmsAccountStatus(sns)
117
- result.sandboxStatus = accountStatus.inSandbox ? 'IN_SANDBOX' : 'OUT_OF_SANDBOX'
118
-
119
- if (accountStatus.inSandbox) {
120
- console.log(' Account is in SMS sandbox')
121
-
122
- // Auto-request sandbox exit if configured
123
- if (config.sandbox?.autoRequestExit && config.sandbox.companyName) {
124
- console.log(' Requesting SMS sandbox exit via AWS Support...')
125
- try {
126
- const caseParams = SupportTemplates.smsSandboxExit({
127
- companyName: config.sandbox.companyName,
128
- useCase: config.sandbox.useCase || 'Transactional notifications and verification codes',
129
- expectedMonthlyVolume: config.sandbox.expectedMonthlyVolume || 1000,
130
- websiteUrl: config.sandbox.websiteUrl,
131
- })
132
- const caseResult = await support.createCase(caseParams)
133
- result.supportCaseId = caseResult.caseId
134
- result.sandboxStatus = 'EXIT_REQUESTED'
135
- console.log(` Support case created: ${caseResult.caseId}`)
136
- } catch (err: any) {
137
- result.warnings.push(`Failed to create sandbox exit support case: ${err.message}`)
138
- console.log(` Warning: Could not create support case: ${err.message}`)
139
- }
140
- }
141
- }
142
-
143
- // Check spending limit
144
- result.spendingLimit = accountStatus.spendingLimit
145
- console.log(` Current spending limit: $${accountStatus.spendingLimit}/month`)
146
-
147
- // Request spending limit increase if needed
148
- if (
149
- config.spending?.autoRequestIncrease &&
150
- config.spending.monthlyLimit &&
151
- accountStatus.spendingLimit < config.spending.monthlyLimit
152
- ) {
153
- console.log(` Requesting spending limit increase to $${config.spending.monthlyLimit}/month...`)
154
- try {
155
- // Try to set it directly via SNS attributes
156
- await setSnsSpendingLimit(awsClient, region, config.spending.monthlyLimit)
157
- result.spendingLimit = config.spending.monthlyLimit
158
- console.log(' Spending limit updated successfully')
159
- } catch (err: any) {
160
- // If direct update fails, file a support ticket
161
- if (config.sandbox?.companyName) {
162
- try {
163
- const caseParams = SupportTemplates.smsSpendLimitIncrease({
164
- companyName: config.sandbox.companyName,
165
- currentLimit: accountStatus.spendingLimit,
166
- requestedLimit: config.spending.monthlyLimit,
167
- useCase: config.sandbox.useCase || 'Production SMS messaging',
168
- })
169
- const caseResult = await support.createCase(caseParams)
170
- result.supportCaseId = caseResult.caseId
171
- result.warnings.push(`Spending limit increase requested via support case: ${caseResult.caseId}`)
172
- console.log(` Support case created for limit increase: ${caseResult.caseId}`)
173
- } catch (supportErr: any) {
174
- result.warnings.push(`Failed to request spending limit increase: ${supportErr.message}`)
175
- }
176
- } else {
177
- result.warnings.push('Spending limit increase requires AWS Support ticket. Provide companyName in config.')
178
- }
179
- }
180
- }
181
- } catch (err: any) {
182
- result.errors.push(`Failed to check SMS account status: ${err.message}`)
183
- console.log(` Error checking status: ${err.message}`)
184
- }
185
-
186
- // 2. Set up S3 inbox
187
- if (config.inbox?.enabled && config.inbox.bucket) {
188
- console.log(' Setting up S3 inbox...')
189
- try {
190
- await setupS3Inbox(s3, {
191
- bucket: config.inbox.bucket,
192
- prefix: config.inbox.prefix || 'sms/inbox/',
193
- retentionDays: config.inbox.retentionDays,
194
- })
195
- result.inboxBucket = config.inbox.bucket
196
- result.inboxPrefix = config.inbox.prefix || 'sms/inbox/'
197
- console.log(` Inbox configured: s3://${config.inbox.bucket}/${result.inboxPrefix}`)
198
- } catch (err: any) {
199
- result.errors.push(`Failed to set up S3 inbox: ${err.message}`)
200
- console.log(` Error setting up inbox: ${err.message}`)
201
- }
202
- }
203
-
204
- // 3. Set up SNS topic for two-way SMS
205
- if (config.twoWay?.enabled) {
206
- console.log(' Setting up two-way SMS...')
207
- try {
208
- const topicName = config.twoWay.topicName || `${config.accountName || 'stacks'}-sms-inbox`
209
- const topicArn = await setupTwoWayTopic(sns, awsClient, region, topicName, config.accountId)
210
- result.twoWayTopicArn = topicArn
211
- console.log(` Two-way topic: ${topicArn}`)
212
- } catch (err: any) {
213
- result.errors.push(`Failed to set up two-way SMS topic: ${err.message}`)
214
- console.log(` Error setting up two-way: ${err.message}`)
215
- }
216
- }
217
-
218
- // 4. Set up delivery receipts topic
219
- if (config.deliveryReceipts?.enabled) {
220
- console.log(' Setting up delivery receipts...')
221
- try {
222
- const topicName = config.deliveryReceipts.topicName || `${config.accountName || 'stacks'}-sms-delivery-receipts`
223
- const topicArn = await setupDeliveryReceiptsTopic(sns, awsClient, region, topicName, config.accountId)
224
- result.deliveryReceiptTopicArn = topicArn
225
- console.log(` Delivery receipts topic: ${topicArn}`)
226
- } catch (err: any) {
227
- result.errors.push(`Failed to set up delivery receipts: ${err.message}`)
228
- console.log(` Error setting up delivery receipts: ${err.message}`)
229
- }
230
- }
231
-
232
- // Final status
233
- result.success = result.errors.length === 0
234
- console.log(result.success ? ' SMS setup completed successfully!' : ' SMS setup completed with errors')
235
-
236
- // Note about phone numbers
237
- if (result.success) {
238
- result.warnings.push(
239
- 'For dedicated phone numbers, use AWS End User Messaging console. SNS uses shared pool by default.',
240
- )
241
- }
242
-
243
- return result
244
- }
245
-
246
- /**
247
- * Check SMS account status (sandbox, spending limits)
248
- */
249
- async function checkSmsAccountStatus(sns: SNSClient): Promise<{
250
- inSandbox: boolean
251
- spendingLimit: number
252
- usedThisMonth: number
253
- }> {
254
- let inSandbox = true
255
- let spendingLimit = 1
256
- let usedThisMonth = 0
257
-
258
- // Check SNS sandbox status
259
- try {
260
- const sandboxStatus = await sns.getSMSSandboxAccountStatus()
261
- inSandbox = sandboxStatus.IsInSandbox
262
- } catch {
263
- // Assume sandbox if we can't check
264
- inSandbox = true
265
- }
266
-
267
- // Get spending quota from SNS attributes
268
- try {
269
- const smsAttrs = await sns.getSMSAttributes()
270
- if (smsAttrs.MonthlySpendLimit) {
271
- spendingLimit = parseFloat(smsAttrs.MonthlySpendLimit)
272
- }
273
- } catch {
274
- // Ignore
275
- }
276
-
277
- return { inSandbox, spendingLimit, usedThisMonth }
278
- }
279
-
280
- /**
281
- * Set SNS SMS spending limit
282
- */
283
- async function setSnsSpendingLimit(awsClient: AWSClient, region: string, limit: number): Promise<void> {
284
- const params = new URLSearchParams({
285
- Action: 'SetSMSAttributes',
286
- Version: '2010-03-31',
287
- 'attributes.entry.1.key': 'MonthlySpendLimit',
288
- 'attributes.entry.1.value': limit.toString(),
289
- })
290
-
291
- await awsClient.request({
292
- service: 'sns',
293
- region,
294
- method: 'POST',
295
- path: '/',
296
- headers: {
297
- 'Content-Type': 'application/x-www-form-urlencoded',
298
- },
299
- body: params.toString(),
300
- })
301
- }
302
-
303
- /**
304
- * Set up S3 bucket for SMS inbox
305
- */
306
- async function setupS3Inbox(
307
- s3: S3Client,
308
- config: {
309
- bucket: string
310
- prefix: string
311
- retentionDays?: number
312
- },
313
- ): Promise<void> {
314
- // Check if bucket exists
315
- try {
316
- const buckets = await s3.listBuckets()
317
- const bucketExists = buckets.Buckets?.some(b => b.Name === config.bucket)
318
-
319
- if (!bucketExists) {
320
- // Create bucket
321
- await s3.createBucket(config.bucket)
322
- }
323
-
324
- // Create placeholder files to ensure prefixes exist
325
- const prefixes = [
326
- `${config.prefix}.keep`,
327
- 'sms/sent/.keep',
328
- 'sms/conversations/.keep',
329
- 'sms/templates/.keep',
330
- 'sms/scheduled/.keep',
331
- 'sms/receipts/.keep',
332
- ]
333
-
334
- for (const key of prefixes) {
335
- try {
336
- await s3.putObject({
337
- bucket: config.bucket,
338
- key,
339
- body: `SMS folder created ${new Date().toISOString()}`,
340
- contentType: 'text/plain',
341
- })
342
- } catch {
343
- // Ignore if already exists
344
- }
345
- }
346
-
347
- // Set up lifecycle rules for retention
348
- if (config.retentionDays) {
349
- try {
350
- await s3.putBucketLifecycleConfiguration(config.bucket, [
351
- {
352
- ID: 'SmsInboxRetention',
353
- Status: 'Enabled',
354
- Filter: { Prefix: config.prefix },
355
- Expiration: { Days: config.retentionDays },
356
- },
357
- {
358
- ID: 'SmsReceiptsRetention',
359
- Status: 'Enabled',
360
- Filter: { Prefix: 'sms/receipts/' },
361
- Expiration: { Days: config.retentionDays },
362
- },
363
- ])
364
- } catch (err: any) {
365
- // Lifecycle configuration might fail if not owner, continue anyway
366
- console.log(` Note: Could not set lifecycle rules: ${err.message}`)
367
- }
368
- }
369
- } catch (err: any) {
370
- throw new Error(`Failed to set up S3 inbox: ${err.message}`)
371
- }
372
- }
373
-
374
- /**
375
- * Set up SNS topic for two-way SMS
376
- */
377
- async function setupTwoWayTopic(
378
- sns: SNSClient,
379
- awsClient: AWSClient,
380
- region: string,
381
- topicName: string,
382
- accountId?: string,
383
- ): Promise<string> {
384
- // Check if topic exists
385
- const topics = await sns.listTopics()
386
- const existingTopic = topics.Topics?.find(t => t.TopicArn?.endsWith(`:${topicName}`))
387
-
388
- if (existingTopic) {
389
- return existingTopic.TopicArn!
390
- }
391
-
392
- // Create new topic
393
- const params = new URLSearchParams({
394
- Action: 'CreateTopic',
395
- Version: '2010-03-31',
396
- Name: topicName,
397
- })
398
-
399
- const result = await awsClient.request({
400
- service: 'sns',
401
- region,
402
- method: 'POST',
403
- path: '/',
404
- headers: {
405
- 'Content-Type': 'application/x-www-form-urlencoded',
406
- },
407
- body: params.toString(),
408
- })
409
-
410
- const topicArn = result?.CreateTopicResponse?.CreateTopicResult?.TopicArn
411
- if (!topicArn) {
412
- throw new Error('Failed to create SNS topic')
413
- }
414
-
415
- // Set up topic policy to allow SMS Voice service to publish
416
- const policyParams = new URLSearchParams({
417
- Action: 'SetTopicAttributes',
418
- Version: '2010-03-31',
419
- TopicArn: topicArn,
420
- AttributeName: 'Policy',
421
- AttributeValue: JSON.stringify({
422
- Version: '2012-10-17',
423
- Statement: [
424
- {
425
- Sid: 'AllowSMSVoicePublish',
426
- Effect: 'Allow',
427
- Principal: {
428
- Service: 'sms-voice.amazonaws.com',
429
- },
430
- Action: 'sns:Publish',
431
- Resource: topicArn,
432
- },
433
- ],
434
- }),
435
- })
436
-
437
- await awsClient.request({
438
- service: 'sns',
439
- region,
440
- method: 'POST',
441
- path: '/',
442
- headers: {
443
- 'Content-Type': 'application/x-www-form-urlencoded',
444
- },
445
- body: policyParams.toString(),
446
- })
447
-
448
- return topicArn
449
- }
450
-
451
- /**
452
- * Set up SNS topic for delivery receipts
453
- */
454
- async function setupDeliveryReceiptsTopic(
455
- sns: SNSClient,
456
- awsClient: AWSClient,
457
- region: string,
458
- topicName: string,
459
- accountId?: string,
460
- ): Promise<string> {
461
- // Same as two-way topic setup
462
- return setupTwoWayTopic(sns, awsClient, region, topicName, accountId)
463
- }
464
-
465
- /**
466
- * Get complete SMS infrastructure status
467
- */
468
- export async function getSmsInfrastructureStatus(config: {
469
- region?: string
470
- accountName?: string
471
- }): Promise<{
472
- sandboxStatus: 'IN_SANDBOX' | 'OUT_OF_SANDBOX' | 'UNKNOWN'
473
- spendingLimit: number
474
- topics: Array<{
475
- name: string
476
- arn: string
477
- }>
478
- }> {
479
- const region = config.region || 'us-east-1'
480
- const sns = new SNSClient(region)
481
-
482
- // Get sandbox status
483
- const accountStatus = await checkSmsAccountStatus(sns)
484
-
485
- // Get topics
486
- const topicsResult = await sns.listTopics()
487
- const smsTopics = (topicsResult.Topics || [])
488
- .filter(t => t.TopicArn?.includes('sms'))
489
- .map(t => ({
490
- name: t.TopicArn?.split(':').pop() || '',
491
- arn: t.TopicArn || '',
492
- }))
493
-
494
- return {
495
- sandboxStatus: accountStatus.inSandbox ? 'IN_SANDBOX' : 'OUT_OF_SANDBOX',
496
- spendingLimit: accountStatus.spendingLimit,
497
- topics: smsTopics,
498
- }
499
- }
500
-
501
- /**
502
- * Create SMS infrastructure for Stacks deploy
503
- * This is the main entry point called by the deploy command
504
- */
505
- export async function createSmsInfrastructure(smsConfig: {
506
- enabled: boolean
507
- provider: 'sns'
508
- originationNumber?: string
509
- defaultCountryCode: string
510
- messageType: 'TRANSACTIONAL' | 'PROMOTIONAL'
511
- maxSpendPerMonth?: number
512
- inbox?: {
513
- enabled: boolean
514
- bucket: string
515
- prefix?: string
516
- retentionDays?: number
517
- }
518
- twoWay?: {
519
- enabled: boolean
520
- snsTopicArn?: string
521
- }
522
- optOut: {
523
- enabled: boolean
524
- keywords: string[]
525
- }
526
- }): Promise<SmsSetupResult> {
527
- if (!smsConfig.enabled) {
528
- return {
529
- success: true,
530
- errors: [],
531
- warnings: ['SMS is disabled in config'],
532
- }
533
- }
534
-
535
- // Build setup config from Stacks SMS config
536
- const setupConfig: SmsSetupConfig = {
537
- region: 'us-east-1',
538
- accountName: 'stacks',
539
- inbox: smsConfig.inbox
540
- ? {
541
- enabled: smsConfig.inbox.enabled,
542
- bucket: smsConfig.inbox.bucket,
543
- prefix: smsConfig.inbox.prefix,
544
- retentionDays: smsConfig.inbox.retentionDays,
545
- }
546
- : undefined,
547
- twoWay: smsConfig.twoWay
548
- ? {
549
- enabled: smsConfig.twoWay.enabled,
550
- }
551
- : undefined,
552
- spending: {
553
- monthlyLimit: smsConfig.maxSpendPerMonth,
554
- autoRequestIncrease: true,
555
- },
556
- sandbox: {
557
- autoRequestExit: true,
558
- companyName: 'Stacks',
559
- useCase:
560
- 'Transactional notifications, verification codes, and account alerts for web applications built with Stacks framework.',
561
- expectedMonthlyVolume: 5000,
562
- websiteUrl: 'https://stacksjs.com',
563
- },
564
- deliveryReceipts: {
565
- enabled: true,
566
- },
567
- }
568
-
569
- return setupSmsInfrastructure(setupConfig)
570
- }
571
-
572
- export default {
573
- setupSmsInfrastructure: setupSmsInfrastructure,
574
- getSmsInfrastructureStatus: getSmsInfrastructureStatus,
575
- createSmsInfrastructure: createSmsInfrastructure,
576
- } as {
577
- setupSmsInfrastructure: typeof setupSmsInfrastructure
578
- getSmsInfrastructureStatus: typeof getSmsInfrastructureStatus
579
- createSmsInfrastructure: typeof createSmsInfrastructure
580
- }