Skip to content

Commit c96ac8a

Browse files
committed
chore: wip
1 parent 1115083 commit c96ac8a

18 files changed

Lines changed: 6907 additions & 4 deletions

File tree

packages/ts-cloud/bin/cli.ts

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,26 @@ import {
2323
registerStackCommands,
2424
registerUtilsCommands,
2525
registerAnalyticsCommands,
26+
// New infrastructure commands
27+
registerCdnCommands,
28+
registerStorageCommands,
29+
registerCacheCommands,
30+
registerQueueCommands,
31+
registerNetworkCommands,
32+
// Scheduling & Events
33+
registerSchedulerCommands,
34+
registerEventsCommands,
35+
// Communication
36+
registerEmailCommands,
37+
registerNotifyCommands,
38+
// Security & Access
39+
registerIamCommands,
40+
registerAuditCommands,
41+
// Operations
42+
registerStatusCommands,
43+
registerBackupCommands,
44+
registerApiCommands,
45+
registerTunnelCommands,
2646
} from './commands'
2747

2848
const app = new CLI('cloud')
@@ -40,27 +60,70 @@ app
4060
// ============================================
4161
// Register All Commands
4262
// ============================================
63+
64+
// Core commands
4365
registerInitCommands(app)
4466
registerConfigCommands(app)
4567
registerGenerateCommands(app)
68+
registerDeployCommands(app)
69+
registerStackCommands(app)
70+
71+
// Infrastructure Management
4672
registerServerCommands(app)
4773
registerFunctionCommands(app)
4874
registerContainerCommands(app)
75+
registerCdnCommands(app)
76+
registerStorageCommands(app)
77+
registerCacheCommands(app)
78+
registerQueueCommands(app)
79+
registerNetworkCommands(app)
80+
81+
// Domain & DNS
4982
registerDomainCommands(app)
83+
registerSslCommands(app)
84+
85+
// Database & Data
5086
registerDatabaseCommands(app)
87+
88+
// Monitoring & Logs
5189
registerLogsCommands(app)
90+
registerStatusCommands(app)
91+
92+
// Scheduling & Events
93+
registerSchedulerCommands(app)
94+
registerEventsCommands(app)
95+
96+
// Communication
97+
registerEmailCommands(app)
98+
registerNotifyCommands(app)
99+
100+
// Security & Access
52101
registerSecretsCommands(app)
53102
registerFirewallCommands(app)
54-
registerSslCommands(app)
103+
registerIamCommands(app)
104+
registerAuditCommands(app)
105+
106+
// Operations & Backup
107+
registerBackupCommands(app)
108+
registerApiCommands(app)
109+
110+
// Cost & Resources
55111
registerCostCommands(app)
112+
113+
// Git & Environment
56114
registerGitCommands(app)
57115
registerEnvironmentCommands(app)
116+
117+
// Assets & Team
58118
registerAssetsCommands(app)
59119
registerTeamCommands(app)
60-
registerDeployCommands(app)
61-
registerStackCommands(app)
62-
registerUtilsCommands(app, version)
120+
121+
// Analytics & Tunnel
63122
registerAnalyticsCommands(app)
123+
registerTunnelCommands(app)
124+
125+
// Utilities
126+
registerUtilsCommands(app, version)
64127

65128
// ============================================
66129
// Help & Version

