Skip to content

Commit 2114881

Browse files
committed
chore: wip
1 parent 23f23af commit 2114881

File tree

10 files changed

+659
-14
lines changed

10 files changed

+659
-14
lines changed

config/phone.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { PhoneConfig } from '@stacksjs/types'
2+
3+
// Use direct environment variable access to avoid circular dependencies
4+
const envVars = typeof Bun !== 'undefined' ? Bun.env : process.env
5+
6+
/**
7+
* **Phone Configuration**
8+
*
9+
* This configuration defines your phone/voice service options.
10+
* Powered by Amazon Connect for enterprise-grade telephony.
11+
*/
12+
export default {
13+
enabled: false, // Set to true to enable phone features
14+
15+
provider: 'connect', // Amazon Connect
16+
17+
instance: {
18+
alias: envVars.CONNECT_INSTANCE_ALIAS || `${envVars.APP_NAME?.toLowerCase() || 'stacks'}-phone`,
19+
inboundCallsEnabled: true,
20+
outboundCallsEnabled: true,
21+
},
22+
23+
phoneNumbers: [
24+
// Example phone number configuration
25+
// {
26+
// type: 'TOLL_FREE',
27+
// countryCode: 'US',
28+
// description: 'Main support line',
29+
// notifyOnCall: ['[email protected]'],
30+
// },
31+
],
32+
33+
notifications: {
34+
incomingCall: {
35+
enabled: true,
36+
channels: ['email'], // 'email', 'sms', 'slack', 'webhook'
37+
},
38+
missedCall: {
39+
enabled: true,
40+
channels: ['email'],
41+
},
42+
voicemail: {
43+
enabled: true,
44+
channels: ['email'],
45+
},
46+
},
47+
48+
voicemail: {
49+
enabled: true,
50+
transcription: true, // Use Amazon Transcribe
51+
maxDurationSeconds: 120,
52+
greeting: 'Please leave a message after the tone.',
53+
},
54+
55+
businessHours: {
56+
timezone: 'America/Los_Angeles',
57+
schedule: [
58+
{ day: 'MONDAY', start: '09:00', end: '17:00' },
59+
{ day: 'TUESDAY', start: '09:00', end: '17:00' },
60+
{ day: 'WEDNESDAY', start: '09:00', end: '17:00' },
61+
{ day: 'THURSDAY', start: '09:00', end: '17:00' },
62+
{ day: 'FRIDAY', start: '09:00', end: '17:00' },
63+
],
64+
},
65+
} satisfies PhoneConfig

config/sms.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { SmsConfig } from '@stacksjs/types'
2+
3+
// Use direct environment variable access to avoid circular dependencies
4+
const envVars = typeof Bun !== 'undefined' ? Bun.env : process.env
5+
6+
/**
7+
* **SMS Configuration**
8+
*
9+
* This configuration defines your SMS service options.
10+
* Powered by AWS Pinpoint for reliable SMS delivery.
11+
*/
12+
export default {
13+
enabled: false, // Set to true to enable SMS features
14+
15+
provider: 'pinpoint', // AWS Pinpoint
16+
17+
senderId: envVars.SMS_SENDER_ID, // Sender ID (where supported by country)
18+
originationNumber: envVars.SMS_ORIGINATION_NUMBER, // Phone number to send from
19+
20+
defaultCountryCode: 'US',
21+
messageType: 'TRANSACTIONAL', // TRANSACTIONAL or PROMOTIONAL
22+
23+
maxSpendPerMonth: 100, // Budget limit in USD
24+
25+
optOut: {
26+
enabled: true,
27+
keywords: ['STOP', 'UNSUBSCRIBE', 'CANCEL', 'END', 'QUIT'],
28+
},
29+
30+
templates: [
31+
// Example templates
32+
// {
33+
// name: 'verification',
34+
// body: 'Your verification code is {{code}}. Valid for 10 minutes.',
35+
// variables: ['code'],
36+
// },
37+
// {
38+
// name: 'welcome',
39+
// body: 'Welcome to {{appName}}! Reply HELP for assistance.',
40+
// variables: ['appName'],
41+
// },
42+
],
43+
44+
twoWay: {
45+
enabled: false,
46+
// webhookUrl: 'https://your-app.com/api/sms/webhook',
47+
},
48+
} satisfies SmsConfig

storage/framework/core/buddy/src/commands/email.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@ import type { CLI } from '@stacksjs/types'
22
import { readFileSync, existsSync } from 'node:fs'
33
import { email as emailConfig } from '@stacksjs/config'
44

