neonctl 2.27.0 → 2.28.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/LICENSE.md +178 -0
- package/README.md +33 -1
- package/{analytics.js → dist/analytics.js} +21 -5
- package/dist/api.js +665 -0
- package/dist/cli.js +9 -0
- package/{commands → dist/commands}/auth.js +7 -0
- package/{commands → dist/commands}/branches.js +7 -4
- package/{commands → dist/commands}/bucket.js +69 -37
- package/{commands → dist/commands}/checkout.js +3 -3
- package/{commands → dist/commands}/config.js +22 -0
- package/{commands → dist/commands}/connection_string.js +1 -1
- package/{commands → dist/commands}/data_api.js +5 -6
- package/{commands → dist/commands}/databases.js +6 -3
- package/{commands → dist/commands}/functions.js +7 -9
- package/{commands → dist/commands}/index.js +2 -0
- package/{commands → dist/commands}/link.js +10 -17
- package/{commands → dist/commands}/neon_auth.js +8 -11
- package/{commands → dist/commands}/projects.js +4 -4
- package/{commands → dist/commands}/psql.js +1 -1
- package/{commands → dist/commands}/roles.js +6 -3
- package/{commands → dist/commands}/schema_diff.js +3 -4
- package/dist/commands/status.js +40 -0
- package/{context.js → dist/context.js} +16 -0
- package/dist/current_branch_fast_path.js +55 -0
- package/dist/errors.js +80 -0
- package/{functions_api.js → dist/functions_api.js} +1 -1
- package/{index.js → dist/index.js} +21 -20
- package/{parameters.gen.js → dist/parameters.gen.js} +14 -14
- package/{psql → dist/psql}/cli.js +0 -0
- package/{storage_api.js → dist/storage_api.js} +7 -8
- package/{test_utils → dist/test_utils}/fixtures.js +45 -15
- package/dist/utils/api_enums.js +33 -0
- package/{utils → dist/utils}/branch_picker.js +1 -1
- package/{utils → dist/utils}/enrichers.js +11 -4
- package/package.json +64 -67
- package/api.js +0 -35
- package/cli.js +0 -2
- package/errors.js +0 -17
- /package/{auth.js → dist/auth.js} +0 -0
- /package/{callback.html → dist/callback.html} +0 -0
- /package/{commands → dist/commands}/bootstrap.js +0 -0
- /package/{commands → dist/commands}/deploy.js +0 -0
- /package/{commands → dist/commands}/dev.js +0 -0
- /package/{commands → dist/commands}/env.js +0 -0
- /package/{commands → dist/commands}/init.js +0 -0
- /package/{commands → dist/commands}/ip_allow.js +0 -0
- /package/{commands → dist/commands}/operations.js +0 -0
- /package/{commands → dist/commands}/orgs.js +0 -0
- /package/{commands → dist/commands}/set_context.js +0 -0
- /package/{commands → dist/commands}/user.js +0 -0
- /package/{commands → dist/commands}/vpc_endpoints.js +0 -0
- /package/{config.js → dist/config.js} +0 -0
- /package/{config_format.js → dist/config_format.js} +0 -0
- /package/{dev → dist/dev}/env.js +0 -0
- /package/{dev → dist/dev}/functions.js +0 -0
- /package/{dev → dist/dev}/inputs.js +0 -0
- /package/{dev → dist/dev}/runtime.js +0 -0
- /package/{env.js → dist/env.js} +0 -0
- /package/{env_file.js → dist/env_file.js} +0 -0
- /package/{help.js → dist/help.js} +0 -0
- /package/{log.js → dist/log.js} +0 -0
- /package/{pkg.js → dist/pkg.js} +0 -0
- /package/{psql → dist/psql}/command/cmd_cond.js +0 -0
- /package/{psql → dist/psql}/command/cmd_connect.js +0 -0
- /package/{psql → dist/psql}/command/cmd_copy.js +0 -0
- /package/{psql → dist/psql}/command/cmd_describe.js +0 -0
- /package/{psql → dist/psql}/command/cmd_format.js +0 -0
- /package/{psql → dist/psql}/command/cmd_io.js +0 -0
- /package/{psql → dist/psql}/command/cmd_lo.js +0 -0
- /package/{psql → dist/psql}/command/cmd_meta.js +0 -0
- /package/{psql → dist/psql}/command/cmd_misc.js +0 -0
- /package/{psql → dist/psql}/command/cmd_pipeline.js +0 -0
- /package/{psql → dist/psql}/command/cmd_restrict.js +0 -0
- /package/{psql → dist/psql}/command/cmd_show.js +0 -0
- /package/{psql → dist/psql}/command/dispatch.js +0 -0
- /package/{psql → dist/psql}/command/inputQueue.js +0 -0
- /package/{psql → dist/psql}/command/shared.js +0 -0
- /package/{psql → dist/psql}/complete/filenames.js +0 -0
- /package/{psql → dist/psql}/complete/index.js +0 -0
- /package/{psql → dist/psql}/complete/matcher.js +0 -0
- /package/{psql → dist/psql}/complete/psqlVars.js +0 -0
- /package/{psql → dist/psql}/complete/queries.js +0 -0
- /package/{psql → dist/psql}/complete/rules.js +0 -0
- /package/{psql → dist/psql}/core/common.js +0 -0
- /package/{psql → dist/psql}/core/help.js +0 -0
- /package/{psql → dist/psql}/core/mainloop.js +0 -0
- /package/{psql → dist/psql}/core/prompt.js +0 -0
- /package/{psql → dist/psql}/core/settings.js +0 -0
- /package/{psql → dist/psql}/core/sqlHelp.js +0 -0
- /package/{psql → dist/psql}/core/startup.js +0 -0
- /package/{psql → dist/psql}/core/syncVars.js +0 -0
- /package/{psql → dist/psql}/core/variables.js +0 -0
- /package/{psql → dist/psql}/describe/formatters.js +0 -0
- /package/{psql → dist/psql}/describe/processNamePattern.js +0 -0
- /package/{psql → dist/psql}/describe/queries.js +0 -0
- /package/{psql → dist/psql}/describe/versionGate.js +0 -0
- /package/{psql → dist/psql}/index.js +0 -0
- /package/{psql → dist/psql}/io/history.js +0 -0
- /package/{psql → dist/psql}/io/input.js +0 -0
- /package/{psql → dist/psql}/io/lineEditor/buffer.js +0 -0
- /package/{psql → dist/psql}/io/lineEditor/complete.js +0 -0
- /package/{psql → dist/psql}/io/lineEditor/filename.js +0 -0
- /package/{psql → dist/psql}/io/lineEditor/index.js +0 -0
- /package/{psql → dist/psql}/io/lineEditor/keymap.js +0 -0
- /package/{psql → dist/psql}/io/lineEditor/vt100.js +0 -0
- /package/{psql → dist/psql}/io/pgpass.js +0 -0
- /package/{psql → dist/psql}/io/pgservice.js +0 -0
- /package/{psql → dist/psql}/io/psqlrc.js +0 -0
- /package/{psql → dist/psql}/print/aligned.js +0 -0
- /package/{psql → dist/psql}/print/asciidoc.js +0 -0
- /package/{psql → dist/psql}/print/crosstab.js +0 -0
- /package/{psql → dist/psql}/print/csv.js +0 -0
- /package/{psql → dist/psql}/print/html.js +0 -0
- /package/{psql → dist/psql}/print/json.js +0 -0
- /package/{psql → dist/psql}/print/latex.js +0 -0
- /package/{psql → dist/psql}/print/pager.js +0 -0
- /package/{psql → dist/psql}/print/troff.js +0 -0
- /package/{psql → dist/psql}/print/unaligned.js +0 -0
- /package/{psql → dist/psql}/print/units.js +0 -0
- /package/{psql → dist/psql}/scanner/slash.js +0 -0
- /package/{psql → dist/psql}/scanner/sql.js +0 -0
- /package/{psql → dist/psql}/scanner/stringutils.js +0 -0
- /package/{psql → dist/psql}/types/backslash.js +0 -0
- /package/{psql → dist/psql}/types/connection.js +0 -0
- /package/{psql → dist/psql}/types/index.js +0 -0
- /package/{psql → dist/psql}/types/printer.js +0 -0
- /package/{psql → dist/psql}/types/repl.js +0 -0
- /package/{psql → dist/psql}/types/scanner.js +0 -0
- /package/{psql → dist/psql}/types/settings.js +0 -0
- /package/{psql → dist/psql}/types/variables.js +0 -0
- /package/{psql → dist/psql}/wire/connection.js +0 -0
- /package/{psql → dist/psql}/wire/copy.js +0 -0
- /package/{psql → dist/psql}/wire/notify.js +0 -0
- /package/{psql → dist/psql}/wire/pipeline.js +0 -0
- /package/{psql → dist/psql}/wire/protocol.js +0 -0
- /package/{psql → dist/psql}/wire/sasl.js +0 -0
- /package/{psql → dist/psql}/wire/tls.js +0 -0
- /package/{test_utils → dist/test_utils}/oauth_server.js +0 -0
- /package/{types.js → dist/types.js} +0 -0
- /package/{utils → dist/utils}/auth.js +0 -0
- /package/{utils → dist/utils}/branch_notice.js +0 -0
- /package/{utils → dist/utils}/compute_units.js +0 -0
- /package/{utils → dist/utils}/esbuild.js +0 -0
- /package/{utils → dist/utils}/formats.js +0 -0
- /package/{utils → dist/utils}/middlewares.js +0 -0
- /package/{utils → dist/utils}/point_in_time.js +0 -0
- /package/{utils → dist/utils}/psql.js +0 -0
- /package/{utils → dist/utils}/string.js +0 -0
- /package/{utils → dist/utils}/ui.js +0 -0
- /package/{utils → dist/utils}/zip.js +0 -0
- /package/{writer.js → dist/writer.js} +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { contextBranch, currentContextFile, readContextFile } from './context.js';
|
|
2
|
+
import { log } from './log.js';
|
|
3
|
+
/**
|
|
4
|
+
* Offline fast path for `(config) status --current-branch` (used by shell prompts).
|
|
5
|
+
*
|
|
6
|
+
* Reading the pinned branch out of the local `.neon` file does not need the CLI's
|
|
7
|
+
* full command tree, `@neondatabase/api-client`, or yargs — importing those is ~200ms,
|
|
8
|
+
* which dwarfs the actual work. So the entry point ({@link file://./cli.ts}) calls this
|
|
9
|
+
* BEFORE importing `index.js`, and only falls through to the full CLI when this returns
|
|
10
|
+
* `false`. On the fast path the process loads only this module + `context.js`/`log.js`
|
|
11
|
+
* (~25ms total incl. Node startup) instead of ~230ms.
|
|
12
|
+
*
|
|
13
|
+
* It mirrors the `--current-branch` short-circuit in `status()` (commands/config.ts):
|
|
14
|
+
* print the pinned branch to stdout and exit 0, or print nothing + a `neonctl checkout`
|
|
15
|
+
* hint on stderr and exit non-zero when no branch is pinned.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately conservative: only the EXACT bare invocation is handled —
|
|
18
|
+
* `status --current-branch` or `config status --current-branch` with no other args.
|
|
19
|
+
* Anything else (extra flags like `--context-file`/`--output`, more args, etc.) returns
|
|
20
|
+
* `false` and flows through the normal yargs pipeline, so behavior can never diverge —
|
|
21
|
+
* the worst case is "not faster", never "wrong".
|
|
22
|
+
*
|
|
23
|
+
* @returns `true` if it handled the invocation (caller should NOT load the full CLI).
|
|
24
|
+
*/
|
|
25
|
+
export const tryCurrentBranchFastPath = (argv,
|
|
26
|
+
// `cwd` is overridable so tests can exercise the `.neon` walk-up without mutating
|
|
27
|
+
// `process.cwd()` (which isn't allowed in vitest workers), mirroring currentContextFile.
|
|
28
|
+
cwd = process.cwd()) => {
|
|
29
|
+
// argv is [execPath, scriptPath, ...userArgs].
|
|
30
|
+
if (!isExactCurrentBranchInvocation(argv.slice(2))) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const branch = contextBranch(readContextFile(currentContextFile(cwd)));
|
|
34
|
+
if (branch) {
|
|
35
|
+
process.stdout.write(`${branch}\n`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
log.info('No branch pinned. Run `neonctl checkout <branch>` to pin a branch and pull its env vars.');
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* True only for `status --current-branch` or `config status --current-branch` with no
|
|
45
|
+
* other arguments. Any extra token (another flag, `--context-file`, `=`-style flags,
|
|
46
|
+
* positional args) makes this false so the full CLI handles it.
|
|
47
|
+
*/
|
|
48
|
+
const isExactCurrentBranchInvocation = (args) => {
|
|
49
|
+
const rest = args[0] === 'status'
|
|
50
|
+
? args.slice(1)
|
|
51
|
+
: args[0] === 'config' && args[1] === 'status'
|
|
52
|
+
? args.slice(2)
|
|
53
|
+
: null;
|
|
54
|
+
return rest !== null && rest.length === 1 && rest[0] === '--current-branch';
|
|
55
|
+
};
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const ERROR_MATCHERS = [
|
|
2
|
+
[/^Unknown command: (.*)$/, 'UNKNOWN_COMMAND'],
|
|
3
|
+
[/^Missing required argument: (.*)$/, 'MISSING_ARGUMENT'],
|
|
4
|
+
[/^Failed to open web browser. (.*)$/, 'AUTH_BROWSER_FAILED'],
|
|
5
|
+
];
|
|
6
|
+
export const matchErrorCode = (message) => {
|
|
7
|
+
if (!message) {
|
|
8
|
+
return 'UNKNOWN_ERROR';
|
|
9
|
+
}
|
|
10
|
+
for (const [matcher, code] of ERROR_MATCHERS) {
|
|
11
|
+
const match = message.match(matcher);
|
|
12
|
+
if (match) {
|
|
13
|
+
return code;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return 'UNKNOWN_ERROR';
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The single, human-readable line shown when the CLI couldn't reach the Neon API because
|
|
20
|
+
* of a connection-level failure (DNS, refused/reset connection, offline). It replaces the
|
|
21
|
+
* cryptic `fetch failed` / empty axios message a network blip otherwise surfaces (see
|
|
22
|
+
* {@link isNetworkError}), pointing at the two things the user can actually check.
|
|
23
|
+
*/
|
|
24
|
+
export const NETWORK_ERROR_MESSAGE = 'Could not reach the Neon API. Please check your internet connection and try again. ' +
|
|
25
|
+
'If your connection is fine and this keeps happening, check https://neonstatus.com for ongoing incidents.';
|
|
26
|
+
/**
|
|
27
|
+
* Node-level socket/DNS error codes that mean the request never reached the server — a
|
|
28
|
+
* genuine connectivity problem rather than an API response we should surface. Deliberately
|
|
29
|
+
* excludes `ECONNABORTED` (axios' timeout), which the CLI already reports as a timeout.
|
|
30
|
+
*/
|
|
31
|
+
const NETWORK_ERROR_CODES = new Set([
|
|
32
|
+
'ECONNREFUSED',
|
|
33
|
+
'ECONNRESET',
|
|
34
|
+
'ETIMEDOUT',
|
|
35
|
+
'ENOTFOUND',
|
|
36
|
+
'EAI_AGAIN',
|
|
37
|
+
'EPIPE',
|
|
38
|
+
'EHOSTUNREACH',
|
|
39
|
+
'ENETUNREACH',
|
|
40
|
+
'EHOSTDOWN',
|
|
41
|
+
'ENETDOWN',
|
|
42
|
+
]);
|
|
43
|
+
/**
|
|
44
|
+
* Message fragments that mark a connection-level failure across our two transports: the
|
|
45
|
+
* `@neon/sdk` / global `fetch` path (used by `env pull` / `config` / `deploy`, and `link`'s
|
|
46
|
+
* bundled env pull) throws a bare `TypeError: fetch failed` / "Failed to fetch", while the
|
|
47
|
+
* axios `api-client` path throws an `AxiosError` whose message is "Network Error".
|
|
48
|
+
*/
|
|
49
|
+
const NETWORK_ERROR_MESSAGE_PATTERN = /fetch failed|failed to fetch|network error/i;
|
|
50
|
+
const readErrorCode = (value) => {
|
|
51
|
+
if (value === null || typeof value !== 'object')
|
|
52
|
+
return undefined;
|
|
53
|
+
const code = value.code;
|
|
54
|
+
return typeof code === 'string' ? code : undefined;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Whether `err` is a connection-level failure (the network blip the user sees as a cryptic
|
|
58
|
+
* `fetch failed`, or no message at all on the axios path) rather than an actual API response.
|
|
59
|
+
*
|
|
60
|
+
* A Node `fetch` failure is a bare `TypeError: fetch failed` whose underlying `cause` carries
|
|
61
|
+
* the real socket `code` (e.g. `ECONNREFUSED`, `ENOTFOUND`, `EAI_AGAIN`), so we walk the
|
|
62
|
+
* `cause` chain checking both the code and the message. This is what lets the CLI swap the
|
|
63
|
+
* confusing default for {@link NETWORK_ERROR_MESSAGE}. It matches only failures where no
|
|
64
|
+
* response was ever received, so it never masks a real 4xx/5xx the user needs to see.
|
|
65
|
+
*/
|
|
66
|
+
export const isNetworkError = (err) => {
|
|
67
|
+
let current = err;
|
|
68
|
+
for (let depth = 0; depth < 6 && current !== null && current !== undefined; depth++) {
|
|
69
|
+
const code = readErrorCode(current);
|
|
70
|
+
if (code !== undefined && NETWORK_ERROR_CODES.has(code)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (current instanceof Error &&
|
|
74
|
+
NETWORK_ERROR_MESSAGE_PATTERN.test(current.message)) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
current = current.cause;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContentType } from '
|
|
1
|
+
import { ContentType } from './api.js';
|
|
2
2
|
const functionsPath = (projectId, branchId) => `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/functions`;
|
|
3
3
|
export const listFunctions = async (apiClient, projectId, branchId, { cursor, limit } = {}) => {
|
|
4
4
|
const { data } = await apiClient.request({
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import { basename } from 'node:path';
|
|
2
2
|
import yargs from 'yargs';
|
|
3
3
|
import { hideBin } from 'yargs/helpers';
|
|
4
|
-
import
|
|
5
|
-
axiosDebug({
|
|
6
|
-
request(debug, config) {
|
|
7
|
-
debug(`${config.method?.toUpperCase()} ${config.url}`);
|
|
8
|
-
},
|
|
9
|
-
response(debug, response) {
|
|
10
|
-
debug(`${response.status} ${response.statusText}`);
|
|
11
|
-
},
|
|
12
|
-
error(debug, error) {
|
|
13
|
-
debug(error);
|
|
14
|
-
},
|
|
15
|
-
});
|
|
4
|
+
import { isNeonApiError, messageFromBody } from './api.js';
|
|
16
5
|
import { ensureAuth, deleteCredentials } from './commands/auth.js';
|
|
17
6
|
import { defaultDir, ensureConfigDir } from './config.js';
|
|
18
7
|
import { log } from './log.js';
|
|
@@ -21,8 +10,7 @@ import { fillInArgs } from './utils/middlewares.js';
|
|
|
21
10
|
import pkg from './pkg.js';
|
|
22
11
|
import commands from './commands/index.js';
|
|
23
12
|
import { analyticsMiddleware, initAnalyticsClientMiddleware, closeAnalytics, getAnalyticsEventProperties, sendError, trackEvent, } from './analytics.js';
|
|
24
|
-
import {
|
|
25
|
-
import { matchErrorCode } from './errors.js';
|
|
13
|
+
import { isNetworkError, matchErrorCode, NETWORK_ERROR_MESSAGE, } from './errors.js';
|
|
26
14
|
import { showHelp } from './help.js';
|
|
27
15
|
import { currentContextFile, enrichFromContext } from './context.js';
|
|
28
16
|
const NO_SUBCOMMANDS_VERBS = [
|
|
@@ -41,7 +29,8 @@ const NO_SUBCOMMANDS_VERBS = [
|
|
|
41
29
|
'dev',
|
|
42
30
|
'deploy',
|
|
43
31
|
'bootstrap',
|
|
44
|
-
//
|
|
32
|
+
// alias of `config status`
|
|
33
|
+
'status',
|
|
45
34
|
];
|
|
46
35
|
let builder = yargs(hideBin(process.argv));
|
|
47
36
|
builder = builder
|
|
@@ -161,13 +150,24 @@ async function handleError(msg, err) {
|
|
|
161
150
|
if (err instanceof Error && err.stack) {
|
|
162
151
|
log.debug('Stack: %s', err.stack);
|
|
163
152
|
}
|
|
164
|
-
|
|
153
|
+
// A connection-level failure (no response ever reached us) reads as a cryptic
|
|
154
|
+
// `fetch failed` from the @neon/sdk / global `fetch` path. Detect it first and
|
|
155
|
+
// swap in one clear "check your connection" hint. We deliberately do not retry
|
|
156
|
+
// here: re-running the whole command could re-trigger a non-idempotent step
|
|
157
|
+
// (e.g. project create), so retries belong at the request layer.
|
|
158
|
+
if (isNetworkError(err)) {
|
|
159
|
+
log.error(NETWORK_ERROR_MESSAGE);
|
|
160
|
+
const error = err instanceof Error ? err : new Error(NETWORK_ERROR_MESSAGE);
|
|
161
|
+
sendError(error, 'NETWORK_ERROR');
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
if (isNeonApiError(err)) {
|
|
165
165
|
if (err.code === 'ECONNABORTED') {
|
|
166
166
|
log.error('Request timed out');
|
|
167
167
|
sendError(err, 'REQUEST_TIMEOUT');
|
|
168
168
|
return false;
|
|
169
169
|
}
|
|
170
|
-
else if (err.
|
|
170
|
+
else if (err.status === 401) {
|
|
171
171
|
sendError(err, 'AUTH_FAILED');
|
|
172
172
|
log.info('Authentication failed, deleting credentials...');
|
|
173
173
|
try {
|
|
@@ -180,10 +180,11 @@ async function handleError(msg, err) {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
else {
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
const serverMessage = messageFromBody(err.data);
|
|
184
|
+
if (serverMessage) {
|
|
185
|
+
log.error(serverMessage);
|
|
185
186
|
}
|
|
186
|
-
log.debug('status: %d %s | path: %s', err.
|
|
187
|
+
log.debug('status: %d %s | path: %s', err.status, err.statusText, err.requestPath);
|
|
187
188
|
sendError(err, 'API_ERROR');
|
|
188
189
|
return false;
|
|
189
190
|
}
|
|
@@ -108,17 +108,17 @@ export const projectCreateRequest = {
|
|
|
108
108
|
},
|
|
109
109
|
'project.provisioner': {
|
|
110
110
|
type: "string",
|
|
111
|
-
description: "The Neon compute provisioner.\nSpecify the `k8s-neonvm` provisioner to create a compute endpoint that supports Autoscaling.\n\nProvisioner can be one of the following values:\n* k8s-pod\n* k8s-neonvm\n\nClients must expect, that any string value that is not documented in the description above should be treated as a error. UNKNOWN value if safe to treat as an error too.\n",
|
|
111
|
+
description: "The Neon compute provisioner.\nSpecify the `k8s-neonvm` provisioner to create a compute endpoint that supports Autoscaling.\n\nProvisioner can be one of the following values:\n* k8s-pod\n* k8s-neonvm\n* serverless-platform\n\nClients must expect, that any string value that is not documented in the description above should be treated as a error. UNKNOWN value if safe to treat as an error too.\n",
|
|
112
112
|
demandOption: false,
|
|
113
113
|
},
|
|
114
114
|
'project.region_id': {
|
|
115
115
|
type: "string",
|
|
116
|
-
description: "The region identifier. Refer to our [Regions](https://neon.
|
|
116
|
+
description: "The region identifier. Refer to our [Regions](https://neon.com/docs/introduction/regions) documentation for supported regions. Values are specified in this format: `aws-us-east-1`\n",
|
|
117
117
|
demandOption: false,
|
|
118
118
|
},
|
|
119
119
|
'project.default_endpoint_settings.suspend_timeout_seconds': {
|
|
120
120
|
type: "number",
|
|
121
|
-
description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.
|
|
121
|
+
description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.com/docs/manage/endpoints#scale-to-zero-configuration).\n",
|
|
122
122
|
demandOption: false,
|
|
123
123
|
},
|
|
124
124
|
'project.pg_version': {
|
|
@@ -236,7 +236,7 @@ export const projectUpdateRequest = {
|
|
|
236
236
|
},
|
|
237
237
|
'project.default_endpoint_settings.suspend_timeout_seconds': {
|
|
238
238
|
type: "number",
|
|
239
|
-
description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.
|
|
239
|
+
description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.com/docs/manage/endpoints#scale-to-zero-configuration).\n",
|
|
240
240
|
demandOption: false,
|
|
241
241
|
},
|
|
242
242
|
'project.history_retention_seconds': {
|
|
@@ -311,12 +311,12 @@ export const branchCreateRequestEndpointOptions = {
|
|
|
311
311
|
},
|
|
312
312
|
'provisioner': {
|
|
313
313
|
type: "string",
|
|
314
|
-
description: "The Neon compute provisioner.\nSpecify the `k8s-neonvm` provisioner to create a compute endpoint that supports Autoscaling.\n\nProvisioner can be one of the following values:\n* k8s-pod\n* k8s-neonvm\n\nClients must expect, that any string value that is not documented in the description above should be treated as a error. UNKNOWN value if safe to treat as an error too.\n",
|
|
314
|
+
description: "The Neon compute provisioner.\nSpecify the `k8s-neonvm` provisioner to create a compute endpoint that supports Autoscaling.\n\nProvisioner can be one of the following values:\n* k8s-pod\n* k8s-neonvm\n* serverless-platform\n\nClients must expect, that any string value that is not documented in the description above should be treated as a error. UNKNOWN value if safe to treat as an error too.\n",
|
|
315
315
|
demandOption: false,
|
|
316
316
|
},
|
|
317
317
|
'suspend_timeout_seconds': {
|
|
318
318
|
type: "number",
|
|
319
|
-
description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.
|
|
319
|
+
description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.com/docs/manage/endpoints#scale-to-zero-configuration).\n",
|
|
320
320
|
demandOption: false,
|
|
321
321
|
},
|
|
322
322
|
};
|
|
@@ -366,17 +366,17 @@ export const endpointCreateRequest = {
|
|
|
366
366
|
},
|
|
367
367
|
'endpoint.provisioner': {
|
|
368
368
|
type: "string",
|
|
369
|
-
description: "The Neon compute provisioner.\nSpecify the `k8s-neonvm` provisioner to create a compute endpoint that supports Autoscaling.\n\nProvisioner can be one of the following values:\n* k8s-pod\n* k8s-neonvm\n\nClients must expect, that any string value that is not documented in the description above should be treated as a error. UNKNOWN value if safe to treat as an error too.\n",
|
|
369
|
+
description: "The Neon compute provisioner.\nSpecify the `k8s-neonvm` provisioner to create a compute endpoint that supports Autoscaling.\n\nProvisioner can be one of the following values:\n* k8s-pod\n* k8s-neonvm\n* serverless-platform\n\nClients must expect, that any string value that is not documented in the description above should be treated as a error. UNKNOWN value if safe to treat as an error too.\n",
|
|
370
370
|
demandOption: false,
|
|
371
371
|
},
|
|
372
372
|
'endpoint.pooler_enabled': {
|
|
373
373
|
type: "boolean",
|
|
374
|
-
description: "Whether to enable connection pooling for the compute endpoint\n",
|
|
374
|
+
description: "DEPRECATED. Whether to enable connection pooling for the compute endpoint.\nThe recommended way to enable connection pooling is to append `-pooler` to the endpoint ID in the connection string.\nSee [How to use connection pooling](https://neon.com/docs/connect/connection-pooling#how-to-use-connection-pooling)\n",
|
|
375
375
|
demandOption: false,
|
|
376
376
|
},
|
|
377
377
|
'endpoint.pooler_mode': {
|
|
378
378
|
type: "string",
|
|
379
|
-
description: "The connection pooler mode.
|
|
379
|
+
description: "DEPRECATED. The connection pooler mode. This field is deprecated and will be removed after 2026-06-20.\n",
|
|
380
380
|
demandOption: false,
|
|
381
381
|
choices: ["transaction"],
|
|
382
382
|
},
|
|
@@ -392,7 +392,7 @@ export const endpointCreateRequest = {
|
|
|
392
392
|
},
|
|
393
393
|
'endpoint.suspend_timeout_seconds': {
|
|
394
394
|
type: "number",
|
|
395
|
-
description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.
|
|
395
|
+
description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.com/docs/manage/endpoints#scale-to-zero-configuration).\n",
|
|
396
396
|
demandOption: false,
|
|
397
397
|
},
|
|
398
398
|
'endpoint.name': {
|
|
@@ -409,7 +409,7 @@ export const endpointUpdateRequest = {
|
|
|
409
409
|
},
|
|
410
410
|
'endpoint.provisioner': {
|
|
411
411
|
type: "string",
|
|
412
|
-
description: "The Neon compute provisioner.\nSpecify the `k8s-neonvm` provisioner to create a compute endpoint that supports Autoscaling.\n\nProvisioner can be one of the following values:\n* k8s-pod\n* k8s-neonvm\n\nClients must expect, that any string value that is not documented in the description above should be treated as a error. UNKNOWN value if safe to treat as an error too.\n",
|
|
412
|
+
description: "The Neon compute provisioner.\nSpecify the `k8s-neonvm` provisioner to create a compute endpoint that supports Autoscaling.\n\nProvisioner can be one of the following values:\n* k8s-pod\n* k8s-neonvm\n* serverless-platform\n\nClients must expect, that any string value that is not documented in the description above should be treated as a error. UNKNOWN value if safe to treat as an error too.\n",
|
|
413
413
|
demandOption: false,
|
|
414
414
|
},
|
|
415
415
|
'endpoint.settings.preload_libraries.use_defaults': {
|
|
@@ -424,12 +424,12 @@ export const endpointUpdateRequest = {
|
|
|
424
424
|
},
|
|
425
425
|
'endpoint.pooler_enabled': {
|
|
426
426
|
type: "boolean",
|
|
427
|
-
description: "Whether to enable connection pooling for the compute endpoint\n",
|
|
427
|
+
description: "DEPRECATED. Whether to enable connection pooling for the compute endpoint.\nThe recommended way to enable connection pooling is to append `-pooler` to the endpoint ID in the connection string.\nSee [How to use connection pooling](https://neon.com/docs/connect/connection-pooling#how-to-use-connection-pooling)\n",
|
|
428
428
|
demandOption: false,
|
|
429
429
|
},
|
|
430
430
|
'endpoint.pooler_mode': {
|
|
431
431
|
type: "string",
|
|
432
|
-
description: "The connection pooler mode.
|
|
432
|
+
description: "DEPRECATED. The connection pooler mode. This field is deprecated and will be removed after 2026-06-20.\n",
|
|
433
433
|
demandOption: false,
|
|
434
434
|
choices: ["transaction"],
|
|
435
435
|
},
|
|
@@ -445,7 +445,7 @@ export const endpointUpdateRequest = {
|
|
|
445
445
|
},
|
|
446
446
|
'endpoint.suspend_timeout_seconds': {
|
|
447
447
|
type: "number",
|
|
448
|
-
description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.
|
|
448
|
+
description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.com/docs/manage/endpoints#scale-to-zero-configuration).\n",
|
|
449
449
|
demandOption: false,
|
|
450
450
|
},
|
|
451
451
|
'endpoint.name': {
|
|
File without changes
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// Typed client helpers for the branch object-storage (bucket/object) API.
|
|
2
2
|
//
|
|
3
3
|
// These endpoints are part of the Neon object-storage surface (the "Buckets"
|
|
4
|
-
// tag in the public API). They are not yet exposed as typed methods on
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// change.
|
|
4
|
+
// tag in the public API). They are not yet exposed as typed methods on
|
|
5
|
+
// `@neon/sdk`, so the request/response types and the thin call helpers live
|
|
6
|
+
// here. They are implemented on top of the API client's low-level `request()`
|
|
7
|
+
// method, which reuses the exact same authentication, base URL, headers and
|
|
8
|
+
// retry behaviour as every other neonctl command. When the SDK gains these
|
|
9
|
+
// methods, the call sites in `src/commands/bucket.ts` can switch over with no
|
|
10
|
+
// behavioural change.
|
|
12
11
|
const bucketsPath = (projectId, branchId) => `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/buckets`;
|
|
13
12
|
const bucketPath = (projectId, branchId, bucketName) => `${bucketsPath(projectId, branchId)}/${encodeURIComponent(bucketName)}`;
|
|
14
13
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
1
2
|
import { fork } from 'node:child_process';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { expect, test as originalTest } from 'vitest';
|
|
@@ -5,41 +6,70 @@ import strip from 'strip-ansi';
|
|
|
5
6
|
import emocks from 'emocks';
|
|
6
7
|
import express from 'express';
|
|
7
8
|
import { log } from '../log';
|
|
9
|
+
/**
|
|
10
|
+
* Reserve a localhost port and close its listener, returning a URL that is guaranteed to
|
|
11
|
+
* refuse connections right now. Lets a test drive the CLI into a real `ECONNREFUSED`
|
|
12
|
+
* (the same shape a network blip produces) deterministically and fast.
|
|
13
|
+
*/
|
|
14
|
+
const reserveClosedPort = () => new Promise((resolve, reject) => {
|
|
15
|
+
const probe = createServer();
|
|
16
|
+
probe.on('error', reject);
|
|
17
|
+
probe.listen(0, '127.0.0.1', () => {
|
|
18
|
+
const { port } = probe.address();
|
|
19
|
+
probe.close((err) => {
|
|
20
|
+
if (err) {
|
|
21
|
+
reject(err);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
resolve(`http://127.0.0.1:${port}`);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
8
29
|
export const test = originalTest.extend({
|
|
9
30
|
// eslint-disable-next-line no-empty-pattern
|
|
10
31
|
runMockServer: async ({}, use) => {
|
|
11
|
-
let
|
|
32
|
+
let startedServer;
|
|
12
33
|
await use(async (mockDir) => {
|
|
13
34
|
const app = express();
|
|
14
35
|
app.use(express.json());
|
|
15
36
|
app.use('/', emocks(join(process.cwd(), 'mocks', mockDir), {
|
|
16
37
|
'404': (_req, res) => res.status(404).send({ message: 'Not Found' }),
|
|
17
38
|
}));
|
|
18
|
-
await new Promise((resolve) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
39
|
+
const server = await new Promise((resolve) => {
|
|
40
|
+
const s = app.listen(0, () => {
|
|
41
|
+
log.debug('Mock server listening at %d', s.address().port);
|
|
42
|
+
resolve(s);
|
|
22
43
|
});
|
|
23
44
|
});
|
|
45
|
+
startedServer = server;
|
|
24
46
|
return server;
|
|
25
47
|
});
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
48
|
+
// `unreachableHost` tests never start the server, so only close it when it ran.
|
|
49
|
+
const server = startedServer;
|
|
50
|
+
if (server) {
|
|
51
|
+
await new Promise((resolve, reject) => {
|
|
52
|
+
server.close((err) => {
|
|
53
|
+
if (err) {
|
|
54
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
resolve();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
34
62
|
},
|
|
35
63
|
testCliCommand: async ({ runMockServer }, use) => {
|
|
36
64
|
await use(async (args, options = {}) => {
|
|
37
|
-
const
|
|
65
|
+
const apiHost = options.unreachableHost
|
|
66
|
+
? await reserveClosedPort()
|
|
67
|
+
: `http://localhost:${(await runMockServer(options.mockDir || 'main')).address().port}`;
|
|
38
68
|
let output = '';
|
|
39
69
|
let error = '';
|
|
40
70
|
const cp = fork(join(process.cwd(), './dist/index.js'), [
|
|
41
71
|
'--api-host',
|
|
42
|
-
|
|
72
|
+
apiHost,
|
|
43
73
|
'--output',
|
|
44
74
|
options.output ?? (options.outputTable ? 'table' : 'yaml'),
|
|
45
75
|
'--api-key',
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Runtime enum-like constants for Neon API string unions.
|
|
2
|
+
//
|
|
3
|
+
// `@neondatabase/api-client` generated TypeScript `enum`s (real runtime objects)
|
|
4
|
+
// for fields like the compute endpoint type. `@neon/sdk` instead models these as
|
|
5
|
+
// plain string-literal union *types*, which have no runtime value — so code that
|
|
6
|
+
// read `EndpointType.ReadWrite` or `Object.values(EndpointType)` no longer works.
|
|
7
|
+
//
|
|
8
|
+
// These `as const` objects restore that runtime surface, and each is paired with
|
|
9
|
+
// a same-named type whose union is identical to the corresponding `@neon/sdk`
|
|
10
|
+
// type, so values stay assignable in both directions.
|
|
11
|
+
export const EndpointType = {
|
|
12
|
+
ReadOnly: 'read_only',
|
|
13
|
+
ReadWrite: 'read_write',
|
|
14
|
+
};
|
|
15
|
+
export const NeonAuthOauthProviderId = {
|
|
16
|
+
Google: 'google',
|
|
17
|
+
Github: 'github',
|
|
18
|
+
Microsoft: 'microsoft',
|
|
19
|
+
Vercel: 'vercel',
|
|
20
|
+
};
|
|
21
|
+
export const NeonAuthOauthProviderType = {
|
|
22
|
+
Standard: 'standard',
|
|
23
|
+
Shared: 'shared',
|
|
24
|
+
};
|
|
25
|
+
export const NeonAuthSupportedAuthProvider = {
|
|
26
|
+
Mock: 'mock',
|
|
27
|
+
Stack: 'stack',
|
|
28
|
+
BetterAuth: 'better_auth',
|
|
29
|
+
};
|
|
30
|
+
export const NeonAuthEmailVerificationMethod = {
|
|
31
|
+
Link: 'link',
|
|
32
|
+
Otp: 'otp',
|
|
33
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { isCurrentBranchProbe } from '../context.js';
|
|
1
2
|
import { looksLikeBranchId } from './formats.js';
|
|
2
|
-
import {
|
|
3
|
+
import { isNeonApiError, messageFromBody } from '../api.js';
|
|
3
4
|
export const branchIdResolve = async ({ branch, apiClient, projectId, }) => {
|
|
4
5
|
branch = branch.toString();
|
|
5
6
|
if (looksLikeBranchId(branch)) {
|
|
@@ -97,6 +98,12 @@ export const resolveSingleDatabase = async (props) => {
|
|
|
97
98
|
throw new Error(`Multiple databases found for the branch, please provide one with the --database option: ${databases.map((d) => d.name).join(', ')}`);
|
|
98
99
|
};
|
|
99
100
|
export const fillSingleProject = async (props) => {
|
|
101
|
+
// The offline `--current-branch` probe needs no project at all and runs with no
|
|
102
|
+
// API client (auth was skipped), so resolving a single project here would both
|
|
103
|
+
// hit the network and dereference a null client. Skip it entirely.
|
|
104
|
+
if (isCurrentBranchProbe(props)) {
|
|
105
|
+
return props;
|
|
106
|
+
}
|
|
100
107
|
if (props.projectId) {
|
|
101
108
|
return { ...props, projectId: props.projectId };
|
|
102
109
|
}
|
|
@@ -126,9 +133,9 @@ export const fillSingleProject = async (props) => {
|
|
|
126
133
|
}
|
|
127
134
|
catch (error) {
|
|
128
135
|
// If the API error is about missing org_id, provide a user-friendly message
|
|
129
|
-
if (
|
|
130
|
-
error.
|
|
131
|
-
error.
|
|
136
|
+
if (isNeonApiError(error) &&
|
|
137
|
+
error.status === 400 &&
|
|
138
|
+
messageFromBody(error.data)?.includes('org_id is required')) {
|
|
132
139
|
throw new Error('Multiple projects found, please provide one with the --project-id option');
|
|
133
140
|
}
|
|
134
141
|
throw error;
|