neonctl 2.28.0 → 2.29.0

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.
Files changed (135) hide show
  1. package/README.md +2 -2
  2. package/dist/analytics.js +35 -33
  3. package/dist/api.js +34 -34
  4. package/dist/auth.js +50 -44
  5. package/dist/cli.js +2 -2
  6. package/dist/commands/auth.js +58 -52
  7. package/dist/commands/bootstrap.js +115 -157
  8. package/dist/commands/branches.js +154 -147
  9. package/dist/commands/bucket.js +124 -118
  10. package/dist/commands/checkout.js +49 -49
  11. package/dist/commands/config.js +212 -88
  12. package/dist/commands/connection_string.js +62 -62
  13. package/dist/commands/data_api.js +96 -96
  14. package/dist/commands/databases.js +23 -23
  15. package/dist/commands/deploy.js +12 -12
  16. package/dist/commands/dev.js +114 -114
  17. package/dist/commands/env.js +43 -43
  18. package/dist/commands/functions.js +97 -98
  19. package/dist/commands/index.js +26 -26
  20. package/dist/commands/init.js +23 -22
  21. package/dist/commands/ip_allow.js +29 -29
  22. package/dist/commands/link.js +223 -166
  23. package/dist/commands/neon_auth.js +381 -363
  24. package/dist/commands/operations.js +11 -11
  25. package/dist/commands/orgs.js +8 -8
  26. package/dist/commands/projects.js +101 -99
  27. package/dist/commands/psql.js +31 -31
  28. package/dist/commands/roles.js +21 -21
  29. package/dist/commands/schema_diff.js +23 -23
  30. package/dist/commands/set_context.js +17 -17
  31. package/dist/commands/status.js +17 -17
  32. package/dist/commands/user.js +5 -5
  33. package/dist/commands/vpc_endpoints.js +50 -50
  34. package/dist/config.js +7 -7
  35. package/dist/config_format.js +5 -5
  36. package/dist/context.js +23 -16
  37. package/dist/current_branch_fast_path.js +6 -6
  38. package/dist/dev/env.js +33 -33
  39. package/dist/dev/functions.js +4 -4
  40. package/dist/dev/inputs.js +6 -6
  41. package/dist/dev/runtime.js +25 -25
  42. package/dist/env.js +14 -14
  43. package/dist/env_file.js +13 -13
  44. package/dist/errors.js +19 -19
  45. package/dist/functions_api.js +10 -10
  46. package/dist/help.js +15 -15
  47. package/dist/index.js +94 -92
  48. package/dist/log.js +2 -2
  49. package/dist/pkg.js +5 -5
  50. package/dist/psql/cli.js +4 -2
  51. package/dist/psql/command/cmd_cond.js +61 -61
  52. package/dist/psql/command/cmd_connect.js +159 -154
  53. package/dist/psql/command/cmd_copy.js +107 -97
  54. package/dist/psql/command/cmd_describe.js +368 -363
  55. package/dist/psql/command/cmd_format.js +276 -263
  56. package/dist/psql/command/cmd_io.js +269 -263
  57. package/dist/psql/command/cmd_lo.js +74 -66
  58. package/dist/psql/command/cmd_meta.js +148 -148
  59. package/dist/psql/command/cmd_misc.js +17 -17
  60. package/dist/psql/command/cmd_pipeline.js +142 -135
  61. package/dist/psql/command/cmd_restrict.js +25 -25
  62. package/dist/psql/command/cmd_show.js +183 -168
  63. package/dist/psql/command/dispatch.js +26 -26
  64. package/dist/psql/command/shared.js +14 -14
  65. package/dist/psql/complete/filenames.js +16 -16
  66. package/dist/psql/complete/index.js +4 -4
  67. package/dist/psql/complete/matcher.js +33 -32
  68. package/dist/psql/complete/psqlVars.js +173 -173
  69. package/dist/psql/complete/queries.js +5 -3
  70. package/dist/psql/complete/rules.js +900 -863
  71. package/dist/psql/core/common.js +136 -133
  72. package/dist/psql/core/help.js +343 -343
  73. package/dist/psql/core/mainloop.js +160 -153
  74. package/dist/psql/core/prompt.js +126 -123
  75. package/dist/psql/core/settings.js +111 -111
  76. package/dist/psql/core/sqlHelp.js +150 -150
  77. package/dist/psql/core/startup.js +211 -205
  78. package/dist/psql/core/syncVars.js +14 -14
  79. package/dist/psql/core/variables.js +24 -24
  80. package/dist/psql/describe/formatters.js +302 -289
  81. package/dist/psql/describe/processNamePattern.js +28 -28
  82. package/dist/psql/describe/queries.js +656 -651
  83. package/dist/psql/index.js +436 -411
  84. package/dist/psql/io/history.js +36 -36
  85. package/dist/psql/io/input.js +15 -15
  86. package/dist/psql/io/lineEditor/buffer.js +27 -25
  87. package/dist/psql/io/lineEditor/complete.js +15 -15
  88. package/dist/psql/io/lineEditor/filename.js +22 -22
  89. package/dist/psql/io/lineEditor/index.js +65 -62
  90. package/dist/psql/io/lineEditor/keymap.js +325 -318
  91. package/dist/psql/io/lineEditor/vt100.js +60 -60
  92. package/dist/psql/io/pgpass.js +18 -18
  93. package/dist/psql/io/pgservice.js +14 -14
  94. package/dist/psql/io/psqlrc.js +46 -46
  95. package/dist/psql/print/aligned.js +175 -166
  96. package/dist/psql/print/asciidoc.js +51 -51
  97. package/dist/psql/print/crosstab.js +34 -31
  98. package/dist/psql/print/csv.js +25 -22
  99. package/dist/psql/print/html.js +54 -54
  100. package/dist/psql/print/json.js +12 -12
  101. package/dist/psql/print/latex.js +118 -118
  102. package/dist/psql/print/pager.js +28 -26
  103. package/dist/psql/print/troff.js +48 -48
  104. package/dist/psql/print/unaligned.js +15 -14
  105. package/dist/psql/print/units.js +17 -17
  106. package/dist/psql/scanner/slash.js +48 -46
  107. package/dist/psql/scanner/sql.js +88 -84
  108. package/dist/psql/scanner/stringutils.js +21 -17
  109. package/dist/psql/types/index.js +7 -7
  110. package/dist/psql/types/scanner.js +8 -8
  111. package/dist/psql/wire/connection.js +341 -327
  112. package/dist/psql/wire/copy.js +7 -7
  113. package/dist/psql/wire/pipeline.js +26 -24
  114. package/dist/psql/wire/protocol.js +102 -102
  115. package/dist/psql/wire/sasl.js +62 -62
  116. package/dist/psql/wire/tls.js +79 -73
  117. package/dist/storage_api.js +15 -15
  118. package/dist/test_utils/fixtures.js +34 -31
  119. package/dist/test_utils/oauth_server.js +5 -5
  120. package/dist/utils/api_enums.js +13 -13
  121. package/dist/utils/branch_notice.js +5 -5
  122. package/dist/utils/branch_picker.js +26 -26
  123. package/dist/utils/compute_units.js +4 -4
  124. package/dist/utils/enrichers.js +20 -15
  125. package/dist/utils/esbuild.js +28 -28
  126. package/dist/utils/formats.js +1 -1
  127. package/dist/utils/middlewares.js +3 -3
  128. package/dist/utils/package_manager.js +68 -0
  129. package/dist/utils/point_in_time.js +12 -12
  130. package/dist/utils/psql.js +30 -30
  131. package/dist/utils/string.js +2 -2
  132. package/dist/utils/ui.js +9 -9
  133. package/dist/utils/zip.js +1 -1
  134. package/dist/writer.js +17 -17
  135. package/package.json +6 -7
