neon 3.0.0 → 3.1.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 (207) hide show
  1. package/README.md +70 -5
  2. package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
  3. package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
  4. package/dist/_chunks/credentials-MYdHdKah.js +188 -0
  5. package/dist/_chunks/env-NbA61JR3.js +585 -0
  6. package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
  7. package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
  8. package/dist/_chunks/profiles-Ir29rqns.js +217 -0
  9. package/dist/_chunks/psql-DWH-kc69.js +2169 -0
  10. package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
  11. package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
  12. package/dist/analytics.js +163 -207
  13. package/dist/api.js +815 -758
  14. package/dist/auth.js +121 -141
  15. package/dist/auth_context.js +39 -53
  16. package/dist/cli.js +4 -7
  17. package/dist/commands/api.js +220 -250
  18. package/dist/commands/api_keys.js +251 -314
  19. package/dist/commands/auth.js +283 -328
  20. package/dist/commands/bootstrap.js +372 -437
  21. package/dist/commands/branches.js +304 -455
  22. package/dist/commands/bucket.js +374 -514
  23. package/dist/commands/checkout.js +213 -298
  24. package/dist/commands/config.js +573 -690
  25. package/dist/commands/connection_string.js +137 -165
  26. package/dist/commands/data_api.js +238 -260
  27. package/dist/commands/databases.js +67 -76
  28. package/dist/commands/deploy.js +31 -25
  29. package/dist/commands/dev.js +639 -719
  30. package/dist/commands/diff.js +156 -200
  31. package/dist/commands/env.js +255 -305
  32. package/dist/commands/functions.js +275 -355
  33. package/dist/commands/index.js +70 -65
  34. package/dist/commands/init.js +84 -119
  35. package/dist/commands/inspect.js +55 -55
  36. package/dist/commands/ip_allow.js +88 -120
  37. package/dist/commands/link.js +874 -1019
  38. package/dist/commands/logs.js +291 -0
  39. package/dist/commands/neon_auth.js +725 -933
  40. package/dist/commands/operations.js +34 -25
  41. package/dist/commands/orgs.js +28 -18
  42. package/dist/commands/profile.js +615 -846
  43. package/dist/commands/projects.js +313 -373
  44. package/dist/commands/psql.js +60 -58
  45. package/dist/commands/roles.js +55 -58
  46. package/dist/commands/schema_diff.js +87 -131
  47. package/dist/commands/set_context.js +34 -26
  48. package/dist/commands/snapshots.js +288 -413
  49. package/dist/commands/status.js +41 -37
  50. package/dist/commands/user.js +21 -10
  51. package/dist/commands/vpc_endpoints.js +85 -113
  52. package/dist/config.js +7 -6
  53. package/dist/config_format.js +50 -66
  54. package/dist/config_template.js +128 -157
  55. package/dist/context.js +183 -235
  56. package/dist/current_branch_fast_path.js +40 -49
  57. package/dist/dev/env.js +2 -446
  58. package/dist/dev/functions.js +54 -68
  59. package/dist/dev/inputs.js +46 -58
  60. package/dist/dev/runtime.js +135 -164
  61. package/dist/dev/websocket.js +766 -959
  62. package/dist/env.js +27 -33
  63. package/dist/env_file.js +118 -132
  64. package/dist/env_services.js +2 -51
  65. package/dist/errors.js +57 -68
  66. package/dist/functions_api.js +45 -43
  67. package/dist/help.js +189 -140
  68. package/dist/index.js +182 -257
  69. package/dist/init/agents.js +137 -118
  70. package/dist/init/auth.js +58 -68
  71. package/dist/init/bootstrap.js +325 -396
  72. package/dist/init/build_config.js +4 -2
  73. package/dist/init/detect_agent.js +56 -101
  74. package/dist/init/editors.js +35 -52
  75. package/dist/init/enrich_output.js +51 -66
  76. package/dist/init/extension.js +134 -171
  77. package/dist/init/inspect.js +179 -266
  78. package/dist/init/interactive.js +510 -622
  79. package/dist/init/neonctl.js +117 -168
  80. package/dist/init/orchestrate.js +157 -173
  81. package/dist/init/phases/auth.js +188 -202
  82. package/dist/init/phases/cleanup.js +23 -23
  83. package/dist/init/phases/db.js +251 -277
  84. package/dist/init/phases/getting_started.js +213 -223
  85. package/dist/init/phases/mcp.js +174 -224
  86. package/dist/init/phases/migrations.js +247 -248
  87. package/dist/init/phases/neon_auth.js +114 -133
  88. package/dist/init/phases/setup.js +546 -703
  89. package/dist/init/phases/skills.js +75 -86
  90. package/dist/init/phases/status.js +72 -67
  91. package/dist/init/resolve_context.js +102 -99
  92. package/dist/init/route_command.js +91 -98
  93. package/dist/init/skills.js +174 -218
  94. package/dist/init/vsix.js +77 -99
  95. package/dist/log.js +17 -16
  96. package/dist/neon_services.js +104 -129
  97. package/dist/parameters.gen.js +481 -471
  98. package/dist/pkg.js +17 -19
  99. package/dist/profile_keys.js +44 -47
  100. package/dist/psql/cli.js +44 -47
  101. package/dist/psql/command/cmd_cond.js +231 -406
  102. package/dist/psql/command/cmd_connect.js +557 -764
  103. package/dist/psql/command/cmd_copy.js +728 -984
  104. package/dist/psql/command/cmd_describe.js +1499 -1688
  105. package/dist/psql/command/cmd_format.js +733 -905
  106. package/dist/psql/command/cmd_io.js +2 -2193
  107. package/dist/psql/command/cmd_lo.js +297 -359
  108. package/dist/psql/command/cmd_meta.js +727 -878
  109. package/dist/psql/command/cmd_misc.js +138 -172
  110. package/dist/psql/command/cmd_pipeline.js +2 -1148
  111. package/dist/psql/command/cmd_restrict.js +119 -155
  112. package/dist/psql/command/cmd_show.js +529 -688
  113. package/dist/psql/command/dispatch.js +260 -325
  114. package/dist/psql/command/inputQueue.js +35 -33
  115. package/dist/psql/command/shared.js +49 -63
  116. package/dist/psql/complete/filenames.js +90 -133
  117. package/dist/psql/complete/index.js +59 -97
  118. package/dist/psql/complete/matcher.js +236 -300
  119. package/dist/psql/complete/psqlVars.js +218 -223
  120. package/dist/psql/complete/queries.js +159 -177
  121. package/dist/psql/complete/rules.js +1493 -2299
  122. package/dist/psql/core/common.js +2 -1253
  123. package/dist/psql/core/help.js +456 -546
  124. package/dist/psql/core/mainloop.js +692 -1303
  125. package/dist/psql/core/prompt.js +391 -408
  126. package/dist/psql/core/settings.js +429 -644
  127. package/dist/psql/core/sqlHelp.js +480 -554
  128. package/dist/psql/core/startup.js +2 -846
  129. package/dist/psql/core/syncVars.js +67 -110
  130. package/dist/psql/core/variables.js +156 -278
  131. package/dist/psql/describe/formatters.js +884 -1285
  132. package/dist/psql/describe/processNamePattern.js +173 -260
  133. package/dist/psql/describe/queries.js +1368 -2403
  134. package/dist/psql/describe/versionGate.js +32 -41
  135. package/dist/psql/index.js +2 -2030
  136. package/dist/psql/io/history.js +232 -271
  137. package/dist/psql/io/input.js +103 -108
  138. package/dist/psql/io/lineEditor/buffer.js +238 -319
  139. package/dist/psql/io/lineEditor/complete.js +135 -213
  140. package/dist/psql/io/lineEditor/filename.js +139 -148
  141. package/dist/psql/io/lineEditor/index.js +653 -870
  142. package/dist/psql/io/lineEditor/keymap.js +544 -702
  143. package/dist/psql/io/lineEditor/vt100.js +294 -341
  144. package/dist/psql/io/pgpass.js +158 -187
  145. package/dist/psql/io/pgservice.js +146 -183
  146. package/dist/psql/io/psqlrc.js +328 -403
  147. package/dist/psql/print/aligned.js +1020 -1683
  148. package/dist/psql/print/asciidoc.js +180 -214
  149. package/dist/psql/print/crosstab.js +281 -442
  150. package/dist/psql/print/csv.js +48 -70
  151. package/dist/psql/print/html.js +195 -226
  152. package/dist/psql/print/json.js +75 -88
  153. package/dist/psql/print/latex.js +291 -364
  154. package/dist/psql/print/pager.js +171 -242
  155. package/dist/psql/print/troff.js +194 -226
  156. package/dist/psql/print/unaligned.js +69 -95
  157. package/dist/psql/print/units.js +167 -169
  158. package/dist/psql/scanner/slash.js +428 -483
  159. package/dist/psql/scanner/sql.js +445 -889
  160. package/dist/psql/scanner/stringutils.js +309 -379
  161. package/dist/psql/types/index.js +8 -7
  162. package/dist/psql/types/scanner.js +25 -22
  163. package/dist/psql/wire/connection.js +2042 -2803
  164. package/dist/psql/wire/copy.js +84 -100
  165. package/dist/psql/wire/notify.js +39 -59
  166. package/dist/psql/wire/pipeline.js +305 -518
  167. package/dist/psql/wire/protocol.js +349 -417
  168. package/dist/psql/wire/sasl.js +180 -265
  169. package/dist/psql/wire/tls.js +400 -561
  170. package/dist/storage_api.js +115 -129
  171. package/dist/test_utils/fixtures.js +94 -113
  172. package/dist/test_utils/oauth_server.js +10 -7
  173. package/dist/test_utils/project_dir.js +33 -0
  174. package/dist/utils/ai_gateway_notice.js +131 -162
  175. package/dist/utils/api_enums.js +21 -28
  176. package/dist/utils/auth.js +10 -4
  177. package/dist/utils/branch_notice.js +20 -19
  178. package/dist/utils/branch_picker.js +83 -89
  179. package/dist/utils/cli_name.js +15 -12
  180. package/dist/utils/compute_units.js +20 -27
  181. package/dist/utils/config_diff.js +127 -158
  182. package/dist/utils/enrichers.js +95 -148
  183. package/dist/utils/esbuild.js +130 -189
  184. package/dist/utils/flags.js +35 -47
  185. package/dist/utils/formats.js +8 -15
  186. package/dist/utils/git_diff.js +69 -80
  187. package/dist/utils/inspect_db.js +101 -143
  188. package/dist/utils/inspect_queries.js +179 -142
  189. package/dist/utils/middlewares.js +39 -45
  190. package/dist/utils/openapi.js +87 -99
  191. package/dist/utils/package_manager.js +312 -110
  192. package/dist/utils/point_in_time.js +49 -53
  193. package/dist/utils/psql.js +89 -106
  194. package/dist/utils/service_picker.js +55 -58
  195. package/dist/utils/string.js +5 -5
  196. package/dist/utils/ui.js +38 -55
  197. package/dist/utils/write_sync.js +26 -35
  198. package/dist/utils/zip.js +4 -3
  199. package/dist/writer.js +67 -87
  200. package/package.json +11 -6
  201. package/dist/_shared/auth_selection.js +0 -86
  202. package/dist/_shared/credentials.js +0 -209
  203. package/dist/_shared/env-core/env.js +0 -558
  204. package/dist/_shared/env-core/reuse-secrets.js +0 -223
  205. package/dist/_shared/paths.js +0 -148
  206. package/dist/_shared/profiles.js +0 -276
  207. package/dist/_shared/secure_file.js +0 -43
