Skip to content

Commit 1115083

Browse files
committed
chore: wip
1 parent 445b6e0 commit 1115083

24 files changed

Lines changed: 5259 additions & 7055 deletions

packages/ts-cloud/bin/cli.ts

Lines changed: 47 additions & 7055 deletions
Large diffs are not rendered by default.
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import type { CLI } from '@stacksjs/clapp'
2+
import * as cli from '../../src/utils/cli'
3+
import { DynamoDBClient } from '../../src/aws/dynamodb'
4+
5+
export function registerAnalyticsCommands(app: CLI): void {
6+
app
7+
.command('analytics:sites:list', 'List all analytics sites')
8+
.option('--table <name>', 'DynamoDB table name', { default: 'ts-analytics' })
9+
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
10+
.action(async (options: { table: string; region: string }) => {
11+
cli.header('Analytics Sites')
12+
13+
try {
14+
const dynamodb = new DynamoDBClient(options.region)
15+
16+
const spinner = new cli.Spinner('Fetching sites...')
17+
spinner.start()
18+
19+
const result = await dynamodb.scan({
20+
TableName: options.table,
21+
FilterExpression: 'begins_with(pk, :pk)',
22+
ExpressionAttributeValues: {
23+
':pk': { S: 'SITE#' },
24+
},
25+
})
26+
27+
spinner.succeed(`Found ${result.Items?.length || 0} site(s)`)
28+
29+
if (!result.Items || result.Items.length === 0) {
30+
cli.info('No analytics sites found')
31+
cli.info('Use `cloud analytics:sites:create` to create a new site')
32+
return
33+
}
34+
35+
const sites = result.Items.map(item => DynamoDBClient.unmarshal(item))
36+
37+
cli.table(
38+
['ID', 'Name', 'Domains', 'Active', 'Created'],
39+
sites.map(site => [
40+
site.siteId || 'N/A',
41+
site.name || 'Unnamed',
42+
(site.domains || []).join(', ') || '-',
43+
site.isActive ? 'Yes' : 'No',
44+
site.createdAt ? new Date(site.createdAt).toLocaleDateString() : 'N/A',
45+
]),
46+
)
47+
}
48+
catch (error: any) {
49+
cli.error(`Failed to list sites: ${error.message}`)
50+
process.exit(1)
51+
}
52+
})
53+
54+
app
55+
.command('analytics:sites:create', 'Create a new analytics site')
56+
.option('--table <name>', 'DynamoDB table name', { default: 'ts-analytics' })
57+
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
58+
.option('--name <name>', 'Site name')
59+
.option('--domain <domain>', 'Site domain(s) (can be specified multiple times)')
60+
.action(async (options: { table: string; region: string; name?: string; domain?: string | string[] }) => {
61+
cli.header('Create Analytics Site')
62+
63+
try {
64+
const dynamodb = new DynamoDBClient(options.region)
65+
66+
// Get site name
67+
const name = options.name || await cli.prompt('Site name', 'My Site')
68+
69+
// Get domains
70+
let domains: string[] = []
71+
if (options.domain) {
72+
domains = Array.isArray(options.domain) ? options.domain : [options.domain]
73+
}
74+
else {
75+
const domainInput = await cli.prompt('Site domains (comma-separated)', 'example.com')
76+
domains = domainInput.split(',').map(d => d.trim()).filter(Boolean)
77+
}
78+
79+
cli.info(`\nCreating site: ${name}`)
80+
cli.info(`Domains: ${domains.join(', ')}`)
81+
82+
const confirm = await cli.confirm('\nCreate this site?', true)
83+
if (!confirm) {
84+
cli.info('Operation cancelled')
85+
return
86+
}
87+
88+
const spinner = new cli.Spinner('Creating site...')
89+
spinner.start()
90+
91+
// Generate site ID
92+
const siteId = `site_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
93+
94+
const now = new Date().toISOString()
95+
96+
await dynamodb.putItem({
97+
TableName: options.table,
98+
Item: {
99+
pk: { S: `SITE#${siteId}` },
100+
sk: { S: `SITE#${siteId}` },
101+
siteId: { S: siteId },
102+
name: { S: name },
103+
domains: { L: domains.map(d => ({ S: d })) },
104+
isActive: { BOOL: true },
105+
createdAt: { S: now },
106+
updatedAt: { S: now },
107+
},
108+
})
109+
110+
spinner.succeed('Site created successfully')
111+
112+
cli.success(`\nSite ID: ${siteId}`)
113+
cli.info('\nAdd the tracking script to your website:')
114+
cli.info(` <script src="https://analytics.stacksjs.com/track.js" data-site-id="${siteId}"></script>`)
115+
}
116+
catch (error: any) {
117+
cli.error(`Failed to create site: ${error.message}`)
118+
process.exit(1)
119+
}
120+
})
121+
122+
app
123+
.command('analytics:sites:update <siteId>', 'Update an analytics site')
124+
.option('--table <name>', 'DynamoDB table name', { default: 'ts-analytics' })
125+
.option('--region <region>', 'AWS region', { default: 'us-east-1' })
126+
.option('--name <name>', 'New site name')
127+
.option('--domain <domain>', 'Set domains (replaces existing)')
128+
.option('--active <boolean>', 'Set site active/inactive status')
129+
.action(async (siteId: string, options: { table: string; region: string; name?: string; domain?: string | string[]; active?: string }) => {
130+
cli.header('Update Analytics Site')
131+
132+
try {
133+
const dynamodb = new DynamoDBClient(options.region)
134+
135+
// First, verify the site exists
136+
const result = await dynamodb.getItem({
137+
TableName: options.table,
138+
Key: {
139+
pk: { S: `SITE#${siteId}` },
140+
sk: { S: `SITE#${siteId}` },
141+
},
142+
})
143+
144+
if (!result.Item) {
145+
cli.error(`Site not found: ${siteId}`)
146+
process.exit(1)
147+
}
148+
149+
const site = DynamoDBClient.unmarshal(result.Item)
150+
cli.info(`Updating site: ${site.name || 'Unnamed'} (${siteId})`)
151+
cli.info('')
152+
153+
// Build update expression
154+
const updates: string[] = []
155+
const expressionNames: Record<string, string> = {}
156+
const expressionValues: Record<string, any> = {}
157+
158+
if (options.name) {
159+
updates.push('#n = :name')
160+
expressionNames['#n'] = 'name'
161+
expressionValues[':name'] = { S: options.name }
162+
cli.info(` Name: ${site.name} -> ${options.name}`)
163+
}
164+
165+
if (options.domain !== undefined) {
166+
const domains = Array.isArray(options.domain) ? options.domain : [options.domain]
167+
updates.push('domains = :domains')
168+
expressionValues[':domains'] = { L: domains.map(d => ({ S: d })) }
169+
cli.info(` Domains: ${JSON.stringify(site.domains || [])} -> ${JSON.stringify(domains)}`)
170+
}
171+
172+
if (options.active !== undefined) {
173+
const isActive = options.active === 'true' || options.active === '1'
174+
updates.push('isActive = :active')
175+
expressionValues[':active'] = { BOOL: isActive }
176+
cli.info(` Active: ${site.isActive} -> ${isActive}`)
177+
}
178+
179+
if (updates.length === 0) {
180+
cli.warn('No updates specified. Use --name, --domain, or --active options.')
181+
return
182+
}
183+
184+
// Always update updatedAt
185+
updates.push('updatedAt = :updatedAt')
186+
expressionValues[':updatedAt'] = { S: new Date().toISOString() }
187+
188+
await dynamodb.updateItem({
189+
TableName: options.table,
190+
Key: {
191+
pk: { S: `SITE#${siteId}` },
192+
sk: { S: `SITE#${siteId}` },
193+
},
194+
UpdateExpression: `SET ${updates.join(', ')}`,
195+
ExpressionAttributeNames: Object.keys(expressionNames).length > 0 ? expressionNames : undefined,
196+
ExpressionAttributeValues: expressionValues,
197+
})
198+
199+
cli.info('')
200+
cli.success('Site updated successfully')
201+
}
202+
catch (error: any) {
203+
cli.error(`Failed to update site: ${error.message}`)
204+
process.exit(1)
205+
}
206+
})
207+
}

0 commit comments

Comments
 (0)