neon 2.47.0 → 3.1.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 (198) hide show
  1. package/README.md +54 -0
  2. package/dist/_shared/auth_selection.js +76 -79
  3. package/dist/_shared/credentials.js +166 -187
  4. package/dist/_shared/env-core/env.js +395 -0
  5. package/dist/_shared/env-core/reuse-secrets.js +179 -0
  6. package/dist/_shared/paths.js +129 -126
  7. package/dist/_shared/profiles.js +192 -242
  8. package/dist/_shared/secure_file.js +36 -38
  9. package/dist/_virtual/_rolldown/runtime.js +13 -0
  10. package/dist/analytics.js +163 -207
  11. package/dist/api.js +815 -758
  12. package/dist/auth.js +121 -141
  13. package/dist/auth_context.js +39 -53
  14. package/dist/cli.js +4 -7
  15. package/dist/commands/api.js +220 -250
  16. package/dist/commands/api_keys.js +251 -314
  17. package/dist/commands/auth.js +283 -328
  18. package/dist/commands/bootstrap.js +372 -437
  19. package/dist/commands/branches.js +304 -455
  20. package/dist/commands/bucket.js +374 -514
  21. package/dist/commands/checkout.js +213 -298
  22. package/dist/commands/config.js +575 -658
  23. package/dist/commands/connection_string.js +137 -165
  24. package/dist/commands/data_api.js +238 -260
  25. package/dist/commands/databases.js +67 -76
  26. package/dist/commands/deploy.js +31 -25
  27. package/dist/commands/dev.js +642 -681
  28. package/dist/commands/diff.js +156 -200
  29. package/dist/commands/env.js +243 -303
  30. package/dist/commands/functions.js +275 -341
  31. package/dist/commands/index.js +70 -65
  32. package/dist/commands/init.js +84 -119
  33. package/dist/commands/inspect.js +55 -55
  34. package/dist/commands/ip_allow.js +88 -120
  35. package/dist/commands/link.js +874 -1019
  36. package/dist/commands/logs.js +291 -0
  37. package/dist/commands/neon_auth.js +725 -933
  38. package/dist/commands/operations.js +34 -25
  39. package/dist/commands/orgs.js +28 -18
  40. package/dist/commands/profile.js +614 -845
  41. package/dist/commands/projects.js +313 -373
  42. package/dist/commands/psql.js +60 -58
  43. package/dist/commands/roles.js +55 -58
  44. package/dist/commands/schema_diff.js +87 -131
  45. package/dist/commands/set_context.js +34 -26
  46. package/dist/commands/snapshots.js +288 -413
  47. package/dist/commands/status.js +41 -37
  48. package/dist/commands/user.js +21 -10
  49. package/dist/commands/vpc_endpoints.js +85 -113
  50. package/dist/config.js +7 -6
  51. package/dist/config_format.js +50 -66
  52. package/dist/config_template.js +128 -157
  53. package/dist/context.js +183 -235
  54. package/dist/current_branch_fast_path.js +40 -49
  55. package/dist/dev/env.js +313 -394
  56. package/dist/dev/functions.js +54 -64
  57. package/dist/dev/inputs.js +46 -58
  58. package/dist/dev/runtime.js +135 -164
  59. package/dist/dev/websocket.js +766 -959
  60. package/dist/env.js +27 -33
  61. package/dist/env_file.js +118 -132
  62. package/dist/env_services.js +36 -38
  63. package/dist/errors.js +57 -68
  64. package/dist/functions_api.js +45 -43
  65. package/dist/help.js +189 -140
  66. package/dist/index.js +182 -257
  67. package/dist/init/agents.js +137 -118
  68. package/dist/init/auth.js +58 -68
  69. package/dist/init/bootstrap.js +325 -396
  70. package/dist/init/build_config.js +4 -2
  71. package/dist/init/detect_agent.js +56 -101
  72. package/dist/init/editors.js +35 -52
  73. package/dist/init/enrich_output.js +51 -66
  74. package/dist/init/extension.js +134 -171
  75. package/dist/init/inspect.js +179 -266
  76. package/dist/init/interactive.js +510 -622
  77. package/dist/init/neonctl.js +117 -168
  78. package/dist/init/orchestrate.js +157 -173
  79. package/dist/init/phases/auth.js +188 -202
  80. package/dist/init/phases/cleanup.js +23 -23
  81. package/dist/init/phases/db.js +251 -277
  82. package/dist/init/phases/getting_started.js +213 -223
  83. package/dist/init/phases/mcp.js +174 -224
  84. package/dist/init/phases/migrations.js +247 -248
  85. package/dist/init/phases/neon_auth.js +114 -133
  86. package/dist/init/phases/setup.js +546 -703
  87. package/dist/init/phases/skills.js +75 -86
  88. package/dist/init/phases/status.js +72 -67
  89. package/dist/init/resolve_context.js +102 -99
  90. package/dist/init/route_command.js +91 -98
  91. package/dist/init/skills.js +174 -218
  92. package/dist/init/vsix.js +77 -99
  93. package/dist/log.js +17 -16
  94. package/dist/neon_services.js +104 -129
  95. package/dist/parameters.gen.js +481 -471
  96. package/dist/pkg.js +17 -19
  97. package/dist/profile_keys.js +44 -47
  98. package/dist/psql/cli.js +44 -47
  99. package/dist/psql/command/cmd_cond.js +231 -406
  100. package/dist/psql/command/cmd_connect.js +557 -764
  101. package/dist/psql/command/cmd_copy.js +727 -983
  102. package/dist/psql/command/cmd_describe.js +1499 -1688
  103. package/dist/psql/command/cmd_format.js +733 -905
  104. package/dist/psql/command/cmd_io.js +1293 -2082
  105. package/dist/psql/command/cmd_lo.js +297 -359
  106. package/dist/psql/command/cmd_meta.js +727 -878
  107. package/dist/psql/command/cmd_misc.js +138 -172
  108. package/dist/psql/command/cmd_pipeline.js +547 -1099
  109. package/dist/psql/command/cmd_restrict.js +119 -155
  110. package/dist/psql/command/cmd_show.js +529 -688
  111. package/dist/psql/command/dispatch.js +261 -325
  112. package/dist/psql/command/inputQueue.js +35 -33
  113. package/dist/psql/command/shared.js +49 -63
  114. package/dist/psql/complete/filenames.js +90 -133
  115. package/dist/psql/complete/index.js +59 -97
  116. package/dist/psql/complete/matcher.js +236 -300
  117. package/dist/psql/complete/psqlVars.js +218 -223
  118. package/dist/psql/complete/queries.js +159 -177
  119. package/dist/psql/complete/rules.js +1493 -2299
  120. package/dist/psql/core/common.js +762 -1180
  121. package/dist/psql/core/help.js +456 -546
  122. package/dist/psql/core/mainloop.js +692 -1302
  123. package/dist/psql/core/prompt.js +391 -408
  124. package/dist/psql/core/settings.js +429 -644
  125. package/dist/psql/core/sqlHelp.js +480 -554
  126. package/dist/psql/core/startup.js +626 -815
  127. package/dist/psql/core/syncVars.js +67 -110
  128. package/dist/psql/core/variables.js +156 -278
  129. package/dist/psql/describe/formatters.js +884 -1285
  130. package/dist/psql/describe/processNamePattern.js +173 -260
  131. package/dist/psql/describe/queries.js +1368 -2403
  132. package/dist/psql/describe/versionGate.js +32 -41
  133. package/dist/psql/index.js +1414 -1927
  134. package/dist/psql/io/history.js +232 -271
  135. package/dist/psql/io/input.js +103 -108
  136. package/dist/psql/io/lineEditor/buffer.js +238 -319
  137. package/dist/psql/io/lineEditor/complete.js +135 -213
  138. package/dist/psql/io/lineEditor/filename.js +139 -148
  139. package/dist/psql/io/lineEditor/index.js +653 -870
  140. package/dist/psql/io/lineEditor/keymap.js +544 -702
  141. package/dist/psql/io/lineEditor/vt100.js +294 -341
  142. package/dist/psql/io/pgpass.js +158 -187
  143. package/dist/psql/io/pgservice.js +146 -183
  144. package/dist/psql/io/psqlrc.js +328 -403
  145. package/dist/psql/print/aligned.js +1020 -1683
  146. package/dist/psql/print/asciidoc.js +180 -214
  147. package/dist/psql/print/crosstab.js +281 -442
  148. package/dist/psql/print/csv.js +48 -70
  149. package/dist/psql/print/html.js +195 -226
  150. package/dist/psql/print/json.js +75 -88
  151. package/dist/psql/print/latex.js +291 -364
  152. package/dist/psql/print/pager.js +171 -242
  153. package/dist/psql/print/troff.js +194 -226
  154. package/dist/psql/print/unaligned.js +69 -95
  155. package/dist/psql/print/units.js +167 -169
  156. package/dist/psql/scanner/slash.js +428 -483
  157. package/dist/psql/scanner/sql.js +445 -889
  158. package/dist/psql/scanner/stringutils.js +309 -379
  159. package/dist/psql/types/index.js +2 -7
  160. package/dist/psql/types/scanner.js +25 -22
  161. package/dist/psql/wire/connection.js +2042 -2803
  162. package/dist/psql/wire/copy.js +84 -100
  163. package/dist/psql/wire/notify.js +39 -59
  164. package/dist/psql/wire/pipeline.js +305 -518
  165. package/dist/psql/wire/protocol.js +349 -417
  166. package/dist/psql/wire/sasl.js +180 -265
  167. package/dist/psql/wire/tls.js +400 -561
  168. package/dist/storage_api.js +115 -129
  169. package/dist/test_utils/fixtures.js +94 -113
  170. package/dist/test_utils/oauth_server.js +10 -7
  171. package/dist/test_utils/project_dir.js +33 -0
  172. package/dist/utils/ai_gateway_notice.js +131 -162
  173. package/dist/utils/api_enums.js +21 -28
  174. package/dist/utils/auth.js +10 -4
  175. package/dist/utils/branch_notice.js +20 -19
  176. package/dist/utils/branch_picker.js +83 -89
  177. package/dist/utils/cli_name.js +15 -12
  178. package/dist/utils/compute_units.js +20 -27
  179. package/dist/utils/config_diff.js +127 -158
  180. package/dist/utils/enrichers.js +95 -148
  181. package/dist/utils/esbuild.js +133 -147
  182. package/dist/utils/flags.js +35 -47
  183. package/dist/utils/formats.js +8 -15
  184. package/dist/utils/git_diff.js +69 -80
  185. package/dist/utils/inspect_db.js +101 -143
  186. package/dist/utils/inspect_queries.js +179 -142
  187. package/dist/utils/middlewares.js +37 -44
  188. package/dist/utils/openapi.js +87 -99
  189. package/dist/utils/package_manager.js +312 -110
  190. package/dist/utils/point_in_time.js +49 -53
  191. package/dist/utils/psql.js +89 -106
  192. package/dist/utils/service_picker.js +55 -58
  193. package/dist/utils/string.js +5 -5
  194. package/dist/utils/ui.js +38 -55
  195. package/dist/utils/write_sync.js +26 -35
  196. package/dist/utils/zip.js +4 -3
  197. package/dist/writer.js +67 -87
  198. package/package.json +9 -7
