lytx 0.3.0
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/.env.example +37 -0
- package/README.md +486 -0
- package/alchemy.run.ts +155 -0
- package/cli/bootstrap-admin.ts +284 -0
- package/cli/deploy-staging.ts +692 -0
- package/cli/import-events.ts +628 -0
- package/cli/import-sites.ts +518 -0
- package/cli/index.ts +609 -0
- package/cli/init-db.ts +269 -0
- package/cli/migrate-to-durable-objects.ts +564 -0
- package/cli/migration-worker.ts +300 -0
- package/cli/performance-test.ts +588 -0
- package/cli/pg/client.ts +4 -0
- package/cli/pg/new-site.ts +153 -0
- package/cli/rollback-durable-objects.ts +622 -0
- package/cli/seed-data.ts +459 -0
- package/cli/setup.js +18 -0
- package/cli/setup.ts +463 -0
- package/cli/validate-migration.ts +200 -0
- package/cli/wrangler-migration.jsonc +28 -0
- package/db/adapter.ts +166 -0
- package/db/analytics_engine/client.ts +0 -0
- package/db/analytics_engine/sites.ts +0 -0
- package/db/client.ts +16 -0
- package/db/d1/client.ts +8 -0
- package/db/d1/drizzle.config.ts +35 -0
- package/db/d1/migrations/0000_true_maelstrom.sql +165 -0
- package/db/d1/migrations/0001_wonderful_bloodaxe.sql +12 -0
- package/db/d1/migrations/0002_late_frightful_four.sql +1 -0
- package/db/d1/migrations/0003_cuddly_obadiah_stane.sql +16 -0
- package/db/d1/migrations/0004_mute_stardust.sql +1 -0
- package/db/d1/migrations/0005_awesome_silvermane.sql +3 -0
- package/db/d1/migrations/0006_volatile_shriek.sql +2 -0
- package/db/d1/migrations/0007_superb_lila_cheney.sql +1 -0
- package/db/d1/migrations/0008_bitter_longshot.sql +17 -0
- package/db/d1/migrations/0009_wonderful_madame_masque.sql +28 -0
- package/db/d1/migrations/meta/0000_snapshot.json +1112 -0
- package/db/d1/migrations/meta/0001_snapshot.json +1187 -0
- package/db/d1/migrations/meta/0002_snapshot.json +1194 -0
- package/db/d1/migrations/meta/0003_snapshot.json +1296 -0
- package/db/d1/migrations/meta/0004_snapshot.json +1303 -0
- package/db/d1/migrations/meta/0005_snapshot.json +1325 -0
- package/db/d1/migrations/meta/0006_snapshot.json +1339 -0
- package/db/d1/migrations/meta/0007_snapshot.json +1347 -0
- package/db/d1/migrations/meta/0008_snapshot.json +1464 -0
- package/db/d1/migrations/meta/0009_snapshot.json +1648 -0
- package/db/d1/migrations/meta/_journal.json +76 -0
- package/db/d1/schema.ts +407 -0
- package/db/d1/sites.ts +374 -0
- package/db/d1/teamAiUsage.ts +101 -0
- package/db/d1/teams.ts +127 -0
- package/db/durable/drizzle.config.ts +8 -0
- package/db/durable/durableObjectClient.ts +480 -0
- package/db/durable/events.ts +100 -0
- package/db/durable/migrations/0000_fair_bucky.sql +38 -0
- package/db/durable/migrations/meta/0000_snapshot.json +278 -0
- package/db/durable/migrations/meta/_journal.json +13 -0
- package/db/durable/migrations/migrations.js +10 -0
- package/db/durable/schema.ts +5 -0
- package/db/durable/siteDurableObject.ts +1352 -0
- package/db/durable/types.ts +53 -0
- package/db/postgres/client.ts +13 -0
- package/db/postgres/drizzle.config.ts +12 -0
- package/db/postgres/migrations/0000_brainy_sprite.sql +116 -0
- package/db/postgres/migrations/meta/0000_snapshot.json +681 -0
- package/db/postgres/migrations/meta/_journal.json +13 -0
- package/db/postgres/schema.ts +145 -0
- package/db/postgres/sites.ts +118 -0
- package/db/tranformReports.ts +595 -0
- package/db/types.ts +55 -0
- package/endpoints/api_worker.tsx +1854 -0
- package/endpoints/site_do_worker.ts +11 -0
- package/index.d.ts +63 -0
- package/index.ts +83 -0
- package/lib/auth.ts +279 -0
- package/lib/geojson/world_countries.json +45307 -0
- package/lib/random_name.ts +41 -0
- package/lib/sendMail.ts +252 -0
- package/package.json +142 -0
- package/public/favicon.ico +0 -0
- package/public/images/android-chrome-192x192.png +0 -0
- package/public/images/android-chrome-512x512.png +0 -0
- package/public/images/apple-touch-icon.png +0 -0
- package/public/images/favicon-16x16.png +0 -0
- package/public/images/favicon-32x32.png +0 -0
- package/public/images/lytx_dark_dashboard.png +0 -0
- package/public/images/lytx_light_dashboard.png +0 -0
- package/public/images/safari-pinned-tab.svg +4 -0
- package/public/logo.png +0 -0
- package/public/site.webmanifest +26 -0
- package/public/sw.js +107 -0
- package/src/Document.tsx +86 -0
- package/src/api/ai_api.ts +1156 -0
- package/src/api/authMiddleware.ts +45 -0
- package/src/api/auth_api.ts +465 -0
- package/src/api/event_labels_api.ts +193 -0
- package/src/api/events_api.ts +210 -0
- package/src/api/queueWorker.ts +303 -0
- package/src/api/reports_api.ts +278 -0
- package/src/api/seed_api.ts +288 -0
- package/src/api/sites_api.ts +904 -0
- package/src/api/tag_api.ts +458 -0
- package/src/api/tag_api_v2.ts +289 -0
- package/src/api/team_api.ts +456 -0
- package/src/app/Dashboard.tsx +1339 -0
- package/src/app/Events.tsx +974 -0
- package/src/app/Explore.tsx +312 -0
- package/src/app/Layout.tsx +58 -0
- package/src/app/Settings.tsx +1302 -0
- package/src/app/components/DashboardCard.tsx +118 -0
- package/src/app/components/EditableCell.tsx +123 -0
- package/src/app/components/EventForm.tsx +93 -0
- package/src/app/components/MarketingFooter.tsx +49 -0
- package/src/app/components/MarketingNav.tsx +150 -0
- package/src/app/components/Nav.tsx +755 -0
- package/src/app/components/NewSiteSetup.tsx +298 -0
- package/src/app/components/SQLEditor.tsx +740 -0
- package/src/app/components/SiteSelector.tsx +126 -0
- package/src/app/components/SiteTag.tsx +42 -0
- package/src/app/components/SiteTagInstallCard.tsx +241 -0
- package/src/app/components/WorldMapCard.tsx +337 -0
- package/src/app/components/charts/ChartComponents.tsx +1481 -0
- package/src/app/components/charts/EventFunnel.tsx +45 -0
- package/src/app/components/charts/EventSummary.tsx +194 -0
- package/src/app/components/charts/SankeyFlows.tsx +72 -0
- package/src/app/components/marketing/CheckIcon.tsx +16 -0
- package/src/app/components/marketing/MarketingLayout.tsx +23 -0
- package/src/app/components/marketing/SectionHeading.tsx +35 -0
- package/src/app/components/reports/AskAiWorkspace.tsx +371 -0
- package/src/app/components/reports/CreateReportStarter.tsx +74 -0
- package/src/app/components/reports/DashboardRouteFiltersContext.tsx +14 -0
- package/src/app/components/reports/DashboardToolbar.tsx +154 -0
- package/src/app/components/reports/DashboardWorkspaceLayout.tsx +63 -0
- package/src/app/components/reports/DashboardWorkspaceShell.tsx +118 -0
- package/src/app/components/reports/ReportBuilderWorkspace.tsx +76 -0
- package/src/app/components/reports/custom/CustomReportBuilderPage.tsx +1667 -0
- package/src/app/components/reports/custom/ReportWidgetChart.tsx +297 -0
- package/src/app/components/reports/custom/buildWidgetSql.ts +151 -0
- package/src/app/components/reports/custom/chartPalettes.ts +18 -0
- package/src/app/components/reports/custom/types.ts +50 -0
- package/src/app/components/reports/reportBuilderMenuItems.ts +17 -0
- package/src/app/components/reports/useDashboardToolbarControls.tsx +235 -0
- package/src/app/components/ui/AlertBanner.tsx +101 -0
- package/src/app/components/ui/Button.tsx +55 -0
- package/src/app/components/ui/Card.tsx +80 -0
- package/src/app/components/ui/Input.tsx +72 -0
- package/src/app/components/ui/Link.tsx +23 -0
- package/src/app/components/ui/ReportBuilderMenu.tsx +246 -0
- package/src/app/components/ui/ThemeToggle.tsx +54 -0
- package/src/app/constants.ts +6 -0
- package/src/app/headers.ts +33 -0
- package/src/app/providers/AuthProvider.tsx +189 -0
- package/src/app/providers/ClientProviders.tsx +18 -0
- package/src/app/providers/QueryProvider.tsx +23 -0
- package/src/app/providers/ThemeProvider.tsx +88 -0
- package/src/app/utils/chartThemes.ts +146 -0
- package/src/app/utils/keybinds.ts +96 -0
- package/src/app/utils/media.tsx +24 -0
- package/src/client.tsx +114 -0
- package/src/config/createLytxAppConfig.ts +252 -0
- package/src/config/resourceNames.ts +88 -0
- package/src/db/index.ts +67 -0
- package/src/index.css +285 -0
- package/src/lib/featureFlags.ts +69 -0
- package/src/pages/GetStarted.tsx +290 -0
- package/src/pages/Home.tsx +268 -0
- package/src/pages/Login.tsx +283 -0
- package/src/pages/PrivacyPolicy.tsx +120 -0
- package/src/pages/Signup.tsx +267 -0
- package/src/pages/TermsOfService.tsx +126 -0
- package/src/pages/VerifyEmail.tsx +56 -0
- package/src/session/durableObject.ts +7 -0
- package/src/session/siteSchema.ts +86 -0
- package/src/session/types.ts +36 -0
- package/src/templates/README.md +80 -0
- package/src/templates/cleanFunctions.js +44 -0
- package/src/templates/embedFunctions.js +52 -0
- package/src/templates/lytx-shared.ts +662 -0
- package/src/templates/lytxpixel-core.ts +144 -0
- package/src/templates/lytxpixel.ts +267 -0
- package/src/templates/lytxpixelBrowser.js +634 -0
- package/src/templates/lytxpixelBrowser.mjs +634 -0
- package/src/templates/parseData.js +12 -0
- package/src/templates/script.ts +31 -0
- package/src/templates/template.tsx +50 -0
- package/src/templates/test.js +3 -0
- package/src/templates/trackWebEvents.ts +177 -0
- package/src/templates/vendors/clickcease.ts +8 -0
- package/src/templates/vendors/google.ts +174 -0
- package/src/templates/vendors/linkedin.ts +23 -0
- package/src/templates/vendors/meta.ts +56 -0
- package/src/templates/vendors/quantcast.ts +22 -0
- package/src/templates/vendors/simplfi.ts +7 -0
- package/src/types/app-context.ts +16 -0
- package/src/utilities/dashboardParams.ts +188 -0
- package/src/utilities/dashboardQueries.ts +537 -0
- package/src/utilities/dashboardTransforms.ts +167 -0
- package/src/utilities/dataValidation.ts +414 -0
- package/src/utilities/detector.ts +73 -0
- package/src/utilities/encrypt.ts +103 -0
- package/src/utilities/index.ts +13 -0
- package/src/utilities/parser.ts +117 -0
- package/src/utilities/performanceMonitoring.ts +570 -0
- package/src/utilities/route_interuptors.ts +24 -0
- package/src/worker.tsx +675 -0
- package/tsconfig.json +78 -0
- package/types/env.d.ts +16 -0
- package/types/rw.d.ts +7 -0
- package/types/shims.d.ts +53 -0
- package/types/vite.d.ts +19 -0
- package/vite/vite-plugin-pixel-bundle.ts +126 -0
- package/vite.config.ts +53 -0
- package/worker-configuration.d.ts +8401 -0
package/cli/seed-data.ts
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { createId } from "@paralleldrive/cuid2";
|
|
3
|
+
|
|
4
|
+
// Parse CLI arguments
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const getArg = (flag: string, defaultValue?: string): string => {
|
|
7
|
+
const index = args.indexOf(flag);
|
|
8
|
+
if (index === -1) {
|
|
9
|
+
if (defaultValue !== undefined) return defaultValue;
|
|
10
|
+
throw new Error(`Missing required argument: ${flag}`);
|
|
11
|
+
}
|
|
12
|
+
const value = args[index + 1];
|
|
13
|
+
if (!value || value.startsWith("-")) {
|
|
14
|
+
throw new Error(`Invalid value for ${flag}`);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const hasFlag = (flag: string): boolean => args.includes(flag);
|
|
20
|
+
|
|
21
|
+
// Check for help flag first
|
|
22
|
+
if (hasFlag("--help") || hasFlag("-h")) {
|
|
23
|
+
console.log(`
|
|
24
|
+
Usage: bun run cli/seed-data.ts [options]
|
|
25
|
+
|
|
26
|
+
Requires the dev server to be running (bun run dev).
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
-t, --team-id <id> Team ID to create sites for (required)
|
|
30
|
+
-s, --sites <number> Number of sites to create (default: 3)
|
|
31
|
+
--site-id <id> Populate existing site ID with events (skips site creation)
|
|
32
|
+
-e, --events <number> Number of events per site (default: 100)
|
|
33
|
+
--days <number> Number of days back to generate events (default: 30)
|
|
34
|
+
--dev-url <url> Base URL for dev server (default: "http://localhost:6123")
|
|
35
|
+
--seed-secret <value> SEED_DATA_SECRET value for dev bypass (required)
|
|
36
|
+
-h, --help Show this help message
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
bun run cli/seed-data.ts --team-id 1 --sites 2 --events 50 --seed-secret "$SEED_DATA_SECRET"
|
|
40
|
+
bun run cli/seed-data.ts --team-id 1 --site-id 3 --events 100 --seed-secret "$SEED_DATA_SECRET"
|
|
41
|
+
`);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// CLI argument parsing
|
|
46
|
+
const getTeamIdArg = () => {
|
|
47
|
+
try {
|
|
48
|
+
return parseInt(getArg("--team-id"));
|
|
49
|
+
} catch {
|
|
50
|
+
return parseInt(getArg("-t"));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const getSitesArg = () => {
|
|
55
|
+
try {
|
|
56
|
+
return parseInt(getArg("--sites"));
|
|
57
|
+
} catch {
|
|
58
|
+
try {
|
|
59
|
+
return parseInt(getArg("-s"));
|
|
60
|
+
} catch {
|
|
61
|
+
return 3;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const getEventsArg = () => {
|
|
67
|
+
try {
|
|
68
|
+
return parseInt(getArg("--events"));
|
|
69
|
+
} catch {
|
|
70
|
+
try {
|
|
71
|
+
return parseInt(getArg("-e"));
|
|
72
|
+
} catch {
|
|
73
|
+
return 100;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const getDaysArg = () => {
|
|
79
|
+
try {
|
|
80
|
+
return parseInt(getArg("--days"));
|
|
81
|
+
} catch {
|
|
82
|
+
return 30;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const getSiteIdArg = () => {
|
|
87
|
+
try {
|
|
88
|
+
return parseInt(getArg("--site-id"));
|
|
89
|
+
} catch {
|
|
90
|
+
return null; // Optional parameter
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const teamId = getTeamIdArg();
|
|
95
|
+
const numSites = getSitesArg();
|
|
96
|
+
const numEvents = getEventsArg();
|
|
97
|
+
const numDays = getDaysArg();
|
|
98
|
+
const siteId = getSiteIdArg();
|
|
99
|
+
const devUrl = hasFlag("--dev-url") ? getArg("--dev-url") : "http://localhost:6123";
|
|
100
|
+
const seedSecret = hasFlag("--seed-secret") ? getArg("--seed-secret") : "";
|
|
101
|
+
|
|
102
|
+
if (!seedSecret) {
|
|
103
|
+
console.error("ā Error: --seed-secret is required.");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Sample data arrays
|
|
108
|
+
const sampleDomains = [
|
|
109
|
+
"example.com",
|
|
110
|
+
"mystore.com",
|
|
111
|
+
"techblog.io",
|
|
112
|
+
"portfolio.dev",
|
|
113
|
+
"startup.co",
|
|
114
|
+
"agency.design",
|
|
115
|
+
"ecommerce.shop",
|
|
116
|
+
"news.today",
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
const sampleSiteNames = [
|
|
120
|
+
"Example Website",
|
|
121
|
+
"My Online Store",
|
|
122
|
+
"Tech Blog",
|
|
123
|
+
"Portfolio Site",
|
|
124
|
+
"Startup Landing",
|
|
125
|
+
"Design Agency",
|
|
126
|
+
"E-commerce Shop",
|
|
127
|
+
"News Portal",
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
const samplePages = [
|
|
131
|
+
"/",
|
|
132
|
+
"/about",
|
|
133
|
+
"/contact",
|
|
134
|
+
"/products",
|
|
135
|
+
"/services",
|
|
136
|
+
"/blog",
|
|
137
|
+
"/pricing",
|
|
138
|
+
"/features",
|
|
139
|
+
"/team",
|
|
140
|
+
"/careers",
|
|
141
|
+
"/support",
|
|
142
|
+
"/login",
|
|
143
|
+
"/signup",
|
|
144
|
+
"/checkout",
|
|
145
|
+
"/cart",
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
const sampleReferrers = [
|
|
149
|
+
"https://google.com",
|
|
150
|
+
"https://facebook.com",
|
|
151
|
+
"https://twitter.com",
|
|
152
|
+
"https://linkedin.com",
|
|
153
|
+
"https://reddit.com",
|
|
154
|
+
"https://github.com",
|
|
155
|
+
"https://stackoverflow.com",
|
|
156
|
+
"https://medium.com",
|
|
157
|
+
"https://dev.to",
|
|
158
|
+
"https://hackernews.com",
|
|
159
|
+
"",
|
|
160
|
+
"direct",
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
const sampleBrowsers = [
|
|
164
|
+
"Chrome 120.0.0",
|
|
165
|
+
"Firefox 121.0.0",
|
|
166
|
+
"Safari 17.2.0",
|
|
167
|
+
"Edge 120.0.0",
|
|
168
|
+
"Opera 105.0.0",
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
const sampleOS = [
|
|
172
|
+
"Windows 11",
|
|
173
|
+
"macOS 14.2",
|
|
174
|
+
"Ubuntu 22.04",
|
|
175
|
+
"iOS 17.2",
|
|
176
|
+
"Android 14",
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
const sampleDeviceTypes = ["desktop", "mobile", "tablet"];
|
|
180
|
+
|
|
181
|
+
const sampleGeo = [
|
|
182
|
+
{ country: "US", region: "California", city: "San Francisco" },
|
|
183
|
+
{ country: "US", region: "Texas", city: "Austin" },
|
|
184
|
+
{ country: "US", region: "New York", city: "New York" },
|
|
185
|
+
{ country: "GB", region: "London", city: "London" },
|
|
186
|
+
{ country: "CA", region: "Ontario", city: "Toronto" },
|
|
187
|
+
{ country: "DE", region: "Bavaria", city: "Munich" },
|
|
188
|
+
{ country: "FR", region: "Ile-de-France", city: "Paris" },
|
|
189
|
+
{ country: "AU", region: "New South Wales", city: "Sydney" },
|
|
190
|
+
{ country: "JP", region: "Tokyo", city: "Tokyo" },
|
|
191
|
+
{ country: "BR", region: "Sao Paulo", city: "Sao Paulo" },
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const sampleEvents = ["page_view", "form_fill", "phone_call"];
|
|
195
|
+
|
|
196
|
+
// Helper function to get random item from array
|
|
197
|
+
function randomItem<T>(array: T[]): T {
|
|
198
|
+
return array[Math.floor(Math.random() * array.length)];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Helper function to generate random timestamp within last N days (returns Unix timestamp in ms)
|
|
202
|
+
function randomTimestamp(daysBack: number): number {
|
|
203
|
+
const now = Date.now();
|
|
204
|
+
const daysInMs = daysBack * 24 * 60 * 60 * 1000;
|
|
205
|
+
return now - Math.floor(Math.random() * daysInMs);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function normalizeTimestampMs(timestamp: number): number {
|
|
209
|
+
return timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Helper function to generate screen dimensions
|
|
213
|
+
function randomScreenDimensions(): { width: number; height: number } {
|
|
214
|
+
const commonResolutions = [
|
|
215
|
+
{ width: 1920, height: 1080 },
|
|
216
|
+
{ width: 1366, height: 768 },
|
|
217
|
+
{ width: 1440, height: 900 },
|
|
218
|
+
{ width: 1536, height: 864 },
|
|
219
|
+
{ width: 375, height: 667 }, // iPhone
|
|
220
|
+
{ width: 414, height: 896 }, // iPhone
|
|
221
|
+
{ width: 768, height: 1024 }, // iPad
|
|
222
|
+
];
|
|
223
|
+
return randomItem(commonResolutions);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// API helper functions
|
|
227
|
+
async function apiRequest(path: string, options: RequestInit = {}) {
|
|
228
|
+
const url = `${devUrl}${path}`;
|
|
229
|
+
const response = await fetch(url, {
|
|
230
|
+
...options,
|
|
231
|
+
headers: {
|
|
232
|
+
"Content-Type": "application/json",
|
|
233
|
+
"x-seed-secret": seedSecret,
|
|
234
|
+
...options.headers,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (!response.ok) {
|
|
239
|
+
const text = await response.text();
|
|
240
|
+
throw new Error(`API request failed (${response.status}): ${text}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return response;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function checkTeamExists(teamId: number): Promise<boolean> {
|
|
247
|
+
try {
|
|
248
|
+
await apiRequest(`/api/seed/team/${teamId}`);
|
|
249
|
+
return true;
|
|
250
|
+
} catch (error: any) {
|
|
251
|
+
if (error.message.includes("404")) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
type SiteResponse = {
|
|
259
|
+
site_id: number;
|
|
260
|
+
uuid: string;
|
|
261
|
+
tag_id: string;
|
|
262
|
+
name: string | null;
|
|
263
|
+
domain: string | null;
|
|
264
|
+
site_db_adapter: string | null;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
type SeedEventsResponse = {
|
|
268
|
+
success: boolean;
|
|
269
|
+
inserted?: number;
|
|
270
|
+
error?: string;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
async function getSiteById(siteId: number, teamId: number): Promise<SiteResponse> {
|
|
274
|
+
const response = await apiRequest(`/api/seed/site/${siteId}?teamId=${teamId}`);
|
|
275
|
+
return response.json() as Promise<SiteResponse>;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function createSite(teamId: number, name: string, domain: string): Promise<SiteResponse> {
|
|
279
|
+
const response = await apiRequest("/api/seed/site", {
|
|
280
|
+
method: "POST",
|
|
281
|
+
body: JSON.stringify({ teamId, name, domain }),
|
|
282
|
+
});
|
|
283
|
+
return response.json() as Promise<SiteResponse>;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function seedEvents(siteId: number, teamId: number, events: Array<Record<string, unknown>>): Promise<SeedEventsResponse> {
|
|
287
|
+
const response = await apiRequest(`/api/seed/events/${siteId}?teamId=${teamId}`, {
|
|
288
|
+
method: "POST",
|
|
289
|
+
body: JSON.stringify(events),
|
|
290
|
+
});
|
|
291
|
+
return response.json() as Promise<SeedEventsResponse>;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function buildEventPayload(site: { tag_id: string; domain: string }, timestampSeconds: number) {
|
|
295
|
+
const page = randomItem(samplePages);
|
|
296
|
+
const referrer = randomItem(sampleReferrers);
|
|
297
|
+
const browser = randomItem(sampleBrowsers);
|
|
298
|
+
const os = randomItem(sampleOS);
|
|
299
|
+
const deviceType = randomItem(sampleDeviceTypes);
|
|
300
|
+
const geo = randomItem(sampleGeo);
|
|
301
|
+
const event = randomItem(sampleEvents);
|
|
302
|
+
const { width, height } = randomScreenDimensions();
|
|
303
|
+
const rid = createId();
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
tag_id: site.tag_id,
|
|
307
|
+
event,
|
|
308
|
+
client_page_url: page,
|
|
309
|
+
page_url: `${site.domain}${page}`,
|
|
310
|
+
referer: referrer,
|
|
311
|
+
browser,
|
|
312
|
+
operating_system: os,
|
|
313
|
+
device_type: deviceType,
|
|
314
|
+
country: geo.country,
|
|
315
|
+
region: geo.region,
|
|
316
|
+
city: geo.city,
|
|
317
|
+
screen_width: width,
|
|
318
|
+
screen_height: height,
|
|
319
|
+
rid,
|
|
320
|
+
createdAt: new Date(timestampSeconds * 1000).toISOString(),
|
|
321
|
+
updatedAt: new Date(timestampSeconds * 1000).toISOString(),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function seedData() {
|
|
326
|
+
try {
|
|
327
|
+
console.log("š± Seeding database with sample data...");
|
|
328
|
+
console.log(`š Dev server: ${devUrl}`);
|
|
329
|
+
console.log(`š¢ Team ID: ${teamId}`);
|
|
330
|
+
|
|
331
|
+
// Verify team exists
|
|
332
|
+
console.log("š Verifying team exists...");
|
|
333
|
+
const teamExists = await checkTeamExists(teamId);
|
|
334
|
+
if (!teamExists) {
|
|
335
|
+
console.error(`ā Team ${teamId} not found. Please create the team first using init-db.ts`);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
console.log("ā
Team verified");
|
|
339
|
+
|
|
340
|
+
if (siteId) {
|
|
341
|
+
console.log(`šÆ Using existing site ID: ${siteId}`);
|
|
342
|
+
console.log(`š Events to generate: ${numEvents}`);
|
|
343
|
+
} else {
|
|
344
|
+
console.log(`š Sites to create: ${numSites}`);
|
|
345
|
+
console.log(`š Events per site: ${numEvents}`);
|
|
346
|
+
}
|
|
347
|
+
console.log(`š
Days back: ${numDays}`);
|
|
348
|
+
|
|
349
|
+
const createdSites: Array<{
|
|
350
|
+
site_id: number;
|
|
351
|
+
tag_id: string;
|
|
352
|
+
name: string;
|
|
353
|
+
domain: string;
|
|
354
|
+
}> = [];
|
|
355
|
+
|
|
356
|
+
if (siteId) {
|
|
357
|
+
// Use existing site - fetch its details
|
|
358
|
+
console.log(`š” Fetching site ${siteId}...`);
|
|
359
|
+
const site = await getSiteById(siteId, teamId);
|
|
360
|
+
createdSites.push({
|
|
361
|
+
site_id: site.site_id,
|
|
362
|
+
tag_id: site.tag_id,
|
|
363
|
+
name: site.name || "Unknown Site",
|
|
364
|
+
domain: site.domain || "unknown.com",
|
|
365
|
+
});
|
|
366
|
+
console.log(`ā
Found existing site: ${site.name} (${site.domain})`);
|
|
367
|
+
} else {
|
|
368
|
+
// Create sample sites
|
|
369
|
+
for (let i = 0; i < numSites; i++) {
|
|
370
|
+
const name = sampleSiteNames[i % sampleSiteNames.length];
|
|
371
|
+
const domain = sampleDomains[i % sampleDomains.length];
|
|
372
|
+
|
|
373
|
+
console.log(`š” Creating site: ${name}...`);
|
|
374
|
+
const site = await createSite(teamId, name, domain);
|
|
375
|
+
|
|
376
|
+
createdSites.push({
|
|
377
|
+
site_id: site.site_id,
|
|
378
|
+
tag_id: site.tag_id,
|
|
379
|
+
name: site.name || name,
|
|
380
|
+
domain: site.domain || domain,
|
|
381
|
+
});
|
|
382
|
+
console.log(`ā
Created site ID: ${site.site_id} for ${name}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Generate sample events for each site
|
|
387
|
+
for (const site of createdSites) {
|
|
388
|
+
console.log(`š Generating ${numEvents} events for ${site.name}...`);
|
|
389
|
+
|
|
390
|
+
// SQLite has a ~999 variable limit per query, each event has ~16 fields
|
|
391
|
+
// So batch size of 25 = ~400 variables, safely under the limit
|
|
392
|
+
const batchSize = 25;
|
|
393
|
+
let eventBatch: Array<Record<string, unknown>> = [];
|
|
394
|
+
|
|
395
|
+
for (let i = 0; i < numEvents; i++) {
|
|
396
|
+
const timestamp = normalizeTimestampMs(randomTimestamp(numDays));
|
|
397
|
+
const timestampSeconds = Math.floor(timestamp / 1000);
|
|
398
|
+
|
|
399
|
+
eventBatch.push(buildEventPayload(site, timestampSeconds));
|
|
400
|
+
|
|
401
|
+
if (eventBatch.length === batchSize || i === numEvents - 1) {
|
|
402
|
+
const result = await seedEvents(site.site_id, teamId, eventBatch);
|
|
403
|
+
console.log(` ā
Inserted batch of ${eventBatch.length} events (total: ${result.inserted || eventBatch.length})`);
|
|
404
|
+
eventBatch = [];
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
console.log("\nā
Data seeding complete!");
|
|
410
|
+
console.log(`
|
|
411
|
+
š Summary:
|
|
412
|
+
Sites: ${createdSites.length}
|
|
413
|
+
Total events generated: ${createdSites.length * numEvents}
|
|
414
|
+
Team ID: ${teamId}
|
|
415
|
+
Dev server: ${devUrl}
|
|
416
|
+
`);
|
|
417
|
+
|
|
418
|
+
console.log(`
|
|
419
|
+
š Created/seeded sites:
|
|
420
|
+
${createdSites.map((site) => ` - ${site.name} (${site.domain}) - Tag ID: ${site.tag_id}`).join("\n")}
|
|
421
|
+
`);
|
|
422
|
+
|
|
423
|
+
console.log(`
|
|
424
|
+
š Next steps:
|
|
425
|
+
1. Login with your user credentials
|
|
426
|
+
2. View the analytics dashboard with sample data
|
|
427
|
+
3. Test the tracking script on your sites
|
|
428
|
+
`);
|
|
429
|
+
} catch (error) {
|
|
430
|
+
console.error("ā Error seeding data:", error);
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Validate required arguments
|
|
436
|
+
if (!teamId || isNaN(teamId)) {
|
|
437
|
+
console.error("ā Error: --team-id is required and must be a number");
|
|
438
|
+
console.log("Use --help for usage information");
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (siteId && (isNaN(siteId) || siteId <= 0)) {
|
|
443
|
+
console.error("ā Error: --site-id must be a positive number");
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if ((!siteId && numSites <= 0) || numEvents <= 0 || numDays <= 0) {
|
|
448
|
+
console.error(
|
|
449
|
+
"ā Error: --sites (when not using --site-id), --events, and --days must be positive numbers",
|
|
450
|
+
);
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (siteId && numSites !== 3) {
|
|
455
|
+
console.log("ā¹ļø Note: --sites parameter is ignored when using --site-id");
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Run the seeding
|
|
459
|
+
seedData();
|
package/cli/setup.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { resolve, dirname } from "node:path";
|
|
5
|
+
|
|
6
|
+
const cliPath = resolve(dirname(fileURLToPath(import.meta.url)), "./setup.ts");
|
|
7
|
+
const result = spawnSync("bun", [cliPath, ...process.argv.slice(2)], {
|
|
8
|
+
stdio: "inherit",
|
|
9
|
+
env: process.env,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
if (result.error) {
|
|
13
|
+
console.error("lytx-setup requires Bun to run: npm i -g bun or install via https://bun.sh");
|
|
14
|
+
console.error(result.error.message);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
process.exit(result.status ?? 0);
|