neonctl 2.37.1 → 2.38.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 (159) hide show
  1. package/README.md +15 -700
  2. package/bin/cli.js +3 -0
  3. package/package.json +8 -75
  4. package/dist/analytics.js +0 -168
  5. package/dist/api.js +0 -720
  6. package/dist/auth.js +0 -125
  7. package/dist/callback.html +0 -60
  8. package/dist/cli.js +0 -9
  9. package/dist/commands/api.js +0 -278
  10. package/dist/commands/auth.js +0 -214
  11. package/dist/commands/bootstrap.js +0 -481
  12. package/dist/commands/branches.js +0 -488
  13. package/dist/commands/bucket.js +0 -549
  14. package/dist/commands/checkout.js +0 -321
  15. package/dist/commands/config.js +0 -626
  16. package/dist/commands/connection_string.js +0 -172
  17. package/dist/commands/data_api.js +0 -284
  18. package/dist/commands/databases.js +0 -82
  19. package/dist/commands/deploy.js +0 -26
  20. package/dist/commands/dev.js +0 -698
  21. package/dist/commands/diff.js +0 -222
  22. package/dist/commands/env.js +0 -189
  23. package/dist/commands/functions.js +0 -373
  24. package/dist/commands/index.js +0 -62
  25. package/dist/commands/init.js +0 -73
  26. package/dist/commands/inspect.js +0 -65
  27. package/dist/commands/ip_allow.js +0 -137
  28. package/dist/commands/link.js +0 -1121
  29. package/dist/commands/neon_auth.js +0 -1028
  30. package/dist/commands/operations.js +0 -28
  31. package/dist/commands/orgs.js +0 -24
  32. package/dist/commands/projects.js +0 -413
  33. package/dist/commands/psql.js +0 -62
  34. package/dist/commands/roles.js +0 -65
  35. package/dist/commands/schema_diff.js +0 -151
  36. package/dist/commands/set_context.js +0 -29
  37. package/dist/commands/snapshots.js +0 -455
  38. package/dist/commands/status.js +0 -40
  39. package/dist/commands/user.js +0 -15
  40. package/dist/commands/vpc_endpoints.js +0 -134
  41. package/dist/config.js +0 -11
  42. package/dist/config_format.js +0 -72
  43. package/dist/context.js +0 -231
  44. package/dist/current_branch_fast_path.js +0 -55
  45. package/dist/dev/env.js +0 -244
  46. package/dist/dev/functions.js +0 -70
  47. package/dist/dev/inputs.js +0 -63
  48. package/dist/dev/runtime.js +0 -146
  49. package/dist/env.js +0 -36
  50. package/dist/env_file.js +0 -159
  51. package/dist/errors.js +0 -80
  52. package/dist/functions_api.js +0 -48
  53. package/dist/help.js +0 -146
  54. package/dist/index.js +0 -240
  55. package/dist/log.js +0 -18
  56. package/dist/parameters.gen.js +0 -480
  57. package/dist/pkg.js +0 -25
  58. package/dist/psql/cli.js +0 -53
  59. package/dist/psql/command/cmd_cond.js +0 -437
  60. package/dist/psql/command/cmd_connect.js +0 -820
  61. package/dist/psql/command/cmd_copy.js +0 -1035
  62. package/dist/psql/command/cmd_describe.js +0 -1815
  63. package/dist/psql/command/cmd_format.js +0 -948
  64. package/dist/psql/command/cmd_io.js +0 -2193
  65. package/dist/psql/command/cmd_lo.js +0 -393
  66. package/dist/psql/command/cmd_meta.js +0 -969
  67. package/dist/psql/command/cmd_misc.js +0 -187
  68. package/dist/psql/command/cmd_pipeline.js +0 -1148
  69. package/dist/psql/command/cmd_restrict.js +0 -171
  70. package/dist/psql/command/cmd_show.js +0 -766
  71. package/dist/psql/command/dispatch.js +0 -343
  72. package/dist/psql/command/inputQueue.js +0 -42
  73. package/dist/psql/command/shared.js +0 -71
  74. package/dist/psql/complete/filenames.js +0 -139
  75. package/dist/psql/complete/index.js +0 -104
  76. package/dist/psql/complete/matcher.js +0 -315
  77. package/dist/psql/complete/psqlVars.js +0 -249
  78. package/dist/psql/complete/queries.js +0 -493
  79. package/dist/psql/complete/rules.js +0 -2424
  80. package/dist/psql/core/common.js +0 -1253
  81. package/dist/psql/core/help.js +0 -576
  82. package/dist/psql/core/mainloop.js +0 -1360
  83. package/dist/psql/core/prompt.js +0 -439
  84. package/dist/psql/core/settings.js +0 -686
  85. package/dist/psql/core/sqlHelp.js +0 -1066
  86. package/dist/psql/core/startup.js +0 -846
  87. package/dist/psql/core/syncVars.js +0 -116
  88. package/dist/psql/core/variables.js +0 -287
  89. package/dist/psql/describe/formatters.js +0 -1330
  90. package/dist/psql/describe/processNamePattern.js +0 -270
  91. package/dist/psql/describe/queries.js +0 -2452
  92. package/dist/psql/describe/versionGate.js +0 -44
  93. package/dist/psql/index.js +0 -2030
  94. package/dist/psql/io/history.js +0 -299
  95. package/dist/psql/io/input.js +0 -120
  96. package/dist/psql/io/lineEditor/buffer.js +0 -325
  97. package/dist/psql/io/lineEditor/complete.js +0 -227
  98. package/dist/psql/io/lineEditor/filename.js +0 -159
  99. package/dist/psql/io/lineEditor/index.js +0 -893
  100. package/dist/psql/io/lineEditor/keymap.js +0 -745
  101. package/dist/psql/io/lineEditor/vt100.js +0 -363
  102. package/dist/psql/io/pgpass.js +0 -202
  103. package/dist/psql/io/pgservice.js +0 -194
  104. package/dist/psql/io/psqlrc.js +0 -422
  105. package/dist/psql/print/aligned.js +0 -1748
  106. package/dist/psql/print/asciidoc.js +0 -230
  107. package/dist/psql/print/crosstab.js +0 -463
  108. package/dist/psql/print/csv.js +0 -76
  109. package/dist/psql/print/html.js +0 -240
  110. package/dist/psql/print/json.js +0 -96
  111. package/dist/psql/print/latex.js +0 -379
  112. package/dist/psql/print/pager.js +0 -267
  113. package/dist/psql/print/troff.js +0 -240
  114. package/dist/psql/print/unaligned.js +0 -99
  115. package/dist/psql/print/units.js +0 -188
  116. package/dist/psql/scanner/slash.js +0 -515
  117. package/dist/psql/scanner/sql.js +0 -914
  118. package/dist/psql/scanner/stringutils.js +0 -394
  119. package/dist/psql/types/backslash.js +0 -1
  120. package/dist/psql/types/connection.js +0 -1
  121. package/dist/psql/types/index.js +0 -7
  122. package/dist/psql/types/printer.js +0 -1
  123. package/dist/psql/types/repl.js +0 -1
  124. package/dist/psql/types/scanner.js +0 -24
  125. package/dist/psql/types/settings.js +0 -1
  126. package/dist/psql/types/variables.js +0 -1
  127. package/dist/psql/wire/connection.js +0 -2857
  128. package/dist/psql/wire/copy.js +0 -108
  129. package/dist/psql/wire/notify.js +0 -59
  130. package/dist/psql/wire/pipeline.js +0 -521
  131. package/dist/psql/wire/protocol.js +0 -466
  132. package/dist/psql/wire/sasl.js +0 -294
  133. package/dist/psql/wire/tls.js +0 -602
  134. package/dist/storage_api.js +0 -147
  135. package/dist/test_utils/fixtures.js +0 -121
  136. package/dist/test_utils/oauth_server.js +0 -9
  137. package/dist/types.js +0 -1
  138. package/dist/utils/ai_gateway_notice.js +0 -180
  139. package/dist/utils/api_enums.js +0 -33
  140. package/dist/utils/auth.js +0 -5
  141. package/dist/utils/branch_notice.js +0 -22
  142. package/dist/utils/branch_picker.js +0 -103
  143. package/dist/utils/compute_units.js +0 -28
  144. package/dist/utils/config_diff.js +0 -185
  145. package/dist/utils/enrichers.js +0 -161
  146. package/dist/utils/esbuild.js +0 -158
  147. package/dist/utils/formats.js +0 -18
  148. package/dist/utils/git_diff.js +0 -90
  149. package/dist/utils/inspect_db.js +0 -153
  150. package/dist/utils/inspect_queries.js +0 -372
  151. package/dist/utils/middlewares.js +0 -20
  152. package/dist/utils/openapi.js +0 -114
  153. package/dist/utils/package_manager.js +0 -68
  154. package/dist/utils/point_in_time.js +0 -56
  155. package/dist/utils/psql.js +0 -120
  156. package/dist/utils/string.js +0 -5
  157. package/dist/utils/ui.js +0 -59
  158. package/dist/utils/zip.js +0 -4
  159. package/dist/writer.js +0 -97
