@stacksjs/ts-cloud 0.1.8 → 0.1.9
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/dist/bin/cli.js +1 -1
- package/package.json +18 -16
- 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 +891 -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 +2334 -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 +945 -0
- package/src/deploy/static-site.ts +1175 -0
- package/src/dns/cloudflare.ts +548 -0
- package/src/dns/godaddy.ts +412 -0
- package/src/dns/index.ts +205 -0
- package/src/dns/porkbun.ts +362 -0
- package/src/dns/route53-adapter.ts +414 -0
- package/src/dns/types.ts +119 -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/security/pre-deploy-scanner.ts +655 -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/src/types.ts
ADDED
package/src/utils/cli.ts
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Utility Functions
|
|
3
|
+
* Helpers for colored output, spinners, prompts, and formatting
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ANSI color codes
|
|
7
|
+
export const colors = {
|
|
8
|
+
reset: '\x1B[0m',
|
|
9
|
+
bright: '\x1B[1m',
|
|
10
|
+
dim: '\x1B[2m',
|
|
11
|
+
red: '\x1B[31m',
|
|
12
|
+
green: '\x1B[32m',
|
|
13
|
+
yellow: '\x1B[33m',
|
|
14
|
+
blue: '\x1B[34m',
|
|
15
|
+
magenta: '\x1B[35m',
|
|
16
|
+
cyan: '\x1B[36m',
|
|
17
|
+
white: '\x1B[37m',
|
|
18
|
+
gray: '\x1B[90m',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Colorize text
|
|
23
|
+
*/
|
|
24
|
+
export function colorize(text: string, color: keyof typeof colors): string {
|
|
25
|
+
return `${colors[color]}${text}${colors.reset}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Success message
|
|
30
|
+
*/
|
|
31
|
+
export function success(message: string): void {
|
|
32
|
+
console.log(`${colors.green}✓${colors.reset} ${message}`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Error message
|
|
37
|
+
*/
|
|
38
|
+
export function error(message: string): void {
|
|
39
|
+
console.error(`${colors.red}✗${colors.reset} ${message}`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Warning message
|
|
44
|
+
*/
|
|
45
|
+
export function warn(message: string): void {
|
|
46
|
+
console.warn(`${colors.yellow}⚠${colors.reset} ${message}`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Warning message (alias)
|
|
51
|
+
*/
|
|
52
|
+
export const warning: typeof warn = warn
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Info message
|
|
56
|
+
*/
|
|
57
|
+
export function info(message: string): void {
|
|
58
|
+
console.log(`${colors.blue}ℹ${colors.reset} ${message}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Step message
|
|
63
|
+
*/
|
|
64
|
+
export function step(message: string): void {
|
|
65
|
+
console.log(`${colors.cyan}→${colors.reset} ${message}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Header message
|
|
70
|
+
*/
|
|
71
|
+
export function header(message: string): void {
|
|
72
|
+
console.log(`\n${colors.bright}${colors.cyan}${message}${colors.reset}\n`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Simple spinner
|
|
77
|
+
*/
|
|
78
|
+
export class Spinner {
|
|
79
|
+
private frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
80
|
+
private interval: Timer | null = null
|
|
81
|
+
private currentFrame = 0
|
|
82
|
+
private message: string
|
|
83
|
+
|
|
84
|
+
constructor(message: string) {
|
|
85
|
+
this.message = message
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get text(): string {
|
|
89
|
+
return this.message
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
set text(value: string) {
|
|
93
|
+
this.message = value
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
start(): void {
|
|
97
|
+
this.interval = setInterval(() => {
|
|
98
|
+
process.stdout.write(`\r${colors.cyan}${this.frames[this.currentFrame]}${colors.reset} ${this.message}`)
|
|
99
|
+
this.currentFrame = (this.currentFrame + 1) % this.frames.length
|
|
100
|
+
}, 80)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
succeed(message?: string): void {
|
|
104
|
+
this.stop()
|
|
105
|
+
success(message || this.message)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fail(message?: string): void {
|
|
109
|
+
this.stop()
|
|
110
|
+
error(message || this.message)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
warn(message?: string): void {
|
|
114
|
+
this.stop()
|
|
115
|
+
warning(message || this.message)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
stop(): void {
|
|
119
|
+
if (this.interval) {
|
|
120
|
+
clearInterval(this.interval)
|
|
121
|
+
process.stdout.write('\r')
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Progress bar
|
|
128
|
+
*/
|
|
129
|
+
export class ProgressBar {
|
|
130
|
+
private total: number
|
|
131
|
+
private current = 0
|
|
132
|
+
private width = 40
|
|
133
|
+
|
|
134
|
+
constructor(total: number) {
|
|
135
|
+
this.total = total
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
update(current: number): void {
|
|
139
|
+
this.current = current
|
|
140
|
+
this.render()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
increment(): void {
|
|
144
|
+
this.current++
|
|
145
|
+
this.render()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private render(): void {
|
|
149
|
+
const percentage = Math.floor((this.current / this.total) * 100)
|
|
150
|
+
const filled = Math.floor((this.current / this.total) * this.width)
|
|
151
|
+
const empty = this.width - filled
|
|
152
|
+
|
|
153
|
+
const bar = `${'█'.repeat(filled)}${'░'.repeat(empty)}`
|
|
154
|
+
process.stdout.write(`\r${colors.cyan}${bar}${colors.reset} ${percentage}% (${this.current}/${this.total})`)
|
|
155
|
+
|
|
156
|
+
if (this.current >= this.total) {
|
|
157
|
+
process.stdout.write('\n')
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Prompt for user input
|
|
164
|
+
*/
|
|
165
|
+
export async function prompt(message: string, defaultValue?: string): Promise<string> {
|
|
166
|
+
const readline = await import('node:readline')
|
|
167
|
+
const rl = readline.createInterface({
|
|
168
|
+
input: process.stdin,
|
|
169
|
+
output: process.stdout,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
return new Promise((resolve) => {
|
|
173
|
+
const promptText = defaultValue
|
|
174
|
+
? `${colors.cyan}?${colors.reset} ${message} ${colors.gray}(${defaultValue})${colors.reset}: `
|
|
175
|
+
: `${colors.cyan}?${colors.reset} ${message}: `
|
|
176
|
+
|
|
177
|
+
rl.question(promptText, (answer) => {
|
|
178
|
+
rl.close()
|
|
179
|
+
resolve(answer || defaultValue || '')
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Prompt for confirmation (yes/no)
|
|
186
|
+
*/
|
|
187
|
+
export async function confirm(message: string, defaultValue = false): Promise<boolean> {
|
|
188
|
+
const answer = await prompt(`${message} (y/n)`, defaultValue ? 'y' : 'n')
|
|
189
|
+
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Select from a list of options
|
|
194
|
+
*/
|
|
195
|
+
export async function select(message: string, options: string[]): Promise<string> {
|
|
196
|
+
console.log(`${colors.cyan}?${colors.reset} ${message}`)
|
|
197
|
+
options.forEach((option, index) => {
|
|
198
|
+
console.log(` ${colors.gray}${index + 1}.${colors.reset} ${option}`)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const answer = await prompt('Select', '1')
|
|
202
|
+
const index = Number.parseInt(answer) - 1
|
|
203
|
+
|
|
204
|
+
if (index >= 0 && index < options.length) {
|
|
205
|
+
return options[index]
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return options[0]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Format a table
|
|
213
|
+
*/
|
|
214
|
+
export function table(headers: string[], rows: string[][]): void {
|
|
215
|
+
// Calculate column widths
|
|
216
|
+
const widths = headers.map((header, i) => {
|
|
217
|
+
const maxRowWidth = Math.max(...rows.map(row => (row[i] || '').length))
|
|
218
|
+
return Math.max(header.length, maxRowWidth)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// Print header
|
|
222
|
+
const headerRow = headers.map((header, i) => header.padEnd(widths[i])).join(' ')
|
|
223
|
+
console.log(colorize(headerRow, 'bright'))
|
|
224
|
+
console.log(colorize('─'.repeat(headerRow.length), 'gray'))
|
|
225
|
+
|
|
226
|
+
// Print rows
|
|
227
|
+
rows.forEach((row) => {
|
|
228
|
+
const formattedRow = row.map((cell, i) => (cell || '').padEnd(widths[i])).join(' ')
|
|
229
|
+
console.log(formattedRow)
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Format bytes to human readable
|
|
235
|
+
*/
|
|
236
|
+
export function formatBytes(bytes: number): string {
|
|
237
|
+
if (bytes === 0)
|
|
238
|
+
return '0 B'
|
|
239
|
+
|
|
240
|
+
const k = 1024
|
|
241
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
242
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
243
|
+
|
|
244
|
+
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Format duration to human readable
|
|
249
|
+
*/
|
|
250
|
+
export function formatDuration(ms: number): string {
|
|
251
|
+
if (ms < 1000)
|
|
252
|
+
return `${ms}ms`
|
|
253
|
+
|
|
254
|
+
const seconds = Math.floor(ms / 1000)
|
|
255
|
+
if (seconds < 60)
|
|
256
|
+
return `${seconds}s`
|
|
257
|
+
|
|
258
|
+
const minutes = Math.floor(seconds / 60)
|
|
259
|
+
const remainingSeconds = seconds % 60
|
|
260
|
+
|
|
261
|
+
if (minutes < 60)
|
|
262
|
+
return `${minutes}m ${remainingSeconds}s`
|
|
263
|
+
|
|
264
|
+
const hours = Math.floor(minutes / 60)
|
|
265
|
+
const remainingMinutes = minutes % 60
|
|
266
|
+
|
|
267
|
+
return `${hours}h ${remainingMinutes}m`
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Box a message
|
|
272
|
+
*/
|
|
273
|
+
export function box(message: string, color: keyof typeof colors = 'cyan'): void {
|
|
274
|
+
const lines = message.split('\n')
|
|
275
|
+
const maxLength = Math.max(...lines.map(line => line.length))
|
|
276
|
+
const border = '─'.repeat(maxLength + 2)
|
|
277
|
+
|
|
278
|
+
console.log(colorize(`┌${border}┐`, color))
|
|
279
|
+
lines.forEach((line) => {
|
|
280
|
+
console.log(colorize(`│ ${line.padEnd(maxLength)} │`, color))
|
|
281
|
+
})
|
|
282
|
+
console.log(colorize(`└${border}┘`, color))
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Check if AWS CLI is installed (deprecated - no longer required)
|
|
287
|
+
* @deprecated AWS CLI is no longer required. Use checkAwsCredentials() instead.
|
|
288
|
+
*/
|
|
289
|
+
export async function checkAwsCli(): Promise<boolean> {
|
|
290
|
+
// AWS CLI is no longer required - direct API calls are used
|
|
291
|
+
return true
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Check if AWS credentials are configured
|
|
296
|
+
* Uses direct API call to STS GetCallerIdentity
|
|
297
|
+
*/
|
|
298
|
+
export async function checkAwsCredentials(): Promise<boolean> {
|
|
299
|
+
try {
|
|
300
|
+
const { AWSClient } = await import('../aws/client')
|
|
301
|
+
const client = new AWSClient()
|
|
302
|
+
|
|
303
|
+
await client.request({
|
|
304
|
+
service: 'sts',
|
|
305
|
+
region: 'us-east-1',
|
|
306
|
+
method: 'POST',
|
|
307
|
+
path: '/',
|
|
308
|
+
body: new URLSearchParams({
|
|
309
|
+
Action: 'GetCallerIdentity',
|
|
310
|
+
Version: '2011-06-15',
|
|
311
|
+
}).toString(),
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
return true
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return false
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Get AWS account ID using direct STS API call
|
|
323
|
+
*/
|
|
324
|
+
export async function getAwsAccountId(): Promise<string | null> {
|
|
325
|
+
try {
|
|
326
|
+
const { AWSClient } = await import('../aws/client')
|
|
327
|
+
const client = new AWSClient()
|
|
328
|
+
|
|
329
|
+
const result = await client.request({
|
|
330
|
+
service: 'sts',
|
|
331
|
+
region: 'us-east-1',
|
|
332
|
+
method: 'POST',
|
|
333
|
+
path: '/',
|
|
334
|
+
body: new URLSearchParams({
|
|
335
|
+
Action: 'GetCallerIdentity',
|
|
336
|
+
Version: '2011-06-15',
|
|
337
|
+
}).toString(),
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
return result.Account || result.GetCallerIdentityResult?.Account || null
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
return null
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Get AWS regions using direct EC2 API call
|
|
349
|
+
*/
|
|
350
|
+
export async function getAwsRegions(): Promise<string[]> {
|
|
351
|
+
try {
|
|
352
|
+
const { AWSClient } = await import('../aws/client')
|
|
353
|
+
const client = new AWSClient()
|
|
354
|
+
|
|
355
|
+
const result = await client.request({
|
|
356
|
+
service: 'ec2',
|
|
357
|
+
region: 'us-east-1',
|
|
358
|
+
method: 'POST',
|
|
359
|
+
path: '/',
|
|
360
|
+
body: new URLSearchParams({
|
|
361
|
+
Action: 'DescribeRegions',
|
|
362
|
+
Version: '2016-11-15',
|
|
363
|
+
}).toString(),
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
// Parse regions from response
|
|
367
|
+
const regions: string[] = []
|
|
368
|
+
if (result.regionInfo) {
|
|
369
|
+
const regionData = Array.isArray(result.regionInfo)
|
|
370
|
+
? result.regionInfo
|
|
371
|
+
: [result.regionInfo]
|
|
372
|
+
|
|
373
|
+
regions.push(...regionData.map((r: any) => r.regionName))
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return regions.length > 0 ? regions : getCommonAwsRegions()
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
// Return common regions as fallback
|
|
380
|
+
return getCommonAwsRegions()
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Get common AWS regions as fallback
|
|
386
|
+
*/
|
|
387
|
+
function getCommonAwsRegions(): string[] {
|
|
388
|
+
return [
|
|
389
|
+
'us-east-1',
|
|
390
|
+
'us-east-2',
|
|
391
|
+
'us-west-1',
|
|
392
|
+
'us-west-2',
|
|
393
|
+
'eu-west-1',
|
|
394
|
+
'eu-central-1',
|
|
395
|
+
'ap-southeast-1',
|
|
396
|
+
'ap-northeast-1',
|
|
397
|
+
]
|
|
398
|
+
}
|