neon 3.6.0 → 4.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 +12 -2
- package/dist/api.js +1 -1
- package/dist/commands/neon_auth.js +27 -49
- package/dist/utils/inspect_queries.js +51 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ Alternatively, you can authenticate a connection with a Neon API key using the `
|
|
|
62
62
|
neon projects list --api-key <neon_api_key>
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
For information about obtaining an Neon API key, see [Authentication](https://
|
|
65
|
+
For information about obtaining an Neon API key, see [Authentication](https://neon.com/docs/reference/api/get-started), in the _Neon API Reference_.
|
|
66
66
|
|
|
67
67
|
## Project and branch creation
|
|
68
68
|
|
|
@@ -766,6 +766,15 @@ neon snapshots schedule set --branch main --schedule '[{"frequency":"weekly","da
|
|
|
766
766
|
|
|
767
767
|
All sub-commands honor the [global options](#global-options), including `--output json|yaml|table`.
|
|
768
768
|
|
|
769
|
+
## Database diagnostics (`inspect`)
|
|
770
|
+
|
|
771
|
+
`neon inspect db stalled-queries` takes a read-only snapshot of active queries that have run for more than 30 seconds and groups parallel workers with their leader. Table output shows duration, wait event, blocking pids, role, query group, and query. `--output json` adds timestamps, query IDs, pids, database, and the rest of the row. A blocking pid can belong to an idle-in-transaction backend this command does not list; `neon inspect db locks` shows lock holders.
|
|
772
|
+
|
|
773
|
+
```bash
|
|
774
|
+
neon inspect db stalled-queries
|
|
775
|
+
neon inspect db stalled-queries --output json
|
|
776
|
+
```
|
|
777
|
+
|
|
769
778
|
## Logs (`logs`)
|
|
770
779
|
|
|
771
780
|
`neon logs` reads the log records the services on a branch emit — Neon Functions, object storage, and Postgres computes. **Logs require Neon Platform Beta and are currently available only for projects in `aws-us-east-2`.**
|
|
@@ -1078,6 +1087,7 @@ Id Name Project Created At Last Used At Last
|
|
|
1078
1087
|
| [roles](https://neon.com/docs/reference/cli-roles) | `list`, `create`, `delete` | Manage roles |
|
|
1079
1088
|
| [operations](https://neon.com/docs/reference/cli-operations) | `list` | Manage operations |
|
|
1080
1089
|
| logs | `query`, `fields`, `field-values` | Query branch logs (Beta) |
|
|
1090
|
+
| inspect | `db stalled-queries` | Inspect Postgres diagnostics |
|
|
1081
1091
|
| snapshots | `list`, `get`, `create`, `update`, `delete`, `restore`, `finalize`, `schedule get`, `schedule set` | Manage snapshots |
|
|
1082
1092
|
| [connection-string](https://neon.com/docs/reference/cli-connection-string) | | Get connection string |
|
|
1083
1093
|
| [psql](https://neon.com/docs/reference/cli-psql) | | Connect to a database via psql |
|
|
@@ -1141,7 +1151,7 @@ Global options are supported with any Neon CLI command.
|
|
|
1141
1151
|
|
|
1142
1152
|
- <a id="api-key"></a>`--api-key`
|
|
1143
1153
|
|
|
1144
|
-
Specifies your Neon API key. You can authenticate using a Neon API key when running a Neon CLI command instead of using `neon auth`. For information about obtaining an Neon API key, see [Authentication](https://
|
|
1154
|
+
Specifies your Neon API key. You can authenticate using a Neon API key when running a Neon CLI command instead of using `neon auth`. For information about obtaining an Neon API key, see [Authentication](https://neon.com/docs/reference/api/get-started), in the _Neon API Reference_.
|
|
1145
1155
|
|
|
1146
1156
|
```bash
|
|
1147
1157
|
neon <command> --api-key <neon_api_key>
|
package/dist/api.js
CHANGED
|
@@ -796,7 +796,7 @@ const getApiClient = ({ apiKey, apiHost, requestTimeoutMs = REQUEST_TIMEOUT_MS }
|
|
|
796
796
|
},
|
|
797
797
|
body: data
|
|
798
798
|
})),
|
|
799
|
-
|
|
799
|
+
sendNeonAuthEmailProviderTest: (projectId, branchId, data) => call(() => raw.sendNeonAuthEmailProviderTest({
|
|
800
800
|
client,
|
|
801
801
|
path: {
|
|
802
802
|
project_id: projectId,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { codeFromBody, isNeonApiError, retryOnLock } from "../api.js";
|
|
3
3
|
import { writer } from "../writer.js";
|
|
4
|
+
import { noPassthrough, single } from "../utils/flags.js";
|
|
4
5
|
import { NeonAuthEmailVerificationMethod, NeonAuthOauthProviderId, NeonAuthOauthProviderType, NeonAuthSupportedAuthProvider } from "../utils/api_enums.js";
|
|
5
6
|
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
6
7
|
import chalk from "chalk";
|
|
@@ -241,43 +242,17 @@ const builder = (argv) => {
|
|
|
241
242
|
}
|
|
242
243
|
}), async (args) => {
|
|
243
244
|
await emailProviderUpdate(args);
|
|
244
|
-
}).command("test", "Send a test
|
|
245
|
-
"
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
port: {
|
|
256
|
-
describe: "SMTP port",
|
|
257
|
-
type: "number",
|
|
258
|
-
demandOption: true
|
|
259
|
-
},
|
|
260
|
-
username: {
|
|
261
|
-
describe: "SMTP username",
|
|
262
|
-
type: "string",
|
|
263
|
-
demandOption: true
|
|
264
|
-
},
|
|
265
|
-
password: {
|
|
266
|
-
describe: "SMTP password",
|
|
267
|
-
type: "string",
|
|
268
|
-
demandOption: true
|
|
269
|
-
},
|
|
270
|
-
"sender-email": {
|
|
271
|
-
describe: "Sender email address",
|
|
272
|
-
type: "string",
|
|
273
|
-
demandOption: true
|
|
274
|
-
},
|
|
275
|
-
"sender-name": {
|
|
276
|
-
describe: "Sender display name",
|
|
277
|
-
type: "string",
|
|
278
|
-
demandOption: true
|
|
279
|
-
}
|
|
280
|
-
}), async (args) => {
|
|
245
|
+
}).command("test", "Send a test message through the saved custom SMTP provider", (yargs) => yargs.options({ "recipient-email": {
|
|
246
|
+
describe: "Email address to deliver the test message to",
|
|
247
|
+
type: "string",
|
|
248
|
+
demandOption: true,
|
|
249
|
+
coerce: single("recipient-email", { required: true })
|
|
250
|
+
} }).strict().check(noPassthrough("neon-auth config email-provider test")).epilogue([
|
|
251
|
+
"Save the provider first with",
|
|
252
|
+
"`neon neon-auth config email-provider update --type standard`.",
|
|
253
|
+
"A shared provider, a missing configuration, or a",
|
|
254
|
+
"non-Better-Auth integration is rejected by the API."
|
|
255
|
+
].join("\n")), async (args) => {
|
|
281
256
|
await emailProviderTest(args);
|
|
282
257
|
});
|
|
283
258
|
}).command("organization", "Manage organization plugin settings", (yargs) => {
|
|
@@ -663,18 +638,21 @@ const emailProviderUpdate = async (props) => {
|
|
|
663
638
|
};
|
|
664
639
|
const emailProviderTest = async (props) => {
|
|
665
640
|
const branchId = await resolveBranch(props);
|
|
666
|
-
const { data } = await props.apiClient.
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
641
|
+
const { data } = await props.apiClient.sendNeonAuthEmailProviderTest(props.projectId, branchId, { recipient_email: props.recipientEmail });
|
|
642
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
643
|
+
writer(props).end(data, { fields: TEST_EMAIL_FIELDS });
|
|
644
|
+
if (!data.success) throw new Error(emailProviderTestFailureMessage(props.recipientEmail, data.error_message));
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (data.success) {
|
|
648
|
+
printMessage(`Test email dispatched to ${props.recipientEmail} using the custom SMTP provider saved on this branch.`);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
throw new Error(emailProviderTestFailureMessage(props.recipientEmail, data.error_message));
|
|
652
|
+
};
|
|
653
|
+
const emailProviderTestFailureMessage = (recipientEmail, errorMessage) => {
|
|
654
|
+
const header = `Test email could NOT be sent to ${recipientEmail} using the custom SMTP provider saved on this branch.`;
|
|
655
|
+
return errorMessage ? `${header}\nUpstream error: ${errorMessage}` : header;
|
|
678
656
|
};
|
|
679
657
|
const printOrganizationEntries = (data) => [
|
|
680
658
|
["Enabled: ", String(data.enabled)],
|
|
@@ -111,6 +111,57 @@ const INSPECT_QUERIES = {
|
|
|
111
111
|
ORDER BY now() - query_start DESC;
|
|
112
112
|
`
|
|
113
113
|
},
|
|
114
|
+
"stalled-queries": {
|
|
115
|
+
describe: "Active queries running longer than 30 seconds with parallel workers, waits, and blockers (compute-wide)",
|
|
116
|
+
scope: "compute",
|
|
117
|
+
fields: [
|
|
118
|
+
"duration",
|
|
119
|
+
"wait_event",
|
|
120
|
+
"blocking_pids",
|
|
121
|
+
"role",
|
|
122
|
+
"query_group",
|
|
123
|
+
"query"
|
|
124
|
+
],
|
|
125
|
+
emptyMessage: "No active queries running longer than 30 seconds.",
|
|
126
|
+
sql: `
|
|
127
|
+
WITH activity AS MATERIALIZED (
|
|
128
|
+
SELECT *
|
|
129
|
+
FROM pg_stat_activity
|
|
130
|
+
WHERE state = 'active'
|
|
131
|
+
AND backend_type IN ('client backend', 'parallel worker')
|
|
132
|
+
AND pid <> pg_backend_pid()
|
|
133
|
+
),
|
|
134
|
+
stalled_groups AS (
|
|
135
|
+
SELECT DISTINCT COALESCE(leader_pid, pid) AS query_group
|
|
136
|
+
FROM activity
|
|
137
|
+
WHERE query_start <= statement_timestamp() - interval '30 seconds'
|
|
138
|
+
)
|
|
139
|
+
SELECT
|
|
140
|
+
statement_timestamp() AS observed_at,
|
|
141
|
+
a.query_start,
|
|
142
|
+
g.query_group,
|
|
143
|
+
a.pid,
|
|
144
|
+
a.leader_pid,
|
|
145
|
+
CASE
|
|
146
|
+
WHEN a.leader_pid IS NULL THEN 'leader'
|
|
147
|
+
ELSE 'worker'
|
|
148
|
+
END AS role,
|
|
149
|
+
a.backend_type,
|
|
150
|
+
a.datname AS database,
|
|
151
|
+
a.application_name,
|
|
152
|
+
a.query_id::text AS query_id,
|
|
153
|
+
a.state,
|
|
154
|
+
a.wait_event_type,
|
|
155
|
+
a.wait_event,
|
|
156
|
+
array_to_string(pg_blocking_pids(a.pid), ',') AS blocking_pids,
|
|
157
|
+
(statement_timestamp() - a.query_start)::text AS duration,
|
|
158
|
+
a.query
|
|
159
|
+
FROM activity a
|
|
160
|
+
JOIN stalled_groups g
|
|
161
|
+
ON g.query_group = COALESCE(a.leader_pid, a.pid)
|
|
162
|
+
ORDER BY g.query_group, a.leader_pid NULLS FIRST, a.pid;
|
|
163
|
+
`
|
|
164
|
+
},
|
|
114
165
|
locks: {
|
|
115
166
|
describe: "Locks held with the acquiring query and its age (pg_locks + pg_stat_activity)",
|
|
116
167
|
scope: "database",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"yaml": "^2.9.0",
|
|
64
64
|
"yargs": "17.7.2",
|
|
65
65
|
"yoctocolors": "^2.1.2",
|
|
66
|
-
"@neon/
|
|
67
|
-
"@neon/
|
|
68
|
-
"@neon/config
|
|
66
|
+
"@neon/sdk": "2.2.0",
|
|
67
|
+
"@neon/config-runtime": "1.0.2",
|
|
68
|
+
"@neon/config": "1.0.2"
|
|
69
69
|
},
|
|
70
70
|
"optionalDependencies": {
|
|
71
71
|
"@napi-rs/keyring": "1.3.0",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"typescript": "^5.9.0",
|
|
98
98
|
"vitest": "^3.0.9",
|
|
99
99
|
"@neon-internals/cli-core": "0.0.0",
|
|
100
|
-
"@neon-internals/env-core": "0.0.
|
|
100
|
+
"@neon-internals/env-core": "0.0.2",
|
|
101
101
|
"@neon/e2e-harness": "0.0.0"
|
|
102
102
|
},
|
|
103
103
|
"publishConfig": {
|