postgresai 0.14.0 → 0.15.0-dev.1
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 +113 -10
- package/dist/bin/postgres-ai.js +422 -17
- package/lib/checkup.ts +226 -0
- package/lib/config.ts +4 -0
- package/lib/metrics-loader.ts +3 -0
- package/package.json +1 -1
- package/scripts/embed-metrics.ts +3 -0
- package/scripts/generate-release-notes.ts +283 -48
- package/test/checkup.test.ts +1 -1
- package/test/mcp-server.test.ts +57 -0
- package/test/monitoring.test.ts +261 -0
package/bin/postgres-ai.ts
CHANGED
|
@@ -24,6 +24,18 @@ import { getCheckupEntry } from "../lib/checkup-dictionary";
|
|
|
24
24
|
import { createCheckupReport, uploadCheckupReportJson, convertCheckupReportJsonToMarkdown, RpcError, formatRpcErrorForDisplay, withRetry } from "../lib/checkup-api";
|
|
25
25
|
import { generateCheckSummary } from "../lib/checkup-summary";
|
|
26
26
|
|
|
27
|
+
// Node.js version check - require Node 18+
|
|
28
|
+
// Node 14 reached EOL in April 2023, Node 16 in September 2023.
|
|
29
|
+
// Node 18+ is required for native ESM, modern crypto APIs, and security updates.
|
|
30
|
+
const nodeVersion = parseInt(process.versions.node.split('.')[0], 10);
|
|
31
|
+
if (nodeVersion < 18) {
|
|
32
|
+
console.error(`\x1b[31mError: postgresai requires Node 18 or higher.\x1b[0m`);
|
|
33
|
+
console.error(`You are running Node.js ${process.versions.node}.`);
|
|
34
|
+
console.error(`Please upgrade to Node.js 20 LTS or Node.js 22 for security updates.`);
|
|
35
|
+
console.error(`\nDownload: https://nodejs.org/`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
// Singleton readline interface for stdin prompts
|
|
28
40
|
let rl: ReturnType<typeof createInterface> | null = null;
|
|
29
41
|
function getReadline() {
|
|
@@ -2061,6 +2073,85 @@ function checkRunningContainers(): { running: boolean; containers: string[] } {
|
|
|
2061
2073
|
}
|
|
2062
2074
|
}
|
|
2063
2075
|
|
|
2076
|
+
/**
|
|
2077
|
+
* Register monitoring instance with the API (non-blocking).
|
|
2078
|
+
* Returns immediately, logs result in background.
|
|
2079
|
+
*/
|
|
2080
|
+
function registerMonitoringInstance(
|
|
2081
|
+
apiKey: string,
|
|
2082
|
+
projectName: string,
|
|
2083
|
+
opts?: { apiBaseUrl?: string; debug?: boolean }
|
|
2084
|
+
): void {
|
|
2085
|
+
const { apiBaseUrl } = resolveBaseUrls(opts);
|
|
2086
|
+
const url = `${apiBaseUrl}/rpc/monitoring_instance_register`;
|
|
2087
|
+
const debug = opts?.debug;
|
|
2088
|
+
|
|
2089
|
+
if (debug) {
|
|
2090
|
+
console.log(`\nDebug: Registering monitoring instance...`);
|
|
2091
|
+
console.log(`Debug: POST ${url}`);
|
|
2092
|
+
console.log(`Debug: project_name=${projectName}`);
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// Fire and forget - don't block the main flow
|
|
2096
|
+
fetch(url, {
|
|
2097
|
+
method: "POST",
|
|
2098
|
+
headers: {
|
|
2099
|
+
"Content-Type": "application/json",
|
|
2100
|
+
},
|
|
2101
|
+
body: JSON.stringify({
|
|
2102
|
+
api_token: apiKey,
|
|
2103
|
+
project_name: projectName,
|
|
2104
|
+
}),
|
|
2105
|
+
})
|
|
2106
|
+
.then(async (res) => {
|
|
2107
|
+
const body = await res.text().catch(() => "");
|
|
2108
|
+
if (!res.ok) {
|
|
2109
|
+
if (debug) {
|
|
2110
|
+
console.log(`Debug: Monitoring registration failed: HTTP ${res.status}`);
|
|
2111
|
+
console.log(`Debug: Response: ${body}`);
|
|
2112
|
+
}
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
if (debug) {
|
|
2116
|
+
console.log(`Debug: Monitoring registration response: ${body}`);
|
|
2117
|
+
}
|
|
2118
|
+
})
|
|
2119
|
+
.catch((err) => {
|
|
2120
|
+
if (debug) {
|
|
2121
|
+
console.log(`Debug: Monitoring registration error: ${err.message}`);
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
/**
|
|
2127
|
+
* Update .pgwatch-config file with key=value pairs.
|
|
2128
|
+
* Preserves existing values not being updated.
|
|
2129
|
+
*/
|
|
2130
|
+
function updatePgwatchConfig(configPath: string, updates: Record<string, string>): void {
|
|
2131
|
+
let lines: string[] = [];
|
|
2132
|
+
|
|
2133
|
+
// Read existing config if it exists
|
|
2134
|
+
if (fs.existsSync(configPath)) {
|
|
2135
|
+
const stats = fs.statSync(configPath);
|
|
2136
|
+
if (!stats.isDirectory()) {
|
|
2137
|
+
const content = fs.readFileSync(configPath, "utf8");
|
|
2138
|
+
lines = content.split(/\r?\n/).filter(l => l.trim() !== "");
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
// Update or add each key
|
|
2143
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
2144
|
+
const existingIndex = lines.findIndex(l => l.startsWith(key + "="));
|
|
2145
|
+
if (existingIndex >= 0) {
|
|
2146
|
+
lines[existingIndex] = `${key}=${value}`;
|
|
2147
|
+
} else {
|
|
2148
|
+
lines.push(`${key}=${value}`);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
fs.writeFileSync(configPath, lines.join("\n") + "\n", { encoding: "utf8", mode: 0o600 });
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2064
2155
|
/**
|
|
2065
2156
|
* Run docker compose command
|
|
2066
2157
|
*/
|
|
@@ -2145,12 +2236,13 @@ mon
|
|
|
2145
2236
|
.option("--api-key <key>", "Postgres AI API key for automated report uploads")
|
|
2146
2237
|
.option("--db-url <url>", "PostgreSQL connection URL to monitor")
|
|
2147
2238
|
.option("--tag <tag>", "Docker image tag to use (e.g., 0.14.0, 0.14.0-dev.33)")
|
|
2239
|
+
.option("--project <name>", "Docker Compose project name (default: postgres_ai)")
|
|
2148
2240
|
.option("-y, --yes", "accept all defaults and skip interactive prompts", false)
|
|
2149
|
-
.action(async (opts: { demo: boolean; apiKey?: string; dbUrl?: string; tag?: string; yes: boolean }) => {
|
|
2241
|
+
.action(async (opts: { demo: boolean; apiKey?: string; dbUrl?: string; tag?: string; project?: string; yes: boolean }) => {
|
|
2150
2242
|
// Get apiKey from global program options (--api-key is defined globally)
|
|
2151
2243
|
// This is needed because Commander.js routes --api-key to the global option, not the subcommand's option
|
|
2152
2244
|
const globalOpts = program.opts<CliOptions>();
|
|
2153
|
-
|
|
2245
|
+
let apiKey = opts.apiKey || globalOpts.apiKey;
|
|
2154
2246
|
|
|
2155
2247
|
console.log("\n=================================");
|
|
2156
2248
|
console.log(" PostgresAI monitoring local install");
|
|
@@ -2161,6 +2253,13 @@ mon
|
|
|
2161
2253
|
const { projectDir } = await resolveOrInitPaths();
|
|
2162
2254
|
console.log(`Project directory: ${projectDir}\n`);
|
|
2163
2255
|
|
|
2256
|
+
// Save project name to .pgwatch-config if provided (used by reporter container)
|
|
2257
|
+
if (opts.project) {
|
|
2258
|
+
const cfgPath = path.resolve(projectDir, ".pgwatch-config");
|
|
2259
|
+
updatePgwatchConfig(cfgPath, { project_name: opts.project });
|
|
2260
|
+
console.log(`Using project name: ${opts.project}\n`);
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2164
2263
|
// Update .env with custom tag if provided
|
|
2165
2264
|
const envFile = path.resolve(projectDir, ".env");
|
|
2166
2265
|
|
|
@@ -2230,10 +2329,7 @@ mon
|
|
|
2230
2329
|
console.log("Using API key provided via --api-key parameter");
|
|
2231
2330
|
config.writeConfig({ apiKey });
|
|
2232
2331
|
// Keep reporter compatibility (docker-compose mounts .pgwatch-config)
|
|
2233
|
-
|
|
2234
|
-
encoding: "utf8",
|
|
2235
|
-
mode: 0o600
|
|
2236
|
-
});
|
|
2332
|
+
updatePgwatchConfig(path.resolve(projectDir, ".pgwatch-config"), { api_key: apiKey });
|
|
2237
2333
|
console.log("✓ API key saved\n");
|
|
2238
2334
|
} else if (opts.yes) {
|
|
2239
2335
|
// Auto-yes mode without API key - skip API key setup
|
|
@@ -2252,10 +2348,8 @@ mon
|
|
|
2252
2348
|
if (trimmedKey) {
|
|
2253
2349
|
config.writeConfig({ apiKey: trimmedKey });
|
|
2254
2350
|
// Keep reporter compatibility (docker-compose mounts .pgwatch-config)
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
mode: 0o600
|
|
2258
|
-
});
|
|
2351
|
+
updatePgwatchConfig(path.resolve(projectDir, ".pgwatch-config"), { api_key: trimmedKey });
|
|
2352
|
+
apiKey = trimmedKey; // Update for later use in registerMonitoringInstance
|
|
2259
2353
|
console.log("✓ API key saved\n");
|
|
2260
2354
|
break;
|
|
2261
2355
|
}
|
|
@@ -2442,6 +2536,15 @@ mon
|
|
|
2442
2536
|
}
|
|
2443
2537
|
console.log("✓ Services started\n");
|
|
2444
2538
|
|
|
2539
|
+
// Register monitoring instance with API (non-blocking, only if API key is configured)
|
|
2540
|
+
if (apiKey && !opts.demo) {
|
|
2541
|
+
const projectName = opts.project || "postgres-ai-monitoring";
|
|
2542
|
+
registerMonitoringInstance(apiKey, projectName, {
|
|
2543
|
+
apiBaseUrl: globalOpts.apiBaseUrl,
|
|
2544
|
+
debug: !!process.env.DEBUG,
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2445
2548
|
// Final summary
|
|
2446
2549
|
console.log("=================================");
|
|
2447
2550
|
console.log(" Local install completed!");
|