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.
Files changed (2) hide show
  1. package/dist/index.js +260 -20
  2. 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.replaceAll("INSERT INTO storage.migrations", `INSERT INTO ${tempTable}`);
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
- const authResponse = await fetch(`${apiUrl}/auth/v1/health`);
3720
- const restResponse = await fetch(`${apiUrl}/rest/v1/`);
3721
- if (authResponse.ok && restResponse.ok) {
3722
- return;
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
- for (const servicePrefix of SERVICE_SUFFIXES) {
3805
- const service = recreateServiceForBranch(
3806
- servicePrefix,
3807
- projectId,
3808
- network,
4025
+ if (realtimeMigrationsDump) {
4026
+ console.log("Copying local Supabase Realtime migration state...");
4027
+ copyLocalRealtimeMigrationsToBranch(
4028
+ realtimeMigrationsDump,
4029
+ originalDbContainer,
3809
4030
  dbContainer,
3810
- branchUser,
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]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {