postgresai 0.14.0-dev.10 → 0.14.0-dev.12

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 CHANGED
@@ -59,7 +59,10 @@ npx postgresai init -h host -p 5432 -U admin -d dbname
59
59
  Password input options (in priority order):
60
60
  - `--password <password>`
61
61
  - `PGAI_MON_PASSWORD` environment variable
62
- - if not provided: a strong password is generated automatically and printed once
62
+ - if not provided: a strong password is generated automatically
63
+
64
+ By default, the generated password is printed **only in interactive (TTY) mode**. In non-interactive mode, you must either provide the password explicitly, or opt-in to printing it:
65
+ - `--print-password` (dangerous in CI logs)
63
66
 
64
67
  Optional permissions (RDS/self-managed extras from the root `README.md`) are enabled by default. To skip them:
65
68
 
@@ -75,10 +78,18 @@ To see what SQL would be executed (passwords redacted by default):
75
78
  npx postgresai init postgresql://admin@host:5432/dbname --print-sql
76
79
  ```
77
80
 
78
- To print SQL and exit without applying anything:
81
+ ### Verify and password reset
82
+
83
+ Verify that everything is configured as expected (no changes):
84
+
85
+ ```bash
86
+ npx postgresai init postgresql://admin@host:5432/dbname --verify
87
+ ```
88
+
89
+ Reset monitoring user password only (no other changes):
79
90
 
80
91
  ```bash