@@ -1,379 +0,0 @@
1
- import { boolDisplayOf, renderCellValue } from "./units.js";
2
- /**
3
- * LaTeX printers — `latex` (tabular) and `latex-longtable` (longtable).
4
- *
5
- * Mirrors print.c `print_latex_text`, `print_latex_vertical`, and
6
- * `print_latex_longtable_text`. Expanded mode for both `latex` and
7
- * `latex-longtable` falls through to the vertical renderer
8
- * (`print_latex_vertical`) — that's how upstream dispatches.
9
- *
10
- * Border behavior (flat tabular, from print.c):
11
- * - `topt.border` is clamped to 0..3.
12
- * - tabular column spec gets ` | ` between columns when border > 0,
13
- * and a leading/trailing `|` at border >= 2.
14
- * - border == 2 emits `\hline` around header and at end of table.
15
- * - border == 3 emits `\hline` after every row.
16
- *
17
- * Border behavior (expanded, from print.c):
18
- * - `topt.border` is clamped to 0..2 (note: not 0..3).
19
- * - tabular column spec is `cl`, `c|l`, or `|c|l|` for border 0/1/2.
20
- * - border >= 1 emits `\hline` between records.
21
- * - border == 2 wraps each "Record N" header in `\hline` lines.
22
- *
23
- * Numeric columns (per the OID heuristic) get the `r` alignment letter;
24
- * everything else gets `l`. That letter is passed straight through to
25
- * LaTeX's column spec.
26
- *
27
- * Escape: 14 LaTeX-special characters are rewritten. Embedded newlines
28
- * in a cell turn into `\\` (LaTeX line break) for `latex`. The
29
- * longtable variant uses the same escape but wraps cells in
30
- * `\raggedright{...}` and ends rows with `\tabularnewline`.
31
- */
32
- // INT2, INT4, INT8, FLOAT4, FLOAT8, NUMERIC, INTERVAL.
33
- const NUMERIC_OIDS = new Set([21, 23, 20, 700, 701, 1700, 1186]);
34
- export const latexPrinter = {
35
- format: "latex",
36
- printQuery(rs, opts, out) {
37
- const topt = opts.topt;
38
- if (topt.expanded === "on") {
39
- return printLatexVertical(rs, opts, out);
40
- }
41
- return printLatexFlat(rs, opts, out);
42
- },
43
- };
44
- const printLatexFlat = (rs, opts, out) => {
45
- const topt = opts.topt;
46
- const tuplesOnly = topt.tuplesOnly;
47
- const startTable = topt.startTable;
48
- const stopTable = topt.stopTable;
49
- const border = clampBorder(topt.border, 3);
50
- const nullPrint = opts.nullPrint !== "" ? opts.nullPrint : topt.nullPrint;
51
- const title = opts.title ?? topt.title;
52
- const footers = opts.footers ?? topt.footers;
53
- const headers = rs.fields.map((f) => f.name);
54
- const ncols = rs.fields.length;
55
- const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ? "r" : "l");
56
- const boolDisplay = boolDisplayOf(topt);
57
- const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
58
- let buf = "";
59
- if (startTable) {
60
- if (!tuplesOnly && title) {
61
- buf += "\\begin{center}\n";
62
- buf += escapeLatex(title);
63
- buf += "\n\\end{center}\n\n";
64
- }
65
- buf += "\\begin{tabular}{";
66
- if (border >= 2)
67
- buf += "| ";
68
- aligns.forEach((a, idx) => {
69
- buf += a;
70
- if (border !== 0 && idx < ncols - 1)
71
- buf += " | ";
72
- });
73
- if (border >= 2)
74
- buf += " |";
75
- buf += "}\n";
76
- if (!tuplesOnly && border >= 2)
77
- buf += "\\hline\n";
78
- if (!tuplesOnly) {
79
- headers.forEach((h, idx) => {
80
- if (idx !== 0)
81
- buf += " & ";
82
- buf += "\\textit{" + escapeLatex(h) + "}";
83
- });
84
- buf += " \\\\\n";
85
- buf += "\\hline\n";
86
- }
87
- }
88
- cells.forEach((row) => {
89
- row.forEach((value, idx) => {
90
- buf += escapeLatex(value);
91
- if (idx === ncols - 1) {
92
- buf += " \\\\\n";
93
- if (border === 3)
94
- buf += "\\hline\n";
95
- }
96
- else {
97
- buf += " & ";
98
- }
99
- });
100
- });
101
- if (stopTable) {
102
- if (border === 2)
103
- buf += "\\hline\n";
104
- buf += "\\end{tabular}\n\n\\noindent ";
105
- if (!tuplesOnly) {
106
- const effective = effectiveFooters(rs, topt, footers);
107
- for (const f of effective) {
108
- buf += escapeLatex(f) + " \\\\\n";
109
- }
110
- }
111
- buf += "\n";
112
- }
113
- out.write(buf);
114
- return Promise.resolve();
115
- };
116
- const printLatexVertical = (rs, opts, out) => {
117
- const topt = opts.topt;
118
- const tuplesOnly = topt.tuplesOnly;
119
- const startTable = topt.startTable;
120
- const stopTable = topt.stopTable;
121
- const border = clampBorder(topt.border, 2);
122
- const nullPrint = opts.nullPrint !== "" ? opts.nullPrint : topt.nullPrint;
123
- const title = opts.title ?? topt.title;
124
- const footers = opts.footers ?? topt.footers;
125
- const headers = rs.fields.map((f) => f.name);
126
- const boolDisplay = boolDisplayOf(topt);
127
- const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
128
- let buf = "";
129
- if (startTable) {
130
- if (!tuplesOnly && title) {
131
- buf += "\\begin{center}\n";
132
- buf += escapeLatex(title);
133
- buf += "\n\\end{center}\n\n";
134
- }
135
- buf += "\\begin{tabular}{";
136
- if (border === 0)
137
- buf += "cl";
138
- else if (border === 1)
139
- buf += "c|l";
140
- else
141
- buf += "|c|l|";
142
- buf += "}\n";
143
- }
144
- let record = topt.prior + 1;
145
- cells.forEach((row) => {
146
- if (!tuplesOnly) {
147
- if (border === 2) {
148
- buf += "\\hline\n";
149
- buf += `\\multicolumn{2}{|c|}{\\textit{Record ${String(record)}}} \\\\\n`;
150
- }
151
- else {
152
- buf += `\\multicolumn{2}{c}{\\textit{Record ${String(record)}}} \\\\\n`;
153
- }
154
- record += 1;
155
- }
156
- if (border >= 1)
157
- buf += "\\hline\n";
158
- row.forEach((value, idx) => {
159
- buf += escapeLatex(headers[idx]);
160
- buf += " & ";
161
- buf += escapeLatex(value);
162
- buf += " \\\\\n";
163
- });
164
- });
165
- if (stopTable) {
166
- if (border === 2)
167
- buf += "\\hline\n";
168
- buf += "\\end{tabular}\n\n\\noindent ";
169
- // Expanded mode does NOT include the default "(N rows)" footer;
170
- // only user-supplied footers (matches print_latex_vertical).
171
- if (!tuplesOnly && footers && footers.length > 0) {
172
- for (const f of footers) {
173
- buf += escapeLatex(f) + " \\\\\n";
174
- }
175
- }
176
- buf += "\n";
177
- }
178
- out.write(buf);
179
- return Promise.resolve();
180
- };
181
- export const latexLongtablePrinter = {
182
- format: "latex-longtable",
183
- printQuery(rs, opts, out) {
184
- // Upstream dispatch sends both `latex` and `latex-longtable` to
185
- // `print_latex_vertical` when expanded is on (cf. print.c).
186
- if (opts.topt.expanded === "on") {
187
- return printLatexVertical(rs, opts, out);
188
- }
189
- const topt = opts.topt;
190
- const tuplesOnly = topt.tuplesOnly;
191
- const startTable = topt.startTable;
192
- const stopTable = topt.stopTable;
193
- const border = clampBorder(topt.border, 3);
194
- const nullPrint = opts.nullPrint !== "" ? opts.nullPrint : topt.nullPrint;
195
- const title = opts.title ?? topt.title;
196
- const headers = rs.fields.map((f) => f.name);
197
- const ncols = rs.fields.length;
198
- const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ? "r" : "l");
199
- const boolDisplay = boolDisplayOf(topt);
200
- const cells = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
201
- // `topt.tableAttr` for longtable encodes per-column widths in a
202
- // whitespace-separated list, consumed left-to-right with a fall
203
- // back to the previous value once exhausted.
204
- const widths = (topt.tableAttr ?? "")
205
- .split(/[\s]+/)
206
- .filter((w) => w !== "");
207
- let widthCursor = 0;
208
- let lastWidth = null;
209
- let buf = "";
210
- if (startTable) {
211
- buf += "\\begin{longtable}{";
212
- if (border >= 2)
213
- buf += "| ";
214
- aligns.forEach((a, idx) => {
215
- if (a === "l" && widths.length > 0) {
216
- let w = null;
217
- if (widthCursor < widths.length) {
218
- w = widths[widthCursor];
219
- widthCursor += 1;
220
- lastWidth = w;
221
- }
222
- else if (lastWidth !== null) {
223
- w = lastWidth;
224
- }
225
- if (w !== null) {
226
- buf += `p{${w}\\textwidth}`;
227
- }
228
- else {
229
- buf += "l";
230
- }
231
- }
232
- else {
233
- buf += a;
234
- }
235
- if (border !== 0 && idx < ncols - 1)
236
- buf += " | ";
237
- });
238
- if (border >= 2)
239
- buf += " |";
240
- buf += "}\n";
241
- if (!tuplesOnly) {
242
- // firsthead
243
- if (border >= 2)
244
- buf += "\\toprule\n";
245
- headers.forEach((h, idx) => {
246
- if (idx !== 0)
247
- buf += " & ";
248
- buf += "\\small\\textbf{\\textit{" + escapeLatex(h) + "}}";
249
- });
250
- buf += " \\\\\n";
251
- buf += "\\midrule\n\\endfirsthead\n";
252
- // continuation heads
253
- if (border >= 2)
254
- buf += "\\toprule\n";
255
- headers.forEach((h, idx) => {
256
- if (idx !== 0)
257
- buf += " & ";
258
- buf += "\\small\\textbf{\\textit{" + escapeLatex(h) + "}}";
259
- });
260
- buf += " \\\\\n";
261
- if (border !== 3)
262
- buf += "\\midrule\n";
263
- buf += "\\endhead\n";
264
- if (title) {
265
- if (border === 2)
266
- buf += "\\bottomrule\n";
267
- buf +=
268
- "\\caption[" +
269
- escapeLatex(title) +
270
- " (Continued)]{" +
271
- escapeLatex(title) +
272
- "}\n\\endfoot\n";
273
- if (border === 2)
274
- buf += "\\bottomrule\n";
275
- buf +=
276
- "\\caption[" +
277
- escapeLatex(title) +
278
- "]{" +
279
- escapeLatex(title) +
280
- "}\n\\endlastfoot\n";
281
- }
282
- else if (border >= 2) {
283
- buf += "\\bottomrule\n\\endfoot\n";
284
- buf += "\\bottomrule\n\\endlastfoot\n";
285
- }
286
- }
287
- }
288
- // Cells. Upstream interleaves `\n&\n` between in-row cells (and
289
- // emits `\tabularnewline` to end a row), wrapping each value in
290
- // `\raggedright{...}`.
291
- let cellIdx = 0;
292
- cells.forEach((row) => {
293
- row.forEach((value) => {
294
- if (cellIdx !== 0 && cellIdx % ncols !== 0)
295
- buf += "\n&\n";
296
- buf += "\\raggedright{" + escapeLatex(value) + "}";
297
- if ((cellIdx + 1) % ncols === 0) {
298
- buf += " \\tabularnewline\n";
299
- if (border === 3)
300
- buf += " \\hline\n";
301
- }
302
- cellIdx += 1;
303
- });
304
- });
305
- if (stopTable)
306
- buf += "\\end{longtable}\n";
307
- out.write(buf);
308
- return Promise.resolve();
309
- },
310
- };
311
- const clampBorder = (b, max) => {
312
- if (b > max)
313
- return max;
314
- if (b < 0)
315
- return 0;
316
- return b;
317
- };
318
- const effectiveFooters = (rs, topt, footers) => {
319
- if (footers && footers.length > 0)
320
- return footers;
321
- if (topt.defaultFooter) {
322
- const n = rs.rows.length;
323
- return [`(${String(n)} ${n === 1 ? "row" : "rows"})`];
324
- }
325
- return [];
326
- };
327
- const escapeLatex = (input) => {
328
- let out = "";
329
- for (const ch of input) {
330
- switch (ch) {
331
- case "#":
332
- out += "\\#";
333
- break;
334
- case "$":
335
- out += "\\$";
336
- break;
337
- case "%":
338
- out += "\\%";
339
- break;
340
- case "&":
341
- out += "\\&";
342
- break;
343
- case "<":
344
- out += "\\textless{}";
345
- break;
346
- case ">":
347
- out += "\\textgreater{}";
348
- break;
349
- case "\\":
350
- out += "\\textbackslash{}";
351
- break;
352
- case "^":
353
- out += "\\^{}";
354
- break;
355
- case "_":
356
- out += "\\_";
357
- break;
358
- case "{":
359
- out += "\\{";
360
- break;
361
- case "|":
362
- out += "\\textbar{}";
363
- break;
364
- case "}":
365
- out += "\\}";
366
- break;
367
- case "~":
368
- out += "\\~{}";
369
- break;
370
- case "\n":
371
- out += "\\\\";
372
- break;
373
- default:
374
- out += ch;
375
- }
376
- }
377
- return out;
378
- };
379
- const renderCell = (cell, nullPrint, numericLocale, dataTypeID, boolDisplay) => renderCellValue(cell, nullPrint, numericLocale, dataTypeID, boolDisplay);
@@ -1,267 +0,0 @@
1
- import { accessSync, constants as fsConstants } from "node:fs";
2
- import { spawn } from "child_process";
3
- import { basename, delimiter, isAbsolute, join } from "path";
4
- /**
5
- * Can `command` be spawned with `shell:false`? A bare name is searched on
6
- * `$PATH`; a path is checked directly. Used to avoid `spawn(missingBinary,
7
- * { shell:false })`, which does NOT throw synchronously (our try/catch never
8
- * fires) but emits an async ENOENT — leaving writes to the dead child's stdin
9
- * to vanish into a blank terminal (review item #19).
10
- */
11
- const pagerCommandResolvable = (command) => {
12
- const ok = (p) => {
13
- try {
14
- accessSync(p, fsConstants.X_OK);
15
- return true;
16
- }
17
- catch {
18
- // On Windows X_OK is not meaningful and a bare name omits `.exe`; fall
19
- // back to a plain existence probe with common executable extensions.
20
- if (process.platform === "win32") {
21
- for (const ext of ["", ".exe", ".cmd", ".bat"]) {
22
- try {
23
- accessSync(p + ext, fsConstants.F_OK);
24
- return true;
25
- }
26
- catch {
27
- /* keep trying */
28
- }
29
- }
30
- }
31
- return false;
32
- }
33
- };
34
- if (command.includes("/") ||
35
- command.includes("\\") ||
36
- isAbsolute(command)) {
37
- return ok(command);
38
- }
39
- return (process.env.PATH ?? "")
40
- .split(delimiter)
41
- .filter((d) => d.length > 0)
42
- .some((dir) => ok(join(dir, command)));
43
- };
44
- /**
45
- * Resolve the pager command string. Mirrors upstream:
46
- * PSQL_PAGER → PAGER → DEFAULT_PAGER (`less` on POSIX, none on Windows).
47
- *
48
- * Empty / whitespace-only env values FALL THROUGH to the next candidate. This
49
- * deliberately diverges from strict upstream (where `PSQL_PAGER=''` would
50
- * disable the pager outright) because in Node a spawned child cannot easily
51
- * "unset" an inherited env var — tests have to override it with the empty
52
- * string. Treating empty values as "unset" matches the conformance spec
53
- * (`tests/psql-conformance/tap/030_pager.spec.ts`) and lets `PSQL_PAGER=''`
54
- * fall through to PAGER, which is the user-friendly interpretation.
55
- *
56
- * Users who want to disable the pager unconditionally should set
57
- * `\pset pager off` (preferred) or unset both env vars before launch.
58
- */
59
- const resolvePagerCmd = (opts) => {
60
- const env = opts.env ?? process.env;
61
- const candidates = [
62
- opts.pagerCmd,
63
- env.PSQL_PAGER,
64
- env.PAGER,
65
- ];
66
- for (const c of candidates) {
67
- if (c === undefined)
68
- continue;
69
- // Empty or whitespace-only → treat as "not set" and try the next slot.
70
- if (/^\s*$/.test(c))
71
- continue;
72
- return c;
73
- }
74
- // DEFAULT_PAGER: `less` on POSIX; nothing on Windows.
75
- if (process.platform === "win32")
76
- return "";
77
- return "less";
78
- };
79
- const getIsTty = (opts) => {
80
- if (opts.isTty !== undefined)
81
- return opts.isTty;
82
- const stream = opts.stdout ?? process.stdout;
83
- // Some test streams won't have isTTY.
84
- const tty = stream.isTTY;
85
- return Boolean(tty);
86
- };
87
- const getTerminalHeight = (opts) => {
88
- if (opts.terminalHeight !== undefined)
89
- return opts.terminalHeight;
90
- const stream = opts.stdout ?? process.stdout;
91
- const rows = stream.rows;
92
- // If we can't tell, fall back to 24 (classic VT100 default).
93
- return typeof rows === "number" && rows > 0 ? rows : 24;
94
- };
95
- /** Standalone helper to determine whether a pager is needed at all. */
96
- export const isPagerNeeded = (opts) => {
97
- if (opts.pager === "off")
98
- return false;
99
- const cmd = resolvePagerCmd(opts);
100
- if (cmd === "")
101
- return false;
102
- if (opts.pager === "always")
103
- return true;
104
- // pager === 'on'
105
- if (!getIsTty(opts))
106
- return false;
107
- if (opts.lines === undefined)
108
- return false;
109
- const threshold = Math.max(opts.pagerMinLines, getTerminalHeight(opts));
110
- return opts.lines >= threshold;
111
- };
112
- /**
113
- * Decide whether a result of `rowCount` rows by `colCount` columns should be
114
- * routed through the pager when written to `output`.
115
- *
116
- * NOTE on `pager === 'always'`: the TTY check is INTENTIONALLY skipped in
117
- * this case. Upstream's `\pset pager always` is a user-explicit "force the
118
- * pager on" override; honouring it on a pipe matches the integration test
119
- * harness contract (see `tests/psql-conformance/tap/030_pager.spec.ts`) where
120
- * the child process has no controlling TTY but the spec still requires the
121
- * configured PAGER to be invoked. For `pager === 'on'` (auto mode) we keep
122
- * the TTY guard so non-interactive runs don't spuriously spawn `less`.
123
- */
124
- export const shouldPage = (opts) => {
125
- if (opts.pager === "off")
126
- return false;
127
- if (opts.redirectedOutput)
128
- return false;
129
- // Rough heuristic for "lines" — header (3) + rows + footer (1). Matches
130
- // upstream `IsPagerNeeded` which counts rendered table lines.
131
- const HEADER_LINES = 3;
132
- const FOOTER_LINES = 1;
133
- const estimatedLines = HEADER_LINES + Math.max(0, opts.rowCount) + FOOTER_LINES;
134
- // TTY check: explicit override wins, else inspect the stream's own isTTY.
135
- const isTty = opts.isTty !== undefined
136
- ? opts.isTty
137
- : Boolean(opts.output.isTTY);
138
- // `pager === 'always'` bypasses the TTY guard — see the docstring above.
139
- // The remaining gates (resolved cmd, redirected output, off) are enforced
140
- // inside `isPagerNeeded`.
141
- if (opts.pager !== "always" && !isTty)
142
- return false;
143
- return isPagerNeeded({
144
- pager: opts.pager,
145
- pagerMinLines: opts.pagerMinLines,
146
- pagerCmd: opts.pagerCmd,
147
- env: opts.env,
148
- stdout: opts.output,
149
- isTty,
150
- terminalHeight: opts.terminalHeight,
151
- lines: estimatedLines,
152
- });
153
- };
154
- const SHELL_META = /[\s|;><]/;
155
- const parsePagerCmd = (cmd) => {
156
- // Match upstream behavior: when the value looks shell-y, hand it off to
157
- // /bin/sh -c. Otherwise treat it as a direct argv[0].
158
- if (SHELL_META.test(cmd)) {
159
- return { command: cmd, args: [], shell: true };
160
- }
161
- return { command: cmd, args: [], shell: false };
162
- };
163
- const buildPagerEnv = (cmd, baseEnv) => {
164
- const env = { ...baseEnv };
165
- // psql sets LESS=FRX by default; mirror it when the resolved pager is
166
- // `less` and the caller hasn't already set LESS.
167
- if (env.LESS === undefined) {
168
- // Pull out the first whitespace-separated token to detect `less` even
169
- // when args follow (e.g. "less -S").
170
- const firstToken = cmd.trim().split(/\s+/, 1)[0] ?? "";
171
- const program = basename(firstToken);
172
- if (program === "less") {
173
- env.LESS = "FRX";
174
- }
175
- }
176
- return env;
177
- };
178
- const noOpHandle = (out) => ({
179
- out,
180
- spawned: false,
181
- close: () => Promise.resolve(0),
182
- });
183
- /**
184
- * Returns a PagerHandle. Caller writes data to `out`, then calls `close()`.
185
- * If no pager spawned (pager='off', not a TTY, or fewer lines than threshold),
186
- * `out` is `stdout`.
187
- */
188
- export const openPager = (opts) => {
189
- const stdout = opts.stdout ?? process.stdout;
190
- if (!isPagerNeeded(opts)) {
191
- return noOpHandle(stdout);
192
- }
193
- const cmd = resolvePagerCmd(opts);
194
- // isPagerNeeded already verified cmd is non-empty, but guard for safety.
195
- if (cmd === "") {
196
- return noOpHandle(stdout);
197
- }
198
- const { command, shell } = parsePagerCmd(cmd);
199
- // A missing pager binary with shell:false emits an async ENOENT that the
200
- // try/catch below cannot catch, and the result would be silently-discarded
201
- // output. Pre-check and fall back to stdout instead (review item #19).
202
- // shell:true goes through `sh -c`, which always exists.
203
- if (!shell && !pagerCommandResolvable(command)) {
204
- return noOpHandle(stdout);
205
- }
206
- const baseEnv = opts.env ?? process.env;
207
- const childEnv = buildPagerEnv(cmd, baseEnv);
208
- let child;
209
- try {
210
- child = spawn(command, [], {
211
- stdio: ["pipe", "inherit", "inherit"],
212
- shell,
213
- env: childEnv,
214
- });
215
- }
216
- catch {
217
- // If the pager fails to spawn, fall back to stdout (matches upstream:
218
- // `if (pagerpipe) return pagerpipe; restore_sigpipe_trap(); ... return stdout`).
219
- return noOpHandle(stdout);
220
- }
221
- const stdin = child.stdin;
222
- if (stdin === null) {
223
- // Should not happen given stdio: ['pipe', ...], but be defensive.
224
- return noOpHandle(stdout);
225
- }
226
- // Swallow EPIPE: the user can quit the pager early, after which any
227
- // pending writes will fail with EPIPE. Upstream relies on SIGPIPE being
228
- // ignored to short-circuit the write loop; we just drop the error.
229
- stdin.on("error", (err) => {
230
- if (err.code !== "EPIPE") {
231
- // Re-throw anything unexpected.
232
- throw err;
233
- }
234
- });
235
- const exitPromise = new Promise((resolve) => {
236
- const settle = (code) => {
237
- resolve(code ?? 0);
238
- };
239
- child.once("exit", (code) => {
240
- settle(code);
241
- });
242
- child.once("error", () => {
243
- // If the child errored (e.g. ENOENT), surface a non-zero exit code
244
- // but don't throw — the caller's writes will have hit EPIPE which
245
- // we already swallow.
246
- settle(127);
247
- });
248
- });
249
- return {
250
- out: stdin,
251
- spawned: true,
252
- close: () => {
253
- // End stdin then wait for the pager to drain & exit.
254
- if (!stdin.writableEnded) {
255
- try {
256
- stdin.end();
257
- }
258
- catch (err) {
259
- const e = err;
260
- if (e.code !== "EPIPE")
261
- throw err;
262
- }
263
- }
264
- return exitPromise;
265
- },
266
- };
267
- };