postgresai 0.14.0-beta.13 → 0.14.0-beta.15
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 +324 -12
- package/dist/bin/postgres-ai.js +1009 -46
- package/dist/sql/02.extensions.sql +8 -0
- package/dist/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
- package/dist/sql/sql/02.extensions.sql +8 -0
- package/dist/sql/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
- package/dist/sql/sql/uninit/01.helpers.sql +5 -0
- package/dist/sql/sql/uninit/02.permissions.sql +30 -0
- package/dist/sql/sql/uninit/03.role.sql +27 -0
- package/dist/sql/uninit/01.helpers.sql +5 -0
- package/dist/sql/uninit/02.permissions.sql +30 -0
- package/dist/sql/uninit/03.role.sql +27 -0
- package/lib/checkup-dictionary.ts +114 -0
- package/lib/checkup.ts +130 -14
- package/lib/init.ts +109 -8
- package/package.json +9 -7
- package/scripts/embed-checkup-dictionary.ts +106 -0
- package/sql/02.extensions.sql +8 -0
- package/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
- package/sql/uninit/01.helpers.sql +5 -0
- package/sql/uninit/02.permissions.sql +30 -0
- package/sql/uninit/03.role.sql +27 -0
- package/test/checkup.test.ts +17 -18
- package/test/init.test.ts +245 -11
- package/lib/metrics-embedded.ts +0 -79
- /package/dist/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
- /package/dist/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
- /package/dist/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
- /package/dist/sql/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
- /package/dist/sql/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
- /package/dist/sql/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
- /package/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
- /package/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
- /package/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
package/bin/postgres-ai.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { Client } from "pg";
|
|
|
12
12
|
import { startMcpServer } from "../lib/mcp-server";
|
|
13
13
|
import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue, createIssue, updateIssue, updateIssueComment, fetchActionItem, fetchActionItems, createActionItem, updateActionItem, type ConfigChange } from "../lib/issues";
|
|
14
14
|
import { resolveBaseUrls } from "../lib/util";
|
|
15
|
-
import { applyInitPlan, buildInitPlan, connectWithSslFallback, DEFAULT_MONITORING_USER, KNOWN_PROVIDERS, redactPasswordsInSql, resolveAdminConnection, resolveMonitoringPassword, validateProvider, verifyInitSetup } from "../lib/init";
|
|
15
|
+
import { applyInitPlan, applyUninitPlan, buildInitPlan, buildUninitPlan, connectWithSslFallback, DEFAULT_MONITORING_USER, KNOWN_PROVIDERS, redactPasswordsInSql, resolveAdminConnection, resolveMonitoringPassword, validateProvider, verifyInitSetup } from "../lib/init";
|
|
16
16
|
import { SupabaseClient, resolveSupabaseConfig, extractProjectRefFromUrl, applyInitPlanViaSupabase, verifyInitSetupViaSupabase, fetchPoolerDatabaseUrl, type PgCompatibleError } from "../lib/supabase";
|
|
17
17
|
import * as pkce from "../lib/pkce";
|
|
18
18
|
import * as authServer from "../lib/auth-server";
|
|
@@ -20,6 +20,7 @@ import { maskSecret } from "../lib/util";
|
|
|
20
20
|
import { createInterface } from "readline";
|
|
21
21
|
import * as childProcess from "child_process";
|
|
22
22
|
import { REPORT_GENERATORS, CHECK_INFO, generateAllReports } from "../lib/checkup";
|
|
23
|
+
import { getCheckupEntry } from "../lib/checkup-dictionary";
|
|
23
24
|
import { createCheckupReport, uploadCheckupReportJson, RpcError, formatRpcErrorForDisplay, withRetry } from "../lib/checkup-api";
|
|
24
25
|
|
|
25
26
|
// Singleton readline interface for stdin prompts
|
|
@@ -1008,12 +1009,17 @@ program
|
|
|
1008
1009
|
if (errAny.code === "42501") {
|
|
1009
1010
|
if (failedStep === "01.role") {
|
|
1010
1011
|
console.error(" Context: role creation/update requires CREATEROLE or superuser");
|
|
1011
|
-
} else if (failedStep === "
|
|
1012
|
+
} else if (failedStep === "03.permissions") {
|
|
1012
1013
|
console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
|
|
1013
1014
|
}
|
|
1014
1015
|
console.error(" Fix: ensure your Supabase access token has sufficient permissions");
|
|
1015
1016
|
console.error(" Tip: run with --print-sql to review the exact SQL plan");
|
|
1016
1017
|
}
|
|
1018
|
+
// Schema already exists (42P06) or other duplicate object errors
|
|
1019
|
+
if (errAny.code === "42P06" || (message.includes("already exists") && failedStep === "03.permissions")) {
|
|
1020
|
+
console.error(" Hint: postgres_ai schema or objects already exist from a previous setup.");
|
|
1021
|
+
console.error(" Fix: run 'postgresai unprepare-db <connection>' first to clean up, then retry prepare-db.");
|
|
1022
|
+
}
|
|
1017
1023
|
}
|
|
1018
1024
|
if (errAny && typeof errAny === "object" && typeof errAny.httpStatus === "number") {
|
|
1019
1025
|
if (errAny.httpStatus === 401) {
|
|
@@ -1305,13 +1311,313 @@ program
|
|
|
1305
1311
|
if (errAny.code === "42501") {
|
|
1306
1312
|
if (failedStep === "01.role") {
|
|
1307
1313
|
console.error(" Context: role creation/update requires CREATEROLE or superuser");
|
|
1308
|
-
} else if (failedStep === "
|
|
1314
|
+
} else if (failedStep === "03.permissions") {
|
|
1309
1315
|
console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
|
|
1310
1316
|
}
|
|
1311
1317
|
console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
|
|
1312
1318
|
console.error(" Fix: on managed Postgres, use the provider's admin/master user");
|
|
1313
1319
|
console.error(" Tip: run with --print-sql to review the exact SQL plan");
|
|
1314
1320
|
}
|
|
1321
|
+
// Schema already exists (42P06) or other duplicate object errors
|
|
1322
|
+
if (errAny.code === "42P06" || (message.includes("already exists") && failedStep === "03.permissions")) {
|
|
1323
|
+
console.error(" Hint: postgres_ai schema or objects already exist from a previous setup.");
|
|
1324
|
+
console.error(" Fix: run 'postgresai unprepare-db <connection>' first to clean up, then retry prepare-db.");
|
|
1325
|
+
}
|
|
1326
|
+
if (errAny.code === "ECONNREFUSED") {
|
|
1327
|
+
console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
|
|
1328
|
+
}
|
|
1329
|
+
if (errAny.code === "ENOTFOUND") {
|
|
1330
|
+
console.error(" Hint: DNS resolution failed; double-check the host name");
|
|
1331
|
+
}
|
|
1332
|
+
if (errAny.code === "ETIMEDOUT") {
|
|
1333
|
+
console.error(" Hint: connection timed out; check network/firewall rules");
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
process.exitCode = 1;
|
|
1337
|
+
}
|
|
1338
|
+
} finally {
|
|
1339
|
+
if (client) {
|
|
1340
|
+
try {
|
|
1341
|
+
await client.end();
|
|
1342
|
+
} catch {
|
|
1343
|
+
// ignore
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
program
|
|
1350
|
+
.command("unprepare-db [conn]")
|
|
1351
|
+
.description("remove monitoring setup: drop monitoring user, views, schema, and revoke permissions")
|
|
1352
|
+
.option("--db-url <url>", "PostgreSQL connection URL (admin) (deprecated; pass it as positional arg)")
|
|
1353
|
+
.option("-h, --host <host>", "PostgreSQL host (psql-like)")
|
|
1354
|
+
.option("-p, --port <port>", "PostgreSQL port (psql-like)")
|
|
1355
|
+
.option("-U, --username <username>", "PostgreSQL user (psql-like)")
|
|
1356
|
+
.option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)")
|
|
1357
|
+
.option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)")
|
|
1358
|
+
.option("--monitoring-user <name>", "Monitoring role name to remove", DEFAULT_MONITORING_USER)
|
|
1359
|
+
.option("--keep-role", "Keep the monitoring role (only revoke permissions and drop objects)", false)
|
|
1360
|
+
.option("--provider <provider>", "Database provider (e.g., supabase). Affects which steps are executed.")
|
|
1361
|
+
.option("--print-sql", "Print SQL plan and exit (no changes applied)", false)
|
|
1362
|
+
.option("--force", "Skip confirmation prompt", false)
|
|
1363
|
+
.option("--json", "Output result as JSON (machine-readable)", false)
|
|
1364
|
+
.addHelpText(
|
|
1365
|
+
"after",
|
|
1366
|
+
[
|
|
1367
|
+
"",
|
|
1368
|
+
"Examples:",
|
|
1369
|
+
" postgresai unprepare-db postgresql://admin@host:5432/dbname",
|
|
1370
|
+
" postgresai unprepare-db \"dbname=dbname host=host user=admin\"",
|
|
1371
|
+
" postgresai unprepare-db -h host -p 5432 -U admin -d dbname",
|
|
1372
|
+
"",
|
|
1373
|
+
"Admin password:",
|
|
1374
|
+
" --admin-password <password> or PGPASSWORD=... (libpq standard)",
|
|
1375
|
+
"",
|
|
1376
|
+
"Keep role but remove objects/permissions:",
|
|
1377
|
+
" postgresai unprepare-db <conn> --keep-role",
|
|
1378
|
+
"",
|
|
1379
|
+
"Inspect SQL without applying changes:",
|
|
1380
|
+
" postgresai unprepare-db <conn> --print-sql",
|
|
1381
|
+
"",
|
|
1382
|
+
"Offline SQL plan (no DB connection):",
|
|
1383
|
+
" postgresai unprepare-db --print-sql",
|
|
1384
|
+
"",
|
|
1385
|
+
"Skip confirmation prompt:",
|
|
1386
|
+
" postgresai unprepare-db <conn> --force",
|
|
1387
|
+
].join("\n")
|
|
1388
|
+
)
|
|
1389
|
+
.action(async (conn: string | undefined, opts: {
|
|
1390
|
+
dbUrl?: string;
|
|
1391
|
+
host?: string;
|
|
1392
|
+
port?: string;
|
|
1393
|
+
username?: string;
|
|
1394
|
+
dbname?: string;
|
|
1395
|
+
adminPassword?: string;
|
|
1396
|
+
monitoringUser: string;
|
|
1397
|
+
keepRole?: boolean;
|
|
1398
|
+
provider?: string;
|
|
1399
|
+
printSql?: boolean;
|
|
1400
|
+
force?: boolean;
|
|
1401
|
+
json?: boolean;
|
|
1402
|
+
}, cmd: Command) => {
|
|
1403
|
+
// JSON output helper
|
|
1404
|
+
const jsonOutput = opts.json;
|
|
1405
|
+
const outputJson = (data: Record<string, unknown>) => {
|
|
1406
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1407
|
+
};
|
|
1408
|
+
const outputError = (error: {
|
|
1409
|
+
message: string;
|
|
1410
|
+
step?: string;
|
|
1411
|
+
code?: string;
|
|
1412
|
+
detail?: string;
|
|
1413
|
+
hint?: string;
|
|
1414
|
+
}) => {
|
|
1415
|
+
if (jsonOutput) {
|
|
1416
|
+
outputJson({
|
|
1417
|
+
success: false,
|
|
1418
|
+
error,
|
|
1419
|
+
});
|
|
1420
|
+
} else {
|
|
1421
|
+
console.error(`Error: unprepare-db: ${error.message}`);
|
|
1422
|
+
if (error.step) console.error(` Step: ${error.step}`);
|
|
1423
|
+
if (error.code) console.error(` Code: ${error.code}`);
|
|
1424
|
+
if (error.detail) console.error(` Detail: ${error.detail}`);
|
|
1425
|
+
if (error.hint) console.error(` Hint: ${error.hint}`);
|
|
1426
|
+
}
|
|
1427
|
+
process.exitCode = 1;
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1430
|
+
const shouldPrintSql = !!opts.printSql;
|
|
1431
|
+
const dropRole = !opts.keepRole;
|
|
1432
|
+
|
|
1433
|
+
// Validate provider and warn if unknown
|
|
1434
|
+
const providerWarning = validateProvider(opts.provider);
|
|
1435
|
+
if (providerWarning) {
|
|
1436
|
+
console.warn(`⚠ ${providerWarning}`);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// Offline mode: allow printing SQL without providing/using an admin connection.
|
|
1440
|
+
if (!conn && !opts.dbUrl && !opts.host && !opts.port && !opts.username && !opts.adminPassword) {
|
|
1441
|
+
if (shouldPrintSql) {
|
|
1442
|
+
const database = (opts.dbname ?? process.env.PGDATABASE ?? "postgres").trim();
|
|
1443
|
+
|
|
1444
|
+
const plan = await buildUninitPlan({
|
|
1445
|
+
database,
|
|
1446
|
+
monitoringUser: opts.monitoringUser,
|
|
1447
|
+
dropRole,
|
|
1448
|
+
provider: opts.provider,
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
console.log("\n--- SQL plan (offline; not connected) ---");
|
|
1452
|
+
console.log(`-- database: ${database}`);
|
|
1453
|
+
console.log(`-- monitoring user: ${opts.monitoringUser}`);
|
|
1454
|
+
console.log(`-- provider: ${opts.provider ?? "self-managed"}`);
|
|
1455
|
+
console.log(`-- drop role: ${dropRole}`);
|
|
1456
|
+
for (const step of plan.steps) {
|
|
1457
|
+
console.log(`\n-- ${step.name}`);
|
|
1458
|
+
console.log(step.sql);
|
|
1459
|
+
}
|
|
1460
|
+
console.log("\n--- end SQL plan ---\n");
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
let adminConn;
|
|
1466
|
+
try {
|
|
1467
|
+
adminConn = resolveAdminConnection({
|
|
1468
|
+
conn,
|
|
1469
|
+
dbUrlFlag: opts.dbUrl,
|
|
1470
|
+
host: opts.host ?? process.env.PGHOST,
|
|
1471
|
+
port: opts.port ?? process.env.PGPORT,
|
|
1472
|
+
username: opts.username ?? process.env.PGUSER,
|
|
1473
|
+
dbname: opts.dbname ?? process.env.PGDATABASE,
|
|
1474
|
+
adminPassword: opts.adminPassword,
|
|
1475
|
+
envPassword: process.env.PGPASSWORD,
|
|
1476
|
+
});
|
|
1477
|
+
} catch (e) {
|
|
1478
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1479
|
+
if (jsonOutput) {
|
|
1480
|
+
outputError({ message: msg });
|
|
1481
|
+
} else {
|
|
1482
|
+
console.error(`Error: unprepare-db: ${msg}`);
|
|
1483
|
+
if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
|
|
1484
|
+
console.error("");
|
|
1485
|
+
cmd.outputHelp({ error: true });
|
|
1486
|
+
}
|
|
1487
|
+
process.exitCode = 1;
|
|
1488
|
+
}
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
if (!jsonOutput) {
|
|
1493
|
+
console.log(`Connecting to: ${adminConn.display}`);
|
|
1494
|
+
console.log(`Monitoring user: ${opts.monitoringUser}`);
|
|
1495
|
+
console.log(`Drop role: ${dropRole}`);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// Confirmation prompt (unless --force or --json)
|
|
1499
|
+
if (!opts.force && !jsonOutput && !shouldPrintSql) {
|
|
1500
|
+
const answer = await new Promise<string>((resolve) => {
|
|
1501
|
+
const readline = getReadline();
|
|
1502
|
+
readline.question(
|
|
1503
|
+
`This will remove the monitoring setup for user "${opts.monitoringUser}"${dropRole ? " and drop the role" : ""}. Continue? [y/N] `,
|
|
1504
|
+
(ans) => resolve(ans.trim().toLowerCase())
|
|
1505
|
+
);
|
|
1506
|
+
});
|
|
1507
|
+
if (answer !== "y" && answer !== "yes") {
|
|
1508
|
+
console.log("Aborted.");
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
let client: Client | undefined;
|
|
1514
|
+
try {
|
|
1515
|
+
const connResult = await connectWithSslFallback(Client, adminConn);
|
|
1516
|
+
client = connResult.client;
|
|
1517
|
+
|
|
1518
|
+
const dbRes = await client.query("select current_database() as db");
|
|
1519
|
+
const database = dbRes.rows?.[0]?.db;
|
|
1520
|
+
if (typeof database !== "string" || !database) {
|
|
1521
|
+
throw new Error("Failed to resolve current database name");
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
const plan = await buildUninitPlan({
|
|
1525
|
+
database,
|
|
1526
|
+
monitoringUser: opts.monitoringUser,
|
|
1527
|
+
dropRole,
|
|
1528
|
+
provider: opts.provider,
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1531
|
+
if (shouldPrintSql) {
|
|
1532
|
+
console.log("\n--- SQL plan ---");
|
|
1533
|
+
for (const step of plan.steps) {
|
|
1534
|
+
console.log(`\n-- ${step.name}`);
|
|
1535
|
+
console.log(step.sql);
|
|
1536
|
+
}
|
|
1537
|
+
console.log("\n--- end SQL plan ---\n");
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
const { applied, errors } = await applyUninitPlan({ client, plan });
|
|
1542
|
+
|
|
1543
|
+
if (jsonOutput) {
|
|
1544
|
+
outputJson({
|
|
1545
|
+
success: errors.length === 0,
|
|
1546
|
+
action: "unprepare",
|
|
1547
|
+
database,
|
|
1548
|
+
monitoringUser: opts.monitoringUser,
|
|
1549
|
+
dropRole,
|
|
1550
|
+
applied,
|
|
1551
|
+
errors,
|
|
1552
|
+
});
|
|
1553
|
+
if (errors.length > 0) {
|
|
1554
|
+
process.exitCode = 1;
|
|
1555
|
+
}
|
|
1556
|
+
} else {
|
|
1557
|
+
if (errors.length === 0) {
|
|
1558
|
+
console.log("✓ unprepare-db completed");
|
|
1559
|
+
console.log(`Applied ${applied.length} steps`);
|
|
1560
|
+
} else {
|
|
1561
|
+
console.log("⚠ unprepare-db completed with errors");
|
|
1562
|
+
console.log(`Applied ${applied.length} steps`);
|
|
1563
|
+
console.log("Errors:");
|
|
1564
|
+
for (const err of errors) {
|
|
1565
|
+
console.log(` - ${err}`);
|
|
1566
|
+
}
|
|
1567
|
+
process.exitCode = 1;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
} catch (error) {
|
|
1571
|
+
const errAny = error as any;
|
|
1572
|
+
let message = "";
|
|
1573
|
+
if (error instanceof Error && error.message) {
|
|
1574
|
+
message = error.message;
|
|
1575
|
+
} else if (errAny && typeof errAny === "object" && typeof errAny.message === "string" && errAny.message) {
|
|
1576
|
+
message = errAny.message;
|
|
1577
|
+
} else {
|
|
1578
|
+
message = String(error);
|
|
1579
|
+
}
|
|
1580
|
+
if (!message || message === "[object Object]") {
|
|
1581
|
+
message = "Unknown error";
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
const errorObj: {
|
|
1585
|
+
message: string;
|
|
1586
|
+
code?: string;
|
|
1587
|
+
detail?: string;
|
|
1588
|
+
hint?: string;
|
|
1589
|
+
} = { message };
|
|
1590
|
+
|
|
1591
|
+
if (errAny && typeof errAny === "object") {
|
|
1592
|
+
if (typeof errAny.code === "string" && errAny.code) errorObj.code = errAny.code;
|
|
1593
|
+
if (typeof errAny.detail === "string" && errAny.detail) errorObj.detail = errAny.detail;
|
|
1594
|
+
if (typeof errAny.hint === "string" && errAny.hint) errorObj.hint = errAny.hint;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
if (jsonOutput) {
|
|
1598
|
+
outputJson({
|
|
1599
|
+
success: false,
|
|
1600
|
+
error: errorObj,
|
|
1601
|
+
});
|
|
1602
|
+
process.exitCode = 1;
|
|
1603
|
+
} else {
|
|
1604
|
+
console.error(`Error: unprepare-db: ${message}`);
|
|
1605
|
+
if (errAny && typeof errAny === "object") {
|
|
1606
|
+
if (typeof errAny.code === "string" && errAny.code) {
|
|
1607
|
+
console.error(` Code: ${errAny.code}`);
|
|
1608
|
+
}
|
|
1609
|
+
if (typeof errAny.detail === "string" && errAny.detail) {
|
|
1610
|
+
console.error(` Detail: ${errAny.detail}`);
|
|
1611
|
+
}
|
|
1612
|
+
if (typeof errAny.hint === "string" && errAny.hint) {
|
|
1613
|
+
console.error(` Hint: ${errAny.hint}`);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
|
|
1617
|
+
if (errAny.code === "42501") {
|
|
1618
|
+
console.error(" Context: dropping roles/objects requires sufficient privileges");
|
|
1619
|
+
console.error(" Fix: connect as a superuser (or a role with appropriate DROP privileges)");
|
|
1620
|
+
}
|
|
1315
1621
|
if (errAny.code === "ECONNREFUSED") {
|
|
1316
1622
|
console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
|
|
1317
1623
|
}
|
|
@@ -1332,13 +1638,14 @@ program
|
|
|
1332
1638
|
// ignore
|
|
1333
1639
|
}
|
|
1334
1640
|
}
|
|
1641
|
+
closeReadline();
|
|
1335
1642
|
}
|
|
1336
1643
|
});
|
|
1337
1644
|
|
|
1338
1645
|
program
|
|
1339
1646
|
.command("checkup [conn]")
|
|
1340
1647
|
.description("generate health check reports directly from PostgreSQL (express mode)")
|
|
1341
|
-
.option("--check-id <id>", `specific check to run
|
|
1648
|
+
.option("--check-id <id>", `specific check to run (see list below), or ALL`, "ALL")
|
|
1342
1649
|
.option("--node-name <name>", "node name for reports", "node-01")
|
|
1343
1650
|
.option("--output <path>", "output directory for JSON files")
|
|
1344
1651
|
.option("--[no-]upload", "upload JSON results to PostgresAI (default: enabled; requires API key)", undefined)
|
|
@@ -1356,11 +1663,9 @@ program
|
|
|
1356
1663
|
"",
|
|
1357
1664
|
"Examples:",
|
|
1358
1665
|
" postgresai checkup postgresql://user:pass@host:5432/db",
|
|
1359
|
-
" postgresai checkup postgresql://user:pass@host:5432/db --check-id
|
|
1666
|
+
" postgresai checkup postgresql://user:pass@host:5432/db --check-id D001",
|
|
1360
1667
|
" postgresai checkup postgresql://user:pass@host:5432/db --output ./reports",
|
|
1361
1668
|
" postgresai checkup postgresql://user:pass@host:5432/db --project my_project",
|
|
1362
|
-
" postgresai set-default-project my_project",
|
|
1363
|
-
" postgresai checkup postgresql://user:pass@host:5432/db",
|
|
1364
1669
|
" postgresai checkup postgresql://user:pass@host:5432/db --no-upload --json",
|
|
1365
1670
|
].join("\n")
|
|
1366
1671
|
)
|
|
@@ -1419,8 +1724,15 @@ program
|
|
|
1419
1724
|
const generator = REPORT_GENERATORS[checkId];
|
|
1420
1725
|
if (!generator) {
|
|
1421
1726
|
spinner.stop();
|
|
1422
|
-
|
|
1423
|
-
|
|
1727
|
+
// Check if it's a valid check ID from the dictionary (just not implemented in express mode)
|
|
1728
|
+
const dictEntry = getCheckupEntry(checkId);
|
|
1729
|
+
if (dictEntry) {
|
|
1730
|
+
console.error(`Check ${checkId} (${dictEntry.title}) is not yet available in express mode.`);
|
|
1731
|
+
console.error(`Express-mode checks: ${Object.keys(CHECK_INFO).join(", ")}`);
|
|
1732
|
+
} else {
|
|
1733
|
+
console.error(`Unknown check ID: ${opts.checkId}`);
|
|
1734
|
+
console.error(`See 'postgresai checkup --help' for available checks.`);
|
|
1735
|
+
}
|
|
1424
1736
|
process.exitCode = 1;
|
|
1425
1737
|
return;
|
|
1426
1738
|
}
|
|
@@ -1518,7 +1830,7 @@ async function resolveOrInitPaths(): Promise<PathResolution> {
|
|
|
1518
1830
|
*/
|
|
1519
1831
|
function isDockerRunning(): boolean {
|
|
1520
1832
|
try {
|
|
1521
|
-
const result = spawnSync("docker", ["info"], { stdio: "pipe" });
|
|
1833
|
+
const result = spawnSync("docker", ["info"], { stdio: "pipe", timeout: 5000 });
|
|
1522
1834
|
return result.status === 0;
|
|
1523
1835
|
} catch {
|
|
1524
1836
|
return false;
|
|
@@ -1530,7 +1842,7 @@ function isDockerRunning(): boolean {
|
|
|
1530
1842
|
*/
|
|
1531
1843
|
function getComposeCmd(): string[] | null {
|
|
1532
1844
|
const tryCmd = (cmd: string, args: string[]): boolean =>
|
|
1533
|
-
spawnSync(cmd, args, { stdio: "ignore" }).status === 0;
|
|
1845
|
+
spawnSync(cmd, args, { stdio: "ignore", timeout: 5000 }).status === 0;
|
|
1534
1846
|
if (tryCmd("docker-compose", ["version"])) return ["docker-compose"];
|
|
1535
1847
|
if (tryCmd("docker", ["compose", "version"])) return ["docker", "compose"];
|
|
1536
1848
|
return null;
|
|
@@ -1544,7 +1856,7 @@ function checkRunningContainers(): { running: boolean; containers: string[] } {
|
|
|
1544
1856
|
const result = spawnSync(
|
|
1545
1857
|
"docker",
|
|
1546
1858
|
["ps", "--filter", "name=grafana-with-datasources", "--filter", "name=pgwatch", "--format", "{{.Names}}"],
|
|
1547
|
-
{ stdio: "pipe", encoding: "utf8" }
|
|
1859
|
+
{ stdio: "pipe", encoding: "utf8", timeout: 5000 }
|
|
1548
1860
|
);
|
|
1549
1861
|
|
|
1550
1862
|
if (result.status === 0 && result.stdout) {
|