81
- npx postgresai init postgresql://admin@host:5432/dbname --dry-run
92
+ npx postgresai init postgresql://admin@host:5432/dbname --reset-password --password 'new_password'
82
93
  ```
83
94
 
84
95
  ## Quick start
@@ -12,10 +12,11 @@ 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";
15
16
  import { startMcpServer } from "../lib/mcp-server";
16
17
  import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue } from "../lib/issues";
17
18
  import { resolveBaseUrls } from "../lib/util";
18
- import { applyInitPlan, buildInitPlan, resolveAdminConnection, resolveMonitoringPassword } from "../lib/init";
19
+ import { applyInitPlan, buildInitPlan, resolveAdminConnection, resolveMonitoringPassword, verifyInitSetup } from "../lib/init";
19
20
 
20
21
  const execPromise = promisify(exec);
21
22
  const execFilePromise = promisify(execFile);
@@ -129,9 +130,41 @@ program
129
130
  .option("--monitoring-user <name>", "Monitoring role name to create/update", "postgres_ai_mon")
130
131
  .option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)")
131
132
  .option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false)
132
- .option("--print-sql", "Print SQL steps before applying (passwords redacted by default)", false)
133
+ .option("--verify", "Verify that monitoring role/permissions are in place (no changes)", false)
134
+ .option("--reset-password", "Reset monitoring role password only (no other changes)", false)
135
+ .option("--print-sql", "Print SQL plan and exit (no changes applied)", false)
133
136
  .option("--show-secrets", "When printing SQL, do not redact secrets (DANGEROUS)", false)
134
- .option("--dry-run", "Print SQL steps and exit without applying changes", false)
137
+ .option("--print-password", "Print generated monitoring password (DANGEROUS in CI logs)", false)
138
+ .addHelpText(
139
+ "after",
140
+ [
141
+ "",
142
+ "Examples:",
143
+ " postgresai init postgresql://admin@host:5432/dbname",
144
+ " postgresai init \"dbname=dbname host=host user=admin\"",
145
+ " postgresai init -h host -p 5432 -U admin -d dbname",
146
+ "",
147
+ "Admin password:",
148
+ " --admin-password <password> or PGPASSWORD=... (libpq standard)",
149
+ "",
150
+ "Monitoring password:",
151
+ " --password <password> or PGAI_MON_PASSWORD=... (otherwise auto-generated)",
152
+ " If auto-generated, it is printed only on TTY by default.",
153
+ " To print it in non-interactive mode: --print-password",
154
+ "",
155
+ "Inspect SQL without applying changes:",
156
+ " postgresai init <conn> --print-sql",
157
+ "",
158
+ "Verify setup (no changes):",
159
+ " postgresai init <conn> --verify",
160
+ "",
161
+ "Reset monitoring password only:",
162
+ " postgresai init <conn> --reset-password --password '...'",
163
+ "",
164
+ "Offline SQL plan (no DB connection):",
165
+ " postgresai init --print-sql -d dbname --password '...' --show-secrets",
166
+ ].join("\n")
167
+ )
135
168
  .action(async (conn: string | undefined, opts: {
136
169
  dbUrl?: string;
137
170
  host?: string;
@@ -142,19 +175,76 @@ program
142
175
  monitoringUser: string;
143
176
  password?: string;
144
177
  skipOptionalPermissions?: boolean;
178
+ verify?: boolean;
179
+ resetPassword?: boolean;
145
180
  printSql?: boolean;
146
181
  showSecrets?: boolean;
147
- dryRun?: boolean;
182
+ printPassword?: boolean;
148
183
  }) => {
184
+ if (opts.verify && opts.resetPassword) {
185
+ console.error("✗ Provide only one of --verify or --reset-password");
186
+ process.exitCode = 1;
187
+ return;
188
+ }
189
+ if (opts.verify && opts.printSql) {
190
+ console.error("✗ --verify cannot be combined with --print-sql");
191
+ process.exitCode = 1;
192
+ return;
193
+ }
194
+
195
+ const shouldPrintSql = !!opts.printSql;
196
+ const shouldRedactSecrets = !opts.showSecrets;
197
+ const redactPasswords = (sql: string): string => {
198
+ if (!shouldRedactSecrets) return sql;
199
+ // Replace PASSWORD '<literal>' (handles doubled quotes inside).
200
+ return sql.replace(/password\s+'(?:''|[^'])*'/gi, "password '<redacted>'");
201
+ };
202
+
203
+ // Offline mode: allow printing SQL without providing/using an admin connection.
204
+ // Useful for audits/reviews; caller can provide -d/PGDATABASE and an explicit monitoring password.
205
+ if (!conn && !opts.dbUrl && !opts.host && !opts.port && !opts.username && !opts.adminPassword) {
206
+ if (shouldPrintSql) {
207
+ const database = (opts.dbname ?? process.env.PGDATABASE ?? "postgres").trim();
208
+ const includeOptionalPermissions = !opts.skipOptionalPermissions;
209
+
210
+ // Use explicit password/env if provided; otherwise use a placeholder (will be redacted unless --show-secrets).
211
+ const monPassword =
212
+ (opts.password ?? process.env.PGAI_MON_PASSWORD ?? "CHANGE_ME").toString();
213
+
214
+ const plan = await buildInitPlan({
215
+ database,
216
+ monitoringUser: opts.monitoringUser,
217
+ monitoringPassword: monPassword,
218
+ includeOptionalPermissions,
219
+ roleExists: undefined,
220
+ });
221
+
222
+ console.log("\n--- SQL plan (offline; not connected) ---");
223
+ console.log(`-- database: ${database}`);
224
+ console.log(`-- monitoring user: ${opts.monitoringUser}`);
225
+ console.log(`-- optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
226
+ for (const step of plan.steps) {
227
+ console.log(`\n-- ${step.name}${step.optional ? " (optional)" : ""}`);
228
+ console.log(redactPasswords(step.sql));
229
+ }
230
+ console.log("\n--- end SQL plan ---\n");
231
+ if (shouldRedactSecrets) {
232
+ console.log("Note: passwords are redacted in the printed SQL (use --show-secrets to print them).");
233
+ }
234
+ return;
235
+ }
236
+ }
237
+
149
238
  let adminConn;