@@ -1,515 +1,460 @@
1
- /**
2
- * psql backslash-command argument scanner.
3
- *
4
- * Hand-port of PostgreSQL's `src/bin/psql/psqlscanslash.l`. The upstream is a
5
- * flex-generated state machine with these exclusive states:
6
- *
7
- * - `xslashcmd` — reading the command name (the letters after `\`)
8
- * - `xslashargstart` — skipping whitespace before the next arg; `|` at
9
- * this position is special in `filepipe` mode
10
- * - `xslasharg` — reading an unquoted arg (handles `:var`,
11
- * `:'var'`, `:"var"` substitutions and the start of
12
- * `'`, `"`, `` ` `` quoted runs)
13
- * - `xslashquote` — inside `'…'` (C-string-style escapes processed)
14
- * - `xslashbackquote` — inside `` `…` `` (variable expansion only; the
15
- * body is shipped to the shell by upstream)
16
- * - `xslashdquote` — inside `"…"` (literal copy, double quotes kept)
17
- * - `xslashwholeline` — slurp rest of line, suppressing leading whitespace
18
- * - `xslashend` — terminator (we don't model it here; the caller
19
- * knows where the slash command ended)
20
- *
21
- * The TS port collapses these into a single {@link scanSlashArgs} function
22
- * that takes the post-command-name remainder of the input line plus a
23
- * {@link SlashArgMode} and returns the list of parsed arguments. We hand-roll
24
- * the state machine instead of attempting to mechanically translate flex
25
- * rules; the resulting code is easier to read and trivially testable.
26
- *
27
- * Behavioural notes vs upstream:
28
- *
29
- * - **Whole-line mode** returns a single-element array containing the entire
30
- * rest-of-line, with leading whitespace suppressed. Empty input still
31
- * yields `[]` so callers can treat the result uniformly.
32
- * - **filepipe mode** treats a leading `|` as the start of a shell command
33
- * and slurps the rest of the line as one argument. Anything else is
34
- * handled as a normal arg.
35
- * - **Variable substitution** matches upstream's three forms:
36
- * `:varname` — plain expansion
37
- * `:'varname'` — SQL-literal-quoted expansion
38
- * `:"varname"` — SQL-identifier-quoted expansion
39
- * `varname` is `[A-Za-z0-9_\x80-\xff]+` (upstream's `variable_char`). When
40
- * the variable is unset, the colon form is emitted literally — matching
41
- * the upstream `ECHO` fallback.
42
- * - **no-vars mode** disables all `:var` substitution; the lexer emits the
43
- * raw text. Useful for commands that should never expand variables
44
- * (e.g. `\setenv`'s value argument).
45
- * - **sql-id / sql-id-keep-case modes** post-process each arg through
46
- * `dequoteDowncaseIdentifier`, which mirrors upstream's
47
- * `dequote_downcase_identifier()`: collapse `"…"` quoting, double `""`
48
- * into a single `"`, and (for `sql-id`) lowercase unquoted letters.
49
- * - **Backticks** ARE executed here, synchronously via `child_process.execSync`
50
- * on `sh -c <body>`. Variable references inside the backticked body are
51
- * expanded first (matching upstream's `xslashbackquote` rules), then the
52
- * resulting command is run with the inherited environment but NO shell
53
- * state; the child's stdout (trimmed of one trailing newline) is the
54
- * arg's value. Non-zero exits / spawn failures are reported on stderr
55
- * in the upstream `psql:...: error: \!:` shape and substitute the empty
56
- * string. This is the scanner-level analogue of upstream's `evaluate_backtick`.
57
- * - **Inside-quote escapes** match upstream `xslashquote`: `\n \t \b \r \f`,
58
- * octal `\ooo`, hex `\xhh`, and `\<other>` as a literal character. We
59
- * apply them in-line so the returned arg contains the decoded value.
60
- */
61
- import { execSync } from "node:child_process";
62
1
  import { dequote } from "./stringutils.js";
