neonctl 2.37.0 → 2.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/{LICENSE.md → LICENSE} +27 -3
  2. package/README.md +15 -700
  3. package/bin/cli.js +3 -0
  4. package/package.json +8 -75
  5. package/dist/analytics.js +0 -168
  6. package/dist/api.js +0 -720
  7. package/dist/auth.js +0 -125
  8. package/dist/callback.html +0 -51
  9. package/dist/cli.js +0 -9
  10. package/dist/commands/api.js +0 -278
  11. package/dist/commands/auth.js +0 -214
  12. package/dist/commands/bootstrap.js +0 -481
  13. package/dist/commands/branches.js +0 -488
  14. package/dist/commands/bucket.js +0 -549
  15. package/dist/commands/checkout.js +0 -321
  16. package/dist/commands/config.js +0 -626
  17. package/dist/commands/connection_string.js +0 -172
  18. package/dist/commands/data_api.js +0 -284
  19. package/dist/commands/databases.js +0 -82
  20. package/dist/commands/deploy.js +0 -26
  21. package/dist/commands/dev.js +0 -698
  22. package/dist/commands/diff.js +0 -222
  23. package/dist/commands/env.js +0 -189
  24. package/dist/commands/functions.js +0 -373
  25. package/dist/commands/index.js +0 -62
  26. package/dist/commands/init.js +0 -73
  27. package/dist/commands/inspect.js +0 -65
  28. package/dist/commands/ip_allow.js +0 -137
  29. package/dist/commands/link.js +0 -1121
  30. package/dist/commands/neon_auth.js +0 -1028
  31. package/dist/commands/operations.js +0 -28
  32. package/dist/commands/orgs.js +0 -24
  33. package/dist/commands/projects.js +0 -413
  34. package/dist/commands/psql.js +0 -62
  35. package/dist/commands/roles.js +0 -65
  36. package/dist/commands/schema_diff.js +0 -151
  37. package/dist/commands/set_context.js +0 -29
  38. package/dist/commands/snapshots.js +0 -455
  39. package/dist/commands/status.js +0 -40
  40. package/dist/commands/user.js +0 -15
  41. package/dist/commands/vpc_endpoints.js +0 -134
  42. package/dist/config.js +0 -11
  43. package/dist/config_format.js +0 -72
  44. package/dist/context.js +0 -231
  45. package/dist/current_branch_fast_path.js +0 -55
  46. package/dist/dev/env.js +0 -244
  47. package/dist/dev/functions.js +0 -70
  48. package/dist/dev/inputs.js +0 -63
  49. package/dist/dev/runtime.js +0 -146
  50. package/dist/env.js +0 -36
  51. package/dist/env_file.js +0 -159
  52. package/dist/errors.js +0 -80
  53. package/dist/functions_api.js +0 -48
  54. package/dist/help.js +0 -146
  55. package/dist/index.js +0 -240
  56. package/dist/log.js +0 -18
  57. package/dist/parameters.gen.js +0 -480
  58. package/dist/pkg.js +0 -25
  59. package/dist/psql/cli.js +0 -53
  60. package/dist/psql/command/cmd_cond.js +0 -437
  61. package/dist/psql/command/cmd_connect.js +0 -820
  62. package/dist/psql/command/cmd_copy.js +0 -1035
  63. package/dist/psql/command/cmd_describe.js +0 -1815
  64. package/dist/psql/command/cmd_format.js +0 -948
  65. package/dist/psql/command/cmd_io.js +0 -2193
  66. package/dist/psql/command/cmd_lo.js +0 -393
  67. package/dist/psql/command/cmd_meta.js +0 -969
  68. package/dist/psql/command/cmd_misc.js +0 -187
  69. package/dist/psql/command/cmd_pipeline.js +0 -1148
  70. package/dist/psql/command/cmd_restrict.js +0 -171
  71. package/dist/psql/command/cmd_show.js +0 -766
  72. package/dist/psql/command/dispatch.js +0 -343
  73. package/dist/psql/command/inputQueue.js +0 -42
  74. package/dist/psql/command/shared.js +0 -71
  75. package/dist/psql/complete/filenames.js +0 -139
  76. package/dist/psql/complete/index.js +0 -104
  77. package/dist/psql/complete/matcher.js +0 -315
  78. package/dist/psql/complete/psqlVars.js +0 -249
  79. package/dist/psql/complete/queries.js +0 -493
  80. package/dist/psql/complete/rules.js +0 -2424
  81. package/dist/psql/core/common.js +0 -1253
  82. package/dist/psql/core/help.js +0 -576
  83. package/dist/psql/core/mainloop.js +0 -1360
  84. package/dist/psql/core/prompt.js +0 -439
  85. package/dist/psql/core/settings.js +0 -686
  86. package/dist/psql/core/sqlHelp.js +0 -1066
  87. package/dist/psql/core/startup.js +0 -846
  88. package/dist/psql/core/syncVars.js +0 -116
  89. package/dist/psql/core/variables.js +0 -287
  90. package/dist/psql/describe/formatters.js +0 -1330
  91. package/dist/psql/describe/processNamePattern.js +0 -270
  92. package/dist/psql/describe/queries.js +0 -2452
  93. package/dist/psql/describe/versionGate.js +0 -44
  94. package/dist/psql/index.js +0 -2030
  95. package/dist/psql/io/history.js +0 -299
  96. package/dist/psql/io/input.js +0 -120
  97. package/dist/psql/io/lineEditor/buffer.js +0 -325
  98. package/dist/psql/io/lineEditor/complete.js +0 -227
  99. package/dist/psql/io/lineEditor/filename.js +0 -159
  100. package/dist/psql/io/lineEditor/index.js +0 -893
  101. package/dist/psql/io/lineEditor/keymap.js +0 -745
  102. package/dist/psql/io/lineEditor/vt100.js +0 -363
  103. package/dist/psql/io/pgpass.js +0 -202
  104. package/dist/psql/io/pgservice.js +0 -194
  105. package/dist/psql/io/psqlrc.js +0 -422
  106. package/dist/psql/print/aligned.js +0 -1748
  107. package/dist/psql/print/asciidoc.js +0 -230
  108. package/dist/psql/print/crosstab.js +0 -463
  109. package/dist/psql/print/csv.js +0 -76
  110. package/dist/psql/print/html.js +0 -240
  111. package/dist/psql/print/json.js +0 -96
  112. package/dist/psql/print/latex.js +0 -379
  113. package/dist/psql/print/pager.js +0 -267
  114. package/dist/psql/print/troff.js +0 -240
  115. package/dist/psql/print/unaligned.js +0 -99
  116. package/dist/psql/print/units.js +0 -188
  117. package/dist/psql/scanner/slash.js +0 -515
  118. package/dist/psql/scanner/sql.js +0 -914
  119. package/dist/psql/scanner/stringutils.js +0 -394
  120. package/dist/psql/types/backslash.js +0 -1
  121. package/dist/psql/types/connection.js +0 -1
  122. package/dist/psql/types/index.js +0 -7
  123. package/dist/psql/types/printer.js +0 -1
  124. package/dist/psql/types/repl.js +0 -1
  125. package/dist/psql/types/scanner.js +0 -24
  126. package/dist/psql/types/settings.js +0 -1
  127. package/dist/psql/types/variables.js +0 -1
  128. package/dist/psql/wire/connection.js +0 -2857
  129. package/dist/psql/wire/copy.js +0 -108
  130. package/dist/psql/wire/notify.js +0 -59
  131. package/dist/psql/wire/pipeline.js +0 -521
  132. package/dist/psql/wire/protocol.js +0 -466
  133. package/dist/psql/wire/sasl.js +0 -294
  134. package/dist/psql/wire/tls.js +0 -602
  135. package/dist/storage_api.js +0 -147
  136. package/dist/test_utils/fixtures.js +0 -121
  137. package/dist/test_utils/oauth_server.js +0 -9
  138. package/dist/types.js +0 -1
  139. package/dist/utils/ai_gateway_notice.js +0 -180
  140. package/dist/utils/api_enums.js +0 -33
  141. package/dist/utils/auth.js +0 -5
  142. package/dist/utils/branch_notice.js +0 -22
  143. package/dist/utils/branch_picker.js +0 -103
  144. package/dist/utils/compute_units.js +0 -28
  145. package/dist/utils/config_diff.js +0 -185
  146. package/dist/utils/enrichers.js +0 -161
  147. package/dist/utils/esbuild.js +0 -158
  148. package/dist/utils/formats.js +0 -18
  149. package/dist/utils/git_diff.js +0 -90
  150. package/dist/utils/inspect_db.js +0 -153
  151. package/dist/utils/inspect_queries.js +0 -372
  152. package/dist/utils/middlewares.js +0 -20
  153. package/dist/utils/openapi.js +0 -114
  154. package/dist/utils/package_manager.js +0 -68
  155. package/dist/utils/point_in_time.js +0 -56
  156. package/dist/utils/psql.js +0 -120
  157. package/dist/utils/string.js +0 -5
  158. package/dist/utils/ui.js +0 -59
  159. package/dist/utils/zip.js +0 -4
  160. 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
- };