@@ -1,267 +1,196 @@
1
- import { accessSync, constants as fsConstants } from "node:fs";
1
+ import { accessSync, constants } from "node:fs";
2
2
  import { spawn } from "child_process";
3
3
  import { basename, delimiter, isAbsolute, join } from "path";
4
+ //#region src/psql/print/pager.ts
4
5
  /**
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
- */
6
+ * Can `command` be spawned with `shell:false`? A bare name is searched on
7
+ * `$PATH`; a path is checked directly. Used to avoid `spawn(missingBinary,
8
+ * { shell:false })`, which does NOT throw synchronously (our try/catch never
9
+ * fires) but emits an async ENOENT — leaving writes to the dead child's stdin
10
+ * to vanish into a blank terminal (review item #19).
11
+ */
11
12
  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)));
13
+ const ok = (p) => {
14
+ try {
15
+ accessSync(p, constants.X_OK);
16
+ return true;
17
+ } catch {
18
+ if (process.platform === "win32") for (const ext of [
19
+ "",
20
+ ".exe",
21
+ ".cmd",
22
+ ".bat"
23
+ ]) try {
24
+ accessSync(p + ext, constants.F_OK);
25
+ return true;
26
+ } catch {}
27
+ return false;
28
+ }
29
+ };
30
+ if (command.includes("/") || command.includes("\\") || isAbsolute(command)) return ok(command);
31
+ return (process.env.PATH ?? "").split(delimiter).filter((d) => d.length > 0).some((dir) => ok(join(dir, command)));
43
32
  };