63
- const WHITESPACE = " \t\n\r\f\v";
2
+ import { execSync } from "node:child_process";
3
+ //#region src/psql/scanner/slash.ts
4
+ /**
5
+ * psql backslash-command argument scanner.
6
+ *
7
+ * Hand-port of PostgreSQL's `src/bin/psql/psqlscanslash.l`. The upstream is a
8
+ * flex-generated state machine with these exclusive states:
9
+ *
10
+ * - `xslashcmd` — reading the command name (the letters after `\`)
11
+ * - `xslashargstart` — skipping whitespace before the next arg; `|` at
12
+ * this position is special in `filepipe` mode
13
+ * - `xslasharg` — reading an unquoted arg (handles `:var`,
14
+ * `:'var'`, `:"var"` substitutions and the start of
15
+ * `'`, `"`, `` ` `` quoted runs)
16
+ * - `xslashquote` — inside `'…'` (C-string-style escapes processed)
17
+ * - `xslashbackquote` — inside `` `…` `` (variable expansion only; the
18
+ * body is shipped to the shell by upstream)
19
+ * - `xslashdquote` — inside `"…"` (literal copy, double quotes kept)
20
+ * - `xslashwholeline` — slurp rest of line, suppressing leading whitespace
21
+ * - `xslashend` — terminator (we don't model it here; the caller
22
+ * knows where the slash command ended)
23
+ *
24
+ * The TS port collapses these into a single {@link scanSlashArgs} function
25
+ * that takes the post-command-name remainder of the input line plus a
26
+ * {@link SlashArgMode} and returns the list of parsed arguments. We hand-roll
27
+ * the state machine instead of attempting to mechanically translate flex
28
+ * rules; the resulting code is easier to read and trivially testable.
29
+ *
30
+ * Behavioural notes vs upstream:
31
+ *
32
+ * - **Whole-line mode** returns a single-element array containing the entire
33
+ * rest-of-line, with leading whitespace suppressed. Empty input still
34
+ * yields `[]` so callers can treat the result uniformly.
35
+ * - **filepipe mode** treats a leading `|` as the start of a shell command
36
+ * and slurps the rest of the line as one argument. Anything else is
37
+ * handled as a normal arg.
38
+ * - **Variable substitution** matches upstream's three forms:
39
+ * `:varname` — plain expansion
40
+ * `:'varname'` — SQL-literal-quoted expansion
41
+ * `:"varname"` — SQL-identifier-quoted expansion
42
+ * `varname` is `[A-Za-z0-9_\x80-\xff]+` (upstream's `variable_char`). When
43
+ * the variable is unset, the colon form is emitted literally — matching
44
+ * the upstream `ECHO` fallback.
45
+ * - **no-vars mode** disables all `:var` substitution; the lexer emits the
46
+ * raw text. Useful for commands that should never expand variables
47
+ * (e.g. `\setenv`'s value argument).
48
+ * - **sql-id / sql-id-keep-case modes** post-process each arg through
49
+ * `dequoteDowncaseIdentifier`, which mirrors upstream's
50
+ * `dequote_downcase_identifier()`: collapse `"…"` quoting, double `""`
51
+ * into a single `"`, and (for `sql-id`) lowercase unquoted letters.
52
+ * - **Backticks** ARE executed here, synchronously via `child_process.execSync`
53
+ * on `sh -c <body>`. Variable references inside the backticked body are
54
+ * expanded first (matching upstream's `xslashbackquote` rules), then the
55
+ * resulting command is run with the inherited environment but NO shell
56
+ * state; the child's stdout (trimmed of one trailing newline) is the
57
+ * arg's value. Non-zero exits / spawn failures are reported on stderr
58
+ * in the upstream `psql:...: error: \!:` shape and substitute the empty
59
+ * string. This is the scanner-level analogue of upstream's `evaluate_backtick`.
60
+ * - **Inside-quote escapes** match upstream `xslashquote`: `\n \t \b \r \f`,
61
+ * octal `\ooo`, hex `\xhh`, and `\<other>` as a literal character. We
62
+ * apply them in-line so the returned arg contains the decoded value.
63
+ */
64
+ const WHITESPACE = " \n\r\f\v";
64
65
  const VARIABLE_CHAR_RE = /[A-Za-z0-9_\x80-\xff]/;
