neon 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/dist/_shared/auth_selection.js +76 -79
- package/dist/_shared/credentials.js +166 -187
- package/dist/_shared/env-core/env.js +354 -517
- package/dist/_shared/env-core/reuse-secrets.js +159 -203
- package/dist/_shared/paths.js +129 -126
- package/dist/_shared/profiles.js +192 -242
- package/dist/_shared/secure_file.js +36 -38
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +243 -303
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +614 -845
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +313 -394
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +36 -38
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +727 -983
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +1293 -2082
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +547 -1099
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +261 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +762 -1180
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1302
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +626 -815
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +1414 -1927
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +2 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +37 -44
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +7 -5
|
@@ -1,42 +1,44 @@
|
|
|
1
|
+
//#region src/psql/command/inputQueue.ts
|
|
1
2
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
3
|
+
* Pending-input queue for `\i` / `\include`.
|
|
4
|
+
*
|
|
5
|
+
* psql's `process_file()` switches the input source for the duration of an
|
|
6
|
+
* included file: while a file is being processed, every subsequent input
|
|
7
|
+
* line comes from the file, not the terminal. The natural place for that
|
|
8
|
+
* switch is the mainloop (`MainLoop` in upstream `mainloop.c`).
|
|
9
|
+
*
|
|
10
|
+
* For WP-15 we keep `src/psql/core/mainloop.ts` untouched. Instead we expose
|
|
11
|
+
* a tiny module-local queue. `\i` enqueues the contents of the included
|
|
12
|
+
* file via {@link enqueue}; a future WP modifies the mainloop's line-source
|
|
13
|
+
* to drain from {@link consumeNext} before reading more user input.
|
|
14
|
+
*
|
|
15
|
+
* Behaviour:
|
|
16
|
+
*
|
|
17
|
+
* - The queue stores file contents as raw strings (typically containing
|
|
18
|
+
* multiple newline-separated SQL statements). Order is FIFO.
|
|
19
|
+
* - {@link consumeNext} returns the head, or `null` if the queue is empty.
|
|
20
|
+
* - {@link reset} clears the queue (used by tests and by any future error
|
|
21
|
+
* recovery path that wants to abandon pending input).
|
|
22
|
+
*
|
|
23
|
+
* The queue is module-scoped because it represents the include stack of a
|
|
24
|
+
* single REPL. Tests should always call {@link reset} in their afterEach so
|
|
25
|
+
* a leftover entry doesn't contaminate the next test.
|
|
26
|
+
*/
|
|
26
27
|
const pending = [];
|
|
27
28
|
/** Append a string of input to the back of the queue. */
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
const enqueue = (content) => {
|
|
30
|
+
pending.push(content);
|
|
30
31
|
};
|
|
31
32
|
/** Return and remove the next pending input, or `null` if none. */
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return pending.shift() ?? null;
|
|
33
|
+
const consumeNext = () => {
|
|
34
|
+
if (pending.length === 0) return null;
|
|
35
|
+
return pending.shift() ?? null;
|
|
36
36
|
};
|
|
37
37
|
/** Number of items currently in the queue. */
|
|
38
|
-
|
|
38
|
+
const size = () => pending.length;
|
|
39
39
|
/** Empty the queue. Tests should call this in cleanup. */
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const reset = () => {
|
|
41
|
+
pending.length = 0;
|
|
42
42
|
};
|
|
43
|
+
//#endregion
|
|
44
|
+
export { consumeNext, enqueue, reset, size };
|
|
@@ -1,71 +1,57 @@
|
|
|
1
|
+
//#region src/psql/command/shared.ts
|
|
1
2
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
* Internal helpers shared by `cmd_meta.ts` and `cmd_format.ts`.
|
|
4
|
+
*
|
|
5
|
+
* Kept deliberately tiny: just stream writes and the boolean coercion
|
|
6
|
+
* shared by `\timing`, `\t`, `\x`, and `\pset` toggles. The implementations
|
|
7
|
+
* mirror the relevant pieces of upstream `command.c` (`ParseVariableBool`)
|
|
8
|
+
* and `print.c` / `print_aligned*` without depending on either.
|
|
9
|
+
*
|
|
10
|
+
* Why a shared file: the WP spec asks for cmd-isolated test factories, not
|
|
11
|
+
* for the command implementations themselves to duplicate one-line
|
|
12
|
+
* primitives. Going through these helpers also keeps the `noConsole`
|
|
13
|
+
* lint rule satisfied — every write touches `process.stdout` /
|
|
14
|
+
* `process.stderr` directly rather than `console.log` / `console.error`.
|
|
15
|
+
*/
|
|
15
16
|
/** Write to stdout. */
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const writeOut = (s) => {
|
|
18
|
+
process.stdout.write(s);
|
|
18
19
|
};
|
|
19
20
|
/** Write to stderr. */
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
const writeErr = (s) => {
|
|
22
|
+
process.stderr.write(s);
|
|
22
23
|
};
|
|
23
24
|
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return true;
|
|
45
|
-
if ("off".startsWith(lower))
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
if (raw === "1")
|
|
49
|
-
return true;
|
|
50
|
-
if (raw === "0")
|
|
51
|
-
return false;
|
|
52
|
-
return null;
|
|
25
|
+
* Parse a psql boolean the way `ParseVariableBool` does — case-insensitive
|
|
26
|
+
* unique-prefix match against `true|false|yes|no|on|off`, plus `1`/`0`.
|
|
27
|
+
*
|
|
28
|
+
* Returns `null` for unrecognised input.
|
|
29
|
+
*/
|
|
30
|
+
const parseBool = (raw) => {
|
|
31
|
+
if (raw.length === 0) return null;
|
|
32
|
+
const lower = raw.toLowerCase();
|
|
33
|
+
const startsWith = (target) => lower.length <= target.length && target.startsWith(lower);
|
|
34
|
+
if (startsWith("true")) return true;
|
|
35
|
+
if (startsWith("false")) return false;
|
|
36
|
+
if (startsWith("yes")) return true;
|
|
37
|
+
if (startsWith("no")) return false;
|
|
38
|
+
if (lower.length >= 2) {
|
|
39
|
+
if ("on".startsWith(lower)) return true;
|
|
40
|
+
if ("off".startsWith(lower)) return false;
|
|
41
|
+
}
|
|
42
|
+
if (raw === "1") return true;
|
|
43
|
+
if (raw === "0") return false;
|
|
44
|
+
return null;
|
|
53
45
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return "on";
|
|
64
|
-
if (b === false)
|
|
65
|
-
return "off";
|
|
66
|
-
if ("auto".startsWith(lower))
|
|
67
|
-
return "auto";
|
|
68
|
-
if ("toggle".startsWith(lower))
|
|
69
|
-
return "toggle";
|
|
70
|
-
return null;
|
|
46
|
+
const parseTriple = (raw) => {
|
|
47
|
+
const lower = raw.toLowerCase();
|
|
48
|
+
if (lower.length === 0) return null;
|
|
49
|
+
const b = parseBool(raw);
|
|
50
|
+
if (b === true) return "on";
|
|
51
|
+
if (b === false) return "off";
|
|
52
|
+
if ("auto".startsWith(lower)) return "auto";
|
|
53
|
+
if ("toggle".startsWith(lower)) return "toggle";
|
|
54
|
+
return null;
|
|
71
55
|
};
|
|
56
|
+
//#endregion
|
|
57
|
+
export { parseBool, parseTriple, writeErr, writeOut };
|
|
@@ -1,139 +1,96 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Filesystem-driven completion candidates for `\lo_*`, `\copy ... FROM/TO`,
|
|
3
|
-
* and SQL `COPY ... FROM/TO`.
|
|
4
|
-
*
|
|
5
|
-
* Upstream psql implements this via readline's `rl_filename_completion_function`
|
|
6
|
-
* plus a couple of custom `complete_from_files*` wrappers. We don't have
|
|
7
|
-
* readline; we re-implement the bits we need with `fs.readdirSync`.
|
|
8
|
-
*
|
|
9
|
-
* The completer enumerates entries in the directory referenced by the
|
|
10
|
-
* partial input, filters by basename prefix, and returns *full* candidates
|
|
11
|
-
* (path + basename, matching what the user typed). Directories get a
|
|
12
|
-
* trailing `/` so the editor's `shouldAppendSpace` keeps the user typing
|
|
13
|
-
* through them.
|
|
14
|
-
*
|
|
15
|
-
* For the SQL `COPY ... FROM/TO` context — where the filename must be a
|
|
16
|
-
* string literal — the candidates are wrapped in single quotes. Closing
|
|
17
|
-
* quotes are added only when the candidate is a final filename (unique
|
|
18
|
-
* match), so the line editor's "balanced quotes → append space" rule
|
|
19
|
-
* fires; partial multi-candidate prefixes leave the closing quote off so
|
|
20
|
-
* the user can keep typing.
|
|
21
|
-
*/
|
|
22
1
|
import { readdirSync, statSync } from "node:fs";
|
|
23
2
|
import { basename, dirname, join } from "node:path";
|
|
3
|
+
//#region src/psql/complete/filenames.ts
|
|
4
|
+
/**
|
|
5
|
+
* Filesystem-driven completion candidates for `\lo_*`, `\copy ... FROM/TO`,
|
|
6
|
+
* and SQL `COPY ... FROM/TO`.
|
|
7
|
+
*
|
|
8
|
+
* Upstream psql implements this via readline's `rl_filename_completion_function`
|
|
9
|
+
* plus a couple of custom `complete_from_files*` wrappers. We don't have
|
|
10
|
+
* readline; we re-implement the bits we need with `fs.readdirSync`.
|
|
11
|
+
*
|
|
12
|
+
* The completer enumerates entries in the directory referenced by the
|
|
13
|
+
* partial input, filters by basename prefix, and returns *full* candidates
|
|
14
|
+
* (path + basename, matching what the user typed). Directories get a
|
|
15
|
+
* trailing `/` so the editor's `shouldAppendSpace` keeps the user typing
|
|
16
|
+
* through them.
|
|
17
|
+
*
|
|
18
|
+
* For the SQL `COPY ... FROM/TO` context — where the filename must be a
|
|
19
|
+
* string literal — the candidates are wrapped in single quotes. Closing
|
|
20
|
+
* quotes are added only when the candidate is a final filename (unique
|
|
21
|
+
* match), so the line editor's "balanced quotes → append space" rule
|
|
22
|
+
* fires; partial multi-candidate prefixes leave the closing quote off so
|
|
23
|
+
* the user can keep typing.
|
|
24
|
+
*/
|
|
24
25
|
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
filtered.sort();
|
|
72
|
-
// Build the candidates. Each is `dirPart + entry` (full path matching
|
|
73
|
-
// what the user typed) plus optional trailing `/` for directories.
|
|
74
|
-
const candidates = [];
|
|
75
|
-
for (const entry of filtered) {
|
|
76
|
-
let isDir = false;
|
|
77
|
-
try {
|
|
78
|
-
isDir = statSync(join(scanDir, entry)).isDirectory();
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
// Broken symlink etc. — treat as regular file.
|
|
82
|
-
}
|
|
83
|
-
const full = dirPart + entry + (isDir ? "/" : "");
|
|
84
|
-
candidates.push(full);
|
|
85
|
-
}
|
|
86
|
-
if (quoteCtx === "none") {
|
|
87
|
-
// Bare paths. Preserve any opening single quote the user already typed
|
|
88
|
-
// (rare for the no-quote contexts, but harmless to mirror).
|
|
89
|
-
if (hadOpeningSingleQuote) {
|
|
90
|
-
return candidates.map((c) => "'" + c);
|
|
91
|
-
}
|
|
92
|
-
return candidates;
|
|
93
|
-
}
|
|
94
|
-
// SQL string-literal context: wrap candidates in single quotes. The
|
|
95
|
-
// line editor's `shouldAppendSpace` checks quote balance — unique
|
|
96
|
-
// candidates close the quote (so `'...'` balances → trailing space
|
|
97
|
-
// fires), multi-candidate common prefixes leave the closing quote off.
|
|
98
|
-
if (candidates.length === 1 && !candidates[0].endsWith("/")) {
|
|
99
|
-
// Unique file (not directory): close the quote so the trailing space
|
|
100
|
-
// fires. Opening quote: re-add if user typed it, else add ourselves.
|
|
101
|
-
return ["'" + candidates[0] + "'"];
|
|
102
|
-
}
|
|
103
|
-
// Multiple candidates OR directory match: opening quote only; closing
|
|
104
|
-
// quote is deferred so the user keeps typing.
|
|
105
|
-
return candidates.map((c) => "'" + c);
|
|
26
|
+
* Enumerate filesystem entries matching the partial path the user typed.
|
|
27
|
+
*
|
|
28
|
+
* `currentWord` is the raw token (post-tokenizer). It may contain a leading
|
|
29
|
+
* `'` (when the user is already inside a single-quoted SQL literal). The
|
|
30
|
+
* function strips that, looks up the dirname/basename, and returns full
|
|
31
|
+
* candidates that the line editor can splice in with `replaceLength = code
|
|
32
|
+
* points in currentWord`.
|
|
33
|
+
*
|
|
34
|
+
* Returns an empty array on any filesystem error (e.g. directory doesn't
|
|
35
|
+
* exist, no read permission). Tab completion is best-effort — failing to
|
|
36
|
+
* complete is the same as "no candidates".
|
|
37
|
+
*/
|
|
38
|
+
const completeFilenames = (currentWord, quoteCtx, cwd = process.cwd()) => {
|
|
39
|
+
let raw = currentWord;
|
|
40
|
+
let hadOpeningSingleQuote = false;
|
|
41
|
+
if (raw.startsWith("'")) {
|
|
42
|
+
hadOpeningSingleQuote = true;
|
|
43
|
+
raw = raw.slice(1);
|
|
44
|
+
}
|
|
45
|
+
const lastSlash = raw.lastIndexOf("/");
|
|
46
|
+
const dirPart = lastSlash === -1 ? "" : raw.slice(0, lastSlash + 1);
|
|
47
|
+
const basePrefix = lastSlash === -1 ? raw : raw.slice(lastSlash + 1);
|
|
48
|
+
const scanDir = dirPart === "" ? cwd : dirPart.startsWith("/") ? dirPart : join(cwd, dirPart);
|
|
49
|
+
let entries;
|
|
50
|
+
try {
|
|
51
|
+
entries = readdirSync(scanDir);
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const filtered = entries.filter((e) => e.startsWith(basePrefix));
|
|
56
|
+
filtered.sort();
|
|
57
|
+
const candidates = [];
|
|
58
|
+
for (const entry of filtered) {
|
|
59
|
+
let isDir = false;
|
|
60
|
+
try {
|
|
61
|
+
isDir = statSync(join(scanDir, entry)).isDirectory();
|
|
62
|
+
} catch {}
|
|
63
|
+
const full = dirPart + entry + (isDir ? "/" : "");
|
|
64
|
+
candidates.push(full);
|
|
65
|
+
}
|
|
66
|
+
if (quoteCtx === "none") {
|
|
67
|
+
if (hadOpeningSingleQuote) return candidates.map((c) => "'" + c);
|
|
68
|
+
return candidates;
|
|
69
|
+
}
|
|
70
|
+
if (candidates.length === 1 && !candidates[0].endsWith("/")) return ["'" + candidates[0] + "'"];
|
|
71
|
+
return candidates.map((c) => "'" + c);
|
|
106
72
|
};
|
|
107
73
|
/**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (prevWords[i].toUpperCase() === "SELECT" ||
|
|
129
|
-
prevWords[i].toUpperCase() === "INSERT" ||
|
|
130
|
-
prevWords[i].toUpperCase() === "UPDATE" ||
|
|
131
|
-
prevWords[i].toUpperCase() === "DELETE" ||
|
|
132
|
-
prevWords[i].toUpperCase() === "WITH") {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return false;
|
|
74
|
+
* Helper used by `rules.ts` to decide whether the SQL `COPY` we're
|
|
75
|
+
* completing for is a `FROM` (input file) or `TO` (output file). Returns
|
|
76
|
+
* `true` for either — both want filename completion.
|
|
77
|
+
*
|
|
78
|
+
* `prevWords` here is the full prev-words token list. We look for the
|
|
79
|
+
* pattern `COPY <table>+ [FROM|TO]` anywhere as a tail match.
|
|
80
|
+
*/
|
|
81
|
+
const isCopyFromOrTo = (prevWords) => {
|
|
82
|
+
if (prevWords.length < 3) return false;
|
|
83
|
+
const last = prevWords[prevWords.length - 1].toUpperCase();
|
|
84
|
+
if (last !== "FROM" && last !== "TO") return false;
|
|
85
|
+
for (let i = prevWords.length - 2; i >= 0; i--) {
|
|
86
|
+
if (prevWords[i].toUpperCase() === "COPY") return true;
|
|
87
|
+
if (prevWords[i].toUpperCase() === "SELECT" || prevWords[i].toUpperCase() === "INSERT" || prevWords[i].toUpperCase() === "UPDATE" || prevWords[i].toUpperCase() === "DELETE" || prevWords[i].toUpperCase() === "WITH") return false;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
};
|
|
91
|
+
const _internals = {
|
|
92
|
+
basename,
|
|
93
|
+
dirname
|
|
137
94
|
};
|
|
138
|
-
|
|
139
|
-
export
|
|
95
|
+
//#endregion
|
|
96
|
+
export { _internals, completeFilenames, isCopyFromOrTo };
|
|
@@ -1,104 +1,66 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public entrypoint for psql tab completion.
|
|
3
|
-
*
|
|
4
|
-
* The `LineEditor` (WP-24) accepts a `Completer` of shape
|
|
5
|
-
* `(input, cursor) => Promise<CompletionResult>`. We return a CURRIED
|
|
6
|
-
* factory `psqlCompleter(ctx)` that captures the settings reference so the
|
|
7
|
-
* completer can read `ctx.settings.db` lazily on every call — important
|
|
8
|
-
* because `\c` swaps out the live connection.
|
|
9
|
-
*
|
|
10
|
-
* `CompletionResult` (defined in lineEditor/complete.ts):
|
|
11
|
-
*
|
|
12
|
-
* - candidates: string[] the list shown / inserted
|
|
13
|
-
* - commonPrefix: string longest prefix the editor can insert without
|
|
14
|
-
* making a choice (`apply` walks this)
|
|
15
|
-
* - replaceLength: number code-points to chop off the buffer before
|
|
16
|
-
* inserting `candidates[i]` / commonPrefix
|
|
17
|
-
*
|
|
18
|
-
* Our `findCompletions` returns raw candidate strings; we compute the common
|
|
19
|
-
* prefix here. `replaceLength` is the code-point length of the partial word
|
|
20
|
-
* the cursor was sitting on (`currentWord`), which we already compute in
|
|
21
|
-
* `splitForCompletion` during tokenization.
|
|
22
|
-
*/
|
|
23
1
|
import { splitForCompletion } from "./matcher.js";
|
|
24
2
|
import { findCompletions } from "./rules.js";
|
|
3
|
+
//#region src/psql/complete/index.ts
|
|
25
4
|
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const commonPrefix = longestCommonPrefix(deduped, currentWord);
|
|
59
|
-
return {
|
|
60
|
-
candidates: deduped,
|
|
61
|
-
commonPrefix,
|
|
62
|
-
replaceLength,
|
|
63
|
-
};
|
|
64
|
-
};
|
|
65
|
-
return completer;
|
|
5
|
+
* Build a completer bound to the given settings. The settings reference is
|
|
6
|
+
* captured by closure; we never snapshot, so changes to `settings.db` (via
|
|
7
|
+
* `\c`) take effect immediately.
|
|
8
|
+
*/
|
|
9
|
+
const psqlCompleter = (ctx) => {
|
|
10
|
+
const completer = async (input, cursor) => {
|
|
11
|
+
const { prevWords, currentWord, replaceLength } = splitForCompletion(input, cursor);
|
|
12
|
+
const ruleCtx = {
|
|
13
|
+
settings: ctx.settings,
|
|
14
|
+
queryBuf: ctx.getQueryBuf?.() ?? ""
|
|
15
|
+
};
|
|
16
|
+
const { candidates } = await findCompletions(prevWords, currentWord, ruleCtx);
|
|
17
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18
|
+
const deduped = [];
|
|
19
|
+
for (const c of candidates) if (!seen.has(c)) {
|
|
20
|
+
seen.add(c);
|
|
21
|
+
deduped.push(c);
|
|
22
|
+
}
|
|
23
|
+
deduped.sort((a, b) => {
|
|
24
|
+
const la = a.toLowerCase();
|
|
25
|
+
const lb = b.toLowerCase();
|
|
26
|
+
if (la < lb) return -1;
|
|
27
|
+
if (la > lb) return 1;
|
|
28
|
+
return 0;
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
candidates: deduped,
|
|
32
|
+
commonPrefix: longestCommonPrefix(deduped, currentWord),
|
|
33
|
+
replaceLength
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
return completer;
|
|
66
37
|
};
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
// Helpers.
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
38
|
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
39
|
+
* Return the longest common prefix of all candidates, but never shorter
|
|
40
|
+
* than the user's partial input (so we don't backtrack the buffer when the
|
|
41
|
+
* completion is a no-op).
|
|
42
|
+
*
|
|
43
|
+
* If there's a single candidate, returns it whole.
|
|
44
|
+
*/
|
|
77
45
|
const longestCommonPrefix = (candidates, fallback) => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
break;
|
|
97
|
-
out.push(first);
|
|
98
|
-
}
|
|
99
|
-
// Common prefix should not be shorter than what's already typed.
|
|
100
|
-
const candidatePrefix = out.join("");
|
|
101
|
-
if (candidatePrefix.length >= fallback.length)
|
|
102
|
-
return candidatePrefix;
|
|
103
|
-
return fallback;
|
|
46
|
+
if (candidates.length === 0) return fallback;
|
|
47
|
+
if (candidates.length === 1) return candidates[0];
|
|
48
|
+
const arrs = candidates.map((c) => Array.from(c));
|
|
49
|
+
const minLen = arrs.reduce((m, a) => Math.min(m, a.length), Infinity);
|
|
50
|
+
const out = [];
|
|
51
|
+
for (let i = 0; i < minLen; i++) {
|
|
52
|
+
const first = arrs[0][i];
|
|
53
|
+
let ok = true;
|
|
54
|
+
for (let j = 1; j < arrs.length; j++) if (arrs[j][i] !== first) {
|
|
55
|
+
ok = false;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
if (!ok) break;
|
|
59
|
+
out.push(first);
|
|
60
|
+
}
|
|
61
|
+
const candidatePrefix = out.join("");
|
|
62
|
+
if (candidatePrefix.length >= fallback.length) return candidatePrefix;
|
|
63
|
+
return fallback;
|
|
104
64
|
};
|
|
65
|
+
//#endregion
|
|
66
|
+
export { psqlCompleter };
|