150
239
  try {
151
240
  adminConn = resolveAdminConnection({
152
241
  conn,
153
242
  dbUrlFlag: opts.dbUrl,
154
- host: opts.host,
155
- port: opts.port,
156
- username: opts.username,
157
- dbname: opts.dbname,
243
+ // Allow libpq standard env vars as implicit defaults (common UX).
244
+ host: opts.host ?? process.env.PGHOST,
245
+ port: opts.port ?? process.env.PGPORT,
246
+ username: opts.username ?? process.env.PGUSER,
247
+ dbname: opts.dbname ?? process.env.PGDATABASE,
158
248
  adminPassword: opts.adminPassword,
159
249
  envPassword: process.env.PGPASSWORD,
160
250
  });
@@ -171,10 +261,7 @@ program
171
261
  console.log(`Monitoring user: ${opts.monitoringUser}`);
172
262
  console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
173
263
 
174
- const shouldPrintSql = !!opts.printSql || !!opts.dryRun;
175
-
176
264
  // Use native pg client instead of requiring psql to be installed
177
- const { Client } = require("pg");
178
265
  const client = new Client(adminConn.clientConfig);
179
266
 
180
267
  try {
@@ -183,7 +270,7 @@ program
183
270
  const roleRes = await client.query("select 1 from pg_catalog.pg_roles where rolname = $1", [
184
271
  opts.monitoringUser,
185
272
  ]);
186
- const roleExists = roleRes.rowCount > 0;
273
+ const roleExists = (roleRes.rowCount ?? 0) > 0;
187
274
 
188
275
  const dbRes = await client.query("select current_database() as db");
189
276
  const database = dbRes.rows?.[0]?.db;
@@ -191,6 +278,31 @@ program
191
278
  throw new Error("Failed to resolve current database name");
192
279
  }
193
280
 
281
+ if (opts.verify) {
282
+ const v = await verifyInitSetup({
283
+ client,
284
+ database,
285
+ monitoringUser: opts.monitoringUser,
286
+ includeOptionalPermissions,
287
+ });
288
+ if (v.ok) {
289
+ console.log("✓ init verify: OK");
290
+ if (v.missingOptional.length > 0) {
291
+ console.log("⚠ Optional items missing:");
292
+ for (const m of v.missingOptional) console.log(`- ${m}`);
293
+ }
294
+ return;
295
+ }
296
+ console.error("✗ init verify failed: missing required items");
297
+ for (const m of v.missingRequired) console.error(`- ${m}`);
298
+ if (v.missingOptional.length > 0) {
299
+ console.error("Optional items missing:");
300
+ for (const m of v.missingOptional) console.error(`- ${m}`);
301
+ }
302
+ process.exitCode = 1;
303
+ return;
304
+ }
305
+
194
306
  let monPassword: string;
195
307
  try {
196
308
  const resolved = await resolveMonitoringPassword({
@@ -200,8 +312,25 @@ program
200
312
  });
201
313
  monPassword = resolved.password;
202
314
  if (resolved.generated) {
203
- console.log(`Generated password for monitoring user ${opts.monitoringUser}: ${monPassword}`);
204
- console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
315
+ const canPrint = process.stdout.isTTY || !!opts.printPassword;
316
+ if (canPrint) {
317
+ console.log(`Generated password for monitoring user ${opts.monitoringUser}: ${monPassword}`);
318
+ console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
319
+ } else {
320
+ console.error(
321
+ [
322
+ `✗ Monitoring password was auto-generated for ${opts.monitoringUser} but not printed in non-interactive mode.`,
323
+ "",
324
+ "Provide it explicitly:",
325
+ " --password <password> or PGAI_MON_PASSWORD=...",
326
+ "",
327
+ "Or (NOT recommended) print the generated password:",
328
+ " --print-password",
329
+ ].join("\n")
330
+ );
331
+ process.exitCode = 1;
332
+ return;
333
+ }
205
334
  }
206
335
  } catch (e) {
207
336
  const msg = e instanceof Error ? e.message : String(e);
@@ -218,33 +347,26 @@ program
218
347
  roleExists,
219
348
  });
220
349
 
221
- if (shouldPrintSql) {
222
- const redact = !opts.showSecrets;
223
- const redactPasswords = (sql: string): string => {
224
- if (!redact) return sql;
225
- // Replace PASSWORD '<literal>' (handles doubled quotes inside).
226
- return sql.replace(/password\s+'(?:''|[^'])*'/gi, "password '<redacted>'");
227
- };
350
+ const effectivePlan = opts.resetPassword
351
+ ? { ...plan, steps: plan.steps.filter((s) => s.name === "01.role") }
352
+ : plan;
228
353
 