65
- const isVarChar = (c) => c !== undefined && VARIABLE_CHAR_RE.test(c);
66
- const isWhitespace = (c) => c !== undefined && WHITESPACE.includes(c);
66
+ const isVarChar = (c) => c !== void 0 && VARIABLE_CHAR_RE.test(c);
67
+ const isWhitespace = (c) => c !== void 0 && WHITESPACE.includes(c);
67
68
  /**
68
- * SQL-literal-quote a value for the `:'varname'` substitution form.
69
- * Mirrors libpq's `PQescapeLiteral` for the common case: wrap in `'…'`,
70
- * double any embedded `'`, and backslash-escape any embedded `\`. Upstream
71
- * additionally emits an `E` prefix when the value contains backslashes; we
72
- * preserve that behaviour for compatibility with code that round-trips
73
- * through the SQL parser.
74
- */
69
+ * SQL-literal-quote a value for the `:'varname'` substitution form.
70
+ * Mirrors libpq's `PQescapeLiteral` for the common case: wrap in `'…'`,
71
+ * double any embedded `'`, and backslash-escape any embedded `\`. Upstream
72
+ * additionally emits an `E` prefix when the value contains backslashes; we
73
+ * preserve that behaviour for compatibility with code that round-trips
74
+ * through the SQL parser.
75
+ */
75
76
  const quoteSqlLiteral = (value) => {
76
- let needsEscape = false;
77
- let inner = "";
78
- for (const c of value) {
79
- if (c === "'")
80
- inner += "''";
81
- else if (c === "\\") {
82
- inner += "\\\\";
83
- needsEscape = true;
84
- }
85
- else {
86
- inner += c;
87
- }
88
- }
89
- return needsEscape ? `E'${inner}'` : `'${inner}'`;
77
+ let needsEscape = false;
78
+ let inner = "";
79
+ for (const c of value) if (c === "'") inner += "''";
80
+ else if (c === "\\") {
81
+ inner += "\\\\";
82
+ needsEscape = true;
83
+ } else inner += c;
84
+ return needsEscape ? `E'${inner}'` : `'${inner}'`;
90
85
  };
91
86
  /**
92
- * SQL-identifier-quote a value for the `:"varname"` substitution form.
93
- * Wraps the value in `"…"` and doubles any embedded `"`.
94
- */
87
+ * SQL-identifier-quote a value for the `:"varname"` substitution form.
88
+ * Wraps the value in `"…"` and doubles any embedded `"`.
89
+ */
95
90
  const quoteSqlIdent = (value) => {
96
- let inner = "";
97
- for (const c of value) {
98
- inner += c === '"' ? '""' : c;
99
- }
100
- return `"${inner}"`;
91
+ let inner = "";
92
+ for (const c of value) inner += c === "\"" ? "\"\"" : c;
93
+ return `"${inner}"`;
101
94
  };
102
95
  /**
103
- * Match upstream's `dequote_downcase_identifier()`. Strips out `"…"` quoting
104
- * (collapsing `""` to `"` inside quotes) and optionally downcases unquoted
105
- * letters. The transformation is in-place semantically: a string like
106
- * `FOO"BAR"BAZ` becomes `fooBARbaz` (when `downcase`) or `FOOBARBAZ`
107
- * (otherwise).
108
- */
96
+ * Match upstream's `dequote_downcase_identifier()`. Strips out `"…"` quoting
97
+ * (collapsing `""` to `"` inside quotes) and optionally downcases unquoted
98
+ * letters. The transformation is in-place semantically: a string like
99
+ * `FOO"BAR"BAZ` becomes `fooBARbaz` (when `downcase`) or `FOOBARBAZ`
100
+ * (otherwise).
101
+ */
109
102
  const dequoteDowncaseIdentifier = (str, downcase) => {
110
- let out = "";
111
- let inquotes = false;
112
- let i = 0;
113
- while (i < str.length) {
114
- const c = str[i];
115
- if (c === '"') {
116
- if (inquotes && str[i + 1] === '"') {
117
- // Keep one quote, drop the other.
118
- out += '"';
119
- i += 2;
120
- continue;
121
- }
122
- inquotes = !inquotes;
123
- i++;
124
- continue;
125
- }
126
- out += downcase && !inquotes ? c.toLowerCase() : c;
127
- i++;
128
- }
129
- return out;
103
+ let out = "";
104
+ let inquotes = false;
105
+ let i = 0;
106
+ while (i < str.length) {
107
+ const c = str[i];
108
+ if (c === "\"") {
109
+ if (inquotes && str[i + 1] === "\"") {
110
+ out += "\"";
111
+ i += 2;
112
+ continue;
113
+ }
114
+ inquotes = !inquotes;
115
+ i++;
116
+ continue;
117
+ }
118
+ out += downcase && !inquotes ? c.toLowerCase() : c;
119
+ i++;
120
+ }
121
+ return out;
130
122
  };