44
33
  /**
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
- */
34
+ * Resolve the pager command string. Mirrors upstream:
35
+ * PSQL_PAGER → PAGER → DEFAULT_PAGER (`less` on POSIX, none on Windows).
36
+ *
37
+ * Empty / whitespace-only env values FALL THROUGH to the next candidate. This
38
+ * deliberately diverges from strict upstream (where `PSQL_PAGER=''` would
39
+ * disable the pager outright) because in Node a spawned child cannot easily
40
+ * "unset" an inherited env var — tests have to override it with the empty
41
+ * string. Treating empty values as "unset" matches the conformance spec
42
+ * (`tests/psql-conformance/tap/030_pager.spec.ts`) and lets `PSQL_PAGER=''`
43
+ * fall through to PAGER, which is the user-friendly interpretation.
44
+ *
45
+ * Users who want to disable the pager unconditionally should set
46
+ * `\pset pager off` (preferred) or unset both env vars before launch.
47
+ */
59
48
  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";
49
+ const env = opts.env ?? process.env;
50
+ const candidates = [
51
+ opts.pagerCmd,
52
+ env.PSQL_PAGER,
53
+ env.PAGER
54
+ ];
55
+ for (const c of candidates) {
56
+ if (c === void 0) continue;
57
+ if (/^\s*$/.test(c)) continue;
58
+ return c;
59
+ }
60
+ if (process.platform === "win32") return "";
61
+ return "less";
78
62
  };
