@gurulu/cli 0.4.6 → 1.0.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.
Files changed (180) hide show
  1. package/LICENSE +92 -0
  2. package/README.md +35 -106
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +25410 -0
  6. package/dist/commands/auth.d.ts +23 -20
  7. package/dist/commands/auth.d.ts.map +1 -0
  8. package/dist/commands/doctor.d.ts +20 -6
  9. package/dist/commands/doctor.d.ts.map +1 -0
  10. package/dist/commands/init.d.ts +25 -11
  11. package/dist/commands/init.d.ts.map +1 -0
  12. package/dist/commands/pull.d.ts +13 -0
  13. package/dist/commands/pull.d.ts.map +1 -0
  14. package/dist/commands/push.d.ts +40 -0
  15. package/dist/commands/push.d.ts.map +1 -0
  16. package/dist/commands/validate.d.ts +36 -0
  17. package/dist/commands/validate.d.ts.map +1 -0
  18. package/dist/index.d.ts +4 -1
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +24985 -853
  21. package/dist/lib/api.d.ts +139 -0
  22. package/dist/lib/api.d.ts.map +1 -0
  23. package/dist/lib/codegen.d.ts +4 -0
  24. package/dist/lib/codegen.d.ts.map +1 -0
  25. package/dist/lib/config.d.ts +43 -0
  26. package/dist/lib/config.d.ts.map +1 -0
  27. package/package.json +40 -20
  28. package/bin/gurulu.js +0 -2
  29. package/dist/api-client.d.ts +0 -33
  30. package/dist/api-client.js +0 -175
  31. package/dist/commands/add-server.d.ts +0 -9
  32. package/dist/commands/add-server.js +0 -162
  33. package/dist/commands/alerts.d.ts +0 -27
  34. package/dist/commands/alerts.js +0 -309
  35. package/dist/commands/api-keys.d.ts +0 -20
  36. package/dist/commands/api-keys.js +0 -130
  37. package/dist/commands/attribution.d.ts +0 -22
  38. package/dist/commands/attribution.js +0 -111
  39. package/dist/commands/audiences.d.ts +0 -23
  40. package/dist/commands/audiences.js +0 -243
  41. package/dist/commands/audit.d.ts +0 -20
  42. package/dist/commands/audit.js +0 -130
  43. package/dist/commands/auth.js +0 -249
  44. package/dist/commands/chat.d.ts +0 -18
  45. package/dist/commands/chat.js +0 -117
  46. package/dist/commands/config.d.ts +0 -10
  47. package/dist/commands/config.js +0 -92
  48. package/dist/commands/consent.d.ts +0 -27
  49. package/dist/commands/consent.js +0 -233
  50. package/dist/commands/conversion-paths.d.ts +0 -19
  51. package/dist/commands/conversion-paths.js +0 -55
  52. package/dist/commands/db.d.ts +0 -25
  53. package/dist/commands/db.js +0 -330
  54. package/dist/commands/destinations.d.ts +0 -20
  55. package/dist/commands/destinations.js +0 -191
  56. package/dist/commands/doctor.js +0 -360
  57. package/dist/commands/errors.d.ts +0 -27
  58. package/dist/commands/errors.js +0 -121
  59. package/dist/commands/events.d.ts +0 -33
  60. package/dist/commands/events.js +0 -349
  61. package/dist/commands/experiments.d.ts +0 -22
  62. package/dist/commands/experiments.js +0 -264
  63. package/dist/commands/funnels.d.ts +0 -17
  64. package/dist/commands/funnels.js +0 -203
  65. package/dist/commands/goals.d.ts +0 -18
  66. package/dist/commands/goals.js +0 -214
  67. package/dist/commands/heatmap.d.ts +0 -27
  68. package/dist/commands/heatmap.js +0 -112
  69. package/dist/commands/identity.d.ts +0 -29
  70. package/dist/commands/identity.js +0 -328
  71. package/dist/commands/init.js +0 -215
  72. package/dist/commands/insights.d.ts +0 -10
  73. package/dist/commands/insights.js +0 -65
  74. package/dist/commands/install.d.ts +0 -259
  75. package/dist/commands/install.js +0 -1590
  76. package/dist/commands/login.d.ts +0 -20
  77. package/dist/commands/login.js +0 -170
  78. package/dist/commands/logout.d.ts +0 -10
  79. package/dist/commands/logout.js +0 -41
  80. package/dist/commands/playground.d.ts +0 -11
  81. package/dist/commands/playground.js +0 -47
  82. package/dist/commands/releases.d.ts +0 -17
  83. package/dist/commands/releases.js +0 -54
  84. package/dist/commands/replay.d.ts +0 -18
  85. package/dist/commands/replay.js +0 -64
  86. package/dist/commands/secrets.d.ts +0 -19
  87. package/dist/commands/secrets.js +0 -145
  88. package/dist/commands/sites.d.ts +0 -18
  89. package/dist/commands/sites.js +0 -139
  90. package/dist/commands/skad.d.ts +0 -18
  91. package/dist/commands/skad.js +0 -53
  92. package/dist/commands/sourcemap.d.ts +0 -33
  93. package/dist/commands/sourcemap.js +0 -204
  94. package/dist/commands/status.d.ts +0 -7
  95. package/dist/commands/status.js +0 -136
  96. package/dist/commands/upgrade.d.ts +0 -21
  97. package/dist/commands/upgrade.js +0 -183
  98. package/dist/commands/warehouse.d.ts +0 -20
  99. package/dist/commands/warehouse.js +0 -65
  100. package/dist/commands/warehouses.d.ts +0 -17
  101. package/dist/commands/warehouses.js +0 -182
  102. package/dist/commands/watch.d.ts +0 -45
  103. package/dist/commands/watch.js +0 -258
  104. package/dist/commands/whoami.d.ts +0 -9
  105. package/dist/commands/whoami.js +0 -50
  106. package/dist/config.d.ts +0 -75
  107. package/dist/config.js +0 -329
  108. package/dist/frameworks/detect.d.ts +0 -8
  109. package/dist/frameworks/detect.js +0 -444
  110. package/dist/install-intent-proposal.d.ts +0 -99
  111. package/dist/install-intent-proposal.js +0 -202
  112. package/dist/utils/api.d.ts +0 -20
  113. package/dist/utils/api.js +0 -47
  114. package/dist/utils/config.d.ts +0 -13
  115. package/dist/utils/config.js +0 -30
  116. package/dist/utils/confirm.d.ts +0 -17
  117. package/dist/utils/confirm.js +0 -40
  118. package/dist/utils/dry-run.d.ts +0 -20
  119. package/dist/utils/dry-run.js +0 -67
  120. package/dist/utils/from-file.d.ts +0 -9
  121. package/dist/utils/from-file.js +0 -72
  122. package/dist/utils/redact.d.ts +0 -14
  123. package/dist/utils/redact.js +0 -48
  124. package/dist/utils/ui.d.ts +0 -14
  125. package/dist/utils/ui.js +0 -59
  126. package/scripts/.gitkeep +0 -0
  127. package/scripts/README-gurulu-agentic-install.md +0 -114
  128. package/scripts/README-gurulu-scan.md +0 -98
  129. package/scripts/audit-cli-scopes.mjs +0 -204
  130. package/scripts/backfill-tenant-id.mjs +0 -172
  131. package/scripts/backfill-tenant-links.ts +0 -252
  132. package/scripts/backup-clickhouse.sh +0 -27
  133. package/scripts/backup-postgres.sh +0 -19
  134. package/scripts/bootstrap-runtime-schema.mjs +0 -87
  135. package/scripts/bootstrap-stripe.mjs +0 -158
  136. package/scripts/gurulu-agentic-install.lib.cjs +0 -762
  137. package/scripts/gurulu-agentic-install.mjs +0 -623
  138. package/scripts/gurulu-scan.lib.cjs +0 -1509
  139. package/scripts/gurulu-scan.mjs +0 -91
  140. package/scripts/gurulu-verify-install.lib.cjs +0 -334
  141. package/scripts/gurulu-verify-install.mjs +0 -59
  142. package/scripts/init-ssl.sh +0 -26
  143. package/scripts/migrate-flow-graph-enums.sh +0 -86
  144. package/scripts/monitor-disk.sh +0 -24
  145. package/scripts/patches/astro.patch.cjs +0 -74
  146. package/scripts/patches/auto-instrument/ast-helper.cjs +0 -480
  147. package/scripts/patches/auto-instrument/astro.cjs +0 -273
  148. package/scripts/patches/auto-instrument/express.cjs +0 -383
  149. package/scripts/patches/auto-instrument/fastify.cjs +0 -262
  150. package/scripts/patches/auto-instrument/hono.cjs +0 -392
  151. package/scripts/patches/auto-instrument/index.cjs +0 -80
  152. package/scripts/patches/auto-instrument/nestjs.cjs +0 -286
  153. package/scripts/patches/auto-instrument/nextjs-app-router.cjs +0 -345
  154. package/scripts/patches/auto-instrument/nextjs-pages.cjs +0 -361
  155. package/scripts/patches/auto-instrument/remix.cjs +0 -168
  156. package/scripts/patches/auto-instrument/sdk-helper-map.cjs +0 -241
  157. package/scripts/patches/auto-instrument/singleton-helper.cjs +0 -193
  158. package/scripts/patches/auto-instrument/sveltekit.cjs +0 -161
  159. package/scripts/patches/auto-instrument/vite-react.cjs +0 -37
  160. package/scripts/patches/auto-instrument/vue.cjs +0 -196
  161. package/scripts/patches/express.patch.cjs +0 -99
  162. package/scripts/patches/fastify.patch.cjs +0 -108
  163. package/scripts/patches/index.cjs +0 -300
  164. package/scripts/patches/nestjs.patch.cjs +0 -112
  165. package/scripts/patches/nextjs-app-router.patch.cjs +0 -97
  166. package/scripts/patches/nextjs-pages.patch.cjs +0 -97
  167. package/scripts/patches/remix.patch.cjs +0 -75
  168. package/scripts/patches/sveltekit.patch.cjs +0 -72
  169. package/scripts/patches/vite-react.patch.cjs +0 -73
  170. package/scripts/patches/vue.patch.cjs +0 -82
  171. package/scripts/renew-ssl.sh +0 -14
  172. package/scripts/resolve-migration.sh +0 -23
  173. package/scripts/seed-cli-dev-keys.mjs +0 -130
  174. package/scripts/seed-test-data.mjs +0 -391
  175. package/scripts/spike-browserless.ts +0 -65
  176. package/scripts/tenant-pivot-consistency-check.mjs +0 -205
  177. package/scripts/tenant-pivot-phase-3-cleanup.lib.cjs +0 -258
  178. package/scripts/tenant-pivot-phase-3-cleanup.mjs +0 -98
  179. package/scripts/test-identity-resolution.ts +0 -804
  180. package/scripts/validate-gurulu-schemas.mjs +0 -79