131
123
  /**
132
- * Attempt to consume one of the `:var`, `:'var'`, `:"var"` variable
133
- * substitution forms at position `i` in `s`. Returns the new index plus the
134
- * substituted text, or `null` if no recognised form is present.
135
- *
136
- * Caller controls whether the colon forms are honoured at all via
137
- * `varLookup`: pass `undefined` to disable substitution entirely (`no-vars`
138
- * mode).
139
- */
124
+ * Attempt to consume one of the `:var`, `:'var'`, `:"var"` variable
125
+ * substitution forms at position `i` in `s`. Returns the new index plus the
126
+ * substituted text, or `null` if no recognised form is present.
127
+ *
128
+ * Caller controls whether the colon forms are honoured at all via
129
+ * `varLookup`: pass `undefined` to disable substitution entirely (`no-vars`
130
+ * mode).
131
+ */
140
132
  const tryConsumeVarSubstitution = (s, i, varLookup) => {
141
- if (varLookup === undefined)
142
- return null;
143
- if (s[i] !== ":")
144
- return null;
145
- // :{?varname} — defined-variable test, emits literal TRUE / FALSE.
146
- // Mirrors upstream `psqlscanslash.l`'s `:\{\?{variable_char}+\}` rule
147
- // (calls `psqlscan_test_variable`). A malformed expression (missing
148
- // closing `}` or empty name) falls through to `null` so the caller emits
149
- // the literal `:` and continues.
150
- if (s[i + 1] === "{" && s[i + 2] === "?") {
151
- let j = i + 3;
152
- while (j < s.length && isVarChar(s[j]))
153
- j++;
154
- if (j > i + 3 && s[j] === "}") {
155
- const name = s.slice(i + 3, j);
156
- const value = varLookup(name);
157
- return { end: j + 1, text: value !== undefined ? "TRUE" : "FALSE" };
158
- }
159
- return null;
160
- }
161
- // :"varname" — SQL identifier quote
162
- if (s[i + 1] === '"') {
163
- let j = i + 2;
164
- while (j < s.length && isVarChar(s[j]))
165
- j++;
166
- if (j > i + 2 && s[j] === '"') {
167
- const name = s.slice(i + 2, j);
168
- const value = varLookup(name);
169
- if (value === undefined) {
170
- // Upstream still substitutes — passing an empty string would quietly
171
- // misparse downstream. We instead pass through the literal so the
172
- // caller can see (and report) the unset reference. This matches the
173
- // ECHO fallback used by upstream's plain `:varname` form.
174
- return { end: j + 1, text: s.slice(i, j + 1) };
175
- }
176
- return { end: j + 1, text: quoteSqlIdent(value) };
177
- }
178
- return null;
179
- }
180
- // :'varname' — SQL literal quote
181
- if (s[i + 1] === "'") {
182
- let j = i + 2;
183
- while (j < s.length && isVarChar(s[j]))
184
- j++;
185
- if (j > i + 2 && s[j] === "'") {
186
- const name = s.slice(i + 2, j);
187
- const value = varLookup(name);
188
- if (value === undefined) {
189
- return { end: j + 1, text: s.slice(i, j + 1) };
190
- }
191
- return { end: j + 1, text: quoteSqlLiteral(value) };
192
- }
193
- return null;
194
- }
195
- // :varname — plain substitution
196
- if (isVarChar(s[i + 1])) {
197
- let j = i + 1;
198
- while (j < s.length && isVarChar(s[j]))
199
- j++;
200
- const name = s.slice(i + 1, j);
201
- const value = varLookup(name);
202
- if (value === undefined) {
203
- // Unset → emit literally so it stays visible. Upstream ECHOes the
204
- // entire `:name` text in this case.
205
- return { end: j, text: s.slice(i, j) };
206
- }
207
- return { end: j, text: value };
208
- }
209
- return null;
133
+ if (varLookup === void 0) return null;
134
+ if (s[i] !== ":") return null;
135
+ if (s[i + 1] === "{" && s[i + 2] === "?") {
136
+ let j = i + 3;
137
+ while (j < s.length && isVarChar(s[j])) j++;
138
+ if (j > i + 3 && s[j] === "}") {
139
+ const value = varLookup(s.slice(i + 3, j));
140
+ return {
141
+ end: j + 1,
142
+ text: value !== void 0 ? "TRUE" : "FALSE"
143
+ };
144
+ }
145
+ return null;
146
+ }
147
+ if (s[i + 1] === "\"") {
148
+ let j = i + 2;
149
+ while (j < s.length && isVarChar(s[j])) j++;
150
+ if (j > i + 2 && s[j] === "\"") {
151
+ const value = varLookup(s.slice(i + 2, j));
152
+ if (value === void 0) return {
153
+ end: j + 1,
154
+ text: s.slice(i, j + 1)
155
+ };
156
+ return {
157
+ end: j + 1,
158
+ text: quoteSqlIdent(value)
159
+ };
160
+ }
161
+ return null;
162
+ }
163
+ if (s[i + 1] === "'") {
164
+ let j = i + 2;
165
+ while (j < s.length && isVarChar(s[j])) j++;
166
+ if (j > i + 2 && s[j] === "'") {
167
+ const value = varLookup(s.slice(i + 2, j));
168
+ if (value === void 0) return {
169
+ end: j + 1,
170
+ text: s.slice(i, j + 1)
171
+ };
172
+ return {
173
+ end: j + 1,
174
+ text: quoteSqlLiteral(value)
175
+ };
176
+ }
177
+ return null;
178
+ }
179
+ if (isVarChar(s[i + 1])) {
180
+ let j = i + 1;
181
+ while (j < s.length && isVarChar(s[j])) j++;
182
+ const value = varLookup(s.slice(i + 1, j));
183
+ if (value === void 0) return {
184
+ end: j,
185
+ text: s.slice(i, j)
186
+ };
187
+ return {
188
+ end: j,
189
+ text: value
190
+ };
191
+ }
192
+ return null;
210
193
  };