79
63
  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);
64
+ if (opts.isTty !== void 0) return opts.isTty;
65
+ const tty = (opts.stdout ?? process.stdout).isTTY;
66
+ return Boolean(tty);
86
67
  };
87
68
  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;
69
+ if (opts.terminalHeight !== void 0) return opts.terminalHeight;
70
+ const rows = (opts.stdout ?? process.stdout).rows;
71
+ return typeof rows === "number" && rows > 0 ? rows : 24;
94
72
  };
95
73
  /** 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;
74
+ const isPagerNeeded = (opts) => {
75
+ if (opts.pager === "off") return false;
76
+ if (resolvePagerCmd(opts) === "") return false;
77
+ if (opts.pager === "always") return true;
78
+ if (!getIsTty(opts)) return false;
79
+ if (opts.lines === void 0) return false;
80
+ const threshold = Math.max(opts.pagerMinLines, getTerminalHeight(opts));
81
+ return opts.lines >= threshold;
111
82
  };
112
83
  /**
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
- });
84
+ * Decide whether a result of `rowCount` rows by `colCount` columns should be
85
+ * routed through the pager when written to `output`.
86
+ *
87
+ * NOTE on `pager === 'always'`: the TTY check is INTENTIONALLY skipped in
88
+ * this case. Upstream's `\pset pager always` is a user-explicit "force the
89
+ * pager on" override; honouring it on a pipe matches the integration test
90
+ * harness contract (see `tests/psql-conformance/tap/030_pager.spec.ts`) where
91
+ * the child process has no controlling TTY but the spec still requires the
92
+ * configured PAGER to be invoked. For `pager === 'on'` (auto mode) we keep
93
+ * the TTY guard so non-interactive runs don't spuriously spawn `less`.
94
+ */
95
+ const shouldPage = (opts) => {
96
+ if (opts.pager === "off") return false;
97
+ if (opts.redirectedOutput) return false;
98
+ const estimatedLines = 3 + Math.max(0, opts.rowCount) + 1;
99
+ const isTty = opts.isTty !== void 0 ? opts.isTty : Boolean(opts.output.isTTY);
100
+ if (opts.pager !== "always" && !isTty) return false;
101
+ return isPagerNeeded({
102
+ pager: opts.pager,
103
+ pagerMinLines: opts.pagerMinLines,
104
+ pagerCmd: opts.pagerCmd,
105
+ env: opts.env,
106
+ stdout: opts.output,
107
+ isTty,
108
+ terminalHeight: opts.terminalHeight,
109
+ lines: estimatedLines
110
+ });
153
111
  };
