ardent-cli 0.0.38 → 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 +253 -19
  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
@@ -2944,6 +2952,7 @@ var ORIGINAL_SUFFIX = "-ardent-original";
2944
2952
  var SERVICE_SUFFIXES = ["rest", "auth", "storage", "realtime", "pg_meta"];
2945
2953
  var DOCKER_HOST_GATEWAY = "host.docker.internal";
2946
2954
  var REALTIME_LIST_CHANGES_MIGRATION_VERSION = "20230328144023";
2955
+ var MIGRATION_IMPORT_TIMEOUT_MS = 15e3;
2947
2956
  var SENSITIVE_DOCKER_ENV_KEYS = /* @__PURE__ */ new Set([
2948
2957
  "DATABASE_URL",
2949
2958
  "DB_PASSWORD",
@@ -3521,19 +3530,68 @@ function dumpLocalStorageMigrations(dbContainer) {
3521
3530
  "--no-privileges"
3522
3531
  ]);
3523
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
+ }
3524
3549
  function storageMigrationsImportSql(dumpSql) {
3525
3550
  const tempTable = "pg_temp.ardent_storage_migrations_import";
3526
- const tempDumpSql = dumpSql.replaceAll("INSERT INTO storage.migrations", `INSERT INTO ${tempTable}`);
3551
+ const tempDumpSql = retargetPgDumpInserts(dumpSql, "storage", "migrations", tempTable);
3527
3552
  return `
3553
+ SET lock_timeout = '2s';
3554
+ SET statement_timeout = '10s';
3528
3555
  BEGIN;
3529
3556
  CREATE TEMP TABLE ardent_storage_migrations_import (LIKE storage.migrations INCLUDING DEFAULTS);
3530
3557
  ${tempDumpSql}
3531
3558
  INSERT INTO storage.migrations
3532
3559
  SELECT * FROM ${tempTable}
3533
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;
3534
3581
  COMMIT;
3535
3582
  `;
3536
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
+ }
3537
3595
  function waitForOriginalDb(containerName2) {
3538
3596
  const deadline = Date.now() + 3e4;
3539
3597
  while (Date.now() < deadline) {
@@ -3546,6 +3604,81 @@ function waitForOriginalDb(containerName2) {
3546
3604
  }
3547
3605
  throw new Error(`Timed out waiting for ${containerName2} to accept local PostgreSQL connections.`);
3548
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
+ }
3549
3682
  function copyLocalStorageMigrationsToBranch(dumpSql, originalDbContainer, dbContainer, network, branchUser, branchPassword) {
3550
3683
  runDocker(["start", originalDbContainer]);
3551
3684
  try {
@@ -3570,7 +3703,39 @@ function copyLocalStorageMigrationsToBranch(dumpSql, originalDbContainer, dbCont
3570
3703
  "-v",
3571
3704
  "ON_ERROR_STOP=1"
3572
3705
  ],
3573
- 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 }
3574
3739
  );
3575
3740
  } finally {
3576
3741
  runDocker(["stop", originalDbContainer]);
@@ -3691,7 +3856,6 @@ function applySupabaseRealtimeCompatibility(projectId, originalDbContainer, dbCo
3691
3856
  supabaseRealtimeCompatibilitySql(),
3692
3857
  { timeoutMs: 7e3 }
3693
3858
  );
3694
- runDocker(["restart", realtimeContainer]);
3695
3859
  return;
3696
3860
  } catch (error) {
3697
3861
  lastError = error instanceof Error ? error.message : String(error);
@@ -3713,6 +3877,42 @@ function kongApiUrl(projectId) {
3713
3877
  const port = first?.split(":").pop();
3714
3878
  return port ? `http://127.0.0.1:${port}` : void 0;
3715
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
+ }
3716
3916
  async function waitForHealth(projectId) {
3717
3917
  const apiUrl = kongApiUrl(projectId);
3718
3918
  if (!apiUrl) {
@@ -3722,12 +3922,22 @@ async function waitForHealth(projectId) {
3722
3922
  let lastError = "";
3723
3923
  while (Date.now() < deadline) {
3724
3924
  try {
3725
- const authResponse = await fetch(`${apiUrl}/auth/v1/health`);
3726
- const restResponse = await fetch(`${apiUrl}/rest/v1/`);
3727
- if (authResponse.ok && restResponse.ok) {
3728
- 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
+ ]);
3729
3940
  }
3730
- lastError = `auth=${authResponse.status}, rest=${restResponse.status}`;
3731
3941
  } catch (error) {
3732
3942
  lastError = error instanceof Error ? error.message : String(error);
3733
3943
  }
@@ -3794,11 +4004,16 @@ async function linkSupabaseAction(branchName, options = {}) {
3794
4004
  }
3795
4005
  console.log(`Linking Supabase project ${projectId} to branch ${branch.name}...`);
3796
4006
  const storageMigrationsDump = dumpLocalStorageMigrations(dbContainer);
4007
+ const realtimeContainer = containerName("realtime", projectId);
4008
+ const realtimeMigrationsDump = dockerExists(realtimeContainer) ? dumpLocalRealtimeMigrations(dbContainer) : "";
3797
4009
  const originalDbContainerState = inspectContainer(dbContainer);
3798
4010
  const dbPortBindings = portPublishArgs(originalDbContainerState);
3799
4011
  runDocker(["stop", dbContainer]);
3800
4012
  runDocker(["rename", dbContainer, originalDbContainer]);
3801
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...");
3802
4017
  copyLocalStorageMigrationsToBranch(
3803
4018
  storageMigrationsDump,
3804
4019
  originalDbContainer,
@@ -3807,19 +4022,17 @@ async function linkSupabaseAction(branchName, options = {}) {
3807
4022
  branchUser,
3808
4023
  branchPassword
3809
4024
  );
3810
- for (const servicePrefix of SERVICE_SUFFIXES) {
3811
- const service = recreateServiceForBranch(
3812
- servicePrefix,
3813
- projectId,
3814
- network,
4025
+ if (realtimeMigrationsDump) {
4026
+ console.log("Copying local Supabase Realtime migration state...");
4027
+ copyLocalRealtimeMigrationsToBranch(
4028
+ realtimeMigrationsDump,
4029
+ originalDbContainer,
3815
4030
  dbContainer,
3816
- branchUser,
4031
+ network,
3817
4032
  branchPassword
3818
4033
  );
3819
- if (service) {
3820
- createdServices.push(service);
3821
- }
3822
4034
  }
4035
+ console.log("Applying Supabase Realtime compatibility...");
3823
4036
  applySupabaseRealtimeCompatibility(
3824
4037
  projectId,
3825
4038
  originalDbContainer,
@@ -3829,6 +4042,27 @@ async function linkSupabaseAction(branchName, options = {}) {
3829
4042
  );
3830
4043
  runDocker(["rm", "-f", dbContainer]);
3831
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
+ }
3832
4066
  const kong = containerName("kong", projectId);
3833
4067
  if (dockerExists(kong)) {
3834
4068
  runDocker(["restart", kong]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {