neonctl 2.28.0 → 2.29.1

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 +71 -71
  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 +34 -34
  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
@@ -27,11 +27,11 @@
27
27
  * is the default and `servername` is set. The connection layer wires
28
28
  * `servername` to the configured host before calling us.
29
29
  */
30
- import * as tls from 'node:tls';
31
- import { createHash, createPrivateKey } from 'node:crypto';
32
- import { promises as fs, appendFileSync } from 'node:fs';
33
- import * as path from 'node:path';
34
- import { SSLRequest } from './protocol.js';
30
+ import { createHash, createPrivateKey } from "node:crypto";
31
+ import { appendFileSync, promises as fs } from "node:fs";
32
+ import * as path from "node:path";
33
+ import * as tls from "node:tls";
34
+ import { SSLRequest } from "./protocol.js";
35
35
  /**
36
36
  * Hash of the peer cert for `tls-server-end-point`. libpq always uses SHA-256
37
37
  * of the DER-encoded certificate; we match that.
@@ -43,9 +43,9 @@ export function computeChannelBindingData(cert) {
43
43
  // marks it as `Buffer | undefined` on some versions, hence the guard.
44
44
  const raw = cert.raw;
45
45
  if (!raw || raw.length === 0) {
46
- throw new Error('TLS channel binding: peer certificate has no DER bytes');
46
+ throw new Error("TLS channel binding: peer certificate has no DER bytes");
47
47
  }
48
- return createHash('sha256').update(raw).digest();
48
+ return createHash("sha256").update(raw).digest();
49
49
  }
50
50
  /**
51
51
  * Send SSLRequest, read the 1-byte server response, and either upgrade the
@@ -73,32 +73,32 @@ export function computeChannelBindingData(cert) {
73
73
  * fallback, so a server that does not speak TLS surfaces the handshake
74
74
  * failure rather than a quiet downgrade.
75
75
  */
