@stacksjs/ts-cloud 0.1.3 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +98 -13
  2. package/dist/aws/acm.d.ts +129 -0
  3. package/dist/aws/application-autoscaling.d.ts +282 -0
  4. package/dist/aws/bedrock.d.ts +2292 -0
  5. package/dist/aws/client.d.ts +79 -0
  6. package/dist/aws/cloudformation.d.ts +105 -0
  7. package/dist/aws/cloudfront.d.ts +265 -0
  8. package/dist/aws/cloudwatch-logs.d.ts +48 -0
  9. package/dist/aws/comprehend.d.ts +505 -0
  10. package/dist/aws/connect.d.ts +377 -0
  11. package/dist/aws/deploy-imap.d.ts +14 -0
  12. package/dist/aws/dynamodb.d.ts +176 -0
  13. package/dist/aws/ec2.d.ts +272 -0
  14. package/dist/aws/ecr.d.ts +149 -0
  15. package/dist/aws/ecs.d.ts +162 -0
  16. package/dist/aws/elasticache.d.ts +71 -0
  17. package/dist/aws/elbv2.d.ts +248 -0
  18. package/dist/aws/email.d.ts +175 -0
  19. package/dist/aws/eventbridge.d.ts +142 -0
  20. package/dist/aws/iam.d.ts +638 -0
  21. package/dist/aws/imap-server.d.ts +119 -0
  22. package/{src/aws/index.ts → dist/aws/index.d.ts} +62 -83
  23. package/{src/aws/kendra.ts → dist/aws/kendra.d.ts} +71 -386
  24. package/dist/aws/lambda.d.ts +232 -0
  25. package/dist/aws/opensearch.d.ts +87 -0
  26. package/dist/aws/personalize.d.ts +516 -0
  27. package/dist/aws/polly.d.ts +214 -0
  28. package/dist/aws/rds.d.ts +240 -0
  29. package/dist/aws/rekognition.d.ts +543 -0
  30. package/dist/aws/route53-domains.d.ts +113 -0
  31. package/dist/aws/route53.d.ts +215 -0
  32. package/dist/aws/s3.d.ts +212 -0
  33. package/dist/aws/scheduler.d.ts +140 -0
  34. package/dist/aws/secrets-manager.d.ts +170 -0
  35. package/dist/aws/ses.d.ts +288 -0
  36. package/dist/aws/setup-phone.d.ts +0 -0
  37. package/dist/aws/setup-sms.d.ts +115 -0
  38. package/dist/aws/sms.d.ts +304 -0
  39. package/dist/aws/smtp-server.d.ts +61 -0
  40. package/dist/aws/sns.d.ts +117 -0
  41. package/dist/aws/sqs.d.ts +65 -0
  42. package/dist/aws/ssm.d.ts +179 -0
  43. package/dist/aws/sts.d.ts +15 -0
  44. package/dist/aws/support.d.ts +104 -0
  45. package/dist/aws/test-imap.d.ts +0 -0
  46. package/dist/aws/textract.d.ts +403 -0
  47. package/dist/aws/transcribe.d.ts +60 -0
  48. package/dist/aws/translate.d.ts +358 -0
  49. package/dist/aws/voice.d.ts +219 -0
  50. package/dist/bin/cli.js +1724 -0
  51. package/dist/config.d.ts +7 -0
  52. package/dist/deploy/index.d.ts +2 -0
  53. package/dist/deploy/static-site-external-dns.d.ts +51 -0
  54. package/dist/deploy/static-site.d.ts +71 -0
  55. package/dist/dns/cloudflare.d.ts +52 -0
  56. package/dist/dns/godaddy.d.ts +38 -0
  57. package/dist/dns/index.d.ts +45 -0
  58. package/dist/dns/porkbun.d.ts +18 -0
  59. package/dist/dns/route53-adapter.d.ts +38 -0
  60. package/{src/dns/types.ts → dist/dns/types.d.ts} +26 -63
  61. package/dist/dns/validator.d.ts +78 -0
  62. package/dist/generators/index.d.ts +1 -0
  63. package/dist/generators/infrastructure.d.ts +30 -0
  64. package/{src/index.ts → dist/index.d.ts} +70 -93
  65. package/dist/index.js +7881 -0
  66. package/dist/push/apns.d.ts +60 -0
  67. package/dist/push/fcm.d.ts +117 -0
  68. package/dist/push/index.d.ts +14 -0
  69. package/dist/security/pre-deploy-scanner.d.ts +69 -0
  70. package/dist/ssl/acme-client.d.ts +67 -0
  71. package/dist/ssl/index.d.ts +2 -0
  72. package/dist/ssl/letsencrypt.d.ts +48 -0
  73. package/dist/types.d.ts +1 -0
  74. package/dist/utils/cli.d.ts +123 -0
  75. package/dist/validation/index.d.ts +1 -0
  76. package/dist/validation/template.d.ts +23 -0
  77. package/package.json +8 -8
  78. package/bin/cli.ts +0 -133
  79. package/bin/commands/analytics.ts +0 -328
  80. package/bin/commands/api.ts +0 -379
  81. package/bin/commands/assets.ts +0 -221
  82. package/bin/commands/audit.ts +0 -501
  83. package/bin/commands/backup.ts +0 -682
  84. package/bin/commands/cache.ts +0 -294
  85. package/bin/commands/cdn.ts +0 -281
  86. package/bin/commands/config.ts +0 -202
  87. package/bin/commands/container.ts +0 -105
  88. package/bin/commands/cost.ts +0 -208
  89. package/bin/commands/database.ts +0 -401
  90. package/bin/commands/deploy.ts +0 -674
  91. package/bin/commands/domain.ts +0 -397
  92. package/bin/commands/email.ts +0 -423
  93. package/bin/commands/environment.ts +0 -285
  94. package/bin/commands/events.ts +0 -424
  95. package/bin/commands/firewall.ts +0 -145
  96. package/bin/commands/function.ts +0 -116
  97. package/bin/commands/generate.ts +0 -280
  98. package/bin/commands/git.ts +0 -139
  99. package/bin/commands/iam.ts +0 -464
  100. package/bin/commands/index.ts +0 -48
  101. package/bin/commands/init.ts +0 -120
  102. package/bin/commands/logs.ts +0 -148
  103. package/bin/commands/network.ts +0 -579
  104. package/bin/commands/notify.ts +0 -489
  105. package/bin/commands/queue.ts +0 -407
  106. package/bin/commands/scheduler.ts +0 -370
  107. package/bin/commands/secrets.ts +0 -54
  108. package/bin/commands/server.ts +0 -629
  109. package/bin/commands/shared.ts +0 -97
  110. package/bin/commands/ssl.ts +0 -138
  111. package/bin/commands/stack.ts +0 -325
  112. package/bin/commands/status.ts +0 -385
  113. package/bin/commands/storage.ts +0 -450
  114. package/bin/commands/team.ts +0 -96
  115. package/bin/commands/tunnel.ts +0 -489
  116. package/bin/commands/utils.ts +0 -202
  117. package/build.ts +0 -15
  118. package/cloud +0 -2
  119. package/src/aws/acm.ts +0 -768
  120. package/src/aws/application-autoscaling.ts +0 -845
  121. package/src/aws/bedrock.ts +0 -4074
  122. package/src/aws/client.ts +0 -878
  123. package/src/aws/cloudformation.ts +0 -896
  124. package/src/aws/cloudfront.ts +0 -1531
  125. package/src/aws/cloudwatch-logs.ts +0 -154
  126. package/src/aws/comprehend.ts +0 -839
  127. package/src/aws/connect.ts +0 -1056
  128. package/src/aws/deploy-imap.ts +0 -384
  129. package/src/aws/dynamodb.ts +0 -340
  130. package/src/aws/ec2.ts +0 -1385
  131. package/src/aws/ecr.ts +0 -621
  132. package/src/aws/ecs.ts +0 -615
  133. package/src/aws/elasticache.ts +0 -301
  134. package/src/aws/elbv2.ts +0 -942
  135. package/src/aws/email.ts +0 -928
  136. package/src/aws/eventbridge.ts +0 -248
  137. package/src/aws/iam.ts +0 -1689
  138. package/src/aws/imap-server.ts +0 -2100
  139. package/src/aws/lambda.ts +0 -786
  140. package/src/aws/opensearch.ts +0 -158
  141. package/src/aws/personalize.ts +0 -977
  142. package/src/aws/polly.ts +0 -559
  143. package/src/aws/rds.ts +0 -888
  144. package/src/aws/rekognition.ts +0 -846
  145. package/src/aws/route53-domains.ts +0 -359
  146. package/src/aws/route53.ts +0 -1046
  147. package/src/aws/s3.ts +0 -2318
  148. package/src/aws/scheduler.ts +0 -571
  149. package/src/aws/secrets-manager.ts +0 -769
  150. package/src/aws/ses.ts +0 -1081
  151. package/src/aws/setup-phone.ts +0 -104
  152. package/src/aws/setup-sms.ts +0 -580
  153. package/src/aws/sms.ts +0 -1735
  154. package/src/aws/smtp-server.ts +0 -531
  155. package/src/aws/sns.ts +0 -758
  156. package/src/aws/sqs.ts +0 -382
  157. package/src/aws/ssm.ts +0 -807
  158. package/src/aws/sts.ts +0 -92
  159. package/src/aws/support.ts +0 -391
  160. package/src/aws/test-imap.ts +0 -86
  161. package/src/aws/textract.ts +0 -780
  162. package/src/aws/transcribe.ts +0 -108
  163. package/src/aws/translate.ts +0 -641
  164. package/src/aws/voice.ts +0 -1379
  165. package/src/config.ts +0 -35
  166. package/src/deploy/index.ts +0 -7
  167. package/src/deploy/static-site-external-dns.ts +0 -906
  168. package/src/deploy/static-site.ts +0 -1125
  169. package/src/dns/godaddy.ts +0 -412
  170. package/src/dns/index.ts +0 -183
  171. package/src/dns/porkbun.ts +0 -362
  172. package/src/dns/route53-adapter.ts +0 -414
  173. package/src/dns/validator.ts +0 -369
  174. package/src/generators/index.ts +0 -5
  175. package/src/generators/infrastructure.ts +0 -1660
  176. package/src/push/apns.ts +0 -452
  177. package/src/push/fcm.ts +0 -506
  178. package/src/push/index.ts +0 -58
  179. package/src/ssl/acme-client.ts +0 -478
  180. package/src/ssl/index.ts +0 -7
  181. package/src/ssl/letsencrypt.ts +0 -747
  182. package/src/types.ts +0 -2
  183. package/src/utils/cli.ts +0 -398
  184. package/src/validation/index.ts +0 -5
  185. package/src/validation/template.ts +0 -405
  186. package/test/index.test.ts +0 -128
  187. package/tsconfig.json +0 -18
@@ -1,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
- }