@@ -1,252 +0,0 @@
1
- #!/usr/bin/env npx tsx
2
- /**
3
- * Backfill script: scan all deterministic claims in a tenant, group by
4
- * (claimType, claimValue), and create TenantIdentityLink + members for
5
- * any claims that exist across sites.
6
- *
7
- * Usage:
8
- * npx tsx scripts/backfill-tenant-links.ts <tenantId>
9
- * npx tsx scripts/backfill-tenant-links.ts --all
10
- *
11
- * Safe to run multiple times — all operations are idempotent upserts.
12
- */
13
-
14
- import { PrismaClient } from '@prisma/client';
15
- import { hashClaimForTenant } from '../src/lib/identity/tenant-link';
16
- import { clickhouseInsert } from '../src/lib/clickhouse';
17
-
18
- const prisma = new PrismaClient();
19
-
20
- const DETERMINISTIC_TYPES = ['email', 'phone', 'oauth_id'];
21
- const BATCH_SIZE = 500;
22
-
23
- interface ClaimGroup {
24
- claimType: string;
25
- claimValue: string;
26
- entries: Array<{ siteId: string; canonicalPersonId: string }>;
27
- }
28
-
29
- async function backfillTenantLinks(tenantId: string): Promise<{
30
- linksCreated: number;
31
- membersCreated: number;
32
- claimsScanned: number;
33
- }> {
34
- console.log(`[backfill] Starting tenant ${tenantId}...`);
35
-
36
- let linksCreated = 0;
37
- let membersCreated = 0;
38
- let claimsScanned = 0;
39
- const now = new Date();
40
-
41
- // Fetch all deterministic claims for this tenant in batches.
42
- // We join through CanonicalPerson to get tenantId filtering.
43
- let cursor: string | undefined;
44
- const claimMap = new Map<string, ClaimGroup>();
45
-
46
- while (true) {
47
- const claims = await prisma.identityClaim.findMany({
48
- where: {
49
- claimType: { in: DETERMINISTIC_TYPES },
50
- retiredAt: null,
51
- canonicalPerson: { tenantId },
52
- },
53
- select: {
54
- id: true,
55
- claimType: true,
56
- claimValue: true,
57
- siteId: true,
58
- canonicalPersonId: true,
59
- },
60
- orderBy: { id: 'asc' },
61
- take: BATCH_SIZE,
62
- ...(cursor ? { skip: 1, cursor: { id: cursor } } : {}),
63
- });
64
-
65
- if (claims.length === 0) break;
66
- cursor = claims[claims.length - 1].id;
67
- claimsScanned += claims.length;
68
-
69
- for (const claim of claims) {
70
- const key = `${claim.claimType}:${claim.claimValue}`;
71
- let group = claimMap.get(key);
72
- if (!group) {
73
- group = {
74
- claimType: claim.claimType,
75
- claimValue: claim.claimValue,
76
- entries: [],
77
- };
78
- claimMap.set(key, group);
79
- }
80
- // Deduplicate by (siteId, canonicalPersonId).
81
- const exists = group.entries.some(
82
- (e) => e.siteId === claim.siteId && e.canonicalPersonId === claim.canonicalPersonId,
83
- );
84
- if (!exists) {
85
- group.entries.push({
86
- siteId: claim.siteId,
87
- canonicalPersonId: claim.canonicalPersonId,
88
- });
89
- }
90
- }
91
-
92
- if (claims.length < BATCH_SIZE) break;
93
- }
94
-
95
- console.log(
96
- `[backfill] Scanned ${claimsScanned} claims, found ${claimMap.size} unique deterministic claim groups`,
97
- );
98
-
99
- // Process each claim group.
100
- const chRows: Array<Record<string, unknown>> = [];
101
-
102
- for (const [, group] of claimMap) {
103
- const claimValueHash = hashClaimForTenant(group.claimValue, tenantId);
104
-
105
- // Upsert the TenantIdentityLink.
106
- let link = await prisma.tenantIdentityLink.findUnique({
107
- where: {
108
- tenantId_claimType_claimValueHash: {
109
- tenantId,
110
- claimType: group.claimType,
111
- claimValueHash,
112
- },
113
- },
114
- });
115
-
116
- if (!link) {
117
- try {
118
- link = await prisma.tenantIdentityLink.create({
119
- data: {
120
- tenantId,
121
- claimType: group.claimType,
122
- claimValueHash,
123
- confidence: 1.0,
124
- firstLinkedAt: now,
125
- lastSeenAt: now,
126
- },
127
- });
128
- linksCreated++;
129
- } catch {
130
- // Race with concurrent run — fetch it.
131
- link = await prisma.tenantIdentityLink.findUnique({
132
- where: {
133
- tenantId_claimType_claimValueHash: {
134
- tenantId,
135
- claimType: group.claimType,
136
- claimValueHash,
137
- },
138
- },
139
- });
140
- if (!link) continue;
141
- }
142
- } else {
143
- await prisma.tenantIdentityLink.update({
144
- where: { id: link.id },
145
- data: { lastSeenAt: now },
146
- });
147
- }
148
-
149
- // Upsert members for each (site, person) pair.
150
- for (const entry of group.entries) {
151
- const existing = await prisma.tenantIdentityLinkMember.findUnique({
152
- where: {
153
- tenantIdentityLinkId_siteId_canonicalPersonId: {
154
- tenantIdentityLinkId: link.id,
155
- siteId: entry.siteId,
156
- canonicalPersonId: entry.canonicalPersonId,
157
- },
158
- },
159
- });
160
-
161
- if (!existing) {
162
- try {
163
- await prisma.tenantIdentityLinkMember.create({
164
- data: {
165
- tenantIdentityLinkId: link.id,
166
- siteId: entry.siteId,
167
- canonicalPersonId: entry.canonicalPersonId,
168
- firstSeenAt: now,
169
- lastSeenAt: now,
170
- },
171
- });
172
- membersCreated++;
173
- } catch {
174
- // Race — already exists.
175
- }
176
- } else {
177
- await prisma.tenantIdentityLinkMember.update({
178
- where: { id: existing.id },
179
- data: { lastSeenAt: now },
180
- });
181
- }
182
-
183
- // Collect ClickHouse row.
184
- chRows.push({
185
- tenant_id: tenantId,
186
- link_id: link.id,
187
- claim_type: group.claimType,
188
- claim_value_hash: claimValueHash,
189
- site_id: entry.siteId,
190
- canonical_person_id: entry.canonicalPersonId,
191
- confidence: link.confidence,
192
- first_linked_at: link.firstLinkedAt.toISOString(),
193
- last_seen_at: now.toISOString(),
194
- updated_at: now.toISOString(),
195
- });
196
- }
197
- }
198
-
199
- // Sync to ClickHouse in chunks.
200
- const CH_CHUNK = 100;
201
- for (let i = 0; i < chRows.length; i += CH_CHUNK) {
202
- const chunk = chRows.slice(i, i + CH_CHUNK);
203
- try {
204
- await clickhouseInsert('tenant_identity_links', chunk);
205
- } catch (err) {
206
- console.warn('[backfill] ClickHouse sync chunk failed:', (err as Error).message);
207
- }
208
- }
209
-
210
- console.log(
211
- `[backfill] Tenant ${tenantId} done: ${linksCreated} links created, ${membersCreated} members created, ${chRows.length} CH rows synced`,
212
- );
213
-
214
- return { linksCreated, membersCreated, claimsScanned };
215
- }
216
-
217
- async function main() {
218
- const arg = process.argv[2];
219
- if (!arg) {
220
- console.error('Usage: npx tsx scripts/backfill-tenant-links.ts <tenantId> | --all');
221
- process.exit(1);
222
- }
223
-
224
- try {
225
- if (arg === '--all') {
226
- const tenants = await prisma.tenant.findMany({ select: { id: true, name: true } });
227
- console.log(`[backfill] Processing ${tenants.length} tenants...`);
228
- let totalLinks = 0;
229
- let totalMembers = 0;
230
- let totalClaims = 0;
231
- for (const tenant of tenants) {
232
- console.log(`\n[backfill] === Tenant: ${tenant.name} (${tenant.id}) ===`);
233
- const result = await backfillTenantLinks(tenant.id);
234
- totalLinks += result.linksCreated;
235
- totalMembers += result.membersCreated;
236
- totalClaims += result.claimsScanned;
237
- }
238
- console.log(
239
- `\n[backfill] ALL DONE: ${totalLinks} links, ${totalMembers} members across ${totalClaims} claims`,
240
- );
241
- } else {
242
- await backfillTenantLinks(arg);
243
- }
244
- } finally {
245
- await prisma.$disconnect();
246
- }
247
- }
248
-
249
- main().catch((err) => {
250
- console.error('[backfill] Fatal error:', err);
251
- process.exit(1);
252
- });
@@ -1,27 +0,0 @@
1
- #!/bin/bash
2
- set -e
3
-
4
- BACKUP_DIR="${BACKUP_DIR:-/backups/clickhouse}"
5
- RETENTION_DAYS="${RETENTION_DAYS:-7}"
6
- TIMESTAMP=$(date +%Y%m%d_%H%M%S)
7
- BACKUP_NAME="gurulu_${TIMESTAMP}"
8
-
9
- mkdir -p "$BACKUP_DIR"
10
-
11
- echo "[backup] Starting ClickHouse backup..."
12
- docker exec gurulu-clickhouse clickhouse-client --query "BACKUP DATABASE gurulu TO Disk('backups', '${BACKUP_NAME}')" 2>/dev/null || {
13
- echo "[backup] Native backup not available, using clickhouse-client dump..."
14
- TABLES=$(docker exec gurulu-clickhouse clickhouse-client --query "SHOW TABLES FROM gurulu" 2>/dev/null)
15
-
16
- mkdir -p "${BACKUP_DIR}/${BACKUP_NAME}"
17
- for table in $TABLES; do
18
- echo "[backup] Dumping table: $table"
19
- docker exec gurulu-clickhouse clickhouse-client --query "SELECT * FROM gurulu.${table} FORMAT Native" > "${BACKUP_DIR}/${BACKUP_NAME}/${table}.native" 2>/dev/null || true
20
- done
21
- }
22
-
23
- echo "[backup] ClickHouse backup complete."
24
-
25
- # Cleanup
26
- find "$BACKUP_DIR" -maxdepth 1 -name "gurulu_*" -mtime +$RETENTION_DAYS -exec rm -rf {} +
27
- echo "[backup] Cleaned backups older than ${RETENTION_DAYS} days."
@@ -1,19 +0,0 @@
1
- #!/bin/bash
2
- set -e
3
-
4
- BACKUP_DIR="${BACKUP_DIR:-/backups/postgres}"
5
- RETENTION_DAYS="${RETENTION_DAYS:-7}"
6
- TIMESTAMP=$(date +%Y%m%d_%H%M%S)
7
- FILENAME="gurulu_${TIMESTAMP}.sql.gz"
8
-
9
- mkdir -p "$BACKUP_DIR"
10
-
11
- echo "[backup] Starting PostgreSQL backup..."
12
- docker exec gurulu-postgres pg_dump -U gurulu gurulu | gzip > "${BACKUP_DIR}/${FILENAME}"
13
-
14
- echo "[backup] Backup saved: ${BACKUP_DIR}/${FILENAME}"
15
- echo "[backup] Size: $(du -h "${BACKUP_DIR}/${FILENAME}" | cut -f1)"
16
-
17
- # Cleanup old backups
18
- find "$BACKUP_DIR" -name "gurulu_*.sql.gz" -mtime +$RETENTION_DAYS -delete
19
- echo "[backup] Cleaned backups older than ${RETENTION_DAYS} days."
@@ -1,87 +0,0 @@
1
- import { execSync } from 'node:child_process';
2
- import { readdirSync, readFileSync, existsSync } from 'node:fs';
3
- import { join } from 'node:path';
4
-
5
- const RETRY_COUNT = parseInt(process.env.MIGRATION_RETRY_COUNT || '5');
6
- const RETRY_DELAY = parseInt(process.env.MIGRATION_RETRY_DELAY_MS || '3000');
7
- const STRICT = process.env.BOOTSTRAP_STRICT !== 'false';
8
- const ENABLED = process.env.SCHEMA_BOOTSTRAP_ENABLED !== 'false';
9
-
10
- if (!ENABLED) {
11
- console.log('[bootstrap] Skipped (SCHEMA_BOOTSTRAP_ENABLED=false)');
12
- process.exit(0);
13
- }
14
-
15
- function sleep(ms) {
16
- return new Promise((r) => setTimeout(r, ms));
17
- }
18
-
19
- function exec(cmd) {
20
- console.log(`[bootstrap] $ ${cmd}`);
21
- return execSync(cmd, { stdio: 'inherit' });
22
- }
23
-
24
- async function retry(fn, label) {
25
- for (let i = 1; i <= RETRY_COUNT; i++) {
26
- try {
27
- await fn();
28
- return;
29
- } catch (err) {
30
- console.error(`[bootstrap] ${label} attempt ${i}/${RETRY_COUNT} failed:`, err.message);
31
- if (i === RETRY_COUNT) throw err;
32
- await sleep(RETRY_DELAY);
33
- }
34
- }
35
- }
36
-
37
- async function main() {
38
- console.log('[bootstrap] Starting runtime schema bootstrap...');
39
-
40
- // Step 1/2: Prisma migrate deploy — see /scripts/bootstrap-runtime-schema.mjs
41
- // for the FA-11 #3 / Sprint I-ops Faz 2 rationale.
42
- if (process.env.POSTGRES_AUTO_MIGRATE !== 'false') {
43
- console.log('\n[bootstrap] Step 1/2: Prisma migrate deploy');
44
- await retry(async () => {
45
- exec('npx prisma migrate deploy');
46
- }, 'migrate deploy');
47
- }
48
-
49
- // Step 2/2: SQL hooks
50
- if (process.env.POSTGRES_AUTO_MIGRATE !== 'false') {
51
- console.log('\n[bootstrap] Step 2/2: SQL hooks');
52
- const hooksDir = join(process.cwd(), 'prisma', 'sql-hooks');
53
-
54
- if (existsSync(hooksDir)) {
55
- const files = readdirSync(hooksDir)
56
- .filter((f) => f.endsWith('.sql'))
57
- .sort();
58
-
59
- for (const file of files) {
60
- console.log(`[bootstrap] Executing hook: ${file}`);
61
- const filePath = join(hooksDir, file);
62
- exec(`npx prisma db execute --file "${filePath}"`);
63
- }
64
- console.log(`[bootstrap] ${files.length} SQL hook(s) executed.`);
65
- } else {
66
- console.log('[bootstrap] No sql-hooks directory found, skipping.');
67
- }
68
- }
69
- // Step 3/3 (db push drift catch) removed — drift is now a hard fail.
70
-
71
- // ClickHouse migration
72
- if (process.env.CLICKHOUSE_AUTO_MIGRATE !== 'false') {
73
- console.log('\n[bootstrap] ClickHouse migration');
74
- const nodeIds = (process.env.CLICKHOUSE_MIGRATION_NODE_IDS || 'tr-01').split(',');
75
- console.log(`[bootstrap] Node IDs: ${nodeIds.join(', ')}`);
76
- // ClickHouse migrations are handled by init script in Docker
77
- console.log('[bootstrap] ClickHouse init handled by Docker entrypoint.');
78
- }
79
-
80
- console.log('\n[bootstrap] ✓ Schema bootstrap complete.');
81
- }
82
-
83
- main().catch((err) => {
84
- console.error('[bootstrap] Fatal error:', err);
85
- if (STRICT) process.exit(1);
86
- else console.warn('[bootstrap] BOOTSTRAP_STRICT=false, continuing despite error.');
87
- });
@@ -1,158 +0,0 @@
1
- import Stripe from 'stripe';
2
- import { readFileSync, writeFileSync, existsSync } from 'node:fs';
3
-
4
- const STRIPE_KEY = process.env.STRIPE_SECRET_KEY;
5
- if (!STRIPE_KEY) {
6
- console.error('[stripe] STRIPE_SECRET_KEY not set. Skipping bootstrap.');
7
- process.exit(1);
8
- }
9
-
10
- if (STRIPE_KEY.startsWith('sk_live_')) {
11
- console.warn(
12
- '[stripe] WARNING: Using a LIVE Stripe key. Products and prices will be created in production.'
13
- );
14
- const rl = await import('node:readline');
15
- const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
16
- const answer = await new Promise((resolve) =>
17
- iface.question('[stripe] Continue? (yes/no): ', resolve)
18
- );
19
- iface.close();
20
- if (answer.trim().toLowerCase() !== 'yes') {
21
- console.log('[stripe] Aborted.');
22
- process.exit(0);
23
- }
24
- }
25
-
26
- const stripe = new Stripe(STRIPE_KEY, {
27
- apiVersion: '2026-03-25.dahlia',
28
- });
29
-
30
- const PRODUCTS = [
31
- {
32
- name: 'Gurulu Starter',
33
- key: 'STARTER',
34
- monthlyPrice: 2900, // cents
35
- annualPrice: 27840, // 29 * 12 * 0.8 = $278.40 → $278
36
- events: 5_000_000,
37
- },
38
- {
39
- name: 'Gurulu Growth',
40
- key: 'GROWTH',
41
- monthlyPrice: 9900,
42
- annualPrice: 95040, // 99 * 12 * 0.8 = $950.40 → $950
43
- events: 25_000_000,
44
- },
45
- {
46
- name: 'Gurulu Business',
47
- key: 'BUSINESS',
48
- monthlyPrice: 29900,
49
- annualPrice: 287040, // 299 * 12 * 0.8 = $2870.40 → $2870
50
- events: 100_000_000,
51
- },
52
- ];
53
-
54
- async function main() {
55
- console.log('[stripe] Starting Stripe bootstrap...\n');
56
-
57
- const envUpdates = {};
58
-
59
- for (const product of PRODUCTS) {
60
- console.log(`[stripe] Creating product: ${product.name}`);
61
-
62
- // Check if product already exists by listing and filtering on metadata
63
- const allProducts = await stripe.products.list({ limit: 100, active: true });
64
- const matchingProduct = allProducts.data.find(
65
- (p) => p.metadata?.gurulu_key === product.key
66
- );
67
-
68
- let stripeProduct;
69
- if (matchingProduct) {
70
- stripeProduct = matchingProduct;
71
- console.log(` → Found existing product: ${stripeProduct.id}`);
72
- } else {
73
- stripeProduct = await stripe.products.create({
74
- name: product.name,
75
- metadata: {
76
- gurulu_key: product.key,
77
- events: String(product.events),
78
- },
79
- });
80
- console.log(` → Created product: ${stripeProduct.id}`);
81
- }
82
-
83
- // Fetch all existing prices for this product
84
- const existingPrices = await stripe.prices.list({
85
- product: stripeProduct.id,
86
- type: 'recurring',
87
- active: true,
88
- limit: 100,
89
- });
90
-
91
- // Monthly price
92
- let monthlyPrice = existingPrices.data.find(
93
- (p) => p.recurring?.interval === 'month' && p.unit_amount === product.monthlyPrice
94
- );
95
-
96
- if (!monthlyPrice) {
97
- monthlyPrice = await stripe.prices.create({
98
- product: stripeProduct.id,
99
- unit_amount: product.monthlyPrice,
100
- currency: 'usd',
101
- recurring: { interval: 'month' },
102
- metadata: { gurulu_key: product.key, interval: 'monthly' },
103
- });
104
- console.log(` → Created monthly price: ${monthlyPrice.id} ($${product.monthlyPrice / 100}/mo)`);
105
- } else {
106
- console.log(` → Found monthly price: ${monthlyPrice.id}`);
107
- }
108
-
109
- // Annual price
110
- let annualPrice = existingPrices.data.find(
111
- (p) => p.recurring?.interval === 'year' && p.unit_amount === product.annualPrice
112
- );
113
-
114
- if (!annualPrice) {
115
- annualPrice = await stripe.prices.create({
116
- product: stripeProduct.id,
117
- unit_amount: product.annualPrice,
118
- currency: 'usd',
119
- recurring: { interval: 'year' },
120
- metadata: { gurulu_key: product.key, interval: 'annual' },
121
- });
122
- console.log(` → Created annual price: ${annualPrice.id} ($${product.annualPrice / 100}/yr)`);
123
- } else {
124
- console.log(` → Found annual price: ${annualPrice.id}`);
125
- }
126
-
127
- envUpdates[`STRIPE_${product.key}_MONTHLY_PRICE_ID`] = monthlyPrice.id;
128
- envUpdates[`STRIPE_${product.key}_ANNUAL_PRICE_ID`] = annualPrice.id;
129
- }
130
-
131
- // Update .env file
132
- const envPath = '.env';
133
- let envContent = '';
134
- if (existsSync(envPath)) {
135
- envContent = readFileSync(envPath, 'utf-8');
136
- }
137
-
138
- for (const [key, value] of Object.entries(envUpdates)) {
139
- const regex = new RegExp(`^${key}=.*$`, 'm');
140
- if (regex.test(envContent)) {
141
- envContent = envContent.replace(regex, `${key}=${value}`);
142
- } else {
143
- envContent += `\n${key}=${value}`;
144
- }
145
- }
146
-
147
- writeFileSync(envPath, envContent.trimEnd() + '\n');
148
-
149
- console.log('\n[stripe] ✓ Bootstrap complete. Price IDs written to .env:');
150
- for (const [key, value] of Object.entries(envUpdates)) {
151
- console.log(` ${key}=${value}`);
152
- }
153
- }
154
-
155
- main().catch((err) => {
156
- console.error('[stripe] Bootstrap failed:', err.message);
157
- process.exit(1);
158
- });