76
- export async function negotiateTls(socket, sslMode, tlsOpts = {}, fileOpts = {}, negotiation = 'postgres') {
77
- if (sslMode === 'disable') {
78
- return { kind: 'plain', socket };
76
+ export async function negotiateTls(socket, sslMode, tlsOpts = {}, fileOpts = {}, negotiation = "postgres") {
77
+ if (sslMode === "disable") {
78
+ return { kind: "plain", socket };
79
79
  }
80
80
  // Direct SSL (libpq `sslnegotiation=direct`, PG 17+): skip the `SSLRequest`
81
81
  // probe and start the TLS handshake straight away. The parse layer has
82
82
  // already rejected weak sslmodes, so this path is only reached with an
83
83
  // encrypted mode — never falling back to plaintext.
84
- if (negotiation === 'direct') {
84
+ if (negotiation === "direct") {
85
85
  const mergedOpts = await loadTlsFileOptions(tlsOpts, fileOpts, sslMode);
86
86
  return upgradeToTls(socket, mergedOpts, fileOpts.sslkeylogfile, fileOpts.sslkey,
87
87
  /* requireAlpn */ true);
88
88
  }
89
89
  const reply = await sendSslRequest(socket);
90
- if (reply === 'S') {
90
+ if (reply === "S") {
91
91
  const mergedOpts = await loadTlsFileOptions(tlsOpts, fileOpts, sslMode);
92
92
  return upgradeToTls(socket, mergedOpts, fileOpts.sslkeylogfile, fileOpts.sslkey);
93
93
  }
94
94
  // reply === 'N': server refused TLS.
95
- if (sslMode === 'require' ||
96
- sslMode === 'verify-ca' ||
97
- sslMode === 'verify-full') {
95
+ if (sslMode === "require" ||
96
+ sslMode === "verify-ca" ||
97
+ sslMode === "verify-full") {
98
98
  throw new Error(`SSL connection required (sslmode=${sslMode}) but server refused (replied 'N')`);
99
99
  }
100
100
  // 'allow' / 'prefer': fall back to plain text.
101
- return { kind: 'plain', socket };
101
+ return { kind: "plain", socket };
102
102
  }
103
103
  /**
104
104
  * Read each non-empty file path in `fileOpts` and merge the bytes onto a
@@ -126,12 +126,12 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
126
126
  // validate the chain. Mirror that so a stale / placeholder
127
127
  // `sslrootcert=` doesn't blow up sslmode=require connections.
128
128
  const needsRootCert = sslMode === undefined ||
129
- sslMode === 'verify-ca' ||
130
- sslMode === 'verify-full';
129
+ sslMode === "verify-ca" ||
130
+ sslMode === "verify-full";
131
131
  if (needsRootCert &&
132
132
  fileOpts.sslrootcert !== undefined &&
133
- fileOpts.sslrootcert !== '') {
134
- if (fileOpts.sslrootcert === 'system') {
133
+ fileOpts.sslrootcert !== "") {
134
+ if (fileOpts.sslrootcert === "system") {
135
135
  // `sslrootcert=system`: use the OS / OpenSSL trust store instead of a
136
136
  // file. libpq's OpenSSL build honours OpenSSL's $SSL_CERT_FILE (a single
137
137
  // bundle) and $SSL_CERT_DIR (a directory of hashed CA files); we read
@@ -140,11 +140,11 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
140
140
  // for verify-* modes) is left intact.
141
141
  const systemCas = [];
142
142
  const sslCertFile = process.env.SSL_CERT_FILE;
143
- if (sslCertFile !== undefined && sslCertFile !== '') {
144
- systemCas.push(await readPem('sslrootcert', sslCertFile, 'CERTIFICATE'));
143
+ if (sslCertFile !== undefined && sslCertFile !== "") {
144
+ systemCas.push(await readPem("sslrootcert", sslCertFile, "CERTIFICATE"));
145
145
  }
146
146
  const sslCertDir = process.env.SSL_CERT_DIR;
147
- if (sslCertDir !== undefined && sslCertDir !== '') {
147
+ if (sslCertDir !== undefined && sslCertDir !== "") {
148
148
  systemCas.push(...(await readCaDir(sslCertDir)));
149
149
  }
150
150
  if (systemCas.length === 1) {
@@ -155,7 +155,7 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
155
155
  }
156
156
  }
157
157
  else {
158
- merged.ca = await readPem('sslrootcert', fileOpts.sslrootcert, 'CERTIFICATE');
158
+ merged.ca = await readPem("sslrootcert", fileOpts.sslrootcert, "CERTIFICATE");
159
159
  }
160
160
  }
161
161
  // libpq `sslcertmode` gates whether the client cert/key are sent.
@@ -163,30 +163,30 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
163
163
  // - `require`: a cert MUST be configured (we only honour an explicit
164
164
  // `sslcert`, not libpq's default `~/.postgresql/postgresql.crt`).
165
165
  // - `allow` / unset: current behaviour (send when present).
166
- const certMode = fileOpts.sslcertmode ?? 'allow';
167
- const clientCert = fileOpts.sslcert !== undefined && fileOpts.sslcert !== ''
166
+ const certMode = fileOpts.sslcertmode ?? "allow";
167
+ const clientCert = fileOpts.sslcert !== undefined && fileOpts.sslcert !== ""
168
168
  ? fileOpts.sslcert
169
169
  : undefined;
170
- if (certMode === 'require' && clientCert === undefined) {
170
+ if (certMode === "require" && clientCert === undefined) {
171
171
  throw new Error(`sslcertmode value "require" requires a client certificate`);
172
172
  }
173
- if (certMode !== 'disable') {
173
+ if (certMode !== "disable") {
174
174
  if (clientCert !== undefined) {
175
- merged.cert = await readPem('sslcert', clientCert, 'CERTIFICATE');
175
+ merged.cert = await readPem("sslcert", clientCert, "CERTIFICATE");
176
176
  }
177
- if (fileOpts.sslkey !== undefined && fileOpts.sslkey !== '') {
177
+ if (fileOpts.sslkey !== undefined && fileOpts.sslkey !== "") {
178
178
  await assertKeyPermissions(fileOpts.sslkey);
179
- merged.key = await readPem('sslkey', fileOpts.sslkey, 'PRIVATE KEY');
179
+ merged.key = await readPem("sslkey", fileOpts.sslkey, "PRIVATE KEY");
180
180
  }
181
181
  }
182
182
  // CRLs come from a single file (`sslcrl`) and/or every file in a directory
183
183
  // (`sslcrldir`). Node's `crl` option accepts an array of PEM buffers, so we
184
184
  // collect each source and only set `crl` when at least one was read.
185
185
  const crls = [];
186
- if (fileOpts.sslcrl !== undefined && fileOpts.sslcrl !== '') {
187
- crls.push(await readPem('sslcrl', fileOpts.sslcrl));
186
+ if (fileOpts.sslcrl !== undefined && fileOpts.sslcrl !== "") {
187
+ crls.push(await readPem("sslcrl", fileOpts.sslcrl));
188
188
  }
189
- if (fileOpts.sslcrldir !== undefined && fileOpts.sslcrldir !== '') {
189
+ if (fileOpts.sslcrldir !== undefined && fileOpts.sslcrldir !== "") {
190
190
  crls.push(...(await readCrlDir(fileOpts.sslcrldir)));
191
191
  }
192
192
  if (crls.length === 1) {
@@ -198,7 +198,7 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
198
198
  // sslpassword is plumbed through verbatim — OpenSSL applies it when it
199
199
  // sees an encrypted key. Empty string is "no passphrase" (libpq's
200
200
  // convention) so we skip it.
201
- if (fileOpts.sslpassword !== undefined && fileOpts.sslpassword !== '') {
201
+ if (fileOpts.sslpassword !== undefined && fileOpts.sslpassword !== "") {
202
202
  merged.passphrase = fileOpts.sslpassword;
203
203
  }
204
204
  // sslkeylogfile is not a tls.connect option; the keylog listener is wired
@@ -206,7 +206,7 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
206
206
  // by opening it for append, so an unwritable path fails fast at connect
207
207
  // time with `could not open sslkeylogfile "<path>": <reason>` rather than
208
208
  // silently dropping keys mid-handshake.
209
- if (fileOpts.sslkeylogfile !== undefined && fileOpts.sslkeylogfile !== '') {
209
+ if (fileOpts.sslkeylogfile !== undefined && fileOpts.sslkeylogfile !== "") {
210
210
  await assertKeyLogFileWritable(fileOpts.sslkeylogfile);
211
211
  }
212
212
  return merged;
@@ -219,7 +219,7 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
219
219
  */
220
220
  async function assertKeyLogFileWritable(filePath) {
221
221
  try {
222
- const handle = await fs.open(filePath, 'a');
222
+ const handle = await fs.open(filePath, "a");
223
223
  await handle.close();
224
224
  }
225
225
  catch (err) {
@@ -232,19 +232,19 @@ function isPemArmored(bytes) {
232
232
  // A PEM file is ASCII text; scan a bounded prefix (skipping leading
233
233
  // whitespace libpq tolerates) for the armor marker. DER is binary and will
234
234
  // not contain this token at the front.
235
- const head = bytes.subarray(0, 64).toString('latin1');
236
- return head.includes('-----BEGIN');
235
+ const head = bytes.subarray(0, 64).toString("latin1");
236
+ return head.includes("-----BEGIN");
237
237
  }
238
238
  /**
239
239
  * Wrap raw DER bytes in the requested PEM armor: base64 the DER, split into
240
240
  * 64-char lines (the PEM convention), and bracket with the BEGIN/END markers.
241
241
  */
242
242
  function derToPem(der, armor) {
243
- const b64 = der.toString('base64');
243
+ const b64 = der.toString("base64");
244
244
  const lines = b64.match(/.{1,64}/g) ?? [];
245
- const body = lines.join('\n');
245
+ const body = lines.join("\n");
246
246
  const pem = `-----BEGIN ${armor}-----\n${body}\n-----END ${armor}-----\n`;
247
- return Buffer.from(pem, 'ascii');
247
+ return Buffer.from(pem, "ascii");
248
248
  }
249
249
  /**
250
250
  * Convert a DER-encoded private key to canonical PKCS#8 PEM, the way libpq
@@ -258,11 +258,11 @@ function derToPem(der, armor) {
258
258
  */
259
259
  function derPrivateKeyToPem(der) {
260
260
  let lastErr;
261
- for (const type of ['pkcs8', 'pkcs1', 'sec1']) {
261
+ for (const type of ["pkcs8", "pkcs1", "sec1"]) {
262
262
  try {
263
- const key = createPrivateKey({ key: der, format: 'der', type });
264
- const pem = key.export({ format: 'pem', type: 'pkcs8' });
265
- return typeof pem === 'string' ? Buffer.from(pem, 'ascii') : pem;
263
+ const key = createPrivateKey({ key: der, format: "der", type });
264
+ const pem = key.export({ format: "pem", type: "pkcs8" });
265
+ return typeof pem === "string" ? Buffer.from(pem, "ascii") : pem;
266
266
  }
267
267
  catch (err) {
268
268
  lastErr = err;
@@ -291,7 +291,7 @@ async function readPem(label, filePath, derArmor) {
291
291
  if (derArmor !== undefined && !isPemArmored(bytes)) {
292
292
  // Private keys need format-aware decoding (PKCS#8 / PKCS#1 / SEC1);
293
293
  // certs are a single ASN.1 shape and wrap directly.
294
- return derArmor === 'PRIVATE KEY'
294
+ return derArmor === "PRIVATE KEY"
295
295
  ? derPrivateKeyToPem(bytes)
296
296
  : derToPem(bytes, derArmor);
297
297
  }
@@ -310,7 +310,7 @@ async function readPem(label, filePath, derArmor) {
310
310
  * carry a private key here).
311
311
  */
312
312
  async function assertKeyPermissions(keyPath) {
313
- if (process.platform === 'win32')
313
+ if (process.platform === "win32")
314
314
  return;
315
315
  let stat;
316
316
  try {
@@ -355,7 +355,7 @@ async function readCrlDir(dirPath) {
355
355
  for (const entry of entries) {
356
356
  if (!entry.isFile())
357
357
  continue;
358
- out.push(await readPem('sslcrldir', path.join(dirPath, entry.name)));
358
+ out.push(await readPem("sslcrldir", path.join(dirPath, entry.name)));
359
359
  }
360
360
  return out;
361
361
  }
@@ -381,7 +381,7 @@ export async function readCaDir(dirPath) {
381
381
  for (const entry of entries) {
382
382
  if (!entry.isFile())
383
383
  continue;
384
- out.push(await readPem('SSL_CERT_DIR', path.join(dirPath, entry.name), 'CERTIFICATE'));
384
+ out.push(await readPem("SSL_CERT_DIR", path.join(dirPath, entry.name), "CERTIFICATE"));
385
385
  }
386
386
  return out;
387
387
  }
@@ -403,7 +403,7 @@ export function sendSslRequest(socket) {
403
403
  if (chunk.length === 0)
404
404
  return;
405
405
  const first = String.fromCharCode(chunk[0]);
406
- if (first !== 'S' && first !== 'N') {
406
+ if (first !== "S" && first !== "N") {
407
407
  cleanup();
408
408
  reject(new Error(`Unexpected SSLRequest response byte 0x${chunk[0].toString(16)}`));
409
409
  return;
@@ -416,11 +416,11 @@ export function sendSslRequest(socket) {
416
416
  resolve(first);
417
417
  };
418
418
  const cleanup = () => {
419
- socket.removeListener('data', onData);
420
- socket.removeListener('error', onError);
419
+ socket.removeListener("data", onData);
420
+ socket.removeListener("error", onError);
421
421
  };
422
- socket.on('data', onData);
423
- socket.on('error', onError);
422
+ socket.on("data", onData);
423
+ socket.on("error", onError);
424
424
  socket.write(SSLRequest());
425
425
  });
426
426
  }
@@ -455,16 +455,16 @@ export function mapTlsHandshakeError(err, servername, keyPath) {
455
455
  // Encrypted client-key decrypt failure (wrong / missing `sslpassword`).
456
456
  // OpenSSL throws this synchronously out of `tls.connect`; reshape it to
457
457
  // libpq's `could not load private key file "<path>": ... bad decrypt`.
458
- if (code === 'ERR_OSSL_BAD_DECRYPT' || /bad decrypt/i.test(msg)) {
459
- const where = keyPath !== undefined && keyPath !== ''
458
+ if (code === "ERR_OSSL_BAD_DECRYPT" || /bad decrypt/i.test(msg)) {
459
+ const where = keyPath !== undefined && keyPath !== ""
460
460
  ? `private key file "${keyPath}"`
461
- : 'private key file';
461
+ : "private key file";
462
462
  const mapped = new Error(`could not load ${where}: ${msg}`);
463
463
  mapped.cause = err;
464
464
  return mapped;
465
465
  }
466
- if (code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
467
- const host = servername ?? '';
466
+ if (code === "ERR_TLS_CERT_ALTNAME_INVALID") {
467
+ const host = servername ?? "";
468
468
  const mapped = new Error(`server certificate for "${host}" does not match host name "${host}"`);
469
469
  mapped.cause = err;
470
470
  return mapped;
@@ -474,10 +474,10 @@ export function mapTlsHandshakeError(err, servername, keyPath) {
474
474
  // `SELF_SIGNED_CERT_IN_CHAIN`, `CERT_HAS_EXPIRED`, etc. libpq collapses
475
475
  // them all to `certificate verify failed`.
476
476
  const isVerifyFailure = code !== undefined &&
477
- code !== 'ERR_TLS_CERT_ALTNAME_INVALID' &&
477
+ code !== "ERR_TLS_CERT_ALTNAME_INVALID" &&
478
478
  /CERT|SIGNATURE|SELF_SIGNED|UNABLE_TO|CHAIN|EXPIRED|NOT_YET_VALID|INVALID_CA/.test(code);
479
479
  if (isVerifyFailure || /certificate verify failed/i.test(msg)) {
480
- const mapped = new Error('certificate verify failed');
480
+ const mapped = new Error("certificate verify failed");
481
481
  mapped.cause = err;
482
482
  return mapped;
483
483
  }
@@ -485,7 +485,7 @@ export function mapTlsHandshakeError(err, servername, keyPath) {
485
485
  }
486
486
  /** Pull the SNI `servername` from the TLS options for error messages. */
487
487
  function getServername(tlsOpts) {
488
- return typeof tlsOpts.servername === 'string'
488
+ return typeof tlsOpts.servername === "string"
489
489
  ? tlsOpts.servername
490
490
  : undefined;
491
491
  }
@@ -498,7 +498,7 @@ requireAlpn = false) {
498
498
  return new Promise((resolve, reject) => {
499
499
  let tlsSocket;
500
500
  const cleanup = () => {
501
- tlsSocket?.removeListener('error', onError);
501
+ tlsSocket?.removeListener("error", onError);
502
502
  };
503
503
  const onError = (err) => {
504
504
  cleanup();
@@ -523,10 +523,11 @@ requireAlpn = false) {
523
523
  // Enforce the mandatory `postgresql` ALPN for direct SSL. Without
524
524
  // it a TLS terminator / HTTPS proxy with a valid host cert would be
525
525
  // accepted and the startup packet sent in the blind (review #9).
526
- if (requireAlpn && established.alpnProtocol !== 'postgresql') {
526
+ if (requireAlpn &&
527
+ established.alpnProtocol !== "postgresql") {
527
528
  cleanup();
528
529
  established.destroy();
529
- reject(new Error('direct SSL connection requires ALPN, but the server did ' +
530
+ reject(new Error("direct SSL connection requires ALPN, but the server did " +
530
531
  'not negotiate the "postgresql" protocol'));
531
532
  return;
532
533
  }
@@ -538,7 +539,7 @@ requireAlpn = false) {
538
539
  // `getPeerCertificate(true)` for compatibility.
539
540
  const x509 = established.getPeerX509Certificate?.();
540
541
  if (x509?.raw && x509.raw.length > 0) {
541
- channelBindingData = createHash('sha256')
542
+ channelBindingData = createHash("sha256")
542
543
  .update(x509.raw)
543
544
  .digest();
544
545
  }
@@ -549,7 +550,8 @@ requireAlpn = false) {
549
550
  // accept that channel binding is unavailable.
550
551
  const peerCert = established.getPeerCertificate(true);
551
552
  if (peerCert?.raw && peerCert.raw.length > 0) {
552
- channelBindingData = computeChannelBindingData(peerCert);
553
+ channelBindingData =
554
+ computeChannelBindingData(peerCert);
553
555
  }
554
556
  }
555
557
  }
@@ -558,18 +560,22 @@ requireAlpn = false) {
558
560
  // path will fall back to SCRAM-SHA-256 (non-PLUS).
559
561
  channelBindingData = null;
560
562
  }
561
- resolve({ kind: 'tls', socket: established, channelBindingData });
563
+ resolve({
564
+ kind: "tls",
565
+ socket: established,
566
+ channelBindingData,
567
+ });
562
568
  });
563
569
  }
564
570
  catch (err) {
565
571
  reject(mapTlsHandshakeError(err instanceof Error ? err : new Error(String(err)), getServername(tlsOpts), keyPath));
566
572
  return;
567
573
  }
568
- tlsSocket.on('error', onError);
574
+ tlsSocket.on("error", onError);
569
575
  // libpq `sslkeylogfile`: append each emitted key-log line so the
570
576
  // handshake can be decrypted offline. The path was pre-checked for
571
577
  // writability in loadTlsFileOptions.
572
- if (sslkeylogfile !== undefined && sslkeylogfile !== '') {
578
+ if (sslkeylogfile !== undefined && sslkeylogfile !== "") {
573
579
  attachKeyLogListener(tlsSocket, sslkeylogfile);
574
580
  }
575
581
  });
@@ -585,12 +591,12 @@ requireAlpn = false) {
585
591
  * fake socket without a real TLS handshake. Exported for tests.
586
592
  */
587
593
  export function attachKeyLogListener(socket, filePath) {
588
- socket.on('keylog', (line) => {
594
+ socket.on("keylog", (line) => {
589
595
  try {
590
596
  appendFileSync(filePath, line);
591
597
  }
592
598
  catch (err) {
593
- socket.emit('error', err instanceof Error ? err : new Error(String(err)));
599
+ socket.emit("error", err instanceof Error ? err : new Error(String(err)));
594
600
  }
595
601
  });
596
602
  }
@@ -23,9 +23,9 @@ export const createProjectBranchBucket = (apiClient, { projectId, branchId, name
23
23
  }
24
24
  return apiClient.request({
25
25
  path: bucketsPath(projectId, branchId),
26
- method: 'POST',
26
+ method: "POST",
27
27
  body,
28
- format: 'json',
28
+ format: "json",
29
29
  secure: true,
30
30
  });
31
31
  };
@@ -36,8 +36,8 @@ export const createProjectBranchBucket = (apiClient, { projectId, branchId, name
36
36
  */
37
37
  export const listProjectBranchBuckets = (apiClient, { projectId, branchId }) => apiClient.request({
38
38
  path: bucketsPath(projectId, branchId),
39
- method: 'GET',
40
- format: 'json',
39
+ method: "GET",
40
+ format: "json",
41
41
  secure: true,
42
42
  });
43
43
  /**
@@ -47,7 +47,7 @@ export const listProjectBranchBuckets = (apiClient, { projectId, branchId }) =>
47
47
  */
48
48
  export const deleteProjectBranchBucket = (apiClient, { projectId, branchId, bucketName, }) => apiClient.request({
49
49
  path: bucketPath(projectId, branchId, bucketName),
50
- method: 'DELETE',
50
+ method: "DELETE",
51
51
  secure: true,
52
52
  });
53
53
  /**
@@ -57,9 +57,9 @@ export const deleteProjectBranchBucket = (apiClient, { projectId, branchId, buck
57
57
  */
58
58
  export const listProjectBranchBucketObjects = (apiClient, { projectId, branchId, bucketName, ...query }) => apiClient.request({
59
59
  path: `${bucketPath(projectId, branchId, bucketName)}/objects`,
60
- method: 'GET',
60
+ method: "GET",
61
61
  query,
62
- format: 'json',
62
+ format: "json",
63
63
  secure: true,
64
64
  });
65
65
  /**
@@ -79,8 +79,8 @@ export const listProjectBranchBucketObjects = (apiClient, { projectId, branchId,
79
79
  */
80
80
  export const getProjectBranchBucketObject = (apiClient, { projectId, branchId, bucketName, objectKey, }) => apiClient.request({
81
81
  path: `${bucketPath(projectId, branchId, bucketName)}/objects/${encodeURIComponent(objectKey)}/download`,
82
- method: 'GET',
83
- format: 'stream',
82
+ method: "GET",
83
+ format: "stream",
84
84
  secure: true,
85
85
  });
86
86
  /**
@@ -93,7 +93,7 @@ export const getProjectBranchBucketObject = (apiClient, { projectId, branchId, b
93
93
  */
94
94
  export const deleteProjectBranchBucketObject = (apiClient, { projectId, branchId, bucketName, objectKey, }) => apiClient.request({
95
95
  path: `${bucketPath(projectId, branchId, bucketName)}/objects/${encodeURIComponent(objectKey)}`,
96
- method: 'DELETE',
96
+ method: "DELETE",
97
97
  secure: true,
98
98
  });
99
99
  /**
@@ -106,9 +106,9 @@ export const deleteProjectBranchBucketObject = (apiClient, { projectId, branchId
106
106
  */
107
107
  export const deleteProjectBranchBucketObjectsByPrefix = (apiClient, { projectId, branchId, bucketName, prefix, }) => apiClient.request({
108
108
  path: `${bucketPath(projectId, branchId, bucketName)}/objects-by-prefix`,
109
- method: 'DELETE',
109
+ method: "DELETE",
110
110
  query: { prefix },
111
- format: 'json',
111
+ format: "json",
112
112
  secure: true,
113
113
  });
114
114
  /**
@@ -130,7 +130,7 @@ export const deleteProjectBranchBucketObjectsByPrefix = (apiClient, { projectId,
130
130
  * @request POST /projects/{project_id}/branches/{branch_id}/buckets/{bucket_name}/objects/{object_key}/presign
131
131
  */
132
132
  export const presignUpload = (apiClient, { projectId, branchId, bucketName, objectKey, contentType, expiresInSeconds, }) => {
133
- const body = { operation: 'upload' };
133
+ const body = { operation: "upload" };
134
134
  if (contentType !== undefined) {
135
135
  body.content_type = contentType;
136
136
  }
@@ -139,9 +139,9 @@ export const presignUpload = (apiClient, { projectId, branchId, bucketName, obje
139
139
  }
140
140
  return apiClient.request({
141
141
  path: `${bucketPath(projectId, branchId, bucketName)}/objects/${encodeURIComponent(objectKey)}/presign`,
142
- method: 'POST',
142
+ method: "POST",
143
143
  body,
144
- format: 'json',
144
+ format: "json",
145
145
  secure: true,
146
146
  });
147
147
  };
@@ -1,11 +1,11 @@
1
- import { createServer } from 'node:http';
2
- import { fork } from 'node:child_process';
3
- import { join } from 'node:path';
4
- import { expect, test as originalTest } from 'vitest';
5
- import strip from 'strip-ansi';
6
- import emocks from 'emocks';
7
- import express from 'express';
8
- import { log } from '../log';
1
+ import { fork } from "node:child_process";
2
+ import { createServer } from "node:http";
3
+ import { join } from "node:path";
4
+ import emocks from "emocks";
5
+ import express from "express";
6
+ import strip from "strip-ansi";
7
+ import { expect, test as originalTest } from "vitest";
8
+ import { log } from "../log";
9
9
  /**
10
10
  * Reserve a localhost port and close its listener, returning a URL that is guaranteed to
11
11
  * refuse connections right now. Lets a test drive the CLI into a real `ECONNREFUSED`
@@ -13,8 +13,8 @@ import { log } from '../log';
13
13
  */
14
14
  const reserveClosedPort = () => new Promise((resolve, reject) => {
15
15
  const probe = createServer();
16
- probe.on('error', reject);
17
- probe.listen(0, '127.0.0.1', () => {
16
+ probe.on("error", reject);
17
+ probe.listen(0, "127.0.0.1", () => {
18
18
  const { port } = probe.address();
19
19
  probe.close((err) => {
20
20
  if (err) {
@@ -33,12 +33,12 @@ export const test = originalTest.extend({
33
33
  await use(async (mockDir) => {
34
34
  const app = express();
35
35
  app.use(express.json());
36
- app.use('/', emocks(join(process.cwd(), 'mocks', mockDir), {
37
- '404': (_req, res) => res.status(404).send({ message: 'Not Found' }),
36
+ app.use("/", emocks(join(process.cwd(), "mocks", mockDir), {
37
+ "404": (_req, res) => res.status(404).send({ message: "Not Found" }),
38
38
  }));
39
39
  const server = await new Promise((resolve) => {
40
40
  const s = app.listen(0, () => {
41
- log.debug('Mock server listening at %d', s.address().port);
41
+ log.debug("Mock server listening at %d", s.address().port);
42
42
  resolve(s);
43
43
  });
44
44
  });
@@ -64,44 +64,47 @@ export const test = originalTest.extend({
64
64
  await use(async (args, options = {}) => {
65
65
  const apiHost = options.unreachableHost
66
66
  ? await reserveClosedPort()
67
- : `http://localhost:${(await runMockServer(options.mockDir || 'main')).address().port}`;
68
- let output = '';
69
- let error = '';
70
- const cp = fork(join(process.cwd(), './dist/index.js'), [
71
- '--api-host',
67
+ : `http://localhost:${(await runMockServer(options.mockDir || "main")).address().port}`;
68
+ let output = "";
69
+ let error = "";
70
+ const cp = fork(join(process.cwd(), "./dist/index.js"), [
71
+ "--api-host",
72
72
  apiHost,
73
- '--output',
74
- options.output ?? (options.outputTable ? 'table' : 'yaml'),
75
- '--api-key',
76
- 'test-key',
77
- '--no-analytics',
73
+ "--output",
74
+ options.output ?? (options.outputTable ? "table" : "yaml"),
75
+ "--api-key",
76
+ "test-key",
77
+ "--no-analytics",
78
78
  ...args,
79
79
  ], {
80
- stdio: 'pipe',
80
+ stdio: "pipe",
81
+ ...(options.cwd ? { cwd: options.cwd } : {}),
81
82
  env: {
82
- PATH: `mocks/bin:${process.env.PATH}`,
83
+ PATH: `${join(process.cwd(), "mocks/bin")}:${process.env.PATH}`,
83
84
  ...(options.env ?? {}),
84
85
  },
85
86
  });
86
87
  return new Promise((resolve, reject) => {
87
- cp.stdout?.on('data', (data) => {
88
+ cp.stdout?.on("data", (data) => {
88
89
  output += data.toString();
89
90
  });
90
- cp.stderr?.on('data', (data) => {
91
+ cp.stderr?.on("data", (data) => {
91
92
  error += data.toString();
92
93
  log.error(data.toString());
93
94
  });
94
- cp.on('error', (err) => {
95
+ cp.on("error", (err) => {
95
96
  log.error(err);
96
97
  throw err;
97
98
  });
98
- cp.on('close', (code) => {
99
+ cp.on("close", (code) => {
99
100
  try {
100
101
  expect(code).toBe(options?.code ?? 0);
101
102
  expect(output).toMatchSnapshot();
102
103
  if (options.stderr !== undefined) {
103
- expect(strip(error).replace(/\s+/g, ' ').trim()).toEqual(typeof options.stderr === 'string'
104
- ? options.stderr.toString().replace(/\s+/g, ' ')
104
+ expect(strip(error).replace(/\s+/g, " ").trim()).toEqual(typeof options.stderr === "string"
105
+ ? options.stderr
106
+ .toString()
107
+ .replace(/\s+/g, " ")
105
108
  : options.stderr);
106
109
  }
107
110
  resolve();
@@ -1,9 +1,9 @@
1
- import { log } from '../log';
2
- import { OAuth2Server } from 'oauth2-mock-server';
1
+ import { OAuth2Server } from "oauth2-mock-server";
2
+ import { log } from "../log";
3
3
  export const startOauthServer = async () => {
4
4
  const server = new OAuth2Server();
5
- await server.issuer.keys.generate('RS256');
6
- await server.start(0, 'localhost');
7
- log.debug('Started OAuth server on port %d', server.address().port);
5
+ await server.issuer.keys.generate("RS256");
6
+ await server.start(0, "localhost");
7
+ log.debug("Started OAuth server on port %d", server.address().port);
8
8
  return server;
9
9
  };