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
package/commands/projects.js
CHANGED
|
@@ -77,6 +77,12 @@ export const builder = (argv) => {
|
|
|
77
77
|
describe: 'Connect to a new project via psql',
|
|
78
78
|
default: false,
|
|
79
79
|
},
|
|
80
|
+
fallback: {
|
|
81
|
+
type: 'boolean',
|
|
82
|
+
describe: 'Force the embedded TypeScript psql fallback (for testing)',
|
|
83
|
+
default: false,
|
|
84
|
+
hidden: true,
|
|
85
|
+
},
|
|
80
86
|
database: {
|
|
81
87
|
describe: projectCreateRequest['project.branch.database_name'].description,
|
|
82
88
|
type: 'string',
|
|
@@ -242,7 +248,9 @@ const create = async (props) => {
|
|
|
242
248
|
if (props.psql) {
|
|
243
249
|
const connection_uri = data.connection_uris[0].connection_uri;
|
|
244
250
|
const psqlArgs = props['--'];
|
|
245
|
-
await psql(connection_uri, psqlArgs
|
|
251
|
+
await psql(connection_uri, psqlArgs, {
|
|
252
|
+
mode: props.fallback ? 'ts' : 'auto',
|
|
253
|
+
});
|
|
246
254
|
}
|
|
247
255
|
};
|
|
248
256
|
const deleteProject = async (props) => {
|
package/commands/psql.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { EndpointType } from '@neondatabase/api-client';
|
|
2
|
+
import { fillSingleProject } from '../utils/enrichers.js';
|
|
3
|
+
import { handler as connectionStringHandler, SSL_MODES, } from './connection_string.js';
|
|
4
|
+
export const command = 'psql [branch]';
|
|
5
|
+
export const describe = 'Connect to a database via psql';
|
|
6
|
+
export const builder = (argv) => {
|
|
7
|
+
return argv
|
|
8
|
+
.usage('$0 psql [branch] [options] [-- psql-args]')
|
|
9
|
+
.example('$0 psql', 'Connect to the default branch via psql')
|
|
10
|
+
.example('$0 psql main', 'Connect to the main branch via psql')
|
|
11
|
+
.example('$0 psql main -- -c "SELECT 1"', 'Run a single query against the main branch')
|
|
12
|
+
.example('$0 psql main@2024-01-01T00:00:00Z', 'Connect to the main branch at a specific point in time')
|
|
13
|
+
.positional('branch', {
|
|
14
|
+
describe: `Branch name or id. Defaults to the default branch if omitted. Can be written in the point-in-time format: "branch@timestamp" or "branch@lsn"`,
|
|
15
|
+
type: 'string',
|
|
16
|
+
})
|
|
17
|
+
.options({
|
|
18
|
+
'project-id': {
|
|
19
|
+
type: 'string',
|
|
20
|
+
describe: 'Project ID',
|
|
21
|
+
},
|
|
22
|
+
'role-name': {
|
|
23
|
+
type: 'string',
|
|
24
|
+
describe: 'Role name',
|
|
25
|
+
},
|
|
26
|
+
'database-name': {
|
|
27
|
+
type: 'string',
|
|
28
|
+
describe: 'Database name',
|
|
29
|
+
},
|
|
30
|
+
pooled: {
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
describe: 'Use pooled connection',
|
|
33
|
+
default: false,
|
|
34
|
+
},
|
|
35
|
+
'endpoint-type': {
|
|
36
|
+
type: 'string',
|
|
37
|
+
choices: Object.values(EndpointType),
|
|
38
|
+
describe: 'Endpoint type',
|
|
39
|
+
},
|
|
40
|
+
ssl: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
choices: SSL_MODES,
|
|
43
|
+
default: 'require',
|
|
44
|
+
describe: 'SSL mode',
|
|
45
|
+
},
|
|
46
|
+
fallback: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
describe: 'Force the embedded TypeScript psql fallback (for testing)',
|
|
49
|
+
default: false,
|
|
50
|
+
hidden: true,
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
.middleware(fillSingleProject);
|
|
54
|
+
};
|
|
55
|
+
export const handler = async (props) => {
|
|
56
|
+
await connectionStringHandler({
|
|
57
|
+
...props,
|
|
58
|
+
psql: true,
|
|
59
|
+
prisma: false,
|
|
60
|
+
extended: false,
|
|
61
|
+
});
|
|
62
|
+
};
|
package/commands/set_context.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { applyContext } from '../context.js';
|
|
2
2
|
export const command = 'set-context';
|
|
3
3
|
export const describe = 'Set the current context';
|
|
4
4
|
export const builder = (argv) => argv.usage('$0 set-context [options]').options({
|
|
@@ -10,11 +10,16 @@ export const builder = (argv) => argv.usage('$0 set-context [options]').options(
|
|
|
10
10
|
describe: 'Organization ID',
|
|
11
11
|
type: 'string',
|
|
12
12
|
},
|
|
13
|
+
'branch-id': {
|
|
14
|
+
describe: 'Branch ID',
|
|
15
|
+
type: 'string',
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
export const handler = (props) => {
|
|
15
19
|
const context = {
|
|
16
20
|
projectId: props.projectId,
|
|
17
21
|
orgId: props.orgId,
|
|
22
|
+
branchId: props.branchId,
|
|
18
23
|
};
|
|
19
|
-
|
|
24
|
+
applyContext(props.contextFile, context);
|
|
20
25
|
};
|
package/context.js
CHANGED
|
@@ -1,23 +1,39 @@
|
|
|
1
|
-
import { accessSync, readFileSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { accessSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { normalize, resolve } from 'node:path';
|
|
3
|
+
import { dirname, normalize, resolve } from 'node:path';
|
|
4
|
+
import { log } from './log.js';
|
|
4
5
|
const CONTEXT_FILE = '.neon';
|
|
5
|
-
const
|
|
6
|
+
const GITIGNORE_FILE = '.gitignore';
|
|
6
7
|
const wrapWithContextFile = (dir) => resolve(dir, CONTEXT_FILE);
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the default `.neon` path for the current working directory.
|
|
10
|
+
*
|
|
11
|
+
* Walks UP from `cwd` looking ONLY for an already-existing `.neon` file so
|
|
12
|
+
* commands run from a sub-directory of a linked project still pick up the
|
|
13
|
+
* project's context. If no `.neon` is found, the path defaults to
|
|
14
|
+
* `<cwd>/.neon`, which makes `neonctl link` and `neonctl set-context`
|
|
15
|
+
* predictable: they always write the context file into the directory they
|
|
16
|
+
* were invoked from.
|
|
17
|
+
*
|
|
18
|
+
* Historically the walk also considered `package.json` and `.git` as project
|
|
19
|
+
* markers, but that led to surprising behaviour when running `link` from a
|
|
20
|
+
* fresh sub-directory inside an unrelated repo (the new link would land in
|
|
21
|
+
* the parent repo's root instead of the cwd).
|
|
22
|
+
*
|
|
23
|
+
* `cwd` is overridable so tests can exercise the walk-up without mutating
|
|
24
|
+
* `process.cwd()` (which would race with other tests running in parallel).
|
|
25
|
+
*/
|
|
26
|
+
export const currentContextFile = (cwd = process.cwd()) => {
|
|
9
27
|
let currentDir = cwd;
|
|
10
28
|
const root = normalize('/');
|
|
11
29
|
const home = homedir();
|
|
12
30
|
while (currentDir !== root && currentDir !== home) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// ignore
|
|
20
|
-
}
|
|
31
|
+
try {
|
|
32
|
+
accessSync(resolve(currentDir, CONTEXT_FILE));
|
|
33
|
+
return wrapWithContextFile(currentDir);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// ignore
|
|
21
37
|
}
|
|
22
38
|
currentDir = resolve(currentDir, '..');
|
|
23
39
|
}
|
|
@@ -32,7 +48,7 @@ export const readContextFile = (file) => {
|
|
|
32
48
|
}
|
|
33
49
|
};
|
|
34
50
|
export const enrichFromContext = (args) => {
|
|
35
|
-
if (args._[0] === 'set-context') {
|
|
51
|
+
if (args._[0] === 'set-context' || args._[0] === 'link') {
|
|
36
52
|
return;
|
|
37
53
|
}
|
|
38
54
|
const context = readContextFile(args.contextFile);
|
|
@@ -52,3 +68,59 @@ export const enrichFromContext = (args) => {
|
|
|
52
68
|
export const updateContextFile = (file, context) => {
|
|
53
69
|
writeFileSync(file, JSON.stringify(context, null, 2));
|
|
54
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* Shared primitive used by `set-context`, `link`, and `checkout` to persist
|
|
73
|
+
* context. Mirrors the destructive write semantics of `updateContextFile` —
|
|
74
|
+
* any field not present in `context` is dropped from the file.
|
|
75
|
+
*
|
|
76
|
+
* `.gitignore` scaffolding only happens when the context file is being
|
|
77
|
+
* *created* (it didn't exist before this write). On updates to an existing
|
|
78
|
+
* `.neon` we never touch `.gitignore`, so a user who deliberately un-ignored
|
|
79
|
+
* the file (e.g. to commit shared context) won't have the entry re-added on
|
|
80
|
+
* every subsequent command.
|
|
81
|
+
*/
|
|
82
|
+
export const applyContext = (file, context) => {
|
|
83
|
+
const isNewFile = !existsSync(file);
|
|
84
|
+
updateContextFile(file, context);
|
|
85
|
+
if (isNewFile) {
|
|
86
|
+
ensureGitignored(file);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Make sure the `.gitignore` next to `file` lists the file's basename
|
|
91
|
+
* (currently always `.neon`). Creates the `.gitignore` if it doesn't exist,
|
|
92
|
+
* or appends `.neon` if it's missing — never duplicates an existing entry.
|
|
93
|
+
*
|
|
94
|
+
* Best-effort: a failure here (e.g. read-only filesystem) is logged at debug
|
|
95
|
+
* level and swallowed; persisting the context file is the primary goal and
|
|
96
|
+
* must not be blocked by a `.gitignore` write error.
|
|
97
|
+
*/
|
|
98
|
+
export const ensureGitignored = (file) => {
|
|
99
|
+
try {
|
|
100
|
+
const dir = dirname(file);
|
|
101
|
+
const entry = basenameOf(file);
|
|
102
|
+
const gitignorePath = resolve(dir, GITIGNORE_FILE);
|
|
103
|
+
if (!existsSync(gitignorePath)) {
|
|
104
|
+
writeFileSync(gitignorePath, `${entry}\n`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const current = readFileSync(gitignorePath, 'utf-8');
|
|
108
|
+
if (hasGitignoreEntry(current, entry)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const needsLeadingNewline = current.length > 0 && !current.endsWith('\n');
|
|
112
|
+
const addition = `${needsLeadingNewline ? '\n' : ''}${entry}\n`;
|
|
113
|
+
writeFileSync(gitignorePath, current + addition);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
+
log.debug('Failed to update .gitignore next to %s: %s', file, message);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const basenameOf = (file) => {
|
|
121
|
+
const parts = file.split(/[\\/]/);
|
|
122
|
+
return parts[parts.length - 1] || CONTEXT_FILE;
|
|
123
|
+
};
|
|
124
|
+
const hasGitignoreEntry = (content, entry) => {
|
|
125
|
+
return content.split(/\r?\n/).some((line) => line.trim() === entry);
|
|
126
|
+
};
|
package/functions_api.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ContentType } from '@neondatabase/api-client';
|
|
2
|
+
const functionsPath = (projectId, branchId) => `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/functions`;
|
|
3
|
+
export const listFunctions = async (apiClient, projectId, branchId) => {
|
|
4
|
+
const { data } = await apiClient.request({
|
|
5
|
+
path: functionsPath(projectId, branchId),
|
|
6
|
+
method: 'GET',
|
|
7
|
+
secure: true,
|
|
8
|
+
format: 'json',
|
|
9
|
+
});
|
|
10
|
+
return data.functions;
|
|
11
|
+
};
|
|
12
|
+
export const getFunction = async (apiClient, projectId, branchId, slug) => {
|
|
13
|
+
const { data } = await apiClient.request({
|
|
14
|
+
path: `${functionsPath(projectId, branchId)}/${encodeURIComponent(slug)}`,
|
|
15
|
+
method: 'GET',
|
|
16
|
+
secure: true,
|
|
17
|
+
format: 'json',
|
|
18
|
+
});
|
|
19
|
+
return data.function;
|
|
20
|
+
};
|
|
21
|
+
export const deleteFunction = async (apiClient, projectId, branchId, slug) => {
|
|
22
|
+
await apiClient.request({
|
|
23
|
+
path: `${functionsPath(projectId, branchId)}/${encodeURIComponent(slug)}`,
|
|
24
|
+
method: 'DELETE',
|
|
25
|
+
secure: true,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
export const createDeployment = async (apiClient, projectId, branchId, slug, params) => {
|
|
29
|
+
const form = new FormData();
|
|
30
|
+
form.append('zip', new Blob([params.zip]), 'bundle.zip');
|
|
31
|
+
form.append('memory_mib', String(params.memoryMib));
|
|
32
|
+
form.append('runtime', params.runtime);
|
|
33
|
+
if (params.environment)
|
|
34
|
+
form.append('environment', params.environment);
|
|
35
|
+
// The deploy POST returns an operation the CLI cannot poll; the body is
|
|
36
|
+
// ignored. We only need the request to succeed.
|
|
37
|
+
await apiClient.request({
|
|
38
|
+
path: `${functionsPath(projectId, branchId)}/${encodeURIComponent(slug)}/deployments`,
|
|
39
|
+
method: 'POST',
|
|
40
|
+
type: ContentType.FormData,
|
|
41
|
+
body: form,
|
|
42
|
+
secure: true,
|
|
43
|
+
});
|
|
44
|
+
};
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -5,68 +5,75 @@
|
|
|
5
5
|
"url": "git+ssh://git@github.com/neondatabase/neonctl.git"
|
|
6
6
|
},
|
|
7
7
|
"type": "module",
|
|
8
|
-
"version": "2.
|
|
8
|
+
"version": "2.23.0",
|
|
9
9
|
"description": "CLI tool for NeonDB Cloud management",
|
|
10
10
|
"main": "index.js",
|
|
11
11
|
"author": "NeonDB",
|
|
12
|
-
"license": "
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
13
|
"private": false,
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=18"
|
|
16
16
|
},
|
|
17
|
+
"packageManager": "pnpm@9.15.9",
|
|
17
18
|
"bin": {
|
|
18
19
|
"neonctl": "cli.js",
|
|
19
20
|
"neon": "cli.js"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
|
-
"@apidevtools/swagger-parser": "
|
|
23
|
-
"@commitlint/cli": "
|
|
24
|
-
"@commitlint/config-conventional": "
|
|
25
|
-
"@eslint/js": "
|
|
26
|
-
"@rollup/plugin-commonjs": "
|
|
27
|
-
"@rollup/plugin-json": "
|
|
28
|
-
"@rollup/plugin-node-resolve": "
|
|
29
|
-
"@
|
|
30
|
-
"@
|
|
31
|
-
"@types/
|
|
32
|
-
"@
|
|
33
|
-
"@types/
|
|
34
|
-
"@types/
|
|
35
|
-
"@types/eslint__js": "^8.42.3",
|
|
36
|
-
"@types/express": "^4.17.17",
|
|
37
|
-
"@types/node": "^18.7.13",
|
|
23
|
+
"@apidevtools/swagger-parser": "12.1.0",
|
|
24
|
+
"@commitlint/cli": "17.8.1",
|
|
25
|
+
"@commitlint/config-conventional": "17.8.1",
|
|
26
|
+
"@eslint/js": "9.29.0",
|
|
27
|
+
"@rollup/plugin-commonjs": "25.0.8",
|
|
28
|
+
"@rollup/plugin-json": "6.1.0",
|
|
29
|
+
"@rollup/plugin-node-resolve": "15.2.3",
|
|
30
|
+
"@testcontainers/postgresql": "^11.14.0",
|
|
31
|
+
"@types/cli-table": "0.3.4",
|
|
32
|
+
"@types/diff": "5.2.1",
|
|
33
|
+
"@types/eslint__js": "8.42.3",
|
|
34
|
+
"@types/express": "4.17.21",
|
|
35
|
+
"@types/node": "18.19.41",
|
|
38
36
|
"@types/prompts": "2.4.9",
|
|
39
|
-
"@types/which": "
|
|
40
|
-
"@types/yargs": "
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
37
|
+
"@types/which": "3.0.4",
|
|
38
|
+
"@types/yargs": "17.0.32",
|
|
39
|
+
"@yao-pkg/pkg": "6.10.0",
|
|
40
|
+
"emocks": "3.0.4",
|
|
41
|
+
"esbuild": "0.28.0",
|
|
42
|
+
"eslint": "9.29.0",
|
|
43
|
+
"express": "4.19.2",
|
|
44
|
+
"husky": "8.0.3",
|
|
45
|
+
"lint-staged": "13.3.0",
|
|
46
|
+
"node-pty": "1.1.0",
|
|
47
|
+
"oauth2-mock-server": "8.1.0",
|
|
48
|
+
"prettier": "3.3.3",
|
|
49
|
+
"rollup": "3.29.4",
|
|
50
|
+
"strip-ansi": "7.1.0",
|
|
51
|
+
"tsx": "4.22.3",
|
|
52
|
+
"typescript": "4.9.5",
|
|
52
53
|
"typescript-eslint": "8.28.0",
|
|
53
|
-
"vitest": "
|
|
54
|
+
"vitest": "1.6.1"
|
|
55
|
+
},
|
|
56
|
+
"optionalDependencies": {
|
|
57
|
+
"esbuild": "0.28.0"
|
|
54
58
|
},
|
|
55
59
|
"dependencies": {
|
|
56
|
-
"@neondatabase/api-client": "2.
|
|
57
|
-
"@segment/analytics-node": "
|
|
58
|
-
"axios": "
|
|
59
|
-
"axios-debug-log": "
|
|
60
|
-
"chalk": "
|
|
61
|
-
"cli-table": "
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
60
|
+
"@neondatabase/api-client": "2.7.1",
|
|
61
|
+
"@segment/analytics-node": "1.3.0",
|
|
62
|
+
"axios": "1.7.2",
|
|
63
|
+
"axios-debug-log": "1.0.0",
|
|
64
|
+
"chalk": "5.3.0",
|
|
65
|
+
"cli-table": "0.3.11",
|
|
66
|
+
"cliui": "8.0.1",
|
|
67
|
+
"diff": "5.2.0",
|
|
68
|
+
"fflate": "^0.8.3",
|
|
69
|
+
"neon-init": "0.14.0",
|
|
70
|
+
"open": "10.1.0",
|
|
71
|
+
"openid-client": "6.8.1",
|
|
72
|
+
"pg-protocol": "^1.14.0",
|
|
66
73
|
"prompts": "2.4.2",
|
|
67
|
-
"which": "
|
|
68
|
-
"yaml": "
|
|
69
|
-
"yargs": "
|
|
74
|
+
"which": "3.0.1",
|
|
75
|
+
"yaml": "2.4.5",
|
|
76
|
+
"yargs": "17.7.2"
|
|
70
77
|
},
|
|
71
78
|
"publishConfig": {
|
|
72
79
|
"access": "public",
|
|
@@ -90,14 +97,16 @@
|
|
|
90
97
|
"scripts": {
|
|
91
98
|
"watch": "tsc --watch",
|
|
92
99
|
"typecheck": "tsc --noEmit",
|
|
93
|
-
"lint": "
|
|
94
|
-
"lint:fix": "
|
|
95
|
-
"build": "
|
|
100
|
+
"lint": "pnpm typecheck && eslint src && prettier --check .",
|
|
101
|
+
"lint:fix": "pnpm typecheck && eslint src --fix && prettier --w .",
|
|
102
|
+
"build": "pnpm generateParams && pnpm clean && tsc -p tsconfig.build.json && cp src/*.html package*.json README.md ./dist",
|
|
96
103
|
"clean": "rm -rf dist",
|
|
97
|
-
"generateParams": "
|
|
98
|
-
"start": "
|
|
99
|
-
"pretest": "
|
|
104
|
+
"generateParams": "tsx generateOptionsFromSpec.ts",
|
|
105
|
+
"start": "node dist/index.js",
|
|
106
|
+
"pretest": "pnpm build",
|
|
100
107
|
"test": "vitest run",
|
|
108
|
+
"test:conformance": "vitest run --config tests/psql-conformance/vitest.config.ts",
|
|
109
|
+
"test:conformance:matrix": "tsx tests/psql-conformance/scripts/run-local-matrix.ts",
|
|
101
110
|
"prepare": "test -d .git && husky install || true"
|
|
102
111
|
},
|
|
103
112
|
"lint-staged": {
|
package/psql/cli.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone psql shim.
|
|
4
|
+
*
|
|
5
|
+
* Thin wrapper around `runPsql` that lets the conformance harness — and any
|
|
6
|
+
* caller that wants a psql-compatible CLI — invoke our embedded TypeScript
|
|
7
|
+
* psql directly via `node dist/psql/cli.js`, without going through the
|
|
8
|
+
* neonctl yargs entrypoint.
|
|
9
|
+
*
|
|
10
|
+
* Why this exists: `dist/cli.js` is the neonctl entrypoint; yargs eats
|
|
11
|
+
* libpq-style flags (e.g. `-v ON_ERROR_STOP=0` collides with yargs'
|
|
12
|
+
* built-in `--version`). The conformance suite needs to spawn a binary
|
|
13
|
+
* that accepts the upstream psql flag grammar (`-h`/`-p`/`-U`/`-d`/`-X`/
|
|
14
|
+
* `-v VAR=VALUE`/`--no-psqlrc`/`--echo-all`/`--quiet`/etc.) and reads SQL
|
|
15
|
+
* from stdin. This shim wires `process.argv.slice(2)` straight into
|
|
16
|
+
* `runPsql`, which already speaks that grammar via `parseStartupArgs`.
|
|
17
|
+
*
|
|
18
|
+
* Argv shape:
|
|
19
|
+
* - If the first positional looks like a connection URI / conninfo
|
|
20
|
+
* (per `looksLikeConnectionString`), it stays at argv[0] — same shape
|
|
21
|
+
* as the legacy native-psql call site in `src/utils/psql.ts` and the
|
|
22
|
+
* existing `runPsql` API.
|
|
23
|
+
* - Otherwise (the regress harness case: bare libpq flags), we prepend
|
|
24
|
+
* an empty placeholder URI so `runPsql([''] + flags)` falls through to
|
|
25
|
+
* `parseStartupArgs` and resolves the connection from `-h`/`-p`/`-U`/
|
|
26
|
+
* `-d` plus PG* env / pgpass / service via the layered resolver.
|
|
27
|
+
*
|
|
28
|
+
* Exit code: forwarded straight from `runPsql` (matches upstream psql's
|
|
29
|
+
* EXIT_SUCCESS / EXIT_FAILURE / EXIT_USER / EXIT_BADCONN values; see
|
|
30
|
+
* `core/mainloop.ts`).
|
|
31
|
+
*/
|
|
32
|
+
import { runPsql, looksLikeConnectionString } from './index.js';
|
|
33
|
+
const main = async () => {
|
|
34
|
+
const raw = process.argv.slice(2);
|
|
35
|
+
// Detect whether the first arg is a connection URI/conninfo string.
|
|
36
|
+
// libpq's `recognized_connection_string()` covers both `postgres[ql]://…`
|
|
37
|
+
// URIs and bare `key=value` conninfo strings — `looksLikeConnectionString`
|
|
38
|
+
// mirrors that test.
|
|
39
|
+
const argv = raw.length > 0 && looksLikeConnectionString(raw[0]) ? raw : ['', ...raw];
|
|
40
|
+
const code = await runPsql(argv, {
|
|
41
|
+
stdin: process.stdin,
|
|
42
|
+
stdout: process.stdout,
|
|
43
|
+
stderr: process.stderr,
|
|
44
|
+
});
|
|
45
|
+
process.exit(code);
|
|
46
|
+
};
|
|
47
|
+
main().catch((err) => {
|
|
48
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
49
|
+
process.stderr.write(`psql: fatal: ${msg}\n`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|