@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.
- package/LICENSE.md +21 -0
- package/README.md +321 -0
- package/bin/cli.ts +133 -0
- package/bin/commands/analytics.ts +328 -0
- package/bin/commands/api.ts +379 -0
- package/bin/commands/assets.ts +221 -0
- package/bin/commands/audit.ts +501 -0
- package/bin/commands/backup.ts +682 -0
- package/bin/commands/cache.ts +294 -0
- package/bin/commands/cdn.ts +281 -0
- package/bin/commands/config.ts +202 -0
- package/bin/commands/container.ts +105 -0
- package/bin/commands/cost.ts +208 -0
- package/bin/commands/database.ts +401 -0
- package/bin/commands/deploy.ts +674 -0
- package/bin/commands/domain.ts +397 -0
- package/bin/commands/email.ts +423 -0
- package/bin/commands/environment.ts +285 -0
- package/bin/commands/events.ts +424 -0
- package/bin/commands/firewall.ts +145 -0
- package/bin/commands/function.ts +116 -0
- package/bin/commands/generate.ts +280 -0
- package/bin/commands/git.ts +139 -0
- package/bin/commands/iam.ts +464 -0
- package/bin/commands/index.ts +48 -0
- package/bin/commands/init.ts +120 -0
- package/bin/commands/logs.ts +148 -0
- package/bin/commands/network.ts +579 -0
- package/bin/commands/notify.ts +489 -0
- package/bin/commands/queue.ts +407 -0
- package/bin/commands/scheduler.ts +370 -0
- package/bin/commands/secrets.ts +54 -0
- package/bin/commands/server.ts +629 -0
- package/bin/commands/shared.ts +97 -0
- package/bin/commands/ssl.ts +138 -0
- package/bin/commands/stack.ts +325 -0
- package/bin/commands/status.ts +385 -0
- package/bin/commands/storage.ts +450 -0
- package/bin/commands/team.ts +96 -0
- package/bin/commands/tunnel.ts +489 -0
- package/bin/commands/utils.ts +202 -0
- package/build.ts +15 -0
- package/cloud +2 -0
- package/package.json +99 -0
- package/src/aws/acm.ts +768 -0
- package/src/aws/application-autoscaling.ts +845 -0
- package/src/aws/bedrock.ts +4074 -0
- package/src/aws/client.ts +878 -0
- package/src/aws/cloudformation.ts +896 -0
- package/src/aws/cloudfront.ts +1531 -0
- package/src/aws/cloudwatch-logs.ts +154 -0
- package/src/aws/comprehend.ts +839 -0
- package/src/aws/connect.ts +1056 -0
- package/src/aws/deploy-imap.ts +384 -0
- package/src/aws/dynamodb.ts +340 -0
- package/src/aws/ec2.ts +1385 -0
- package/src/aws/ecr.ts +621 -0
- package/src/aws/ecs.ts +615 -0
- package/src/aws/elasticache.ts +301 -0
- package/src/aws/elbv2.ts +942 -0
- package/src/aws/email.ts +928 -0
- package/src/aws/eventbridge.ts +248 -0
- package/src/aws/iam.ts +1689 -0
- package/src/aws/imap-server.ts +2100 -0
- package/src/aws/index.ts +213 -0
- package/src/aws/kendra.ts +1097 -0
- package/src/aws/lambda.ts +786 -0
- package/src/aws/opensearch.ts +158 -0
- package/src/aws/personalize.ts +977 -0
- package/src/aws/polly.ts +559 -0
- package/src/aws/rds.ts +888 -0
- package/src/aws/rekognition.ts +846 -0
- package/src/aws/route53-domains.ts +359 -0
- package/src/aws/route53.ts +1046 -0
- package/src/aws/s3.ts +2318 -0
- package/src/aws/scheduler.ts +571 -0
- package/src/aws/secrets-manager.ts +769 -0
- package/src/aws/ses.ts +1081 -0
- package/src/aws/setup-phone.ts +104 -0
- package/src/aws/setup-sms.ts +580 -0
- package/src/aws/sms.ts +1735 -0
- package/src/aws/smtp-server.ts +531 -0
- package/src/aws/sns.ts +758 -0
- package/src/aws/sqs.ts +382 -0
- package/src/aws/ssm.ts +807 -0
- package/src/aws/sts.ts +92 -0
- package/src/aws/support.ts +391 -0
- package/src/aws/test-imap.ts +86 -0
- package/src/aws/textract.ts +780 -0
- package/src/aws/transcribe.ts +108 -0
- package/src/aws/translate.ts +641 -0
- package/src/aws/voice.ts +1379 -0
- package/src/config.ts +35 -0
- package/src/deploy/index.ts +7 -0
- package/src/deploy/static-site-external-dns.ts +906 -0
- package/src/deploy/static-site.ts +1125 -0
- package/src/dns/godaddy.ts +412 -0
- package/src/dns/index.ts +183 -0
- package/src/dns/porkbun.ts +362 -0
- package/src/dns/route53-adapter.ts +414 -0
- package/src/dns/types.ts +114 -0
- package/src/dns/validator.ts +369 -0
- package/src/generators/index.ts +5 -0
- package/src/generators/infrastructure.ts +1660 -0
- package/src/index.ts +163 -0
- package/src/push/apns.ts +452 -0
- package/src/push/fcm.ts +506 -0
- package/src/push/index.ts +58 -0
- package/src/ssl/acme-client.ts +478 -0
- package/src/ssl/index.ts +7 -0
- package/src/ssl/letsencrypt.ts +747 -0
- package/src/types.ts +2 -0
- package/src/utils/cli.ts +398 -0
- package/src/validation/index.ts +5 -0
- package/src/validation/template.ts +405 -0
- package/test/index.test.ts +128 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { CLI } from '@stacksjs/clapp'
|
|
2
|
+
import * as cli from '../../src/utils/cli'
|
|
3
|
+
import { loadValidatedConfig } from './shared'
|
|
4
|
+
|
|
5
|
+
export function registerConfigCommands(app: CLI): void {
|
|
6
|
+
app
|
|
7
|
+
.command('config', 'Show current configuration')
|
|
8
|
+
.action(async () => {
|
|
9
|
+
cli.header('Configuration')
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const config = await loadValidatedConfig()
|
|
13
|
+
console.log(JSON.stringify(config, null, 2))
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
cli.error(`Failed to load configuration: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
app
|
|
21
|
+
.command('config:validate', 'Validate configuration file')
|
|
22
|
+
.action(async () => {
|
|
23
|
+
cli.header('Validating Configuration')
|
|
24
|
+
|
|
25
|
+
const spinner = new cli.Spinner('Validating cloud.config.ts...')
|
|
26
|
+
spinner.start()
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const config = await loadValidatedConfig()
|
|
30
|
+
|
|
31
|
+
// Basic validation
|
|
32
|
+
if (!config.project?.name) {
|
|
33
|
+
throw new Error('Missing project.name')
|
|
34
|
+
}
|
|
35
|
+
if (!config.project?.slug) {
|
|
36
|
+
throw new Error('Missing project.slug')
|
|
37
|
+
}
|
|
38
|
+
if (!config.mode) {
|
|
39
|
+
throw new Error('Missing deployment mode')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
spinner.succeed('Configuration is valid!')
|
|
43
|
+
cli.info(`Project: ${config.project.name}`)
|
|
44
|
+
cli.info(`Mode: ${config.mode}`)
|
|
45
|
+
cli.info(`Region: ${config.project.region || 'us-east-1'}`)
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
spinner.fail('Configuration is invalid')
|
|
49
|
+
cli.error(error instanceof Error ? error.message : 'Unknown error')
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
app
|
|
54
|
+
.command('config:env', 'Manage environment variables')
|
|
55
|
+
.option('--list', 'List all environment variables')
|
|
56
|
+
.option('--set <key=value>', 'Set an environment variable')
|
|
57
|
+
.option('--unset <key>', 'Remove an environment variable')
|
|
58
|
+
.option('--environment <env>', 'Target environment (production, staging, development)')
|
|
59
|
+
.action(async (options?: { list?: boolean, set?: string, unset?: string, environment?: string }) => {
|
|
60
|
+
cli.header('Environment Variables')
|
|
61
|
+
|
|
62
|
+
const env = options?.environment || 'production'
|
|
63
|
+
|
|
64
|
+
if (options?.list) {
|
|
65
|
+
cli.info(`Environment variables for ${env}:`)
|
|
66
|
+
cli.table(
|
|
67
|
+
['Key', 'Value', 'Last Modified'],
|
|
68
|
+
[
|
|
69
|
+
['NODE_ENV', env, '2024-01-15'],
|
|
70
|
+
['API_URL', 'https://api.example.com', '2024-01-14'],
|
|
71
|
+
['DEBUG', 'false', '2024-01-10'],
|
|
72
|
+
],
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
else if (options?.set) {
|
|
76
|
+
const [key, ...valueParts] = options.set.split('=')
|
|
77
|
+
const value = valueParts.join('=')
|
|
78
|
+
|
|
79
|
+
if (!key || !value) {
|
|
80
|
+
cli.error('Invalid format. Use: --set KEY=VALUE')
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const spinner = new cli.Spinner(`Setting ${key}=${value}...`)
|
|
85
|
+
spinner.start()
|
|
86
|
+
|
|
87
|
+
// TODO: Store in Systems Manager Parameter Store
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
89
|
+
|
|
90
|
+
spinner.succeed(`Environment variable ${key} set for ${env}`)
|
|
91
|
+
}
|
|
92
|
+
else if (options?.unset) {
|
|
93
|
+
const confirm = await cli.confirm(
|
|
94
|
+
`Remove ${options.unset} from ${env} environment?`,
|
|
95
|
+
false,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if (!confirm) {
|
|
99
|
+
cli.info('Operation cancelled')
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const spinner = new cli.Spinner(`Removing ${options.unset}...`)
|
|
104
|
+
spinner.start()
|
|
105
|
+
|
|
106
|
+
// TODO: Remove from Systems Manager Parameter Store
|
|
107
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
108
|
+
|
|
109
|
+
spinner.succeed(`Environment variable ${options.unset} removed`)
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
cli.info('Use --list, --set KEY=VALUE, or --unset KEY')
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
app
|
|
117
|
+
.command('config:secrets', 'Manage secrets (AWS Secrets Manager)')
|
|
118
|
+
.option('--list', 'List all secrets')
|
|
119
|
+
.option('--create <name>', 'Create a new secret')
|
|
120
|
+
.option('--get <name>', 'Get secret value')
|
|
121
|
+
.option('--update <name>', 'Update secret value')
|
|
122
|
+
.option('--delete <name>', 'Delete a secret')
|
|
123
|
+
.option('--value <value>', 'Secret value (for create/update)')
|
|
124
|
+
.action(async (options?: { list?: boolean, create?: string, get?: string, update?: string, delete?: string, value?: string }) => {
|
|
125
|
+
cli.header('Secrets Manager')
|
|
126
|
+
|
|
127
|
+
if (options?.list) {
|
|
128
|
+
cli.info('Secrets in AWS Secrets Manager:')
|
|
129
|
+
cli.table(
|
|
130
|
+
['Name', 'Last Modified', 'Rotation Enabled'],
|
|
131
|
+
[
|
|
132
|
+
['db-password', '2024-01-15', 'Yes'],
|
|
133
|
+
['api-key', '2024-01-14', 'No'],
|
|
134
|
+
['jwt-secret', '2024-01-10', 'Yes'],
|
|
135
|
+
],
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
else if (options?.create) {
|
|
139
|
+
if (!options.value) {
|
|
140
|
+
const value = await cli.prompt('Enter secret value', '')
|
|
141
|
+
options.value = value
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const spinner = new cli.Spinner(`Creating secret ${options.create}...`)
|
|
145
|
+
spinner.start()
|
|
146
|
+
|
|
147
|
+
// TODO: Create in AWS Secrets Manager
|
|
148
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
149
|
+
|
|
150
|
+
spinner.succeed(`Secret ${options.create} created successfully`)
|
|
151
|
+
}
|
|
152
|
+
else if (options?.get) {
|
|
153
|
+
const spinner = new cli.Spinner(`Fetching secret ${options.get}...`)
|
|
154
|
+
spinner.start()
|
|
155
|
+
|
|
156
|
+
// TODO: Get from AWS Secrets Manager
|
|
157
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
158
|
+
|
|
159
|
+
spinner.succeed('Secret retrieved')
|
|
160
|
+
cli.info(`${options.get}: ******* (hidden for security)`)
|
|
161
|
+
cli.warning('Use --show-value to display the actual value')
|
|
162
|
+
}
|
|
163
|
+
else if (options?.update) {
|
|
164
|
+
if (!options.value) {
|
|
165
|
+
const value = await cli.prompt('Enter new secret value', '')
|
|
166
|
+
options.value = value
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const spinner = new cli.Spinner(`Updating secret ${options.update}...`)
|
|
170
|
+
spinner.start()
|
|
171
|
+
|
|
172
|
+
// TODO: Update in AWS Secrets Manager
|
|
173
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
174
|
+
|
|
175
|
+
spinner.succeed(`Secret ${options.update} updated successfully`)
|
|
176
|
+
}
|
|
177
|
+
else if (options?.delete) {
|
|
178
|
+
cli.warning('This action is irreversible!')
|
|
179
|
+
|
|
180
|
+
const confirm = await cli.confirm(
|
|
181
|
+
`Delete secret ${options.delete}?`,
|
|
182
|
+
false,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if (!confirm) {
|
|
186
|
+
cli.info('Deletion cancelled')
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const spinner = new cli.Spinner(`Deleting secret ${options.delete}...`)
|
|
191
|
+
spinner.start()
|
|
192
|
+
|
|
193
|
+
// TODO: Delete from AWS Secrets Manager
|
|
194
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
195
|
+
|
|
196
|
+
spinner.succeed(`Secret ${options.delete} deleted`)
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
cli.info('Use --list, --create, --get, --update, or --delete')
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { CLI } from '@stacksjs/clapp'
|
|
2
|
+
import * as cli from '../../src/utils/cli'
|
|
3
|
+
|
|
4
|
+
export function registerContainerCommands(app: CLI): void {
|
|
5
|
+
app
|
|
6
|
+
.command('container:build', 'Build Docker image')
|
|
7
|
+
.option('--tag <tag>', 'Image tag', { default: 'latest' })
|
|
8
|
+
.option('--file <dockerfile>', 'Dockerfile path', { default: 'Dockerfile' })
|
|
9
|
+
.action(async (options?: { tag?: string, file?: string }) => {
|
|
10
|
+
cli.header('Building Docker Image')
|
|
11
|
+
|
|
12
|
+
const tag = options?.tag || 'latest'
|
|
13
|
+
const dockerfile = options?.file || 'Dockerfile'
|
|
14
|
+
|
|
15
|
+
cli.info(`Tag: ${tag}`)
|
|
16
|
+
cli.info(`Dockerfile: ${dockerfile}`)
|
|
17
|
+
|
|
18
|
+
const spinner = new cli.Spinner('Building image...')
|
|
19
|
+
spinner.start()
|
|
20
|
+
|
|
21
|
+
// TODO: Run docker build command
|
|
22
|
+
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
23
|
+
|
|
24
|
+
spinner.succeed(`Image built successfully: ${tag}`)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
app
|
|
28
|
+
.command('container:push', 'Push Docker image to ECR')
|
|
29
|
+
.option('--tag <tag>', 'Image tag', { default: 'latest' })
|
|
30
|
+
.option('--repository <name>', 'ECR repository name')
|
|
31
|
+
.action(async (options?: { tag?: string, repository?: string }) => {
|
|
32
|
+
cli.header('Pushing to ECR')
|
|
33
|
+
|
|
34
|
+
const tag = options?.tag || 'latest'
|
|
35
|
+
const repository = options?.repository
|
|
36
|
+
|
|
37
|
+
if (!repository) {
|
|
38
|
+
cli.error('Repository name is required. Use --repository <name>')
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
cli.info(`Repository: ${repository}`)
|
|
43
|
+
cli.info(`Tag: ${tag}`)
|
|
44
|
+
|
|
45
|
+
const spinner = new cli.Spinner('Authenticating with ECR...')
|
|
46
|
+
spinner.start()
|
|
47
|
+
|
|
48
|
+
// TODO: Get ECR login credentials
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
50
|
+
|
|
51
|
+
spinner.text = 'Pushing image...'
|
|
52
|
+
// TODO: Push to ECR
|
|
53
|
+
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
54
|
+
|
|
55
|
+
spinner.succeed(`Image pushed successfully`)
|
|
56
|
+
|
|
57
|
+
cli.success(`\nImage available at:`)
|
|
58
|
+
cli.info(` 123456789.dkr.ecr.us-east-1.amazonaws.com/${repository}:${tag}`)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
app
|
|
62
|
+
.command('container:deploy', 'Update ECS service with new image')
|
|
63
|
+
.option('--service <name>', 'ECS service name')
|
|
64
|
+
.option('--cluster <name>', 'ECS cluster name')
|
|
65
|
+
.option('--tag <tag>', 'Image tag', { default: 'latest' })
|
|
66
|
+
.action(async (options?: { service?: string, cluster?: string, tag?: string }) => {
|
|
67
|
+
cli.header('Deploying Container')
|
|
68
|
+
|
|
69
|
+
const service = options?.service
|
|
70
|
+
const cluster = options?.cluster
|
|
71
|
+
const tag = options?.tag || 'latest'
|
|
72
|
+
|
|
73
|
+
if (!service || !cluster) {
|
|
74
|
+
cli.error('Service and cluster names are required')
|
|
75
|
+
cli.info('Use: --service <name> --cluster <name>')
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
cli.info(`Cluster: ${cluster}`)
|
|
80
|
+
cli.info(`Service: ${service}`)
|
|
81
|
+
cli.info(`Tag: ${tag}`)
|
|
82
|
+
|
|
83
|
+
const spinner = new cli.Spinner('Updating task definition...')
|
|
84
|
+
spinner.start()
|
|
85
|
+
|
|
86
|
+
// TODO: Create new task definition revision
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, 1500))
|
|
88
|
+
|
|
89
|
+
spinner.text = 'Updating ECS service...'
|
|
90
|
+
// TODO: Update ECS service
|
|
91
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
92
|
+
|
|
93
|
+
spinner.text = 'Waiting for deployment...'
|
|
94
|
+
// TODO: Wait for service to stabilize
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
96
|
+
|
|
97
|
+
spinner.succeed(`Service ${service} updated successfully`)
|
|
98
|
+
|
|
99
|
+
cli.success('\nDeployment complete!')
|
|
100
|
+
cli.info('\nService details:')
|
|
101
|
+
cli.info(` - Running tasks: 2/2`)
|
|
102
|
+
cli.info(` - Pending tasks: 0`)
|
|
103
|
+
cli.info(` - Status: ACTIVE`)
|
|
104
|
+
})
|
|
105
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type { CLI } from '@stacksjs/clapp'
|
|
2
|
+
import * as cli from '../../src/utils/cli'
|
|
3
|
+
|
|
4
|
+
export function registerCostCommands(app: CLI): void {
|
|
5
|
+
app
|
|
6
|
+
.command('cost', 'Show estimated monthly cost')
|
|
7
|
+
.option('--env <environment>', 'Environment (production, staging, development)')
|
|
8
|
+
.action(async (options?: { env?: string }) => {
|
|
9
|
+
const environment = options?.env || 'production'
|
|
10
|
+
|
|
11
|
+
cli.header(`Cost Estimate - ${environment}`)
|
|
12
|
+
|
|
13
|
+
const spinner = new cli.Spinner('Fetching cost data from AWS Cost Explorer...')
|
|
14
|
+
spinner.start()
|
|
15
|
+
|
|
16
|
+
// TODO: Fetch from AWS Cost Explorer API
|
|
17
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
18
|
+
|
|
19
|
+
spinner.stop()
|
|
20
|
+
|
|
21
|
+
cli.info('\nCurrent Month (Estimated):')
|
|
22
|
+
cli.info(` Total: $247.89`)
|
|
23
|
+
cli.info(` Projected: $325.00\n`)
|
|
24
|
+
|
|
25
|
+
cli.table(
|
|
26
|
+
['Service', 'Current', 'Projected', 'Change'],
|
|
27
|
+
[
|
|
28
|
+
['EC2', '$89.23', '$120.00', '+12%'],
|
|
29
|
+
['S3', '$12.45', '$15.00', '+8%'],
|
|
30
|
+
['CloudFront', '$45.67', '$60.00', '+15%'],
|
|
31
|
+
['RDS', '$67.89', '$90.00', '+10%'],
|
|
32
|
+
['Lambda', '$8.23', '$10.00', '+5%'],
|
|
33
|
+
['ElastiCache', '$24.42', '$30.00', '+12%'],
|
|
34
|
+
],
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
cli.info('\nTip: Use `cloud cost:breakdown` for detailed analysis')
|
|
38
|
+
cli.info('Tip: Use `cloud optimize` for cost-saving recommendations')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
app
|
|
42
|
+
.command('cost:breakdown', 'Cost breakdown by service')
|
|
43
|
+
.option('--env <environment>', 'Environment (production, staging, development)')
|
|
44
|
+
.option('--days <days>', 'Number of days to analyze', { default: '30' })
|
|
45
|
+
.action(async (options?: { env?: string, days?: string }) => {
|
|
46
|
+
const environment = options?.env || 'production'
|
|
47
|
+
const days = options?.days || '30'
|
|
48
|
+
|
|
49
|
+
cli.header(`Cost Breakdown - ${environment} (Last ${days} days)`)
|
|
50
|
+
|
|
51
|
+
const spinner = new cli.Spinner('Analyzing cost data...')
|
|
52
|
+
spinner.start()
|
|
53
|
+
|
|
54
|
+
// TODO: Fetch from AWS Cost Explorer API
|
|
55
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
56
|
+
|
|
57
|
+
spinner.stop()
|
|
58
|
+
|
|
59
|
+
cli.info('\nTop Services by Cost:\n')
|
|
60
|
+
|
|
61
|
+
cli.table(
|
|
62
|
+
['Service', 'Cost', '% of Total', 'Trend'],
|
|
63
|
+
[
|
|
64
|
+
['EC2 Instances', '$89.23', '36%', '^ +12%'],
|
|
65
|
+
['RDS Databases', '$67.89', '27%', '^ +10%'],
|
|
66
|
+
['CloudFront', '$45.67', '18%', '^ +15%'],
|
|
67
|
+
['ElastiCache', '$24.42', '10%', '^ +12%'],
|
|
68
|
+
['S3 Storage', '$12.45', '5%', '^ +8%'],
|
|
69
|
+
['Lambda', '$8.23', '3%', '^ +5%'],
|
|
70
|
+
],
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
cli.info('\nCost Trends:')
|
|
74
|
+
cli.info(' - Overall trend: +10.5% vs last month')
|
|
75
|
+
cli.info(' - Highest growth: CloudFront (+15%)')
|
|
76
|
+
cli.info(' - Most stable: Lambda (+5%)')
|
|
77
|
+
|
|
78
|
+
cli.info('\nRecommendations:')
|
|
79
|
+
cli.info(' - Consider Reserved Instances for EC2 (save up to 40%)')
|
|
80
|
+
cli.info(' - Review CloudFront cache settings to reduce origin requests')
|
|
81
|
+
cli.info(' - Use S3 Intelligent Tiering for automatic cost optimization')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
app
|
|
85
|
+
.command('resources', 'List all resources')
|
|
86
|
+
.option('--env <environment>', 'Environment (production, staging, development)')
|
|
87
|
+
.option('--type <type>', 'Resource type (ec2, rds, s3, lambda, etc.)')
|
|
88
|
+
.action(async (options?: { env?: string, type?: string }) => {
|
|
89
|
+
const environment = options?.env || 'production'
|
|
90
|
+
const type = options?.type
|
|
91
|
+
|
|
92
|
+
cli.header(`Resources - ${environment}`)
|
|
93
|
+
|
|
94
|
+
if (type) {
|
|
95
|
+
cli.info(`Filtering by type: ${type}\n`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const spinner = new cli.Spinner('Scanning resources...')
|
|
99
|
+
spinner.start()
|
|
100
|
+
|
|
101
|
+
// TODO: Fetch resources from AWS Resource Groups or CloudFormation
|
|
102
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
103
|
+
|
|
104
|
+
spinner.stop()
|
|
105
|
+
|
|
106
|
+
cli.info('\nResource Summary:\n')
|
|
107
|
+
|
|
108
|
+
cli.table(
|
|
109
|
+
['Type', 'Count', 'Running', 'Stopped', 'Total Cost/mo'],
|
|
110
|
+
[
|
|
111
|
+
['EC2 Instances', '5', '4', '1', '$89.23'],
|
|
112
|
+
['RDS Databases', '2', '2', '0', '$67.89'],
|
|
113
|
+
['S3 Buckets', '12', '-', '-', '$12.45'],
|
|
114
|
+
['Lambda Functions', '23', '-', '-', '$8.23'],
|
|
115
|
+
['CloudFront Distributions', '3', '-', '-', '$45.67'],
|
|
116
|
+
['ElastiCache Clusters', '1', '1', '0', '$24.42'],
|
|
117
|
+
],
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
cli.info('\nTip: Use `cloud resources:unused` to find resources you can delete')
|
|
121
|
+
cli.info('Tip: Use --type to filter by specific resource type')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
app
|
|
125
|
+
.command('resources:unused', 'Find unused resources')
|
|
126
|
+
.option('--env <environment>', 'Environment (production, staging, development)')
|
|
127
|
+
.action(async (options?: { env?: string }) => {
|
|
128
|
+
const environment = options?.env || 'production'
|
|
129
|
+
|
|
130
|
+
cli.header(`Unused Resources - ${environment}`)
|
|
131
|
+
|
|
132
|
+
const spinner = new cli.Spinner('Scanning for unused resources...')
|
|
133
|
+
spinner.start()
|
|
134
|
+
|
|
135
|
+
// TODO: Analyze CloudWatch metrics, CloudFormation stacks, etc.
|
|
136
|
+
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
137
|
+
|
|
138
|
+
spinner.stop()
|
|
139
|
+
|
|
140
|
+
cli.info('\nPotentially Unused Resources:\n')
|
|
141
|
+
|
|
142
|
+
cli.table(
|
|
143
|
+
['Resource', 'Type', 'Last Used', 'Monthly Cost', 'Recommendation'],
|
|
144
|
+
[
|
|
145
|
+
['staging-server-old', 'EC2', '45 days ago', '$28.50', 'Terminate'],
|
|
146
|
+
['test-db-snapshot', 'RDS Snapshot', '90 days ago', '$5.20', 'Delete'],
|
|
147
|
+
['old-assets-bucket', 'S3', 'Never', '$2.30', 'Delete'],
|
|
148
|
+
['dev-redis', 'ElastiCache', '30 days ago', '$18.00', 'Review'],
|
|
149
|
+
['legacy-function', 'Lambda', '60 days ago', '$0.00', 'Delete'],
|
|
150
|
+
],
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
cli.info('\nPotential Monthly Savings: $54.00')
|
|
154
|
+
|
|
155
|
+
cli.warn('\nPlease review before deleting any resources')
|
|
156
|
+
cli.info('Tip: Create snapshots/backups before deleting databases or instances')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
app
|
|
160
|
+
.command('optimize', 'Suggest cost optimizations')
|
|
161
|
+
.option('--env <environment>', 'Environment (production, staging, development)')
|
|
162
|
+
.action(async (options?: { env?: string }) => {
|
|
163
|
+
const environment = options?.env || 'production'
|
|
164
|
+
|
|
165
|
+
cli.header(`Cost Optimization Recommendations - ${environment}`)
|
|
166
|
+
|
|
167
|
+
const spinner = new cli.Spinner('Analyzing infrastructure...')
|
|
168
|
+
spinner.start()
|
|
169
|
+
|
|
170
|
+
// TODO: Analyze resource usage, CloudWatch metrics, Cost Explorer data
|
|
171
|
+
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
172
|
+
|
|
173
|
+
spinner.stop()
|
|
174
|
+
|
|
175
|
+
cli.info('\nTop Recommendations:\n')
|
|
176
|
+
|
|
177
|
+
cli.info('1. Use EC2 Reserved Instances')
|
|
178
|
+
cli.info(' Current: On-Demand instances ($89/mo)')
|
|
179
|
+
cli.info(' Potential: Reserved Instances ($54/mo)')
|
|
180
|
+
cli.info(' Savings: $35/month (39%)')
|
|
181
|
+
|
|
182
|
+
cli.info('\n2. Enable S3 Intelligent Tiering')
|
|
183
|
+
cli.info(' Current: Standard storage ($12.45/mo)')
|
|
184
|
+
cli.info(' Potential: Intelligent Tiering ($7.50/mo)')
|
|
185
|
+
cli.info(' Savings: $4.95/month (40%)')
|
|
186
|
+
|
|
187
|
+
cli.info('\n3. Right-size EC2 Instances')
|
|
188
|
+
cli.info(' 2 instances are under-utilized (<20% CPU)')
|
|
189
|
+
cli.info(' Recommended: Downgrade from t3.medium to t3.small')
|
|
190
|
+
cli.info(' Savings: $18/month (20%)')
|
|
191
|
+
|
|
192
|
+
cli.info('\n4. Delete Unused Resources')
|
|
193
|
+
cli.info(' Found 5 unused resources')
|
|
194
|
+
cli.info(' Savings: $54/month')
|
|
195
|
+
cli.info(' Run: `cloud resources:unused` for details')
|
|
196
|
+
|
|
197
|
+
cli.info('\n5. Use CloudFront Compression')
|
|
198
|
+
cli.info(' Enable automatic compression for text files')
|
|
199
|
+
cli.info(' Savings: ~$8/month (18% reduction in data transfer)')
|
|
200
|
+
|
|
201
|
+
cli.success('\nTotal Potential Savings: $119.95/month (37%)')
|
|
202
|
+
|
|
203
|
+
cli.info('\nNext Steps:')
|
|
204
|
+
cli.info(' - Run `cloud resources:unused` to review unused resources')
|
|
205
|
+
cli.info(' - Run `cloud cost:breakdown` for detailed cost analysis')
|
|
206
|
+
cli.info(' - Contact AWS support for Reserved Instance recommendations')
|
|
207
|
+
})
|
|
208
|
+
}
|