Skip to content

Commit c33ffd4

Browse files
committed
chore: wip
1 parent 88f4e82 commit c33ffd4

File tree

3 files changed

+144
-35
lines changed

3 files changed

+144
-35
lines changed

storage/framework/core/actions/deploy.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ interface DeployStackOptions {
6262
environment: string
6363
region: string
6464
waitForCompletion?: boolean
65+
verbose?: boolean
6566
}
6667

6768
interface DeployFrontendOptions {
@@ -125,9 +126,10 @@ async function generateStacksTemplate(options: {
125126
// ACM certificate ARN (only used when useLoadBalancer && !useLetsEncrypt)
126127
const certificateArn = sslConfig.certificateArn || ''
127128

128-
log.info(` Compute: ${useMixedInstances ? 'mixed fleet' : `${instanceCount} x ${instanceSize} (${instanceType})`}`)
129-
log.info(` Auto Scaling: ${useAutoScaling ? 'enabled' : 'disabled'}`)
130-
log.info(` Load Balancer: ${useLoadBalancer ? 'enabled' : 'disabled'}`)
129+
// Config details are only shown in verbose mode
130+
log.debug(` Compute: ${useMixedInstances ? 'mixed fleet' : `${instanceCount} x ${instanceSize} (${instanceType})`}`)
131+
log.debug(` Auto Scaling: ${useAutoScaling ? 'enabled' : 'disabled'}`)
132+
log.debug(` Load Balancer: ${useLoadBalancer ? 'enabled' : 'disabled'}`)
131133

132134
// Generate Let's Encrypt setup script for UserData
133135
function generateLetsEncryptSetup(): string {
@@ -426,10 +428,10 @@ echo "Setup complete!"
426428
const hostedZoneId = dnsConfig.hostedZoneId || ''
427429
const sslDomains = sslConfig.domains || [siteDomain, `www.${siteDomain}`]
428430

429-
// Log DNS config
431+
// Log DNS config (verbose mode only)
430432
if (hostedZoneId) {
431-
log.info(` DNS: Route53 (${siteDomain})`)
432-
log.info(` SSL Domains: ${sslDomains.join(', ')}`)
433+
log.debug(` DNS: Route53 (${siteDomain})`)
434+
log.debug(` SSL Domains: ${sslDomains.join(', ')}`)
433435
}
434436

435437
// Build resources object
@@ -1084,13 +1086,13 @@ async function setupDnsAndSsl(options: {
10841086
* Deploy the infrastructure stack using ts-cloud
10851087
*/
10861088
export async function deployStack(options: DeployStackOptions): Promise<void> {
1087-
const { environment, region, waitForCompletion = true } = options
1089+
const { environment, region, waitForCompletion = true, verbose = false } = options
10881090

10891091
const projectConfig = await getProjectConfig()
10901092
const projectName = projectConfig.name
10911093
const stackName = `stacks-cloud-${environment}`
10921094

1093-
log.info(`Deploying infrastructure to ${environment} in ${region}...`)
1095+
if (verbose) log.info(`Deploying infrastructure to ${environment} in ${region}...`)
10941096

10951097
try {
10961098
const { CloudFormationClient } = await import('ts-cloud/aws')

storage/framework/core/actions/src/deploy/index.ts

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,70 @@
11
import type { Subprocess } from '@stacksjs/types'
22
import process from 'node:process'
3-
import { log, runCommand } from '@stacksjs/cli'
3+
import { log, runCommand, spinner } from '@stacksjs/cli'
44
import { config } from '@stacksjs/config'
55
import { path as p } from '@stacksjs/path'
66
import { storage } from '@stacksjs/storage'
77
import { ExitCode } from '@stacksjs/types'
88

9-
log.info('Building the framework...')
9+
// Check if verbose mode is enabled via CLI args
10+
const isVerbose = process.argv.includes('--verbose') || process.argv.includes('-v')
11+
12+
// Build framework with spinner (quiet unless verbose)
13+
const buildSpinner = spinner('Building framework...')
14+
buildSpinner.start()
1015
await runCommand('bun run build', {
1116
cwd: p.frameworkPath(),
17+
quiet: !isVerbose,
1218
})
13-
log.success('Framework built')
19+
buildSpinner.succeed('Framework built')
1420

1521
// Skip docs build for now - demo components have missing dependencies
1622
// TODO: Fix demo components to use actual installed packages
1723
// if (storage.hasFiles(p.projectPath('docs'))) {
18-
// log.info('Building the documentation...')
24+
// const docsSpinner = spinner('Building documentation...')
25+
// docsSpinner.start()
1926
// await runCommand('bun run build', {
2027
// cwd: p.frameworkPath('docs'),
28+
// quiet: !isVerbose,
2129
// })
22-
// log.success('Documentation built')
30+
// docsSpinner.succeed('Documentation built')
2331
// }
24-
log.info('Skipping documentation build (demo components need updates)')
32+
if (isVerbose) log.debug('Skipping documentation build (demo components need updates)')
2533

2634
// Skip views build for now - vite-config is not set up
2735
// TODO: Set up vite-config for views build
2836
// if (config.app.docMode !== true) {
29-
// log.info('Building the views...')
37+
// const viewsSpinner = spinner('Building views...')
38+
// viewsSpinner.start()
3039
// await runCommand('bun run build', {
3140
// cwd: p.frameworkPath('views/web'),
41+
// quiet: !isVerbose,
3242
// })
33-
// log.success('Views built')
43+
// viewsSpinner.succeed('Views built')
3444
// }
35-
log.info('Skipping views build (vite-config not configured)')
45+
if (isVerbose) log.debug('Skipping views build (vite-config not configured)')
3646

3747
// await runCommand('bun run build-edge', {
3848
// cwd: p.corePath('cloud'),
3949
// })
4050

41-
log.info('Building the server...')
51+
// Build server
52+
const serverSpinner = spinner('Building server...')
53+
serverSpinner.start()
4254
await runCommand('bun build.ts', {
4355
cwd: p.frameworkPath('server'),
56+
quiet: !isVerbose,
4457
})
45-
log.success('Server built')
58+
serverSpinner.succeed('Server built')
4659

60+
// Package for deployment
61+
const packageSpinner = spinner('Packaging for deployment...')
62+
packageSpinner.start()
4763
await runCommand('bun zip.ts', {
4864
cwd: p.corePath('cloud'),
65+
quiet: !isVerbose,
4966
})
50-
51-
log.info('Deploying using ts-cloud...')
67+
packageSpinner.succeed('Package ready')
5268

5369
// Load AWS credentials from .env.production if not already set
5470
if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) {
@@ -66,7 +82,7 @@ if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) {
6682
else if (key === 'AWS_SECRET_ACCESS_KEY' && value) process.env.AWS_SECRET_ACCESS_KEY = value
6783
else if (key === 'AWS_REGION' && value && !process.env.AWS_REGION) process.env.AWS_REGION = value
6884
}
69-
log.success('Loaded AWS credentials from .env.production')
85+
if (isVerbose) log.debug('Loaded AWS credentials from .env.production')
7086
}
7187
}
7288

@@ -78,27 +94,31 @@ try {
7894
const region = process.env.AWS_REGION || 'us-east-1'
7995

8096
// Step 1: Deploy infrastructure stack
81-
log.info('Deploying infrastructure stack...')
97+
const deploySpinner = spinner('Deploying infrastructure...')
98+
deploySpinner.start()
8299
await deployStack({
83100
environment,
84101
region,
85102
waitForCompletion: true,
103+
verbose: isVerbose,
86104
})
87-
log.success('Infrastructure stack deployed')
105+
deploySpinner.succeed('Infrastructure deployed')
88106

89107
// Skip frontend deployment for now - views build is disabled
90108
// TODO: Enable frontend deployment when vite-config is set up
91109
// if (config.app.docMode !== true) {
92-
// log.info('Deploying frontend...')
110+
// const frontendSpinner = spinner('Deploying frontend...')
111+
// frontendSpinner.start()
93112
// await deployFrontend({
94113
// environment,
95114
// region,
96115
// buildDir: p.frameworkPath('views/web/dist'),
97116
// })
98-
// log.success('Frontend deployed')
117+
// frontendSpinner.succeed('Frontend deployed')
99118
// }
100-
log.info('Skipping frontend deployment (views build is disabled)')
119+
if (isVerbose) log.debug('Skipping frontend deployment (views build is disabled)')
101120

121+
console.log('')
102122
log.success('Deployment completed successfully!')
103123
} catch (error) {
104124
log.error('Deployment failed:', error)
Lines changed: 95 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,128 @@
11
/**
2-
* Spinner utilities for CLI
3-
* Placeholder implementation - can be enhanced later
2+
* Spinner utilities for CLI with reactive terminal output
43
*/
54

65
export interface Spinner {
76
start: (message?: string) => void
87
stop: (message?: string) => void
98
succeed: (message?: string) => void
109
fail: (message?: string) => void
10+
update: (message: string) => void
1111
text: string
12+
isSpinning: boolean
1213
}
1314

15+
const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
16+
const isInteractive = process.stdout.isTTY && !process.env.CI
17+
1418
export function spinner(message?: string): Spinner {
1519
let currentMessage = message || ''
20+
let frameIndex = 0
21+
let intervalId: ReturnType<typeof setInterval> | null = null
22+
let isSpinning = false
23+
24+
const clearLine = () => {
25+
if (isInteractive) {
26+
process.stdout.write('\r\x1b[K')
27+
}
28+
}
29+
30+
const render = () => {
31+
if (!isInteractive) return
32+
clearLine()
33+
const frame = spinnerFrames[frameIndex]
34+
process.stdout.write(`${frame} ${currentMessage}`)
35+
frameIndex = (frameIndex + 1) % spinnerFrames.length
36+
}
1637

1738
return {
1839
start(msg?: string) {
1940
if (msg) currentMessage = msg
20-
// Simple console output for now
21-
if (currentMessage) console.log(currentMessage)
41+
if (isSpinning) return
42+
43+
isSpinning = true
44+
if (isInteractive) {
45+
render()
46+
intervalId = setInterval(render, 80)
47+
} else {
48+
// Non-interactive mode: just print the message
49+
if (currentMessage) console.log(` ${currentMessage}`)
50+
}
2251
},
52+
2353
stop(msg?: string) {
54+
isSpinning = false
55+
if (intervalId) {
56+
clearInterval(intervalId)
57+
intervalId = null
58+
}
59+
clearLine()
2460
if (msg) console.log(msg)
2561
},
62+
63+
update(msg: string) {
64+
currentMessage = msg
65+
if (!isInteractive && isSpinning) {
66+
console.log(` ${msg}`)
67+
}
68+
},
69+
2670
succeed(msg?: string) {
27-
if (msg) console.log(`✓ ${msg}`)
28-
else if (currentMessage) console.log(`✓ ${currentMessage}`)
71+
const finalMsg = msg || currentMessage
72+
isSpinning = false
73+
if (intervalId) {
74+
clearInterval(intervalId)
75+
intervalId = null
76+
}
77+
clearLine()
78+
console.log(`✓ ${finalMsg}`)
2979
},
80+
3081
fail(msg?: string) {
31-
if (msg) console.error(`✗ ${msg}`)
32-
else if (currentMessage) console.error(`✗ ${currentMessage}`)
82+
const finalMsg = msg || currentMessage
83+
isSpinning = false
84+
if (intervalId) {
85+
clearInterval(intervalId)
86+
intervalId = null
87+
}
88+
clearLine()
89+
console.error(`✗ ${finalMsg}`)
3390
},
91+
3492
get text() {
3593
return currentMessage
3694
},
95+
3796
set text(value: string) {
3897
currentMessage = value
98+
if (isInteractive && isSpinning) {
99+
render()
100+
}
39101
},
102+
103+
get isSpinning() {
104+
return isSpinning
105+
},
106+
}
107+
}
108+
109+
/**
110+
* Run an async operation with a spinner
111+
*/
112+
export async function withSpinner<T>(
113+
message: string,
114+
operation: () => Promise<T>,
115+
options?: { successMessage?: string; failMessage?: string }
116+
): Promise<T> {
117+
const s = spinner(message)
118+
s.start()
119+
120+
try {
121+
const result = await operation()
122+
s.succeed(options?.successMessage || message)
123+
return result
124+
} catch (error) {
125+
s.fail(options?.failMessage || `Failed: ${message}`)
126+
throw error
40127
}
41128
}

0 commit comments

Comments
 (0)