154
112
  const SHELL_META = /[\s|;><]/;
155
113
  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 };
114
+ if (SHELL_META.test(cmd)) return {
115
+ command: cmd,
116
+ args: [],
117
+ shell: true
118
+ };
119
+ return {
120
+ command: cmd,
121
+ args: [],
122
+ shell: false
123
+ };
162
124
  };
163
125
  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;
126
+ const env = { ...baseEnv };
127
+ if (env.LESS === void 0) {
128
+ const firstToken = cmd.trim().split(/\s+/, 1)[0] ?? "";
129
+ if (basename(firstToken) === "less") env.LESS = "FRX";
130
+ }
131
+ return env;
177
132
  };
178
133
  const noOpHandle = (out) => ({
179
- out,
180
- spawned: false,
181
- close: () => Promise.resolve(0),
134
+ out,
135
+ spawned: false,
136
+ close: () => Promise.resolve(0)
182
137
  });
183
138
  /**
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
- };
139
+ * Returns a PagerHandle. Caller writes data to `out`, then calls `close()`.
140
+ * If no pager spawned (pager='off', not a TTY, or fewer lines than threshold),
141
+ * `out` is `stdout`.
142
+ */
143
+ const openPager = (opts) => {
144
+ const stdout = opts.stdout ?? process.stdout;
145
+ if (!isPagerNeeded(opts)) return noOpHandle(stdout);
146
+ const cmd = resolvePagerCmd(opts);
147
+ if (cmd === "") return noOpHandle(stdout);
148
+ const { command, shell } = parsePagerCmd(cmd);
149
+ if (!shell && !pagerCommandResolvable(command)) return noOpHandle(stdout);
150
+ const baseEnv = opts.env ?? process.env;
151
+ const childEnv = buildPagerEnv(cmd, baseEnv);
152
+ let child;
153
+ try {
154
+ child = spawn(command, [], {
155
+ stdio: [
156
+ "pipe",
157
+ "inherit",
158
+ "inherit"
159
+ ],
160
+ shell,
161
+ env: childEnv
162
+ });
163
+ } catch {
164
+ return noOpHandle(stdout);
165
+ }
166
+ const stdin = child.stdin;
167
+ if (stdin === null) return noOpHandle(stdout);
168
+ stdin.on("error", (err) => {
169
+ if (err.code !== "EPIPE") throw err;
170
+ });
171
+ const exitPromise = new Promise((resolve) => {
172
+ const settle = (code) => {
173
+ resolve(code ?? 0);
174
+ };
175
+ child.once("exit", (code) => {
176
+ settle(code);
177
+ });
178
+ child.once("error", () => {
179
+ settle(127);
180
+ });
181
+ });
182
+ return {
183
+ out: stdin,
184
+ spawned: true,
185
+ close: () => {
186
+ if (!stdin.writableEnded) try {
187
+ stdin.end();
188
+ } catch (err) {
189
+ if (err.code !== "EPIPE") throw err;
190
+ }
191
+ return exitPromise;
192
+ }
193
+ };
267
194
  };
195
+ //#endregion
196
+ export { isPagerNeeded, openPager, shouldPage };