neonctl 2.27.1 → 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.
- package/README.md +35 -3
- package/dist/analytics.js +52 -34
- package/dist/api.js +643 -13
- package/dist/auth.js +50 -44
- package/dist/cli.js +8 -1
- package/dist/commands/auth.js +64 -51
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +160 -150
- package/dist/commands/bucket.js +183 -146
- package/dist/commands/checkout.js +51 -51
- package/dist/commands/config.js +228 -82
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +100 -101
- package/dist/commands/databases.js +29 -26
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +101 -104
- package/dist/commands/index.js +27 -25
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +232 -182
- package/dist/commands/neon_auth.js +385 -370
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +103 -101
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +27 -24
- package/dist/commands/schema_diff.js +25 -26
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +40 -0
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +37 -14
- package/dist/current_branch_fast_path.js +55 -0
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +68 -5
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +110 -107
- package/dist/log.js +2 -2
- package/dist/parameters.gen.js +14 -14
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +22 -23
- package/dist/test_utils/fixtures.js +74 -41
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +33 -0
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +28 -16
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +10 -12
|
@@ -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
|
|
59
|
-
import { createHash, createHmac,
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
71
|
-
import { writeErr, writeOut } from
|
|
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 ===
|
|
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 ===
|
|
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(
|
|
135
|
-
trimmed.startsWith(
|
|
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
|
|
170
|
+
case "host":
|
|
171
171
|
out.host = value;
|
|
172
172
|
break;
|
|
173
|
-
case
|
|
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
|
|
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
|
|
186
|
+
case "user":
|
|
187
187
|
out.user = value;
|
|
188
188
|
break;
|
|
189
|
-
case
|
|
189
|
+
case "password":
|
|
190
190
|
out.password = value;
|
|
191
191
|
break;
|
|
192
|
-
case
|
|
193
|
-
case
|
|
192
|
+
case "dbname":
|
|
193
|
+
case "database":
|
|
194
194
|
out.database = value;
|
|
195
195
|
break;
|
|
196
|
-
case
|
|
196
|
+
case "application_name":
|
|
197
197
|
out.applicationName = value;
|
|
198
198
|
break;
|
|
199
|
-
case
|
|
199
|
+
case "sslmode": {
|
|
200
200
|
const allowed = [
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
214
|
+
case "channel_binding": {
|
|
215
215
|
const allowed = [
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
226
|
+
case "client_encoding":
|
|
227
227
|
out.clientEncoding = value;
|
|
228
228
|
break;
|
|
229
|
-
case
|
|
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
|
|
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 ===
|
|
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(
|
|
394
|
-
const portStr = vars.get(
|
|
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(
|
|
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:
|
|
405
|
+
return { error: "no user name specified" };
|
|
406
406
|
}
|
|
407
|
-
const database = merged.database ?? vars.get(
|
|
408
|
-
const password = merged.password ?? vars.get(
|
|
409
|
-
const ssl = merged.ssl ??
|
|
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(
|
|
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:
|
|
425
|
-
aliases: [
|
|
426
|
-
argMode:
|
|
427
|
-
helpKey:
|
|
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 (
|
|
436
|
+
if ("error" in parsed) {
|
|
437
437
|
writeErr(`\\${ctx.cmdName}: ${parsed.error}\n`);
|
|
438
|
-
return { status:
|
|
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 (
|
|
450
|
+
if ("error" in newOpts) {
|
|
451
451
|
writeErr(`\\${ctx.cmdName}: ${newOpts.error}\n`);
|
|
452
|
-
return { status:
|
|
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:
|
|
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:
|
|
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
|
|
496
|
-
case
|
|
495
|
+
case "aligned":
|
|
496
|
+
case "wrapped":
|
|
497
497
|
return alignedPrinter;
|
|
498
|
-
case
|
|
498
|
+
case "unaligned":
|
|
499
499
|
return unalignedPrinter;
|
|
500
|
-
case
|
|
500
|
+
case "csv":
|
|
501
501
|
return csvPrinter;
|
|
502
|
-
case
|
|
502
|
+
case "json":
|
|
503
503
|
return jsonPrinter;
|
|
504
|
-
case
|
|
504
|
+
case "html":
|
|
505
505
|
return htmlPrinter;
|
|
506
|
-
case
|
|
506
|
+
case "asciidoc":
|
|
507
507
|
return asciidocPrinter;
|
|
508
|
-
case
|
|
508
|
+
case "latex":
|
|
509
509
|
return latexPrinter;
|
|
510
|
-
case
|
|
510
|
+
case "latex-longtable":
|
|
511
511
|
return latexLongtablePrinter;
|
|
512
|
-
case
|
|
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(
|
|
540
|
+
const database = ctx.settings.vars.get("DBNAME") ??
|
|
541
541
|
db.database ??
|
|
542
|
-
|
|
543
|
-
const user = ctx.settings.vars.get(
|
|
542
|
+
"";
|
|
543
|
+
const user = ctx.settings.vars.get("USER") ??
|
|
544
544
|
db.user ??
|
|
545
|
-
|
|
546
|
-
const host = info?.host ?? ctx.settings.vars.get(
|
|
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(
|
|
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([
|
|
552
|
-
rows.push([
|
|
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([
|
|
559
|
+
rows.push(["Host Address", hostaddr]);
|
|
560
560
|
}
|
|
561
561
|
else {
|
|
562
|
-
rows.push([
|
|
562
|
+
rows.push(["Socket Directory", host]);
|
|
563
563
|
}
|
|
564
564
|
}
|
|
565
565
|
else {
|
|
566
|
-
rows.push([
|
|
566
|
+
rows.push(["Host", host]);
|
|
567
567
|
if (hostaddr !== null && hostaddr.length > 0 && hostaddr !== host) {
|
|
568
|
-
rows.push([
|
|
568
|
+
rows.push(["Host Address", hostaddr]);
|
|
569
569
|
}
|
|
570
570
|
}
|
|
571
|
-
rows.push([
|
|
572
|
-
rows.push([
|
|
573
|
-
rows.push([
|
|
574
|
-
rows.push([
|
|
575
|
-
rows.push([
|
|
576
|
-
rows.push([
|
|
577
|
-
rows.push([
|
|
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([
|
|
580
|
-
rows.push([
|
|
579
|
+
rows.push(["SSL Library", tls.library]);
|
|
580
|
+
rows.push(["SSL Protocol", tls.protocol]);
|
|
581
581
|
rows.push([
|
|
582
|
-
|
|
583
|
-
tls.keyBits !== null ? String(tls.keyBits) :
|
|
582
|
+
"SSL Key Bits",
|
|
583
|
+
tls.keyBits !== null ? String(tls.keyBits) : "",
|
|
584
584
|
]);
|
|
585
|
-
rows.push([
|
|
585
|
+
rows.push(["SSL Cipher", tls.cipher]);
|
|
586
586
|
rows.push([
|
|
587
|
-
|
|
588
|
-
tls.compression !==
|
|
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
|
-
|
|
594
|
-
ctx.settings.db?.parameterStatus(
|
|
596
|
+
"Superuser",
|
|
597
|
+
ctx.settings.db?.parameterStatus("is_superuser") ?? "unknown",
|
|
595
598
|
]);
|
|
596
599
|
rows.push([
|
|
597
|
-
|
|
598
|
-
ctx.settings.db?.parameterStatus(
|
|
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(
|
|
606
|
-
return { status:
|
|
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:
|
|
613
|
+
command: "SELECT",
|
|
611
614
|
rowCount: rows.length,
|
|
612
615
|
oid: null,
|
|
613
|
-
fields: [textField(
|
|
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:
|
|
625
|
+
title: "Connection Information",
|
|
623
626
|
topt: {
|
|
624
627
|
...ctx.settings.popt.topt,
|
|
625
|
-
title:
|
|
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:
|
|
634
|
+
return { status: "ok" };
|
|
632
635
|
};
|
|
633
636
|
/** `\conninfo` — print info about the current connection. */
|
|
634
637
|
export const cmdConninfo = {
|
|
635
|
-
name:
|
|
636
|
-
helpKey:
|
|
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:
|
|
646
|
-
helpKey:
|
|
648
|
+
name: "encoding",
|
|
649
|
+
helpKey: "encoding",
|
|
647
650
|
run: async (ctx) => {
|
|
648
|
-
const arg = ctx.nextArg(
|
|
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:
|
|
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:
|
|
667
|
+
return { status: "error" };
|
|
665
668
|
}
|
|
666
669
|
}
|
|
667
670
|
topt.encoding = arg;
|
|
668
|
-
ctx.settings.vars.set(
|
|
669
|
-
return { status:
|
|
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,
|
|
699
|
-
const clientKey = createHmac(
|
|
700
|
-
.update(
|
|
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(
|
|
703
|
-
const serverKey = createHmac(
|
|
704
|
-
.update(
|
|
705
|
+
const storedKey = createHash("sha256").update(clientKey).digest();
|
|
706
|
+
const serverKey = createHmac("sha256", saltedPassword)
|
|
707
|
+
.update("Server Key")
|
|
705
708
|
.digest();
|
|
706
|
-
return (
|
|
709
|
+
return ("SCRAM-SHA-256$" +
|
|
707
710
|
String(iterations) +
|
|
708
|
-
|
|
709
|
-
salt.toString(
|
|
710
|
-
|
|
711
|
-
storedKey.toString(
|
|
712
|
-
|
|
713
|
-
serverKey.toString(
|
|
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:
|
|
727
|
-
helpKey:
|
|
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:
|
|
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:
|
|
745
|
+
return { status: "error" };
|
|
743
746
|
}
|
|
744
|
-
const userArg = ctx.nextArg(
|
|
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(
|
|
751
|
+
const rs = await db.query("SELECT CURRENT_USER");
|
|
749
752
|
const row = rs.rows[0] ?? [];
|
|
750
|
-
user = typeof row[0] ===
|
|
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:
|
|
758
|
+
return { status: "error" };
|
|
756
759
|
}
|
|
757
760
|
if (!user) {
|
|
758
761
|
writeErr(`\\${ctx.cmdName}: could not determine current user\n`);
|
|
759
|
-
return { status:
|
|
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(
|
|
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:
|
|
776
|
+
return { status: "error" };
|
|
772
777
|
}
|
|
773
778
|
if (pw1 !== pw2) {
|
|
774
779
|
writeErr(`\\${ctx.cmdName}: Passwords didn't match.\n`);
|
|
775
|
-
return { status:
|
|
780
|
+
return { status: "error" };
|
|
776
781
|
}
|
|
777
782
|
if (pw1.length === 0) {
|
|
778
783
|
writeErr(`\\${ctx.cmdName}: empty password\n`);
|
|
779
|
-
return { status:
|
|
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 =
|
|
791
|
+
const sql = "ALTER USER " +
|
|
787
792
|
db.escapeIdentifier(user) +
|
|
788
|
-
|
|
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:
|
|
801
|
+
return { status: "error" };
|
|
797
802
|
}
|
|
798
|
-
return { status:
|
|
803
|
+
return { status: "ok" };
|
|
799
804
|
},
|
|
800
805
|
};
|
|
801
806
|
// ---------------------------------------------------------------------------
|