postgresai 0.14.0-beta.6 → 0.14.0-beta.8
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/bin/postgres-ai.ts +73 -32
- package/bunfig.toml +3 -2
- package/dist/bin/postgres-ai.js +62 -27
- package/lib/metrics-embedded.ts +1 -1
- package/package.json +1 -1
- package/test/checkup.integration.test.ts +7 -5
- package/test/init.integration.test.ts +4 -2
package/bin/postgres-ai.ts
CHANGED
|
@@ -1104,7 +1104,7 @@ function checkRunningContainers(): { running: boolean; containers: string[] } {
|
|
|
1104
1104
|
/**
|
|
1105
1105
|
* Run docker compose command
|
|
1106
1106
|
*/
|
|
1107
|
-
async function runCompose(args: string[]): Promise<number> {
|
|
1107
|
+
async function runCompose(args: string[], grafanaPassword?: string): Promise<number> {
|
|
1108
1108
|
let composeFile: string;
|
|
1109
1109
|
let projectDir: string;
|
|
1110
1110
|
try {
|
|
@@ -1130,21 +1130,28 @@ async function runCompose(args: string[]): Promise<number> {
|
|
|
1130
1130
|
return 1;
|
|
1131
1131
|
}
|
|
1132
1132
|
|
|
1133
|
-
//
|
|
1133
|
+
// Set Grafana password from parameter or .pgwatch-config
|
|
1134
1134
|
const env = { ...process.env };
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
const
|
|
1142
|
-
if (
|
|
1143
|
-
|
|
1135
|
+
if (grafanaPassword) {
|
|
1136
|
+
env.GF_SECURITY_ADMIN_PASSWORD = grafanaPassword;
|
|
1137
|
+
} else {
|
|
1138
|
+
const cfgPath = path.resolve(projectDir, ".pgwatch-config");
|
|
1139
|
+
if (fs.existsSync(cfgPath)) {
|
|
1140
|
+
try {
|
|
1141
|
+
const stats = fs.statSync(cfgPath);
|
|
1142
|
+
if (!stats.isDirectory()) {
|
|
1143
|
+
const content = fs.readFileSync(cfgPath, "utf8");
|
|
1144
|
+
const match = content.match(/^grafana_password=([^\r\n]+)/m);
|
|
1145
|
+
if (match) {
|
|
1146
|
+
env.GF_SECURITY_ADMIN_PASSWORD = match[1].trim();
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
} catch (err) {
|
|
1150
|
+
// If we can't read the config, log warning and continue without setting the password
|
|
1151
|
+
if (process.env.DEBUG) {
|
|
1152
|
+
console.warn(`Warning: Could not read Grafana password from config: ${err instanceof Error ? err.message : String(err)}`);
|
|
1144
1153
|
}
|
|
1145
1154
|
}
|
|
1146
|
-
} catch (err) {
|
|
1147
|
-
// If we can't read the config, continue without setting the password
|
|
1148
1155
|
}
|
|
1149
1156
|
}
|
|
1150
1157
|
|
|
@@ -1461,8 +1468,8 @@ mon
|
|
|
1461
1468
|
}
|
|
1462
1469
|
|
|
1463
1470
|
// Step 5: Start services
|
|
1464
|
-
console.log(
|
|
1465
|
-
const code2 = await runCompose(["up", "-d", "--force-recreate"]);
|
|
1471
|
+
console.log("Step 5: Starting monitoring services...");
|
|
1472
|
+
const code2 = await runCompose(["up", "-d", "--force-recreate"], grafanaPassword);
|
|
1466
1473
|
if (code2 !== 0) {
|
|
1467
1474
|
process.exitCode = code2;
|
|
1468
1475
|
return;
|
|
@@ -1758,34 +1765,68 @@ mon
|
|
|
1758
1765
|
});
|
|
1759
1766
|
mon
|
|
1760
1767
|
.command("clean")
|
|
1761
|
-
.description("cleanup monitoring services artifacts")
|
|
1762
|
-
.
|
|
1763
|
-
|
|
1768
|
+
.description("cleanup monitoring services artifacts (stops services and removes volumes)")
|
|
1769
|
+
.option("--keep-volumes", "keep data volumes (only stop and remove containers)")
|
|
1770
|
+
.action(async (options: { keepVolumes?: boolean }) => {
|
|
1771
|
+
console.log("Cleaning up monitoring services...\n");
|
|
1764
1772
|
|
|
1765
1773
|
try {
|
|
1766
|
-
//
|
|
1767
|
-
const
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1774
|
+
// First, use docker-compose down to properly stop and remove containers/volumes
|
|
1775
|
+
const downArgs = options.keepVolumes ? ["down"] : ["down", "-v"];
|
|
1776
|
+
console.log(options.keepVolumes
|
|
1777
|
+
? "Stopping and removing containers (keeping volumes)..."
|
|
1778
|
+
: "Stopping and removing containers and volumes...");
|
|
1779
|
+
|
|
1780
|
+
const downCode = await runCompose(downArgs);
|
|
1781
|
+
if (downCode === 0) {
|
|
1782
|
+
console.log("✓ Monitoring services stopped and removed");
|
|
1772
1783
|
} else {
|
|
1773
|
-
console.log("
|
|
1784
|
+
console.log("⚠ Could not stop services (may not be running)");
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// Remove orphaned volumes from previous installs with different project names
|
|
1788
|
+
if (!options.keepVolumes) {
|
|
1789
|
+
const volumePatterns = [
|
|
1790
|
+
"monitoring_grafana_data",
|
|
1791
|
+
"monitoring_postgres_ai_configs",
|
|
1792
|
+
"monitoring_sink_postgres_data",
|
|
1793
|
+
"monitoring_target_db_data",
|
|
1794
|
+
"monitoring_victoria_metrics_data",
|
|
1795
|
+
"postgres_ai_configs_grafana_data",
|
|
1796
|
+
"postgres_ai_configs_sink_postgres_data",
|
|
1797
|
+
"postgres_ai_configs_target_db_data",
|
|
1798
|
+
"postgres_ai_configs_victoria_metrics_data",
|
|
1799
|
+
"postgres_ai_configs_postgres_ai_configs",
|
|
1800
|
+
];
|
|
1801
|
+
|
|
1802
|
+
const { stdout: existingVolumes } = await execFilePromise("docker", ["volume", "ls", "-q"]);
|
|
1803
|
+
const volumeList = existingVolumes.trim().split('\n').filter(Boolean);
|
|
1804
|
+
const orphanedVolumes = volumeList.filter(v => volumePatterns.includes(v));
|
|
1805
|
+
|
|
1806
|
+
if (orphanedVolumes.length > 0) {
|
|
1807
|
+
let removedCount = 0;
|
|
1808
|
+
for (const vol of orphanedVolumes) {
|
|
1809
|
+
try {
|
|
1810
|
+
await execFilePromise("docker", ["volume", "rm", vol]);
|
|
1811
|
+
removedCount++;
|
|
1812
|
+
} catch {
|
|
1813
|
+
// Volume might be in use, skip silently
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
if (removedCount > 0) {
|
|
1817
|
+
console.log(`✓ Removed ${removedCount} orphaned volume(s) from previous installs`);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1774
1820
|
}
|
|
1775
1821
|
|
|
1776
|
-
// Remove
|
|
1777
|
-
await execFilePromise("docker", ["volume", "prune", "-f"]);
|
|
1778
|
-
console.log("✓ Removed unused volumes");
|
|
1779
|
-
|
|
1780
|
-
// Remove unused networks
|
|
1822
|
+
// Remove any dangling resources
|
|
1781
1823
|
await execFilePromise("docker", ["network", "prune", "-f"]);
|
|
1782
1824
|
console.log("✓ Removed unused networks");
|
|
1783
1825
|
|
|
1784
|
-
// Remove dangling images
|
|
1785
1826
|
await execFilePromise("docker", ["image", "prune", "-f"]);
|
|
1786
1827
|
console.log("✓ Removed dangling images");
|
|
1787
1828
|
|
|
1788
|
-
console.log("\
|
|
1829
|
+
console.log("\n✓ Cleanup completed - ready for fresh install");
|
|
1789
1830
|
} catch (error) {
|
|
1790
1831
|
const message = error instanceof Error ? error.message : String(error);
|
|
1791
1832
|
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 (
|
|
5
|
+
# Default timeout for all tests (60 seconds)
|
|
6
6
|
# Integration tests that connect to databases need longer timeouts
|
|
7
|
-
|
|
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
|
package/dist/bin/postgres-ai.js
CHANGED
|
@@ -13064,7 +13064,7 @@ var {
|
|
|
13064
13064
|
// package.json
|
|
13065
13065
|
var package_default = {
|
|
13066
13066
|
name: "postgresai",
|
|
13067
|
-
version: "0.14.0-beta.
|
|
13067
|
+
version: "0.14.0-beta.8",
|
|
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.
|
|
15890
|
+
var version = "0.14.0-beta.8";
|
|
15891
15891
|
var package_default2 = {
|
|
15892
15892
|
name: "postgresai",
|
|
15893
15893
|
version,
|
|
@@ -27138,7 +27138,7 @@ function checkRunningContainers() {
|
|
|
27138
27138
|
return { running: false, containers: [] };
|
|
27139
27139
|
}
|
|
27140
27140
|
}
|
|
27141
|
-
async function runCompose(args) {
|
|
27141
|
+
async function runCompose(args, grafanaPassword) {
|
|
27142
27142
|
let composeFile;
|
|
27143
27143
|
let projectDir;
|
|
27144
27144
|
try {
|
|
@@ -27161,18 +27161,26 @@ async function runCompose(args) {
|
|
|
27161
27161
|
return 1;
|
|
27162
27162
|
}
|
|
27163
27163
|
const env = { ...process.env };
|
|
27164
|
-
|
|
27165
|
-
|
|
27166
|
-
|
|
27167
|
-
|
|
27168
|
-
|
|
27169
|
-
|
|
27170
|
-
const
|
|
27171
|
-
if (
|
|
27172
|
-
|
|
27164
|
+
if (grafanaPassword) {
|
|
27165
|
+
env.GF_SECURITY_ADMIN_PASSWORD = grafanaPassword;
|
|
27166
|
+
} else {
|
|
27167
|
+
const cfgPath = path5.resolve(projectDir, ".pgwatch-config");
|
|
27168
|
+
if (fs5.existsSync(cfgPath)) {
|
|
27169
|
+
try {
|
|
27170
|
+
const stats = fs5.statSync(cfgPath);
|
|
27171
|
+
if (!stats.isDirectory()) {
|
|
27172
|
+
const content = fs5.readFileSync(cfgPath, "utf8");
|
|
27173
|
+
const match = content.match(/^grafana_password=([^\r\n]+)/m);
|
|
27174
|
+
if (match) {
|
|
27175
|
+
env.GF_SECURITY_ADMIN_PASSWORD = match[1].trim();
|
|
27176
|
+
}
|
|
27177
|
+
}
|
|
27178
|
+
} catch (err) {
|
|
27179
|
+
if (process.env.DEBUG) {
|
|
27180
|
+
console.warn(`Warning: Could not read Grafana password from config: ${err instanceof Error ? err.message : String(err)}`);
|
|
27173
27181
|
}
|
|
27174
27182
|
}
|
|
27175
|
-
}
|
|
27183
|
+
}
|
|
27176
27184
|
}
|
|
27177
27185
|
return new Promise((resolve6) => {
|
|
27178
27186
|
const child = spawn2(cmd[0], [...cmd.slice(1), "-f", composeFile, ...args], {
|
|
@@ -27484,8 +27492,8 @@ You can provide either:`);
|
|
|
27484
27492
|
`);
|
|
27485
27493
|
grafanaPassword = "demo";
|
|
27486
27494
|
}
|
|
27487
|
-
console.log(
|
|
27488
|
-
const code2 = await runCompose(["up", "-d", "--force-recreate"]);
|
|
27495
|
+
console.log("Step 5: Starting monitoring services...");
|
|
27496
|
+
const code2 = await runCompose(["up", "-d", "--force-recreate"], grafanaPassword);
|
|
27489
27497
|
if (code2 !== 0) {
|
|
27490
27498
|
process.exitCode = code2;
|
|
27491
27499
|
return;
|
|
@@ -27740,27 +27748,54 @@ Stopping services and removing data...`);
|
|
|
27740
27748
|
process.exitCode = 1;
|
|
27741
27749
|
}
|
|
27742
27750
|
});
|
|
27743
|
-
mon.command("clean").description("cleanup monitoring services artifacts").action(async () => {
|
|
27744
|
-
console.log(`Cleaning up
|
|
27751
|
+
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) => {
|
|
27752
|
+
console.log(`Cleaning up monitoring services...
|
|
27745
27753
|
`);
|
|
27746
27754
|
try {
|
|
27747
|
-
const
|
|
27748
|
-
|
|
27749
|
-
|
|
27750
|
-
|
|
27751
|
-
|
|
27752
|
-
console.log("\u2713 Removed stopped containers");
|
|
27755
|
+
const downArgs = options.keepVolumes ? ["down"] : ["down", "-v"];
|
|
27756
|
+
console.log(options.keepVolumes ? "Stopping and removing containers (keeping volumes)..." : "Stopping and removing containers and volumes...");
|
|
27757
|
+
const downCode = await runCompose(downArgs);
|
|
27758
|
+
if (downCode === 0) {
|
|
27759
|
+
console.log("\u2713 Monitoring services stopped and removed");
|
|
27753
27760
|
} else {
|
|
27754
|
-
console.log("\
|
|
27761
|
+
console.log("\u26A0 Could not stop services (may not be running)");
|
|
27762
|
+
}
|
|
27763
|
+
if (!options.keepVolumes) {
|
|
27764
|
+
const volumePatterns = [
|
|
27765
|
+
"monitoring_grafana_data",
|
|
27766
|
+
"monitoring_postgres_ai_configs",
|
|
27767
|
+
"monitoring_sink_postgres_data",
|
|
27768
|
+
"monitoring_target_db_data",
|
|
27769
|
+
"monitoring_victoria_metrics_data",
|
|
27770
|
+
"postgres_ai_configs_grafana_data",
|
|
27771
|
+
"postgres_ai_configs_sink_postgres_data",
|
|
27772
|
+
"postgres_ai_configs_target_db_data",
|
|
27773
|
+
"postgres_ai_configs_victoria_metrics_data",
|
|
27774
|
+
"postgres_ai_configs_postgres_ai_configs"
|
|
27775
|
+
];
|
|
27776
|
+
const { stdout: existingVolumes } = await execFilePromise("docker", ["volume", "ls", "-q"]);
|
|
27777
|
+
const volumeList = existingVolumes.trim().split(`
|
|
27778
|
+
`).filter(Boolean);
|
|
27779
|
+
const orphanedVolumes = volumeList.filter((v) => volumePatterns.includes(v));
|
|
27780
|
+
if (orphanedVolumes.length > 0) {
|
|
27781
|
+
let removedCount = 0;
|
|
27782
|
+
for (const vol of orphanedVolumes) {
|
|
27783
|
+
try {
|
|
27784
|
+
await execFilePromise("docker", ["volume", "rm", vol]);
|
|
27785
|
+
removedCount++;
|
|
27786
|
+
} catch {}
|
|
27787
|
+
}
|
|
27788
|
+
if (removedCount > 0) {
|
|
27789
|
+
console.log(`\u2713 Removed ${removedCount} orphaned volume(s) from previous installs`);
|
|
27790
|
+
}
|
|
27791
|
+
}
|
|
27755
27792
|
}
|
|
27756
|
-
await execFilePromise("docker", ["volume", "prune", "-f"]);
|
|
27757
|
-
console.log("\u2713 Removed unused volumes");
|
|
27758
27793
|
await execFilePromise("docker", ["network", "prune", "-f"]);
|
|
27759
27794
|
console.log("\u2713 Removed unused networks");
|
|
27760
27795
|
await execFilePromise("docker", ["image", "prune", "-f"]);
|
|
27761
27796
|
console.log("\u2713 Removed dangling images");
|
|
27762
27797
|
console.log(`
|
|
27763
|
-
Cleanup completed`);
|
|
27798
|
+
\u2713 Cleanup completed - ready for fresh install`);
|
|
27764
27799
|
} catch (error2) {
|
|
27765
27800
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
27766
27801
|
console.error(`Error during cleanup: ${message}`);
|
package/lib/metrics-embedded.ts
CHANGED
package/package.json
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
{
|