postgresai 0.14.0-beta.7 → 0.14.0-beta.9

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.
@@ -1155,8 +1155,14 @@ async function runCompose(args: string[], grafanaPassword?: string): Promise<num
1155
1155
  }
1156
1156
  }
1157
1157
 
1158
+ // On macOS, node-exporter can't mount host root filesystem - skip it
1159
+ const finalArgs = [...args];
1160
+ if (process.platform === "darwin" && args.includes("up")) {
1161
+ finalArgs.push("--scale", "node-exporter=0");
1162
+ }
1163
+
1158
1164
  return new Promise<number>((resolve) => {
1159
- const child = spawn(cmd[0], [...cmd.slice(1), "-f", composeFile, ...args], {
1165
+ const child = spawn(cmd[0], [...cmd.slice(1), "-f", composeFile, ...finalArgs], {
1160
1166
  stdio: "inherit",
1161
1167
  env: env,
1162
1168
  cwd: projectDir
@@ -1215,8 +1221,8 @@ mon
1215
1221
  if (pwdMatch) existingPassword = pwdMatch[1].trim();
1216
1222
  }
1217
1223
 
1218
- // Priority: CLI --tag flag > existing .env > package version
1219
- const imageTag = opts.tag || existingTag || pkg.version;
1224
+ // Priority: CLI --tag flag > PGAI_TAG env var > existing .env > package version
1225
+ const imageTag = opts.tag || process.env.PGAI_TAG || existingTag || pkg.version;
1220
1226
 
1221
1227
  const envLines: string[] = [`PGAI_TAG=${imageTag}`];
1222
1228
  if (existingRegistry) {
@@ -1527,6 +1533,34 @@ mon
1527
1533
  if (code !== 0) process.exitCode = code;
1528
1534
  });
1529
1535
 
1536
+ // Known container names for cleanup
1537
+ const MONITORING_CONTAINERS = [
1538
+ "postgres-ai-config-init",
1539
+ "node-exporter",
1540
+ "cadvisor",
1541
+ "grafana-with-datasources",
1542
+ "sink-postgres",
1543
+ "sink-prometheus",
1544
+ "target-db",
1545
+ "pgwatch-postgres",
1546
+ "pgwatch-prometheus",
1547
+ "postgres-exporter-sink",
1548
+ "flask-pgss-api",
1549
+ "sources-generator",
1550
+ "postgres-reports",
1551
+ ];
1552
+
1553
+ /** Remove orphaned containers that docker compose down might miss */
1554
+ async function removeOrphanedContainers(): Promise<void> {
1555
+ for (const container of MONITORING_CONTAINERS) {
1556
+ try {
1557
+ await execFilePromise("docker", ["rm", "-f", container]);
1558
+ } catch {
1559
+ // Container doesn't exist, ignore
1560
+ }
1561
+ }
1562
+ }
1563
+
1530
1564
  mon
1531
1565
  .command("stop")
1532
1566
  .description("stop monitoring services")
@@ -1765,34 +1799,72 @@ mon
1765
1799
  });
1766
1800
  mon
1767
1801
  .command("clean")
1768
- .description("cleanup monitoring services artifacts")
1769
- .action(async () => {
1770
- console.log("Cleaning up Docker resources...\n");
1802
+ .description("cleanup monitoring services artifacts (stops services and removes volumes)")
1803
+ .option("--keep-volumes", "keep data volumes (only stop and remove containers)")
1804
+ .action(async (options: { keepVolumes?: boolean }) => {
1805
+ console.log("Cleaning up monitoring services...\n");
1771
1806
 
1772
1807
  try {
1773
- // Remove stopped containers
1774
- const { stdout: containers } = await execFilePromise("docker", ["ps", "-aq", "--filter", "status=exited"]);
1775
- if (containers.trim()) {
1776
- const containerIds = containers.trim().split('\n');
1777
- await execFilePromise("docker", ["rm", ...containerIds]);
1778
- console.log("✓ Removed stopped containers");
1808
+ // First, use docker-compose down to properly stop and remove containers/volumes
1809
+ const downArgs = options.keepVolumes ? ["down"] : ["down", "-v"];
1810
+ console.log(options.keepVolumes
1811
+ ? "Stopping and removing containers (keeping volumes)..."
1812
+ : "Stopping and removing containers and volumes...");
1813
+
1814
+ const downCode = await runCompose(downArgs);
1815
+ if (downCode === 0) {
1816
+ console.log("✓ Monitoring services stopped and removed");
1779
1817
  } else {
1780
- console.log(" No stopped containers to remove");
1818
+ console.log(" Could not stop services (may not be running)");
1819
+ }
1820
+
1821
+ // Remove any orphaned containers that docker compose down missed
1822
+ await removeOrphanedContainers();
1823
+ console.log("✓ Removed orphaned containers");
1824
+
1825
+ // Remove orphaned volumes from previous installs with different project names
1826
+ if (!options.keepVolumes) {
1827
+ const volumePatterns = [
1828
+ "monitoring_grafana_data",
1829
+ "monitoring_postgres_ai_configs",
1830
+ "monitoring_sink_postgres_data",
1831
+ "monitoring_target_db_data",
1832
+ "monitoring_victoria_metrics_data",
1833
+ "postgres_ai_configs_grafana_data",
1834
+ "postgres_ai_configs_sink_postgres_data",
1835
+ "postgres_ai_configs_target_db_data",
1836
+ "postgres_ai_configs_victoria_metrics_data",
1837
+ "postgres_ai_configs_postgres_ai_configs",
1838
+ ];
1839
+
1840
+ const { stdout: existingVolumes } = await execFilePromise("docker", ["volume", "ls", "-q"]);
1841
+ const volumeList = existingVolumes.trim().split('\n').filter(Boolean);
1842
+ const orphanedVolumes = volumeList.filter(v => volumePatterns.includes(v));
1843
+
1844
+ if (orphanedVolumes.length > 0) {
1845
+ let removedCount = 0;
1846
+ for (const vol of orphanedVolumes) {
1847
+ try {
1848
+ await execFilePromise("docker", ["volume", "rm", vol]);
1849
+ removedCount++;
1850
+ } catch {
1851
+ // Volume might be in use, skip silently
1852
+ }
1853
+ }
1854
+ if (removedCount > 0) {
1855
+ console.log(`✓ Removed ${removedCount} orphaned volume(s) from previous installs`);
1856
+ }
1857
+ }
1781
1858
  }
1782
1859
 
1783
- // Remove unused volumes
1784
- await execFilePromise("docker", ["volume", "prune", "-f"]);
1785
- console.log("✓ Removed unused volumes");
1786
-
1787
- // Remove unused networks
1860
+ // Remove any dangling resources
1788
1861
  await execFilePromise("docker", ["network", "prune", "-f"]);
1789
1862
  console.log("✓ Removed unused networks");
1790
1863
 
1791
- // Remove dangling images
1792
1864
  await execFilePromise("docker", ["image", "prune", "-f"]);
1793
1865
  console.log("✓ Removed dangling images");
1794
1866
 
1795
- console.log("\nCleanup completed");
1867
+ console.log("\n✓ Cleanup completed - ready for fresh install");
1796
1868
  } catch (error) {
1797
1869
  const message = error instanceof Error ? error.message : String(error);
1798
1870
  console.error(`Error during cleanup: ${message}`);
package/bunfig.toml CHANGED
@@ -2,9 +2,10 @@
2
2
  # https://bun.sh/docs/runtime/bunfig
3
3
 
4
4
  [test]
5
- # Default timeout for all tests (30 seconds)
5
+ # Default timeout for all tests (60 seconds)
6
6
  # Integration tests that connect to databases need longer timeouts
7
- timeout = 30000
7
+ # PostgreSQL startup can take up to 30s in slow CI environments
8
+ timeout = 60000
8
9
 
9
10
  # Coverage settings - enabled by default for test runs
10
11
  coverage = true
@@ -13064,7 +13064,7 @@ var {
13064
13064
  // package.json
13065
13065
  var package_default = {
13066
13066
  name: "postgresai",
13067
- version: "0.14.0-beta.7",
13067
+ version: "0.14.0-beta.9",
13068
13068
  description: "postgres_ai CLI",
13069
13069
  license: "Apache-2.0",
13070
13070
  private: false,
@@ -15887,7 +15887,7 @@ var Result = import_lib.default.Result;
15887
15887
  var TypeOverrides = import_lib.default.TypeOverrides;
15888
15888
  var defaults = import_lib.default.defaults;
15889
15889
  // package.json
15890
- var version = "0.14.0-beta.7";
15890
+ var version = "0.14.0-beta.9";
15891
15891
  var package_default2 = {
15892
15892
  name: "postgresai",
15893
15893
  version,
@@ -27182,8 +27182,12 @@ async function runCompose(args, grafanaPassword) {
27182
27182
  }
27183
27183
  }
27184
27184
  }
27185
+ const finalArgs = [...args];
27186
+ if (process.platform === "darwin" && args.includes("up")) {
27187
+ finalArgs.push("--scale", "node-exporter=0");
27188
+ }
27185
27189
  return new Promise((resolve6) => {
27186
- const child = spawn2(cmd[0], [...cmd.slice(1), "-f", composeFile, ...args], {
27190
+ const child = spawn2(cmd[0], [...cmd.slice(1), "-f", composeFile, ...finalArgs], {
27187
27191
  stdio: "inherit",
27188
27192
  env,
27189
27193
  cwd: projectDir
@@ -27224,7 +27228,7 @@ mon.command("local-install").description("install local monitoring stack (genera
27224
27228
  if (pwdMatch)
27225
27229
  existingPassword = pwdMatch[1].trim();
27226
27230
  }
27227
- const imageTag = opts.tag || existingTag || package_default.version;
27231
+ const imageTag = opts.tag || process.env.PGAI_TAG || existingTag || package_default.version;
27228
27232
  const envLines = [`PGAI_TAG=${imageTag}`];
27229
27233
  if (existingRegistry) {
27230
27234
  envLines.push(`PGAI_REGISTRY=${existingRegistry}`);
@@ -27546,6 +27550,28 @@ mon.command("start").description("start monitoring services").action(async () =>
27546
27550
  if (code !== 0)
27547
27551
  process.exitCode = code;
27548
27552
  });
27553
+ var MONITORING_CONTAINERS = [
27554
+ "postgres-ai-config-init",
27555
+ "node-exporter",
27556
+ "cadvisor",
27557
+ "grafana-with-datasources",
27558
+ "sink-postgres",
27559
+ "sink-prometheus",
27560
+ "target-db",
27561
+ "pgwatch-postgres",
27562
+ "pgwatch-prometheus",
27563
+ "postgres-exporter-sink",
27564
+ "flask-pgss-api",
27565
+ "sources-generator",
27566
+ "postgres-reports"
27567
+ ];
27568
+ async function removeOrphanedContainers() {
27569
+ for (const container of MONITORING_CONTAINERS) {
27570
+ try {
27571
+ await execFilePromise("docker", ["rm", "-f", container]);
27572
+ } catch {}
27573
+ }
27574
+ }
27549
27575
  mon.command("stop").description("stop monitoring services").action(async () => {
27550
27576
  const code = await runCompose(["down"]);
27551
27577
  if (code !== 0)
@@ -27748,27 +27774,56 @@ Stopping services and removing data...`);
27748
27774
  process.exitCode = 1;
27749
27775
  }
27750
27776
  });
27751
- mon.command("clean").description("cleanup monitoring services artifacts").action(async () => {
27752
- console.log(`Cleaning up Docker resources...
27777
+ mon.command("clean").description("cleanup monitoring services artifacts (stops services and removes volumes)").option("--keep-volumes", "keep data volumes (only stop and remove containers)").action(async (options) => {
27778
+ console.log(`Cleaning up monitoring services...
27753
27779
  `);
27754
27780
  try {
27755
- const { stdout: containers } = await execFilePromise("docker", ["ps", "-aq", "--filter", "status=exited"]);
27756
- if (containers.trim()) {
27757
- const containerIds = containers.trim().split(`
27758
- `);
27759
- await execFilePromise("docker", ["rm", ...containerIds]);
27760
- console.log("\u2713 Removed stopped containers");
27781
+ const downArgs = options.keepVolumes ? ["down"] : ["down", "-v"];
27782
+ console.log(options.keepVolumes ? "Stopping and removing containers (keeping volumes)..." : "Stopping and removing containers and volumes...");
27783
+ const downCode = await runCompose(downArgs);
27784
+ if (downCode === 0) {
27785
+ console.log("\u2713 Monitoring services stopped and removed");
27761
27786
  } else {
27762
- console.log("\u2713 No stopped containers to remove");
27787
+ console.log("\u26A0 Could not stop services (may not be running)");
27788
+ }
27789
+ await removeOrphanedContainers();
27790
+ console.log("\u2713 Removed orphaned containers");
27791
+ if (!options.keepVolumes) {
27792
+ const volumePatterns = [
27793
+ "monitoring_grafana_data",
27794
+ "monitoring_postgres_ai_configs",
27795
+ "monitoring_sink_postgres_data",
27796
+ "monitoring_target_db_data",
27797
+ "monitoring_victoria_metrics_data",
27798
+ "postgres_ai_configs_grafana_data",
27799
+ "postgres_ai_configs_sink_postgres_data",
27800
+ "postgres_ai_configs_target_db_data",
27801
+ "postgres_ai_configs_victoria_metrics_data",
27802
+ "postgres_ai_configs_postgres_ai_configs"
27803
+ ];
27804
+ const { stdout: existingVolumes } = await execFilePromise("docker", ["volume", "ls", "-q"]);
27805
+ const volumeList = existingVolumes.trim().split(`
27806
+ `).filter(Boolean);
27807
+ const orphanedVolumes = volumeList.filter((v) => volumePatterns.includes(v));
27808
+ if (orphanedVolumes.length > 0) {
27809
+ let removedCount = 0;
27810
+ for (const vol of orphanedVolumes) {
27811
+ try {
27812
+ await execFilePromise("docker", ["volume", "rm", vol]);
27813
+ removedCount++;
27814
+ } catch {}
27815
+ }
27816
+ if (removedCount > 0) {
27817
+ console.log(`\u2713 Removed ${removedCount} orphaned volume(s) from previous installs`);
27818
+ }
27819
+ }
27763
27820
  }
27764
- await execFilePromise("docker", ["volume", "prune", "-f"]);
27765
- console.log("\u2713 Removed unused volumes");
27766
27821
  await execFilePromise("docker", ["network", "prune", "-f"]);
27767
27822
  console.log("\u2713 Removed unused networks");
27768
27823
  await execFilePromise("docker", ["image", "prune", "-f"]);
27769
27824
  console.log("\u2713 Removed dangling images");
27770
27825
  console.log(`
27771
- Cleanup completed`);
27826
+ \u2713 Cleanup completed - ready for fresh install`);
27772
27827
  } catch (error2) {
27773
27828
  const message = error2 instanceof Error ? error2.message : String(error2);
27774
27829
  console.error(`Error during cleanup: ${message}`);
@@ -1,6 +1,6 @@
1
1
  // AUTO-GENERATED FILE - DO NOT EDIT
2
2
  // Generated from config/pgwatch-prometheus/metrics.yml by scripts/embed-metrics.ts
3
- // Generated at: 2025-12-29T23:36:32.654Z
3
+ // Generated at: 2025-12-30T03:12:19.377Z
4
4
 
5
5
  /**
6
6
  * Metric definition from metrics.yml
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresai",
3
- "version": "0.14.0-beta.7",
3
+ "version": "0.14.0-beta.9",
4
4
  "description": "postgres_ai CLI",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,
@@ -112,11 +112,12 @@ async function createTempPostgres(): Promise<TempPostgres> {
112
112
  const cleanup = async () => {
113
113
  postgresProc.kill("SIGTERM");
114
114
  try {
115
+ // 30s timeout to handle slower CI environments gracefully
115
116
  await waitFor(
116
117
  async () => {
117
118
  if (postgresProc.exitCode === null) throw new Error("still running");
118
119
  },
119
- { timeoutMs: 5000, intervalMs: 100 }
120
+ { timeoutMs: 30000, intervalMs: 100 }
120
121
  );
121
122
  } catch {
122
123
  postgresProc.kill("SIGKILL");
@@ -130,11 +131,11 @@ async function createTempPostgres(): Promise<TempPostgres> {
130
131
  return c;
131
132
  };
132
133
 
133
- // Wait for Postgres to start
134
+ // Wait for Postgres to start (30s timeout for slower CI environments)
134
135
  await waitFor(async () => {
135
136
  const c = await connect();
136
137
  await c.end();
137
- });
138
+ }, { timeoutMs: 30000, intervalMs: 100 });
138
139
 
139
140
  return { port, socketDir, cleanup, connect };
140
141
  }
@@ -171,15 +172,16 @@ describe.skipIf(!!skipReason)("checkup integration: express mode schema compatib
171
172
  let pg: TempPostgres;
172
173
  let client: Client;
173
174
 
175
+ // 60s timeout for hooks - PostgreSQL startup can take 30s+ in slow CI
174
176
  beforeAll(async () => {
175
177
  pg = await createTempPostgres();
176
178
  client = await pg.connect();
177
- });
179
+ }, { timeout: 60000 });
178
180
 
179
181
  afterAll(async () => {
180
182
  if (client) await client.end();
181
183
  if (pg) await pg.cleanup();
182
- });
184
+ }, { timeout: 60000 });
183
185
 
184
186
  // Test all checks supported by express mode
185
187
  const expressChecks = Object.keys(checkup.CHECK_INFO);
@@ -118,11 +118,12 @@ async function createTempPostgres(): Promise<TempPostgres> {
118
118
  const cleanup = async () => {
119
119
  postgresProc.kill("SIGTERM");
120
120
  try {
121
+ // 30s timeout to handle slower CI environments gracefully
121
122
  await waitFor(
122
123
  async () => {
123
124
  if (postgresProc.exitCode === null) throw new Error("still running");
124
125
  },
125
- { timeoutMs: 5000, intervalMs: 100 }
126
+ { timeoutMs: 30000, intervalMs: 100 }
126
127
  );
127
128
  } catch {
128
129
  postgresProc.kill("SIGKILL");
@@ -136,10 +137,11 @@ async function createTempPostgres(): Promise<TempPostgres> {
136
137
  return c;
137
138
  };
138
139
 
140
+ // Wait for Postgres to start (30s timeout for slower CI environments)
139
141
  await waitFor(async () => {
140
142
  const c = await connectLocal();
141
143
  await c.end();
142
- });
144
+ }, { timeoutMs: 30000, intervalMs: 100 });
143
145
 
144
146
  const postgresPassword = "postgrespw";
145
147
  {