354
+ if (shouldPrintSql) {
229
355
  console.log("\n--- SQL plan ---");
230
- for (const step of plan.steps) {
356
+ for (const step of effectivePlan.steps) {
231
357
  console.log(`\n-- ${step.name}${step.optional ? " (optional)" : ""}`);
232
358
  console.log(redactPasswords(step.sql));
233
359
  }
234
360
  console.log("\n--- end SQL plan ---\n");
235
- if (redact) {
361
+ if (shouldRedactSecrets) {
236
362
  console.log("Note: passwords are redacted in the printed SQL (use --show-secrets to print them).");
237
363
  }
238
- }
239
-
240
- if (opts.dryRun) {
241
- console.log("✓ dry-run completed (no changes were applied)");
242
364
  return;
243
365
  }
244
366
 
245
- const { applied, skippedOptional } = await applyInitPlan({ client, plan });
367
+ const { applied, skippedOptional } = await applyInitPlan({ client, plan: effectivePlan });
246
368
 
247
- console.log("✓ init completed");
369
+ console.log(opts.resetPassword ? "✓ init password reset completed" : "✓ init completed");
248
370
  if (skippedOptional.length > 0) {
249
371
  console.log("⚠ Some optional steps were skipped (not supported or insufficient privileges):");
250
372
  for (const s of skippedOptional) console.log(`- ${s}`);
@@ -282,6 +404,15 @@ program
282
404
  if (errAny.code === "42501") {
283
405
  console.error("Hint: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges).");
284
406
  }
407
+ if (errAny.code === "ECONNREFUSED") {
408
+ console.error("Hint: check host/port and ensure Postgres is reachable from this machine.");
409
+ }
410
+ if (errAny.code === "ENOTFOUND") {
411
+ console.error("Hint: DNS resolution failed; double-check the host name.");
412
+ }
413
+ if (errAny.code === "ETIMEDOUT") {
414
+ console.error("Hint: connection timed out; check network/firewall rules.");
415
+ }
285
416
  }
286
417
  process.exitCode = 1;
287
418
  } finally {
@@ -46,6 +46,7 @@ const util_1 = require("util");
46
46
  const readline = __importStar(require("readline"));
47
47
  const http = __importStar(require("https"));
48
48
  const url_1 = require("url");
49
+ const pg_1 = require("pg");
49
50
  const mcp_server_1 = require("../lib/mcp-server");
50
51
  const issues_1 = require("../lib/issues");
51
52
  const util_2 = require("../lib/util");
@@ -111,19 +112,97 @@ program
111
112
  .option("--monitoring-user <name>", "Monitoring role name to create/update", "postgres_ai_mon")
112
113
  .option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)")
113
114
  .option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false)
114
- .option("--print-sql", "Print SQL steps before applying (passwords redacted by default)", false)
115
+ .option("--verify", "Verify that monitoring role/permissions are in place (no changes)", false)
116
+ .option("--reset-password", "Reset monitoring role password only (no other changes)", false)
117
+ .option("--print-sql", "Print SQL plan and exit (no changes applied)", false)
115
118
  .option("--show-secrets", "When printing SQL, do not redact secrets (DANGEROUS)", false)