211
194
  /**
212
- * Process the contents of a `'…'` slash-quoted token: handle psql's C-style
213
- * escapes (\n, \t, \b, \r, \f, octal, hex, and \<other>) and undouble `''`.
214
- * The opening quote has already been consumed; we read until the matching
215
- * closing quote and return the decoded payload plus the new index (pointing
216
- * just past the closing quote).
217
- */
195
+ * Process the contents of a `'…'` slash-quoted token: handle psql's C-style
196
+ * escapes (\n, \t, \b, \r, \f, octal, hex, and \<other>) and undouble `''`.
197
+ * The opening quote has already been consumed; we read until the matching
198
+ * closing quote and return the decoded payload plus the new index (pointing
199
+ * just past the closing quote).
200
+ */
218
201
  const consumeSingleQuoted = (s, start) => {
219
- let out = "";
220
- let i = start;
221
- while (i < s.length) {
222
- const c = s[i];
223
- if (c === "'") {
224
- if (s[i + 1] === "'") {
225
- out += "'";
226
- i += 2;
227
- continue;
228
- }
229
- return { end: i + 1, text: out };
230
- }
231
- if (c === "\\" && i + 1 < s.length) {
232
- const next = s[i + 1];
233
- if (next === "n") {
234
- out += "\n";
235
- i += 2;
236
- continue;
237
- }
238
- if (next === "t") {
239
- out += "\t";
240
- i += 2;
241
- continue;
242
- }
243
- if (next === "b") {
244
- out += "\b";
245
- i += 2;
246
- continue;
247
- }
248
- if (next === "r") {
249
- out += "\r";
250
- i += 2;
251
- continue;
252
- }
253
- if (next === "f") {
254
- out += "\f";
255
- i += 2;
256
- continue;
257
- }
258
- // Octal: \ooo (1–3 digits)
259
- if (next >= "0" && next <= "7") {
260
- const j = i + 1;
261
- let octEnd = j;
262
- while (octEnd < s.length &&
263
- octEnd - j < 3 &&
264
- s[octEnd] >= "0" &&
265
- s[octEnd] <= "7") {
266
- octEnd++;
267
- }
268
- const code = parseInt(s.slice(j, octEnd), 8);
269
- out += String.fromCharCode(code);
270
- i = octEnd;
271
- continue;
272
- }
273
- // Hex: \xhh (1–2 digits)
274
- if (next === "x") {
275
- const j = i + 2;
276
- const hexRe = /[0-9a-fA-F]/;
277
- let hexEnd = j;
278
- while (hexEnd < s.length &&
279
- hexEnd - j < 2 &&
280
- hexRe.test(s[hexEnd])) {
281
- hexEnd++;
282
- }
283
- if (hexEnd > j) {
284
- const code = parseInt(s.slice(j, hexEnd), 16);
285
- out += String.fromCharCode(code);
286
- i = hexEnd;
287
- continue;
288
- }
289
- }
290
- // \<other> → literal next char
291
- out += next;
292
- i += 2;
293
- continue;
294
- }
295
- out += c;
296
- i++;
297
- }
298
- // Unterminated — return what we have. Upstream reports an error; for the
299
- // scanner-as-library shape we'd rather surface the partial text and let
300
- // the caller decide. Tests cover both well-formed and unterminated cases.
301
- return { end: i, text: out };
202
+ let out = "";
203
+ let i = start;
204
+ while (i < s.length) {
205
+ const c = s[i];
206
+ if (c === "'") {
207
+ if (s[i + 1] === "'") {
208
+ out += "'";
209
+ i += 2;
210
+ continue;
211
+ }
212
+ return {
213
+ end: i + 1,
214
+ text: out
215
+ };
216
+ }
217
+ if (c === "\\" && i + 1 < s.length) {
218
+ const next = s[i + 1];
219
+ if (next === "n") {
220
+ out += "\n";
221
+ i += 2;
222
+ continue;
223
+ }
224
+ if (next === "t") {
225
+ out += " ";
226
+ i += 2;
227
+ continue;
228
+ }
229
+ if (next === "b") {
230
+ out += "\b";
231
+ i += 2;
232
+ continue;
233
+ }
234
+ if (next === "r") {
235
+ out += "\r";
236
+ i += 2;
237
+ continue;
238
+ }
239
+ if (next === "f") {
240
+ out += "\f";
241
+ i += 2;
242
+ continue;
243
+ }
244
+ if (next >= "0" && next <= "7") {
245
+ const j = i + 1;
246
+ let octEnd = j;
247
+ while (octEnd < s.length && octEnd - j < 3 && s[octEnd] >= "0" && s[octEnd] <= "7") octEnd++;
248
+ const code = parseInt(s.slice(j, octEnd), 8);
249
+ out += String.fromCharCode(code);
250
+ i = octEnd;
251
+ continue;
252
+ }
253
+ if (next === "x") {
254
+ const j = i + 2;
255
+ const hexRe = /[0-9a-fA-F]/;
256
+ let hexEnd = j;
257
+ while (hexEnd < s.length && hexEnd - j < 2 && hexRe.test(s[hexEnd])) hexEnd++;
258
+ if (hexEnd > j) {
259
+ const code = parseInt(s.slice(j, hexEnd), 16);
260
+ out += String.fromCharCode(code);
261
+ i = hexEnd;
262
+ continue;
263
+ }
264
+ }
265
+ out += next;
266
+ i += 2;
267
+ continue;
268
+ }
269
+ out += c;
270
+ i++;
271
+ }
272
+ return {
273
+ end: i,
274
+ text: out
275
+ };
302
276
  };
303
277
  /**
304
- * Process the contents of a `"…"` slash-quoted token. Upstream copies the
305
- * body verbatim *including the double quotes themselves* (see `xslashdquote`
306
- * rule, which ECHOes the opening dquote on entry). That preserves
307
- * SQL-identifier semantics — the caller's `dequoteDowncaseIdentifier()` is
308
- * what eventually unwraps the quotes for `sql-id` modes.
309
- */
278
+ * Process the contents of a `"…"` slash-quoted token. Upstream copies the
279
+ * body verbatim *including the double quotes themselves* (see `xslashdquote`
280
+ * rule, which ECHOes the opening dquote on entry). That preserves
281
+ * SQL-identifier semantics — the caller's `dequoteDowncaseIdentifier()` is
282
+ * what eventually unwraps the quotes for `sql-id` modes.
283
+ */
310
284
  const consumeDoubleQuoted = (s, start) => {
311
- let i = start;
312
- while (i < s.length) {
313
- if (s[i] === '"') {
314
- return { end: i + 1, text: s.slice(start - 1, i + 1) };
315
- }
316
- i++;
317
- }
318
- // Unterminated — return what we have, including the opening quote.
319
- return { end: i, text: s.slice(start - 1, i) };
285
+ let i = start;
286
+ while (i < s.length) {
287
+ if (s[i] === "\"") return {
288
+ end: i + 1,
289
+ text: s.slice(start - 1, i + 1)
290
+ };
291
+ i++;
292
+ }
293
+ return {
294
+ end: i,
295
+ text: s.slice(start - 1, i)
296
+ };
320
297
  };
