ardent-cli 0.0.38 → 0.0.40
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 +390 -64
- 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
|
|
@@ -796,6 +804,126 @@ function printDegradedWarnings(warnings) {
|
|
|
796
804
|
console.log(" Review this connector with: ardent connector list");
|
|
797
805
|
}
|
|
798
806
|
|
|
807
|
+
// src/lib/operation_stage_display.ts
|
|
808
|
+
var STAGE_DISPLAY = {
|
|
809
|
+
// engine_setup_worker / postgres_engine_setup
|
|
810
|
+
"dispatched": "Starting",
|
|
811
|
+
"preparing": "Preparing",
|
|
812
|
+
"creating-neon-project": "Provisioning the branch target",
|
|
813
|
+
"preparing-target-databases": "Preparing target databases",
|
|
814
|
+
"deploying-pgstream": "Starting replication",
|
|
815
|
+
"applying-rls": "Applying RLS policies",
|
|
816
|
+
"storing-credentials": "Storing connection credentials",
|
|
817
|
+
"validating": "Validating the branch target",
|
|
818
|
+
// reset_worker / reset_connector
|
|
819
|
+
"resetting": "Resetting",
|
|
820
|
+
"deleting-pgstream": "Stopping replication",
|
|
821
|
+
"rediscovering-source": "Re-checking the source database",
|
|
822
|
+
"resetting-neon-main": "Resetting the branch target",
|
|
823
|
+
"creating-target-schemas": "Recreating target schemas",
|
|
824
|
+
"redeploying-pgstream": "Restarting replication",
|
|
825
|
+
// environment deploy_worker
|
|
826
|
+
"loading_config": "Loading environment configuration",
|
|
827
|
+
"deploying_infrastructure": "Provisioning environment infrastructure",
|
|
828
|
+
"recording_success": "Finalizing environment",
|
|
829
|
+
"cleaning_failed_deploy": "Cleaning up failed environment provisioning",
|
|
830
|
+
// environment destroy_worker
|
|
831
|
+
"deleting_private_links": "Removing private network links",
|
|
832
|
+
"destroying_infrastructure": "Tearing down environment infrastructure",
|
|
833
|
+
"recording_destroy_success": "Finalizing environment teardown",
|
|
834
|
+
// discovery_worker / run_discovery_crawl (ARD-1098)
|
|
835
|
+
"connecting": "Connecting to source",
|
|
836
|
+
"enumerating_databases": "Listing databases",
|
|
837
|
+
"scanning_database": "Scanning schema",
|
|
838
|
+
"writing_schema": "Saving discovered schema",
|
|
839
|
+
"merging_results": "Combining results"
|
|
840
|
+
};
|
|
841
|
+
function humanizeRawStage(_raw) {
|
|
842
|
+
return "Working";
|
|
843
|
+
}
|
|
844
|
+
function operationStageDisplay(stage) {
|
|
845
|
+
if (!stage) return "Working";
|
|
846
|
+
const colonIndex = stage.indexOf(":");
|
|
847
|
+
const base = colonIndex === -1 ? stage : stage.slice(0, colonIndex);
|
|
848
|
+
const suffix = colonIndex === -1 ? "" : stage.slice(colonIndex + 1);
|
|
849
|
+
const label = STAGE_DISPLAY[base] ?? humanizeRawStage(base);
|
|
850
|
+
return suffix ? `${label} (for ${suffix})` : label;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// src/lib/discover.ts
|
|
854
|
+
var DiscoveryTimeoutError = class extends Error {
|
|
855
|
+
constructor(message) {
|
|
856
|
+
super(message);
|
|
857
|
+
this.name = "DiscoveryTimeoutError";
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
var DiscoveryOperationFailedError = class extends Error {
|
|
861
|
+
operationError;
|
|
862
|
+
constructor(operationError) {
|
|
863
|
+
super(
|
|
864
|
+
`Discovery failed: ${operationError ?? "unknown error"}. Inspect the connector with \`ardent connector list\`, fix the underlying issue, then re-run \`ardent connector create\` or \`ardent connector rediscover\`.`
|
|
865
|
+
);
|
|
866
|
+
this.name = "DiscoveryOperationFailedError";
|
|
867
|
+
this.operationError = operationError;
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
async function runDiscoveryWithPolling(connectorId, options) {
|
|
871
|
+
const maxWaitMs = options?.maxWaitMs ?? 10 * 60 * 1e3;
|
|
872
|
+
const pollIntervalMs = options?.pollIntervalMs ?? 3 * 1e3;
|
|
873
|
+
let dispatch;
|
|
874
|
+
try {
|
|
875
|
+
dispatch = await api.post(
|
|
876
|
+
`/v1/connectors/${connectorId}/discover`,
|
|
877
|
+
{}
|
|
878
|
+
);
|
|
879
|
+
} catch (err) {
|
|
880
|
+
if (isGatewayTimeoutError(err)) {
|
|
881
|
+
throw new Error(
|
|
882
|
+
"Backend dispatch timed out. The /discover endpoint is now async and should respond in under a few seconds; this may indicate a backend incident. Try again, or check `ardent connector list` to see if a discovery operation was created anyway."
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
throw err;
|
|
886
|
+
}
|
|
887
|
+
if (options?.onPrerequisites) {
|
|
888
|
+
options.onPrerequisites(dispatch.prerequisites, dispatch.source_metadata);
|
|
889
|
+
}
|
|
890
|
+
const operationId = dispatch.operation_id;
|
|
891
|
+
const startedAt = Date.now();
|
|
892
|
+
let lastStage = null;
|
|
893
|
+
while (Date.now() - startedAt < maxWaitMs) {
|
|
894
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
895
|
+
let op;
|
|
896
|
+
try {
|
|
897
|
+
op = await api.get(`/v1/operations/${operationId}`);
|
|
898
|
+
} catch (pollErr) {
|
|
899
|
+
if (isGatewayTimeoutError(pollErr)) continue;
|
|
900
|
+
throw pollErr;
|
|
901
|
+
}
|
|
902
|
+
if (op.stage && op.stage !== lastStage) {
|
|
903
|
+
const progressLabel = op.progress != null ? ` (${op.progress}%)` : "";
|
|
904
|
+
console.log(` ${operationStageDisplay(op.stage)}${progressLabel}`);
|
|
905
|
+
lastStage = op.stage;
|
|
906
|
+
}
|
|
907
|
+
if (op.status === "completed") {
|
|
908
|
+
const result = op.result ?? {};
|
|
909
|
+
const schemaVersion = typeof result.schema_version === "number" ? result.schema_version : 0;
|
|
910
|
+
const databasesCount = typeof result.databases_count === "number" ? result.databases_count : 0;
|
|
911
|
+
return {
|
|
912
|
+
prerequisites: dispatch.prerequisites,
|
|
913
|
+
source_metadata: dispatch.source_metadata,
|
|
914
|
+
schema_version: schemaVersion,
|
|
915
|
+
databases_count: databasesCount
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
if (op.status === "failed") {
|
|
919
|
+
throw new DiscoveryOperationFailedError(op.error);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
throw new DiscoveryTimeoutError(
|
|
923
|
+
`Discovery did not complete within ${Math.round(maxWaitMs / 6e4)} minutes. The catalog walk may still be running server-side \u2014 do NOT delete the connector. Check status with: ardent connector list. If you contact Ardent support, reference operation id ${operationId}.`
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
799
927
|
// src/lib/connector_status_display.ts
|
|
800
928
|
var ENGINE_STATUS_DISPLAY = {
|
|
801
929
|
healthy: "ready",
|
|
@@ -882,46 +1010,6 @@ function assertEngineSetupCompleted(operation, connectorName) {
|
|
|
882
1010
|
);
|
|
883
1011
|
}
|
|
884
1012
|
|
|
885
|
-
// src/lib/operation_stage_display.ts
|
|
886
|
-
var STAGE_DISPLAY = {
|
|
887
|
-
// engine_setup_worker / postgres_engine_setup
|
|
888
|
-
"dispatched": "Starting",
|
|
889
|
-
"preparing": "Preparing",
|
|
890
|
-
"creating-neon-project": "Provisioning the branch target",
|
|
891
|
-
"preparing-target-databases": "Preparing target databases",
|
|
892
|
-
"deploying-pgstream": "Starting replication",
|
|
893
|
-
"applying-rls": "Applying RLS policies",
|
|
894
|
-
"storing-credentials": "Storing connection credentials",
|
|
895
|
-
"validating": "Validating the branch target",
|
|
896
|
-
// reset_worker / reset_connector
|
|
897
|
-
"resetting": "Resetting",
|
|
898
|
-
"deleting-pgstream": "Stopping replication",
|
|
899
|
-
"rediscovering-source": "Re-checking the source database",
|
|
900
|
-
"resetting-neon-main": "Resetting the branch target",
|
|
901
|
-
"creating-target-schemas": "Recreating target schemas",
|
|
902
|
-
"redeploying-pgstream": "Restarting replication",
|
|
903
|
-
// environment deploy_worker
|
|
904
|
-
"loading_config": "Loading environment configuration",
|
|
905
|
-
"deploying_infrastructure": "Provisioning environment infrastructure",
|
|
906
|
-
"recording_success": "Finalizing environment",
|
|
907
|
-
"cleaning_failed_deploy": "Cleaning up failed environment provisioning",
|
|
908
|
-
// environment destroy_worker
|
|
909
|
-
"deleting_private_links": "Removing private network links",
|
|
910
|
-
"destroying_infrastructure": "Tearing down environment infrastructure",
|
|
911
|
-
"recording_destroy_success": "Finalizing environment teardown"
|
|
912
|
-
};
|
|
913
|
-
function humanizeRawStage(_raw) {
|
|
914
|
-
return "Working";
|
|
915
|
-
}
|
|
916
|
-
function operationStageDisplay(stage) {
|
|
917
|
-
if (!stage) return "Working";
|
|
918
|
-
const colonIndex = stage.indexOf(":");
|
|
919
|
-
const base = colonIndex === -1 ? stage : stage.slice(0, colonIndex);
|
|
920
|
-
const suffix = colonIndex === -1 ? "" : stage.slice(colonIndex + 1);
|
|
921
|
-
const label = STAGE_DISPLAY[base] ?? humanizeRawStage(base);
|
|
922
|
-
return suffix ? `${label} (for ${suffix})` : label;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
1013
|
// src/lib/engine_setup.ts
|
|
926
1014
|
var EngineSetupTimeoutError = class extends Error {
|
|
927
1015
|
constructor(message) {
|
|
@@ -1697,11 +1785,23 @@ async function createAction2(type, url, options) {
|
|
|
1697
1785
|
}
|
|
1698
1786
|
} else {
|
|
1699
1787
|
console.log("Discovering schema...");
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
{
|
|
1703
|
-
|
|
1704
|
-
|
|
1788
|
+
let discoverPollResult;
|
|
1789
|
+
try {
|
|
1790
|
+
discoverPollResult = await runDiscoveryWithPolling(connectorId, {
|
|
1791
|
+
onPrerequisites: (prereqs) => {
|
|
1792
|
+
if (prereqs.pass) {
|
|
1793
|
+
console.log("Connected \u2713 \u2014 scanning your schema\u2026");
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
});
|
|
1797
|
+
} catch (discoverErr) {
|
|
1798
|
+
if (discoverErr instanceof DiscoveryTimeoutError || discoverErr instanceof DiscoveryOperationFailedError) {
|
|
1799
|
+
console.error(`\u2717 ${discoverErr.message}`);
|
|
1800
|
+
process.exit(1);
|
|
1801
|
+
}
|
|
1802
|
+
throw discoverErr;
|
|
1803
|
+
}
|
|
1804
|
+
const discoveryWarnings = discoverPollResult.source_metadata?.warnings ?? [];
|
|
1705
1805
|
if (discoveryWarnings.length > 0) {
|
|
1706
1806
|
const yellow = "\x1B[33m";
|
|
1707
1807
|
const reset2 = "\x1B[0m";
|
|
@@ -2944,6 +3044,7 @@ var ORIGINAL_SUFFIX = "-ardent-original";
|
|
|
2944
3044
|
var SERVICE_SUFFIXES = ["rest", "auth", "storage", "realtime", "pg_meta"];
|
|
2945
3045
|
var DOCKER_HOST_GATEWAY = "host.docker.internal";
|
|
2946
3046
|
var REALTIME_LIST_CHANGES_MIGRATION_VERSION = "20230328144023";
|
|
3047
|
+
var MIGRATION_IMPORT_TIMEOUT_MS = 15e3;
|
|
2947
3048
|
var SENSITIVE_DOCKER_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
2948
3049
|
"DATABASE_URL",
|
|
2949
3050
|
"DB_PASSWORD",
|
|
@@ -3521,19 +3622,68 @@ function dumpLocalStorageMigrations(dbContainer) {
|
|
|
3521
3622
|
"--no-privileges"
|
|
3522
3623
|
]);
|
|
3523
3624
|
}
|
|
3625
|
+
function dumpLocalRealtimeMigrations(dbContainer) {
|
|
3626
|
+
return runDocker([
|
|
3627
|
+
"exec",
|
|
3628
|
+
dbContainer,
|
|
3629
|
+
"pg_dump",
|
|
3630
|
+
"-U",
|
|
3631
|
+
"postgres",
|
|
3632
|
+
"-d",
|
|
3633
|
+
"postgres",
|
|
3634
|
+
"--data-only",
|
|
3635
|
+
"--table=realtime.schema_migrations",
|
|
3636
|
+
"--column-inserts",
|
|
3637
|
+
"--no-owner",
|
|
3638
|
+
"--no-privileges"
|
|
3639
|
+
]);
|
|
3640
|
+
}
|
|
3524
3641
|
function storageMigrationsImportSql(dumpSql) {
|
|
3525
3642
|
const tempTable = "pg_temp.ardent_storage_migrations_import";
|
|
3526
|
-
const tempDumpSql = dumpSql
|
|
3643
|
+
const tempDumpSql = retargetPgDumpInserts(dumpSql, "storage", "migrations", tempTable);
|
|
3527
3644
|
return `
|
|
3645
|
+
SET lock_timeout = '2s';
|
|
3646
|
+
SET statement_timeout = '10s';
|
|
3528
3647
|
BEGIN;
|
|
3529
3648
|
CREATE TEMP TABLE ardent_storage_migrations_import (LIKE storage.migrations INCLUDING DEFAULTS);
|
|
3530
3649
|
${tempDumpSql}
|
|
3531
3650
|
INSERT INTO storage.migrations
|
|
3532
3651
|
SELECT * FROM ${tempTable}
|
|
3533
3652
|
ON CONFLICT DO NOTHING;
|
|
3653
|
+
COMMIT;
|
|
3654
|
+
`;
|
|
3655
|
+
}
|
|
3656
|
+
function realtimeMigrationsImportSql(dumpSql) {
|
|
3657
|
+
const tempTable = "pg_temp.ardent_realtime_migrations_import";
|
|
3658
|
+
const tempDumpSql = retargetPgDumpInserts(dumpSql, "realtime", "schema_migrations", tempTable);
|
|
3659
|
+
return `
|
|
3660
|
+
SET lock_timeout = '2s';
|
|
3661
|
+
SET statement_timeout = '10s';
|
|
3662
|
+
BEGIN;
|
|
3663
|
+
CREATE SCHEMA IF NOT EXISTS realtime;
|
|
3664
|
+
CREATE TABLE IF NOT EXISTS realtime.schema_migrations (
|
|
3665
|
+
version bigint PRIMARY KEY,
|
|
3666
|
+
inserted_at timestamp(0) without time zone
|
|
3667
|
+
);
|
|
3668
|
+
CREATE TEMP TABLE ardent_realtime_migrations_import (LIKE realtime.schema_migrations INCLUDING DEFAULTS);
|
|
3669
|
+
${tempDumpSql}
|
|
3670
|
+
INSERT INTO realtime.schema_migrations
|
|
3671
|
+
SELECT * FROM ${tempTable}
|
|
3672
|
+
ON CONFLICT DO NOTHING;
|
|
3534
3673
|
COMMIT;
|
|
3535
3674
|
`;
|
|
3536
3675
|
}
|
|
3676
|
+
function retargetPgDumpInserts(dumpSql, schema, table, tempTable) {
|
|
3677
|
+
const insertPattern = new RegExp(
|
|
3678
|
+
`INSERT INTO (?:${schema}\\.${table}|"${schema}"\\."${table}")`,
|
|
3679
|
+
"g"
|
|
3680
|
+
);
|
|
3681
|
+
const tempDumpSql = dumpSql.replace(insertPattern, `INSERT INTO ${tempTable}`);
|
|
3682
|
+
if (/\bINSERT INTO\b/.test(dumpSql) && tempDumpSql === dumpSql) {
|
|
3683
|
+
throw new Error(`Could not rewrite pg_dump INSERT statements for ${schema}.${table}.`);
|
|
3684
|
+
}
|
|
3685
|
+
return tempDumpSql;
|
|
3686
|
+
}
|
|
3537
3687
|
function waitForOriginalDb(containerName2) {
|
|
3538
3688
|
const deadline = Date.now() + 3e4;
|
|
3539
3689
|
while (Date.now() < deadline) {
|
|
@@ -3546,6 +3696,81 @@ function waitForOriginalDb(containerName2) {
|
|
|
3546
3696
|
}
|
|
3547
3697
|
throw new Error(`Timed out waiting for ${containerName2} to accept local PostgreSQL connections.`);
|
|
3548
3698
|
}
|
|
3699
|
+
function waitForBranchProxy(originalDbContainer, dbContainer, network, branchUser, branchPassword) {
|
|
3700
|
+
runDocker(["start", originalDbContainer]);
|
|
3701
|
+
try {
|
|
3702
|
+
waitForOriginalDb(originalDbContainer);
|
|
3703
|
+
const dbContainerIpAddress = containerIpAddress(dbContainer, network);
|
|
3704
|
+
const deadline = Date.now() + 6e4;
|
|
3705
|
+
let lastError = "";
|
|
3706
|
+
while (Date.now() < deadline) {
|
|
3707
|
+
try {
|
|
3708
|
+
runDocker([
|
|
3709
|
+
"exec",
|
|
3710
|
+
"-e",
|
|
3711
|
+
`PGPASSWORD=${branchPassword}`,
|
|
3712
|
+
originalDbContainer,
|
|
3713
|
+
"psql",
|
|
3714
|
+
"-h",
|
|
3715
|
+
dbContainerIpAddress,
|
|
3716
|
+
"-p",
|
|
3717
|
+
"5432",
|
|
3718
|
+
"-U",
|
|
3719
|
+
branchUser,
|
|
3720
|
+
"-d",
|
|
3721
|
+
"postgres",
|
|
3722
|
+
"-v",
|
|
3723
|
+
"ON_ERROR_STOP=1",
|
|
3724
|
+
"-c",
|
|
3725
|
+
"SELECT 1"
|
|
3726
|
+
]);
|
|
3727
|
+
return;
|
|
3728
|
+
} catch (error) {
|
|
3729
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
3730
|
+
execFileSync("sleep", ["1"]);
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
throw new Error(`Timed out waiting for branch DB proxy to accept connections: ${lastError}`);
|
|
3734
|
+
} finally {
|
|
3735
|
+
runDocker(["stop", originalDbContainer]);
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
function waitForBranchProxyWithClientImage(clientImage, network, dbContainer, branchUser, branchPassword) {
|
|
3739
|
+
const deadline = Date.now() + 6e4;
|
|
3740
|
+
let lastError = "";
|
|
3741
|
+
while (Date.now() < deadline) {
|
|
3742
|
+
try {
|
|
3743
|
+
runDocker([
|
|
3744
|
+
"run",
|
|
3745
|
+
"--rm",
|
|
3746
|
+
"--network",
|
|
3747
|
+
network,
|
|
3748
|
+
"--entrypoint",
|
|
3749
|
+
"psql",
|
|
3750
|
+
"-e",
|
|
3751
|
+
`PGPASSWORD=${branchPassword}`,
|
|
3752
|
+
clientImage,
|
|
3753
|
+
"-h",
|
|
3754
|
+
dbContainer,
|
|
3755
|
+
"-p",
|
|
3756
|
+
"5432",
|
|
3757
|
+
"-U",
|
|
3758
|
+
branchUser,
|
|
3759
|
+
"-d",
|
|
3760
|
+
"postgres",
|
|
3761
|
+
"-v",
|
|
3762
|
+
"ON_ERROR_STOP=1",
|
|
3763
|
+
"-c",
|
|
3764
|
+
"SELECT 1"
|
|
3765
|
+
]);
|
|
3766
|
+
return;
|
|
3767
|
+
} catch (error) {
|
|
3768
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
3769
|
+
execFileSync("sleep", ["1"]);
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
throw new Error(`Timed out waiting for final branch DB proxy to accept connections: ${lastError}`);
|
|
3773
|
+
}
|
|
3549
3774
|
function copyLocalStorageMigrationsToBranch(dumpSql, originalDbContainer, dbContainer, network, branchUser, branchPassword) {
|
|
3550
3775
|
runDocker(["start", originalDbContainer]);
|
|
3551
3776
|
try {
|
|
@@ -3570,7 +3795,39 @@ function copyLocalStorageMigrationsToBranch(dumpSql, originalDbContainer, dbCont
|
|
|
3570
3795
|
"-v",
|
|
3571
3796
|
"ON_ERROR_STOP=1"
|
|
3572
3797
|
],
|
|
3573
|
-
storageMigrationsImportSql(dumpSql)
|
|
3798
|
+
storageMigrationsImportSql(dumpSql),
|
|
3799
|
+
{ timeoutMs: MIGRATION_IMPORT_TIMEOUT_MS }
|
|
3800
|
+
);
|
|
3801
|
+
} finally {
|
|
3802
|
+
runDocker(["stop", originalDbContainer]);
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
function copyLocalRealtimeMigrationsToBranch(dumpSql, originalDbContainer, dbContainer, network, branchPassword) {
|
|
3806
|
+
runDocker(["start", originalDbContainer]);
|
|
3807
|
+
try {
|
|
3808
|
+
waitForOriginalDb(originalDbContainer);
|
|
3809
|
+
const dbContainerIpAddress = containerIpAddress(dbContainer, network);
|
|
3810
|
+
runDockerWithInput(
|
|
3811
|
+
[
|
|
3812
|
+
"exec",
|
|
3813
|
+
"-i",
|
|
3814
|
+
"-e",
|
|
3815
|
+
`PGPASSWORD=${branchPassword}`,
|
|
3816
|
+
originalDbContainer,
|
|
3817
|
+
"psql",
|
|
3818
|
+
"-h",
|
|
3819
|
+
dbContainerIpAddress,
|
|
3820
|
+
"-p",
|
|
3821
|
+
"5432",
|
|
3822
|
+
"-U",
|
|
3823
|
+
"supabase_admin",
|
|
3824
|
+
"-d",
|
|
3825
|
+
"postgres",
|
|
3826
|
+
"-v",
|
|
3827
|
+
"ON_ERROR_STOP=1"
|
|
3828
|
+
],
|
|
3829
|
+
realtimeMigrationsImportSql(dumpSql),
|
|
3830
|
+
{ timeoutMs: MIGRATION_IMPORT_TIMEOUT_MS }
|
|
3574
3831
|
);
|
|
3575
3832
|
} finally {
|
|
3576
3833
|
runDocker(["stop", originalDbContainer]);
|
|
@@ -3691,7 +3948,6 @@ function applySupabaseRealtimeCompatibility(projectId, originalDbContainer, dbCo
|
|
|
3691
3948
|
supabaseRealtimeCompatibilitySql(),
|
|
3692
3949
|
{ timeoutMs: 7e3 }
|
|
3693
3950
|
);
|
|
3694
|
-
runDocker(["restart", realtimeContainer]);
|
|
3695
3951
|
return;
|
|
3696
3952
|
} catch (error) {
|
|
3697
3953
|
lastError = error instanceof Error ? error.message : String(error);
|
|
@@ -3713,6 +3969,42 @@ function kongApiUrl(projectId) {
|
|
|
3713
3969
|
const port = first?.split(":").pop();
|
|
3714
3970
|
return port ? `http://127.0.0.1:${port}` : void 0;
|
|
3715
3971
|
}
|
|
3972
|
+
function requireLinkedServiceContainersRunning(projectId) {
|
|
3973
|
+
const stoppedServices = [];
|
|
3974
|
+
for (const servicePrefix of SERVICE_SUFFIXES) {
|
|
3975
|
+
const name = containerName(servicePrefix, projectId);
|
|
3976
|
+
if (!dockerExists(name)) {
|
|
3977
|
+
continue;
|
|
3978
|
+
}
|
|
3979
|
+
const state = runDocker([
|
|
3980
|
+
"inspect",
|
|
3981
|
+
"-f",
|
|
3982
|
+
"{{.State.Status}}",
|
|
3983
|
+
name
|
|
3984
|
+
]);
|
|
3985
|
+
if (state !== "running") {
|
|
3986
|
+
stoppedServices.push(`${name}=${state}`);
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
if (stoppedServices.length > 0) {
|
|
3990
|
+
throw new Error(`Linked Supabase service container is not running: ${stoppedServices.join(", ")}`);
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
async function closeHealthResponse(response) {
|
|
3994
|
+
try {
|
|
3995
|
+
await response.body?.cancel();
|
|
3996
|
+
} catch {
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
async function fetchHealthCheck(url) {
|
|
4000
|
+
const controller = new AbortController();
|
|
4001
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
4002
|
+
try {
|
|
4003
|
+
return await fetch(url, { signal: controller.signal });
|
|
4004
|
+
} finally {
|
|
4005
|
+
clearTimeout(timeout);
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
3716
4008
|
async function waitForHealth(projectId) {
|
|
3717
4009
|
const apiUrl = kongApiUrl(projectId);
|
|
3718
4010
|
if (!apiUrl) {
|
|
@@ -3722,12 +4014,22 @@ async function waitForHealth(projectId) {
|
|
|
3722
4014
|
let lastError = "";
|
|
3723
4015
|
while (Date.now() < deadline) {
|
|
3724
4016
|
try {
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
4017
|
+
requireLinkedServiceContainersRunning(projectId);
|
|
4018
|
+
let authResponse;
|
|
4019
|
+
let restResponse;
|
|
4020
|
+
try {
|
|
4021
|
+
authResponse = await fetchHealthCheck(`${apiUrl}/auth/v1/health`);
|
|
4022
|
+
restResponse = await fetchHealthCheck(`${apiUrl}/rest/v1/`);
|
|
4023
|
+
if (authResponse.ok && restResponse.ok) {
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
4026
|
+
lastError = `auth=${authResponse.status}, rest=${restResponse.status}`;
|
|
4027
|
+
} finally {
|
|
4028
|
+
await Promise.all([
|
|
4029
|
+
authResponse ? closeHealthResponse(authResponse) : Promise.resolve(),
|
|
4030
|
+
restResponse ? closeHealthResponse(restResponse) : Promise.resolve()
|
|
4031
|
+
]);
|
|
3729
4032
|
}
|
|
3730
|
-
lastError = `auth=${authResponse.status}, rest=${restResponse.status}`;
|
|
3731
4033
|
} catch (error) {
|
|
3732
4034
|
lastError = error instanceof Error ? error.message : String(error);
|
|
3733
4035
|
}
|
|
@@ -3794,11 +4096,16 @@ async function linkSupabaseAction(branchName, options = {}) {
|
|
|
3794
4096
|
}
|
|
3795
4097
|
console.log(`Linking Supabase project ${projectId} to branch ${branch.name}...`);
|
|
3796
4098
|
const storageMigrationsDump = dumpLocalStorageMigrations(dbContainer);
|
|
4099
|
+
const realtimeContainer = containerName("realtime", projectId);
|
|
4100
|
+
const realtimeMigrationsDump = dockerExists(realtimeContainer) ? dumpLocalRealtimeMigrations(dbContainer) : "";
|
|
3797
4101
|
const originalDbContainerState = inspectContainer(dbContainer);
|
|
3798
4102
|
const dbPortBindings = portPublishArgs(originalDbContainerState);
|
|
3799
4103
|
runDocker(["stop", dbContainer]);
|
|
3800
4104
|
runDocker(["rename", dbContainer, originalDbContainer]);
|
|
3801
4105
|
startProxyContainer(projectId, network, dbContainer, parsedBranchUrl, []);
|
|
4106
|
+
console.log("Checking branch database connection...");
|
|
4107
|
+
waitForBranchProxy(originalDbContainer, dbContainer, network, branchUser, branchPassword);
|
|
4108
|
+
console.log("Copying local Supabase Storage migration state...");
|
|
3802
4109
|
copyLocalStorageMigrationsToBranch(
|
|
3803
4110
|
storageMigrationsDump,
|
|
3804
4111
|
originalDbContainer,
|
|
@@ -3807,19 +4114,17 @@ async function linkSupabaseAction(branchName, options = {}) {
|
|
|
3807
4114
|
branchUser,
|
|
3808
4115
|
branchPassword
|
|
3809
4116
|
);
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
4117
|
+
if (realtimeMigrationsDump) {
|
|
4118
|
+
console.log("Copying local Supabase Realtime migration state...");
|
|
4119
|
+
copyLocalRealtimeMigrationsToBranch(
|
|
4120
|
+
realtimeMigrationsDump,
|
|
4121
|
+
originalDbContainer,
|
|
3815
4122
|
dbContainer,
|
|
3816
|
-
|
|
4123
|
+
network,
|
|
3817
4124
|
branchPassword
|
|
3818
4125
|
);
|
|
3819
|
-
if (service) {
|
|
3820
|
-
createdServices.push(service);
|
|
3821
|
-
}
|
|
3822
4126
|
}
|
|
4127
|
+
console.log("Applying Supabase Realtime compatibility...");
|
|
3823
4128
|
applySupabaseRealtimeCompatibility(
|
|
3824
4129
|
projectId,
|
|
3825
4130
|
originalDbContainer,
|
|
@@ -3829,6 +4134,27 @@ async function linkSupabaseAction(branchName, options = {}) {
|
|
|
3829
4134
|
);
|
|
3830
4135
|
runDocker(["rm", "-f", dbContainer]);
|
|
3831
4136
|
startProxyContainer(projectId, network, dbContainer, parsedBranchUrl, dbPortBindings);
|
|
4137
|
+
console.log("Starting linked Supabase database proxy...");
|
|
4138
|
+
waitForBranchProxyWithClientImage(
|
|
4139
|
+
originalDbContainerState.Config.Image,
|
|
4140
|
+
network,
|
|
4141
|
+
dbContainer,
|
|
4142
|
+
branchUser,
|
|
4143
|
+
branchPassword
|
|
4144
|
+
);
|
|
4145
|
+
for (const servicePrefix of SERVICE_SUFFIXES) {
|
|
4146
|
+
const service = recreateServiceForBranch(
|
|
4147
|
+
servicePrefix,
|
|
4148
|
+
projectId,
|
|
4149
|
+
network,
|
|
4150
|
+
dbContainer,
|
|
4151
|
+
branchUser,
|
|
4152
|
+
branchPassword
|
|
4153
|
+
);
|
|
4154
|
+
if (service) {
|
|
4155
|
+
createdServices.push(service);
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
3832
4158
|
const kong = containerName("kong", projectId);
|
|
3833
4159
|
if (dockerExists(kong)) {
|
|
3834
4160
|
runDocker(["restart", kong]);
|