postgresai 0.14.0-dev.14 → 0.14.0-dev.16
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 +20 -13
- package/bin/postgres-ai.ts +37 -39
- package/dist/bin/postgres-ai.js +37 -37
- package/dist/bin/postgres-ai.js.map +1 -1
- package/dist/lib/init.d.ts +4 -2
- package/dist/lib/init.d.ts.map +1 -1
- package/dist/lib/init.js +162 -104
- package/dist/lib/init.js.map +1 -1
- package/dist/package.json +1 -1
- package/lib/init.ts +178 -125
- package/package.json +1 -1
- package/sql/01.role.sql +8 -7
- package/test/init.integration.test.cjs +35 -21
- package/test/init.test.cjs +192 -23
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ Or install the latest beta release explicitly:
|
|
|
15
15
|
npm install -g postgresai@beta
|
|
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
|
+
|
|
18
20
|
### From Homebrew (macOS)
|
|
19
21
|
|
|
20
22
|
```bash
|
|
@@ -31,7 +33,12 @@ The CLI provides three command aliases:
|
|
|
31
33
|
```bash
|
|
32
34
|
postgres-ai --help
|
|
33
35
|
postgresai --help
|
|
34
|
-
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
You can also run it without installing via `npx`:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx postgresai --help
|
|
35
42
|
```
|
|
36
43
|
|
|
37
44
|
## init (create monitoring user in Postgres)
|
|
@@ -98,7 +105,7 @@ npx postgresai init postgresql://admin@host:5432/dbname --reset-password --passw
|
|
|
98
105
|
|
|
99
106
|
Authenticate via browser to obtain API key:
|
|
100
107
|
```bash
|
|
101
|
-
|
|
108
|
+
postgresai auth
|
|
102
109
|
```
|
|
103
110
|
|
|
104
111
|
This will:
|
|
@@ -180,7 +187,7 @@ postgres-ai mon shell <service> # Open shell to monitoring servic
|
|
|
180
187
|
### MCP server (`mcp` group)
|
|
181
188
|
|
|
182
189
|
```bash
|
|
183
|
-
|
|
190
|
+
postgresai mcp start # Start MCP stdio server exposing tools
|
|
184
191
|
```
|
|
185
192
|
|
|
186
193
|
Cursor configuration example (Settings → MCP):
|
|
@@ -189,7 +196,7 @@ Cursor configuration example (Settings → MCP):
|
|
|
189
196
|
{
|
|
190
197
|
"mcpServers": {
|
|
191
198
|
"PostgresAI": {
|
|
192
|
-
"command": "
|
|
199
|
+
"command": "postgresai",
|
|
193
200
|
"args": ["mcp", "start"],
|
|
194
201
|
"env": {
|
|
195
202
|
"PGAI_API_BASE_URL": "https://postgres.ai/api/general/"
|
|
@@ -200,16 +207,16 @@ Cursor configuration example (Settings → MCP):
|
|
|
200
207
|
```
|
|
201
208
|
|
|
202
209
|
Tools exposed:
|
|
203
|
-
- list_issues: returns the same JSON as `
|
|
210
|
+
- list_issues: returns the same JSON as `postgresai issues list`.
|
|
204
211
|
- view_issue: view a single issue with its comments (args: { issue_id, debug? })
|
|
205
212
|
- post_issue_comment: post a comment (args: { issue_id, content, parent_comment_id?, debug? })
|
|
206
213
|
|
|
207
214
|
### Issues management (`issues` group)
|
|
208
215
|
|
|
209
216
|
```bash
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
postgresai issues list # List issues (shows: id, title, status, created_at)
|
|
218
|
+
postgresai issues view <issueId> # View issue details and comments
|
|
219
|
+
postgresai issues post_comment <issueId> <content> # Post a comment to an issue
|
|
213
220
|
# Options:
|
|
214
221
|
# --parent <uuid> Parent comment ID (for replies)
|
|
215
222
|
# --debug Enable debug output
|
|
@@ -223,13 +230,13 @@ By default, issues commands print human-friendly YAML when writing to a terminal
|
|
|
223
230
|
- Use `--json` to force JSON output:
|
|
224
231
|
|
|
225
232
|
```bash
|
|
226
|
-
|
|
233
|
+
postgresai issues list --json | jq '.[] | {id, title}'
|
|
227
234
|
```
|
|
228
235
|
|
|
229
236
|
- Rely on auto-detection: when stdout is not a TTY (e.g., piped or redirected), output is JSON automatically:
|
|
230
237
|
|
|
231
238
|
```bash
|
|
232
|
-
|
|
239
|
+
postgresai issues view <issueId> > issue.json
|
|
233
240
|
```
|
|
234
241
|
|
|
235
242
|
#### Grafana management
|
|
@@ -293,7 +300,7 @@ Linux/macOS (bash/zsh):
|
|
|
293
300
|
```bash
|
|
294
301
|
export PGAI_API_BASE_URL=https://v2.postgres.ai/api/general/
|
|
295
302
|
export PGAI_UI_BASE_URL=https://console-dev.postgres.ai
|
|
296
|
-
|
|
303
|
+
postgresai auth --debug
|
|
297
304
|
```
|
|
298
305
|
|
|
299
306
|
Windows PowerShell:
|
|
@@ -301,13 +308,13 @@ Windows PowerShell:
|
|
|
301
308
|
```powershell
|
|
302
309
|
$env:PGAI_API_BASE_URL = "https://v2.postgres.ai/api/general/"
|
|
303
310
|
$env:PGAI_UI_BASE_URL = "https://console-dev.postgres.ai"
|
|
304
|
-
|
|
311
|
+
postgresai auth --debug
|
|
305
312
|
```
|
|
306
313
|
|
|
307
314
|
Via CLI options (overrides env):
|
|
308
315
|
|
|
309
316
|
```bash
|
|
310
|
-
|
|
317
|
+
postgresai auth --debug \
|
|
311
318
|
--api-base-url https://v2.postgres.ai/api/general/ \
|
|
312
319
|
--ui-base-url https://console-dev.postgres.ai
|
|
313
320
|
```
|
package/bin/postgres-ai.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { Client } from "pg";
|
|
|
16
16
|
import { startMcpServer } from "../lib/mcp-server";
|
|
17
17
|
import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue } from "../lib/issues";
|
|
18
18
|
import { resolveBaseUrls } from "../lib/util";
|
|
19
|
-
import { applyInitPlan, buildInitPlan, resolveAdminConnection, resolveMonitoringPassword, verifyInitSetup } from "../lib/init";
|
|
19
|
+
import { applyInitPlan, buildInitPlan, DEFAULT_MONITORING_USER, redactPasswordsInSql, resolveAdminConnection, resolveMonitoringPassword, verifyInitSetup } from "../lib/init";
|
|
20
20
|
|
|
21
21
|
const execPromise = promisify(exec);
|
|
22
22
|
const execFilePromise = promisify(execFile);
|
|
@@ -127,7 +127,7 @@ program
|
|
|
127
127
|
.option("-U, --username <username>", "PostgreSQL user (psql-like)")
|
|
128
128
|
.option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)")
|
|
129
129
|
.option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)")
|
|
130
|
-
.option("--monitoring-user <name>", "Monitoring role name to create/update",
|
|
130
|
+
.option("--monitoring-user <name>", "Monitoring role name to create/update", DEFAULT_MONITORING_USER)
|
|
131
131
|
.option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)")
|
|
132
132
|
.option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false)
|
|
133
133
|
.option("--verify", "Verify that monitoring role/permissions are in place (no changes)", false)
|
|
@@ -152,6 +152,11 @@ program
|
|
|
152
152
|
" If auto-generated, it is printed only on TTY by default.",
|
|
153
153
|
" To print it in non-interactive mode: --print-password",
|
|
154
154
|
"",
|
|
155
|
+
"Environment variables (libpq standard):",
|
|
156
|
+
" PGHOST, PGPORT, PGUSER, PGDATABASE — connection defaults",
|
|
157
|
+
" PGPASSWORD — admin password",
|
|
158
|
+
" PGAI_MON_PASSWORD — monitoring password",
|
|
159
|
+
"",
|
|
155
160
|
"Inspect SQL without applying changes:",
|
|
156
161
|
" postgresai init <conn> --print-sql",
|
|
157
162
|
"",
|
|
@@ -197,7 +202,7 @@ program
|
|
|
197
202
|
const redactPasswords = (sql: string): string => {
|
|
198
203
|
if (!shouldRedactSecrets) return sql;
|
|
199
204
|
// Replace PASSWORD '<literal>' (handles doubled quotes inside).
|
|
200
|
-
return sql
|
|
205
|
+
return redactPasswordsInSql(sql);
|
|
201
206
|
};
|
|
202
207
|
|
|
203
208
|
// Offline mode: allow printing SQL without providing/using an admin connection.
|
|
@@ -216,7 +221,6 @@ program
|
|
|
216
221
|
monitoringUser: opts.monitoringUser,
|
|
217
222
|
monitoringPassword: monPassword,
|
|
218
223
|
includeOptionalPermissions,
|
|
219
|
-
roleExists: undefined,
|
|
220
224
|
});
|
|
221
225
|
|
|
222
226
|
console.log("\n--- SQL plan (offline; not connected) ---");
|
|
@@ -250,7 +254,7 @@ program
|
|
|
250
254
|
});
|
|
251
255
|
} catch (e) {
|
|
252
256
|
const msg = e instanceof Error ? e.message : String(e);
|
|
253
|
-
console.error(
|
|
257
|
+
console.error(`Error: init: ${msg}`);
|
|
254
258
|
// When connection details are missing, show full init help (options + examples).
|
|
255
259
|
if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
|
|
256
260
|
console.error("");
|
|
@@ -267,16 +271,11 @@ program
|
|
|
267
271
|
console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
|
|
268
272
|
|
|
269
273
|
// Use native pg client instead of requiring psql to be installed
|
|
270
|
-
|
|
271
|
-
|
|
274
|
+
let client: Client | undefined;
|
|
272
275
|
try {
|
|
276
|
+
client = new Client(adminConn.clientConfig);
|
|
273
277
|
await client.connect();
|
|
274
278
|
|
|
275
|
-
const roleRes = await client.query("select 1 from pg_catalog.pg_roles where rolname = $1", [
|
|
276
|
-
opts.monitoringUser,
|
|
277
|
-
]);
|
|
278
|
-
const roleExists = (roleRes.rowCount ?? 0) > 0;
|
|
279
|
-
|
|
280
279
|
const dbRes = await client.query("select current_database() as db");
|
|
281
280
|
const database = dbRes.rows?.[0]?.db;
|
|
282
281
|
if (typeof database !== "string" || !database) {
|
|
@@ -319,10 +318,13 @@ program
|
|
|
319
318
|
if (resolved.generated) {
|
|
320
319
|
const canPrint = process.stdout.isTTY || !!opts.printPassword;
|
|
321
320
|
if (canPrint) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
console.
|
|
325
|
-
console.
|
|
321
|
+
// Print secrets to stderr to reduce the chance they end up in piped stdout logs.
|
|
322
|
+
const shellSafe = monPassword.replace(/'/g, "'\\''");
|
|
323
|
+
console.error("");
|
|
324
|
+
console.error(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
|
|
325
|
+
// Quote for shell copy/paste safety.
|
|
326
|
+
console.error(`PGAI_MON_PASSWORD='${shellSafe}'`);
|
|
327
|
+
console.error("");
|
|
326
328
|
console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
|
|
327
329
|
} else {
|
|
328
330
|
console.error(
|
|
@@ -352,7 +354,6 @@ program
|
|
|
352
354
|
monitoringUser: opts.monitoringUser,
|
|
353
355
|
monitoringPassword: monPassword,
|
|
354
356
|
includeOptionalPermissions,
|
|
355
|
-
roleExists,
|
|
356
357
|
});
|
|
357
358
|
|
|
358
359
|
const effectivePlan = opts.resetPassword
|
|
@@ -396,57 +397,54 @@ program
|
|
|
396
397
|
if (!message || message === "[object Object]") {
|
|
397
398
|
message = "Unknown error";
|
|
398
399
|
}
|
|
399
|
-
console.error(
|
|
400
|
+
console.error(`Error: init: ${message}`);
|
|
400
401
|
// If this was a plan step failure, surface the step name explicitly to help users diagnose quickly.
|
|
401
402
|
const stepMatch =
|
|
402
403
|
typeof message === "string" ? message.match(/Failed at step "([^"]+)":/i) : null;
|
|
403
404
|
const failedStep = stepMatch?.[1];
|
|
404
405
|
if (failedStep) {
|
|
405
|
-
console.error(`Step: ${failedStep}`);
|
|
406
|
+
console.error(` Step: ${failedStep}`);
|
|
406
407
|
}
|
|
407
408
|
if (errAny && typeof errAny === "object") {
|
|
408
409
|
if (typeof errAny.code === "string" && errAny.code) {
|
|
409
|
-
console.error(`
|
|
410
|
+
console.error(` Code: ${errAny.code}`);
|
|
410
411
|
}
|
|
411
412
|
if (typeof errAny.detail === "string" && errAny.detail) {
|
|
412
|
-
console.error(`Detail: ${errAny.detail}`);
|
|
413
|
+
console.error(` Detail: ${errAny.detail}`);
|
|
413
414
|
}
|
|
414
415
|
if (typeof errAny.hint === "string" && errAny.hint) {
|
|
415
|
-
console.error(`Hint: ${errAny.hint}`);
|
|
416
|
+
console.error(` Hint: ${errAny.hint}`);
|
|
416
417
|
}
|
|
417
418
|
}
|
|
418
419
|
if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
|
|
419
420
|
if (errAny.code === "42501") {
|
|
420
|
-
console.error("");
|
|
421
|
-
console.error("Permission error: your admin connection is not allowed to complete the setup.");
|
|
422
421
|
if (failedStep === "01.role") {
|
|
423
|
-
console.error("
|
|
422
|
+
console.error(" Context: role creation/update requires CREATEROLE or superuser");
|
|
424
423
|
} else if (failedStep === "02.permissions") {
|
|
425
|
-
console.error("
|
|
424
|
+
console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
|
|
426
425
|
}
|
|
427
|
-
console.error("
|
|
428
|
-
console.error("
|
|
429
|
-
console.error("
|
|
430
|
-
console.error("Tip: run with --print-sql to review the exact SQL plan.");
|
|
431
|
-
console.error("");
|
|
432
|
-
console.error("Hint: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges).");
|
|
426
|
+
console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
|
|
427
|
+
console.error(" Fix: on managed Postgres, use the provider's admin/master user");
|
|
428
|
+
console.error(" Tip: run with --print-sql to review the exact SQL plan");
|
|
433
429
|
}
|
|
434
430
|
if (errAny.code === "ECONNREFUSED") {
|
|
435
|
-
console.error("Hint: check host/port and ensure Postgres is reachable from this machine
|
|
431
|
+
console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
|
|
436
432
|
}
|
|
437
433
|
if (errAny.code === "ENOTFOUND") {
|
|
438
|
-
console.error("Hint: DNS resolution failed; double-check the host name
|
|
434
|
+
console.error(" Hint: DNS resolution failed; double-check the host name");
|
|
439
435
|
}
|
|
440
436
|
if (errAny.code === "ETIMEDOUT") {
|
|
441
|
-
console.error("Hint: connection timed out; check network/firewall rules
|
|
437
|
+
console.error(" Hint: connection timed out; check network/firewall rules");
|
|
442
438
|
}
|
|
443
439
|
}
|
|
444
440
|
process.exitCode = 1;
|
|
445
441
|
} finally {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
442
|
+
if (client) {
|
|
443
|
+
try {
|
|
444
|
+
await client.end();
|
|
445
|
+
} catch {
|
|
446
|
+
// ignore
|
|
447
|
+
}
|
|
450
448
|
}
|
|
451
449
|
}
|
|
452
450
|
});
|
package/dist/bin/postgres-ai.js
CHANGED
|
@@ -109,7 +109,7 @@ program
|
|
|
109
109
|
.option("-U, --username <username>", "PostgreSQL user (psql-like)")
|
|
110
110
|
.option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)")
|
|
111
111
|
.option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)")
|
|
112
|
-
.option("--monitoring-user <name>", "Monitoring role name to create/update",
|
|
112
|
+
.option("--monitoring-user <name>", "Monitoring role name to create/update", init_1.DEFAULT_MONITORING_USER)
|
|
113
113
|
.option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)")
|
|
114
114
|
.option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false)
|
|
115
115
|
.option("--verify", "Verify that monitoring role/permissions are in place (no changes)", false)
|
|
@@ -132,6 +132,11 @@ program
|
|
|
132
132
|
" If auto-generated, it is printed only on TTY by default.",
|
|
133
133
|
" To print it in non-interactive mode: --print-password",
|
|
134
134
|
"",
|
|
135
|
+
"Environment variables (libpq standard):",
|
|
136
|
+
" PGHOST, PGPORT, PGUSER, PGDATABASE — connection defaults",
|
|
137
|
+
" PGPASSWORD — admin password",
|
|
138
|
+
" PGAI_MON_PASSWORD — monitoring password",
|
|
139
|
+
"",
|
|
135
140
|
"Inspect SQL without applying changes:",
|
|
136
141
|
" postgresai init <conn> --print-sql",
|
|
137
142
|
"",
|
|
@@ -161,7 +166,7 @@ program
|
|
|
161
166
|
if (!shouldRedactSecrets)
|
|
162
167
|
return sql;
|
|
163
168
|
// Replace PASSWORD '<literal>' (handles doubled quotes inside).
|
|
164
|
-
return
|
|
169
|
+
return (0, init_1.redactPasswordsInSql)(sql);
|
|
165
170
|
};
|
|
166
171
|
// Offline mode: allow printing SQL without providing/using an admin connection.
|
|
167
172
|
// Useful for audits/reviews; caller can provide -d/PGDATABASE and an explicit monitoring password.
|
|
@@ -176,7 +181,6 @@ program
|
|
|
176
181
|
monitoringUser: opts.monitoringUser,
|
|
177
182
|
monitoringPassword: monPassword,
|
|
178
183
|
includeOptionalPermissions,
|
|
179
|
-
roleExists: undefined,
|
|
180
184
|
});
|
|
181
185
|
console.log("\n--- SQL plan (offline; not connected) ---");
|
|
182
186
|
console.log(`-- database: ${database}`);
|
|
@@ -209,7 +213,7 @@ program
|
|
|
209
213
|
}
|
|
210
214
|
catch (e) {
|
|
211
215
|
const msg = e instanceof Error ? e.message : String(e);
|
|
212
|
-
console.error(
|
|
216
|
+
console.error(`Error: init: ${msg}`);
|
|
213
217
|
// When connection details are missing, show full init help (options + examples).
|
|
214
218
|
if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
|
|
215
219
|
console.error("");
|
|
@@ -223,13 +227,10 @@ program
|
|
|
223
227
|
console.log(`Monitoring user: ${opts.monitoringUser}`);
|
|
224
228
|
console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
|
|
225
229
|
// Use native pg client instead of requiring psql to be installed
|
|
226
|
-
|
|
230
|
+
let client;
|
|
227
231
|
try {
|
|
232
|
+
client = new pg_1.Client(adminConn.clientConfig);
|
|
228
233
|
await client.connect();
|
|
229
|
-
const roleRes = await client.query("select 1 from pg_catalog.pg_roles where rolname = $1", [
|
|
230
|
-
opts.monitoringUser,
|
|
231
|
-
]);
|
|
232
|
-
const roleExists = (roleRes.rowCount ?? 0) > 0;
|
|
233
234
|
const dbRes = await client.query("select current_database() as db");
|
|
234
235
|
const database = dbRes.rows?.[0]?.db;
|
|
235
236
|
if (typeof database !== "string" || !database) {
|
|
@@ -273,10 +274,13 @@ program
|
|
|
273
274
|
if (resolved.generated) {
|
|
274
275
|
const canPrint = process.stdout.isTTY || !!opts.printPassword;
|
|
275
276
|
if (canPrint) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
console.
|
|
279
|
-
console.
|
|
277
|
+
// Print secrets to stderr to reduce the chance they end up in piped stdout logs.
|
|
278
|
+
const shellSafe = monPassword.replace(/'/g, "'\\''");
|
|
279
|
+
console.error("");
|
|
280
|
+
console.error(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
|
|
281
|
+
// Quote for shell copy/paste safety.
|
|
282
|
+
console.error(`PGAI_MON_PASSWORD='${shellSafe}'`);
|
|
283
|
+
console.error("");
|
|
280
284
|
console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
|
|
281
285
|
}
|
|
282
286
|
else {
|
|
@@ -305,7 +309,6 @@ program
|
|
|
305
309
|
monitoringUser: opts.monitoringUser,
|
|
306
310
|
monitoringPassword: monPassword,
|
|
307
311
|
includeOptionalPermissions,
|
|
308
|
-
roleExists,
|
|
309
312
|
});
|
|
310
313
|
const effectivePlan = opts.resetPassword
|
|
311
314
|
? { ...plan, steps: plan.steps.filter((s) => s.name === "01.role") }
|
|
@@ -349,59 +352,56 @@ program
|
|
|
349
352
|
if (!message || message === "[object Object]") {
|
|
350
353
|
message = "Unknown error";
|
|
351
354
|
}
|
|
352
|
-
console.error(
|
|
355
|
+
console.error(`Error: init: ${message}`);
|
|
353
356
|
// If this was a plan step failure, surface the step name explicitly to help users diagnose quickly.
|
|
354
357
|
const stepMatch = typeof message === "string" ? message.match(/Failed at step "([^"]+)":/i) : null;
|
|
355
358
|
const failedStep = stepMatch?.[1];
|
|
356
359
|
if (failedStep) {
|
|
357
|
-
console.error(`Step: ${failedStep}`);
|
|
360
|
+
console.error(` Step: ${failedStep}`);
|
|
358
361
|
}
|
|
359
362
|
if (errAny && typeof errAny === "object") {
|
|
360
363
|
if (typeof errAny.code === "string" && errAny.code) {
|
|
361
|
-
console.error(`
|
|
364
|
+
console.error(` Code: ${errAny.code}`);
|
|
362
365
|
}
|
|
363
366
|
if (typeof errAny.detail === "string" && errAny.detail) {
|
|
364
|
-
console.error(`Detail: ${errAny.detail}`);
|
|
367
|
+
console.error(` Detail: ${errAny.detail}`);
|
|
365
368
|
}
|
|
366
369
|
if (typeof errAny.hint === "string" && errAny.hint) {
|
|
367
|
-
console.error(`Hint: ${errAny.hint}`);
|
|
370
|
+
console.error(` Hint: ${errAny.hint}`);
|
|
368
371
|
}
|
|
369
372
|
}
|
|
370
373
|
if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
|
|
371
374
|
if (errAny.code === "42501") {
|
|
372
|
-
console.error("");
|
|
373
|
-
console.error("Permission error: your admin connection is not allowed to complete the setup.");
|
|
374
375
|
if (failedStep === "01.role") {
|
|
375
|
-
console.error("
|
|
376
|
+
console.error(" Context: role creation/update requires CREATEROLE or superuser");
|
|
376
377
|
}
|
|
377
378
|
else if (failedStep === "02.permissions") {
|
|
378
|
-
console.error("
|
|
379
|
+
console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
|
|
379
380
|
}
|
|
380
|
-
console.error("
|
|
381
|
-
console.error("
|
|
382
|
-
console.error("
|
|
383
|
-
console.error("Tip: run with --print-sql to review the exact SQL plan.");
|
|
384
|
-
console.error("");
|
|
385
|
-
console.error("Hint: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges).");
|
|
381
|
+
console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
|
|
382
|
+
console.error(" Fix: on managed Postgres, use the provider's admin/master user");
|
|
383
|
+
console.error(" Tip: run with --print-sql to review the exact SQL plan");
|
|
386
384
|
}
|
|
387
385
|
if (errAny.code === "ECONNREFUSED") {
|
|
388
|
-
console.error("Hint: check host/port and ensure Postgres is reachable from this machine
|
|
386
|
+
console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
|
|
389
387
|
}
|
|
390
388
|
if (errAny.code === "ENOTFOUND") {
|
|
391
|
-
console.error("Hint: DNS resolution failed; double-check the host name
|
|
389
|
+
console.error(" Hint: DNS resolution failed; double-check the host name");
|
|
392
390
|
}
|
|
393
391
|
if (errAny.code === "ETIMEDOUT") {
|
|
394
|
-
console.error("Hint: connection timed out; check network/firewall rules
|
|
392
|
+
console.error(" Hint: connection timed out; check network/firewall rules");
|
|
395
393
|
}
|
|
396
394
|
}
|
|
397
395
|
process.exitCode = 1;
|
|
398
396
|
}
|
|
399
397
|
finally {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
398
|
+
if (client) {
|
|
399
|
+
try {
|
|
400
|
+
await client.end();
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
// ignore
|
|
404
|
+
}
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
407
|
});
|