stripe-experiment-sync 1.0.7 → 1.0.8-beta.1765872477
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/dist/{chunk-SX3HLE4H.js → chunk-E2HSDYSR.js} +39 -25
- package/dist/{chunk-7JWRDXNB.js → chunk-E7LIOBPP.js} +195 -23
- package/dist/{chunk-YXQZXR7S.js → chunk-M3MYAMG2.js} +1 -1
- package/dist/{chunk-3P5TZKWU.js → chunk-XDVV7YHP.js} +102 -63
- package/dist/cli/index.cjs +333 -106
- package/dist/cli/index.js +10 -6
- package/dist/cli/lib.cjs +327 -104
- package/dist/cli/lib.d.cts +5 -6
- package/dist/cli/lib.d.ts +5 -6
- package/dist/cli/lib.js +4 -4
- package/dist/index.cjs +196 -23
- package/dist/index.d.cts +53 -1
- package/dist/index.d.ts +53 -1
- package/dist/index.js +4 -2
- package/dist/migrations/0058_improve_sync_runs_status.sql +36 -0
- package/dist/supabase/index.cjs +39 -25
- package/dist/supabase/index.d.cts +6 -1
- package/dist/supabase/index.d.ts +6 -1
- package/dist/supabase/index.js +2 -2
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -33,7 +33,7 @@ var import_commander = require("commander");
|
|
|
33
33
|
// package.json
|
|
34
34
|
var package_default = {
|
|
35
35
|
name: "stripe-experiment-sync",
|
|
36
|
-
version: "1.0.
|
|
36
|
+
version: "1.0.8-beta.1765872477",
|
|
37
37
|
private: false,
|
|
38
38
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
39
39
|
type: "module",
|
|
@@ -145,20 +145,6 @@ async function loadConfig(options) {
|
|
|
145
145
|
}
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
|
-
if (!config.ngrokAuthToken) {
|
|
149
|
-
questions.push({
|
|
150
|
-
type: "password",
|
|
151
|
-
name: "ngrokAuthToken",
|
|
152
|
-
message: "Enter your ngrok auth token:",
|
|
153
|
-
mask: "*",
|
|
154
|
-
validate: (input) => {
|
|
155
|
-
if (!input || input.trim() === "") {
|
|
156
|
-
return "Ngrok auth token is required";
|
|
157
|
-
}
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
148
|
if (!config.databaseUrl) {
|
|
163
149
|
questions.push({
|
|
164
150
|
type: "password",
|
|
@@ -1789,19 +1775,8 @@ var StripeSync = class {
|
|
|
1789
1775
|
if (params?.runStartedAt) {
|
|
1790
1776
|
runStartedAt = params.runStartedAt;
|
|
1791
1777
|
} else {
|
|
1792
|
-
const runKey = await this.
|
|
1793
|
-
|
|
1794
|
-
params?.triggeredBy ?? "processNext"
|
|
1795
|
-
);
|
|
1796
|
-
if (!runKey) {
|
|
1797
|
-
const activeRun = await this.postgresClient.getActiveSyncRun(accountId);
|
|
1798
|
-
if (!activeRun) {
|
|
1799
|
-
throw new Error("Failed to get or create sync run");
|
|
1800
|
-
}
|
|
1801
|
-
runStartedAt = activeRun.runStartedAt;
|
|
1802
|
-
} else {
|
|
1803
|
-
runStartedAt = runKey.runStartedAt;
|
|
1804
|
-
}
|
|
1778
|
+
const { runKey } = await this.joinOrCreateSyncRun(params?.triggeredBy ?? "processNext");
|
|
1779
|
+
runStartedAt = runKey.runStartedAt;
|
|
1805
1780
|
}
|
|
1806
1781
|
await this.postgresClient.createObjectRuns(accountId, runStartedAt, [resourceName]);
|
|
1807
1782
|
const objRun = await this.postgresClient.getObjectRun(accountId, runStartedAt, resourceName);
|
|
@@ -1964,18 +1939,39 @@ var StripeSync = class {
|
|
|
1964
1939
|
}
|
|
1965
1940
|
return { synced: totalSynced };
|
|
1966
1941
|
}
|
|
1967
|
-
|
|
1968
|
-
|
|
1942
|
+
/**
|
|
1943
|
+
* Join existing sync run or create a new one.
|
|
1944
|
+
* Returns sync run key and list of supported objects to sync.
|
|
1945
|
+
*
|
|
1946
|
+
* Cooperative behavior: If a sync run already exists, joins it instead of failing.
|
|
1947
|
+
* This is used by workers and background processes that should cooperate.
|
|
1948
|
+
*
|
|
1949
|
+
* @param triggeredBy - What triggered this sync (for observability)
|
|
1950
|
+
* @returns Run key and list of objects to sync
|
|
1951
|
+
*/
|
|
1952
|
+
async joinOrCreateSyncRun(triggeredBy = "worker") {
|
|
1969
1953
|
await this.getCurrentAccount();
|
|
1970
1954
|
const accountId = await this.getAccountId();
|
|
1971
|
-
const
|
|
1972
|
-
if (!
|
|
1955
|
+
const result = await this.postgresClient.getOrCreateSyncRun(accountId, triggeredBy);
|
|
1956
|
+
if (!result) {
|
|
1973
1957
|
const activeRun = await this.postgresClient.getActiveSyncRun(accountId);
|
|
1974
1958
|
if (!activeRun) {
|
|
1975
1959
|
throw new Error("Failed to get or create sync run");
|
|
1976
1960
|
}
|
|
1977
|
-
return
|
|
1961
|
+
return {
|
|
1962
|
+
runKey: { accountId: activeRun.accountId, runStartedAt: activeRun.runStartedAt },
|
|
1963
|
+
objects: this.getSupportedSyncObjects()
|
|
1964
|
+
};
|
|
1978
1965
|
}
|
|
1966
|
+
const { accountId: runAccountId, runStartedAt } = result;
|
|
1967
|
+
return {
|
|
1968
|
+
runKey: { accountId: runAccountId, runStartedAt },
|
|
1969
|
+
objects: this.getSupportedSyncObjects()
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
async processUntilDone(params) {
|
|
1973
|
+
const { object } = params ?? { object: "all" };
|
|
1974
|
+
const { runKey } = await this.joinOrCreateSyncRun("processUntilDone");
|
|
1979
1975
|
return this.processUntilDoneWithRun(runKey.runStartedAt, object, params);
|
|
1980
1976
|
}
|
|
1981
1977
|
/**
|
|
@@ -3434,6 +3430,167 @@ async function runMigrations(config) {
|
|
|
3434
3430
|
}
|
|
3435
3431
|
}
|
|
3436
3432
|
|
|
3433
|
+
// src/websocket-client.ts
|
|
3434
|
+
var import_ws = __toESM(require("ws"), 1);
|
|
3435
|
+
var CLI_VERSION = "1.33.0";
|
|
3436
|
+
var PONG_WAIT = 10 * 1e3;
|
|
3437
|
+
var PING_PERIOD = PONG_WAIT * 2 / 10;
|
|
3438
|
+
function getClientUserAgent() {
|
|
3439
|
+
return JSON.stringify({
|
|
3440
|
+
name: "stripe-cli",
|
|
3441
|
+
version: CLI_VERSION,
|
|
3442
|
+
publisher: "stripe",
|
|
3443
|
+
os: process.platform
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
async function createCliSession(stripeApiKey) {
|
|
3447
|
+
const params = new URLSearchParams();
|
|
3448
|
+
params.append("device_name", "stripe-sync-engine");
|
|
3449
|
+
params.append("websocket_features[]", "webhooks");
|
|
3450
|
+
const response = await fetch("https://api.stripe.com/v1/stripecli/sessions", {
|
|
3451
|
+
method: "POST",
|
|
3452
|
+
headers: {
|
|
3453
|
+
Authorization: `Bearer ${stripeApiKey}`,
|
|
3454
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
3455
|
+
"User-Agent": `Stripe/v1 stripe-cli/${CLI_VERSION}`,
|
|
3456
|
+
"X-Stripe-Client-User-Agent": getClientUserAgent()
|
|
3457
|
+
},
|
|
3458
|
+
body: params.toString()
|
|
3459
|
+
});
|
|
3460
|
+
if (!response.ok) {
|
|
3461
|
+
const error = await response.text();
|
|
3462
|
+
throw new Error(`Failed to create CLI session: ${error}`);
|
|
3463
|
+
}
|
|
3464
|
+
return await response.json();
|
|
3465
|
+
}
|
|
3466
|
+
async function createStripeWebSocketClient(options) {
|
|
3467
|
+
const { stripeApiKey, onEvent, onReady, onError, onClose } = options;
|
|
3468
|
+
const session = await createCliSession(stripeApiKey);
|
|
3469
|
+
let ws = null;
|
|
3470
|
+
let pingInterval = null;
|
|
3471
|
+
let connected = false;
|
|
3472
|
+
let shouldReconnect = true;
|
|
3473
|
+
function connect() {
|
|
3474
|
+
const wsUrl = `${session.websocket_url}?websocket_feature=${encodeURIComponent(session.websocket_authorized_feature)}`;
|
|
3475
|
+
ws = new import_ws.default(wsUrl, {
|
|
3476
|
+
headers: {
|
|
3477
|
+
"Accept-Encoding": "identity",
|
|
3478
|
+
"User-Agent": `Stripe/v1 stripe-cli/${CLI_VERSION}`,
|
|
3479
|
+
"X-Stripe-Client-User-Agent": getClientUserAgent(),
|
|
3480
|
+
"Websocket-Id": session.websocket_id
|
|
3481
|
+
}
|
|
3482
|
+
});
|
|
3483
|
+
ws.on("pong", () => {
|
|
3484
|
+
});
|
|
3485
|
+
ws.on("open", () => {
|
|
3486
|
+
connected = true;
|
|
3487
|
+
pingInterval = setInterval(() => {
|
|
3488
|
+
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
3489
|
+
ws.ping();
|
|
3490
|
+
}
|
|
3491
|
+
}, PING_PERIOD);
|
|
3492
|
+
if (onReady) {
|
|
3493
|
+
onReady(session.secret);
|
|
3494
|
+
}
|
|
3495
|
+
});
|
|
3496
|
+
ws.on("message", async (data) => {
|
|
3497
|
+
try {
|
|
3498
|
+
const message = JSON.parse(data.toString());
|
|
3499
|
+
const ack = {
|
|
3500
|
+
type: "event_ack",
|
|
3501
|
+
event_id: message.webhook_id,
|
|
3502
|
+
webhook_conversation_id: message.webhook_conversation_id,
|
|
3503
|
+
webhook_id: message.webhook_id
|
|
3504
|
+
};
|
|
3505
|
+
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
3506
|
+
ws.send(JSON.stringify(ack));
|
|
3507
|
+
}
|
|
3508
|
+
let response;
|
|
3509
|
+
try {
|
|
3510
|
+
const result = await onEvent(message);
|
|
3511
|
+
response = {
|
|
3512
|
+
type: "webhook_response",
|
|
3513
|
+
webhook_id: message.webhook_id,
|
|
3514
|
+
webhook_conversation_id: message.webhook_conversation_id,
|
|
3515
|
+
forward_url: "stripe-sync-engine",
|
|
3516
|
+
status: result?.status ?? 200,
|
|
3517
|
+
http_headers: {},
|
|
3518
|
+
body: JSON.stringify({
|
|
3519
|
+
event_type: result?.event_type,
|
|
3520
|
+
event_id: result?.event_id,
|
|
3521
|
+
database_url: result?.databaseUrl,
|
|
3522
|
+
error: result?.error
|
|
3523
|
+
}),
|
|
3524
|
+
request_headers: message.http_headers,
|
|
3525
|
+
request_body: message.event_payload,
|
|
3526
|
+
notification_id: message.webhook_id
|
|
3527
|
+
};
|
|
3528
|
+
} catch (err) {
|
|
3529
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3530
|
+
response = {
|
|
3531
|
+
type: "webhook_response",
|
|
3532
|
+
webhook_id: message.webhook_id,
|
|
3533
|
+
webhook_conversation_id: message.webhook_conversation_id,
|
|
3534
|
+
forward_url: "stripe-sync-engine",
|
|
3535
|
+
status: 500,
|
|
3536
|
+
http_headers: {},
|
|
3537
|
+
body: JSON.stringify({ error: errorMessage }),
|
|
3538
|
+
request_headers: message.http_headers,
|
|
3539
|
+
request_body: message.event_payload,
|
|
3540
|
+
notification_id: message.webhook_id
|
|
3541
|
+
};
|
|
3542
|
+
if (onError) {
|
|
3543
|
+
onError(err instanceof Error ? err : new Error(errorMessage));
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
3547
|
+
ws.send(JSON.stringify(response));
|
|
3548
|
+
}
|
|
3549
|
+
} catch (err) {
|
|
3550
|
+
if (onError) {
|
|
3551
|
+
onError(err instanceof Error ? err : new Error(String(err)));
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
});
|
|
3555
|
+
ws.on("error", (error) => {
|
|
3556
|
+
if (onError) {
|
|
3557
|
+
onError(error);
|
|
3558
|
+
}
|
|
3559
|
+
});
|
|
3560
|
+
ws.on("close", (code, reason) => {
|
|
3561
|
+
connected = false;
|
|
3562
|
+
if (pingInterval) {
|
|
3563
|
+
clearInterval(pingInterval);
|
|
3564
|
+
pingInterval = null;
|
|
3565
|
+
}
|
|
3566
|
+
if (onClose) {
|
|
3567
|
+
onClose(code, reason.toString());
|
|
3568
|
+
}
|
|
3569
|
+
if (shouldReconnect) {
|
|
3570
|
+
const delay = (session.reconnect_delay || 5) * 1e3;
|
|
3571
|
+
setTimeout(() => {
|
|
3572
|
+
connect();
|
|
3573
|
+
}, delay);
|
|
3574
|
+
}
|
|
3575
|
+
});
|
|
3576
|
+
}
|
|
3577
|
+
connect();
|
|
3578
|
+
return {
|
|
3579
|
+
close: () => {
|
|
3580
|
+
shouldReconnect = false;
|
|
3581
|
+
if (pingInterval) {
|
|
3582
|
+
clearInterval(pingInterval);
|
|
3583
|
+
pingInterval = null;
|
|
3584
|
+
}
|
|
3585
|
+
if (ws) {
|
|
3586
|
+
ws.close(1e3, "Connection Done");
|
|
3587
|
+
ws = null;
|
|
3588
|
+
}
|
|
3589
|
+
},
|
|
3590
|
+
isConnected: () => connected
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3437
3594
|
// src/index.ts
|
|
3438
3595
|
var VERSION = package_default.version;
|
|
3439
3596
|
|
|
@@ -3473,14 +3630,14 @@ Creating ngrok tunnel for port ${port}...`));
|
|
|
3473
3630
|
// src/supabase/supabase.ts
|
|
3474
3631
|
var import_supabase_management_js = require("supabase-management-js");
|
|
3475
3632
|
|
|
3476
|
-
// raw-ts:/
|
|
3633
|
+
// raw-ts:/Users/lfdepombo/src/stripe-sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts
|
|
3477
3634
|
var stripe_setup_default = "import { StripeSync, runMigrations } from 'npm:stripe-experiment-sync'\n\nDeno.serve(async (req) => {\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 })\n }\n\n const authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n let stripeSync = null\n try {\n // Get and validate database URL\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n throw new Error('SUPABASE_DB_URL environment variable is not set')\n }\n // Remove sslmode from connection string (not supported by pg in Deno)\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n\n await runMigrations({ databaseUrl: dbUrl })\n\n stripeSync = new StripeSync({\n poolConfig: { connectionString: dbUrl, max: 2 }, // Need 2 for advisory lock + queries\n stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY'),\n })\n\n // Release any stale advisory locks from previous timeouts\n await stripeSync.postgresClient.query('SELECT pg_advisory_unlock_all()')\n\n // Construct webhook URL from SUPABASE_URL (available in all Edge Functions)\n const supabaseUrl = Deno.env.get('SUPABASE_URL')\n if (!supabaseUrl) {\n throw new Error('SUPABASE_URL environment variable is not set')\n }\n const webhookUrl = supabaseUrl + '/functions/v1/stripe-webhook'\n\n const webhook = await stripeSync.findOrCreateManagedWebhook(webhookUrl)\n\n await stripeSync.postgresClient.pool.end()\n\n return new Response(\n JSON.stringify({\n success: true,\n message: 'Setup complete',\n webhookId: webhook.id,\n }),\n {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n }\n )\n } catch (error) {\n console.error('Setup error:', error)\n // Cleanup on error\n if (stripeSync) {\n try {\n await stripeSync.postgresClient.query('SELECT pg_advisory_unlock_all()')\n await stripeSync.postgresClient.pool.end()\n } catch (cleanupErr) {\n console.warn('Cleanup failed:', cleanupErr)\n }\n }\n return new Response(JSON.stringify({ success: false, error: error.message }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n})\n";
|
|
3478
3635
|
|
|
3479
|
-
// raw-ts:/
|
|
3636
|
+
// raw-ts:/Users/lfdepombo/src/stripe-sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-webhook.ts
|
|
3480
3637
|
var stripe_webhook_default = "import { StripeSync } from 'npm:stripe-experiment-sync'\n\nDeno.serve(async (req) => {\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 })\n }\n\n const sig = req.headers.get('stripe-signature')\n if (!sig) {\n return new Response('Missing stripe-signature header', { status: 400 })\n }\n\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), { status: 500 })\n }\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n\n const stripeSync = new StripeSync({\n poolConfig: { connectionString: dbUrl, max: 1 },\n stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY')!,\n })\n\n try {\n const rawBody = new Uint8Array(await req.arrayBuffer())\n await stripeSync.processWebhook(rawBody, sig)\n return new Response(JSON.stringify({ received: true }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n } catch (error) {\n console.error('Webhook processing error:', error)\n const isSignatureError =\n error.message?.includes('signature') || error.type === 'StripeSignatureVerificationError'\n const status = isSignatureError ? 400 : 500\n return new Response(JSON.stringify({ error: error.message }), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n } finally {\n await stripeSync.postgresClient.pool.end()\n }\n})\n";
|
|
3481
3638
|
|
|
3482
|
-
// raw-ts:/
|
|
3483
|
-
var stripe_worker_default = "/**\n * Stripe Sync Worker\n *\n * Triggered by pg_cron every 10 seconds. Uses pgmq for durable work queue.\n *\n * Flow:\n * 1. Read batch of messages from pgmq (qty=10, vt=60s)\n * 2. If queue empty: enqueue all objects (continuous sync)\n * 3. Process messages in parallel (Promise.all):\n * - processNext(object)\n * - Delete message on success\n * - Re-enqueue if hasMore\n * 4. Return results summary\n *\n * Concurrency:\n * - Multiple workers can run concurrently via overlapping pg_cron triggers.\n * - Each worker processes its batch of messages in parallel (Promise.all).\n * - pgmq visibility timeout prevents duplicate message reads across workers.\n * - processNext() is idempotent (uses internal cursor tracking), so duplicate\n * processing on timeout/crash is safe.\n */\n\nimport { StripeSync } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\nconst QUEUE_NAME = 'stripe_sync_work'\nconst VISIBILITY_TIMEOUT = 60 // seconds\nconst BATCH_SIZE = 10\n\nDeno.serve(async (req) => {\n const authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), { status: 500 })\n }\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n\n let sql\n let stripeSync\n\n try {\n sql = postgres(dbUrl, { max: 1, prepare: false })\n } catch (error) {\n return new Response(\n JSON.stringify({\n error: 'Failed to create postgres connection',\n details: error.message,\n stack: error.stack,\n }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n )\n }\n\n try {\n stripeSync = new StripeSync({\n poolConfig: { connectionString: dbUrl, max: 1 },\n stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY')!,\n })\n } catch (error) {\n await sql.end()\n return new Response(\n JSON.stringify({\n error: 'Failed to create StripeSync',\n details: error.message,\n stack: error.stack,\n }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n )\n }\n\n try {\n // Read batch of messages from queue\n const messages = await sql`\n SELECT * FROM pgmq.read(${QUEUE_NAME}::text, ${VISIBILITY_TIMEOUT}::int, ${BATCH_SIZE}::int)\n `\n\n // If queue empty, enqueue all objects for continuous sync\n if (messages.length === 0) {\n const objects = stripeSync.
|
|
3639
|
+
// raw-ts:/Users/lfdepombo/src/stripe-sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-worker.ts
|
|
3640
|
+
var stripe_worker_default = "/**\n * Stripe Sync Worker\n *\n * Triggered by pg_cron every 10 seconds. Uses pgmq for durable work queue.\n *\n * Flow:\n * 1. Read batch of messages from pgmq (qty=10, vt=60s)\n * 2. If queue empty: enqueue all objects (continuous sync)\n * 3. Process messages in parallel (Promise.all):\n * - processNext(object)\n * - Delete message on success\n * - Re-enqueue if hasMore\n * 4. Return results summary\n *\n * Concurrency:\n * - Multiple workers can run concurrently via overlapping pg_cron triggers.\n * - Each worker processes its batch of messages in parallel (Promise.all).\n * - pgmq visibility timeout prevents duplicate message reads across workers.\n * - processNext() is idempotent (uses internal cursor tracking), so duplicate\n * processing on timeout/crash is safe.\n */\n\nimport { StripeSync } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\nconst QUEUE_NAME = 'stripe_sync_work'\nconst VISIBILITY_TIMEOUT = 60 // seconds\nconst BATCH_SIZE = 10\n\nDeno.serve(async (req) => {\n const authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), { status: 500 })\n }\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n\n let sql\n let stripeSync\n\n try {\n sql = postgres(dbUrl, { max: 1, prepare: false })\n } catch (error) {\n return new Response(\n JSON.stringify({\n error: 'Failed to create postgres connection',\n details: error.message,\n stack: error.stack,\n }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n )\n }\n\n try {\n stripeSync = new StripeSync({\n poolConfig: { connectionString: dbUrl, max: 1 },\n stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY')!,\n })\n } catch (error) {\n await sql.end()\n return new Response(\n JSON.stringify({\n error: 'Failed to create StripeSync',\n details: error.message,\n stack: error.stack,\n }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n )\n }\n\n try {\n // Read batch of messages from queue\n const messages = await sql`\n SELECT * FROM pgmq.read(${QUEUE_NAME}::text, ${VISIBILITY_TIMEOUT}::int, ${BATCH_SIZE}::int)\n `\n\n // If queue empty, enqueue all objects for continuous sync\n if (messages.length === 0) {\n // Create sync run to make enqueued work visible (status='pending')\n const { objects } = await stripeSync.joinOrCreateSyncRun('worker')\n const msgs = objects.map((object) => JSON.stringify({ object }))\n\n await sql`\n SELECT pgmq.send_batch(\n ${QUEUE_NAME}::text,\n ${sql.array(msgs)}::jsonb[]\n )\n `\n\n return new Response(JSON.stringify({ enqueued: objects.length, objects }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n // Process messages in parallel\n const results = await Promise.all(\n messages.map(async (msg) => {\n const { object } = msg.message as { object: string }\n\n try {\n const result = await stripeSync.processNext(object)\n\n // Delete message on success (cast to bigint to disambiguate overloaded function)\n await sql`SELECT pgmq.delete(${QUEUE_NAME}::text, ${msg.msg_id}::bigint)`\n\n // Re-enqueue if more pages\n if (result.hasMore) {\n await sql`SELECT pgmq.send(${QUEUE_NAME}::text, ${sql.json({ object })}::jsonb)`\n }\n\n return { object, ...result }\n } catch (error) {\n // Log error but continue to next message\n // Message will become visible again after visibility timeout\n console.error(`Error processing ${object}:`, error)\n return {\n object,\n processed: 0,\n hasMore: false,\n error: error.message,\n stack: error.stack,\n }\n }\n })\n )\n\n return new Response(JSON.stringify({ results }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n } catch (error) {\n console.error('Worker error:', error)\n return new Response(JSON.stringify({ error: error.message, stack: error.stack }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n } finally {\n if (sql) await sql.end()\n if (stripeSync) await stripeSync.postgresClient.pool.end()\n }\n})\n";
|
|
3484
3641
|
|
|
3485
3642
|
// src/supabase/edge-function-code.ts
|
|
3486
3643
|
var setupFunctionCode = stripe_setup_default;
|
|
@@ -3787,45 +3944,59 @@ var SupabaseSetupClient = class {
|
|
|
3787
3944
|
} catch (err) {
|
|
3788
3945
|
console.warn("Could not delete vault secret:", err);
|
|
3789
3946
|
}
|
|
3947
|
+
try {
|
|
3948
|
+
await this.runSQL(`
|
|
3949
|
+
SELECT pg_terminate_backend(pid)
|
|
3950
|
+
FROM pg_stat_activity
|
|
3951
|
+
WHERE datname = current_database()
|
|
3952
|
+
AND pid != pg_backend_pid()
|
|
3953
|
+
AND query ILIKE '%stripe.%'
|
|
3954
|
+
`);
|
|
3955
|
+
} catch (err) {
|
|
3956
|
+
console.warn("Could not terminate connections:", err);
|
|
3957
|
+
}
|
|
3790
3958
|
await this.runSQL(`DROP SCHEMA IF EXISTS stripe CASCADE`);
|
|
3791
3959
|
} catch (error) {
|
|
3792
3960
|
throw new Error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3793
3961
|
}
|
|
3794
3962
|
}
|
|
3795
|
-
|
|
3963
|
+
/**
|
|
3964
|
+
* Inject package version into Edge Function code
|
|
3965
|
+
*/
|
|
3966
|
+
injectPackageVersion(code, version) {
|
|
3967
|
+
if (version === "latest") {
|
|
3968
|
+
return code;
|
|
3969
|
+
}
|
|
3970
|
+
return code.replace(
|
|
3971
|
+
/from ['"]npm:stripe-experiment-sync['"]/g,
|
|
3972
|
+
`from 'npm:stripe-experiment-sync@${version}'`
|
|
3973
|
+
);
|
|
3974
|
+
}
|
|
3975
|
+
async install(stripeKey, packageVersion) {
|
|
3796
3976
|
const trimmedStripeKey = stripeKey.trim();
|
|
3797
3977
|
if (!trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
3798
3978
|
throw new Error('Stripe key should start with "sk_" or "rk_"');
|
|
3799
3979
|
}
|
|
3980
|
+
const version = packageVersion || "latest";
|
|
3800
3981
|
try {
|
|
3801
3982
|
await this.validateProject();
|
|
3802
3983
|
await this.runSQL(`CREATE SCHEMA IF NOT EXISTS stripe`);
|
|
3803
3984
|
await this.updateInstallationComment(
|
|
3804
3985
|
`${STRIPE_SCHEMA_COMMENT_PREFIX} v${package_default.version} ${INSTALLATION_STARTED_SUFFIX}`
|
|
3805
3986
|
);
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3987
|
+
const versionedSetup = this.injectPackageVersion(setupFunctionCode, version);
|
|
3988
|
+
const versionedWebhook = this.injectPackageVersion(webhookFunctionCode, version);
|
|
3989
|
+
const versionedWorker = this.injectPackageVersion(workerFunctionCode, version);
|
|
3990
|
+
await this.deployFunction("stripe-setup", versionedSetup);
|
|
3991
|
+
await this.deployFunction("stripe-webhook", versionedWebhook);
|
|
3992
|
+
await this.deployFunction("stripe-worker", versionedWorker);
|
|
3809
3993
|
await this.setSecrets([{ name: "STRIPE_SECRET_KEY", value: trimmedStripeKey }]);
|
|
3810
3994
|
const serviceRoleKey = await this.getServiceRoleKey();
|
|
3811
3995
|
const setupResult = await this.invokeFunction("stripe-setup", serviceRoleKey);
|
|
3812
3996
|
if (!setupResult.success) {
|
|
3813
3997
|
throw new Error(`Setup failed: ${setupResult.error}`);
|
|
3814
3998
|
}
|
|
3815
|
-
|
|
3816
|
-
try {
|
|
3817
|
-
await this.setupPgCronJob();
|
|
3818
|
-
pgCronEnabled = true;
|
|
3819
|
-
} catch {
|
|
3820
|
-
console.warn("pg_cron setup failed - falling back to manual worker invocation");
|
|
3821
|
-
}
|
|
3822
|
-
if (!pgCronEnabled) {
|
|
3823
|
-
try {
|
|
3824
|
-
await this.invokeFunction("stripe-worker", serviceRoleKey);
|
|
3825
|
-
} catch (err) {
|
|
3826
|
-
console.warn("Failed to trigger initial worker invocation:", err);
|
|
3827
|
-
}
|
|
3828
|
-
}
|
|
3999
|
+
await this.setupPgCronJob();
|
|
3829
4000
|
await this.updateInstallationComment(
|
|
3830
4001
|
`${STRIPE_SCHEMA_COMMENT_PREFIX} v${package_default.version} ${INSTALLATION_INSTALLED_SUFFIX}`
|
|
3831
4002
|
);
|
|
@@ -3838,14 +4009,14 @@ var SupabaseSetupClient = class {
|
|
|
3838
4009
|
}
|
|
3839
4010
|
};
|
|
3840
4011
|
async function install(params) {
|
|
3841
|
-
const { supabaseAccessToken, supabaseProjectRef, stripeKey } = params;
|
|
4012
|
+
const { supabaseAccessToken, supabaseProjectRef, stripeKey, packageVersion } = params;
|
|
3842
4013
|
const client = new SupabaseSetupClient({
|
|
3843
4014
|
accessToken: supabaseAccessToken,
|
|
3844
4015
|
projectRef: supabaseProjectRef,
|
|
3845
4016
|
projectBaseUrl: params.baseProjectUrl,
|
|
3846
4017
|
managementApiBaseUrl: params.baseManagementApiUrl
|
|
3847
4018
|
});
|
|
3848
|
-
await client.install(stripeKey);
|
|
4019
|
+
await client.install(stripeKey, packageVersion);
|
|
3849
4020
|
}
|
|
3850
4021
|
async function uninstall(params) {
|
|
3851
4022
|
const { supabaseAccessToken, supabaseProjectRef, stripeKey } = params;
|
|
@@ -4049,10 +4220,19 @@ async function syncCommand(options) {
|
|
|
4049
4220
|
let tunnel = null;
|
|
4050
4221
|
let server = null;
|
|
4051
4222
|
let webhookId = null;
|
|
4223
|
+
let wsClient = null;
|
|
4052
4224
|
const cleanup = async (signal) => {
|
|
4053
4225
|
console.log(import_chalk3.default.blue(`
|
|
4054
4226
|
|
|
4055
4227
|
Cleaning up... (signal: ${signal || "manual"})`));
|
|
4228
|
+
if (wsClient) {
|
|
4229
|
+
try {
|
|
4230
|
+
wsClient.close();
|
|
4231
|
+
console.log(import_chalk3.default.green("\u2713 WebSocket closed"));
|
|
4232
|
+
} catch {
|
|
4233
|
+
console.log(import_chalk3.default.yellow("\u26A0 Could not close WebSocket"));
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4056
4236
|
const keepWebhooksOnShutdown = process.env.KEEP_WEBHOOKS_ON_SHUTDOWN === "true";
|
|
4057
4237
|
if (webhookId && stripeSync && !keepWebhooksOnShutdown) {
|
|
4058
4238
|
try {
|
|
@@ -4096,7 +4276,12 @@ Cleaning up... (signal: ${signal || "manual"})`));
|
|
|
4096
4276
|
process.on("SIGTERM", () => cleanup("SIGTERM"));
|
|
4097
4277
|
try {
|
|
4098
4278
|
const config = await loadConfig(options);
|
|
4099
|
-
|
|
4279
|
+
const useWebSocketMode = !config.ngrokAuthToken;
|
|
4280
|
+
const modeLabel = useWebSocketMode ? "WebSocket" : "Webhook (ngrok)";
|
|
4281
|
+
console.log(import_chalk3.default.blue(`
|
|
4282
|
+
Mode: ${modeLabel}`));
|
|
4283
|
+
const maskedDbUrl = config.databaseUrl.replace(/:[^:@]+@/, ":****@");
|
|
4284
|
+
console.log(import_chalk3.default.gray(`Database: ${maskedDbUrl}`));
|
|
4100
4285
|
try {
|
|
4101
4286
|
await runMigrations({
|
|
4102
4287
|
databaseUrl: config.databaseUrl
|
|
@@ -4121,53 +4306,90 @@ Cleaning up... (signal: ${signal || "manual"})`));
|
|
|
4121
4306
|
backfillRelatedEntities: process.env.BACKFILL_RELATED_ENTITIES !== "false",
|
|
4122
4307
|
poolConfig
|
|
4123
4308
|
});
|
|
4124
|
-
const
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4309
|
+
const databaseUrlWithoutPassword = config.databaseUrl.replace(/:[^:@]+@/, ":****@");
|
|
4310
|
+
if (useWebSocketMode) {
|
|
4311
|
+
console.log(import_chalk3.default.blue("\nConnecting to Stripe WebSocket..."));
|
|
4312
|
+
wsClient = await createStripeWebSocketClient({
|
|
4313
|
+
stripeApiKey: config.stripeApiKey,
|
|
4314
|
+
onEvent: async (event) => {
|
|
4315
|
+
try {
|
|
4316
|
+
const payload = JSON.parse(event.event_payload);
|
|
4317
|
+
console.log(import_chalk3.default.cyan(`\u2190 ${payload.type}`) + import_chalk3.default.gray(` (${payload.id})`));
|
|
4318
|
+
if (stripeSync) {
|
|
4319
|
+
await stripeSync.processEvent(payload);
|
|
4320
|
+
return {
|
|
4321
|
+
status: 200,
|
|
4322
|
+
event_type: payload.type,
|
|
4323
|
+
event_id: payload.id,
|
|
4324
|
+
databaseUrl: databaseUrlWithoutPassword
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
4327
|
+
} catch (err) {
|
|
4328
|
+
console.error(import_chalk3.default.red("Error processing event:"), err);
|
|
4329
|
+
return {
|
|
4330
|
+
status: 500,
|
|
4331
|
+
databaseUrl: databaseUrlWithoutPassword,
|
|
4332
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4333
|
+
};
|
|
4334
|
+
}
|
|
4335
|
+
},
|
|
4336
|
+
onReady: (secret) => {
|
|
4337
|
+
console.log(import_chalk3.default.green("\u2713 Connected to Stripe WebSocket"));
|
|
4338
|
+
const maskedSecret = secret.length > 14 ? `${secret.slice(0, 10)}...${secret.slice(-4)}` : "****";
|
|
4339
|
+
console.log(import_chalk3.default.gray(` Webhook secret: ${maskedSecret}`));
|
|
4340
|
+
},
|
|
4341
|
+
onError: (error) => {
|
|
4342
|
+
console.error(import_chalk3.default.red("WebSocket error:"), error.message);
|
|
4343
|
+
},
|
|
4344
|
+
onClose: (code, reason) => {
|
|
4345
|
+
console.log(import_chalk3.default.yellow(`WebSocket closed: ${code} - ${reason}`));
|
|
4346
|
+
}
|
|
4347
|
+
});
|
|
4348
|
+
} else {
|
|
4349
|
+
const port = 3e3;
|
|
4350
|
+
tunnel = await createTunnel(port, config.ngrokAuthToken);
|
|
4351
|
+
const webhookPath = process.env.WEBHOOK_PATH || "/stripe-webhooks";
|
|
4352
|
+
console.log(import_chalk3.default.blue("\nCreating Stripe webhook endpoint..."));
|
|
4353
|
+
const webhook = await stripeSync.findOrCreateManagedWebhook(`${tunnel.url}${webhookPath}`);
|
|
4354
|
+
webhookId = webhook.id;
|
|
4355
|
+
const eventCount = webhook.enabled_events?.length || 0;
|
|
4356
|
+
console.log(import_chalk3.default.green(`\u2713 Webhook created: ${webhook.id}`));
|
|
4357
|
+
console.log(import_chalk3.default.cyan(` URL: ${webhook.url}`));
|
|
4358
|
+
console.log(import_chalk3.default.cyan(` Events: ${eventCount} supported events`));
|
|
4359
|
+
const app = (0, import_express.default)();
|
|
4360
|
+
const webhookRoute = webhookPath;
|
|
4361
|
+
app.use(webhookRoute, import_express.default.raw({ type: "application/json" }));
|
|
4362
|
+
app.post(webhookRoute, async (req, res) => {
|
|
4363
|
+
const sig = req.headers["stripe-signature"];
|
|
4364
|
+
if (!sig || typeof sig !== "string") {
|
|
4365
|
+
console.error("[Webhook] Missing stripe-signature header");
|
|
4366
|
+
return res.status(400).send({ error: "Missing stripe-signature header" });
|
|
4367
|
+
}
|
|
4368
|
+
const rawBody = req.body;
|
|
4369
|
+
if (!rawBody || !Buffer.isBuffer(rawBody)) {
|
|
4370
|
+
console.error("[Webhook] Body is not a Buffer!");
|
|
4371
|
+
return res.status(400).send({ error: "Missing raw body for signature verification" });
|
|
4372
|
+
}
|
|
4373
|
+
try {
|
|
4374
|
+
await stripeSync.processWebhook(rawBody, sig);
|
|
4375
|
+
return res.status(200).send({ received: true });
|
|
4376
|
+
} catch (error) {
|
|
4377
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
4378
|
+
console.error("[Webhook] Processing error:", errorMessage);
|
|
4379
|
+
return res.status(400).send({ error: errorMessage });
|
|
4380
|
+
}
|
|
4381
|
+
});
|
|
4382
|
+
app.use(import_express.default.json());
|
|
4383
|
+
app.use(import_express.default.urlencoded({ extended: false }));
|
|
4384
|
+
app.get("/health", async (req, res) => res.status(200).json({ status: "ok" }));
|
|
4385
|
+
console.log(import_chalk3.default.blue(`
|
|
4163
4386
|
Starting server on port ${port}...`));
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4387
|
+
await new Promise((resolve, reject) => {
|
|
4388
|
+
server = app.listen(port, "0.0.0.0", () => resolve());
|
|
4389
|
+
server.on("error", reject);
|
|
4167
4390
|
});
|
|
4168
|
-
|
|
4169
|
-
}
|
|
4170
|
-
console.log(import_chalk3.default.green(`\u2713 Server started on port ${port}`));
|
|
4391
|
+
console.log(import_chalk3.default.green(`\u2713 Server started on port ${port}`));
|
|
4392
|
+
}
|
|
4171
4393
|
if (process.env.SKIP_BACKFILL !== "true") {
|
|
4172
4394
|
console.log(import_chalk3.default.blue("\nStarting initial sync of all Stripe data..."));
|
|
4173
4395
|
const syncResult = await stripeSync.processUntilDone();
|
|
@@ -4244,7 +4466,8 @@ async function installCommand(options) {
|
|
|
4244
4466
|
await install({
|
|
4245
4467
|
supabaseAccessToken: accessToken,
|
|
4246
4468
|
supabaseProjectRef: projectRef,
|
|
4247
|
-
stripeKey
|
|
4469
|
+
stripeKey,
|
|
4470
|
+
packageVersion: options.packageVersion
|
|
4248
4471
|
});
|
|
4249
4472
|
console.log(import_chalk3.default.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"));
|
|
4250
4473
|
console.log(import_chalk3.default.cyan.bold(" Installation Complete!"));
|
|
@@ -4359,11 +4582,15 @@ program.command("backfill <entityName>").description("Backfill a specific entity
|
|
|
4359
4582
|
);
|
|
4360
4583
|
});
|
|
4361
4584
|
var supabase = program.command("supabase").description("Supabase Edge Functions commands");
|
|
4362
|
-
supabase.command("install").description("Install Stripe sync to Supabase Edge Functions").option("--token <token>", "Supabase access token (or SUPABASE_ACCESS_TOKEN env)").option("--project <ref>", "Supabase project ref (or SUPABASE_PROJECT_REF env)").option("--stripe-key <key>", "Stripe API key (or STRIPE_API_KEY env)").
|
|
4585
|
+
supabase.command("install").description("Install Stripe sync to Supabase Edge Functions").option("--token <token>", "Supabase access token (or SUPABASE_ACCESS_TOKEN env)").option("--project <ref>", "Supabase project ref (or SUPABASE_PROJECT_REF env)").option("--stripe-key <key>", "Stripe API key (or STRIPE_API_KEY env)").option(
|
|
4586
|
+
"--package-version <version>",
|
|
4587
|
+
"Package version to install (e.g., 1.0.8-beta.1, defaults to latest)"
|
|
4588
|
+
).action(async (options) => {
|
|
4363
4589
|
await installCommand({
|
|
4364
4590
|
supabaseAccessToken: options.token,
|
|
4365
4591
|
supabaseProjectRef: options.project,
|
|
4366
|
-
stripeKey: options.stripeKey
|
|
4592
|
+
stripeKey: options.stripeKey,
|
|
4593
|
+
packageVersion: options.packageVersion
|
|
4367
4594
|
});
|
|
4368
4595
|
});
|
|
4369
4596
|
supabase.command("uninstall").description("Uninstall Stripe sync from Supabase Edge Functions").option("--token <token>", "Supabase access token (or SUPABASE_ACCESS_TOKEN env)").option("--project <ref>", "Supabase project ref (or SUPABASE_PROJECT_REF env)").option("--stripe-key <key>", "Stripe API key (or STRIPE_API_KEY env)").action(async (options) => {
|
package/dist/cli/index.js
CHANGED
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
migrateCommand,
|
|
6
6
|
syncCommand,
|
|
7
7
|
uninstallCommand
|
|
8
|
-
} from "../chunk-
|
|
9
|
-
import "../chunk-
|
|
10
|
-
import "../chunk-
|
|
8
|
+
} from "../chunk-XDVV7YHP.js";
|
|
9
|
+
import "../chunk-E7LIOBPP.js";
|
|
10
|
+
import "../chunk-E2HSDYSR.js";
|
|
11
11
|
import {
|
|
12
12
|
package_default
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-M3MYAMG2.js";
|
|
14
14
|
|
|
15
15
|
// src/cli/index.ts
|
|
16
16
|
import { Command } from "commander";
|
|
@@ -38,11 +38,15 @@ program.command("backfill <entityName>").description("Backfill a specific entity
|
|
|
38
38
|
);
|
|
39
39
|
});
|
|
40
40
|
var supabase = program.command("supabase").description("Supabase Edge Functions commands");
|
|
41
|
-
supabase.command("install").description("Install Stripe sync to Supabase Edge Functions").option("--token <token>", "Supabase access token (or SUPABASE_ACCESS_TOKEN env)").option("--project <ref>", "Supabase project ref (or SUPABASE_PROJECT_REF env)").option("--stripe-key <key>", "Stripe API key (or STRIPE_API_KEY env)").
|
|
41
|
+
supabase.command("install").description("Install Stripe sync to Supabase Edge Functions").option("--token <token>", "Supabase access token (or SUPABASE_ACCESS_TOKEN env)").option("--project <ref>", "Supabase project ref (or SUPABASE_PROJECT_REF env)").option("--stripe-key <key>", "Stripe API key (or STRIPE_API_KEY env)").option(
|
|
42
|
+
"--package-version <version>",
|
|
43
|
+
"Package version to install (e.g., 1.0.8-beta.1, defaults to latest)"
|
|
44
|
+
).action(async (options) => {
|
|
42
45
|
await installCommand({
|
|
43
46
|
supabaseAccessToken: options.token,
|
|
44
47
|
supabaseProjectRef: options.project,
|
|
45
|
-
stripeKey: options.stripeKey
|
|
48
|
+
stripeKey: options.stripeKey,
|
|
49
|
+
packageVersion: options.packageVersion
|
|
46
50
|
});
|
|
47
51
|
});
|
|
48
52
|
supabase.command("uninstall").description("Uninstall Stripe sync from Supabase Edge Functions").option("--token <token>", "Supabase access token (or SUPABASE_ACCESS_TOKEN env)").option("--project <ref>", "Supabase project ref (or SUPABASE_PROJECT_REF env)").option("--stripe-key <key>", "Stripe API key (or STRIPE_API_KEY env)").action(async (options) => {
|