321
298
  /**
322
- * Test seam for swapping the shell executor. Vitest sets `current` to its
323
- * own mock; production uses `execSync(cmd, { shell: '/bin/sh' })`. Kept as
324
- * an exported object so tests can flip it in `beforeEach` without monkey-
325
- * patching `child_process`.
326
- */
327
- export const BACKTICK_EXECUTOR = {
328
- current: (cmd) => execSync(cmd, {
329
- shell: "/bin/sh",
330
- encoding: "utf8",
331
- // Children inherit the parent env but get no stdin pipe — matches
332
- // upstream's `popen(cmd, "r")` semantics. stderr passes through so
333
- // shell error output is visible to the user.
334
- stdio: ["ignore", "pipe", "inherit"],
335
- // Defensive cap: backtick output goes into a slash arg, so a runaway
336
- // command shouldn't be able to fill arbitrary memory.
337
- maxBuffer: 1 << 20,
338
- }),
339
- };
299
+ * Test seam for swapping the shell executor. Vitest sets `current` to its
300
+ * own mock; production uses `execSync(cmd, { shell: '/bin/sh' })`. Kept as
301
+ * an exported object so tests can flip it in `beforeEach` without monkey-
302
+ * patching `child_process`.
303
+ */
304
+ const BACKTICK_EXECUTOR = { current: (cmd) => execSync(cmd, {
305
+ shell: "/bin/sh",
306
+ encoding: "utf8",
307
+ stdio: [
308
+ "ignore",
309
+ "pipe",
310
+ "inherit"
311
+ ],
312
+ maxBuffer: 1 << 20
313
+ }) };
340
314
  /**
341
- * Execute the lexed body of a `` `…` `` token via `sh -c` and return its
342
- * stdout with one trailing newline stripped (matching shell command-
343
- * substitution convention). Errors are reported on stderr in the upstream
344
- * `psql: error: \!: <command>: <message>` shape and substitute the empty
345
- * string — so a failed backtick never aborts the surrounding slash command.
346
- *
347
- * Called only by {@link consumeBackQuoted}; lives at module scope so the
348
- * scanner stays free of inline I/O and tests can spy on the executor via
349
- * {@link BACKTICK_EXECUTOR}.
350
- */
315
+ * Execute the lexed body of a `` `…` `` token via `sh -c` and return its
316
+ * stdout with one trailing newline stripped (matching shell command-
317
+ * substitution convention). Errors are reported on stderr in the upstream
318
+ * `psql: error: \!: <command>: <message>` shape and substitute the empty
319
+ * string — so a failed backtick never aborts the surrounding slash command.
320
+ *
321
+ * Called only by {@link consumeBackQuoted}; lives at module scope so the
322
+ * scanner stays free of inline I/O and tests can spy on the executor via
323
+ * {@link BACKTICK_EXECUTOR}.
324
+ */
351
325
  const runBacktickCommand = (cmd) => {
352
- if (cmd.length === 0)
353
- return "";
354
- try {
355
- const out = BACKTICK_EXECUTOR.current(cmd);
356
- // Trim a single trailing newline; preserve interior newlines so multi-line
357
- // output (e.g. `\set FOO `cat file``) lands as-is.
358
- return out.endsWith("\n") ? out.slice(0, -1) : out;
359
- }
360
- catch (err) {
361
- const msg = err instanceof Error ? err.message : String(err);
362
- // Upstream prints `psql:file:line: error: \!: <cmd>: <msg>`. We don't
363
- // have file/line context in the scanner; emit the prefix verbatim and
364
- // include the command for diagnosis.
365
- process.stderr.write(`psql: error: \\!: ${cmd}: ${msg}\n`);
366
- return "";
367
- }
326
+ if (cmd.length === 0) return "";
327
+ try {
328
+ const out = BACKTICK_EXECUTOR.current(cmd);
329
+ return out.endsWith("\n") ? out.slice(0, -1) : out;
330
+ } catch (err) {
331
+ const msg = err instanceof Error ? err.message : String(err);
332
+ process.stderr.write(`psql: error: \\!: ${cmd}: ${msg}\n`);
333
+ return "";
334
+ }
368
335
  };
