stripe-experiment-sync 1.0.6 → 1.0.8

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.
@@ -0,0 +1,634 @@
1
+ import {
2
+ StripeSync,
3
+ createStripeWebSocketClient,
4
+ runMigrations
5
+ } from "./chunk-J7BH3XD6.js";
6
+ import {
7
+ install,
8
+ uninstall
9
+ } from "./chunk-X2OQQCC2.js";
10
+
11
+ // src/cli/config.ts
12
+ import dotenv from "dotenv";
13
+ import inquirer from "inquirer";
14
+ import chalk from "chalk";
15
+ async function loadConfig(options) {
16
+ dotenv.config();
17
+ const config = {};
18
+ config.stripeApiKey = options.stripeKey || process.env.STRIPE_API_KEY || "";
19
+ config.ngrokAuthToken = options.ngrokToken || process.env.NGROK_AUTH_TOKEN || "";
20
+ config.databaseUrl = options.databaseUrl || process.env.DATABASE_URL || "";
21
+ const questions = [];
22
+ if (!config.stripeApiKey) {
23
+ questions.push({
24
+ type: "password",
25
+ name: "stripeApiKey",
26
+ message: "Enter your Stripe API key:",
27
+ mask: "*",
28
+ validate: (input) => {
29
+ if (!input || input.trim() === "") {
30
+ return "Stripe API key is required";
31
+ }
32
+ if (!input.startsWith("sk_")) {
33
+ return 'Stripe API key should start with "sk_"';
34
+ }
35
+ return true;
36
+ }
37
+ });
38
+ }
39
+ if (!config.databaseUrl) {
40
+ questions.push({
41
+ type: "password",
42
+ name: "databaseUrl",
43
+ message: "Enter your Postgres DATABASE_URL:",
44
+ mask: "*",
45
+ validate: (input) => {
46
+ if (!input || input.trim() === "") {
47
+ return "DATABASE_URL is required";
48
+ }
49
+ if (!input.startsWith("postgres://") && !input.startsWith("postgresql://")) {
50
+ return 'DATABASE_URL should start with "postgres://" or "postgresql://"';
51
+ }
52
+ return true;
53
+ }
54
+ });
55
+ }
56
+ if (questions.length > 0) {
57
+ console.log(chalk.yellow("\nMissing required configuration. Please provide:"));
58
+ const answers = await inquirer.prompt(questions);
59
+ Object.assign(config, answers);
60
+ }
61
+ return config;
62
+ }
63
+
64
+ // src/cli/ngrok.ts
65
+ import ngrok from "@ngrok/ngrok";
66
+ import chalk2 from "chalk";
67
+ async function createTunnel(port, authToken) {
68
+ try {
69
+ console.log(chalk2.blue(`
70
+ Creating ngrok tunnel for port ${port}...`));
71
+ const listener = await ngrok.forward({
72
+ addr: port,
73
+ authtoken: authToken
74
+ });
75
+ const url = listener.url();
76
+ if (!url) {
77
+ throw new Error("Failed to get ngrok URL");
78
+ }
79
+ console.log(chalk2.green(`\u2713 ngrok tunnel created: ${url}`));
80
+ return {
81
+ url,
82
+ close: async () => {
83
+ console.log(chalk2.blue("\nClosing ngrok tunnel..."));
84
+ await listener.close();
85
+ console.log(chalk2.green("\u2713 ngrok tunnel closed"));
86
+ }
87
+ };
88
+ } catch (error) {
89
+ console.error(chalk2.red("\nFailed to create ngrok tunnel:"));
90
+ if (error instanceof Error) {
91
+ console.error(chalk2.red(error.message));
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+
97
+ // src/cli/commands.ts
98
+ import chalk3 from "chalk";
99
+ import express from "express";
100
+ import dotenv2 from "dotenv";
101
+ var VALID_SYNC_OBJECTS = [
102
+ "all",
103
+ "customer",
104
+ "customer_with_entitlements",
105
+ "invoice",
106
+ "price",
107
+ "product",
108
+ "subscription",
109
+ "subscription_schedules",
110
+ "setup_intent",
111
+ "payment_method",
112
+ "dispute",
113
+ "charge",
114
+ "payment_intent",
115
+ "plan",
116
+ "tax_id",
117
+ "credit_note",
118
+ "early_fraud_warning",
119
+ "refund",
120
+ "checkout_sessions"
121
+ ];
122
+ async function backfillCommand(options, entityName) {
123
+ let stripeSync = null;
124
+ try {
125
+ if (!VALID_SYNC_OBJECTS.includes(entityName)) {
126
+ console.error(
127
+ chalk3.red(
128
+ `Error: Invalid entity name "${entityName}". Valid entities are: ${VALID_SYNC_OBJECTS.join(", ")}`
129
+ )
130
+ );
131
+ process.exit(1);
132
+ }
133
+ dotenv2.config();
134
+ let stripeApiKey = options.stripeKey || process.env.STRIPE_API_KEY || process.env.STRIPE_SECRET_KEY || "";
135
+ let databaseUrl = options.databaseUrl || process.env.DATABASE_URL || "";
136
+ if (!stripeApiKey || !databaseUrl) {
137
+ const inquirer2 = (await import("inquirer")).default;
138
+ const questions = [];
139
+ if (!stripeApiKey) {
140
+ questions.push({
141
+ type: "password",
142
+ name: "stripeApiKey",
143
+ message: "Enter your Stripe API key:",
144
+ mask: "*",
145
+ validate: (input) => {
146
+ if (!input || input.trim() === "") {
147
+ return "Stripe API key is required";
148
+ }
149
+ if (!input.startsWith("sk_")) {
150
+ return 'Stripe API key should start with "sk_"';
151
+ }
152
+ return true;
153
+ }
154
+ });
155
+ }
156
+ if (!databaseUrl) {
157
+ questions.push({
158
+ type: "password",
159
+ name: "databaseUrl",
160
+ message: "Enter your Postgres DATABASE_URL:",
161
+ mask: "*",
162
+ validate: (input) => {
163
+ if (!input || input.trim() === "") {
164
+ return "DATABASE_URL is required";
165
+ }
166
+ if (!input.startsWith("postgres://") && !input.startsWith("postgresql://")) {
167
+ return 'DATABASE_URL should start with "postgres://" or "postgresql://"';
168
+ }
169
+ return true;
170
+ }
171
+ });
172
+ }
173
+ if (questions.length > 0) {
174
+ console.log(chalk3.yellow("\nMissing required configuration. Please provide:"));
175
+ const answers = await inquirer2.prompt(questions);
176
+ if (answers.stripeApiKey) stripeApiKey = answers.stripeApiKey;
177
+ if (answers.databaseUrl) databaseUrl = answers.databaseUrl;
178
+ }
179
+ }
180
+ const config = {
181
+ stripeApiKey,
182
+ databaseUrl,
183
+ ngrokAuthToken: ""
184
+ // Not needed for backfill
185
+ };
186
+ console.log(chalk3.blue(`Backfilling ${entityName} from Stripe in 'stripe' schema...`));
187
+ console.log(chalk3.gray(`Database: ${config.databaseUrl.replace(/:[^:@]+@/, ":****@")}`));
188
+ try {
189
+ await runMigrations({
190
+ databaseUrl: config.databaseUrl
191
+ });
192
+ } catch (migrationError) {
193
+ console.error(chalk3.red("Failed to run migrations:"));
194
+ console.error(
195
+ migrationError instanceof Error ? migrationError.message : String(migrationError)
196
+ );
197
+ throw migrationError;
198
+ }
199
+ const poolConfig = {
200
+ max: 10,
201
+ connectionString: config.databaseUrl,
202
+ keepAlive: true
203
+ };
204
+ stripeSync = new StripeSync({
205
+ databaseUrl: config.databaseUrl,
206
+ stripeSecretKey: config.stripeApiKey,
207
+ stripeApiVersion: process.env.STRIPE_API_VERSION || "2020-08-27",
208
+ autoExpandLists: process.env.AUTO_EXPAND_LISTS === "true",
209
+ backfillRelatedEntities: process.env.BACKFILL_RELATED_ENTITIES !== "false",
210
+ poolConfig
211
+ });
212
+ const result = await stripeSync.processUntilDone({ object: entityName });
213
+ const totalSynced = Object.values(result).reduce(
214
+ (sum, syncResult) => sum + (syncResult?.synced || 0),
215
+ 0
216
+ );
217
+ console.log(chalk3.green(`\u2713 Backfill complete: ${totalSynced} ${entityName} objects synced`));
218
+ await stripeSync.close();
219
+ } catch (error) {
220
+ if (error instanceof Error) {
221
+ console.error(chalk3.red(error.message));
222
+ }
223
+ if (stripeSync) {
224
+ try {
225
+ await stripeSync.close();
226
+ } catch {
227
+ }
228
+ }
229
+ process.exit(1);
230
+ }
231
+ }
232
+ async function migrateCommand(options) {
233
+ try {
234
+ dotenv2.config();
235
+ let databaseUrl = options.databaseUrl || process.env.DATABASE_URL || "";
236
+ if (!databaseUrl) {
237
+ const inquirer2 = (await import("inquirer")).default;
238
+ const answers = await inquirer2.prompt([
239
+ {
240
+ type: "password",
241
+ name: "databaseUrl",
242
+ message: "Enter your Postgres DATABASE_URL:",
243
+ mask: "*",
244
+ validate: (input) => {
245
+ if (!input || input.trim() === "") {
246
+ return "DATABASE_URL is required";
247
+ }
248
+ if (!input.startsWith("postgres://") && !input.startsWith("postgresql://")) {
249
+ return 'DATABASE_URL should start with "postgres://" or "postgresql://"';
250
+ }
251
+ return true;
252
+ }
253
+ }
254
+ ]);
255
+ databaseUrl = answers.databaseUrl;
256
+ }
257
+ console.log(chalk3.blue("Running database migrations in 'stripe' schema..."));
258
+ console.log(chalk3.gray(`Database: ${databaseUrl.replace(/:[^:@]+@/, ":****@")}`));
259
+ try {
260
+ await runMigrations({
261
+ databaseUrl
262
+ });
263
+ console.log(chalk3.green("\u2713 Migrations completed successfully"));
264
+ } catch (migrationError) {
265
+ console.warn(chalk3.yellow("Migrations failed."));
266
+ if (migrationError instanceof Error) {
267
+ const errorMsg = migrationError.message || migrationError.toString();
268
+ console.warn("Migration error:", errorMsg);
269
+ if (migrationError.stack) {
270
+ console.warn(chalk3.gray(migrationError.stack));
271
+ }
272
+ } else {
273
+ console.warn("Migration error:", String(migrationError));
274
+ }
275
+ throw migrationError;
276
+ }
277
+ } catch (error) {
278
+ if (error instanceof Error) {
279
+ console.error(chalk3.red(error.message));
280
+ }
281
+ process.exit(1);
282
+ }
283
+ }
284
+ async function syncCommand(options) {
285
+ let stripeSync = null;
286
+ let tunnel = null;
287
+ let server = null;
288
+ let webhookId = null;
289
+ let wsClient = null;
290
+ const cleanup = async (signal) => {
291
+ console.log(chalk3.blue(`
292
+
293
+ Cleaning up... (signal: ${signal || "manual"})`));
294
+ if (wsClient) {
295
+ try {
296
+ wsClient.close();
297
+ console.log(chalk3.green("\u2713 WebSocket closed"));
298
+ } catch {
299
+ console.log(chalk3.yellow("\u26A0 Could not close WebSocket"));
300
+ }
301
+ }
302
+ const keepWebhooksOnShutdown = process.env.KEEP_WEBHOOKS_ON_SHUTDOWN === "true";
303
+ if (webhookId && stripeSync && !keepWebhooksOnShutdown) {
304
+ try {
305
+ await stripeSync.deleteManagedWebhook(webhookId);
306
+ console.log(chalk3.green("\u2713 Webhook cleanup complete"));
307
+ } catch {
308
+ console.log(chalk3.yellow("\u26A0 Could not delete webhook"));
309
+ }
310
+ }
311
+ if (server) {
312
+ try {
313
+ await new Promise((resolve, reject) => {
314
+ server.close((err) => {
315
+ if (err) reject(err);
316
+ else resolve();
317
+ });
318
+ });
319
+ console.log(chalk3.green("\u2713 Server stopped"));
320
+ } catch {
321
+ console.log(chalk3.yellow("\u26A0 Server already stopped"));
322
+ }
323
+ }
324
+ if (tunnel) {
325
+ try {
326
+ await tunnel.close();
327
+ } catch {
328
+ console.log(chalk3.yellow("\u26A0 Could not close tunnel"));
329
+ }
330
+ }
331
+ if (stripeSync) {
332
+ try {
333
+ await stripeSync.close();
334
+ console.log(chalk3.green("\u2713 Database pool closed"));
335
+ } catch {
336
+ console.log(chalk3.yellow("\u26A0 Could not close database pool"));
337
+ }
338
+ }
339
+ process.exit(0);
340
+ };
341
+ process.on("SIGINT", () => cleanup("SIGINT"));
342
+ process.on("SIGTERM", () => cleanup("SIGTERM"));
343
+ try {
344
+ const config = await loadConfig(options);
345
+ const useWebSocketMode = !config.ngrokAuthToken;
346
+ const modeLabel = useWebSocketMode ? "WebSocket" : "Webhook (ngrok)";
347
+ console.log(chalk3.blue(`
348
+ Mode: ${modeLabel}`));
349
+ const maskedDbUrl = config.databaseUrl.replace(/:[^:@]+@/, ":****@");
350
+ console.log(chalk3.gray(`Database: ${maskedDbUrl}`));
351
+ try {
352
+ await runMigrations({
353
+ databaseUrl: config.databaseUrl
354
+ });
355
+ } catch (migrationError) {
356
+ console.error(chalk3.red("Failed to run migrations:"));
357
+ console.error(
358
+ migrationError instanceof Error ? migrationError.message : String(migrationError)
359
+ );
360
+ throw migrationError;
361
+ }
362
+ const poolConfig = {
363
+ max: 10,
364
+ connectionString: config.databaseUrl,
365
+ keepAlive: true
366
+ };
367
+ stripeSync = new StripeSync({
368
+ databaseUrl: config.databaseUrl,
369
+ stripeSecretKey: config.stripeApiKey,
370
+ stripeApiVersion: process.env.STRIPE_API_VERSION || "2020-08-27",
371
+ autoExpandLists: process.env.AUTO_EXPAND_LISTS === "true",
372
+ backfillRelatedEntities: process.env.BACKFILL_RELATED_ENTITIES !== "false",
373
+ poolConfig
374
+ });
375
+ const databaseUrlWithoutPassword = config.databaseUrl.replace(/:[^:@]+@/, ":****@");
376
+ if (useWebSocketMode) {
377
+ console.log(chalk3.blue("\nConnecting to Stripe WebSocket..."));
378
+ wsClient = await createStripeWebSocketClient({
379
+ stripeApiKey: config.stripeApiKey,
380
+ onEvent: async (event) => {
381
+ try {
382
+ const payload = JSON.parse(event.event_payload);
383
+ console.log(chalk3.cyan(`\u2190 ${payload.type}`) + chalk3.gray(` (${payload.id})`));
384
+ if (stripeSync) {
385
+ await stripeSync.processEvent(payload);
386
+ return {
387
+ status: 200,
388
+ event_type: payload.type,
389
+ event_id: payload.id,
390
+ databaseUrl: databaseUrlWithoutPassword
391
+ };
392
+ }
393
+ } catch (err) {
394
+ console.error(chalk3.red("Error processing event:"), err);
395
+ return {
396
+ status: 500,
397
+ databaseUrl: databaseUrlWithoutPassword,
398
+ error: err instanceof Error ? err.message : String(err)
399
+ };
400
+ }
401
+ },
402
+ onReady: (secret) => {
403
+ console.log(chalk3.green("\u2713 Connected to Stripe WebSocket"));
404
+ const maskedSecret = secret.length > 14 ? `${secret.slice(0, 10)}...${secret.slice(-4)}` : "****";
405
+ console.log(chalk3.gray(` Webhook secret: ${maskedSecret}`));
406
+ },
407
+ onError: (error) => {
408
+ console.error(chalk3.red("WebSocket error:"), error.message);
409
+ },
410
+ onClose: (code, reason) => {
411
+ console.log(chalk3.yellow(`WebSocket closed: ${code} - ${reason}`));
412
+ }
413
+ });
414
+ } else {
415
+ const port = 3e3;
416
+ tunnel = await createTunnel(port, config.ngrokAuthToken);
417
+ const webhookPath = process.env.WEBHOOK_PATH || "/stripe-webhooks";
418
+ console.log(chalk3.blue("\nCreating Stripe webhook endpoint..."));
419
+ const webhook = await stripeSync.findOrCreateManagedWebhook(`${tunnel.url}${webhookPath}`);
420
+ webhookId = webhook.id;
421
+ const eventCount = webhook.enabled_events?.length || 0;
422
+ console.log(chalk3.green(`\u2713 Webhook created: ${webhook.id}`));
423
+ console.log(chalk3.cyan(` URL: ${webhook.url}`));
424
+ console.log(chalk3.cyan(` Events: ${eventCount} supported events`));
425
+ const app = express();
426
+ const webhookRoute = webhookPath;
427
+ app.use(webhookRoute, express.raw({ type: "application/json" }));
428
+ app.post(webhookRoute, async (req, res) => {
429
+ const sig = req.headers["stripe-signature"];
430
+ if (!sig || typeof sig !== "string") {
431
+ console.error("[Webhook] Missing stripe-signature header");
432
+ return res.status(400).send({ error: "Missing stripe-signature header" });
433
+ }
434
+ const rawBody = req.body;
435
+ if (!rawBody || !Buffer.isBuffer(rawBody)) {
436
+ console.error("[Webhook] Body is not a Buffer!");
437
+ return res.status(400).send({ error: "Missing raw body for signature verification" });
438
+ }
439
+ try {
440
+ await stripeSync.processWebhook(rawBody, sig);
441
+ return res.status(200).send({ received: true });
442
+ } catch (error) {
443
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
444
+ console.error("[Webhook] Processing error:", errorMessage);
445
+ return res.status(400).send({ error: errorMessage });
446
+ }
447
+ });
448
+ app.use(express.json());
449
+ app.use(express.urlencoded({ extended: false }));
450
+ app.get("/health", async (req, res) => res.status(200).json({ status: "ok" }));
451
+ console.log(chalk3.blue(`
452
+ Starting server on port ${port}...`));
453
+ await new Promise((resolve, reject) => {
454
+ server = app.listen(port, "0.0.0.0", () => resolve());
455
+ server.on("error", reject);
456
+ });
457
+ console.log(chalk3.green(`\u2713 Server started on port ${port}`));
458
+ }
459
+ if (process.env.SKIP_BACKFILL !== "true") {
460
+ console.log(chalk3.blue("\nStarting initial sync of all Stripe data..."));
461
+ const syncResult = await stripeSync.processUntilDone();
462
+ const totalSynced = Object.values(syncResult).reduce(
463
+ (sum, result) => sum + (result?.synced || 0),
464
+ 0
465
+ );
466
+ console.log(chalk3.green(`\u2713 Sync complete: ${totalSynced} objects synced`));
467
+ } else {
468
+ console.log(chalk3.yellow("\n\u23ED\uFE0F Skipping initial sync (SKIP_BACKFILL=true)"));
469
+ }
470
+ console.log(
471
+ chalk3.cyan("\n\u25CF Streaming live changes...") + chalk3.gray(" [press Ctrl-C to abort]")
472
+ );
473
+ await new Promise(() => {
474
+ });
475
+ } catch (error) {
476
+ if (error instanceof Error) {
477
+ console.error(chalk3.red(error.message));
478
+ }
479
+ await cleanup();
480
+ process.exit(1);
481
+ }
482
+ }
483
+ async function installCommand(options) {
484
+ try {
485
+ dotenv2.config();
486
+ let accessToken = options.supabaseAccessToken || process.env.SUPABASE_ACCESS_TOKEN || "";
487
+ let projectRef = options.supabaseProjectRef || process.env.SUPABASE_PROJECT_REF || "";
488
+ let stripeKey = options.stripeKey || process.env.STRIPE_API_KEY || process.env.STRIPE_SECRET_KEY || "";
489
+ if (!accessToken || !projectRef || !stripeKey) {
490
+ const inquirer2 = (await import("inquirer")).default;
491
+ const questions = [];
492
+ if (!accessToken) {
493
+ questions.push({
494
+ type: "password",
495
+ name: "accessToken",
496
+ message: "Enter your Supabase access token (from supabase.com/dashboard/account/tokens):",
497
+ mask: "*",
498
+ validate: (input) => input.trim() !== "" || "Access token is required"
499
+ });
500
+ }
501
+ if (!projectRef) {
502
+ questions.push({
503
+ type: "input",
504
+ name: "projectRef",
505
+ message: "Enter your Supabase project ref (e.g., abcdefghijklmnop):",
506
+ validate: (input) => input.trim() !== "" || "Project ref is required"
507
+ });
508
+ }
509
+ if (!stripeKey) {
510
+ questions.push({
511
+ type: "password",
512
+ name: "stripeKey",
513
+ message: "Enter your Stripe secret key:",
514
+ mask: "*",
515
+ validate: (input) => {
516
+ if (!input.trim()) return "Stripe key is required";
517
+ if (!input.startsWith("sk_")) return 'Stripe key should start with "sk_"';
518
+ return true;
519
+ }
520
+ });
521
+ }
522
+ if (questions.length > 0) {
523
+ console.log(chalk3.yellow("\nMissing required configuration. Please provide:"));
524
+ const answers = await inquirer2.prompt(questions);
525
+ if (answers.accessToken) accessToken = answers.accessToken;
526
+ if (answers.projectRef) projectRef = answers.projectRef;
527
+ if (answers.stripeKey) stripeKey = answers.stripeKey;
528
+ }
529
+ }
530
+ console.log(chalk3.blue("\n\u{1F680} Installing Stripe Sync to Supabase Edge Functions...\n"));
531
+ console.log(chalk3.gray("Validating project access..."));
532
+ await install({
533
+ supabaseAccessToken: accessToken,
534
+ supabaseProjectRef: projectRef,
535
+ stripeKey,
536
+ packageVersion: options.packageVersion
537
+ });
538
+ console.log(chalk3.cyan("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
539
+ console.log(chalk3.cyan.bold(" Installation Complete!"));
540
+ console.log(chalk3.cyan("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
541
+ console.log(chalk3.gray("\n Your Stripe data will stay in sync to your Supabase database."));
542
+ console.log(
543
+ chalk3.gray(' View your data in the Supabase dashboard under the "stripe" schema.\n')
544
+ );
545
+ } catch (error) {
546
+ if (error instanceof Error) {
547
+ console.error(chalk3.red(`
548
+ \u2717 Installation failed: ${error.message}`));
549
+ }
550
+ process.exit(1);
551
+ }
552
+ }
553
+ async function uninstallCommand(options) {
554
+ try {
555
+ dotenv2.config();
556
+ let accessToken = options.supabaseAccessToken || process.env.SUPABASE_ACCESS_TOKEN || "";
557
+ let projectRef = options.supabaseProjectRef || process.env.SUPABASE_PROJECT_REF || "";
558
+ let stripeKey = options.stripeKey || process.env.STRIPE_API_KEY || process.env.STRIPE_SECRET_KEY || "";
559
+ if (!accessToken || !projectRef || !stripeKey) {
560
+ const inquirer2 = (await import("inquirer")).default;
561
+ const questions = [];
562
+ if (!accessToken) {
563
+ questions.push({
564
+ type: "password",
565
+ name: "accessToken",
566
+ message: "Enter your Supabase access token (from supabase.com/dashboard/account/tokens):",
567
+ mask: "*",
568
+ validate: (input) => input.trim() !== "" || "Access token is required"
569
+ });
570
+ }
571
+ if (!projectRef) {
572
+ questions.push({
573
+ type: "input",
574
+ name: "projectRef",
575
+ message: "Enter your Supabase project ref (e.g., abcdefghijklmnop):",
576
+ validate: (input) => input.trim() !== "" || "Project ref is required"
577
+ });
578
+ }
579
+ if (!stripeKey) {
580
+ questions.push({
581
+ type: "password",
582
+ name: "stripeKey",
583
+ message: "Enter your Stripe secret key:",
584
+ mask: "*",
585
+ validate: (input) => {
586
+ if (!input.trim()) return "Stripe key is required";
587
+ if (!input.startsWith("sk_")) return 'Stripe key should start with "sk_"';
588
+ return true;
589
+ }
590
+ });
591
+ }
592
+ if (questions.length > 0) {
593
+ console.log(chalk3.yellow("\nMissing required configuration. Please provide:"));
594
+ const answers = await inquirer2.prompt(questions);
595
+ if (answers.accessToken) accessToken = answers.accessToken;
596
+ if (answers.projectRef) projectRef = answers.projectRef;
597
+ if (answers.stripeKey) stripeKey = answers.stripeKey;
598
+ }
599
+ }
600
+ console.log(chalk3.blue("\n\u{1F5D1}\uFE0F Uninstalling Stripe Sync from Supabase...\n"));
601
+ console.log(chalk3.yellow("\u26A0\uFE0F Warning: This will delete all Stripe data from your database!\n"));
602
+ console.log(chalk3.gray("Removing all resources..."));
603
+ await uninstall({
604
+ supabaseAccessToken: accessToken,
605
+ supabaseProjectRef: projectRef,
606
+ stripeKey
607
+ });
608
+ console.log(chalk3.cyan("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
609
+ console.log(chalk3.cyan.bold(" Uninstall Complete!"));
610
+ console.log(chalk3.cyan("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
611
+ console.log(
612
+ chalk3.gray("\n All Stripe sync resources have been removed from your Supabase project.")
613
+ );
614
+ console.log(chalk3.gray(" - Edge Functions deleted"));
615
+ console.log(chalk3.gray(" - Stripe webhooks removed"));
616
+ console.log(chalk3.gray(" - Database schema dropped\n"));
617
+ } catch (error) {
618
+ if (error instanceof Error) {
619
+ console.error(chalk3.red(`
620
+ \u2717 Uninstall failed: ${error.message}`));
621
+ }
622
+ process.exit(1);
623
+ }
624
+ }
625
+
626
+ export {
627
+ loadConfig,
628
+ createTunnel,
629
+ backfillCommand,
630
+ migrateCommand,
631
+ syncCommand,
632
+ installCommand,
633
+ uninstallCommand
634
+ };