neonctl 2.27.1 → 2.29.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.
- package/README.md +35 -3
- package/dist/analytics.js +52 -34
- package/dist/api.js +643 -13
- package/dist/auth.js +50 -44
- package/dist/cli.js +8 -1
- package/dist/commands/auth.js +64 -51
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +160 -150
- package/dist/commands/bucket.js +183 -146
- package/dist/commands/checkout.js +51 -51
- package/dist/commands/config.js +228 -82
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +100 -101
- package/dist/commands/databases.js +29 -26
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +101 -104
- package/dist/commands/index.js +27 -25
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +232 -182
- package/dist/commands/neon_auth.js +385 -370
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +103 -101
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +27 -24
- package/dist/commands/schema_diff.js +25 -26
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +40 -0
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +37 -14
- package/dist/current_branch_fast_path.js +55 -0
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +68 -5
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +110 -107
- package/dist/log.js +2 -2
- package/dist/parameters.gen.js +14 -14
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +22 -23
- package/dist/test_utils/fixtures.js +74 -41
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +33 -0
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +28 -16
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +10 -12
|
@@ -34,21 +34,20 @@
|
|
|
34
34
|
* additionally honours a separate "query output" file set via `\o`;
|
|
35
35
|
* that wiring lives in WP-15 and we leave the hook in place.
|
|
36
36
|
*/
|
|
37
|
-
import { spawnSync } from
|
|
38
|
-
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from
|
|
39
|
-
import { tmpdir } from
|
|
40
|
-
import { join } from
|
|
41
|
-
import {
|
|
42
|
-
import { getHistory } from
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import { writeOut, writeErr, parseBool } from './shared.js';
|
|
37
|
+
import { spawnSync } from "node:child_process";
|
|
38
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
39
|
+
import { tmpdir } from "node:os";
|
|
40
|
+
import { join } from "node:path";
|
|
41
|
+
import { helpSQL, slashUsage } from "../core/help.js";
|
|
42
|
+
import { getHistory } from "../io/history.js";
|
|
43
|
+
import { readLine } from "../io/input.js";
|
|
44
|
+
import { parseBool, writeErr, writeOut } from "./shared.js";
|
|
46
45
|
/** `\q` / `\quit` — exit the REPL. */
|
|
47
46
|
export const cmdQuit = {
|
|
48
|
-
name:
|
|
49
|
-
aliases: [
|
|
50
|
-
helpKey:
|
|
51
|
-
run: () => Promise.resolve({ status:
|
|
47
|
+
name: "q",
|
|
48
|
+
aliases: ["quit"],
|
|
49
|
+
helpKey: "q",
|
|
50
|
+
run: () => Promise.resolve({ status: "exit" }),
|
|
52
51
|
};
|
|
53
52
|
/**
|
|
54
53
|
* `\!` — shell escape. Whole-line mode: the entire rest of the line is the
|
|
@@ -64,18 +63,18 @@ export const cmdQuit = {
|
|
|
64
63
|
* resilient against environments where `sh` is unavailable.
|
|
65
64
|
*/
|
|
66
65
|
export const cmdShell = {
|
|
67
|
-
name:
|
|
68
|
-
argMode:
|
|
69
|
-
helpKey:
|
|
66
|
+
name: "!",
|
|
67
|
+
argMode: "whole-line",
|
|
68
|
+
helpKey: "!",
|
|
70
69
|
run: (ctx) => {
|
|
71
70
|
const line = ctx.restOfLine().trim();
|
|
72
71
|
try {
|
|
73
72
|
if (line.length === 0) {
|
|
74
|
-
const shell = process.env.SHELL ??
|
|
75
|
-
spawnSync(shell, [
|
|
73
|
+
const shell = process.env.SHELL ?? "/bin/sh";
|
|
74
|
+
spawnSync(shell, ["-i"], { stdio: "inherit" });
|
|
76
75
|
}
|
|
77
76
|
else {
|
|
78
|
-
spawnSync(
|
|
77
|
+
spawnSync("sh", ["-c", line], { stdio: "inherit" });
|
|
79
78
|
}
|
|
80
79
|
}
|
|
81
80
|
catch {
|
|
@@ -83,28 +82,28 @@ export const cmdShell = {
|
|
|
83
82
|
// keep running even when the child won't start. Status stays `ok` so
|
|
84
83
|
// a failing `\!` is purely informational.
|
|
85
84
|
}
|
|
86
|
-
return Promise.resolve({ status:
|
|
85
|
+
return Promise.resolve({ status: "ok" });
|
|
87
86
|
},
|
|
88
87
|
};
|
|
89
88
|
/** `\cd [dir]` — change cwd. No arg falls back to `$HOME`. */
|
|
90
89
|
export const cmdCd = {
|
|
91
|
-
name:
|
|
92
|
-
helpKey:
|
|
90
|
+
name: "cd",
|
|
91
|
+
helpKey: "cd",
|
|
93
92
|
run: (ctx) => {
|
|
94
|
-
const dir = ctx.nextArg(
|
|
93
|
+
const dir = ctx.nextArg("normal");
|
|
95
94
|
const target = dir && dir.length > 0 ? dir : (process.env.HOME ?? null);
|
|
96
95
|
if (!target) {
|
|
97
96
|
writeErr(`\\${ctx.cmdName}: could not determine home directory\n`);
|
|
98
|
-
return Promise.resolve({ status:
|
|
97
|
+
return Promise.resolve({ status: "error" });
|
|
99
98
|
}
|
|
100
99
|
try {
|
|
101
100
|
process.chdir(target);
|
|
102
|
-
return Promise.resolve({ status:
|
|
101
|
+
return Promise.resolve({ status: "ok" });
|
|
103
102
|
}
|
|
104
103
|
catch (err) {
|
|
105
104
|
const msg = err instanceof Error ? err.message : String(err);
|
|
106
105
|
writeErr(`\\${ctx.cmdName}: ${msg}\n`);
|
|
107
|
-
return Promise.resolve({ status:
|
|
106
|
+
return Promise.resolve({ status: "error" });
|
|
108
107
|
}
|
|
109
108
|
},
|
|
110
109
|
};
|
|
@@ -130,14 +129,14 @@ const runEcho = (ctx, write) => {
|
|
|
130
129
|
let i = 0;
|
|
131
130
|
while (i < ctx.rawArgs.length && /\s/.test(ctx.rawArgs[i]))
|
|
132
131
|
i++;
|
|
133
|
-
return (ctx.rawArgs.slice(i, i + 2) ===
|
|
132
|
+
return (ctx.rawArgs.slice(i, i + 2) === "-n" &&
|
|
134
133
|
(i + 2 === ctx.rawArgs.length || /\s/.test(ctx.rawArgs[i + 2])));
|
|
135
134
|
})();
|
|
136
135
|
for (;;) {
|
|
137
|
-
const arg = ctx.nextArg(
|
|
136
|
+
const arg = ctx.nextArg("normal");
|
|
138
137
|
if (arg === null)
|
|
139
138
|
break;
|
|
140
|
-
if (first && firstArgIsUnquotedDashN && arg ===
|
|
139
|
+
if (first && firstArgIsUnquotedDashN && arg === "-n") {
|
|
141
140
|
noNewline = true;
|
|
142
141
|
first = false;
|
|
143
142
|
continue;
|
|
@@ -145,20 +144,20 @@ const runEcho = (ctx, write) => {
|
|
|
145
144
|
first = false;
|
|
146
145
|
parts.push(arg);
|
|
147
146
|
}
|
|
148
|
-
const out = parts.join(
|
|
147
|
+
const out = parts.join(" ") + (noNewline ? "" : "\n");
|
|
149
148
|
write(out);
|
|
150
|
-
return { status:
|
|
149
|
+
return { status: "ok" };
|
|
151
150
|
};
|
|
152
151
|
/** `\echo` — write args to stdout. */
|
|
153
152
|
export const cmdEcho = {
|
|
154
|
-
name:
|
|
155
|
-
helpKey:
|
|
153
|
+
name: "echo",
|
|
154
|
+
helpKey: "echo",
|
|
156
155
|
run: (ctx) => Promise.resolve(runEcho(ctx, writeOut)),
|
|
157
156
|
};
|
|
158
157
|
/** `\qecho` — write args to the query output (logfile if set, else stdout). */
|
|
159
158
|
export const cmdQecho = {
|
|
160
|
-
name:
|
|
161
|
-
helpKey:
|
|
159
|
+
name: "qecho",
|
|
160
|
+
helpKey: "qecho",
|
|
162
161
|
run: (ctx) => {
|
|
163
162
|
const { logfile } = ctx.settings;
|
|
164
163
|
const write = (s) => {
|
|
@@ -174,8 +173,8 @@ export const cmdQecho = {
|
|
|
174
173
|
};
|
|
175
174
|
/** `\warn` — write args to stderr. */
|
|
176
175
|
export const cmdWarn = {
|
|
177
|
-
name:
|
|
178
|
-
helpKey:
|
|
176
|
+
name: "warn",
|
|
177
|
+
helpKey: "warn",
|
|
179
178
|
run: (ctx) => Promise.resolve(runEcho(ctx, writeErr)),
|
|
180
179
|
};
|
|
181
180
|
/**
|
|
@@ -193,27 +192,27 @@ export const cmdWarn = {
|
|
|
193
192
|
* the first is the prompt and the second the variable.
|
|
194
193
|
*/
|
|
195
194
|
export const cmdPrompt = {
|
|
196
|
-
name:
|
|
197
|
-
helpKey:
|
|
195
|
+
name: "prompt",
|
|
196
|
+
helpKey: "prompt",
|
|
198
197
|
run: async (ctx) => {
|
|
199
198
|
const args = [];
|
|
200
199
|
for (;;) {
|
|
201
|
-
const a = ctx.nextArg(
|
|
200
|
+
const a = ctx.nextArg("normal");
|
|
202
201
|
if (a === null)
|
|
203
202
|
break;
|
|
204
203
|
args.push(a);
|
|
205
204
|
}
|
|
206
205
|
// A leading `-` selects the no-echo (password) read path.
|
|
207
206
|
let echo = true;
|
|
208
|
-
if (args.length > 0 && args[0] ===
|
|
207
|
+
if (args.length > 0 && args[0] === "-") {
|
|
209
208
|
echo = false;
|
|
210
209
|
args.shift();
|
|
211
210
|
}
|
|
212
211
|
if (args.length === 0) {
|
|
213
212
|
writeErr(`\\${ctx.cmdName}: missing required argument\n`);
|
|
214
|
-
return { status:
|
|
213
|
+
return { status: "error" };
|
|
215
214
|
}
|
|
216
|
-
let promptText =
|
|
215
|
+
let promptText = "";
|
|
217
216
|
let varname;
|
|
218
217
|
if (args.length === 1) {
|
|
219
218
|
varname = args[0];
|
|
@@ -225,9 +224,9 @@ export const cmdPrompt = {
|
|
|
225
224
|
const line = await readLine(promptText, { echo });
|
|
226
225
|
if (!ctx.settings.vars.set(varname, line)) {
|
|
227
226
|
writeErr(`\\${ctx.cmdName}: invalid variable name "${varname}"\n`);
|
|
228
|
-
return { status:
|
|
227
|
+
return { status: "error" };
|
|
229
228
|
}
|
|
230
|
-
return { status:
|
|
229
|
+
return { status: "ok" };
|
|
231
230
|
},
|
|
232
231
|
};
|
|
233
232
|
/**
|
|
@@ -250,30 +249,30 @@ export const cmdPrompt = {
|
|
|
250
249
|
* accepts or returns a wording string.
|
|
251
250
|
*/
|
|
252
251
|
export const cmdSet = {
|
|
253
|
-
name:
|
|
254
|
-
helpKey:
|
|
252
|
+
name: "set",
|
|
253
|
+
helpKey: "set",
|
|
255
254
|
run: (ctx) => {
|
|
256
|
-
const name = ctx.nextArg(
|
|
255
|
+
const name = ctx.nextArg("normal");
|
|
257
256
|
if (name === null) {
|
|
258
257
|
// List all vars sorted by name.
|
|
259
258
|
const entries = [...ctx.settings.vars.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
260
259
|
for (const [k, v] of entries) {
|
|
261
260
|
writeOut(`${k} = '${v}'\n`);
|
|
262
261
|
}
|
|
263
|
-
return Promise.resolve({ status:
|
|
262
|
+
return Promise.resolve({ status: "ok" });
|
|
264
263
|
}
|
|
265
264
|
const values = [];
|
|
266
265
|
for (;;) {
|
|
267
|
-
const a = ctx.nextArg(
|
|
266
|
+
const a = ctx.nextArg("normal");
|
|
268
267
|
if (a === null)
|
|
269
268
|
break;
|
|
270
269
|
values.push(a);
|
|
271
270
|
}
|
|
272
|
-
const value = values.join(
|
|
271
|
+
const value = values.join("");
|
|
273
272
|
const result = ctx.settings.vars.trySet(name, value);
|
|
274
273
|
if (!result.ok) {
|
|
275
274
|
const prefix = psqlErrorPrefix(ctx.settings);
|
|
276
|
-
if (result.reason ===
|
|
275
|
+
if (result.reason === "invalid-name") {
|
|
277
276
|
writeErr(`${prefix}invalid variable name: "${name}"\n`);
|
|
278
277
|
}
|
|
279
278
|
else if (result.error !== undefined) {
|
|
@@ -289,9 +288,9 @@ export const cmdSet = {
|
|
|
289
288
|
// built-in hooks take this path, but third-party callers might.
|
|
290
289
|
writeErr(`${prefix}error while setting variable "${name}"\n`);
|
|
291
290
|
}
|
|
292
|
-
return Promise.resolve({ status:
|
|
291
|
+
return Promise.resolve({ status: "error" });
|
|
293
292
|
}
|
|
294
|
-
return Promise.resolve({ status:
|
|
293
|
+
return Promise.resolve({ status: "ok" });
|
|
295
294
|
},
|
|
296
295
|
};
|
|
297
296
|
/**
|
|
@@ -310,49 +309,49 @@ export const cmdSet = {
|
|
|
310
309
|
* the regress harness, which passes `--quiet`) produces no output.
|
|
311
310
|
*/
|
|
312
311
|
export const cmdReset = {
|
|
313
|
-
name:
|
|
314
|
-
aliases: [
|
|
315
|
-
helpKey:
|
|
312
|
+
name: "r",
|
|
313
|
+
aliases: ["reset"],
|
|
314
|
+
helpKey: "r",
|
|
316
315
|
run: (ctx) => {
|
|
317
316
|
if (!ctx.settings.quiet) {
|
|
318
|
-
writeOut(
|
|
317
|
+
writeOut("Query buffer reset (cleared).\n");
|
|
319
318
|
}
|
|
320
|
-
return Promise.resolve({ status:
|
|
319
|
+
return Promise.resolve({ status: "reset-buf", newBuf: "" });
|
|
321
320
|
},
|
|
322
321
|
};
|
|
323
322
|
/** `\unset varname` — unset a psql variable. */
|
|
324
323
|
export const cmdUnset = {
|
|
325
|
-
name:
|
|
326
|
-
helpKey:
|
|
324
|
+
name: "unset",
|
|
325
|
+
helpKey: "unset",
|
|
327
326
|
run: (ctx) => {
|
|
328
|
-
const name = ctx.nextArg(
|
|
327
|
+
const name = ctx.nextArg("normal");
|
|
329
328
|
if (name === null) {
|
|
330
329
|
writeErr(`\\${ctx.cmdName}: missing required argument\n`);
|
|
331
|
-
return Promise.resolve({ status:
|
|
330
|
+
return Promise.resolve({ status: "error" });
|
|
332
331
|
}
|
|
333
332
|
ctx.settings.vars.unset(name);
|
|
334
|
-
return Promise.resolve({ status:
|
|
333
|
+
return Promise.resolve({ status: "ok" });
|
|
335
334
|
},
|
|
336
335
|
};
|
|
337
336
|
export const cmdGetenv = {
|
|
338
|
-
name:
|
|
339
|
-
helpKey:
|
|
337
|
+
name: "getenv",
|
|
338
|
+
helpKey: "getenv",
|
|
340
339
|
run: (ctx) => {
|
|
341
|
-
const varname = ctx.nextArg(
|
|
342
|
-
const envname = ctx.nextArg(
|
|
340
|
+
const varname = ctx.nextArg("normal");
|
|
341
|
+
const envname = ctx.nextArg("normal");
|
|
343
342
|
if (varname === null || envname === null) {
|
|
344
343
|
writeErr(`\\${ctx.cmdName}: missing required argument\n`);
|
|
345
|
-
return Promise.resolve({ status:
|
|
344
|
+
return Promise.resolve({ status: "error" });
|
|
346
345
|
}
|
|
347
346
|
const value = process.env[envname];
|
|
348
347
|
if (value === undefined) {
|
|
349
|
-
return Promise.resolve({ status:
|
|
348
|
+
return Promise.resolve({ status: "ok" });
|
|
350
349
|
}
|
|
351
350
|
if (!ctx.settings.vars.set(varname, value)) {
|
|
352
351
|
writeErr(`\\${ctx.cmdName}: invalid variable name "${varname}"\n`);
|
|
353
|
-
return Promise.resolve({ status:
|
|
352
|
+
return Promise.resolve({ status: "error" });
|
|
354
353
|
}
|
|
355
|
-
return Promise.resolve({ status:
|
|
354
|
+
return Promise.resolve({ status: "ok" });
|
|
356
355
|
},
|
|
357
356
|
};
|
|
358
357
|
/**
|
|
@@ -362,17 +361,17 @@ export const cmdGetenv = {
|
|
|
362
361
|
* rejects names containing `=`.
|
|
363
362
|
*/
|
|
364
363
|
export const cmdSetenv = {
|
|
365
|
-
name:
|
|
366
|
-
helpKey:
|
|
364
|
+
name: "setenv",
|
|
365
|
+
helpKey: "setenv",
|
|
367
366
|
run: (ctx) => {
|
|
368
|
-
const envname = ctx.nextArg(
|
|
367
|
+
const envname = ctx.nextArg("normal");
|
|
369
368
|
if (envname === null) {
|
|
370
369
|
writeErr(`\\${ctx.cmdName}: missing required argument\n`);
|
|
371
|
-
return Promise.resolve({ status:
|
|
370
|
+
return Promise.resolve({ status: "error" });
|
|
372
371
|
}
|
|
373
|
-
if (envname.includes(
|
|
372
|
+
if (envname.includes("=")) {
|
|
374
373
|
writeErr(`\\${ctx.cmdName}: environment variable name must not contain "="\n`);
|
|
375
|
-
return Promise.resolve({ status:
|
|
374
|
+
return Promise.resolve({ status: "error" });
|
|
376
375
|
}
|
|
377
376
|
// Upstream `exec_command_setenv` reads BOTH the name AND the value with
|
|
378
377
|
// OT_NORMAL — `:VAR` substitution applies to the value so
|
|
@@ -381,7 +380,7 @@ export const cmdSetenv = {
|
|
|
381
380
|
// value.) The mainloop context maintains a per-mode cursor, so using
|
|
382
381
|
// a single mode for both calls also keeps positional reads in sync —
|
|
383
382
|
// each cursor advances exactly once per call.
|
|
384
|
-
const value = ctx.nextArg(
|
|
383
|
+
const value = ctx.nextArg("normal");
|
|
385
384
|
if (value === null) {
|
|
386
385
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
387
386
|
delete process.env[envname];
|
|
@@ -389,7 +388,7 @@ export const cmdSetenv = {
|
|
|
389
388
|
else {
|
|
390
389
|
process.env[envname] = value;
|
|
391
390
|
}
|
|
392
|
-
return Promise.resolve({ status:
|
|
391
|
+
return Promise.resolve({ status: "ok" });
|
|
393
392
|
},
|
|
394
393
|
};
|
|
395
394
|
/**
|
|
@@ -409,11 +408,11 @@ export const cmdSetenv = {
|
|
|
409
408
|
* concatenate the severity directly (`prefix + 'ERROR: msg'`).
|
|
410
409
|
*/
|
|
411
410
|
export const psqlErrorPrefix = (settings, lineNumber) => {
|
|
412
|
-
if (settings.curCmdSource ===
|
|
413
|
-
const lineSuffix = lineNumber !== undefined ? String(lineNumber) :
|
|
411
|
+
if (settings.curCmdSource === "file" && settings.inputfile) {
|
|
412
|
+
const lineSuffix = lineNumber !== undefined ? String(lineNumber) : "";
|
|
414
413
|
return `psql:${settings.inputfile}:${lineSuffix}: `;
|
|
415
414
|
}
|
|
416
|
-
return
|
|
415
|
+
return "";
|
|
417
416
|
};
|
|
418
417
|
/**
|
|
419
418
|
* Walk past leading whitespace + `--` line comments + slash-star block
|
|
@@ -535,9 +534,9 @@ export const renderLineAndCaret = (sqlText, position) => {
|
|
|
535
534
|
// The server's offset is 1-based and points at the failing character.
|
|
536
535
|
const idx = Math.min(rebasedPos - 1, trimmed.length);
|
|
537
536
|
// Find the line containing `idx`.
|
|
538
|
-
let lineStart = trimmed.lastIndexOf(
|
|
537
|
+
let lineStart = trimmed.lastIndexOf("\n", idx - 1);
|
|
539
538
|
lineStart = lineStart === -1 ? 0 : lineStart + 1;
|
|
540
|
-
let lineEnd = trimmed.indexOf(
|
|
539
|
+
let lineEnd = trimmed.indexOf("\n", lineStart);
|
|
541
540
|
if (lineEnd === -1)
|
|
542
541
|
lineEnd = trimmed.length;
|
|
543
542
|
const lineText = trimmed.slice(lineStart, lineEnd);
|
|
@@ -550,15 +549,15 @@ export const renderLineAndCaret = (sqlText, position) => {
|
|
|
550
549
|
let col = idx - lineStart;
|
|
551
550
|
// Snap past trailing whitespace when the position lands inside it —
|
|
552
551
|
// see the function header for the rationale (trim-on-send delta).
|
|
553
|
-
const lineTrimEndLen = lineText.replace(/[ \t\f\v]+$/u,
|
|
552
|
+
const lineTrimEndLen = lineText.replace(/[ \t\f\v]+$/u, "").length;
|
|
554
553
|
if (col >= lineTrimEndLen && col < lineText.length) {
|
|
555
554
|
col = lineText.length;
|
|
556
555
|
}
|
|
557
|
-
const caretIndent =
|
|
556
|
+
const caretIndent = " ".repeat(Math.max(0, col));
|
|
558
557
|
const prefix = `LINE ${String(lineNumber)}: `;
|
|
559
558
|
return {
|
|
560
559
|
line: `${prefix}${lineText}`,
|
|
561
|
-
caret: `${
|
|
560
|
+
caret: `${" ".repeat(prefix.length)}${caretIndent}^`,
|
|
562
561
|
};
|
|
563
562
|
};
|
|
564
563
|
/**
|
|
@@ -588,10 +587,10 @@ export const renderLineAndCaret = (sqlText, position) => {
|
|
|
588
587
|
* appears when we have both originating SQL text and a 1-based position
|
|
589
588
|
* pointing inside it.
|
|
590
589
|
*/
|
|
591
|
-
export const formatErrorReport = (e, verbosity =
|
|
592
|
-
const severity = e.severity ??
|
|
593
|
-
const sqlstate = e.code ?? e.sqlstate ??
|
|
594
|
-
const message = e.message ??
|
|
590
|
+
export const formatErrorReport = (e, verbosity = "default", showContext = "errors") => {
|
|
591
|
+
const severity = e.severity ?? "ERROR";
|
|
592
|
+
const sqlstate = e.code ?? e.sqlstate ?? "XX000";
|
|
593
|
+
const message = e.message ?? "";
|
|
595
594
|
const out = [];
|
|
596
595
|
// `sqlstate` mode is the upstream "just give me the code" flavour:
|
|
597
596
|
// emit `<severity>: <sqlstate>` with NO message body. `verbose` mode
|
|
@@ -601,14 +600,14 @@ export const formatErrorReport = (e, verbosity = 'default', showContext = 'error
|
|
|
601
600
|
// Reference: upstream `pg_log_pre_callback` / `PQresultErrorMessage`
|
|
602
601
|
// with `verbosity = PQERRORS_SQLSTATE`, which formats just
|
|
603
602
|
// `severity: sqlstate\n` and stops.
|
|
604
|
-
if (verbosity ===
|
|
603
|
+
if (verbosity === "sqlstate") {
|
|
605
604
|
out.push(`${severity}: ${sqlstate}`);
|
|
606
605
|
return out;
|
|
607
606
|
}
|
|
608
|
-
if (verbosity ===
|
|
607
|
+
if (verbosity === "verbose") {
|
|
609
608
|
out.push(`${severity}: ${sqlstate}: ${message}`);
|
|
610
609
|
}
|
|
611
|
-
else if (verbosity ===
|
|
610
|
+
else if (verbosity === "terse") {
|
|
612
611
|
// Terse suppresses LINE/caret/DETAIL/HINT/CONTEXT, but it merges the
|
|
613
612
|
// server's `position` into the severity line as `at character N` —
|
|
614
613
|
// matches libpq's `pqGetErrorNotice3` with `PQERRORS_TERSE` (and
|
|
@@ -641,12 +640,12 @@ export const formatErrorReport = (e, verbosity = 'default', showContext = 'error
|
|
|
641
640
|
// formatter as an error report, so 'errors' and 'always' both include
|
|
642
641
|
// CONTEXT, while 'never' suppresses it. Verbose verbosity unconditionally
|
|
643
642
|
// includes CONTEXT.
|
|
644
|
-
const includeContext = verbosity ===
|
|
643
|
+
const includeContext = verbosity === "verbose" || showContext !== "never";
|
|
645
644
|
if (includeContext && e.where) {
|
|
646
645
|
out.push(`CONTEXT: ${e.where}`);
|
|
647
646
|
}
|
|
648
|
-
if (verbosity ===
|
|
649
|
-
const location = (e.routine ??
|
|
647
|
+
if (verbosity === "verbose" && (e.routine || e.file || e.line)) {
|
|
648
|
+
const location = (e.routine ?? "") + (e.file ? `, ${e.file}:${e.line ?? ""}` : "");
|
|
650
649
|
out.push(`LOCATION: ${location}`);
|
|
651
650
|
}
|
|
652
651
|
return out;
|
|
@@ -671,27 +670,27 @@ export const formatErrorReport = (e, verbosity = 'default', showContext = 'error
|
|
|
671
670
|
* we have both the originating SQL text and a server-provided position.
|
|
672
671
|
*/
|
|
673
672
|
export const cmdErrverbose = {
|
|
674
|
-
name:
|
|
675
|
-
helpKey:
|
|
673
|
+
name: "errverbose",
|
|
674
|
+
helpKey: "errverbose",
|
|
676
675
|
run: (ctx) => {
|
|
677
676
|
const e = ctx.settings.lastErrorResult;
|
|
678
677
|
if (!e || (!e.message && !e.sqlstate && !e.code)) {
|
|
679
678
|
// Upstream `exec_command_errverbose` writes the "no previous error"
|
|
680
679
|
// notice to stdout (via `printf`); only the verbose re-render goes to
|
|
681
680
|
// stderr (via `pg_log_error`).
|
|
682
|
-
writeOut(
|
|
683
|
-
return Promise.resolve({ status:
|
|
681
|
+
writeOut("There is no previous error.\n");
|
|
682
|
+
return Promise.resolve({ status: "ok" });
|
|
684
683
|
}
|
|
685
684
|
// `\errverbose` always emits the full verbose form regardless of the
|
|
686
685
|
// currently active VERBOSITY setting. Output is prefixed with the same
|
|
687
686
|
// `psql:[<file>:<n>]:` tag upstream's `pg_log_pre_callback` adds — only
|
|
688
687
|
// on the leading severity line; subsequent layers (LINE / caret / DETAIL
|
|
689
688
|
// / HINT / LOCATION) stay unprefixed to match libpq's `PQresultErrorMessage`.
|
|
690
|
-
const lines = formatErrorReport(e,
|
|
689
|
+
const lines = formatErrorReport(e, "verbose", "always");
|
|
691
690
|
const prefix = psqlErrorPrefix(ctx.settings);
|
|
692
691
|
const prefixed = [prefix + lines[0], ...lines.slice(1)];
|
|
693
|
-
writeErr(prefixed.join(
|
|
694
|
-
return Promise.resolve({ status:
|
|
692
|
+
writeErr(prefixed.join("\n") + "\n");
|
|
693
|
+
return Promise.resolve({ status: "ok" });
|
|
695
694
|
},
|
|
696
695
|
};
|
|
697
696
|
/**
|
|
@@ -700,10 +699,10 @@ export const cmdErrverbose = {
|
|
|
700
699
|
* upstream errors "Boolean expected" (review: minor divergences).
|
|
701
700
|
*/
|
|
702
701
|
export const cmdTiming = {
|
|
703
|
-
name:
|
|
704
|
-
helpKey:
|
|
702
|
+
name: "timing",
|
|
703
|
+
helpKey: "timing",
|
|
705
704
|
run: (ctx) => {
|
|
706
|
-
const arg = ctx.nextArg(
|
|
705
|
+
const arg = ctx.nextArg("normal");
|
|
707
706
|
let next;
|
|
708
707
|
if (arg === null) {
|
|
709
708
|
next = !ctx.settings.timing;
|
|
@@ -712,13 +711,13 @@ export const cmdTiming = {
|
|
|
712
711
|
const parsed = parseBool(arg);
|
|
713
712
|
if (parsed === null) {
|
|
714
713
|
writeErr(`\\${ctx.cmdName}: unrecognized value "${arg}" for "\\timing": Boolean expected\n`);
|
|
715
|
-
return Promise.resolve({ status:
|
|
714
|
+
return Promise.resolve({ status: "error" });
|
|
716
715
|
}
|
|
717
716
|
next = parsed;
|
|
718
717
|
}
|
|
719
718
|
ctx.settings.timing = next;
|
|
720
|
-
writeOut(`Timing is ${next ?
|
|
721
|
-
return Promise.resolve({ status:
|
|
719
|
+
writeOut(`Timing is ${next ? "on" : "off"}.\n`);
|
|
720
|
+
return Promise.resolve({ status: "ok" });
|
|
722
721
|
},
|
|
723
722
|
};
|
|
724
723
|
/**
|
|
@@ -772,11 +771,11 @@ LICENSE file for distribution terms.
|
|
|
772
771
|
* Neon notice is appended after it.
|
|
773
772
|
*/
|
|
774
773
|
export const cmdCopyright = {
|
|
775
|
-
name:
|
|
776
|
-
helpKey:
|
|
774
|
+
name: "copyright",
|
|
775
|
+
helpKey: "copyright",
|
|
777
776
|
run: () => {
|
|
778
777
|
writeOut(COPYRIGHT_TEXT + NEON_NOTICE);
|
|
779
|
-
return Promise.resolve({ status:
|
|
778
|
+
return Promise.resolve({ status: "ok" });
|
|
780
779
|
},
|
|
781
780
|
};
|
|
782
781
|
/**
|
|
@@ -787,7 +786,7 @@ export const cmdCopyright = {
|
|
|
787
786
|
*/
|
|
788
787
|
const screenWidth = () => {
|
|
789
788
|
const cols = process.stdout.columns;
|
|
790
|
-
return typeof cols ===
|
|
789
|
+
return typeof cols === "number" && cols > 0 ? cols : 80;
|
|
791
790
|
};
|
|
792
791
|
/**
|
|
793
792
|
* `\h [TOPIC]` (alias `\help`) — show SQL command help.
|
|
@@ -798,15 +797,15 @@ const screenWidth = () => {
|
|
|
798
797
|
* matches. Mirrors upstream `exec_command_help` in `command.c`.
|
|
799
798
|
*/
|
|
800
799
|
export const cmdHelpSQL = {
|
|
801
|
-
name:
|
|
802
|
-
aliases: [
|
|
803
|
-
helpKey:
|
|
800
|
+
name: "h",
|
|
801
|
+
aliases: ["help"],
|
|
802
|
+
helpKey: "h",
|
|
804
803
|
run: (ctx) => {
|
|
805
804
|
// Upstream consumes the rest of the line in `OT_WHOLE_LINE` mode so
|
|
806
805
|
// multi-word topics like "CREATE TABLE" come through intact.
|
|
807
806
|
const topic = ctx.restOfLine();
|
|
808
807
|
helpSQL(process.stdout, topic.length === 0 ? null : topic, screenWidth());
|
|
809
|
-
return Promise.resolve({ status:
|
|
808
|
+
return Promise.resolve({ status: "ok" });
|
|
810
809
|
},
|
|
811
810
|
};
|
|
812
811
|
/**
|
|
@@ -824,9 +823,9 @@ export const cmdHelpSQL = {
|
|
|
824
823
|
* so a stray topic doesn't leak into the next command.
|
|
825
824
|
*/
|
|
826
825
|
export const cmdSlashHelp = {
|
|
827
|
-
name:
|
|
828
|
-
argMode:
|
|
829
|
-
helpKey:
|
|
826
|
+
name: "?",
|
|
827
|
+
argMode: "whole-line",
|
|
828
|
+
helpKey: "?",
|
|
830
829
|
run: (ctx) => {
|
|
831
830
|
// Consume the rest of the line (`\? options`, `\? variables`) so the
|
|
832
831
|
// cursor doesn't strand trailing text; we only render the command help.
|
|
@@ -834,7 +833,7 @@ export const cmdSlashHelp = {
|
|
|
834
833
|
const out = process.stdout;
|
|
835
834
|
const pager = Boolean(out.isTTY);
|
|
836
835
|
slashUsage(out, pager);
|
|
837
|
-
return Promise.resolve({ status:
|
|
836
|
+
return Promise.resolve({ status: "ok" });
|
|
838
837
|
},
|
|
839
838
|
};
|
|
840
839
|
/**
|
|
@@ -850,7 +849,7 @@ export const resolveEditor = (env = process.env) => {
|
|
|
850
849
|
const explicit = env.PSQL_EDITOR ?? env.EDITOR ?? env.VISUAL;
|
|
851
850
|
if (explicit !== undefined && explicit.length > 0)
|
|
852
851
|
return explicit;
|
|
853
|
-
return process.platform ===
|
|
852
|
+
return process.platform === "win32" ? "notepad.exe" : "vi";
|
|
854
853
|
};
|
|
855
854
|
/**
|
|
856
855
|
* `\e` / `\edit [FILE] [LINE]` — edit the current query buffer (or a file)
|
|
@@ -875,43 +874,44 @@ export const resolveEditor = (env = process.env) => {
|
|
|
875
874
|
* (`\e file`) and `\ef`/`\ev` on top.
|
|
876
875
|
*/
|
|
877
876
|
export const cmdEdit = {
|
|
878
|
-
name:
|
|
879
|
-
aliases: [
|
|
880
|
-
argMode:
|
|
881
|
-
helpKey:
|
|
877
|
+
name: "e",
|
|
878
|
+
aliases: ["edit"],
|
|
879
|
+
argMode: "whole-line",
|
|
880
|
+
helpKey: "e",
|
|
882
881
|
run: (ctx) => {
|
|
883
882
|
// We don't yet support `\e FILE`; consume the args so they don't strand.
|
|
884
883
|
ctx.restOfLine();
|
|
885
884
|
const editor = resolveEditor();
|
|
886
885
|
// psql seeds the temp file with the current query buffer. A trailing
|
|
887
886
|
// newline keeps editors that expect newline-terminated files happy.
|
|
888
|
-
const seed = ctx.queryBuf.length > 0 && !ctx.queryBuf.endsWith(
|
|
889
|
-
? ctx.queryBuf +
|
|
887
|
+
const seed = ctx.queryBuf.length > 0 && !ctx.queryBuf.endsWith("\n")
|
|
888
|
+
? ctx.queryBuf + "\n"
|
|
890
889
|
: ctx.queryBuf;
|
|
891
890
|
let dir = null;
|
|
892
891
|
try {
|
|
893
|
-
dir = mkdtempSync(join(tmpdir(),
|
|
894
|
-
const file = join(dir,
|
|
895
|
-
writeFileSync(file, seed,
|
|
896
|
-
const result = spawnSync(editor, [file], { stdio:
|
|
897
|
-
if (result.error ||
|
|
892
|
+
dir = mkdtempSync(join(tmpdir(), "psql.edit."));
|
|
893
|
+
const file = join(dir, "edit.sql");
|
|
894
|
+
writeFileSync(file, seed, "utf8");
|
|
895
|
+
const result = spawnSync(editor, [file], { stdio: "inherit" });
|
|
896
|
+
if (result.error ||
|
|
897
|
+
(result.status !== null && result.status !== 0)) {
|
|
898
898
|
const why = result.error
|
|
899
899
|
? result.error.message
|
|
900
900
|
: `editor exited with status ${String(result.status)}`;
|
|
901
901
|
writeErr(`\\${ctx.cmdName}: ${why}\n`);
|
|
902
|
-
return Promise.resolve({ status:
|
|
902
|
+
return Promise.resolve({ status: "error" });
|
|
903
903
|
}
|
|
904
|
-
let edited = readFileSync(file,
|
|
904
|
+
let edited = readFileSync(file, "utf8");
|
|
905
905
|
// Upstream drops a single trailing newline the editor may have added
|
|
906
906
|
// so an unchanged round-trip restores the original buffer exactly.
|
|
907
|
-
if (edited.endsWith(
|
|
907
|
+
if (edited.endsWith("\n"))
|
|
908
908
|
edited = edited.slice(0, -1);
|
|
909
|
-
return Promise.resolve({ status:
|
|
909
|
+
return Promise.resolve({ status: "reset-buf", newBuf: edited });
|
|
910
910
|
}
|
|
911
911
|
catch (err) {
|
|
912
912
|
const msg = err instanceof Error ? err.message : String(err);
|
|
913
913
|
writeErr(`\\${ctx.cmdName}: ${msg}\n`);
|
|
914
|
-
return Promise.resolve({ status:
|
|
914
|
+
return Promise.resolve({ status: "error" });
|
|
915
915
|
}
|
|
916
916
|
finally {
|
|
917
917
|
if (dir) {
|
|
@@ -942,29 +942,29 @@ export const cmdEdit = {
|
|
|
942
942
|
* populated as each line is submitted (see `io/history.ts`).
|
|
943
943
|
*/
|
|
944
944
|
export const cmdS = {
|
|
945
|
-
name:
|
|
946
|
-
helpKey:
|
|
945
|
+
name: "s",
|
|
946
|
+
helpKey: "s",
|
|
947
947
|
run: (ctx) => {
|
|
948
|
-
const fname = ctx.nextArg(
|
|
948
|
+
const fname = ctx.nextArg("normal");
|
|
949
949
|
const entries = getHistory();
|
|
950
950
|
// Each entry is one logical command; readline's `\s` prints them one
|
|
951
951
|
// per line, so a trailing newline per entry reproduces that layout.
|
|
952
|
-
const body = entries.map((e) => e +
|
|
952
|
+
const body = entries.map((e) => e + "\n").join("");
|
|
953
953
|
if (fname === null || fname.length === 0) {
|
|
954
954
|
writeOut(body);
|
|
955
|
-
return Promise.resolve({ status:
|
|
955
|
+
return Promise.resolve({ status: "ok" });
|
|
956
956
|
}
|
|
957
957
|
try {
|
|
958
|
-
writeFileSync(fname, body,
|
|
958
|
+
writeFileSync(fname, body, "utf8");
|
|
959
959
|
}
|
|
960
960
|
catch (err) {
|
|
961
961
|
const msg = err instanceof Error ? err.message : String(err);
|
|
962
962
|
writeErr(`\\${ctx.cmdName}: ${msg}\n`);
|
|
963
|
-
return Promise.resolve({ status:
|
|
963
|
+
return Promise.resolve({ status: "error" });
|
|
964
964
|
}
|
|
965
965
|
if (!ctx.settings.quiet) {
|
|
966
966
|
writeOut(`Wrote history to file "${fname}".\n`);
|
|
967
967
|
}
|
|
968
|
-
return Promise.resolve({ status:
|
|
968
|
+
return Promise.resolve({ status: "ok" });
|
|
969
969
|
},
|
|
970
970
|
};
|