369
336
  /**
370
- * Process the contents of a `` `…` `` slash-backquoted token.
371
- *
372
- * Phase 1 (this function): consume the body, expanding `:var` references
373
- * along the way (matching upstream's `xslashbackquote` rules).
374
- *
375
- * Phase 2 (delegated to {@link runBacktickCommand}): run the assembled
376
- * command via `sh -c` and return its stdout.
377
- *
378
- * Returns the command's stdout (one trailing `\n` stripped). Unterminated
379
- * backticks still execute the body so partial input doesn't silently
380
- * succeed; tests cover both well-formed and unterminated cases.
381
- */
337
+ * Process the contents of a `` `…` `` slash-backquoted token.
338
+ *
339
+ * Phase 1 (this function): consume the body, expanding `:var` references
340
+ * along the way (matching upstream's `xslashbackquote` rules).
341
+ *
342
+ * Phase 2 (delegated to {@link runBacktickCommand}): run the assembled
343
+ * command via `sh -c` and return its stdout.
344
+ *
345
+ * Returns the command's stdout (one trailing `\n` stripped). Unterminated
346
+ * backticks still execute the body so partial input doesn't silently
347
+ * succeed; tests cover both well-formed and unterminated cases.
348
+ */
382
349
  const consumeBackQuoted = (s, start, varLookup) => {
383
- let inner = "";
384
- let i = start;
385
- while (i < s.length) {
386
- const c = s[i];
387
- if (c === "`") {
388
- return { end: i + 1, text: runBacktickCommand(inner) };
389
- }
390
- const sub = tryConsumeVarSubstitution(s, i, varLookup);
391
- if (sub !== null) {
392
- inner += sub.text;
393
- i = sub.end;
394
- continue;
395
- }
396
- inner += c;
397
- i++;
398
- }
399
- // Unterminated — still run what we accumulated so the user can see the
400
- // error from `sh` itself rather than silently dropping the command.
401
- return { end: i, text: runBacktickCommand(inner) };
350
+ let inner = "";
351
+ let i = start;
352
+ while (i < s.length) {
353
+ const c = s[i];
354
+ if (c === "`") return {
355
+ end: i + 1,
356
+ text: runBacktickCommand(inner)
357
+ };
358
+ const sub = tryConsumeVarSubstitution(s, i, varLookup);
359
+ if (sub !== null) {
360
+ inner += sub.text;
361
+ i = sub.end;
362
+ continue;
363
+ }
364
+ inner += c;
365
+ i++;
366
+ }
367
+ return {
368
+ end: i,
369
+ text: runBacktickCommand(inner)
370
+ };
402
371
  };
403
372
  /**
404
- * Lex a single slash-command argument starting at `s[i]`. Returns the parsed
405
- * argument text and the index just past it, or `null` if no argument is
406
- * available before end of input.
407
- */
373
+ * Lex a single slash-command argument starting at `s[i]`. Returns the parsed
374
+ * argument text and the index just past it, or `null` if no argument is
375
+ * available before end of input.
376
+ */
408
377
  const scanOneArg = (s, i, mode, varLookup) => {
409
- // Skip leading whitespace (xslashargstart).
410
- while (i < s.length && isWhitespace(s[i]))
411
- i++;
412
- if (i >= s.length)
413
- return null;
414
- // filepipe special: a leading `|` flips into whole-line mode for this arg.
415
- if (mode === "filepipe" && s[i] === "|") {
416
- const rest = s.slice(i);
417
- return { end: s.length, arg: rest };
418
- }
419
- // Accumulate the argument piece by piece. Each iteration consumes either:
420
- // - a single-quoted run
421
- // - a double-quoted run
422
- // - a backticked run
423
- // - a :var / :'var' / :"var" substitution
424
- // - a literal character (the catch-all)
425
- // We stop on whitespace or `\` (which begins the next slash command).
426
- let out = "";
427
- while (i < s.length) {
428
- const c = s[i];
429
- if (isWhitespace(c))
430
- break;
431
- if (c === "\\")
432
- break;
433
- if (c === "'") {
434
- const r = consumeSingleQuoted(s, i + 1);
435
- out += r.text;
436
- i = r.end;
437
- continue;
438
- }
439
- if (c === '"') {
440
- const r = consumeDoubleQuoted(s, i + 1);
441
- out += r.text;
442
- i = r.end;
443
- continue;
444
- }
445
- if (c === "`") {
446
- const r = consumeBackQuoted(s, i + 1, varLookup);
447
- out += r.text;
448
- i = r.end;
449
- continue;
450
- }
451
- const sub = tryConsumeVarSubstitution(s, i, varLookup);
452
- if (sub !== null) {
453
- out += sub.text;
454
- i = sub.end;
455
- continue;
456
- }
457
- out += c;
458
- i++;
459
- }
460
- return { end: i, arg: out };
378
+ while (i < s.length && isWhitespace(s[i])) i++;
379
+ if (i >= s.length) return null;
380
+ if (mode === "filepipe" && s[i] === "|") {
381
+ const rest = s.slice(i);
382
+ return {
383
+ end: s.length,
384
+ arg: rest
385
+ };
386
+ }
387
+ let out = "";
388
+ while (i < s.length) {
389
+ const c = s[i];
390
+ if (isWhitespace(c)) break;
391
+ if (c === "\\") break;
392
+ if (c === "'") {
393
+ const r = consumeSingleQuoted(s, i + 1);
394
+ out += r.text;
395
+ i = r.end;
396
+ continue;
397
+ }
398
+ if (c === "\"") {
399
+ const r = consumeDoubleQuoted(s, i + 1);
400
+ out += r.text;
401
+ i = r.end;
402
+ continue;
403
+ }
404
+ if (c === "`") {
405
+ const r = consumeBackQuoted(s, i + 1, varLookup);
406
+ out += r.text;
407
+ i = r.end;
408
+ continue;
409
+ }
410
+ const sub = tryConsumeVarSubstitution(s, i, varLookup);
411
+ if (sub !== null) {
412
+ out += sub.text;
413
+ i = sub.end;
414
+ continue;
415
+ }
416
+ out += c;
417
+ i++;
418
+ }
419
+ return {
420
+ end: i,
421
+ arg: out
422
+ };
461
423
  };
462
424
  /**
463
- * Scan the argument portion of a backslash command.
464
- *
465
- * @param input the rest of the input line *after* the command name (e.g.
466
- * `" foo 'bar baz'"` for `\echo foo 'bar baz'`)
467
- * @param mode argument processing mode — see {@link SlashArgMode}
468
- * @param varLookup callback that resolves `:varname` references. Omit (or
469
- * pass `undefined`) for `no-vars` mode behaviour even when
470
- * `mode !== 'no-vars'`.
471
- *
472
- * @returns array of parsed argument strings; empty input yields `[]`.
473
- */
474
- export const scanSlashArgs = (input, mode, varLookup) => {
475
- // Whole-line: return everything, with leading whitespace suppressed and a
476
- // single trailing newline (if any) preserved verbatim. Empty (or
477
- // whitespace-only) input yields no args.
478
- if (mode === "whole-line") {
479
- let start = 0;
480
- while (start < input.length && isWhitespace(input[start]))
481
- start++;
482
- if (start >= input.length)
483
- return [];
484
- return [input.slice(start)];
485
- }
486
- const effectiveLookup = mode === "no-vars" ? undefined : varLookup;
487
- const args = [];
488
- let i = 0;
489
- while (i < input.length) {
490
- const result = scanOneArg(input, i, mode, effectiveLookup);
491
- if (result === null)
492
- break;
493
- let arg = result.arg;
494
- // sql-id / sql-id-keep-case post-process: collapse SQL-identifier
495
- // quoting, optionally downcasing unquoted letters.
496
- if (mode === "sql-id") {
497
- arg = dequoteDowncaseIdentifier(arg, true);
498
- }
499
- else if (mode === "sql-id-keep-case") {
500
- arg = dequoteDowncaseIdentifier(arg, false);
501
- }
502
- args.push(arg);
503
- i = result.end;
504
- // Consume the inter-arg whitespace so the next iteration starts cleanly.
505
- while (i < input.length && isWhitespace(input[i]))
506
- i++;
507
- // Stop on a `\` — start of the next backslash command.
508
- if (input[i] === "\\")
509
- break;
510
- }
511
- return args;
425
+ * Scan the argument portion of a backslash command.
426
+ *
427
+ * @param input the rest of the input line *after* the command name (e.g.
428
+ * `" foo 'bar baz'"` for `\echo foo 'bar baz'`)
429
+ * @param mode argument processing mode — see {@link SlashArgMode}
430
+ * @param varLookup callback that resolves `:varname` references. Omit (or
431
+ * pass `undefined`) for `no-vars` mode behaviour even when
432
+ * `mode !== 'no-vars'`.
433
+ *
434
+ * @returns array of parsed argument strings; empty input yields `[]`.
435
+ */
436
+ const scanSlashArgs = (input, mode, varLookup) => {
437
+ if (mode === "whole-line") {
438
+ let start = 0;
439
+ while (start < input.length && isWhitespace(input[start])) start++;
440
+ if (start >= input.length) return [];
441
+ return [input.slice(start)];
442
+ }
443
+ const effectiveLookup = mode === "no-vars" ? void 0 : varLookup;
444
+ const args = [];
445
+ let i = 0;
446
+ while (i < input.length) {
447
+ const result = scanOneArg(input, i, mode, effectiveLookup);
448
+ if (result === null) break;
449
+ let arg = result.arg;
450
+ if (mode === "sql-id") arg = dequoteDowncaseIdentifier(arg, true);
451
+ else if (mode === "sql-id-keep-case") arg = dequoteDowncaseIdentifier(arg, false);
452
+ args.push(arg);
453
+ i = result.end;
454
+ while (i < input.length && isWhitespace(input[i])) i++;
455
+ if (input[i] === "\\") break;
456
+ }
457
+ return args;
512
458
  };
513
- // Re-export `dequote` for callers that want to undo `quoteIfNeeded` on
514
- // scanned args without reaching across modules.
515
- export { dequote };
459
+ //#endregion
460
+ export { BACKTICK_EXECUTOR, dequote, scanSlashArgs };