@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/deploy.ts
DELETED
|
@@ -1,674 +0,0 @@
|
|
|
1
|
-
import type { CLI } from '@stacksjs/clapp'
|
|
2
|
-
import { existsSync, statSync, writeFileSync } from 'node:fs'
|
|
3
|
-
import * as cli from '../../src/utils/cli'
|
|
4
|
-
import { InfrastructureGenerator } from '../../src/generators/infrastructure'
|
|
5
|
-
import { CloudFormationClient } from '../../src/aws/cloudformation'
|
|
6
|
-
import { S3Client } from '../../src/aws/s3'
|
|
7
|
-
import { CloudFrontClient } from '../../src/aws/cloudfront'
|
|
8
|
-
import { ECRClient } from '../../src/aws/ecr'
|
|
9
|
-
import { ECSClient } from '../../src/aws/ecs'
|
|
10
|
-
import { validateTemplate, validateTemplateSize, validateResourceLimits } from '../../src/validation/template'
|
|
11
|
-
import { loadValidatedConfig } from './shared'
|
|
12
|
-
|
|
13
|
-
export function registerDeployCommands(app: CLI): void {
|
|
14
|
-
app
|
|
15
|
-
.command('deploy', 'Deploy infrastructure')
|
|
16
|
-
.option('--stack <name>', 'Stack name')
|
|
17
|
-
.option('--env <environment>', 'Environment to deploy to')
|
|
18
|
-
.action(async (options?: { stack?: string, env?: string }) => {
|
|
19
|
-
cli.header('Deploying Infrastructure')
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
// Load configuration
|
|
23
|
-
const config = await loadValidatedConfig()
|
|
24
|
-
const environment = (options?.env || 'production') as 'production' | 'staging' | 'development'
|
|
25
|
-
const stackName = options?.stack || `${config.project.slug}-${environment}`
|
|
26
|
-
const region = config.project.region || 'us-east-1'
|
|
27
|
-
|
|
28
|
-
cli.info(`Stack: ${stackName}`)
|
|
29
|
-
cli.info(`Region: ${region}`)
|
|
30
|
-
cli.info(`Environment: ${environment}`)
|
|
31
|
-
|
|
32
|
-
// Generate CloudFormation template
|
|
33
|
-
cli.step('Generating CloudFormation template...')
|
|
34
|
-
const generator = new InfrastructureGenerator({
|
|
35
|
-
config,
|
|
36
|
-
environment,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
generator.generate()
|
|
40
|
-
const templateBody = generator.toJSON()
|
|
41
|
-
const template = JSON.parse(templateBody)
|
|
42
|
-
|
|
43
|
-
// Validate template
|
|
44
|
-
cli.step('Validating template...')
|
|
45
|
-
const validation = validateTemplate(template)
|
|
46
|
-
const sizeValidation = validateTemplateSize(templateBody)
|
|
47
|
-
const limitsValidation = validateResourceLimits(template)
|
|
48
|
-
|
|
49
|
-
// Show errors
|
|
50
|
-
const allErrors = [
|
|
51
|
-
...validation.errors,
|
|
52
|
-
...sizeValidation.errors,
|
|
53
|
-
...limitsValidation.errors,
|
|
54
|
-
]
|
|
55
|
-
|
|
56
|
-
if (allErrors.length > 0) {
|
|
57
|
-
cli.error('Template validation failed:')
|
|
58
|
-
for (const error of allErrors) {
|
|
59
|
-
cli.error(` - ${error.path}: ${error.message}`)
|
|
60
|
-
}
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Show warnings
|
|
65
|
-
const allWarnings = [
|
|
66
|
-
...validation.warnings,
|
|
67
|
-
...sizeValidation.warnings,
|
|
68
|
-
...limitsValidation.warnings,
|
|
69
|
-
]
|
|
70
|
-
|
|
71
|
-
if (allWarnings.length > 0) {
|
|
72
|
-
for (const warning of allWarnings) {
|
|
73
|
-
cli.warn(` - ${warning.path}: ${warning.message}`)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
cli.success('Template validated successfully')
|
|
78
|
-
|
|
79
|
-
// Show resource summary
|
|
80
|
-
const resourceCount = Object.keys(template.Resources).length
|
|
81
|
-
cli.info(`\nResources to deploy: ${resourceCount}`)
|
|
82
|
-
|
|
83
|
-
// Count resource types
|
|
84
|
-
const typeCounts: Record<string, number> = {}
|
|
85
|
-
for (const resource of Object.values(template.Resources)) {
|
|
86
|
-
const type = (resource as any).Type
|
|
87
|
-
typeCounts[type] = (typeCounts[type] || 0) + 1
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
for (const [type, count] of Object.entries(typeCounts).sort((a, b) => b[1] - a[1]).slice(0, 5)) {
|
|
91
|
-
cli.info(` - ${type}: ${count}`)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Confirm deployment
|
|
95
|
-
const confirmed = await cli.confirm('\nDeploy now?', true)
|
|
96
|
-
if (!confirmed) {
|
|
97
|
-
cli.info('Deployment cancelled')
|
|
98
|
-
return
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Initialize CloudFormation client
|
|
102
|
-
const cfn = new CloudFormationClient(region)
|
|
103
|
-
|
|
104
|
-
// Check if stack exists
|
|
105
|
-
cli.step('Checking stack status...')
|
|
106
|
-
let stackExists = false
|
|
107
|
-
try {
|
|
108
|
-
const result = await cfn.describeStacks({ stackName })
|
|
109
|
-
stackExists = result.Stacks && result.Stacks.length > 0
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
// Stack doesn't exist, that's fine
|
|
113
|
-
stackExists = false
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (stackExists) {
|
|
117
|
-
cli.info('Stack exists, updating...')
|
|
118
|
-
const updateSpinner = new cli.Spinner('Updating CloudFormation stack...')
|
|
119
|
-
updateSpinner.start()
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
await cfn.updateStack({
|
|
123
|
-
stackName,
|
|
124
|
-
templateBody,
|
|
125
|
-
capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
|
|
126
|
-
tags: [
|
|
127
|
-
{ Key: 'Project', Value: config.project.name },
|
|
128
|
-
{ Key: 'Environment', Value: environment },
|
|
129
|
-
{ Key: 'ManagedBy', Value: 'ts-cloud' },
|
|
130
|
-
],
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
updateSpinner.succeed('Update initiated')
|
|
134
|
-
|
|
135
|
-
// Wait for completion
|
|
136
|
-
cli.step('Waiting for stack update to complete...')
|
|
137
|
-
await cfn.waitForStack(stackName, 'stack-update-complete')
|
|
138
|
-
|
|
139
|
-
cli.success('Stack updated successfully!')
|
|
140
|
-
}
|
|
141
|
-
catch (error: any) {
|
|
142
|
-
if (error.message.includes('No updates are to be performed')) {
|
|
143
|
-
updateSpinner.succeed('No changes detected')
|
|
144
|
-
cli.info('Stack is already up to date')
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
throw error
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
cli.info('Creating new stack...')
|
|
152
|
-
const createSpinner = new cli.Spinner('Creating CloudFormation stack...')
|
|
153
|
-
createSpinner.start()
|
|
154
|
-
|
|
155
|
-
await cfn.createStack({
|
|
156
|
-
stackName,
|
|
157
|
-
templateBody,
|
|
158
|
-
capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
|
|
159
|
-
tags: [
|
|
160
|
-
{ Key: 'Project', Value: config.project.name },
|
|
161
|
-
{ Key: 'Environment', Value: environment },
|
|
162
|
-
{ Key: 'ManagedBy', Value: 'ts-cloud' },
|
|
163
|
-
],
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
createSpinner.succeed('Stack creation initiated')
|
|
167
|
-
|
|
168
|
-
// Wait for completion
|
|
169
|
-
cli.step('Waiting for stack creation to complete...')
|
|
170
|
-
await cfn.waitForStack(stackName, 'stack-create-complete')
|
|
171
|
-
|
|
172
|
-
cli.success('Stack created successfully!')
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Get stack outputs
|
|
176
|
-
const outputs = await cfn.getStackOutputs(stackName)
|
|
177
|
-
|
|
178
|
-
cli.box(`Deployment Complete!
|
|
179
|
-
|
|
180
|
-
Stack: ${stackName}
|
|
181
|
-
Region: ${region}
|
|
182
|
-
Environment: ${environment}
|
|
183
|
-
Resources: ${resourceCount}
|
|
184
|
-
|
|
185
|
-
View in console:
|
|
186
|
-
https://console.aws.amazon.com/cloudformation/home?region=${region}#/stacks/stackinfo?stackId=${encodeURIComponent(stackName)}`, 'green')
|
|
187
|
-
|
|
188
|
-
if (Object.keys(outputs).length > 0) {
|
|
189
|
-
cli.info('\nStack Outputs:')
|
|
190
|
-
for (const [key, value] of Object.entries(outputs)) {
|
|
191
|
-
cli.info(` - ${key}: ${value}`)
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
catch (error: any) {
|
|
196
|
-
cli.error(`Deployment failed: ${error.message}`)
|
|
197
|
-
if (error.stack) {
|
|
198
|
-
cli.info('\nStack trace:')
|
|
199
|
-
console.error(error.stack)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
app
|
|
205
|
-
.command('deploy:server', 'Deploy EC2 infrastructure')
|
|
206
|
-
.option('--env <environment>', 'Environment (production, staging, development)')
|
|
207
|
-
.action(async (options?: { env?: string }) => {
|
|
208
|
-
cli.header('Deploying Server Infrastructure')
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
const config = await loadValidatedConfig()
|
|
212
|
-
const environment = (options?.env || 'production') as 'production' | 'staging' | 'development'
|
|
213
|
-
const stackName = `${config.project.slug}-server-${environment}`
|
|
214
|
-
const region = config.project.region || 'us-east-1'
|
|
215
|
-
|
|
216
|
-
cli.info(`Stack: ${stackName}`)
|
|
217
|
-
cli.info(`Region: ${region}`)
|
|
218
|
-
cli.info(`Environment: ${environment}`)
|
|
219
|
-
|
|
220
|
-
cli.step('Generating EC2 server infrastructure...')
|
|
221
|
-
|
|
222
|
-
// TODO: Generate server-specific infrastructure
|
|
223
|
-
const spinner = new cli.Spinner('Deploying server infrastructure...')
|
|
224
|
-
spinner.start()
|
|
225
|
-
|
|
226
|
-
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
227
|
-
|
|
228
|
-
spinner.succeed('Server infrastructure deployed successfully!')
|
|
229
|
-
|
|
230
|
-
cli.success('\nDeployment complete!')
|
|
231
|
-
cli.info('\nNext steps:')
|
|
232
|
-
cli.info(' - cloud server:list - View deployed servers')
|
|
233
|
-
cli.info(' - cloud server:ssh <name> - SSH into a server')
|
|
234
|
-
}
|
|
235
|
-
catch (error: any) {
|
|
236
|
-
cli.error(`Deployment failed: ${error.message}`)
|
|
237
|
-
}
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
app
|
|
241
|
-
.command('deploy:serverless', 'Deploy serverless infrastructure')
|
|
242
|
-
.option('--env <environment>', 'Environment (production, staging, development)')
|
|
243
|
-
.option('--function <name>', 'Deploy specific function only')
|
|
244
|
-
.action(async (options?: { env?: string, function?: string }) => {
|
|
245
|
-
cli.header('Deploying Serverless Infrastructure')
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
const config = await loadValidatedConfig()
|
|
249
|
-
const environment = (options?.env || 'production') as 'production' | 'staging' | 'development'
|
|
250
|
-
const stackName = `${config.project.slug}-serverless-${environment}`
|
|
251
|
-
const region = config.project.region || 'us-east-1'
|
|
252
|
-
|
|
253
|
-
cli.info(`Stack: ${stackName}`)
|
|
254
|
-
cli.info(`Region: ${region}`)
|
|
255
|
-
cli.info(`Environment: ${environment}`)
|
|
256
|
-
|
|
257
|
-
if (options?.function) {
|
|
258
|
-
cli.info(`Function: ${options.function}`)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
cli.step('Generating serverless infrastructure...')
|
|
262
|
-
|
|
263
|
-
const spinner = new cli.Spinner('Deploying serverless infrastructure...')
|
|
264
|
-
spinner.start()
|
|
265
|
-
|
|
266
|
-
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
267
|
-
|
|
268
|
-
spinner.succeed('Serverless infrastructure deployed successfully!')
|
|
269
|
-
|
|
270
|
-
cli.success('\nDeployment complete!')
|
|
271
|
-
cli.info('\nNext steps:')
|
|
272
|
-
cli.info(' - cloud function:list - View deployed functions')
|
|
273
|
-
cli.info(' - cloud function:logs <name> - View function logs')
|
|
274
|
-
cli.info(' - cloud function:invoke <name> - Test function')
|
|
275
|
-
}
|
|
276
|
-
catch (error: any) {
|
|
277
|
-
cli.error(`Deployment failed: ${error.message}`)
|
|
278
|
-
}
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
app
|
|
282
|
-
.command('deploy:status', 'Check deployment status')
|
|
283
|
-
.option('--stack <name>', 'Stack name')
|
|
284
|
-
.option('--env <environment>', 'Environment')
|
|
285
|
-
.action(async (options?: { stack?: string, env?: string }) => {
|
|
286
|
-
cli.header('Deployment Status')
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
const config = await loadValidatedConfig()
|
|
290
|
-
const environment = options?.env || 'production'
|
|
291
|
-
const stackName = options?.stack || `${config.project.slug}-${environment}`
|
|
292
|
-
const region = config.project.region || 'us-east-1'
|
|
293
|
-
|
|
294
|
-
cli.info(`Stack: ${stackName}`)
|
|
295
|
-
cli.info(`Region: ${region}`)
|
|
296
|
-
|
|
297
|
-
const spinner = new cli.Spinner('Checking deployment status...')
|
|
298
|
-
spinner.start()
|
|
299
|
-
|
|
300
|
-
const cfn = new CloudFormationClient(region)
|
|
301
|
-
|
|
302
|
-
// Get stack status
|
|
303
|
-
const result = await cfn.describeStacks({ stackName })
|
|
304
|
-
|
|
305
|
-
if (result.Stacks.length === 0) {
|
|
306
|
-
spinner.fail('Stack not found')
|
|
307
|
-
cli.warning('No deployment found for this environment')
|
|
308
|
-
return
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const stack = result.Stacks[0]
|
|
312
|
-
spinner.succeed('Status retrieved')
|
|
313
|
-
|
|
314
|
-
cli.info(`\nStatus: ${stack.StackStatus}`)
|
|
315
|
-
cli.info(`Created: ${stack.CreationTime}`)
|
|
316
|
-
if (stack.LastUpdatedTime) {
|
|
317
|
-
cli.info(`Last Updated: ${stack.LastUpdatedTime}`)
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Show outputs
|
|
321
|
-
if (stack.Outputs && stack.Outputs.length > 0) {
|
|
322
|
-
cli.info('\nOutputs:')
|
|
323
|
-
for (const output of stack.Outputs) {
|
|
324
|
-
cli.info(` ${output.OutputKey}: ${output.OutputValue}`)
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
catch (error: any) {
|
|
329
|
-
cli.error(`Failed to get status: ${error.message}`)
|
|
330
|
-
}
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
app
|
|
334
|
-
.command('deploy:rollback', 'Rollback to previous version')
|
|
335
|
-
.option('--stack <name>', 'Stack name')
|
|
336
|
-
.option('--env <environment>', 'Environment')
|
|
337
|
-
.action(async (options?: { stack?: string, env?: string }) => {
|
|
338
|
-
cli.header('Rolling Back Deployment')
|
|
339
|
-
|
|
340
|
-
try {
|
|
341
|
-
const config = await loadValidatedConfig()
|
|
342
|
-
const environment = options?.env || 'production'
|
|
343
|
-
const stackName = options?.stack || `${config.project.slug}-${environment}`
|
|
344
|
-
const region = config.project.region || 'us-east-1'
|
|
345
|
-
|
|
346
|
-
cli.info(`Stack: ${stackName}`)
|
|
347
|
-
cli.info(`Region: ${region}`)
|
|
348
|
-
|
|
349
|
-
const confirmed = await cli.confirm('\nAre you sure you want to rollback?', false)
|
|
350
|
-
if (!confirmed) {
|
|
351
|
-
cli.info('Rollback cancelled')
|
|
352
|
-
return
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const spinner = new cli.Spinner('Rolling back stack...')
|
|
356
|
-
spinner.start()
|
|
357
|
-
|
|
358
|
-
const cfn = new CloudFormationClient(region)
|
|
359
|
-
|
|
360
|
-
// Delete the stack
|
|
361
|
-
await cfn.deleteStack(stackName)
|
|
362
|
-
|
|
363
|
-
spinner.succeed('Stack deletion initiated')
|
|
364
|
-
|
|
365
|
-
// Wait for deletion
|
|
366
|
-
cli.step('Waiting for stack deletion...')
|
|
367
|
-
await cfn.waitForStack(stackName, 'stack-delete-complete')
|
|
368
|
-
|
|
369
|
-
cli.success('Stack rolled back successfully!')
|
|
370
|
-
}
|
|
371
|
-
catch (error: any) {
|
|
372
|
-
cli.error(`Rollback failed: ${error.message}`)
|
|
373
|
-
}
|
|
374
|
-
})
|
|
375
|
-
|
|
376
|
-
app
|
|
377
|
-
.command('deploy:static', 'Deploy static site (S3 + CloudFront invalidation)')
|
|
378
|
-
.option('--source <path>', 'Source directory', { default: 'dist' })
|
|
379
|
-
.option('--bucket <name>', 'S3 bucket name')
|
|
380
|
-
.option('--distribution <id>', 'CloudFront distribution ID')
|
|
381
|
-
.option('--prefix <prefix>', 'S3 prefix/folder')
|
|
382
|
-
.option('--delete', 'Delete files not in source')
|
|
383
|
-
.option('--cache-control <value>', 'Cache-Control header', { default: 'public, max-age=31536000' })
|
|
384
|
-
.option('--no-invalidate', 'Skip CloudFront invalidation')
|
|
385
|
-
.option('--wait', 'Wait for invalidation to complete')
|
|
386
|
-
.action(async (options?: {
|
|
387
|
-
source?: string
|
|
388
|
-
bucket?: string
|
|
389
|
-
distribution?: string
|
|
390
|
-
prefix?: string
|
|
391
|
-
delete?: boolean
|
|
392
|
-
cacheControl?: string
|
|
393
|
-
invalidate?: boolean
|
|
394
|
-
wait?: boolean
|
|
395
|
-
}) => {
|
|
396
|
-
cli.header('Deploying Static Site')
|
|
397
|
-
|
|
398
|
-
try {
|
|
399
|
-
const config = await loadValidatedConfig()
|
|
400
|
-
const region = config.project.region || 'us-east-1'
|
|
401
|
-
|
|
402
|
-
const source = options?.source || 'dist'
|
|
403
|
-
const bucket = options?.bucket
|
|
404
|
-
const distributionId = options?.distribution
|
|
405
|
-
const prefix = options?.prefix
|
|
406
|
-
const shouldDelete = options?.delete || false
|
|
407
|
-
const cacheControl = options?.cacheControl || 'public, max-age=31536000'
|
|
408
|
-
const shouldInvalidate = options?.invalidate !== false
|
|
409
|
-
const shouldWait = options?.wait || false
|
|
410
|
-
|
|
411
|
-
if (!bucket) {
|
|
412
|
-
cli.error('--bucket is required')
|
|
413
|
-
return
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Check if source directory exists
|
|
417
|
-
if (!existsSync(source)) {
|
|
418
|
-
cli.error(`Source directory not found: ${source}`)
|
|
419
|
-
return
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
cli.info(`Source: ${source}`)
|
|
423
|
-
cli.info(`Bucket: s3://${bucket}${prefix ? `/${prefix}` : ''}`)
|
|
424
|
-
cli.info(`Cache-Control: ${cacheControl}`)
|
|
425
|
-
if (distributionId) {
|
|
426
|
-
cli.info(`CloudFront Distribution: ${distributionId}`)
|
|
427
|
-
}
|
|
428
|
-
if (shouldDelete) {
|
|
429
|
-
cli.warn('Delete mode enabled - files not in source will be removed')
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const confirmed = await cli.confirm('\nDeploy static site now?', true)
|
|
433
|
-
if (!confirmed) {
|
|
434
|
-
cli.info('Deployment cancelled')
|
|
435
|
-
return
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Step 1: Upload to S3
|
|
439
|
-
const s3 = new S3Client(region)
|
|
440
|
-
const uploadSpinner = new cli.Spinner('Uploading files to S3...')
|
|
441
|
-
uploadSpinner.start()
|
|
442
|
-
|
|
443
|
-
await s3.sync({
|
|
444
|
-
source,
|
|
445
|
-
bucket,
|
|
446
|
-
prefix,
|
|
447
|
-
delete: shouldDelete,
|
|
448
|
-
cacheControl,
|
|
449
|
-
acl: 'public-read',
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
uploadSpinner.succeed('Files uploaded successfully!')
|
|
453
|
-
|
|
454
|
-
// Get bucket size
|
|
455
|
-
const size = await s3.getBucketSize(bucket, prefix)
|
|
456
|
-
const sizeInMB = (size / 1024 / 1024).toFixed(2)
|
|
457
|
-
cli.info(`Total size: ${sizeInMB} MB`)
|
|
458
|
-
|
|
459
|
-
// Step 2: Invalidate CloudFront (if distribution provided)
|
|
460
|
-
if (shouldInvalidate && distributionId) {
|
|
461
|
-
const cloudfront = new CloudFrontClient()
|
|
462
|
-
const invalidateSpinner = new cli.Spinner('Invalidating CloudFront cache...')
|
|
463
|
-
invalidateSpinner.start()
|
|
464
|
-
|
|
465
|
-
const invalidation = await cloudfront.invalidateAll(distributionId)
|
|
466
|
-
invalidateSpinner.succeed('Invalidation created')
|
|
467
|
-
|
|
468
|
-
cli.info(`Invalidation ID: ${invalidation.Id}`)
|
|
469
|
-
|
|
470
|
-
if (shouldWait) {
|
|
471
|
-
const waitSpinner = new cli.Spinner('Waiting for invalidation to complete...')
|
|
472
|
-
waitSpinner.start()
|
|
473
|
-
await cloudfront.waitForInvalidation(distributionId, invalidation.Id)
|
|
474
|
-
waitSpinner.succeed('Invalidation completed!')
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
cli.box(`Static Site Deployed!
|
|
479
|
-
|
|
480
|
-
Source: ${source}
|
|
481
|
-
Bucket: s3://${bucket}${prefix ? `/${prefix}` : ''}
|
|
482
|
-
Size: ${sizeInMB} MB
|
|
483
|
-
${distributionId ? `Distribution: ${distributionId}` : ''}
|
|
484
|
-
|
|
485
|
-
View your site:
|
|
486
|
-
https://${bucket}.s3.${region}.amazonaws.com${prefix ? `/${prefix}` : ''}/index.html`, 'green')
|
|
487
|
-
}
|
|
488
|
-
catch (error: any) {
|
|
489
|
-
cli.error(`Deployment failed: ${error.message}`)
|
|
490
|
-
}
|
|
491
|
-
})
|
|
492
|
-
|
|
493
|
-
app
|
|
494
|
-
.command('deploy:container', 'Deploy container (ECR push + ECS service update)')
|
|
495
|
-
.option('--cluster <name>', 'ECS cluster name')
|
|
496
|
-
.option('--service <name>', 'ECS service name')
|
|
497
|
-
.option('--repository <name>', 'ECR repository name')
|
|
498
|
-
.option('--image <tag>', 'Docker image tag', { default: 'latest' })
|
|
499
|
-
.option('--dockerfile <path>', 'Dockerfile path', { default: 'Dockerfile' })
|
|
500
|
-
.option('--context <path>', 'Docker build context', { default: '.' })
|
|
501
|
-
.option('--task-definition <name>', 'Task definition family name')
|
|
502
|
-
.option('--force', 'Force new deployment even if no changes')
|
|
503
|
-
.option('--wait', 'Wait for deployment to stabilize')
|
|
504
|
-
.action(async (options?: {
|
|
505
|
-
cluster?: string
|
|
506
|
-
service?: string
|
|
507
|
-
repository?: string
|
|
508
|
-
image?: string
|
|
509
|
-
dockerfile?: string
|
|
510
|
-
context?: string
|
|
511
|
-
taskDefinition?: string
|
|
512
|
-
force?: boolean
|
|
513
|
-
wait?: boolean
|
|
514
|
-
}) => {
|
|
515
|
-
cli.header('Deploying Container')
|
|
516
|
-
|
|
517
|
-
try {
|
|
518
|
-
const config = await loadValidatedConfig()
|
|
519
|
-
const region = config.project.region || 'us-east-1'
|
|
520
|
-
|
|
521
|
-
const cluster = options?.cluster
|
|
522
|
-
const service = options?.service
|
|
523
|
-
const repository = options?.repository
|
|
524
|
-
const imageTag = options?.image || 'latest'
|
|
525
|
-
const dockerfile = options?.dockerfile || 'Dockerfile'
|
|
526
|
-
const context = options?.context || '.'
|
|
527
|
-
const forceDeployment = options?.force || false
|
|
528
|
-
const shouldWait = options?.wait || false
|
|
529
|
-
|
|
530
|
-
if (!cluster || !service) {
|
|
531
|
-
cli.error('--cluster and --service are required')
|
|
532
|
-
return
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (!repository) {
|
|
536
|
-
cli.error('--repository is required')
|
|
537
|
-
return
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Check if Dockerfile exists
|
|
541
|
-
if (!existsSync(dockerfile)) {
|
|
542
|
-
cli.error(`Dockerfile not found: ${dockerfile}`)
|
|
543
|
-
return
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
cli.info(`Cluster: ${cluster}`)
|
|
547
|
-
cli.info(`Service: ${service}`)
|
|
548
|
-
cli.info(`Repository: ${repository}`)
|
|
549
|
-
cli.info(`Image Tag: ${imageTag}`)
|
|
550
|
-
cli.info(`Dockerfile: ${dockerfile}`)
|
|
551
|
-
|
|
552
|
-
const confirmed = await cli.confirm('\nDeploy container now?', true)
|
|
553
|
-
if (!confirmed) {
|
|
554
|
-
cli.info('Deployment cancelled')
|
|
555
|
-
return
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const ecr = new ECRClient(region)
|
|
559
|
-
const ecs = new ECSClient(region)
|
|
560
|
-
|
|
561
|
-
// Step 1: Get ECR login credentials
|
|
562
|
-
const loginSpinner = new cli.Spinner('Getting ECR credentials...')
|
|
563
|
-
loginSpinner.start()
|
|
564
|
-
|
|
565
|
-
const authResult = await ecr.getAuthorizationToken()
|
|
566
|
-
if (!authResult.authorizationData?.[0]) {
|
|
567
|
-
loginSpinner.fail('Failed to get ECR credentials')
|
|
568
|
-
return
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
const auth = authResult.authorizationData[0]
|
|
572
|
-
const registryEndpoint = auth.proxyEndpoint || ''
|
|
573
|
-
const registryHost = registryEndpoint.replace('https://', '')
|
|
574
|
-
|
|
575
|
-
loginSpinner.succeed('ECR credentials obtained')
|
|
576
|
-
|
|
577
|
-
// Step 2: Docker login to ECR
|
|
578
|
-
const dockerLoginSpinner = new cli.Spinner('Logging into ECR...')
|
|
579
|
-
dockerLoginSpinner.start()
|
|
580
|
-
|
|
581
|
-
const token = auth.authorizationToken || ''
|
|
582
|
-
const decoded = Buffer.from(token, 'base64').toString('utf8')
|
|
583
|
-
const password = decoded.split(':')[1]
|
|
584
|
-
|
|
585
|
-
// Run docker login
|
|
586
|
-
const { spawn } = await import('child_process')
|
|
587
|
-
const dockerLogin = spawn('docker', ['login', '--username', 'AWS', '--password-stdin', registryHost], {
|
|
588
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
589
|
-
})
|
|
590
|
-
|
|
591
|
-
dockerLogin.stdin.write(password)
|
|
592
|
-
dockerLogin.stdin.end()
|
|
593
|
-
|
|
594
|
-
await new Promise<void>((resolve, reject) => {
|
|
595
|
-
dockerLogin.on('close', (code) => {
|
|
596
|
-
if (code === 0) resolve()
|
|
597
|
-
else reject(new Error(`Docker login failed with code ${code}`))
|
|
598
|
-
})
|
|
599
|
-
})
|
|
600
|
-
|
|
601
|
-
dockerLoginSpinner.succeed('Logged into ECR')
|
|
602
|
-
|
|
603
|
-
// Step 3: Build Docker image
|
|
604
|
-
const buildSpinner = new cli.Spinner('Building Docker image...')
|
|
605
|
-
buildSpinner.start()
|
|
606
|
-
|
|
607
|
-
const imageUri = `${registryHost}/${repository}:${imageTag}`
|
|
608
|
-
|
|
609
|
-
const dockerBuild = spawn('docker', ['build', '-t', imageUri, '-f', dockerfile, context], {
|
|
610
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
611
|
-
})
|
|
612
|
-
|
|
613
|
-
await new Promise<void>((resolve, reject) => {
|
|
614
|
-
let stderr = ''
|
|
615
|
-
dockerBuild.stderr.on('data', (data) => { stderr += data.toString() })
|
|
616
|
-
dockerBuild.on('close', (code) => {
|
|
617
|
-
if (code === 0) resolve()
|
|
618
|
-
else reject(new Error(`Docker build failed: ${stderr}`))
|
|
619
|
-
})
|
|
620
|
-
})
|
|
621
|
-
|
|
622
|
-
buildSpinner.succeed('Docker image built')
|
|
623
|
-
|
|
624
|
-
// Step 4: Push to ECR
|
|
625
|
-
const pushSpinner = new cli.Spinner('Pushing image to ECR...')
|
|
626
|
-
pushSpinner.start()
|
|
627
|
-
|
|
628
|
-
const dockerPush = spawn('docker', ['push', imageUri], {
|
|
629
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
630
|
-
})
|
|
631
|
-
|
|
632
|
-
await new Promise<void>((resolve, reject) => {
|
|
633
|
-
let stderr = ''
|
|
634
|
-
dockerPush.stderr.on('data', (data) => { stderr += data.toString() })
|
|
635
|
-
dockerPush.on('close', (code) => {
|
|
636
|
-
if (code === 0) resolve()
|
|
637
|
-
else reject(new Error(`Docker push failed: ${stderr}`))
|
|
638
|
-
})
|
|
639
|
-
})
|
|
640
|
-
|
|
641
|
-
pushSpinner.succeed('Image pushed to ECR')
|
|
642
|
-
|
|
643
|
-
// Step 5: Update ECS service
|
|
644
|
-
const updateSpinner = new cli.Spinner('Updating ECS service...')
|
|
645
|
-
updateSpinner.start()
|
|
646
|
-
|
|
647
|
-
await ecs.updateService({
|
|
648
|
-
cluster,
|
|
649
|
-
service,
|
|
650
|
-
forceNewDeployment: forceDeployment,
|
|
651
|
-
})
|
|
652
|
-
|
|
653
|
-
updateSpinner.succeed('ECS service updated')
|
|
654
|
-
|
|
655
|
-
// Step 6: Wait for deployment (if requested)
|
|
656
|
-
if (shouldWait) {
|
|
657
|
-
const waitSpinner = new cli.Spinner('Waiting for deployment to stabilize...')
|
|
658
|
-
waitSpinner.start()
|
|
659
|
-
|
|
660
|
-
await ecs.waitForServiceStable(cluster, service)
|
|
661
|
-
|
|
662
|
-
waitSpinner.succeed('Deployment stabilized')
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
cli.success('\nContainer deployment complete!')
|
|
666
|
-
cli.info(`\nImage: ${imageUri}`)
|
|
667
|
-
cli.info(`Cluster: ${cluster}`)
|
|
668
|
-
cli.info(`Service: ${service}`)
|
|
669
|
-
}
|
|
670
|
-
catch (error: any) {
|
|
671
|
-
cli.error(`Deployment failed: ${error.message}`)
|
|
672
|
-
}
|
|
673
|
-
})
|
|
674
|
-
}
|