neonctl 2.22.0 → 2.23.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 +242 -16
- package/analytics.js +5 -2
- package/commands/branches.js +9 -1
- package/commands/checkout.js +249 -0
- package/commands/connection_string.js +15 -2
- package/commands/data_api.js +286 -0
- package/commands/functions.js +277 -0
- package/commands/index.js +12 -0
- package/commands/link.js +667 -0
- package/commands/neon_auth.js +1013 -0
- package/commands/projects.js +9 -1
- package/commands/psql.js +62 -0
- package/commands/set_context.js +7 -2
- package/context.js +86 -14
- package/functions_api.js +44 -0
- package/index.js +3 -0
- package/package.json +60 -51
- package/psql/cli.js +51 -0
- package/psql/command/cmd_cond.js +437 -0
- package/psql/command/cmd_connect.js +815 -0
- package/psql/command/cmd_copy.js +1025 -0
- package/psql/command/cmd_describe.js +1810 -0
- package/psql/command/cmd_format.js +909 -0
- package/psql/command/cmd_io.js +2187 -0
- package/psql/command/cmd_lo.js +385 -0
- package/psql/command/cmd_meta.js +970 -0
- package/psql/command/cmd_misc.js +187 -0
- package/psql/command/cmd_pipeline.js +1141 -0
- package/psql/command/cmd_restrict.js +171 -0
- package/psql/command/cmd_show.js +751 -0
- package/psql/command/dispatch.js +343 -0
- package/psql/command/inputQueue.js +42 -0
- package/psql/command/shared.js +71 -0
- package/psql/complete/filenames.js +139 -0
- package/psql/complete/index.js +104 -0
- package/psql/complete/matcher.js +314 -0
- package/psql/complete/psqlVars.js +247 -0
- package/psql/complete/queries.js +491 -0
- package/psql/complete/rules.js +2387 -0
- package/psql/core/common.js +1250 -0
- package/psql/core/help.js +576 -0
- package/psql/core/mainloop.js +1353 -0
- package/psql/core/prompt.js +437 -0
- package/psql/core/settings.js +684 -0
- package/psql/core/sqlHelp.js +1066 -0
- package/psql/core/startup.js +840 -0
- package/psql/core/syncVars.js +116 -0
- package/psql/core/variables.js +287 -0
- package/psql/describe/formatters.js +1277 -0
- package/psql/describe/processNamePattern.js +270 -0
- package/psql/describe/queries.js +2373 -0
- package/psql/describe/versionGate.js +43 -0
- package/psql/index.js +2005 -0
- package/psql/io/history.js +299 -0
- package/psql/io/input.js +120 -0
- package/psql/io/lineEditor/buffer.js +323 -0
- package/psql/io/lineEditor/complete.js +227 -0
- package/psql/io/lineEditor/filename.js +159 -0
- package/psql/io/lineEditor/index.js +891 -0
- package/psql/io/lineEditor/keymap.js +738 -0
- package/psql/io/lineEditor/vt100.js +363 -0
- package/psql/io/pgpass.js +202 -0
- package/psql/io/pgservice.js +194 -0
- package/psql/io/psqlrc.js +422 -0
- package/psql/print/aligned.js +1756 -0
- package/psql/print/asciidoc.js +248 -0
- package/psql/print/crosstab.js +460 -0
- package/psql/print/csv.js +92 -0
- package/psql/print/html.js +258 -0
- package/psql/print/json.js +96 -0
- package/psql/print/latex.js +396 -0
- package/psql/print/pager.js +265 -0
- package/psql/print/troff.js +258 -0
- package/psql/print/unaligned.js +118 -0
- package/psql/print/units.js +135 -0
- package/psql/scanner/slash.js +513 -0
- package/psql/scanner/sql.js +910 -0
- package/psql/scanner/stringutils.js +390 -0
- package/psql/types/backslash.js +1 -0
- package/psql/types/connection.js +1 -0
- package/psql/types/index.js +7 -0
- package/psql/types/printer.js +1 -0
- package/psql/types/repl.js +1 -0
- package/psql/types/scanner.js +24 -0
- package/psql/types/settings.js +1 -0
- package/psql/types/variables.js +1 -0
- package/psql/wire/connection.js +2844 -0
- package/psql/wire/copy.js +108 -0
- package/psql/wire/notify.js +59 -0
- package/psql/wire/pipeline.js +519 -0
- package/psql/wire/protocol.js +466 -0
- package/psql/wire/sasl.js +296 -0
- package/psql/wire/tls.js +596 -0
- package/test_utils/fixtures.js +1 -0
- package/utils/enrichers.js +18 -1
- package/utils/esbuild.js +147 -0
- package/utils/middlewares.js +1 -1
- package/utils/psql.js +107 -11
- package/utils/zip.js +4 -0
- package/writer.js +1 -1
- package/commands/auth.test.js +0 -211
- package/commands/branches.test.js +0 -460
- package/commands/connection_string.test.js +0 -196
- package/commands/databases.test.js +0 -39
- package/commands/help.test.js +0 -9
- package/commands/init.test.js +0 -56
- package/commands/ip_allow.test.js +0 -59
- package/commands/operations.test.js +0 -7
- package/commands/orgs.test.js +0 -7
- package/commands/projects.test.js +0 -144
- package/commands/roles.test.js +0 -37
- package/commands/set_context.test.js +0 -159
- package/commands/vpc_endpoints.test.js +0 -69
- package/env.test.js +0 -55
- package/utils/formats.test.js +0 -32
- package/writer.test.js +0 -104
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `\restrict` and `\unrestrict` — PG 18 introduced these to lock the
|
|
3
|
+
* psql REPL into a "restricted" sub-shell where shell-side or
|
|
4
|
+
* filesystem-side backslash commands are refused.
|
|
5
|
+
*
|
|
6
|
+
* Implementation notes (deviations from upstream by design):
|
|
7
|
+
*
|
|
8
|
+
* - Upstream PG 18 blocks **every** backslash command except
|
|
9
|
+
* `\unrestrict` while restricted. The brief for this work package
|
|
10
|
+
* instructs a narrower policy: block only the commands that can
|
|
11
|
+
* spawn a child process, write to the local filesystem, or mutate
|
|
12
|
+
* the surrounding environment (`\!`, `\cd`, `\copy`, `\setenv`,
|
|
13
|
+
* `\write` / `\w`). Plain SQL and read-only meta commands continue
|
|
14
|
+
* to run. The trade-off: easier for scripted use, slightly looser
|
|
15
|
+
* than upstream. Documented here and surfaced to the user via the
|
|
16
|
+
* error message.
|
|
17
|
+
*
|
|
18
|
+
* - The restriction state lives in `settings.restrictedKey`, NOT in the
|
|
19
|
+
* user-writable `vars` store. An earlier design kept it in a psql
|
|
20
|
+
* variable named `RESTRICTED`, which let `\set RESTRICTED ''` (or
|
|
21
|
+
* `\unset` / `\getenv` / `\gset` of that name) silently leave restricted
|
|
22
|
+
* mode without knowing the `\restrict` key — fully defeating the control
|
|
23
|
+
* (review item #12). Only `\restrict` / `\unrestrict` touch the field.
|
|
24
|
+
*
|
|
25
|
+
* - `\copy` is restricted in full, not only `\copy ... FROM PROGRAM`.
|
|
26
|
+
* The brief explicitly asks for registry-level interception
|
|
27
|
+
* (avoid touching every cmd_* file) — that constrains us to whole
|
|
28
|
+
* commands by name. Documented as a deliberate over-restriction.
|
|
29
|
+
*
|
|
30
|
+
* - Name matching: `\unrestrict NAME` requires the same NAME that
|
|
31
|
+
* `\restrict NAME` provided. A mismatch keeps the session in
|
|
32
|
+
* restricted mode and returns an error.
|
|
33
|
+
*/
|
|
34
|
+
import { writeErr } from './shared.js';
|
|
35
|
+
/** Standard refusal message emitted when a gated command is invoked while
|
|
36
|
+
* the session is in restricted mode. Public so callers and tests can match
|
|
37
|
+
* against the exact string. */
|
|
38
|
+
export const RESTRICTED_REFUSAL_MESSAGE = (cmdName) => `\\${cmdName}: command is not allowed in restricted mode; ` +
|
|
39
|
+
`use \\unrestrict to leave restricted mode\n`;
|
|
40
|
+
/**
|
|
41
|
+
* Backslash command names blocked while restricted. Lookup is by
|
|
42
|
+
* **primary** name — aliases (e.g. `write` → `w`, `quit` → `q`)
|
|
43
|
+
* resolve to the primary spec before the gate runs, so the gate sees
|
|
44
|
+
* the canonical name.
|
|
45
|
+
*
|
|
46
|
+
* Trade-off (per the WP brief): block `\copy` entirely rather than
|
|
47
|
+
* inspect the args for `PROGRAM`. Aligns with "intercept at the
|
|
48
|
+
* registry level" and avoids touching cmd_copy.ts internals.
|
|
49
|
+
*/
|
|
50
|
+
export const RESTRICTED_COMMANDS = new Set([
|
|
51
|
+
'!',
|
|
52
|
+
'cd',
|
|
53
|
+
'copy',
|
|
54
|
+
'setenv',
|
|
55
|
+
'w',
|
|
56
|
+
// `\o`/`\g`/`\gx` route through openWriter(), which spawns `sh -c <cmd>`
|
|
57
|
+
// for a `|command` target (arbitrary shell exec) and writes the filesystem
|
|
58
|
+
// for a FILE target; `\s FILE` writes history to disk. Block them by name
|
|
59
|
+
// (review item #13). Plain query execution still works via `;`.
|
|
60
|
+
'o',
|
|
61
|
+
'g',
|
|
62
|
+
'gx',
|
|
63
|
+
's', // \s [FILE] — write command history
|
|
64
|
+
]);
|
|
65
|
+
/**
|
|
66
|
+
* True iff the session is currently in restricted mode. Reads the
|
|
67
|
+
* protected {@link PsqlSettings.restrictedKey} field (NOT a psql var, so
|
|
68
|
+
* `\set`/`\unset`/`\getenv`/`\gset` cannot flip it — review item #12).
|
|
69
|
+
*/
|
|
70
|
+
export const isRestricted = (settings) => settings.restrictedKey !== null;
|
|
71
|
+
/**
|
|
72
|
+
* Return the active restriction name (the value supplied to
|
|
73
|
+
* `\restrict NAME`), or `null` if not restricted.
|
|
74
|
+
*/
|
|
75
|
+
export const restrictedName = (settings) => settings.restrictedKey;
|
|
76
|
+
/**
|
|
77
|
+
* Predicate used by the dispatcher: should the given primary command
|
|
78
|
+
* name be refused right now?
|
|
79
|
+
*/
|
|
80
|
+
export const isCommandRestricted = (settings, primaryName) => isRestricted(settings) && RESTRICTED_COMMANDS.has(primaryName);
|
|
81
|
+
/**
|
|
82
|
+
* `\restrict NAME` — enter restricted mode. `NAME` (any non-empty
|
|
83
|
+
* string) becomes the key that `\unrestrict` must match to leave.
|
|
84
|
+
*
|
|
85
|
+
* If already restricted, this is an error — upstream treats this
|
|
86
|
+
* with an Assert; we surface it as a regular command error.
|
|
87
|
+
*/
|
|
88
|
+
export const cmdRestrict = {
|
|
89
|
+
name: 'restrict',
|
|
90
|
+
helpKey: 'restrict',
|
|
91
|
+
run: (ctx) => {
|
|
92
|
+
const arg = ctx.nextArg('normal');
|
|
93
|
+
if (arg === null || arg.length === 0) {
|
|
94
|
+
writeErr(`\\${ctx.cmdName}: missing required argument\n`);
|
|
95
|
+
return Promise.resolve({ status: 'error' });
|
|
96
|
+
}
|
|
97
|
+
if (isRestricted(ctx.settings)) {
|
|
98
|
+
writeErr(`\\${ctx.cmdName}: already in restricted mode\n`);
|
|
99
|
+
return Promise.resolve({ status: 'error' });
|
|
100
|
+
}
|
|
101
|
+
ctx.settings.restrictedKey = arg;
|
|
102
|
+
return Promise.resolve({ status: 'ok' });
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* `\unrestrict NAME` — leave restricted mode if NAME matches the key
|
|
107
|
+
* given to the original `\restrict`.
|
|
108
|
+
*/
|
|
109
|
+
export const cmdUnrestrict = {
|
|
110
|
+
name: 'unrestrict',
|
|
111
|
+
helpKey: 'unrestrict',
|
|
112
|
+
run: (ctx) => {
|
|
113
|
+
const arg = ctx.nextArg('normal');
|
|
114
|
+
if (arg === null || arg.length === 0) {
|
|
115
|
+
writeErr(`\\${ctx.cmdName}: missing required argument\n`);
|
|
116
|
+
return Promise.resolve({ status: 'error' });
|
|
117
|
+
}
|
|
118
|
+
const current = restrictedName(ctx.settings);
|
|
119
|
+
if (current === null) {
|
|
120
|
+
writeErr(`\\${ctx.cmdName}: not currently in restricted mode\n`);
|
|
121
|
+
return Promise.resolve({ status: 'error' });
|
|
122
|
+
}
|
|
123
|
+
if (arg !== current) {
|
|
124
|
+
writeErr(`\\${ctx.cmdName}: wrong key\n`);
|
|
125
|
+
return Promise.resolve({ status: 'error' });
|
|
126
|
+
}
|
|
127
|
+
ctx.settings.restrictedKey = null;
|
|
128
|
+
return Promise.resolve({ status: 'ok' });
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Convenience registration helper, used by `defaultRegistry()` in
|
|
133
|
+
* `dispatch.ts`. Kept in this file so the dispatch.ts addition is a
|
|
134
|
+
* single line.
|
|
135
|
+
*/
|
|
136
|
+
export const registerRestrictCommands = (registry) => {
|
|
137
|
+
registry.register(cmdRestrict);
|
|
138
|
+
registry.register(cmdUnrestrict);
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Wrap every already-registered spec whose primary name is in
|
|
142
|
+
* {@link RESTRICTED_COMMANDS} so its `run` gates on the live restriction
|
|
143
|
+
* state. Without this the gate in `dispatch.ts::dispatchBackslash` only
|
|
144
|
+
* fires for the `psqlrc` path; the REPL mainloop currently invokes
|
|
145
|
+
* `spec.run` directly and would bypass the check.
|
|
146
|
+
*
|
|
147
|
+
* Idempotent: a wrapped spec carries a `[WRAPPED_FLAG]` marker so a second
|
|
148
|
+
* call is a no-op. Must be called *after* all restricted commands have
|
|
149
|
+
* been registered (so we see them via `registry.lookup`).
|
|
150
|
+
*/
|
|
151
|
+
const WRAPPED_FLAG = Symbol.for('neonctl.psql.restrictWrapped');
|
|
152
|
+
export const wrapRestrictedCommands = (registry) => {
|
|
153
|
+
for (const name of RESTRICTED_COMMANDS) {
|
|
154
|
+
const spec = registry.lookup(name);
|
|
155
|
+
if (!spec || spec[WRAPPED_FLAG])
|
|
156
|
+
continue;
|
|
157
|
+
const originalRun = spec.run.bind(spec);
|
|
158
|
+
const gated = {
|
|
159
|
+
...spec,
|
|
160
|
+
run: (ctx) => {
|
|
161
|
+
if (isRestricted(ctx.settings)) {
|
|
162
|
+
writeErr(RESTRICTED_REFUSAL_MESSAGE(ctx.cmdName));
|
|
163
|
+
return Promise.resolve({ status: 'error' });
|
|
164
|
+
}
|
|
165
|
+
return originalRun(ctx);
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
gated[WRAPPED_FLAG] = true;
|
|
169
|
+
registry.register(gated);
|
|
170
|
+
}
|
|
171
|
+
};
|