@gurulu/cli 0.4.7 → 1.0.1

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 (190) 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 +25751 -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 +33 -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 +25326 -876
  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/dist/lib/detect.d.ts +27 -0
  28. package/dist/lib/detect.d.ts.map +1 -0
  29. package/dist/lib/detect.js +106 -0
  30. package/dist/lib/exec-install.d.ts +21 -0
  31. package/dist/lib/exec-install.d.ts.map +1 -0
  32. package/dist/lib/install-plan.d.ts +25 -0
  33. package/dist/lib/install-plan.d.ts.map +1 -0
  34. package/dist/lib/install-plan.js +161 -0
  35. package/package.json +51 -20
  36. package/bin/gurulu.js +0 -2
  37. package/dist/api-client.d.ts +0 -33
  38. package/dist/api-client.js +0 -175
  39. package/dist/commands/add-server.d.ts +0 -9
  40. package/dist/commands/add-server.js +0 -162
  41. package/dist/commands/alerts.d.ts +0 -27
  42. package/dist/commands/alerts.js +0 -309
  43. package/dist/commands/api-keys.d.ts +0 -20
  44. package/dist/commands/api-keys.js +0 -130
  45. package/dist/commands/attribution.d.ts +0 -22
  46. package/dist/commands/attribution.js +0 -111
  47. package/dist/commands/audiences.d.ts +0 -23
  48. package/dist/commands/audiences.js +0 -243
  49. package/dist/commands/audit.d.ts +0 -20
  50. package/dist/commands/audit.js +0 -130
  51. package/dist/commands/auth.js +0 -249
  52. package/dist/commands/chat.d.ts +0 -19
  53. package/dist/commands/chat.js +0 -118
  54. package/dist/commands/config.d.ts +0 -10
  55. package/dist/commands/config.js +0 -92
  56. package/dist/commands/consent.d.ts +0 -27
  57. package/dist/commands/consent.js +0 -233
  58. package/dist/commands/conversion-paths.d.ts +0 -19
  59. package/dist/commands/conversion-paths.js +0 -55
  60. package/dist/commands/db.d.ts +0 -25
  61. package/dist/commands/db.js +0 -330
  62. package/dist/commands/destinations.d.ts +0 -20
  63. package/dist/commands/destinations.js +0 -191
  64. package/dist/commands/doctor.js +0 -360
  65. package/dist/commands/errors.d.ts +0 -27
  66. package/dist/commands/errors.js +0 -121
  67. package/dist/commands/events.d.ts +0 -33
  68. package/dist/commands/events.js +0 -371
  69. package/dist/commands/experiments.d.ts +0 -22
  70. package/dist/commands/experiments.js +0 -264
  71. package/dist/commands/funnels.d.ts +0 -17
  72. package/dist/commands/funnels.js +0 -203
  73. package/dist/commands/goals.d.ts +0 -18
  74. package/dist/commands/goals.js +0 -214
  75. package/dist/commands/heatmap.d.ts +0 -27
  76. package/dist/commands/heatmap.js +0 -112
  77. package/dist/commands/identity.d.ts +0 -29
  78. package/dist/commands/identity.js +0 -328
  79. package/dist/commands/init.js +0 -215
  80. package/dist/commands/insights.d.ts +0 -10
  81. package/dist/commands/insights.js +0 -77
  82. package/dist/commands/install.d.ts +0 -259
  83. package/dist/commands/install.js +0 -1590
  84. package/dist/commands/login.d.ts +0 -20
  85. package/dist/commands/login.js +0 -170
  86. package/dist/commands/logout.d.ts +0 -10
  87. package/dist/commands/logout.js +0 -41
  88. package/dist/commands/playground.d.ts +0 -11
  89. package/dist/commands/playground.js +0 -47
  90. package/dist/commands/releases.d.ts +0 -17
  91. package/dist/commands/releases.js +0 -54
  92. package/dist/commands/replay.d.ts +0 -18
  93. package/dist/commands/replay.js +0 -64
  94. package/dist/commands/secrets.d.ts +0 -19
  95. package/dist/commands/secrets.js +0 -145
  96. package/dist/commands/setup.d.ts +0 -21
  97. package/dist/commands/setup.js +0 -67
  98. package/dist/commands/sites.d.ts +0 -18
  99. package/dist/commands/sites.js +0 -139
  100. package/dist/commands/skad.d.ts +0 -18
  101. package/dist/commands/skad.js +0 -53
  102. package/dist/commands/sourcemap.d.ts +0 -33
  103. package/dist/commands/sourcemap.js +0 -204
  104. package/dist/commands/status.d.ts +0 -7
  105. package/dist/commands/status.js +0 -136
  106. package/dist/commands/upgrade.d.ts +0 -21
  107. package/dist/commands/upgrade.js +0 -183
  108. package/dist/commands/warehouse.d.ts +0 -20
  109. package/dist/commands/warehouse.js +0 -65
  110. package/dist/commands/warehouses.d.ts +0 -17
  111. package/dist/commands/warehouses.js +0 -182
  112. package/dist/commands/watch.d.ts +0 -45
  113. package/dist/commands/watch.js +0 -258
  114. package/dist/commands/whoami.d.ts +0 -9
  115. package/dist/commands/whoami.js +0 -50
  116. package/dist/config.d.ts +0 -75
  117. package/dist/config.js +0 -329
  118. package/dist/frameworks/detect.d.ts +0 -8
  119. package/dist/frameworks/detect.js +0 -458
  120. package/dist/install-intent-proposal.d.ts +0 -99
  121. package/dist/install-intent-proposal.js +0 -202
  122. package/dist/utils/api.d.ts +0 -20
  123. package/dist/utils/api.js +0 -47
  124. package/dist/utils/config.d.ts +0 -13
  125. package/dist/utils/config.js +0 -30
  126. package/dist/utils/confirm.d.ts +0 -17
  127. package/dist/utils/confirm.js +0 -40
  128. package/dist/utils/dry-run.d.ts +0 -20
  129. package/dist/utils/dry-run.js +0 -67
  130. package/dist/utils/from-file.d.ts +0 -9
  131. package/dist/utils/from-file.js +0 -72
  132. package/dist/utils/redact.d.ts +0 -14
  133. package/dist/utils/redact.js +0 -48
  134. package/dist/utils/ui.d.ts +0 -14
  135. package/dist/utils/ui.js +0 -59
  136. package/scripts/.gitkeep +0 -0
  137. package/scripts/README-gurulu-agentic-install.md +0 -114
  138. package/scripts/README-gurulu-scan.md +0 -98
  139. package/scripts/audit-cli-scopes.mjs +0 -204
  140. package/scripts/backfill-tenant-id.mjs +0 -172
  141. package/scripts/backfill-tenant-links.ts +0 -252
  142. package/scripts/backup-clickhouse.sh +0 -27
  143. package/scripts/backup-postgres.sh +0 -19
  144. package/scripts/bootstrap-runtime-schema.mjs +0 -87
  145. package/scripts/bootstrap-stripe.mjs +0 -158
  146. package/scripts/gurulu-agentic-install.lib.cjs +0 -762
  147. package/scripts/gurulu-agentic-install.mjs +0 -623
  148. package/scripts/gurulu-scan.lib.cjs +0 -1509
  149. package/scripts/gurulu-scan.mjs +0 -91
  150. package/scripts/gurulu-verify-install.lib.cjs +0 -334
  151. package/scripts/gurulu-verify-install.mjs +0 -59
  152. package/scripts/init-ssl.sh +0 -26
  153. package/scripts/migrate-flow-graph-enums.sh +0 -86
  154. package/scripts/monitor-disk.sh +0 -24
  155. package/scripts/patches/astro.patch.cjs +0 -74
  156. package/scripts/patches/auto-instrument/ast-helper.cjs +0 -480
  157. package/scripts/patches/auto-instrument/astro.cjs +0 -273
  158. package/scripts/patches/auto-instrument/express.cjs +0 -383
  159. package/scripts/patches/auto-instrument/fastify.cjs +0 -262
  160. package/scripts/patches/auto-instrument/hono.cjs +0 -392
  161. package/scripts/patches/auto-instrument/index.cjs +0 -80
  162. package/scripts/patches/auto-instrument/nestjs.cjs +0 -286
  163. package/scripts/patches/auto-instrument/nextjs-app-router.cjs +0 -345
  164. package/scripts/patches/auto-instrument/nextjs-pages.cjs +0 -361
  165. package/scripts/patches/auto-instrument/remix.cjs +0 -168
  166. package/scripts/patches/auto-instrument/sdk-helper-map.cjs +0 -241
  167. package/scripts/patches/auto-instrument/singleton-helper.cjs +0 -193
  168. package/scripts/patches/auto-instrument/sveltekit.cjs +0 -161
  169. package/scripts/patches/auto-instrument/vite-react.cjs +0 -37
  170. package/scripts/patches/auto-instrument/vue.cjs +0 -196
  171. package/scripts/patches/express.patch.cjs +0 -99
  172. package/scripts/patches/fastify.patch.cjs +0 -108
  173. package/scripts/patches/index.cjs +0 -300
  174. package/scripts/patches/nestjs.patch.cjs +0 -112
  175. package/scripts/patches/nextjs-app-router.patch.cjs +0 -97
  176. package/scripts/patches/nextjs-pages.patch.cjs +0 -97
  177. package/scripts/patches/remix.patch.cjs +0 -75
  178. package/scripts/patches/sveltekit.patch.cjs +0 -72
  179. package/scripts/patches/vite-react.patch.cjs +0 -73
  180. package/scripts/patches/vue.patch.cjs +0 -82
  181. package/scripts/renew-ssl.sh +0 -14
  182. package/scripts/resolve-migration.sh +0 -23
  183. package/scripts/seed-cli-dev-keys.mjs +0 -130
  184. package/scripts/seed-test-data.mjs +0 -391
  185. package/scripts/spike-browserless.ts +0 -65
  186. package/scripts/tenant-pivot-consistency-check.mjs +0 -205
  187. package/scripts/tenant-pivot-phase-3-cleanup.lib.cjs +0 -258
  188. package/scripts/tenant-pivot-phase-3-cleanup.mjs +0 -98
  189. package/scripts/test-identity-resolution.ts +0 -804
  190. 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
- });