@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.
- package/README.md +98 -13
- package/dist/aws/acm.d.ts +129 -0
- package/dist/aws/application-autoscaling.d.ts +282 -0
- package/dist/aws/bedrock.d.ts +2292 -0
- package/dist/aws/client.d.ts +79 -0
- package/dist/aws/cloudformation.d.ts +105 -0
- package/dist/aws/cloudfront.d.ts +265 -0
- package/dist/aws/cloudwatch-logs.d.ts +48 -0
- package/dist/aws/comprehend.d.ts +505 -0
- package/dist/aws/connect.d.ts +377 -0
- package/dist/aws/deploy-imap.d.ts +14 -0
- package/dist/aws/dynamodb.d.ts +176 -0
- package/dist/aws/ec2.d.ts +272 -0
- package/dist/aws/ecr.d.ts +149 -0
- package/dist/aws/ecs.d.ts +162 -0
- package/dist/aws/elasticache.d.ts +71 -0
- package/dist/aws/elbv2.d.ts +248 -0
- package/dist/aws/email.d.ts +175 -0
- package/dist/aws/eventbridge.d.ts +142 -0
- package/dist/aws/iam.d.ts +638 -0
- package/dist/aws/imap-server.d.ts +119 -0
- package/{src/aws/index.ts → dist/aws/index.d.ts} +62 -83
- package/{src/aws/kendra.ts → dist/aws/kendra.d.ts} +71 -386
- package/dist/aws/lambda.d.ts +232 -0
- package/dist/aws/opensearch.d.ts +87 -0
- package/dist/aws/personalize.d.ts +516 -0
- package/dist/aws/polly.d.ts +214 -0
- package/dist/aws/rds.d.ts +240 -0
- package/dist/aws/rekognition.d.ts +543 -0
- package/dist/aws/route53-domains.d.ts +113 -0
- package/dist/aws/route53.d.ts +215 -0
- package/dist/aws/s3.d.ts +212 -0
- package/dist/aws/scheduler.d.ts +140 -0
- package/dist/aws/secrets-manager.d.ts +170 -0
- package/dist/aws/ses.d.ts +288 -0
- package/dist/aws/setup-phone.d.ts +0 -0
- package/dist/aws/setup-sms.d.ts +115 -0
- package/dist/aws/sms.d.ts +304 -0
- package/dist/aws/smtp-server.d.ts +61 -0
- package/dist/aws/sns.d.ts +117 -0
- package/dist/aws/sqs.d.ts +65 -0
- package/dist/aws/ssm.d.ts +179 -0
- package/dist/aws/sts.d.ts +15 -0
- package/dist/aws/support.d.ts +104 -0
- package/dist/aws/test-imap.d.ts +0 -0
- package/dist/aws/textract.d.ts +403 -0
- package/dist/aws/transcribe.d.ts +60 -0
- package/dist/aws/translate.d.ts +358 -0
- package/dist/aws/voice.d.ts +219 -0
- package/dist/bin/cli.js +1724 -0
- package/dist/config.d.ts +7 -0
- package/dist/deploy/index.d.ts +2 -0
- package/dist/deploy/static-site-external-dns.d.ts +51 -0
- package/dist/deploy/static-site.d.ts +71 -0
- package/dist/dns/cloudflare.d.ts +52 -0
- package/dist/dns/godaddy.d.ts +38 -0
- package/dist/dns/index.d.ts +45 -0
- package/dist/dns/porkbun.d.ts +18 -0
- package/dist/dns/route53-adapter.d.ts +38 -0
- package/{src/dns/types.ts → dist/dns/types.d.ts} +26 -63
- package/dist/dns/validator.d.ts +78 -0
- package/dist/generators/index.d.ts +1 -0
- package/dist/generators/infrastructure.d.ts +30 -0
- package/{src/index.ts → dist/index.d.ts} +70 -93
- package/dist/index.js +7881 -0
- package/dist/push/apns.d.ts +60 -0
- package/dist/push/fcm.d.ts +117 -0
- package/dist/push/index.d.ts +14 -0
- package/dist/security/pre-deploy-scanner.d.ts +69 -0
- package/dist/ssl/acme-client.d.ts +67 -0
- package/dist/ssl/index.d.ts +2 -0
- package/dist/ssl/letsencrypt.d.ts +48 -0
- package/dist/types.d.ts +1 -0
- package/dist/utils/cli.d.ts +123 -0
- package/dist/validation/index.d.ts +1 -0
- package/dist/validation/template.d.ts +23 -0
- package/package.json +8 -8
- package/bin/cli.ts +0 -133
- package/bin/commands/analytics.ts +0 -328
- package/bin/commands/api.ts +0 -379
- package/bin/commands/assets.ts +0 -221
- package/bin/commands/audit.ts +0 -501
- package/bin/commands/backup.ts +0 -682
- package/bin/commands/cache.ts +0 -294
- package/bin/commands/cdn.ts +0 -281
- package/bin/commands/config.ts +0 -202
- package/bin/commands/container.ts +0 -105
- package/bin/commands/cost.ts +0 -208
- package/bin/commands/database.ts +0 -401
- package/bin/commands/deploy.ts +0 -674
- package/bin/commands/domain.ts +0 -397
- package/bin/commands/email.ts +0 -423
- package/bin/commands/environment.ts +0 -285
- package/bin/commands/events.ts +0 -424
- package/bin/commands/firewall.ts +0 -145
- package/bin/commands/function.ts +0 -116
- package/bin/commands/generate.ts +0 -280
- package/bin/commands/git.ts +0 -139
- package/bin/commands/iam.ts +0 -464
- package/bin/commands/index.ts +0 -48
- package/bin/commands/init.ts +0 -120
- package/bin/commands/logs.ts +0 -148
- package/bin/commands/network.ts +0 -579
- package/bin/commands/notify.ts +0 -489
- package/bin/commands/queue.ts +0 -407
- package/bin/commands/scheduler.ts +0 -370
- package/bin/commands/secrets.ts +0 -54
- package/bin/commands/server.ts +0 -629
- package/bin/commands/shared.ts +0 -97
- package/bin/commands/ssl.ts +0 -138
- package/bin/commands/stack.ts +0 -325
- package/bin/commands/status.ts +0 -385
- package/bin/commands/storage.ts +0 -450
- package/bin/commands/team.ts +0 -96
- package/bin/commands/tunnel.ts +0 -489
- package/bin/commands/utils.ts +0 -202
- package/build.ts +0 -15
- package/cloud +0 -2
- package/src/aws/acm.ts +0 -768
- package/src/aws/application-autoscaling.ts +0 -845
- package/src/aws/bedrock.ts +0 -4074
- package/src/aws/client.ts +0 -878
- package/src/aws/cloudformation.ts +0 -896
- package/src/aws/cloudfront.ts +0 -1531
- package/src/aws/cloudwatch-logs.ts +0 -154
- package/src/aws/comprehend.ts +0 -839
- package/src/aws/connect.ts +0 -1056
- package/src/aws/deploy-imap.ts +0 -384
- package/src/aws/dynamodb.ts +0 -340
- package/src/aws/ec2.ts +0 -1385
- package/src/aws/ecr.ts +0 -621
- package/src/aws/ecs.ts +0 -615
- package/src/aws/elasticache.ts +0 -301
- package/src/aws/elbv2.ts +0 -942
- package/src/aws/email.ts +0 -928
- package/src/aws/eventbridge.ts +0 -248
- package/src/aws/iam.ts +0 -1689
- package/src/aws/imap-server.ts +0 -2100
- package/src/aws/lambda.ts +0 -786
- package/src/aws/opensearch.ts +0 -158
- package/src/aws/personalize.ts +0 -977
- package/src/aws/polly.ts +0 -559
- package/src/aws/rds.ts +0 -888
- package/src/aws/rekognition.ts +0 -846
- package/src/aws/route53-domains.ts +0 -359
- package/src/aws/route53.ts +0 -1046
- package/src/aws/s3.ts +0 -2318
- package/src/aws/scheduler.ts +0 -571
- package/src/aws/secrets-manager.ts +0 -769
- package/src/aws/ses.ts +0 -1081
- package/src/aws/setup-phone.ts +0 -104
- package/src/aws/setup-sms.ts +0 -580
- package/src/aws/sms.ts +0 -1735
- package/src/aws/smtp-server.ts +0 -531
- package/src/aws/sns.ts +0 -758
- package/src/aws/sqs.ts +0 -382
- package/src/aws/ssm.ts +0 -807
- package/src/aws/sts.ts +0 -92
- package/src/aws/support.ts +0 -391
- package/src/aws/test-imap.ts +0 -86
- package/src/aws/textract.ts +0 -780
- package/src/aws/transcribe.ts +0 -108
- package/src/aws/translate.ts +0 -641
- package/src/aws/voice.ts +0 -1379
- package/src/config.ts +0 -35
- package/src/deploy/index.ts +0 -7
- package/src/deploy/static-site-external-dns.ts +0 -906
- package/src/deploy/static-site.ts +0 -1125
- package/src/dns/godaddy.ts +0 -412
- package/src/dns/index.ts +0 -183
- package/src/dns/porkbun.ts +0 -362
- package/src/dns/route53-adapter.ts +0 -414
- package/src/dns/validator.ts +0 -369
- package/src/generators/index.ts +0 -5
- package/src/generators/infrastructure.ts +0 -1660
- package/src/push/apns.ts +0 -452
- package/src/push/fcm.ts +0 -506
- package/src/push/index.ts +0 -58
- package/src/ssl/acme-client.ts +0 -478
- package/src/ssl/index.ts +0 -7
- package/src/ssl/letsencrypt.ts +0 -747
- package/src/types.ts +0 -2
- package/src/utils/cli.ts +0 -398
- package/src/validation/index.ts +0 -5
- package/src/validation/template.ts +0 -405
- package/test/index.test.ts +0 -128
- package/tsconfig.json +0 -18
package/bin/commands/email.ts
DELETED
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
import type { CLI } from '@stacksjs/clapp'
|
|
2
|
-
import * as cli from '../../src/utils/cli'
|
|
3
|
-
import { SESClient } from '../../src/aws/ses'
|
|
4
|
-
import { loadValidatedConfig } from './shared'
|
|
5
|
-
|
|
6
|
-
export function registerEmailCommands(app: CLI): void {
|
|
7
|
-
app
|
|
8
|
-
.command('email:identities', 'List verified email identities')
|
|
9
|
-
.option('--region <region>', 'AWS region')
|
|
10
|
-
.action(async (options: { region?: string }) => {
|
|
11
|
-
cli.header('Email Identities')
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const config = await loadValidatedConfig()
|
|
15
|
-
const region = options.region || config.project.region || 'us-east-1'
|
|
16
|
-
const ses = new SESClient(region)
|
|
17
|
-
|
|
18
|
-
const spinner = new cli.Spinner('Fetching identities...')
|
|
19
|
-
spinner.start()
|
|
20
|
-
|
|
21
|
-
const result = await ses.listEmailIdentities()
|
|
22
|
-
const identities = result.EmailIdentities || []
|
|
23
|
-
|
|
24
|
-
spinner.succeed(`Found ${identities.length} identity(s)`)
|
|
25
|
-
|
|
26
|
-
if (identities.length === 0) {
|
|
27
|
-
cli.info('No email identities found')
|
|
28
|
-
cli.info('Use `cloud email:verify` to verify an email or domain')
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Get verification status for each identity
|
|
33
|
-
const rows: string[][] = []
|
|
34
|
-
for (const identity of identities) {
|
|
35
|
-
const name = identity.IdentityName || 'Unknown'
|
|
36
|
-
let verificationStatus = 'Unknown'
|
|
37
|
-
try {
|
|
38
|
-
const detail = await ses.getEmailIdentity(name)
|
|
39
|
-
verificationStatus = detail.VerificationStatus || 'Unknown'
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
// If we can't fetch details, just show what we have
|
|
43
|
-
}
|
|
44
|
-
const type = identity.IdentityType === 'EMAIL_ADDRESS' ? 'Email' : identity.IdentityType === 'DOMAIN' ? 'Domain' : (identity.IdentityType || 'Unknown')
|
|
45
|
-
rows.push([name, type, verificationStatus])
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
cli.table(
|
|
49
|
-
['Identity', 'Type', 'Verification Status'],
|
|
50
|
-
rows,
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
catch (error: unknown) {
|
|
54
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
55
|
-
cli.error(`Failed to list identities: ${message}`)
|
|
56
|
-
process.exit(1)
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
app
|
|
61
|
-
.command('email:verify <identity>', 'Verify an email address or domain')
|
|
62
|
-
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
|
|
63
|
-
.action(async (identity: string, options: { region: string }) => {
|
|
64
|
-
cli.header('Verify Email Identity')
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const ses = new SESClient(options.region)
|
|
68
|
-
|
|
69
|
-
const isEmail = identity.includes('@')
|
|
70
|
-
|
|
71
|
-
cli.info(`Identity: ${identity}`)
|
|
72
|
-
cli.info(`Type: ${isEmail ? 'Email Address' : 'Domain'}`)
|
|
73
|
-
|
|
74
|
-
const confirmed = await cli.confirm('\nSend verification?', true)
|
|
75
|
-
if (!confirmed) {
|
|
76
|
-
cli.info('Operation cancelled')
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const spinner = new cli.Spinner('Initiating verification...')
|
|
81
|
-
spinner.start()
|
|
82
|
-
|
|
83
|
-
if (isEmail) {
|
|
84
|
-
await ses.createEmailIdentity({ EmailIdentity: identity })
|
|
85
|
-
spinner.succeed('Verification email sent')
|
|
86
|
-
|
|
87
|
-
cli.info(`\nA verification email has been sent to ${identity}`)
|
|
88
|
-
cli.info('Click the link in the email to complete verification.')
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
const result = await ses.verifyDomain(identity)
|
|
92
|
-
spinner.succeed('Domain verification initiated')
|
|
93
|
-
|
|
94
|
-
const dkimTokens = result.dkimTokens || []
|
|
95
|
-
|
|
96
|
-
if (dkimTokens.length > 0) {
|
|
97
|
-
cli.info('\nDKIM Records (for email authentication):')
|
|
98
|
-
for (const token of dkimTokens) {
|
|
99
|
-
cli.info(`\n Name: ${token}._domainkey.${identity}`)
|
|
100
|
-
cli.info(` Type: CNAME`)
|
|
101
|
-
cli.info(` Value: ${token}.dkim.amazonses.com`)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
cli.info(`\nVerification Status: ${result.verificationStatus || 'PENDING'}`)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
catch (error: unknown) {
|
|
109
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
110
|
-
cli.error(`Failed to verify identity: ${message}`)
|
|
111
|
-
process.exit(1)
|
|
112
|
-
}
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
app
|
|
116
|
-
.command('email:delete <identity>', 'Delete a verified email identity')
|
|
117
|
-
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
|
|
118
|
-
.action(async (identity: string, options: { region: string }) => {
|
|
119
|
-
cli.header('Delete Email Identity')
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
const ses = new SESClient(options.region)
|
|
123
|
-
|
|
124
|
-
cli.warn(`This will remove identity: ${identity}`)
|
|
125
|
-
|
|
126
|
-
const confirmed = await cli.confirm('\nDelete this identity?', false)
|
|
127
|
-
if (!confirmed) {
|
|
128
|
-
cli.info('Operation cancelled')
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const spinner = new cli.Spinner('Deleting identity...')
|
|
133
|
-
spinner.start()
|
|
134
|
-
|
|
135
|
-
await ses.deleteEmailIdentity(identity)
|
|
136
|
-
|
|
137
|
-
spinner.succeed('Identity deleted')
|
|
138
|
-
}
|
|
139
|
-
catch (error: unknown) {
|
|
140
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
141
|
-
cli.error(`Failed to delete identity: ${message}`)
|
|
142
|
-
process.exit(1)
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
app
|
|
147
|
-
.command('email:send', 'Send a test email')
|
|
148
|
-
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
|
|
149
|
-
.option('--from <email>', 'From email address (must be verified)')
|
|
150
|
-
.option('--to <email>', 'To email address')
|
|
151
|
-
.option('--subject <text>', 'Email subject')
|
|
152
|
-
.option('--body <text>', 'Email body (text)')
|
|
153
|
-
.option('--html <html>', 'Email body (HTML)')
|
|
154
|
-
.action(async (options: {
|
|
155
|
-
region: string
|
|
156
|
-
from?: string
|
|
157
|
-
to?: string
|
|
158
|
-
subject?: string
|
|
159
|
-
body?: string
|
|
160
|
-
html?: string
|
|
161
|
-
}) => {
|
|
162
|
-
cli.header('Send Email')
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
const ses = new SESClient(options.region)
|
|
166
|
-
|
|
167
|
-
const from = options.from || await cli.prompt('From (verified email)')
|
|
168
|
-
const to = options.to || await cli.prompt('To')
|
|
169
|
-
const subject = options.subject || await cli.prompt('Subject', 'Test Email from ts-cloud')
|
|
170
|
-
const body = options.body || await cli.prompt('Body', 'This is a test email sent from ts-cloud CLI.')
|
|
171
|
-
|
|
172
|
-
cli.info(`\nFrom: ${from}`)
|
|
173
|
-
cli.info(`To: ${to}`)
|
|
174
|
-
cli.info(`Subject: ${subject}`)
|
|
175
|
-
|
|
176
|
-
const confirmed = await cli.confirm('\nSend this email?', true)
|
|
177
|
-
if (!confirmed) {
|
|
178
|
-
cli.info('Operation cancelled')
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const spinner = new cli.Spinner('Sending email...')
|
|
183
|
-
spinner.start()
|
|
184
|
-
|
|
185
|
-
const result = await ses.sendEmail({
|
|
186
|
-
FromEmailAddress: from,
|
|
187
|
-
Destination: {
|
|
188
|
-
ToAddresses: [to],
|
|
189
|
-
},
|
|
190
|
-
Content: {
|
|
191
|
-
Simple: {
|
|
192
|
-
Subject: {
|
|
193
|
-
Data: subject,
|
|
194
|
-
},
|
|
195
|
-
Body: {
|
|
196
|
-
Text: {
|
|
197
|
-
Data: body,
|
|
198
|
-
},
|
|
199
|
-
...(options.html && {
|
|
200
|
-
Html: {
|
|
201
|
-
Data: options.html,
|
|
202
|
-
},
|
|
203
|
-
}),
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
spinner.succeed('Email sent')
|
|
210
|
-
|
|
211
|
-
cli.success(`\nMessage ID: ${result.MessageId}`)
|
|
212
|
-
}
|
|
213
|
-
catch (error: unknown) {
|
|
214
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
215
|
-
cli.error(`Failed to send email: ${message}`)
|
|
216
|
-
process.exit(1)
|
|
217
|
-
}
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
app
|
|
221
|
-
.command('email:templates', 'List email templates')
|
|
222
|
-
.option('--region <region>', 'AWS region')
|
|
223
|
-
.action(async (options: { region?: string }) => {
|
|
224
|
-
cli.header('Email Templates')
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
const config = await loadValidatedConfig()
|
|
228
|
-
const region = options.region || config.project.region || 'us-east-1'
|
|
229
|
-
const ses = new SESClient(region)
|
|
230
|
-
|
|
231
|
-
const spinner = new cli.Spinner('Fetching templates...')
|
|
232
|
-
spinner.start()
|
|
233
|
-
|
|
234
|
-
const result = await ses.listEmailTemplates()
|
|
235
|
-
const templates = result.TemplatesMetadata || []
|
|
236
|
-
|
|
237
|
-
spinner.succeed(`Found ${templates.length} template(s)`)
|
|
238
|
-
|
|
239
|
-
if (templates.length === 0) {
|
|
240
|
-
cli.info('No email templates found')
|
|
241
|
-
cli.info('Use `cloud email:template:create` to create a template')
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
cli.table(
|
|
246
|
-
['Name', 'Created'],
|
|
247
|
-
templates.map((t: { TemplateName?: string, CreatedTimestamp?: string }) => [
|
|
248
|
-
t.TemplateName || 'N/A',
|
|
249
|
-
t.CreatedTimestamp ? new Date(t.CreatedTimestamp).toLocaleString() : 'N/A',
|
|
250
|
-
]),
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
catch (error: unknown) {
|
|
254
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
255
|
-
cli.error(`Failed to list templates: ${message}`)
|
|
256
|
-
process.exit(1)
|
|
257
|
-
}
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
app
|
|
261
|
-
.command('email:template:create <name>', 'Create an email template')
|
|
262
|
-
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
|
|
263
|
-
.option('--subject <text>', 'Email subject (supports {{variable}} placeholders)')
|
|
264
|
-
.option('--text <text>', 'Text body')
|
|
265
|
-
.option('--html <html>', 'HTML body')
|
|
266
|
-
.option('--html-file <path>', 'HTML body from file')
|
|
267
|
-
.action(async (name: string, options: {
|
|
268
|
-
region: string
|
|
269
|
-
subject?: string
|
|
270
|
-
text?: string
|
|
271
|
-
html?: string
|
|
272
|
-
htmlFile?: string
|
|
273
|
-
}) => {
|
|
274
|
-
cli.header('Create Email Template')
|
|
275
|
-
|
|
276
|
-
try {
|
|
277
|
-
const ses = new SESClient(options.region)
|
|
278
|
-
|
|
279
|
-
const subject = options.subject || await cli.prompt('Subject template', 'Hello {{name}}')
|
|
280
|
-
const textBody = options.text || await cli.prompt('Text body', 'Hello {{name}}, this is a test.')
|
|
281
|
-
|
|
282
|
-
let htmlBody = options.html
|
|
283
|
-
if (options.htmlFile) {
|
|
284
|
-
const file = Bun.file(options.htmlFile)
|
|
285
|
-
htmlBody = await file.text()
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
cli.info(`\nTemplate Name: ${name}`)
|
|
289
|
-
cli.info(`Subject: ${subject}`)
|
|
290
|
-
|
|
291
|
-
const confirmed = await cli.confirm('\nCreate this template?', true)
|
|
292
|
-
if (!confirmed) {
|
|
293
|
-
cli.info('Operation cancelled')
|
|
294
|
-
return
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const spinner = new cli.Spinner('Creating template...')
|
|
298
|
-
spinner.start()
|
|
299
|
-
|
|
300
|
-
await ses.createEmailTemplate({
|
|
301
|
-
TemplateName: name,
|
|
302
|
-
TemplateContent: {
|
|
303
|
-
Subject: subject,
|
|
304
|
-
Text: textBody,
|
|
305
|
-
Html: htmlBody,
|
|
306
|
-
},
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
spinner.succeed('Template created')
|
|
310
|
-
|
|
311
|
-
cli.info('\nTo send using this template:')
|
|
312
|
-
cli.info(` cloud email:send:template --template ${name} --to user@example.com --data '{"name":"John"}'`)
|
|
313
|
-
}
|
|
314
|
-
catch (error: unknown) {
|
|
315
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
316
|
-
cli.error(`Failed to create template: ${message}`)
|
|
317
|
-
process.exit(1)
|
|
318
|
-
}
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
app
|
|
322
|
-
.command('email:template:delete <name>', 'Delete an email template')
|
|
323
|
-
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
|
|
324
|
-
.action(async (name: string, options: { region: string }) => {
|
|
325
|
-
cli.header('Delete Email Template')
|
|
326
|
-
|
|
327
|
-
try {
|
|
328
|
-
const ses = new SESClient(options.region)
|
|
329
|
-
|
|
330
|
-
cli.warn(`This will delete template: ${name}`)
|
|
331
|
-
|
|
332
|
-
const confirmed = await cli.confirm('\nDelete this template?', false)
|
|
333
|
-
if (!confirmed) {
|
|
334
|
-
cli.info('Operation cancelled')
|
|
335
|
-
return
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const spinner = new cli.Spinner('Deleting template...')
|
|
339
|
-
spinner.start()
|
|
340
|
-
|
|
341
|
-
await ses.deleteEmailTemplate(name)
|
|
342
|
-
|
|
343
|
-
spinner.succeed('Template deleted')
|
|
344
|
-
}
|
|
345
|
-
catch (error: unknown) {
|
|
346
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
347
|
-
cli.error(`Failed to delete template: ${message}`)
|
|
348
|
-
process.exit(1)
|
|
349
|
-
}
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
app
|
|
353
|
-
.command('email:stats', 'Show SES sending statistics')
|
|
354
|
-
.option('--region <region>', 'AWS region')
|
|
355
|
-
.action(async (options: { region?: string }) => {
|
|
356
|
-
cli.header('Email Statistics')
|
|
357
|
-
|
|
358
|
-
try {
|
|
359
|
-
const config = await loadValidatedConfig()
|
|
360
|
-
const region = options.region || config.project.region || 'us-east-1'
|
|
361
|
-
const ses = new SESClient(region)
|
|
362
|
-
|
|
363
|
-
const spinner = new cli.Spinner('Fetching statistics...')
|
|
364
|
-
spinner.start()
|
|
365
|
-
|
|
366
|
-
const [quota, stats] = await Promise.all([
|
|
367
|
-
ses.getSendQuota(),
|
|
368
|
-
ses.getSendStatistics(),
|
|
369
|
-
])
|
|
370
|
-
|
|
371
|
-
spinner.succeed('Statistics loaded')
|
|
372
|
-
|
|
373
|
-
cli.info('\nSending Quota:')
|
|
374
|
-
cli.info(` Max 24-hour Send: ${quota.Max24HourSend || 0}`)
|
|
375
|
-
cli.info(` Max Send Rate: ${quota.MaxSendRate || 0} emails/second`)
|
|
376
|
-
cli.info(` Sent Last 24h: ${quota.SentLast24Hours || 0}`)
|
|
377
|
-
|
|
378
|
-
const remaining = (quota.Max24HourSend || 0) - (quota.SentLast24Hours || 0)
|
|
379
|
-
cli.info(` Remaining: ${remaining}`)
|
|
380
|
-
|
|
381
|
-
if (stats.SendDataPoints && stats.SendDataPoints.length > 0) {
|
|
382
|
-
cli.info('\nRecent Statistics:')
|
|
383
|
-
|
|
384
|
-
// Aggregate stats
|
|
385
|
-
let totalDelivered = 0
|
|
386
|
-
let totalBounces = 0
|
|
387
|
-
let totalComplaints = 0
|
|
388
|
-
let totalRejects = 0
|
|
389
|
-
|
|
390
|
-
for (const point of stats.SendDataPoints) {
|
|
391
|
-
totalDelivered += point.DeliveryAttempts || 0
|
|
392
|
-
totalBounces += point.Bounces || 0
|
|
393
|
-
totalComplaints += point.Complaints || 0
|
|
394
|
-
totalRejects += point.Rejects || 0
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
cli.info(` Total Attempts: ${totalDelivered}`)
|
|
398
|
-
cli.info(` Bounces: ${totalBounces}`)
|
|
399
|
-
cli.info(` Complaints: ${totalComplaints}`)
|
|
400
|
-
cli.info(` Rejects: ${totalRejects}`)
|
|
401
|
-
|
|
402
|
-
if (totalDelivered > 0) {
|
|
403
|
-
const bounceRate = ((totalBounces / totalDelivered) * 100).toFixed(2)
|
|
404
|
-
const complaintRate = ((totalComplaints / totalDelivered) * 100).toFixed(2)
|
|
405
|
-
cli.info(`\n Bounce Rate: ${bounceRate}%`)
|
|
406
|
-
cli.info(` Complaint Rate: ${complaintRate}%`)
|
|
407
|
-
|
|
408
|
-
if (Number.parseFloat(bounceRate) > 5) {
|
|
409
|
-
cli.warn('\n Warning: Bounce rate is high (>5%). This may affect your sender reputation.')
|
|
410
|
-
}
|
|
411
|
-
if (Number.parseFloat(complaintRate) > 0.1) {
|
|
412
|
-
cli.warn('\n Warning: Complaint rate is high (>0.1%). This may affect your sender reputation.')
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
catch (error: unknown) {
|
|
418
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
419
|
-
cli.error(`Failed to get statistics: ${message}`)
|
|
420
|
-
process.exit(1)
|
|
421
|
-
}
|
|
422
|
-
})
|
|
423
|
-
}
|
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import type { CLI } from '@stacksjs/clapp'
|
|
2
|
-
import * as cli from '../../src/utils/cli'
|
|
3
|
-
|
|
4
|
-
export function registerEnvironmentCommands(app: CLI): void {
|
|
5
|
-
app
|
|
6
|
-
.command('env:create <name>', 'Create new environment')
|
|
7
|
-
.option('--clone <source>', 'Clone from existing environment')
|
|
8
|
-
.action(async (name: string, options?: { clone?: string }) => {
|
|
9
|
-
cli.header(`Creating Environment: ${name}`)
|
|
10
|
-
|
|
11
|
-
const validEnvs = ['production', 'staging', 'development', 'preview', 'test']
|
|
12
|
-
if (!validEnvs.includes(name.toLowerCase())) {
|
|
13
|
-
cli.warn(`Warning: Creating non-standard environment name`)
|
|
14
|
-
cli.info(`Standard names: ${validEnvs.join(', ')}`)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (options?.clone) {
|
|
18
|
-
cli.info(`Cloning from: ${options.clone}`)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const confirm = await cli.confirm('\nCreate this environment?', true)
|
|
22
|
-
if (!confirm) {
|
|
23
|
-
cli.info('Operation cancelled')
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const spinner = new cli.Spinner('Creating environment infrastructure...')
|
|
28
|
-
spinner.start()
|
|
29
|
-
|
|
30
|
-
// TODO: Create CloudFormation stack for new environment
|
|
31
|
-
// TODO: If cloning, copy configuration from source environment
|
|
32
|
-
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
33
|
-
|
|
34
|
-
spinner.succeed('Environment created successfully')
|
|
35
|
-
|
|
36
|
-
cli.success('\nEnvironment created!')
|
|
37
|
-
cli.info(`Environment ${name} is now available`)
|
|
38
|
-
|
|
39
|
-
cli.info('\nNext steps:')
|
|
40
|
-
cli.info(` - Deploy to environment: cloud deploy --env ${name}`)
|
|
41
|
-
cli.info(` - Switch to environment: cloud env:switch ${name}`)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
app
|
|
45
|
-
.command('env:list', 'List environments')
|
|
46
|
-
.action(async () => {
|
|
47
|
-
cli.header('Environments')
|
|
48
|
-
|
|
49
|
-
const spinner = new cli.Spinner('Fetching environments...')
|
|
50
|
-
spinner.start()
|
|
51
|
-
|
|
52
|
-
// TODO: Fetch from CloudFormation stacks or config
|
|
53
|
-
await new Promise(resolve => setTimeout(resolve, 1500))
|
|
54
|
-
|
|
55
|
-
spinner.stop()
|
|
56
|
-
|
|
57
|
-
cli.table(
|
|
58
|
-
['Environment', 'Status', 'Region', 'Last Deployed', 'Active'],
|
|
59
|
-
[
|
|
60
|
-
['production', 'Active', 'us-east-1', '2 hours ago', ''],
|
|
61
|
-
['staging', 'Active', 'us-east-1', '1 day ago', '*'],
|
|
62
|
-
['development', 'Active', 'us-west-2', '3 days ago', ''],
|
|
63
|
-
['preview-pr-123', 'Active', 'us-east-1', '5 hours ago', ''],
|
|
64
|
-
],
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
cli.info('\nTip: Use `cloud env:switch NAME` to switch active environment')
|
|
68
|
-
cli.info('Tip: Use `cloud env:create NAME` to create new environment')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
app
|
|
72
|
-
.command('env:switch <name>', 'Switch active environment')
|
|
73
|
-
.action(async (name: string) => {
|
|
74
|
-
cli.header(`Switching to Environment: ${name}`)
|
|
75
|
-
|
|
76
|
-
cli.info(`Switching to: ${name}`)
|
|
77
|
-
|
|
78
|
-
const spinner = new cli.Spinner('Updating environment configuration...')
|
|
79
|
-
spinner.start()
|
|
80
|
-
|
|
81
|
-
// TODO: Update config to set active environment
|
|
82
|
-
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
83
|
-
|
|
84
|
-
spinner.succeed('Environment switched successfully')
|
|
85
|
-
|
|
86
|
-
cli.success(`\nNow using environment: ${name}`)
|
|
87
|
-
cli.info(`All commands will now target the ${name} environment`)
|
|
88
|
-
|
|
89
|
-
cli.info('\nEnvironment details:')
|
|
90
|
-
cli.info(` - Region: us-east-1`)
|
|
91
|
-
cli.info(` - Status: Active`)
|
|
92
|
-
cli.info(` - Last deployed: 1 day ago`)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
app
|
|
96
|
-
.command('env:clone <source> <target>', 'Clone environment')
|
|
97
|
-
.action(async (source: string, target: string) => {
|
|
98
|
-
cli.header('Cloning Environment')
|
|
99
|
-
|
|
100
|
-
cli.info(`Source: ${source}`)
|
|
101
|
-
cli.info(`Target: ${target}`)
|
|
102
|
-
|
|
103
|
-
cli.warn('\nThis will copy:')
|
|
104
|
-
cli.info(' - Infrastructure configuration')
|
|
105
|
-
cli.info(' - Environment variables')
|
|
106
|
-
cli.info(' - Database schema (not data)')
|
|
107
|
-
|
|
108
|
-
const confirm = await cli.confirm('\nClone environment?', true)
|
|
109
|
-
if (!confirm) {
|
|
110
|
-
cli.info('Operation cancelled')
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const spinner = new cli.Spinner('Cloning environment...')
|
|
115
|
-
spinner.start()
|
|
116
|
-
|
|
117
|
-
// TODO: Copy CloudFormation stack and config
|
|
118
|
-
await new Promise(resolve => setTimeout(resolve, 5000))
|
|
119
|
-
|
|
120
|
-
spinner.succeed('Environment cloned')
|
|
121
|
-
|
|
122
|
-
cli.success(`\nEnvironment ${target} created from ${source}!`)
|
|
123
|
-
cli.info('Deploy with: cloud deploy --env ' + target)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
app
|
|
127
|
-
.command('env:promote <source> <target>', 'Promote environment')
|
|
128
|
-
.action(async (source: string, target: string) => {
|
|
129
|
-
cli.header('Promoting Environment')
|
|
130
|
-
|
|
131
|
-
cli.info(`From: ${source}`)
|
|
132
|
-
cli.info(`To: ${target}`)
|
|
133
|
-
|
|
134
|
-
cli.warn('\nThis will:')
|
|
135
|
-
cli.info(' - Deploy code from source to target')
|
|
136
|
-
cli.info(' - Update target configuration')
|
|
137
|
-
cli.info(' - Run database migrations if any')
|
|
138
|
-
|
|
139
|
-
const confirm = await cli.confirm('\nPromote to ' + target + '?', false)
|
|
140
|
-
if (!confirm) {
|
|
141
|
-
cli.info('Operation cancelled')
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const spinner = new cli.Spinner('Promoting environment...')
|
|
146
|
-
spinner.start()
|
|
147
|
-
|
|
148
|
-
// TODO: Deploy source to target
|
|
149
|
-
await new Promise(resolve => setTimeout(resolve, 6000))
|
|
150
|
-
|
|
151
|
-
spinner.succeed('Promotion complete')
|
|
152
|
-
|
|
153
|
-
cli.success(`\n${source} promoted to ${target}!`)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
app
|
|
157
|
-
.command('env:compare <env1> <env2>', 'Compare configurations')
|
|
158
|
-
.action(async (env1: string, env2: string) => {
|
|
159
|
-
cli.header('Comparing Environments')
|
|
160
|
-
|
|
161
|
-
cli.info(`Environment 1: ${env1}`)
|
|
162
|
-
cli.info(`Environment 2: ${env2}`)
|
|
163
|
-
|
|
164
|
-
const spinner = new cli.Spinner('Analyzing configurations...')
|
|
165
|
-
spinner.start()
|
|
166
|
-
|
|
167
|
-
// TODO: Compare CloudFormation stacks and config
|
|
168
|
-
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
169
|
-
|
|
170
|
-
spinner.stop()
|
|
171
|
-
|
|
172
|
-
cli.info('\nConfiguration Differences:\n')
|
|
173
|
-
|
|
174
|
-
cli.table(
|
|
175
|
-
['Setting', env1, env2, 'Match'],
|
|
176
|
-
[
|
|
177
|
-
['Instance Type', 't3.medium', 't3.small', 'X'],
|
|
178
|
-
['Database Size', 'db.t3.medium', 'db.t3.micro', 'X'],
|
|
179
|
-
['Auto Scaling', 'Enabled', 'Disabled', 'X'],
|
|
180
|
-
['Region', 'us-east-1', 'us-east-1', '*'],
|
|
181
|
-
['Node Version', '20.x', '20.x', '*'],
|
|
182
|
-
],
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
cli.info('\nFound 3 differences')
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
app
|
|
189
|
-
.command('env:sync <source> <target>', 'Sync configuration')
|
|
190
|
-
.action(async (source: string, target: string) => {
|
|
191
|
-
cli.header('Syncing Configuration')
|
|
192
|
-
|
|
193
|
-
cli.info(`Source: ${source}`)
|
|
194
|
-
cli.info(`Target: ${target}`)
|
|
195
|
-
|
|
196
|
-
cli.warn('\nThis will sync configuration (not resources or data)')
|
|
197
|
-
|
|
198
|
-
const confirm = await cli.confirm('\nSync configuration?', true)
|
|
199
|
-
if (!confirm) {
|
|
200
|
-
cli.info('Operation cancelled')
|
|
201
|
-
return
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const spinner = new cli.Spinner('Syncing configuration...')
|
|
205
|
-
spinner.start()
|
|
206
|
-
|
|
207
|
-
// TODO: Sync config files
|
|
208
|
-
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
209
|
-
|
|
210
|
-
spinner.succeed('Configuration synced')
|
|
211
|
-
|
|
212
|
-
cli.success('\nConfiguration synchronized!')
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
app
|
|
216
|
-
.command('env:preview <branch>', 'Create preview environment from branch')
|
|
217
|
-
.action(async (branch: string) => {
|
|
218
|
-
cli.header(`Creating Preview Environment for ${branch}`)
|
|
219
|
-
|
|
220
|
-
const envName = `preview-${branch.replace(/[^a-z0-9]/gi, '-').toLowerCase()}`
|
|
221
|
-
|
|
222
|
-
cli.info(`Environment name: ${envName}`)
|
|
223
|
-
cli.info(`Branch: ${branch}`)
|
|
224
|
-
|
|
225
|
-
const confirm = await cli.confirm('\nCreate preview environment?', true)
|
|
226
|
-
if (!confirm) {
|
|
227
|
-
cli.info('Operation cancelled')
|
|
228
|
-
return
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const spinner = new cli.Spinner('Creating preview environment...')
|
|
232
|
-
spinner.start()
|
|
233
|
-
|
|
234
|
-
// TODO: Create temporary CloudFormation stack
|
|
235
|
-
await new Promise(resolve => setTimeout(resolve, 8000))
|
|
236
|
-
|
|
237
|
-
spinner.succeed('Preview environment created')
|
|
238
|
-
|
|
239
|
-
cli.success('\nPreview environment ready!')
|
|
240
|
-
cli.info(`URL: https://${envName}.preview.example.com`)
|
|
241
|
-
cli.info('\nThis environment will auto-delete after 7 days')
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
app
|
|
245
|
-
.command('env:cleanup', 'Remove stale preview environments')
|
|
246
|
-
.action(async () => {
|
|
247
|
-
cli.header('Cleaning Up Preview Environments')
|
|
248
|
-
|
|
249
|
-
const spinner = new cli.Spinner('Finding stale preview environments...')
|
|
250
|
-
spinner.start()
|
|
251
|
-
|
|
252
|
-
// TODO: Find old preview stacks
|
|
253
|
-
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
254
|
-
|
|
255
|
-
spinner.stop()
|
|
256
|
-
|
|
257
|
-
cli.info('\nFound 3 stale preview environments:\n')
|
|
258
|
-
|
|
259
|
-
cli.table(
|
|
260
|
-
['Environment', 'Created', 'Age', 'Status'],
|
|
261
|
-
[
|
|
262
|
-
['preview-feature-123', '2024-10-15', '30 days', 'Inactive'],
|
|
263
|
-
['preview-bugfix-456', '2024-10-20', '25 days', 'Inactive'],
|
|
264
|
-
['preview-test-789', '2024-11-01', '14 days', 'Inactive'],
|
|
265
|
-
],
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
const confirm = await cli.confirm('\nDelete these environments?', true)
|
|
269
|
-
if (!confirm) {
|
|
270
|
-
cli.info('Operation cancelled')
|
|
271
|
-
return
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const cleanupSpinner = new cli.Spinner('Deleting stale environments...')
|
|
275
|
-
cleanupSpinner.start()
|
|
276
|
-
|
|
277
|
-
// TODO: Delete CloudFormation stacks
|
|
278
|
-
await new Promise(resolve => setTimeout(resolve, 4000))
|
|
279
|
-
|
|
280
|
-
cleanupSpinner.succeed('Cleanup complete')
|
|
281
|
-
|
|
282
|
-
cli.success('\n3 preview environments deleted!')
|
|
283
|
-
cli.info('Estimated monthly savings: $87')
|
|
284
|
-
})
|
|
285
|
-
}
|