116
- .option("--dry-run", "Print SQL steps and exit without applying changes", false)
119
+ .option("--print-password", "Print generated monitoring password (DANGEROUS in CI logs)", false)
120
+ .addHelpText("after", [
121
+ "",
122
+ "Examples:",
123
+ " postgresai init postgresql://admin@host:5432/dbname",
124
+ " postgresai init \"dbname=dbname host=host user=admin\"",
125
+ " postgresai init -h host -p 5432 -U admin -d dbname",
126
+ "",
127
+ "Admin password:",
128
+ " --admin-password <password> or PGPASSWORD=... (libpq standard)",
129
+ "",
130
+ "Monitoring password:",
131
+ " --password <password> or PGAI_MON_PASSWORD=... (otherwise auto-generated)",
132
+ " If auto-generated, it is printed only on TTY by default.",
133
+ " To print it in non-interactive mode: --print-password",
134
+ "",
135
+ "Inspect SQL without applying changes:",
136
+ " postgresai init <conn> --print-sql",
137
+ "",
138
+ "Verify setup (no changes):",
139
+ " postgresai init <conn> --verify",
140
+ "",
141
+ "Reset monitoring password only:",
142
+ " postgresai init <conn> --reset-password --password '...'",
143
+ "",
144
+ "Offline SQL plan (no DB connection):",
145
+ " postgresai init --print-sql -d dbname --password '...' --show-secrets",
146
+ ].join("\n"))
117
147
  .action(async (conn, opts) => {
148
+ if (opts.verify && opts.resetPassword) {
149
+ console.error("✗ Provide only one of --verify or --reset-password");
150
+ process.exitCode = 1;
151
+ return;
152
+ }
153
+ if (opts.verify && opts.printSql) {
154
+ console.error("✗ --verify cannot be combined with --print-sql");
155
+ process.exitCode = 1;
156
+ return;
157
+ }
158
+ const shouldPrintSql = !!opts.printSql;
159
+ const shouldRedactSecrets = !opts.showSecrets;
160
+ const redactPasswords = (sql) => {
161
+ if (!shouldRedactSecrets)
162
+ return sql;
163
+ // Replace PASSWORD '<literal>' (handles doubled quotes inside).
164
+ return sql.replace(/password\s+'(?:''|[^'])*'/gi, "password '<redacted>'");
165
+ };
166
+ // Offline mode: allow printing SQL without providing/using an admin connection.
167
+ // Useful for audits/reviews; caller can provide -d/PGDATABASE and an explicit monitoring password.
168
+ if (!conn && !opts.dbUrl && !opts.host && !opts.port && !opts.username && !opts.adminPassword) {
169
+ if (shouldPrintSql) {
170
+ const database = (opts.dbname ?? process.env.PGDATABASE ?? "postgres").trim();
171
+ const includeOptionalPermissions = !opts.skipOptionalPermissions;
172
+ // Use explicit password/env if provided; otherwise use a placeholder (will be redacted unless --show-secrets).
173
+ const monPassword = (opts.password ?? process.env.PGAI_MON_PASSWORD ?? "CHANGE_ME").toString();
174
+ const plan = await (0, init_1.buildInitPlan)({
175
+ database,
176
+ monitoringUser: opts.monitoringUser,
177
+ monitoringPassword: monPassword,
178
+ includeOptionalPermissions,
179
+ roleExists: undefined,
180
+ });
181
+ console.log("\n--- SQL plan (offline; not connected) ---");
182
+ console.log(`-- database: ${database}`);
183
+ console.log(`-- monitoring user: ${opts.monitoringUser}`);
184
+ console.log(`-- optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
185
+ for (const step of plan.steps) {
186
+ console.log(`\n-- ${step.name}${step.optional ? " (optional)" : ""}`);
187
+ console.log(redactPasswords(step.sql));
188
+ }
189
+ console.log("\n--- end SQL plan ---\n");
190
+ if (shouldRedactSecrets) {
191
+ console.log("Note: passwords are redacted in the printed SQL (use --show-secrets to print them).");
192
+ }
193
+ return;
194
+ }
195
+ }
118
196
  let adminConn;
119
197
  try {
120
198
  adminConn = (0, init_1.resolveAdminConnection)({
121
199
  conn,
122
200
  dbUrlFlag: opts.dbUrl,
123
- host: opts.host,
124
- port: opts.port,
125
- username: opts.username,
126
- dbname: opts.dbname,
201
+ // Allow libpq standard env vars as implicit defaults (common UX).
202
+ host: opts.host ?? process.env.PGHOST,
203
+ port: opts.port ?? process.env.PGPORT,
204
+ username: opts.username ?? process.env.PGUSER,
205
+ dbname: opts.dbname ?? process.env.PGDATABASE,
127
206
  adminPassword: opts.adminPassword,
128
207
  envPassword: process.env.PGPASSWORD,
129
208
  });
@@ -138,21 +217,46 @@ program
138
217
  console.log(`Connecting to: ${adminConn.display}`);
139
218
  console.log(`Monitoring user: ${opts.monitoringUser}`);
140
219
  console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
141
- const shouldPrintSql = !!opts.printSql || !!opts.dryRun;
142
220
  // Use native pg client instead of requiring psql to be installed
143
- const { Client } = require("pg");
144
- const client = new Client(adminConn.clientConfig);
221
+ const client = new pg_1.Client(adminConn.clientConfig);
145
222
  try {
146
223
  await client.connect();
147
224
  const roleRes = await client.query("select 1 from pg_catalog.pg_roles where rolname = $1", [
148
225
  opts.monitoringUser,
149
226
  ]);
150
- const roleExists = roleRes.rowCount > 0;
227
+ const roleExists = (roleRes.rowCount ?? 0) > 0;
151
228
  const dbRes = await client.query("select current_database() as db");
152
229
  const database = dbRes.rows?.[0]?.db;
153
230
  if (typeof database !== "string" || !database) {
154
231
  throw new Error("Failed to resolve current database name");
155
232
  }
233
+ if (opts.verify) {
234
+ const v = await (0, init_1.verifyInitSetup)({
235
+ client,
236
+ database,
237
+ monitoringUser: opts.monitoringUser,
238
+ includeOptionalPermissions,
239
+ });
240
+ if (v.ok) {
241
+ console.log("✓ init verify: OK");
242
+ if (v.missingOptional.length > 0) {
243
+ console.log("⚠ Optional items missing:");
244
+ for (const m of v.missingOptional)
245
+ console.log(`- ${m}`);
246
+ }
247
+ return;
248
+ }
249
+ console.error("✗ init verify failed: missing required items");
250
+ for (const m of v.missingRequired)
251
+ console.error(`- ${m}`);
252
+ if (v.missingOptional.length > 0) {
253
+ console.error("Optional items missing:");
254
+ for (const m of v.missingOptional)
255
+ console.error(`- ${m}`);
256
+ }
257
+ process.exitCode = 1;
258
+ return;
259
+ }
156
260
  let monPassword;
157
261
  try {
158
262
  const resolved = await (0, init_1.resolveMonitoringPassword)({
@@ -162,8 +266,24 @@ program
162
266
  });
163
267
  monPassword = resolved.password;
164
268
  if (resolved.generated) {
165
- console.log(`Generated password for monitoring user ${opts.monitoringUser}: ${monPassword}`);
166
- console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
269
+ const canPrint = process.stdout.isTTY || !!opts.printPassword;
270
+ if (canPrint) {
271
+ console.log(`Generated password for monitoring user ${opts.monitoringUser}: ${monPassword}`);
272
+ console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
273
+ }
274
+ else {
275
+ console.error([
276
+ `✗ Monitoring password was auto-generated for ${opts.monitoringUser} but not printed in non-interactive mode.`,
277
+ "",
278
+ "Provide it explicitly:",
279
+ " --password <password> or PGAI_MON_PASSWORD=...",
280
+ "",
281
+ "Or (NOT recommended) print the generated password:",
282
+ " --print-password",
283
+ ].join("\n"));
284
+ process.exitCode = 1;
285
+ return;
286
+ }
167
287
  }
168
288
  }
169
289
  catch (e) {
@@ -179,30 +299,23 @@ program
179
299
  includeOptionalPermissions,
180
300
  roleExists,
181
301
  });
302
+ const effectivePlan = opts.resetPassword
303
+ ? { ...plan, steps: plan.steps.filter((s) => s.name === "01.role") }
304
+ : plan;
182
305
  if (shouldPrintSql) {
183
- const redact = !opts.showSecrets;
184
- const redactPasswords = (sql) => {
185
- if (!redact)
186
- return sql;
187
- // Replace PASSWORD '<literal>' (handles doubled quotes inside).
188
- return sql.replace(/password\s+'(?:''|[^'])*'/gi, "password '<redacted>'");
189
- };
190
306
  console.log("\n--- SQL plan ---");
191
- for (const step of plan.steps) {
307
+ for (const step of effectivePlan.steps) {
192
308
  console.log(`\n-- ${step.name}${step.optional ? " (optional)" : ""}`);
193
309
  console.log(redactPasswords(step.sql));
194
310
  }
195
311
  console.log("\n--- end SQL plan ---\n");
196
- if (redact) {
312
+ if (shouldRedactSecrets) {
197
313
  console.log("Note: passwords are redacted in the printed SQL (use --show-secrets to print them).");
198
314
  }
199
- }
200
- if (opts.dryRun) {
201
- console.log("✓ dry-run completed (no changes were applied)");
202
315
  return;
203
316
  }
204
- const { applied, skippedOptional } = await (0, init_1.applyInitPlan)({ client, plan });
205
- console.log("✓ init completed");
317
+ const { applied, skippedOptional } = await (0, init_1.applyInitPlan)({ client, plan: effectivePlan });
318
+ console.log(opts.resetPassword ? "✓ init password reset completed" : "✓ init completed");
206
319
  if (skippedOptional.length > 0) {
207
320
  console.log("⚠ Some optional steps were skipped (not supported or insufficient privileges):");
208
321
  for (const s of skippedOptional)
@@ -244,6 +357,15 @@ program
244
357
  if (errAny.code === "42501") {
245
358
  console.error("Hint: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges).");
246
359
  }
360
+ if (errAny.code === "ECONNREFUSED") {
361
+ console.error("Hint: check host/port and ensure Postgres is reachable from this machine.");
362
+ }
363
+ if (errAny.code === "ENOTFOUND") {
364
+ console.error("Hint: DNS resolution failed; double-check the host name.");
365
+ }
366
+ if (errAny.code === "ETIMEDOUT") {
367
+ console.error("Hint: connection timed out; check network/firewall rules.");
368
+ }
247
369
  }
248
370
  process.exitCode = 1;
249
371
  }