@@ -55,20 +55,20 @@
55
55
  * works without re-prompting. A new password supplied in the conninfo /
56
56
  * URI override always wins.
57
57
  */
58
- import { Buffer } from 'node:buffer';
59
- import { createHash, createHmac, pbkdf2Sync, randomBytes as nodeRandomBytes, } from 'node:crypto';
60
- import { readLine as readInputLine } from '../io/input.js';
61
- import { syncConnectionVars } from '../core/syncVars.js';
62
- import { PgConnection } from '../wire/connection.js';
63
- import { alignedPrinter } from '../print/aligned.js';
64
- import { asciidocPrinter } from '../print/asciidoc.js';
65
- import { csvPrinter } from '../print/csv.js';
66
- import { htmlPrinter } from '../print/html.js';
67
- import { jsonPrinter } from '../print/json.js';
68
- import { latexLongtablePrinter, latexPrinter } from '../print/latex.js';
69
- import { troffMsPrinter } from '../print/troff.js';
70
- import { unalignedPrinter } from '../print/unaligned.js';
71
- import { writeErr, writeOut } from './shared.js';
58
+ import { Buffer } from "node:buffer";
59
+ import { createHash, createHmac, randomBytes as nodeRandomBytes, pbkdf2Sync, } from "node:crypto";
60
+ import { syncConnectionVars } from "../core/syncVars.js";
61
+ import { readLine as readInputLine } from "../io/input.js";
62
+ import { alignedPrinter } from "../print/aligned.js";
63
+ import { asciidocPrinter } from "../print/asciidoc.js";
64
+ import { csvPrinter } from "../print/csv.js";
65
+ import { htmlPrinter } from "../print/html.js";
66
+ import { jsonPrinter } from "../print/json.js";
67
+ import { latexLongtablePrinter, latexPrinter } from "../print/latex.js";
68
+ import { troffMsPrinter } from "../print/troff.js";
69
+ import { unalignedPrinter } from "../print/unaligned.js";
70
+ import { PgConnection } from "../wire/connection.js";
71
+ import { writeErr, writeOut } from "./shared.js";
72
72
  const defaultDeps = {
73
73
  connect: (opts) => PgConnection.connect(opts),
74
74
  readLine: defaultReadLine,
@@ -87,7 +87,7 @@ const readConnectionPassword = (conn) => {
87
87
  if (!conn)
88
88
  return null;
89
89
  const raw = conn.password;
90
- return typeof raw === 'string' && raw.length > 0 ? raw : null;
90
+ return typeof raw === "string" && raw.length > 0 ? raw : null;
91
91
  };
92
92
  /**
93
93
  * Read the live connection's EFFECTIVE {@link ConnectOptions} (the full set
@@ -103,14 +103,14 @@ const readConnectionOptions = (conn) => {
103
103
  if (!conn)
104
104
  return null;
105
105
  const raw = conn.opts;
106
- return raw !== undefined && raw !== null && typeof raw === 'object'
106
+ return raw !== undefined && raw !== null && typeof raw === "object"
107
107
  ? raw
108
108
  : null;
109
109
  };
110
110
  // ---------------------------------------------------------------------------
111
111
  // \c / \connect
112
112
  // ---------------------------------------------------------------------------
113
- const KEEP = '-';
113
+ const KEEP = "-";
114
114
  /**
115
115
  * Parse the argument tail of `\c` into a partial override of
116
116
  * {@link ConnectOptions}. Three forms:
@@ -131,8 +131,8 @@ export const parseConnectArgs = (rawArgs) => {
131
131
  if (trimmed.length === 0)
132
132
  return {};
133
133
  // URI form.
134
- if (trimmed.startsWith('postgresql://') ||
135
- trimmed.startsWith('postgres://')) {
134
+ if (trimmed.startsWith("postgresql://") ||
135
+ trimmed.startsWith("postgres://")) {
136
136
  return parseUri(trimmed);
137
137
  }
138
138
  // conninfo form — at least one token contains `=` (and that `=` is not
@@ -167,15 +167,15 @@ export const parseConnectArgs = (rawArgs) => {
167
167
  */
168
168
  const applyConnInfoKey = (out, key, value) => {
169
169
  switch (key) {
170
- case 'host':
170
+ case "host":
171
171
  out.host = value;
172
172
  break;
173
- case 'hostaddr':
173
+ case "hostaddr":
174
174
  // Distinct from `host`: hostaddr is the literal IP to dial, while the
175
175
  // cert/SNI is still verified against `host`.
176
176
  out.hostaddr = value;
177
177
  break;
178
- case 'port': {
178
+ case "port": {
179
179
  const port = parseInt(value, 10);
180
180
  if (!Number.isFinite(port) || port <= 0 || port > 65535) {
181
181
  return { error: `invalid port "${value}"` };
@@ -183,27 +183,27 @@ const applyConnInfoKey = (out, key, value) => {
183
183
  out.port = port;
184
184
  break;
185
185
  }
186
- case 'user':
186
+ case "user":
187
187
  out.user = value;
188
188
  break;
189
- case 'password':
189
+ case "password":
190
190
  out.password = value;
191
191
  break;
192
- case 'dbname':
193
- case 'database':
192
+ case "dbname":
193
+ case "database":
194
194
  out.database = value;
195
195
  break;
196
- case 'application_name':
196
+ case "application_name":
197
197
  out.applicationName = value;
198
198
  break;
199
- case 'sslmode': {
199
+ case "sslmode": {
200
200
  const allowed = [
201
- 'disable',
202
- 'allow',
203
- 'prefer',
204
- 'require',
205
- 'verify-ca',
206
- 'verify-full',
201
+ "disable",
202
+ "allow",
203
+ "prefer",
204
+ "require",
205
+ "verify-ca",
206
+ "verify-full",
207
207
  ];
208
208
  if (!allowed.includes(value)) {
209
209
  return { error: `invalid sslmode "${value}"` };
@@ -211,11 +211,11 @@ const applyConnInfoKey = (out, key, value) => {
211
211
  out.ssl = value;
212
212
  break;
213
213
  }
214
- case 'channel_binding': {
214
+ case "channel_binding": {
215
215
  const allowed = [
216
- 'disable',
217
- 'prefer',
218
- 'require',
216
+ "disable",
217
+ "prefer",
218
+ "require",
219
219
  ];
220
220
  if (!allowed.includes(value)) {
221
221
  return { error: `invalid channel_binding "${value}"` };
@@ -223,10 +223,10 @@ const applyConnInfoKey = (out, key, value) => {
223
223
  out.channelBinding = value;
224
224
  break;
225
225
  }
226
- case 'client_encoding':
226
+ case "client_encoding":
227
227
  out.clientEncoding = value;
228
228
  break;
229
- case 'connect_timeout': {
229
+ case "connect_timeout": {
230
230
  const n = parseInt(value, 10);
231
231
  if (!Number.isFinite(n) || n < 0) {
232
232
  return { error: `invalid connect_timeout "${value}"` };
@@ -234,7 +234,7 @@ const applyConnInfoKey = (out, key, value) => {
234
234
  out.connectTimeoutMs = n * 1000;
235
235
  break;
236
236
  }
237
- case 'options':
237
+ case "options":
238
238
  out.options = value;
239
239
  break;
240
240
  default:
@@ -274,7 +274,7 @@ const parseUri = (raw) => {
274
274
  if (url.password.length > 0) {
275
275
  out.password = decodeURIComponent(url.password);
276
276
  }
277
- const pathname = url.pathname.replace(/^\//, '');
277
+ const pathname = url.pathname.replace(/^\//, "");
278
278
  if (pathname.length > 0)
279
279
  out.database = decodeURIComponent(pathname);
280
280
  // Map the query string (?sslmode=require&connect_timeout=10&…) onto the
@@ -297,12 +297,12 @@ const parseConninfo = (raw) => {
297
297
  // quotes. libpq supports backslash-escapes inside quotes; we accept them
298
298
  // verbatim for now (none of the keys we care about typically need them).
299
299
  const pairs = [];
300
- let current = '';
300
+ let current = "";
301
301
  let inQuote = false;
302
302
  for (let i = 0; i < raw.length; i++) {
303
303
  const c = raw[i];
304
304
  if (inQuote) {
305
- if (c === '\\' && i + 1 < raw.length) {
305
+ if (c === "\\" && i + 1 < raw.length) {
306
306
  current += raw[i + 1];
307
307
  i++;
308
308
  continue;
@@ -321,7 +321,7 @@ const parseConninfo = (raw) => {
321
321
  if (/\s/.test(c)) {
322
322
  if (current.length > 0) {
323
323
  pairs.push(current);
324
- current = '';
324
+ current = "";
325
325
  }
326
326
  continue;
327
327
  }
@@ -330,7 +330,7 @@ const parseConninfo = (raw) => {
330
330
  if (current.length > 0)
331
331
  pairs.push(current);
332
332
  for (const pair of pairs) {
333
- const eq = pair.indexOf('=');
333
+ const eq = pair.indexOf("=");
334
334
  if (eq < 0) {
335
335
  return { error: `missing "=" after "${pair}" in connection info` };
336
336
  }
@@ -390,23 +390,23 @@ export const mergeConnectOpts = (settings, override, prior = null) => {
390
390
  const merged = { ...seed, ...ov };
391
391
  // Fill the required fields from vars / env / defaults when neither the
392
392
  // override nor the prior connection supplied them.
393
- const host = merged.host ?? vars.get('HOST') ?? 'localhost';
394
- const portStr = vars.get('PORT');
393
+ const host = merged.host ?? vars.get("HOST") ?? "localhost";
394
+ const portStr = vars.get("PORT");
395
395
  const port = merged.port ?? (portStr !== undefined ? parseInt(portStr, 10) : 5432);
396
396
  if (!Number.isFinite(port) || port <= 0 || port > 65535) {
397
397
  return { error: `invalid port number` };
398
398
  }
399
399
  const user = merged.user ??
400
- vars.get('USER') ??
400
+ vars.get("USER") ??
401
401
  process.env.USER ??
402
402
  process.env.USERNAME ??
403
- '';
403
+ "";
404
404
  if (user.length === 0) {
405
- return { error: 'no user name specified' };
405
+ return { error: "no user name specified" };
406
406
  }
407
- const database = merged.database ?? vars.get('DBNAME') ?? user;
408
- const password = merged.password ?? vars.get('PASSWORD');
409
- const ssl = merged.ssl ?? 'prefer';
407
+ const database = merged.database ?? vars.get("DBNAME") ?? user;
408
+ const password = merged.password ?? vars.get("PASSWORD");
409
+ const ssl = merged.ssl ?? "prefer";
410
410
  return {
411
411
  ...merged,
412
412
  host,
@@ -415,16 +415,16 @@ export const mergeConnectOpts = (settings, override, prior = null) => {
415
415
  password: password ?? undefined,
416
416
  database,
417
417
  ssl,
418
- applicationName: merged.applicationName ?? vars.get('APPLICATION_NAME'),
418
+ applicationName: merged.applicationName ?? vars.get("APPLICATION_NAME"),
419
419
  clientEncoding: merged.clientEncoding ?? settings.popt.topt.encoding,
420
420
  };
421
421
  };
422
422
  /** `\c` / `\connect` — reconnect, possibly to a different database/user/host. */
423
423
  export const cmdConnect = {
424
- name: 'c',
425
- aliases: ['connect'],
426
- argMode: 'whole-line',
427
- helpKey: 'c',
424
+ name: "c",
425
+ aliases: ["connect"],
426
+ argMode: "whole-line",
427
+ helpKey: "c",
428
428
  run: async (ctx) => {
429
429
  const rawArgs = ctx.restOfLine();
430
430
  if (rawArgs.trim().length === 0) {
@@ -433,9 +433,9 @@ export const cmdConnect = {
433
433
  return runConninfo(ctx);
434
434
  }
435
435
  const parsed = parseConnectArgs(rawArgs);
436
- if ('error' in parsed) {
436
+ if ("error" in parsed) {
437
437
  writeErr(`\\${ctx.cmdName}: ${parsed.error}\n`);
438
- return { status: 'error' };
438
+ return { status: "error" };
439
439
  }
440
440
  // Seed the reconnect from the live connection's EFFECTIVE options so TLS /
441
441
  // cert / auth settings survive and the prior password is only
@@ -447,9 +447,9 @@ export const cmdConnect = {
447
447
  const priorPw = readConnectionPassword(ctx.settings.db);
448
448
  const prior = priorOpts ?? (priorPw !== null ? { password: priorPw } : null);
449
449
  const newOpts = mergeConnectOpts(ctx.settings, parsed, prior);
450
- if ('error' in newOpts) {
450
+ if ("error" in newOpts) {
451
451
  writeErr(`\\${ctx.cmdName}: ${newOpts.error}\n`);
452
- return { status: 'error' };
452
+ return { status: "error" };
453
453
  }
454
454
  let next;
455
455
  try {
@@ -459,7 +459,7 @@ export const cmdConnect = {
459
459
  // psql keeps the old connection on failure.
460
460
  const msg = err instanceof Error ? err.message : String(err);
461
461
  writeErr(`\\${ctx.cmdName}: connection failed: ${msg}\n`);
462
- return { status: 'error' };
462
+ return { status: "error" };
463
463
  }
464
464
  const old = ctx.settings.db;
465
465
  ctx.settings.db = next;
@@ -477,7 +477,7 @@ export const cmdConnect = {
477
477
  }
478
478
  }
479
479
  writeOut(`You are now connected to database "${newOpts.database}" as user "${newOpts.user}".\n`);
480
- return { status: 'ok' };
480
+ return { status: "ok" };
481
481
  },
482
482
  };
483
483
  // ---------------------------------------------------------------------------
@@ -492,24 +492,24 @@ export const cmdConnect = {
492
492
  */
493
493
  const pickActivePrinter = (settings) => {
494
494
  switch (settings.popt.topt.format) {
495
- case 'aligned':
496
- case 'wrapped':
495
+ case "aligned":
496
+ case "wrapped":
497
497
  return alignedPrinter;
498
- case 'unaligned':
498
+ case "unaligned":
499
499
  return unalignedPrinter;
500
- case 'csv':
500
+ case "csv":
501
501
  return csvPrinter;
502
- case 'json':
502
+ case "json":
503
503
  return jsonPrinter;
504
- case 'html':
504
+ case "html":
505
505
  return htmlPrinter;
506
- case 'asciidoc':
506
+ case "asciidoc":
507
507
  return asciidocPrinter;
508
- case 'latex':
508
+ case "latex":
509
509
  return latexPrinter;
510
- case 'latex-longtable':
510
+ case "latex-longtable":
511
511
  return latexLongtablePrinter;
512
- case 'troff-ms':
512
+ case "troff-ms":
513
513
  return troffMsPrinter;
514
514
  default:
515
515
  return alignedPrinter;
@@ -537,80 +537,83 @@ const buildConninfoRows = (ctx, db) => {
537
537
  // Database / user come from the psql vars the startup WP populates (and
538
538
  // that `\c` keeps in sync); fall back to the connect-opts surfaced on the
539
539
  // connection when a var is missing.
540
- const database = ctx.settings.vars.get('DBNAME') ??
540
+ const database = ctx.settings.vars.get("DBNAME") ??
541
541
  db.database ??
542
- '';
543
- const user = ctx.settings.vars.get('USER') ??
542
+ "";
543
+ const user = ctx.settings.vars.get("USER") ??
544
544
  db.user ??
545
- '';
546
- const host = info?.host ?? ctx.settings.vars.get('HOST') ?? '';
545
+ "";
546
+ const host = info?.host ?? ctx.settings.vars.get("HOST") ?? "";
547
547
  const hostaddr = info?.hostaddr ?? null;
548
- const port = info?.port ?? Number(ctx.settings.vars.get('PORT') ?? Number.NaN);
549
- const portStr = Number.isFinite(port) ? String(port) : '';
548
+ const port = info?.port ?? Number(ctx.settings.vars.get("PORT") ?? Number.NaN);
549
+ const portStr = Number.isFinite(port) ? String(port) : "";
550
550
  const rows = [];
551
- rows.push(['Database', database]);
552
- rows.push(['Client User', user]);
551
+ rows.push(["Database", database]);
552
+ rows.push(["Client User", user]);
553
553
  // Host rows. A Unix-domain socket path (starts with '/') prints a
554
554
  // "Socket Directory" (or "Host Address" when a hostaddr was fixed);
555
555
  // otherwise "Host", plus a separate "Host Address" only when a distinct
556
556
  // hostaddr is present.
557
- if (host.startsWith('/')) {
557
+ if (host.startsWith("/")) {
558
558
  if (hostaddr !== null && hostaddr.length > 0) {
559
- rows.push(['Host Address', hostaddr]);
559
+ rows.push(["Host Address", hostaddr]);
560
560
  }
561
561
  else {
562
- rows.push(['Socket Directory', host]);
562
+ rows.push(["Socket Directory", host]);
563
563
  }
564
564
  }
565
565
  else {
566
- rows.push(['Host', host]);
566
+ rows.push(["Host", host]);
567
567
  if (hostaddr !== null && hostaddr.length > 0 && hostaddr !== host) {
568
- rows.push(['Host Address', hostaddr]);
568
+ rows.push(["Host Address", hostaddr]);
569
569
  }
570
570
  }
571
- rows.push(['Server Port', portStr]);
572
- rows.push(['Options', info?.options ?? '']);
573
- rows.push(['Protocol Version', '3.0']);
574
- rows.push(['Password Used', info?.passwordUsed ? 'true' : 'false']);
575
- rows.push(['GSSAPI Authenticated', info?.gssapiUsed ? 'true' : 'false']);
576
- rows.push(['Backend PID', String(info?.backendPid ?? 0)]);
577
- rows.push(['SSL Connection', tls ? 'true' : 'false']);
571
+ rows.push(["Server Port", portStr]);
572
+ rows.push(["Options", info?.options ?? ""]);
573
+ rows.push(["Protocol Version", "3.0"]);
574
+ rows.push(["Password Used", info?.passwordUsed ? "true" : "false"]);
575
+ rows.push(["GSSAPI Authenticated", info?.gssapiUsed ? "true" : "false"]);
576
+ rows.push(["Backend PID", String(info?.backendPid ?? 0)]);
577
+ rows.push(["SSL Connection", tls ? "true" : "false"]);
578
578
  if (tls) {
579
- rows.push(['SSL Library', tls.library]);
580
- rows.push(['SSL Protocol', tls.protocol]);
579
+ rows.push(["SSL Library", tls.library]);
580
+ rows.push(["SSL Protocol", tls.protocol]);
581
581
  rows.push([
582
- 'SSL Key Bits',
583
- tls.keyBits !== null ? String(tls.keyBits) : '',
582
+ "SSL Key Bits",
583
+ tls.keyBits !== null ? String(tls.keyBits) : "",
584
584
  ]);
585
- rows.push(['SSL Cipher', tls.cipher]);
585
+ rows.push(["SSL Cipher", tls.cipher]);
586
586
  rows.push([
587
- 'SSL Compression',
588
- tls.compression !== 'off' ? 'true' : 'false',
587
+ "SSL Compression",
588
+ tls.compression !== "off" ? "true" : "false",
589
+ ]);
590
+ rows.push([
591
+ "ALPN",
592
+ tls.alpn && tls.alpn.length > 0 ? tls.alpn : "none",
589
593
  ]);
590
- rows.push(['ALPN', tls.alpn && tls.alpn.length > 0 ? tls.alpn : 'none']);
591
594
  }
592
595
  rows.push([
593
- 'Superuser',
594
- ctx.settings.db?.parameterStatus('is_superuser') ?? 'unknown',
596
+ "Superuser",
597
+ ctx.settings.db?.parameterStatus("is_superuser") ?? "unknown",
595
598
  ]);
596
599
  rows.push([
597
- 'Hot Standby',
598
- ctx.settings.db?.parameterStatus('in_hot_standby') ?? 'unknown',
600
+ "Hot Standby",
601
+ ctx.settings.db?.parameterStatus("in_hot_standby") ?? "unknown",
599
602
  ]);
600
603
  return rows;
601
604
  };
602
605
  const runConninfo = async (ctx) => {
603
606
  const db = ctx.settings.db;
604
607
  if (!db) {
605
- writeOut('You are currently not connected to a database.\n');
606
- return { status: 'ok' };
608
+ writeOut("You are currently not connected to a database.\n");
609
+ return { status: "ok" };
607
610
  }
608
611
  const rows = buildConninfoRows(ctx, db);
609
612
  const rs = {
610
- command: 'SELECT',
613
+ command: "SELECT",
611
614
  rowCount: rows.length,
612
615
  oid: null,
613
- fields: [textField('Parameter'), textField('Value')],
616
+ fields: [textField("Parameter"), textField("Value")],
614
617
  rows: rows.map(([p, v]) => [p, v]),
615
618
  notices: [],
616
619
  };
@@ -619,21 +622,21 @@ const runConninfo = async (ctx) => {
619
622
  // matching PG18's `printQuery(... title = "Connection Information")`.
620
623
  const popt = {
621
624
  ...ctx.settings.popt,
622
- title: 'Connection Information',
625
+ title: "Connection Information",
623
626
  topt: {
624
627
  ...ctx.settings.popt.topt,
625
- title: 'Connection Information',
628
+ title: "Connection Information",
626
629
  defaultFooter: true,
627
630
  },
628
631
  };
629
632
  const out = process.stdout;
630
633
  await pickActivePrinter(ctx.settings).printQuery(rs, popt, out);
631
- return { status: 'ok' };
634
+ return { status: "ok" };
632
635
  };
633
636
  /** `\conninfo` — print info about the current connection. */
634
637
  export const cmdConninfo = {
635
- name: 'conninfo',
636
- helpKey: 'conninfo',
638
+ name: "conninfo",
639
+ helpKey: "conninfo",
637
640
  run: (ctx) => runConninfo(ctx),
638
641
  };
639
642
  // ---------------------------------------------------------------------------
@@ -642,14 +645,14 @@ export const cmdConninfo = {
642
645
  // ---------------------------------------------------------------------------
643
646
  /** `\encoding [NAME]` — show or set the client encoding, propagating to the connection. */
644
647
  export const cmdEncoding = {
645
- name: 'encoding',
646
- helpKey: 'encoding',
648
+ name: "encoding",
649
+ helpKey: "encoding",
647
650
  run: async (ctx) => {
648
- const arg = ctx.nextArg('normal');
651
+ const arg = ctx.nextArg("normal");
649
652
  const topt = ctx.settings.popt.topt;
650
653
  if (arg === null) {
651
654
  writeOut(`${topt.encoding}\n`);
652
- return { status: 'ok' };
655
+ return { status: "ok" };
653
656
  }
654
657
  const db = ctx.settings.db;
655
658
  if (db && !db.isClosed()) {
@@ -661,12 +664,12 @@ export const cmdEncoding = {
661
664
  catch (err) {
662
665
  const msg = err instanceof Error ? err.message : String(err);
663
666
  writeErr(`\\${ctx.cmdName}: ${msg}\n`);
664
- return { status: 'error' };
667
+ return { status: "error" };
665
668
  }
666
669
  }
667
670
  topt.encoding = arg;
668
- ctx.settings.vars.set('ENCODING', arg);
669
- return { status: 'ok' };
671
+ ctx.settings.vars.set("ENCODING", arg);
672
+ return { status: "ok" };
670
673
  },
671
674
  };
672
675
  // ---------------------------------------------------------------------------
@@ -695,22 +698,22 @@ export const cmdEncoding = {
695
698
  * higher for stronger hardening).
696
699
  */
697
700
  export const scramSha256Verifier = (password, salt, iterations = 4096) => {
698
- const saltedPassword = pbkdf2Sync(Buffer.from(password, 'utf8'), salt, iterations, 32, 'sha256');
699
- const clientKey = createHmac('sha256', saltedPassword)
700
- .update('Client Key')
701
+ const saltedPassword = pbkdf2Sync(Buffer.from(password, "utf8"), salt, iterations, 32, "sha256");
702
+ const clientKey = createHmac("sha256", saltedPassword)
703
+ .update("Client Key")
701
704
  .digest();
702
- const storedKey = createHash('sha256').update(clientKey).digest();
703
- const serverKey = createHmac('sha256', saltedPassword)
704
- .update('Server Key')
705
+ const storedKey = createHash("sha256").update(clientKey).digest();
706
+ const serverKey = createHmac("sha256", saltedPassword)
707
+ .update("Server Key")
705
708
  .digest();
706
- return ('SCRAM-SHA-256$' +
709
+ return ("SCRAM-SHA-256$" +
707
710
  String(iterations) +
708
- ':' +
709
- salt.toString('base64') +
710
- '$' +
711
- storedKey.toString('base64') +
712
- ':' +
713
- serverKey.toString('base64'));
711
+ ":" +
712
+ salt.toString("base64") +
713
+ "$" +
714
+ storedKey.toString("base64") +
715
+ ":" +
716
+ serverKey.toString("base64"));
714
717
  };
715
718
  /**
716
719
  * Default password / prompt reader. Delegates to the shared input layer
@@ -723,13 +726,13 @@ function defaultReadLine(prompt, opts) {
723
726
  }
724
727
  /** `\password [USERNAME]` — change a role's password, locally hashed as SCRAM. */
725
728
  export const cmdPassword = {
726
- name: 'password',
727
- helpKey: 'password',
729
+ name: "password",
730
+ helpKey: "password",
728
731
  run: async (ctx) => {
729
732
  const db = ctx.settings.db;
730
733
  if (!db || db.isClosed()) {
731
734
  writeErr(`\\${ctx.cmdName}: not connected\n`);
732
- return { status: 'error' };
735
+ return { status: "error" };
733
736
  }
734
737
  // Refuse to prompt for a password when stdin is not a TTY. Upstream uses
735
738
  // /dev/tty as a fallback so it works even with piped stdin; we don't yet
@@ -739,53 +742,55 @@ export const cmdPassword = {
739
742
  // default.
740
743
  if (ctx.settings.notty) {
741
744
  writeErr(`\\${ctx.cmdName}: not in interactive mode\n`);
742
- return { status: 'error' };
745
+ return { status: "error" };
743
746
  }
744
- const userArg = ctx.nextArg('sql-id');
747
+ const userArg = ctx.nextArg("sql-id");
745
748
  let user = userArg;
746
749
  if (user === null) {
747
750
  try {
748
- const rs = await db.query('SELECT CURRENT_USER');
751
+ const rs = await db.query("SELECT CURRENT_USER");
749
752
  const row = rs.rows[0] ?? [];
750
- user = typeof row[0] === 'string' ? row[0] : null;
753
+ user = typeof row[0] === "string" ? row[0] : null;
751
754
  }
752
755
  catch (err) {
753
756
  const msg = err instanceof Error ? err.message : String(err);
754
757
  writeErr(`\\${ctx.cmdName}: ${msg}\n`);
755
- return { status: 'error' };
758
+ return { status: "error" };
756
759
  }
757
760
  if (!user) {
758
761
  writeErr(`\\${ctx.cmdName}: could not determine current user\n`);
759
- return { status: 'error' };
762
+ return { status: "error" };
760
763
  }
761
764
  }
762
765
  let pw1;
763
766
  let pw2;
764
767
  try {
765
768
  pw1 = await currentDeps.readLine(`Enter new password for user "${user}": `, { echo: false });
766
- pw2 = await currentDeps.readLine('Enter it again: ', { echo: false });
769
+ pw2 = await currentDeps.readLine("Enter it again: ", {
770
+ echo: false,
771
+ });
767
772
  }
768
773
  catch (err) {
769
774
  const msg = err instanceof Error ? err.message : String(err);
770
775
  writeErr(`\\${ctx.cmdName}: ${msg}\n`);
771
- return { status: 'error' };
776
+ return { status: "error" };
772
777
  }
773
778
  if (pw1 !== pw2) {
774
779
  writeErr(`\\${ctx.cmdName}: Passwords didn't match.\n`);
775
- return { status: 'error' };
780
+ return { status: "error" };
776
781
  }
777
782
  if (pw1.length === 0) {
778
783
  writeErr(`\\${ctx.cmdName}: empty password\n`);
779
- return { status: 'error' };
784
+ return { status: "error" };
780
785
  }
781
786
  // Match upstream's PQchangePassword: `ALTER USER <id> PASSWORD <lit>`.
782
787
  // (ALTER USER and ALTER ROLE are synonyms in PG, but we follow the
783
788
  // libpq spelling for byte-for-byte parity with vanilla psql.)
784
789
  const salt = currentDeps.randomBytes(16);
785
790
  const verifier = scramSha256Verifier(pw1, salt);
786
- const sql = 'ALTER USER ' +
791
+ const sql = "ALTER USER " +
787
792
  db.escapeIdentifier(user) +
788
- ' PASSWORD ' +
793
+ " PASSWORD " +
789
794
  db.escapeLiteral(verifier);
790
795
  try {
791
796
  await db.execSimple(sql);
@@ -793,9 +798,9 @@ export const cmdPassword = {
793
798
  catch (err) {
794
799
  const msg = err instanceof Error ? err.message : String(err);
795
800
  writeErr(`\\${ctx.cmdName}: ${msg}\n`);
796
- return { status: 'error' };
801
+ return { status: "error" };
797
802
  }
798
- return { status: 'ok' };
803
+ return { status: "ok" };
799
804
  },
800
805
  };
801
806
  // ---------------------------------------------------------------------------