packages/ts-cloud/bin/commands/analytics.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,125 @@ export function registerAnalyticsCommands(app: CLI): void {
204204
process.exit(1)
205205
}
206206
})
207+
208+
app
209+
.command('analytics:query <siteId>', 'Query analytics data for a site')
210+
.option('--table <name>', 'DynamoDB table name', { default: 'ts-analytics' })
211+
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
212+
.option('--limit <n>', 'Max items to return', { default: '10' })
213+
.option('--type <type>', 'Filter by type (pageview, event, etc.)')
214+
.action(async (siteId: string, options: { table: string; region: string; limit: string; type?: string }) => {
215+
cli.header(`Analytics Data: ${siteId}`)
216+
217+
try {
218+
const dynamodb = new DynamoDBClient(options.region)
219+
220+
const spinner = new cli.Spinner('Querying data...')
221+
spinner.start()
222+
223+
const result = await dynamodb.query({
224+
TableName: options.table,
225+
KeyConditionExpression: 'pk = :pk',
226+
ExpressionAttributeValues: {
227+
':pk': { S: `SITE#${siteId}` },
228+
},
229+
ScanIndexForward: false,
230+
Limit: parseInt(options.limit, 10),
231+
})
232+
233+
spinner.succeed(`Found ${result.Items?.length || 0} items`)
234+
235+
if (!result.Items || result.Items.length === 0) {
236+
cli.info('No data found for this site')
237+
return
238+
}
239+
240+
const items = result.Items.map(item => DynamoDBClient.unmarshal(item))
241+
242+
for (const item of items) {
243+
cli.info('')
244+
cli.info(`SK: ${item.sk}`)
245+
if (item.path) cli.info(` Path: ${item.path}`)
246+
if (item.timestamp) cli.info(` Time: ${item.timestamp}`)
247+
if (item.visitorId) cli.info(` Visitor: ${item.visitorId}`)
248+
if (item.browser) cli.info(` Browser: ${item.browser}`)
249+
if (item.country) cli.info(` Country: ${item.country}`)
250+
}
251+
}
252+
catch (error: any) {
253+
cli.error(`Failed to query data: ${error.message}`)
254+
process.exit(1)
255+
}
256+
})
257+
258+
app
259+
.command('analytics:realtime <siteId>', 'Check realtime visitors for a site')
260+
.option('--table <name>', 'DynamoDB table name', { default: 'ts-analytics' })
261+
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
262+
.option('--minutes <n>', 'Minutes to look back', { default: '5' })
263+
.action(async (siteId: string, options: { table: string; region: string; minutes: string }) => {
264+
cli.header(`Realtime: ${siteId}`)
265+
266+
try {
267+
const dynamodb = new DynamoDBClient(options.region)
268+
269+
const spinner = new cli.Spinner('Checking realtime...')
270+
spinner.start()
271+
272+
const minutesAgo = parseInt(options.minutes, 10)
273+
const cutoff = new Date(Date.now() - minutesAgo * 60 * 1000)
274+
275+
const result = await dynamodb.query({
276+
TableName: options.table,
277+
KeyConditionExpression: 'pk = :pk AND sk BETWEEN :start AND :end',
278+
ExpressionAttributeValues: {
279+
':pk': { S: `SITE#${siteId}` },
280+
':start': { S: `PAGEVIEW#${cutoff.toISOString()}` },
281+
':end': { S: 'PAGEVIEW#Z' },
282+
},
283+
ScanIndexForward: false,
284+
})
285+
286+
spinner.succeed(`Found ${result.Items?.length || 0} pageviews in last ${minutesAgo} minutes`)
287+
288+
if (!result.Items || result.Items.length === 0) {
289+
cli.warn('No recent pageviews found')
290+
cli.info('')
291+
cli.info('Possible issues:')
292+
cli.info(' 1. Tracking script not installed correctly')
293+
cli.info(' 2. Site ID mismatch')
294+
cli.info(' 3. CORS or network issues')
295+
return
296+
}
297+
298+
const items = result.Items.map(item => DynamoDBClient.unmarshal(item))
299+
const uniqueVisitors = new Set(items.map(i => i.visitorId)).size
300+
301+
cli.info('')
302+
cli.success(`${uniqueVisitors} unique visitor(s) online`)
303+
cli.info('')
304+
305+
// Group by path
306+
const byPath: Record<string, number> = {}
307+
for (const item of items) {
308+
const path = item.path || '/'
309+
byPath[path] = (byPath[path] || 0) + 1
310+
}
311+
312+
cli.info('Active pages:')
313+
for (const [path, count] of Object.entries(byPath).sort((a, b) => b[1] - a[1]).slice(0, 5)) {
314+
cli.info(` ${path}: ${count} view(s)`)
315+
}
316+
317+
cli.info('')
318+
cli.info('Recent pageviews:')
319+
for (const item of items.slice(0, 5)) {
320+
cli.info(` ${item.timestamp} - ${item.path} (${item.browser || 'Unknown'})`)
321+
}
322+
}
323+
catch (error: any) {
324+
cli.error(`Failed to check realtime: ${error.message}`)
325+
process.exit(1)
326+
}
327+
})
207328
}

0 commit comments

Comments
 (0)