postgresai 0.14.0-beta.3 → 0.14.0-dev.11
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/README.md +18 -41
- package/bin/postgres-ai.ts +51 -147
- package/dist/bin/postgres-ai.js +45 -139
- package/dist/bin/postgres-ai.js.map +1 -1
- package/dist/lib/init.d.ts +4 -15
- package/dist/lib/init.d.ts.map +1 -1
- package/dist/lib/init.js +94 -181
- package/dist/lib/init.js.map +1 -1
- package/dist/package.json +1 -1
- package/lib/init.ts +106 -215
- package/package.json +1 -1
- package/sql/01.role.sql +7 -8
- package/test/init.integration.test.cjs +18 -98
- package/test/init.test.cjs +22 -217
package/README.md
CHANGED
|
@@ -10,13 +10,11 @@ Command-line interface for PostgresAI monitoring and database management.
|
|
|
10
10
|
npm install -g postgresai
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Or install the latest
|
|
13
|
+
Or install the latest alpha release explicitly:
|
|
14
14
|
```bash
|
|
15
|
-
npm install -g postgresai@
|
|
15
|
+
npm install -g postgresai@alpha
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Note: in this repository, `cli/package.json` uses a placeholder version (`0.0.0-dev.0`). The real published version is set by the git tag in CI when publishing to npm.
|
|
19
|
-
|
|
20
18
|
### From Homebrew (macOS)
|
|
21
19
|
|
|
22
20
|
```bash
|
|
@@ -29,24 +27,11 @@ brew install postgresai
|
|
|
29
27
|
|
|
30
28
|
## Usage
|
|
31
29
|
|
|
32
|
-
The
|
|
30
|
+
The CLI provides three command aliases:
|
|
33
31
|
```bash
|
|
34
32
|
postgres-ai --help
|
|
35
33
|
postgresai --help
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
You can also run it without installing via `npx`:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npx postgresai --help
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Optional shorthand: `pgai`
|
|
45
|
-
|
|
46
|
-
If you want `npx pgai ...` as a shorthand for `npx postgresai ...`, install the separate `pgai` wrapper package:
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
npx pgai --help
|
|
34
|
+
pgai --help # short alias
|
|
50
35
|
```
|
|
51
36
|
|
|
52
37
|
## init (create monitoring user in Postgres)
|
|
@@ -93,18 +78,10 @@ To see what SQL would be executed (passwords redacted by default):
|
|
|
93
78
|
npx postgresai init postgresql://admin@host:5432/dbname --print-sql
|
|
94
79
|
```
|
|
95
80
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
Verify that everything is configured as expected (no changes):
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
npx postgresai init postgresql://admin@host:5432/dbname --verify
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Reset monitoring user password only (no other changes):
|
|
81
|
+
To print SQL and exit without applying anything:
|
|
105
82
|
|
|
106
83
|
```bash
|
|
107
|
-
npx postgresai init postgresql://admin@host:5432/dbname --
|
|
84
|
+
npx postgresai init postgresql://admin@host:5432/dbname --dry-run
|
|
108
85
|
```
|
|
109
86
|
|
|
110
87
|
## Quick start
|
|
@@ -113,7 +90,7 @@ npx postgresai init postgresql://admin@host:5432/dbname --reset-password --passw
|
|
|
113
90
|
|
|
114
91
|
Authenticate via browser to obtain API key:
|
|
115
92
|
```bash
|
|
116
|
-
|
|
93
|
+
pgai auth
|
|
117
94
|
```
|
|
118
95
|
|
|
119
96
|
This will:
|
|
@@ -195,7 +172,7 @@ postgres-ai mon shell <service> # Open shell to monitoring servic
|
|
|
195
172
|
### MCP server (`mcp` group)
|
|
196
173
|
|
|
197
174
|
```bash
|
|
198
|
-
|
|
175
|
+
pgai mcp start # Start MCP stdio server exposing tools
|
|
199
176
|
```
|
|
200
177
|
|
|
201
178
|
Cursor configuration example (Settings → MCP):
|
|
@@ -204,7 +181,7 @@ Cursor configuration example (Settings → MCP):
|
|
|
204
181
|
{
|
|
205
182
|
"mcpServers": {
|
|
206
183
|
"PostgresAI": {
|
|
207
|
-
"command": "
|
|
184
|
+
"command": "pgai",
|
|
208
185
|
"args": ["mcp", "start"],
|
|
209
186
|
"env": {
|
|
210
187
|
"PGAI_API_BASE_URL": "https://postgres.ai/api/general/"
|
|
@@ -215,16 +192,16 @@ Cursor configuration example (Settings → MCP):
|
|
|
215
192
|
```
|
|
216
193
|
|
|
217
194
|
Tools exposed:
|
|
218
|
-
- list_issues: returns the same JSON as `
|
|
195
|
+
- list_issues: returns the same JSON as `pgai issues list`.
|
|
219
196
|
- view_issue: view a single issue with its comments (args: { issue_id, debug? })
|
|
220
197
|
- post_issue_comment: post a comment (args: { issue_id, content, parent_comment_id?, debug? })
|
|
221
198
|
|
|
222
199
|
### Issues management (`issues` group)
|
|
223
200
|
|
|
224
201
|
```bash
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
202
|
+
pgai issues list # List issues (shows: id, title, status, created_at)
|
|
203
|
+
pgai issues view <issueId> # View issue details and comments
|
|
204
|
+
pgai issues post_comment <issueId> <content> # Post a comment to an issue
|
|
228
205
|
# Options:
|
|
229
206
|
# --parent <uuid> Parent comment ID (for replies)
|
|
230
207
|
# --debug Enable debug output
|
|
@@ -238,13 +215,13 @@ By default, issues commands print human-friendly YAML when writing to a terminal
|
|
|
238
215
|
- Use `--json` to force JSON output:
|
|
239
216
|
|
|
240
217
|
```bash
|
|
241
|
-
|
|
218
|
+
pgai issues list --json | jq '.[] | {id, title}'
|
|
242
219
|
```
|
|
243
220
|
|
|
244
221
|
- Rely on auto-detection: when stdout is not a TTY (e.g., piped or redirected), output is JSON automatically:
|
|
245
222
|
|
|
246
223
|
```bash
|
|
247
|
-
|
|
224
|
+
pgai issues view <issueId> > issue.json
|
|
248
225
|
```
|
|
249
226
|
|
|
250
227
|
#### Grafana management
|
|
@@ -308,7 +285,7 @@ Linux/macOS (bash/zsh):
|
|
|
308
285
|
```bash
|
|
309
286
|
export PGAI_API_BASE_URL=https://v2.postgres.ai/api/general/
|
|
310
287
|
export PGAI_UI_BASE_URL=https://console-dev.postgres.ai
|
|
311
|
-
|
|
288
|
+
pgai auth --debug
|
|
312
289
|
```
|
|
313
290
|
|
|
314
291
|
Windows PowerShell:
|
|
@@ -316,13 +293,13 @@ Windows PowerShell:
|
|
|
316
293
|
```powershell
|
|
317
294
|
$env:PGAI_API_BASE_URL = "https://v2.postgres.ai/api/general/"
|
|
318
295
|
$env:PGAI_UI_BASE_URL = "https://console-dev.postgres.ai"
|
|
319
|
-
|
|
296
|
+
pgai auth --debug
|
|
320
297
|
```
|
|
321
298
|
|
|
322
299
|
Via CLI options (overrides env):
|
|
323
300
|
|
|
324
301
|
```bash
|
|
325
|
-
|
|
302
|
+
pgai auth --debug \
|
|
326
303
|
--api-base-url https://v2.postgres.ai/api/general/ \
|
|
327
304
|
--ui-base-url https://console-dev.postgres.ai
|
|
328
305
|
```
|
package/bin/postgres-ai.ts
CHANGED
|
@@ -12,11 +12,10 @@ import { promisify } from "util";
|
|
|
12
12
|
import * as readline from "readline";
|
|
13
13
|
import * as http from "https";
|
|
14
14
|
import { URL } from "url";
|
|
15
|
-
import { Client } from "pg";
|
|
16
15
|
import { startMcpServer } from "../lib/mcp-server";
|
|
17
16
|
import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue } from "../lib/issues";
|
|
18
17
|
import { resolveBaseUrls } from "../lib/util";
|
|
19
|
-
import { applyInitPlan, buildInitPlan,
|
|
18
|
+
import { applyInitPlan, buildInitPlan, resolveAdminConnection, resolveMonitoringPassword } from "../lib/init";
|
|
20
19
|
|
|
21
20
|
const execPromise = promisify(exec);
|
|
22
21
|
const execFilePromise = promisify(execFile);
|
|
@@ -127,13 +126,13 @@ program
|
|
|
127
126
|
.option("-U, --username <username>", "PostgreSQL user (psql-like)")
|
|
128
127
|
.option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)")
|
|
129
128
|
.option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)")
|
|
130
|
-
.option("--monitoring-user <name>", "Monitoring role name to create/update",
|
|
129
|
+
.option("--monitoring-user <name>", "Monitoring role name to create/update", "postgres_ai_mon")
|
|
131
130
|
.option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)")
|
|
132
131
|
.option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false)
|
|
133
|
-
.option("--
|
|
134
|
-
.option("--
|
|
135
|
-
.option("--print-sql", "Print SQL plan and exit (no changes applied)", false)
|
|
132
|
+
.option("--print-sql", "Print SQL plan before applying (does not exit; use --dry-run to exit)", false)
|
|
133
|
+
.option("--show-secrets", "When printing SQL, do not redact secrets (DANGEROUS)", false)
|
|
136
134
|
.option("--print-password", "Print generated monitoring password (DANGEROUS in CI logs)", false)
|
|
135
|
+
.option("--dry-run", "Print SQL steps and exit without applying changes", false)
|
|
137
136
|
.addHelpText(
|
|
138
137
|
"after",
|
|
139
138
|
[
|
|
@@ -151,22 +150,8 @@ program
|
|
|
151
150
|
" If auto-generated, it is printed only on TTY by default.",
|
|
152
151
|
" To print it in non-interactive mode: --print-password",
|
|
153
152
|
"",
|
|
154
|
-
"Environment variables (libpq standard):",
|
|
155
|
-
" PGHOST, PGPORT, PGUSER, PGDATABASE — connection defaults",
|
|
156
|
-
" PGPASSWORD — admin password",
|
|
157
|
-
" PGAI_MON_PASSWORD — monitoring password",
|
|
158
|
-
"",
|
|
159
153
|
"Inspect SQL without applying changes:",
|
|
160
|
-
" postgresai init <conn> --
|
|
161
|
-
"",
|
|
162
|
-
"Verify setup (no changes):",
|
|
163
|
-
" postgresai init <conn> --verify",
|
|
164
|
-
"",
|
|
165
|
-
"Reset monitoring password only:",
|
|
166
|
-
" postgresai init <conn> --reset-password --password '...'",
|
|
167
|
-
"",
|
|
168
|
-
"Offline SQL plan (no DB connection):",
|
|
169
|
-
" postgresai init --print-sql",
|
|
154
|
+
" postgresai init <conn> --dry-run",
|
|
170
155
|
].join("\n")
|
|
171
156
|
)
|
|
172
157
|
.action(async (conn: string | undefined, opts: {
|
|
@@ -179,58 +164,11 @@ program
|
|
|
179
164
|
monitoringUser: string;
|
|
180
165
|
password?: string;
|
|
181
166
|
skipOptionalPermissions?: boolean;
|
|
182
|
-
verify?: boolean;
|
|
183
|
-
resetPassword?: boolean;
|
|
184
167
|
printSql?: boolean;
|
|
168
|
+
showSecrets?: boolean;
|
|
185
169
|
printPassword?: boolean;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
console.error("✗ Provide only one of --verify or --reset-password");
|
|
189
|
-
process.exitCode = 1;
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
if (opts.verify && opts.printSql) {
|
|
193
|
-
console.error("✗ --verify cannot be combined with --print-sql");
|
|
194
|
-
process.exitCode = 1;
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const shouldPrintSql = !!opts.printSql;
|
|
199
|
-
const redactPasswords = (sql: string): string => redactPasswordsInSql(sql);
|
|
200
|
-
|
|
201
|
-
// Offline mode: allow printing SQL without providing/using an admin connection.
|
|
202
|
-
// Useful for audits/reviews; caller can provide -d/PGDATABASE.
|
|
203
|
-
if (!conn && !opts.dbUrl && !opts.host && !opts.port && !opts.username && !opts.adminPassword) {
|
|
204
|
-
if (shouldPrintSql) {
|
|
205
|
-
const database = (opts.dbname ?? process.env.PGDATABASE ?? "postgres").trim();
|
|
206
|
-
const includeOptionalPermissions = !opts.skipOptionalPermissions;
|
|
207
|
-
|
|
208
|
-
// Use explicit password/env if provided; otherwise use a placeholder.
|
|
209
|
-
// Printed SQL always redacts secrets.
|
|
210
|
-
const monPassword =
|
|
211
|
-
(opts.password ?? process.env.PGAI_MON_PASSWORD ?? "<redacted>").toString();
|
|
212
|
-
|
|
213
|
-
const plan = await buildInitPlan({
|
|
214
|
-
database,
|
|
215
|
-
monitoringUser: opts.monitoringUser,
|
|
216
|
-
monitoringPassword: monPassword,
|
|
217
|
-
includeOptionalPermissions,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
console.log("\n--- SQL plan (offline; not connected) ---");
|
|
221
|
-
console.log(`-- database: ${database}`);
|
|
222
|
-
console.log(`-- monitoring user: ${opts.monitoringUser}`);
|
|
223
|
-
console.log(`-- optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
|
|
224
|
-
for (const step of plan.steps) {
|
|
225
|
-
console.log(`\n-- ${step.name}${step.optional ? " (optional)" : ""}`);
|
|
226
|
-
console.log(redactPasswords(step.sql));
|
|
227
|
-
}
|
|
228
|
-
console.log("\n--- end SQL plan ---\n");
|
|
229
|
-
console.log("Note: passwords are redacted in the printed SQL output.");
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
170
|
+
dryRun?: boolean;
|
|
171
|
+
}) => {
|
|
234
172
|
let adminConn;
|
|
235
173
|
try {
|
|
236
174
|
adminConn = resolveAdminConnection({
|
|
@@ -246,12 +184,7 @@ program
|
|
|
246
184
|
});
|
|
247
185
|
} catch (e) {
|
|
248
186
|
const msg = e instanceof Error ? e.message : String(e);
|
|
249
|
-
console.error(
|
|
250
|
-
// When connection details are missing, show full init help (options + examples).
|
|
251
|
-
if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
|
|
252
|
-
console.error("");
|
|
253
|
-
cmd.outputHelp({ error: true });
|
|
254
|
-
}
|
|
187
|
+
console.error(`✗ ${msg}`);
|
|
255
188
|
process.exitCode = 1;
|
|
256
189
|
return;
|
|
257
190
|
}
|
|
@@ -262,43 +195,26 @@ program
|
|
|
262
195
|
console.log(`Monitoring user: ${opts.monitoringUser}`);
|
|
263
196
|
console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
|
|
264
197
|
|
|
198
|
+
const shouldPrintSql = !!opts.printSql || !!opts.dryRun;
|
|
199
|
+
|
|
265
200
|
// Use native pg client instead of requiring psql to be installed
|
|
266
|
-
|
|
201
|
+
const { Client } = require("pg");
|
|
202
|
+
const client = new Client(adminConn.clientConfig);
|
|
203
|
+
|
|
267
204
|
try {
|
|
268
|
-
client = new Client(adminConn.clientConfig);
|
|
269
205
|
await client.connect();
|
|
270
206
|
|
|
207
|
+
const roleRes = await client.query("select 1 from pg_catalog.pg_roles where rolname = $1", [
|
|
208
|
+
opts.monitoringUser,
|
|
209
|
+
]);
|
|
210
|
+
const roleExists = roleRes.rowCount > 0;
|
|
211
|
+
|
|
271
212
|
const dbRes = await client.query("select current_database() as db");
|
|
272
213
|
const database = dbRes.rows?.[0]?.db;
|
|
273
214
|
if (typeof database !== "string" || !database) {
|
|
274
215
|
throw new Error("Failed to resolve current database name");
|
|
275
216
|
}
|
|
276
217
|
|
|
277
|
-
if (opts.verify) {
|
|
278
|
-
const v = await verifyInitSetup({
|
|
279
|
-
client,
|
|
280
|
-
database,
|
|
281
|
-
monitoringUser: opts.monitoringUser,
|
|
282
|
-
includeOptionalPermissions,
|
|
283
|
-
});
|
|
284
|
-
if (v.ok) {
|
|
285
|
-
console.log("✓ init verify: OK");
|
|
286
|
-
if (v.missingOptional.length > 0) {
|
|
287
|
-
console.log("⚠ Optional items missing:");
|
|
288
|
-
for (const m of v.missingOptional) console.log(`- ${m}`);
|
|
289
|
-
}
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
console.error("✗ init verify failed: missing required items");
|
|
293
|
-
for (const m of v.missingRequired) console.error(`- ${m}`);
|
|
294
|
-
if (v.missingOptional.length > 0) {
|
|
295
|
-
console.error("Optional items missing:");
|
|
296
|
-
for (const m of v.missingOptional) console.error(`- ${m}`);
|
|
297
|
-
}
|
|
298
|
-
process.exitCode = 1;
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
218
|
let monPassword: string;
|
|
303
219
|
try {
|
|
304
220
|
const resolved = await resolveMonitoringPassword({
|
|
@@ -310,13 +226,7 @@ program
|
|
|
310
226
|
if (resolved.generated) {
|
|
311
227
|
const canPrint = process.stdout.isTTY || !!opts.printPassword;
|
|
312
228
|
if (canPrint) {
|
|
313
|
-
|
|
314
|
-
const shellSafe = monPassword.replace(/'/g, "'\\''");
|
|
315
|
-
console.error("");
|
|
316
|
-
console.error(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
|
|
317
|
-
// Quote for shell copy/paste safety.
|
|
318
|
-
console.error(`PGAI_MON_PASSWORD='${shellSafe}'`);
|
|
319
|
-
console.error("");
|
|
229
|
+
console.log(`Generated password for monitoring user ${opts.monitoringUser}: ${monPassword}`);
|
|
320
230
|
console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
|
|
321
231
|
} else {
|
|
322
232
|
console.error(
|
|
@@ -346,26 +256,36 @@ program
|
|
|
346
256
|
monitoringUser: opts.monitoringUser,
|
|
347
257
|
monitoringPassword: monPassword,
|
|
348
258
|
includeOptionalPermissions,
|
|
259
|
+
roleExists,
|
|
349
260
|
});
|
|
350
261
|
|
|
351
|
-
const effectivePlan = opts.resetPassword
|
|
352
|
-
? { ...plan, steps: plan.steps.filter((s) => s.name === "01.role") }
|
|
353
|
-
: plan;
|
|
354
|
-
|
|
355
262
|
if (shouldPrintSql) {
|
|
263
|
+
const redact = !opts.showSecrets;
|
|
264
|
+
const redactPasswords = (sql: string): string => {
|
|
265
|
+
if (!redact) return sql;
|
|
266
|
+
// Replace PASSWORD '<literal>' (handles doubled quotes inside).
|
|
267
|
+
return sql.replace(/password\s+'(?:''|[^'])*'/gi, "password '<redacted>'");
|
|
268
|
+
};
|
|
269
|
+
|
|
356
270
|
console.log("\n--- SQL plan ---");
|
|
357
|
-
for (const step of
|
|
271
|
+
for (const step of plan.steps) {
|
|
358
272
|
console.log(`\n-- ${step.name}${step.optional ? " (optional)" : ""}`);
|
|
359
273
|
console.log(redactPasswords(step.sql));
|
|
360
274
|
}
|
|
361
275
|
console.log("\n--- end SQL plan ---\n");
|
|
362
|
-
|
|
276
|
+
if (redact) {
|
|
277
|
+
console.log("Note: passwords are redacted in the printed SQL (use --show-secrets to print them).");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (opts.dryRun) {
|
|
282
|
+
console.log("✓ dry-run completed (no changes were applied)");
|
|
363
283
|
return;
|
|
364
284
|
}
|
|
365
285
|
|
|
366
|
-
const { applied, skippedOptional } = await applyInitPlan({ client, plan
|
|
286
|
+
const { applied, skippedOptional } = await applyInitPlan({ client, plan });
|
|
367
287
|
|
|
368
|
-
console.log(
|
|
288
|
+
console.log("✓ init completed");
|
|
369
289
|
if (skippedOptional.length > 0) {
|
|
370
290
|
console.log("⚠ Some optional steps were skipped (not supported or insufficient privileges):");
|
|
371
291
|
for (const s of skippedOptional) console.log(`- ${s}`);
|
|
@@ -387,54 +307,38 @@ program
|
|
|
387
307
|
if (!message || message === "[object Object]") {
|
|
388
308
|
message = "Unknown error";
|
|
389
309
|
}
|
|
390
|
-
console.error(
|
|
391
|
-
// If this was a plan step failure, surface the step name explicitly to help users diagnose quickly.
|
|
392
|
-
const stepMatch =
|
|
393
|
-
typeof message === "string" ? message.match(/Failed at step "([^"]+)":/i) : null;
|
|
394
|
-
const failedStep = stepMatch?.[1];
|
|
395
|
-
if (failedStep) {
|
|
396
|
-
console.error(` Step: ${failedStep}`);
|
|
397
|
-
}
|
|
310
|
+
console.error(`✗ init failed: ${message}`);
|
|
398
311
|
if (errAny && typeof errAny === "object") {
|
|
399
312
|
if (typeof errAny.code === "string" && errAny.code) {
|
|
400
|
-
console.error(`
|
|
313
|
+
console.error(`Error code: ${errAny.code}`);
|
|
401
314
|
}
|
|
402
315
|
if (typeof errAny.detail === "string" && errAny.detail) {
|
|
403
|
-
console.error(`
|
|
316
|
+
console.error(`Detail: ${errAny.detail}`);
|
|
404
317
|
}
|
|
405
318
|
if (typeof errAny.hint === "string" && errAny.hint) {
|
|
406
|
-
console.error(`
|
|
319
|
+
console.error(`Hint: ${errAny.hint}`);
|
|
407
320
|
}
|
|
408
321
|
}
|
|
409
322
|
if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
|
|
410
323
|
if (errAny.code === "42501") {
|
|
411
|
-
|
|
412
|
-
console.error(" Context: role creation/update requires CREATEROLE or superuser");
|
|
413
|
-
} else if (failedStep === "02.permissions") {
|
|
414
|
-
console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
|
|
415
|
-
}
|
|
416
|
-
console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
|
|
417
|
-
console.error(" Fix: on managed Postgres, use the provider's admin/master user");
|
|
418
|
-
console.error(" Tip: run with --print-sql to review the exact SQL plan");
|
|
324
|
+
console.error("Hint: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges).");
|
|
419
325
|
}
|
|
420
326
|
if (errAny.code === "ECONNREFUSED") {
|
|
421
|
-
console.error("
|
|
327
|
+
console.error("Hint: check host/port and ensure Postgres is reachable from this machine.");
|
|
422
328
|
}
|
|
423
329
|
if (errAny.code === "ENOTFOUND") {
|
|
424
|
-
console.error("
|
|
330
|
+
console.error("Hint: DNS resolution failed; double-check the host name.");
|
|
425
331
|
}
|
|
426
332
|
if (errAny.code === "ETIMEDOUT") {
|
|
427
|
-
console.error("
|
|
333
|
+
console.error("Hint: connection timed out; check network/firewall rules.");
|
|
428
334
|
}
|
|
429
335
|
}
|
|
430
336
|
process.exitCode = 1;
|
|
431
337
|
} finally {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// ignore
|
|
437
|
-
}
|
|
338
|
+
try {
|
|
339
|
+
await client.end();
|
|
340
|
+
} catch {
|
|
341
|
+
// ignore
|
|
438
342
|
}
|
|
439
343
|
}
|
|
440
344
|
});
|