5+
const TIMEOUT_MS = 30000 // 30 second timeout for AWS operations
6+
7+
// Helper to run async operations with timeout
8+
async function withTimeout<T>(promise: Promise<T>, ms: number = TIMEOUT_MS): Promise<T> {
9+
let timeoutId: ReturnType<typeof setTimeout>
10+
const timeoutPromise = new Promise<never>((_, reject) => {
11+
timeoutId = setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms)
12+
})
13+
try {
14+
return await Promise.race([promise, timeoutPromise])
15+
} finally {
16+
clearTimeout(timeoutId!)
17+
}
18+
}
19+
520
// Load AWS credentials from .env.production
621
function loadAwsCredentials(): void {
722
const envPath = '.env.production'
@@ -52,7 +67,7 @@ export function email(buddy: CLI): void {
5267
const emailDomain = emailConfig?.from?.address?.split('@')[1] || 'stacksjs.com'
5368
console.log(`Domain: ${emailDomain}`)
5469

55-
const identity = await ses.getEmailIdentity(emailDomain)
70+
const identity = await withTimeout(ses.getEmailIdentity(emailDomain))
5671

5772
if (identity) {
5873
console.log(`\n✅ Domain Status: ${identity.VerificationStatus || 'Unknown'}`)
@@ -75,6 +90,7 @@ export function email(buddy: CLI): void {
7590
catch (error: any) {
7691
console.error('\n❌ Error checking verification:', error.message)
7792
}
93+
process.exit(0)
7894
})
7995

8096
buddy
@@ -91,7 +107,7 @@ export function email(buddy: CLI): void {
91107
const emailDomain = emailConfig?.from?.address?.split('@')[1] || 'stacksjs.com'
92108
const from = `noreply@${emailDomain}`
93109

94-
const result = await ses.sendEmail({
110+
const result = await withTimeout(ses.sendEmail({
95111
FromEmailAddress: from,
96112
Destination: {
97113
ToAddresses: [to],
@@ -126,7 +142,7 @@ export function email(buddy: CLI): void {
126142
},
127143
},
128144
},
129-
})
145+
}))
130146

131147
console.log('✅ Test email sent successfully!')
132148
console.log(` Message ID: ${result.MessageId}`)
@@ -138,6 +154,7 @@ export function email(buddy: CLI): void {
138154
console.log(' Run `buddy email:verify` to check status.')
139155
}
140156
}
157+
process.exit(0)
141158
})
142159

143160
buddy
@@ -164,6 +181,7 @@ export function email(buddy: CLI): void {
164181
}
165182
}
166183
console.log('')
184+
process.exit(0)
167185
})
168186

169187
buddy
@@ -184,12 +202,12 @@ export function email(buddy: CLI): void {
184202
console.log(`Showing last ${options.lines} events...\n`)
185203

186204
// First get the latest log stream
187-
const streams = await logs.describeLogStreams({
205+
const streams = await withTimeout(logs.describeLogStreams({
188206
logGroupName,
189207
orderBy: 'LastEventTime',
190208
descending: true,
191209
limit: 1,
192-
})
210+
}))
193211

194212
if (!streams.logStreams || streams.logStreams.length === 0) {
195213
console.log('No log streams found.')
@@ -203,11 +221,11 @@ export function email(buddy: CLI): void {
203221
return
204222
}
205223

206-
const events = await logs.getLogEvents({
224+
const events = await withTimeout(logs.getLogEvents({
207225
logGroupName,
208226
logStreamName,
209227
limit: parseInt(options.lines || '20', 10),
210-
})
228+
}))
211229

212230
if (events.events && events.events.length > 0) {
213231
for (const event of events.events) {
@@ -221,13 +239,14 @@ export function email(buddy: CLI): void {
221239
}
222240
}
223241
catch (error: any) {
224-
if (error.message.includes('ResourceNotFoundException')) {
225-
console.log('Log group not found. No emails have been processed yet.')
242+
if (error.message.includes('ResourceNotFoundException') || error.message.includes('timed out')) {
243+
console.log('Log group not found or not accessible. No emails have been processed yet.')
226244
}
227245
else {
228246
console.error('Error fetching logs:', error.message)
229247
}
230248
}
249+
process.exit(0)
231250
})
232251

233252
buddy
@@ -243,7 +262,7 @@ export function email(buddy: CLI): void {
243262
const appName = (process.env.APP_NAME || 'stacks').toLowerCase().replace(/[^a-z0-9-]/g, '-')
244263
const stackName = `${appName}-cloud`
245264

246-
const result = await cf.listStackResources(stackName)
265+
const result = await withTimeout(cf.listStackResources(stackName))
247266
const emailResources = result.StackResourceSummaries?.filter(
248267
(r: any) => r.LogicalResourceId.includes('Email') || r.LogicalResourceId.includes('Inbound') || r.LogicalResourceId.includes('Outbound')
249268
) || []
@@ -262,7 +281,7 @@ export function email(buddy: CLI): void {
262281
}
263282

264283
// Get outputs
265-
const stacks = await cf.describeStacks({ stackName })
284+
const stacks = await withTimeout(cf.describeStacks({ stackName }))
266285
const outputs = stacks.Stacks?.[0]?.Outputs || []
267286
const emailOutputs = outputs.filter((o: any) => o.OutputKey?.includes('Email'))
268287

@@ -276,5 +295,6 @@ export function email(buddy: CLI): void {
276295
catch (error: any) {
277296
console.error('Error checking status:', error.message)
278297
}
298+
process.exit(0)
279299
})
280300
}

storage/framework/core/buddy/src/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export * from './list'
2525
export * from './make'
2626
export * from './migrate'
2727
export * from './outdated'
28+
export * from './phone'
2829
export * from './ports'
2930
export * from './prepublish'
3031
export * from './projects'
@@ -36,6 +37,7 @@ export * from './schedule'
3637
export * from './search'
3738
export * from './seed'
3839
export * from './setup'
40+
export * from './sms'
3941
export * from './telemetry'
4042
export * from './test'
4143
export * from './tinker'

0 commit comments

Comments
 (0)