ardent-cli 0.0.37 → 0.0.39
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/index.js +260 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -460,9 +460,13 @@ function getAnonymousId() {
|
|
|
460
460
|
}
|
|
461
461
|
function trackEvent(event, properties = {}) {
|
|
462
462
|
const distinctId = getConfig("userId") ?? getAnonymousId();
|
|
463
|
+
const controller = new AbortController();
|
|
464
|
+
const timeout = setTimeout(() => controller.abort(), 500);
|
|
465
|
+
timeout.unref?.();
|
|
463
466
|
fetch(`${getApiUrl()}/v1/posthog/event`, {
|
|
464
467
|
method: "POST",
|
|
465
468
|
headers: { "Content-Type": "application/json" },
|
|
469
|
+
signal: controller.signal,
|
|
466
470
|
body: JSON.stringify({
|
|
467
471
|
events: [{
|
|
468
472
|
event,
|
|
@@ -474,14 +478,18 @@ function trackEvent(event, properties = {}) {
|
|
|
474
478
|
}]
|
|
475
479
|
})
|
|
476
480
|
}).catch(() => {
|
|
477
|
-
});
|
|
481
|
+
}).finally(() => clearTimeout(timeout));
|
|
478
482
|
}
|
|
479
483
|
function identifyUser(userId, personProperties = {}) {
|
|
480
484
|
const anonymousId = getAnonymousId();
|
|
481
485
|
setConfig("userId", userId);
|
|
486
|
+
const controller = new AbortController();
|
|
487
|
+
const timeout = setTimeout(() => controller.abort(), 500);
|
|
488
|
+
timeout.unref?.();
|
|
482
489
|
fetch(`${getApiUrl()}/v1/posthog/event`, {
|
|
483
490
|
method: "POST",
|
|
484
491
|
headers: { "Content-Type": "application/json" },
|
|
492
|
+
signal: controller.signal,
|
|
485
493
|
body: JSON.stringify({
|
|
486
494
|
events: [{
|
|
487
495
|
event: "$identify",
|
|
@@ -494,7 +502,7 @@ function identifyUser(userId, personProperties = {}) {
|
|
|
494
502
|
}]
|
|
495
503
|
})
|
|
496
504
|
}).catch(() => {
|
|
497
|
-
});
|
|
505
|
+
}).finally(() => clearTimeout(timeout));
|
|
498
506
|
}
|
|
499
507
|
|
|
500
508
|
// src/commands/branch/create.ts
|
|
@@ -805,7 +813,13 @@ var ENGINE_STATUS_DISPLAY = {
|
|
|
805
813
|
validating: "validation in progress",
|
|
806
814
|
failed_validation: "validation failed",
|
|
807
815
|
unhealthy: "unhealthy",
|
|
808
|
-
unchecked: "not yet configured"
|
|
816
|
+
unchecked: "not yet configured",
|
|
817
|
+
// ARD-1181: kafka2pg apply halt due to missing target relation. Phrase is
|
|
818
|
+
// deliberately operator-action-oriented and free of vendor names (Neon,
|
|
819
|
+
// Kafka, pgstream) and internal config keys per the CLAUDE.md
|
|
820
|
+
// customer-facing terseness rules. The internal error string + quarantine
|
|
821
|
+
// row count are logged by the detector for operator triage.
|
|
822
|
+
target_schema_lost: "needs reset (contact Ardent support)"
|
|
809
823
|
};
|
|
810
824
|
var CONNECTION_STATUS_DISPLAY = {
|
|
811
825
|
connected: "connected",
|
|
@@ -2938,6 +2952,7 @@ var ORIGINAL_SUFFIX = "-ardent-original";
|
|
|
2938
2952
|
var SERVICE_SUFFIXES = ["rest", "auth", "storage", "realtime", "pg_meta"];
|
|
2939
2953
|
var DOCKER_HOST_GATEWAY = "host.docker.internal";
|
|
2940
2954
|
var REALTIME_LIST_CHANGES_MIGRATION_VERSION = "20230328144023";
|
|
2955
|
+
var MIGRATION_IMPORT_TIMEOUT_MS = 15e3;
|
|
2941
2956
|
var SENSITIVE_DOCKER_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
2942
2957
|
"DATABASE_URL",
|
|
2943
2958
|
"DB_PASSWORD",
|
|
@@ -3515,19 +3530,68 @@ function dumpLocalStorageMigrations(dbContainer) {
|
|
|
3515
3530
|
"--no-privileges"
|
|
3516
3531
|
]);
|
|
3517
3532
|
}
|
|
3533
|
+
function dumpLocalRealtimeMigrations(dbContainer) {
|
|
3534
|
+
return runDocker([
|
|
3535
|
+
"exec",
|
|
3536
|
+
dbContainer,
|
|
3537
|
+
"pg_dump",
|
|
3538
|
+
"-U",
|
|
3539
|
+
"postgres",
|
|
3540
|
+
"-d",
|
|
3541
|
+
"postgres",
|
|
3542
|
+
"--data-only",
|
|
3543
|
+
"--table=realtime.schema_migrations",
|
|
3544
|
+
"--column-inserts",
|
|
3545
|
+
"--no-owner",
|
|
3546
|
+
"--no-privileges"
|
|
3547
|
+
]);
|
|
3548
|
+
}
|
|
3518
3549
|
function storageMigrationsImportSql(dumpSql) {
|
|
3519
3550
|
const tempTable = "pg_temp.ardent_storage_migrations_import";
|
|
3520
|
-
const tempDumpSql = dumpSql
|
|
3551
|
+
const tempDumpSql = retargetPgDumpInserts(dumpSql, "storage", "migrations", tempTable);
|
|
3521
3552
|
return `
|
|
3553
|
+
SET lock_timeout = '2s';
|
|
3554
|
+
SET statement_timeout = '10s';
|
|
3522
3555
|
BEGIN;
|
|
3523
3556
|
CREATE TEMP TABLE ardent_storage_migrations_import (LIKE storage.migrations INCLUDING DEFAULTS);
|
|
3524
3557
|
${tempDumpSql}
|
|
3525
3558
|
INSERT INTO storage.migrations
|
|
3526
3559
|
SELECT * FROM ${tempTable}
|
|
3527
3560
|
ON CONFLICT DO NOTHING;
|
|
3561
|
+
COMMIT;
|
|
3562
|
+
`;
|
|
3563
|
+
}
|
|
3564
|
+
function realtimeMigrationsImportSql(dumpSql) {
|
|
3565
|
+
const tempTable = "pg_temp.ardent_realtime_migrations_import";
|
|
3566
|
+
const tempDumpSql = retargetPgDumpInserts(dumpSql, "realtime", "schema_migrations", tempTable);
|
|
3567
|
+
return `
|
|
3568
|
+
SET lock_timeout = '2s';
|
|
3569
|
+
SET statement_timeout = '10s';
|
|
3570
|
+
BEGIN;
|
|
3571
|
+
CREATE SCHEMA IF NOT EXISTS realtime;
|
|
3572
|
+
CREATE TABLE IF NOT EXISTS realtime.schema_migrations (
|
|
3573
|
+
version bigint PRIMARY KEY,
|
|
3574
|
+
inserted_at timestamp(0) without time zone
|
|
3575
|
+
);
|
|
3576
|
+
CREATE TEMP TABLE ardent_realtime_migrations_import (LIKE realtime.schema_migrations INCLUDING DEFAULTS);
|
|
3577
|
+
${tempDumpSql}
|
|
3578
|
+
INSERT INTO realtime.schema_migrations
|
|
3579
|
+
SELECT * FROM ${tempTable}
|
|
3580
|
+
ON CONFLICT DO NOTHING;
|
|
3528
3581
|
COMMIT;
|
|
3529
3582
|
`;
|
|
3530
3583
|
}
|
|
3584
|
+
function retargetPgDumpInserts(dumpSql, schema, table, tempTable) {
|
|
3585
|
+
const insertPattern = new RegExp(
|
|
3586
|
+
`INSERT INTO (?:${schema}\\.${table}|"${schema}"\\."${table}")`,
|
|
3587
|
+
"g"
|
|
3588
|
+
);
|
|
3589
|
+
const tempDumpSql = dumpSql.replace(insertPattern, `INSERT INTO ${tempTable}`);
|
|
3590
|
+
if (/\bINSERT INTO\b/.test(dumpSql) && tempDumpSql === dumpSql) {
|
|
3591
|
+
throw new Error(`Could not rewrite pg_dump INSERT statements for ${schema}.${table}.`);
|
|
3592
|
+
}
|
|
3593
|
+
return tempDumpSql;
|
|
3594
|
+
}
|
|
3531
3595
|
function waitForOriginalDb(containerName2) {
|
|
3532
3596
|
const deadline = Date.now() + 3e4;
|
|
3533
3597
|
while (Date.now() < deadline) {
|
|
@@ -3540,6 +3604,81 @@ function waitForOriginalDb(containerName2) {
|
|
|
3540
3604
|
}
|
|
3541
3605
|
throw new Error(`Timed out waiting for ${containerName2} to accept local PostgreSQL connections.`);
|
|
3542
3606
|
}
|
|
3607
|
+
function waitForBranchProxy(originalDbContainer, dbContainer, network, branchUser, branchPassword) {
|
|
3608
|
+
runDocker(["start", originalDbContainer]);
|
|
3609
|
+
try {
|
|
3610
|
+
waitForOriginalDb(originalDbContainer);
|
|
3611
|
+
const dbContainerIpAddress = containerIpAddress(dbContainer, network);
|
|
3612
|
+
const deadline = Date.now() + 6e4;
|
|
3613
|
+
let lastError = "";
|
|
3614
|
+
while (Date.now() < deadline) {
|
|
3615
|
+
try {
|
|
3616
|
+
runDocker([
|
|
3617
|
+
"exec",
|
|
3618
|
+
"-e",
|
|
3619
|
+
`PGPASSWORD=${branchPassword}`,
|
|
3620
|
+
originalDbContainer,
|
|
3621
|
+
"psql",
|
|
3622
|
+
"-h",
|
|
3623
|
+
dbContainerIpAddress,
|
|
3624
|
+
"-p",
|
|
3625
|
+
"5432",
|
|
3626
|
+
"-U",
|
|
3627
|
+
branchUser,
|
|
3628
|
+
"-d",
|
|
3629
|
+
"postgres",
|
|
3630
|
+
"-v",
|
|
3631
|
+
"ON_ERROR_STOP=1",
|
|
3632
|
+
"-c",
|
|
3633
|
+
"SELECT 1"
|
|
3634
|
+
]);
|
|
3635
|
+
return;
|
|
3636
|
+
} catch (error) {
|
|
3637
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
3638
|
+
execFileSync("sleep", ["1"]);
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
throw new Error(`Timed out waiting for branch DB proxy to accept connections: ${lastError}`);
|
|
3642
|
+
} finally {
|
|
3643
|
+
runDocker(["stop", originalDbContainer]);
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
function waitForBranchProxyWithClientImage(clientImage, network, dbContainer, branchUser, branchPassword) {
|
|
3647
|
+
const deadline = Date.now() + 6e4;
|
|
3648
|
+
let lastError = "";
|
|
3649
|
+
while (Date.now() < deadline) {
|
|
3650
|
+
try {
|
|
3651
|
+
runDocker([
|
|
3652
|
+
"run",
|
|
3653
|
+
"--rm",
|
|
3654
|
+
"--network",
|
|
3655
|
+
network,
|
|
3656
|
+
"--entrypoint",
|
|
3657
|
+
"psql",
|
|
3658
|
+
"-e",
|
|
3659
|
+
`PGPASSWORD=${branchPassword}`,
|
|
3660
|
+
clientImage,
|
|
3661
|
+
"-h",
|
|
3662
|
+
dbContainer,
|
|
3663
|
+
"-p",
|
|
3664
|
+
"5432",
|
|
3665
|
+
"-U",
|
|
3666
|
+
branchUser,
|
|
3667
|
+
"-d",
|
|
3668
|
+
"postgres",
|
|
3669
|
+
"-v",
|
|
3670
|
+
"ON_ERROR_STOP=1",
|
|
3671
|
+
"-c",
|
|
3672
|
+
"SELECT 1"
|
|
3673
|
+
]);
|
|
3674
|
+
return;
|
|
3675
|
+
} catch (error) {
|
|
3676
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
3677
|
+
execFileSync("sleep", ["1"]);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
throw new Error(`Timed out waiting for final branch DB proxy to accept connections: ${lastError}`);
|
|
3681
|
+
}
|
|
3543
3682
|
function copyLocalStorageMigrationsToBranch(dumpSql, originalDbContainer, dbContainer, network, branchUser, branchPassword) {
|
|
3544
3683
|
runDocker(["start", originalDbContainer]);
|
|
3545
3684
|
try {
|
|
@@ -3564,7 +3703,39 @@ function copyLocalStorageMigrationsToBranch(dumpSql, originalDbContainer, dbCont
|
|
|
3564
3703
|
"-v",
|
|
3565
3704
|
"ON_ERROR_STOP=1"
|
|
3566
3705
|
],
|
|
3567
|
-
storageMigrationsImportSql(dumpSql)
|
|
3706
|
+
storageMigrationsImportSql(dumpSql),
|
|
3707
|
+
{ timeoutMs: MIGRATION_IMPORT_TIMEOUT_MS }
|
|
3708
|
+
);
|
|
3709
|
+
} finally {
|
|
3710
|
+
runDocker(["stop", originalDbContainer]);
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
function copyLocalRealtimeMigrationsToBranch(dumpSql, originalDbContainer, dbContainer, network, branchPassword) {
|
|
3714
|
+
runDocker(["start", originalDbContainer]);
|
|
3715
|
+
try {
|
|
3716
|
+
waitForOriginalDb(originalDbContainer);
|
|
3717
|
+
const dbContainerIpAddress = containerIpAddress(dbContainer, network);
|
|
3718
|
+
runDockerWithInput(
|
|
3719
|
+
[
|
|
3720
|
+
"exec",
|
|
3721
|
+
"-i",
|
|
3722
|
+
"-e",
|
|
3723
|
+
`PGPASSWORD=${branchPassword}`,
|
|
3724
|
+
originalDbContainer,
|
|
3725
|
+
"psql",
|
|
3726
|
+
"-h",
|
|
3727
|
+
dbContainerIpAddress,
|
|
3728
|
+
"-p",
|
|
3729
|
+
"5432",
|
|
3730
|
+
"-U",
|
|
3731
|
+
"supabase_admin",
|
|
3732
|
+
"-d",
|
|
3733
|
+
"postgres",
|
|
3734
|
+
"-v",
|
|
3735
|
+
"ON_ERROR_STOP=1"
|
|
3736
|
+
],
|
|
3737
|
+
realtimeMigrationsImportSql(dumpSql),
|
|
3738
|
+
{ timeoutMs: MIGRATION_IMPORT_TIMEOUT_MS }
|
|
3568
3739
|
);
|
|
3569
3740
|
} finally {
|
|
3570
3741
|
runDocker(["stop", originalDbContainer]);
|
|
@@ -3685,7 +3856,6 @@ function applySupabaseRealtimeCompatibility(projectId, originalDbContainer, dbCo
|
|
|
3685
3856
|
supabaseRealtimeCompatibilitySql(),
|
|
3686
3857
|
{ timeoutMs: 7e3 }
|
|
3687
3858
|
);
|
|
3688
|
-
runDocker(["restart", realtimeContainer]);
|
|
3689
3859
|
return;
|
|
3690
3860
|
} catch (error) {
|
|
3691
3861
|
lastError = error instanceof Error ? error.message : String(error);
|
|
@@ -3707,6 +3877,42 @@ function kongApiUrl(projectId) {
|
|
|
3707
3877
|
const port = first?.split(":").pop();
|
|
3708
3878
|
return port ? `http://127.0.0.1:${port}` : void 0;
|
|
3709
3879
|
}
|
|
3880
|
+
function requireLinkedServiceContainersRunning(projectId) {
|
|
3881
|
+
const stoppedServices = [];
|
|
3882
|
+
for (const servicePrefix of SERVICE_SUFFIXES) {
|
|
3883
|
+
const name = containerName(servicePrefix, projectId);
|
|
3884
|
+
if (!dockerExists(name)) {
|
|
3885
|
+
continue;
|
|
3886
|
+
}
|
|
3887
|
+
const state = runDocker([
|
|
3888
|
+
"inspect",
|
|
3889
|
+
"-f",
|
|
3890
|
+
"{{.State.Status}}",
|
|
3891
|
+
name
|
|
3892
|
+
]);
|
|
3893
|
+
if (state !== "running") {
|
|
3894
|
+
stoppedServices.push(`${name}=${state}`);
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
if (stoppedServices.length > 0) {
|
|
3898
|
+
throw new Error(`Linked Supabase service container is not running: ${stoppedServices.join(", ")}`);
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
async function closeHealthResponse(response) {
|
|
3902
|
+
try {
|
|
3903
|
+
await response.body?.cancel();
|
|
3904
|
+
} catch {
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
async function fetchHealthCheck(url) {
|
|
3908
|
+
const controller = new AbortController();
|
|
3909
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
3910
|
+
try {
|
|
3911
|
+
return await fetch(url, { signal: controller.signal });
|
|
3912
|
+
} finally {
|
|
3913
|
+
clearTimeout(timeout);
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3710
3916
|
async function waitForHealth(projectId) {
|
|
3711
3917
|
const apiUrl = kongApiUrl(projectId);
|
|
3712
3918
|
if (!apiUrl) {
|
|
@@ -3716,12 +3922,22 @@ async function waitForHealth(projectId) {
|
|
|
3716
3922
|
let lastError = "";
|
|
3717
3923
|
while (Date.now() < deadline) {
|
|
3718
3924
|
try {
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3925
|
+
requireLinkedServiceContainersRunning(projectId);
|
|
3926
|
+
let authResponse;
|
|
3927
|
+
let restResponse;
|
|
3928
|
+
try {
|
|
3929
|
+
authResponse = await fetchHealthCheck(`${apiUrl}/auth/v1/health`);
|
|
3930
|
+
restResponse = await fetchHealthCheck(`${apiUrl}/rest/v1/`);
|
|
3931
|
+
if (authResponse.ok && restResponse.ok) {
|
|
3932
|
+
return;
|
|
3933
|
+
}
|
|
3934
|
+
lastError = `auth=${authResponse.status}, rest=${restResponse.status}`;
|
|
3935
|
+
} finally {
|
|
3936
|
+
await Promise.all([
|
|
3937
|
+
authResponse ? closeHealthResponse(authResponse) : Promise.resolve(),
|
|
3938
|
+
restResponse ? closeHealthResponse(restResponse) : Promise.resolve()
|
|
3939
|
+
]);
|
|
3723
3940
|
}
|
|
3724
|
-
lastError = `auth=${authResponse.status}, rest=${restResponse.status}`;
|
|
3725
3941
|
} catch (error) {
|
|
3726
3942
|
lastError = error instanceof Error ? error.message : String(error);
|
|
3727
3943
|
}
|
|
@@ -3788,11 +4004,16 @@ async function linkSupabaseAction(branchName, options = {}) {
|
|
|
3788
4004
|
}
|
|
3789
4005
|
console.log(`Linking Supabase project ${projectId} to branch ${branch.name}...`);
|
|
3790
4006
|
const storageMigrationsDump = dumpLocalStorageMigrations(dbContainer);
|
|
4007
|
+
const realtimeContainer = containerName("realtime", projectId);
|
|
4008
|
+
const realtimeMigrationsDump = dockerExists(realtimeContainer) ? dumpLocalRealtimeMigrations(dbContainer) : "";
|
|
3791
4009
|
const originalDbContainerState = inspectContainer(dbContainer);
|
|
3792
4010
|
const dbPortBindings = portPublishArgs(originalDbContainerState);
|
|
3793
4011
|
runDocker(["stop", dbContainer]);
|
|
3794
4012
|
runDocker(["rename", dbContainer, originalDbContainer]);
|
|
3795
4013
|
startProxyContainer(projectId, network, dbContainer, parsedBranchUrl, []);
|
|
4014
|
+
console.log("Checking branch database connection...");
|
|
4015
|
+
waitForBranchProxy(originalDbContainer, dbContainer, network, branchUser, branchPassword);
|
|
4016
|
+
console.log("Copying local Supabase Storage migration state...");
|
|
3796
4017
|
copyLocalStorageMigrationsToBranch(
|
|
3797
4018
|
storageMigrationsDump,
|
|
3798
4019
|
originalDbContainer,
|
|
@@ -3801,19 +4022,17 @@ async function linkSupabaseAction(branchName, options = {}) {
|
|
|
3801
4022
|
branchUser,
|
|
3802
4023
|
branchPassword
|
|
3803
4024
|
);
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
4025
|
+
if (realtimeMigrationsDump) {
|
|
4026
|
+
console.log("Copying local Supabase Realtime migration state...");
|
|
4027
|
+
copyLocalRealtimeMigrationsToBranch(
|
|
4028
|
+
realtimeMigrationsDump,
|
|
4029
|
+
originalDbContainer,
|
|
3809
4030
|
dbContainer,
|
|
3810
|
-
|
|
4031
|
+
network,
|
|
3811
4032
|
branchPassword
|
|
3812
4033
|
);
|
|
3813
|
-
if (service) {
|
|
3814
|
-
createdServices.push(service);
|
|
3815
|
-
}
|
|
3816
4034
|
}
|
|
4035
|
+
console.log("Applying Supabase Realtime compatibility...");
|
|
3817
4036
|
applySupabaseRealtimeCompatibility(
|
|
3818
4037
|
projectId,
|
|
3819
4038
|
originalDbContainer,
|
|
@@ -3823,6 +4042,27 @@ async function linkSupabaseAction(branchName, options = {}) {
|
|
|
3823
4042
|
);
|
|
3824
4043
|
runDocker(["rm", "-f", dbContainer]);
|
|
3825
4044
|
startProxyContainer(projectId, network, dbContainer, parsedBranchUrl, dbPortBindings);
|
|
4045
|
+
console.log("Starting linked Supabase database proxy...");
|
|
4046
|
+
waitForBranchProxyWithClientImage(
|
|
4047
|
+
originalDbContainerState.Config.Image,
|
|
4048
|
+
network,
|
|
4049
|
+
dbContainer,
|
|
4050
|
+
branchUser,
|
|
4051
|
+
branchPassword
|
|
4052
|
+
);
|
|
4053
|
+
for (const servicePrefix of SERVICE_SUFFIXES) {
|
|
4054
|
+
const service = recreateServiceForBranch(
|
|
4055
|
+
servicePrefix,
|
|
4056
|
+
projectId,
|
|
4057
|
+
network,
|
|
4058
|
+
dbContainer,
|
|
4059
|
+
branchUser,
|
|
4060
|
+
branchPassword
|
|
4061
|
+
);
|
|
4062
|
+
if (service) {
|
|
4063
|
+
createdServices.push(service);
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
3826
4066
|
const kong = containerName("kong", projectId);
|
|
3827
4067
|
if (dockerExists(kong)) {
|
|
3828
4068
|
runDocker(["restart", kong]);
|