neon 8.8.0 → 8.10.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 -0
- package/dist/_chunks/{credential_io-ByNuojoE.js → credential_io-BS-0Pmwc.js} +9 -1
- package/dist/analytics.js +22 -16
- package/dist/anonymous_id.js +59 -0
- package/dist/auth.js +1 -1
- package/dist/auth_recovery.js +1 -1
- package/dist/commands/auth.js +12 -16
- package/dist/commands/dev.js +3 -3
- package/dist/commands/docs.js +157 -0
- package/dist/commands/env.js +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/manifest.js +5 -0
- package/dist/commands/profile.js +1 -1
- package/dist/commands/subscribe.js +1 -1
- package/dist/context.js +4 -2
- package/dist/credential_io.js +1 -1
- package/dist/init/detect.js +1 -1
- package/dist/keyring.js +5 -1
- package/dist/psql/io/history.js +1 -1
- package/dist/refresh_lock.js +1 -1
- package/dist/retire_credential.js +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1005,6 +1005,18 @@ neon feedback --message "The branch docs were unclear" --output json
|
|
|
1005
1005
|
|
|
1006
1006
|
Default table output is `Feedback received. Thank you!`. `--output json` and `--output yaml` print `{ "received": true }`. Messages are limited to 10,000 characters. Do not include passwords, API keys, connection strings, or other secrets.
|
|
1007
1007
|
|
|
1008
|
+
## Search neon.com (`docs search`)
|
|
1009
|
+
|
|
1010
|
+
`neon docs search <query>` searches neon.com: the docs, API reference, PostgreSQL tutorial, guides, FAQs, changelog, and blog. It does not log in and does not use your Neon account. It uses the same search as the neon.com search box.
|
|
1011
|
+
|
|
1012
|
+
```bash
|
|
1013
|
+
neon docs search "reset a branch"
|
|
1014
|
+
neon docs search "create branch" --category api --category docs
|
|
1015
|
+
neon docs search "pg_dump" --limit 5 --output json
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
`--category` limits results to `docs`, `api`, `postgresql`, `guides`, `faqs`, `blog`, or `changelog`; repeat it for more than one. `--limit` is 1 to 40, default 10. Default table output is one row per page: title, the section that matched, and URL. `--output json` and `--output yaml` print an array of hits with `title`, `url`, `category`, `breadcrumb`, `heading`, and `excerpt`, in rank order. No match prints `No results for "<query>".` and exits 0 (`[]` in JSON).
|
|
1019
|
+
|
|
1008
1020
|
## Install the Neon plugin (`plugins`)
|
|
1009
1021
|
|
|
1010
1022
|
`neon plugins` installs the Neon agent plugin (`neon-postgres`) by running `npx plugins add`. It does not call the Neon API.
|
|
@@ -200,6 +200,12 @@ var KeyringUnavailableError = class extends Error {
|
|
|
200
200
|
this.name = "KeyringUnavailableError";
|
|
201
201
|
}
|
|
202
202
|
};
|
|
203
|
+
var KeyringValueTooLargeError = class extends Error {
|
|
204
|
+
constructor(profile, actual, maximum) {
|
|
205
|
+
super(`Credentials for profile "${profile}" are ${actual} characters, and this OS keyring stores at most ${maximum}. Run \`neon login --profile ${profile}\` without \`--keyring\` to keep the credential in an owner-only file. If this profile already uses the keyring, remove it first with \`neon profile remove ${profile} --yes\`.`);
|
|
206
|
+
this.name = "KeyringValueTooLargeError";
|
|
207
|
+
}
|
|
208
|
+
};
|
|
203
209
|
var KeyringUnreadableError = class extends Error {
|
|
204
210
|
constructor(profile) {
|
|
205
211
|
const replace = `\`neon login --profile ${profile}\``;
|
|
@@ -248,8 +254,10 @@ const createCredentialStore = (dir, options = {}) => {
|
|
|
248
254
|
} catch {
|
|
249
255
|
previous = null;
|
|
250
256
|
}
|
|
257
|
+
const serialized = JSON.stringify(credentials);
|
|
258
|
+
if (kr.maxPasswordCodeUnits !== void 0 && serialized.length > kr.maxPasswordCodeUnits) throw new KeyringValueTooLargeError(profile, serialized.length, kr.maxPasswordCodeUnits);
|
|
251
259
|
try {
|
|
252
|
-
kr.set(KEYRING_SERVICE, account,
|
|
260
|
+
kr.set(KEYRING_SERVICE, account, serialized);
|
|
253
261
|
} catch {
|
|
254
262
|
throw new KeyringUnavailableError();
|
|
255
263
|
}
|
package/dist/analytics.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { a as OAUTH, t as storeFor } from "./_chunks/credential_io-
|
|
1
|
+
import { a as OAUTH, t as storeFor } from "./_chunks/credential_io-BS-0Pmwc.js";
|
|
2
2
|
import { getCliAgent, getGithubEnvVars, isCi } from "./env.js";
|
|
3
3
|
import { log } from "./log.js";
|
|
4
|
+
import { getOrCreateAnonymousId } from "./anonymous_id.js";
|
|
4
5
|
import pkg_default from "./pkg.js";
|
|
5
6
|
import { UnreadableResponseError, getApiClient, isNeonApiError } from "./api.js";
|
|
6
7
|
import { getAuthContext } from "./auth_context.js";
|
|
@@ -19,16 +20,17 @@ const WRITE_KEY = "3SQXn5ejjXWLEJ8xU2PRYhAotLtTaeeV";
|
|
|
19
20
|
const hasCurrentBranchArgv = () => process.argv.includes("--current-branch");
|
|
20
21
|
const ANONYMOUS = "anonymous";
|
|
21
22
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* rejecting it, so the substitution has to happen here. `""` is falsy but not nullish, which
|
|
27
|
-
* is why the fallback has to be `||`.
|
|
23
|
+
* The identity to send an event under. A run nothing identified (an empty `userId`, which
|
|
24
|
+
* Segment would forward as-is) is the shared `"anonymous"` user, as it always was, so existing
|
|
25
|
+
* queries on it keep working. The stored anonymous id goes with every event, signed in or not,
|
|
26
|
+
* so the signed-out events of an installation can be joined to its account once a user is known.
|
|
28
27
|
*
|
|
29
28
|
* Exported for tests.
|
|
30
29
|
*/
|
|
31
|
-
const
|
|
30
|
+
const analyticsIdentity = (userId, anonymousId) => ({
|
|
31
|
+
userId: userId || ANONYMOUS,
|
|
32
|
+
...anonymousId ? { anonymousId } : {}
|
|
33
|
+
});
|
|
32
34
|
/**
|
|
33
35
|
* The account an invocation that presented no API key may claim, which is nothing at all
|
|
34
36
|
* unless stored credentials named a user.
|
|
@@ -77,10 +79,13 @@ const telemetryCredential = (authContext, apiKey, defaultCredentialsPath) => {
|
|
|
77
79
|
let client;
|
|
78
80
|
let clientInitialized = false;
|
|
79
81
|
let errorEventContext;
|
|
82
|
+
/** Set with the client, so it exists only when analytics is on. */
|
|
83
|
+
let anonymousId;
|
|
80
84
|
/** Lets tests send to a local collector; the CLI itself always uses track.neon.tech. */
|
|
81
|
-
const useAnalyticsClientForTests = (analytics) => {
|
|
85
|
+
const useAnalyticsClientForTests = (analytics, testAnonymousId) => {
|
|
82
86
|
client = analytics;
|
|
83
87
|
clientInitialized = true;
|
|
88
|
+
anonymousId = testAnonymousId;
|
|
84
89
|
};
|
|
85
90
|
/**
|
|
86
91
|
* Phase 1: Run before validation so the Segment client exists if any
|
|
@@ -97,7 +102,8 @@ const initAnalyticsClientMiddleware = (args) => {
|
|
|
97
102
|
host: "https://track.neon.tech"
|
|
98
103
|
});
|
|
99
104
|
log.debug("Initialized CLI analytics client");
|
|
100
|
-
|
|
105
|
+
anonymousId = args.configDir ? getOrCreateAnonymousId(args.configDir) : void 0;
|
|
106
|
+
client.identify(analyticsIdentity(void 0, anonymousId));
|
|
101
107
|
};
|
|
102
108
|
/**
|
|
103
109
|
* The attribution of the current attempt. A 401 retry runs the middleware again and starts a
|
|
@@ -179,9 +185,9 @@ const analyticsMiddleware = (args) => {
|
|
|
179
185
|
})();
|
|
180
186
|
queue = queue.then(() => lookup);
|
|
181
187
|
enqueue((analytics, { userId }) => {
|
|
182
|
-
analytics.identify(
|
|
188
|
+
analytics.identify(analyticsIdentity(userId, anonymousId));
|
|
183
189
|
analytics.track({
|
|
184
|
-
userId
|
|
190
|
+
...analyticsIdentity(userId, anonymousId),
|
|
185
191
|
event: "CLI Started",
|
|
186
192
|
properties: startedProperties,
|
|
187
193
|
context: { direct: true }
|
|
@@ -266,7 +272,7 @@ const sendError = (err, errCode) => {
|
|
|
266
272
|
enqueue((analytics, { userId }) => {
|
|
267
273
|
analytics.track({
|
|
268
274
|
event: "CLI Error",
|
|
269
|
-
userId
|
|
275
|
+
...analyticsIdentity(userId, anonymousId),
|
|
270
276
|
properties
|
|
271
277
|
});
|
|
272
278
|
});
|
|
@@ -284,7 +290,7 @@ const trackEvent = (event, properties) => {
|
|
|
284
290
|
enqueue((analytics, { userId }) => {
|
|
285
291
|
analytics.track({
|
|
286
292
|
event,
|
|
287
|
-
userId
|
|
293
|
+
...analyticsIdentity(userId, anonymousId),
|
|
288
294
|
properties
|
|
289
295
|
});
|
|
290
296
|
});
|
|
@@ -300,7 +306,7 @@ const trackCommandSuccess = (args) => {
|
|
|
300
306
|
enqueue((analytics, { userId, accountId, authMethod, authData }) => {
|
|
301
307
|
analytics.track({
|
|
302
308
|
event: "cli_command_success",
|
|
303
|
-
userId
|
|
309
|
+
...analyticsIdentity(userId, anonymousId),
|
|
304
310
|
properties: {
|
|
305
311
|
...properties,
|
|
306
312
|
accountId,
|
|
@@ -348,4 +354,4 @@ const getAnalyticsEventProperties = (args) => ({
|
|
|
348
354
|
githubEnvVars: getGithubEnvVars(process.env)
|
|
349
355
|
});
|
|
350
356
|
//#endregion
|
|
351
|
-
export {
|
|
357
|
+
export { analyticsIdentity, analyticsMiddleware, closeAnalytics, commandSuccessProperties, getAnalyticsEventProperties, getErrorAnalyticsEventProperties, initAnalyticsClientMiddleware, recordCommandSuccessExtras, recordScaffoldedTemplate, sendError, storedCredentialAttribution, takeCommandSuccessExtras, telemetryCredential, trackCommandSuccess, trackEvent, useAnalyticsClientForTests };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { t as writeSecretFile } from "./_chunks/secure_file-BucZj4yQ.js";
|
|
2
|
+
import { isCi } from "./env.js";
|
|
3
|
+
import { log } from "./log.js";
|
|
4
|
+
import { isEexist } from "./refresh_lock.js";
|
|
5
|
+
import { linkSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
//#region src/anonymous_id.ts
|
|
9
|
+
const ANONYMOUS_ID_FILE = "anonymous_id";
|
|
10
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
11
|
+
const readStored = (path) => {
|
|
12
|
+
try {
|
|
13
|
+
const stored = readFileSync(path, "utf8").trim();
|
|
14
|
+
return UUID.test(stored) ? stored : void 0;
|
|
15
|
+
} catch {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Names this installation in analytics before sign-in. Commands that know the user send it
|
|
21
|
+
* alongside `userId`, so earlier events can be linked to the account.
|
|
22
|
+
*
|
|
23
|
+
* Not stored in CI, where the config directory doesn't outlive the run and every run would
|
|
24
|
+
* mint a new installation. Call only once analytics is known to be enabled.
|
|
25
|
+
*/
|
|
26
|
+
const getOrCreateAnonymousId = (configDir) => {
|
|
27
|
+
if (isCi()) return;
|
|
28
|
+
const path = join(configDir, ANONYMOUS_ID_FILE);
|
|
29
|
+
try {
|
|
30
|
+
const stored = readStored(path);
|
|
31
|
+
if (stored) return stored;
|
|
32
|
+
mkdirSync(configDir, { recursive: true });
|
|
33
|
+
const id = randomUUID();
|
|
34
|
+
const temporary = `${path}.${id}.tmp`;
|
|
35
|
+
writeFileSync(temporary, `${id}\n`, {
|
|
36
|
+
mode: 384,
|
|
37
|
+
flag: "wx"
|
|
38
|
+
});
|
|
39
|
+
try {
|
|
40
|
+
linkSync(temporary, path);
|
|
41
|
+
return id;
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (!isEexist(err)) throw err;
|
|
44
|
+
const winner = readStored(path);
|
|
45
|
+
if (winner) return winner;
|
|
46
|
+
writeSecretFile(path, `${id}\n`);
|
|
47
|
+
return id;
|
|
48
|
+
} finally {
|
|
49
|
+
try {
|
|
50
|
+
unlinkSync(temporary);
|
|
51
|
+
} catch {}
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
log.debug("Could not store the anonymous analytics id: %s", err);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
//#endregion
|
|
59
|
+
export { ANONYMOUS_ID_FILE, getOrCreateAnonymousId };
|
package/dist/auth.js
CHANGED
|
@@ -221,4 +221,4 @@ const auth = async ({ oauthHost, clientId, allowUnsafeTls }) => {
|
|
|
221
221
|
});
|
|
222
222
|
};
|
|
223
223
|
//#endregion
|
|
224
|
-
export { AuthRefreshError, auth, classifyRefreshFailure, defaultClientID, refreshToken, revokeToken };
|
|
224
|
+
export { AuthRefreshError, NEONCTL_SCOPES, auth, classifyRefreshFailure, defaultClientID, refreshToken, revokeToken };
|
package/dist/auth_recovery.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as storeFor } from "./_chunks/credential_io-
|
|
1
|
+
import { t as storeFor } from "./_chunks/credential_io-BS-0Pmwc.js";
|
|
2
2
|
import { log } from "./log.js";
|
|
3
3
|
import { authFailureMessage, getAuthContext, locationFromContext } from "./auth_context.js";
|
|
4
4
|
import { AuthRefreshError } from "./auth.js";
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
|
-
import { a as OAUTH, i as API_KEY, l as interpretCredentials, s as credentialLabel, t as storeFor } from "../_chunks/credential_io-
|
|
2
|
+
import { a as OAUTH, i as API_KEY, l as interpretCredentials, s as credentialLabel, t as storeFor } from "../_chunks/credential_io-BS-0Pmwc.js";
|
|
3
3
|
import { isCi } from "../env.js";
|
|
4
4
|
import { log } from "../log.js";
|
|
5
|
+
import { withExclusiveLock } from "../refresh_lock.js";
|
|
5
6
|
import { getApiClient, isNeonApiError } from "../api.js";
|
|
6
7
|
import { setAuthContext } from "../auth_context.js";
|
|
7
8
|
import { a as isOwnedCredentialPath } from "../_chunks/paths-DMq0Lt7a.js";
|
|
8
|
-
import { currentContextFile, isAskCommand, isClaimCommand, isConfigAdd, isConfigInit, isCurrentBranchProbe, isFeedbackCommand, isGitLocalCommand, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, isUnfollowedGitHookSync, readContextFile } from "../context.js";
|
|
9
|
+
import { currentContextFile, isAskCommand, isClaimCommand, isConfigAdd, isConfigInit, isCurrentBranchProbe, isDocsCommand, isFeedbackCommand, isGitLocalCommand, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, isUnfollowedGitHookSync, readContextFile } from "../context.js";
|
|
9
10
|
import { _ as upsertProfile, d as newProfileLocation, g as selectProfileName, h as resolveProfile, i as assertValidProfileName, l as locationForName, m as readProfiles, n as KEYRING_CREDENTIALS, p as profilesUsingPath, r as assertProfilesUsable, s as isKeyringPointer, t as DEFAULT_PROFILE, u as locationOf } from "../_chunks/profiles-DIB1av2Q.js";
|
|
10
11
|
import { extendTokenSet } from "../utils/auth.js";
|
|
11
12
|
import { AuthRefreshError, auth, classifyRefreshFailure, refreshToken } from "../auth.js";
|
|
12
13
|
import { i as selectCredential, n as displacedProfileWarning, t as credentialInputs } from "../_chunks/auth_selection-YX5_KZgD.js";
|
|
13
14
|
import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
|
|
14
15
|
import { assertionHasExpired, claimableCredentialsExist, claimableCredentialsPath, readLinkedClaimableCredentials, shouldUseClaimableCredentials } from "../claimable/state.js";
|
|
15
|
-
import { withExclusiveLock } from "../refresh_lock.js";
|
|
16
16
|
import { readOutgoingCredential, retirePreviousCredential } from "../retire_credential.js";
|
|
17
17
|
import { getCliName } from "../utils/cli_name.js";
|
|
18
18
|
import { helpEpilogue, helpWidth, wrapHelpText } from "../utils/help_text.js";
|
|
@@ -136,20 +136,16 @@ const authFlow = async ({ configDir, oauthHost, clientId, apiHost, forceAuth, "f
|
|
|
136
136
|
return tokenSet.access_token || "";
|
|
137
137
|
};
|
|
138
138
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
139
|
+
* One write, after the lookup: a second write that adds `user_id` could fail the keyring size
|
|
140
|
+
* check after the first had already replaced the previous credential.
|
|
141
141
|
*/
|
|
142
142
|
const persistNewSession = async (at, credentials, apiClient, configDir) => {
|
|
143
|
-
const stored = oauthCredentialsFromTokenSet(credentials, null);
|
|
144
|
-
writeOAuthCredentials(configDir, at, stored);
|
|
145
143
|
const identity = await fetchIdentity(apiClient);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
});
|
|
152
|
-
}
|
|
144
|
+
const stored = oauthCredentialsFromTokenSet(credentials, null);
|
|
145
|
+
writeOAuthCredentials(configDir, at, identity.id ? {
|
|
146
|
+
...stored,
|
|
147
|
+
user_id: identity.id
|
|
148
|
+
} : stored);
|
|
153
149
|
return identity;
|
|
154
150
|
};
|
|
155
151
|
const fetchIdentity = async (apiClient) => {
|
|
@@ -248,8 +244,8 @@ const refreshStoredCredentials = async (at, props) => {
|
|
|
248
244
|
const oauthCredentialsFromTokenSet = (tokenSet, previous) => {
|
|
249
245
|
const refreshTokenValue = typeof tokenSet.refresh_token === "string" && tokenSet.refresh_token !== "" ? tokenSet.refresh_token : previous?.refresh_token;
|
|
250
246
|
const stored = {
|
|
251
|
-
...tokenSet,
|
|
252
247
|
type: OAUTH,
|
|
248
|
+
...typeof tokenSet.access_token === "string" && tokenSet.access_token !== "" ? { access_token: tokenSet.access_token } : {},
|
|
253
249
|
expires_at: tokenSet.expires_at,
|
|
254
250
|
...typeof refreshTokenValue === "string" && refreshTokenValue !== "" ? { refresh_token: refreshTokenValue } : {}
|
|
255
251
|
};
|
|
@@ -304,7 +300,7 @@ const ensureAuth = async (props) => {
|
|
|
304
300
|
if (isInspectDbUrl(props)) return;
|
|
305
301
|
if (props._[0] === "open") return;
|
|
306
302
|
if (isSkillsCommand(props) || isPluginsCommand(props)) return;
|
|
307
|
-
if (isAskCommand(props) || isFeedbackCommand(props)) return;
|
|
303
|
+
if (isAskCommand(props) || isFeedbackCommand(props) || isDocsCommand(props)) return;
|
|
308
304
|
if (props._[0] === "init") {
|
|
309
305
|
selectCredential({
|
|
310
306
|
...credentialInputs(),
|
package/dist/commands/dev.js
CHANGED
|
@@ -4,12 +4,12 @@ import { isClaimableEnvTarget } from "../claimable/state.js";
|
|
|
4
4
|
import { getCliName } from "../utils/cli_name.js";
|
|
5
5
|
import { defaultDevEnvFiles, readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
6
6
|
import { l as functionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
|
|
7
|
-
import { branchIdResolve } from "../utils/enrichers.js";
|
|
8
|
-
import { bundleEntry } from "../utils/esbuild.js";
|
|
9
7
|
import { a as resolveDevEnv } from "../_chunks/env-BNm69Qh8.js";
|
|
10
|
-
import { envFlag } from "./config.js";
|
|
11
8
|
import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
|
|
12
9
|
import { resolveWatchInputs } from "../dev/inputs.js";
|
|
10
|
+
import { branchIdResolve } from "../utils/enrichers.js";
|
|
11
|
+
import { bundleEntry } from "../utils/esbuild.js";
|
|
12
|
+
import { envFlag } from "./config.js";
|
|
13
13
|
import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { isNetworkError } from "../errors.js";
|
|
3
|
+
import { writer } from "../writer.js";
|
|
4
|
+
import { single } from "../utils/flags.js";
|
|
5
|
+
//#region src/commands/docs.ts
|
|
6
|
+
var docs_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
DEFAULT_SITE_SEARCH_URL: () => DEFAULT_SITE_SEARCH_URL,
|
|
8
|
+
MAX_LIMIT: () => 40,
|
|
9
|
+
MAX_QUERY_LENGTH: () => 500,
|
|
10
|
+
SITE_SEARCH_CATEGORIES: () => SITE_SEARCH_CATEGORIES,
|
|
11
|
+
builder: () => builder,
|
|
12
|
+
command: () => command,
|
|
13
|
+
describe: () => describe,
|
|
14
|
+
parseSearchHits: () => parseSearchHits,
|
|
15
|
+
resolveSiteSearchUrl: () => resolveSiteSearchUrl
|
|
16
|
+
});
|
|
17
|
+
const DEFAULT_SITE_SEARCH_URL = "https://br-frosty-cell-a5smzg39-assistant.compute.c-1.us-east-2.aws.neon.tech/site-search";
|
|
18
|
+
const SITE_SEARCH_CATEGORIES = [
|
|
19
|
+
"docs",
|
|
20
|
+
"api",
|
|
21
|
+
"postgresql",
|
|
22
|
+
"guides",
|
|
23
|
+
"faqs",
|
|
24
|
+
"blog",
|
|
25
|
+
"changelog"
|
|
26
|
+
];
|
|
27
|
+
const MAX_QUERY_LENGTH = 500;
|
|
28
|
+
const MAX_LIMIT = 40;
|
|
29
|
+
const DEFAULT_LIMIT = 10;
|
|
30
|
+
const SEARCH_TIMEOUT_MS = 1e4;
|
|
31
|
+
const command = "docs";
|
|
32
|
+
const describe = "Search neon.com docs, guides, and more";
|
|
33
|
+
const SEARCH_DESCRIBE = "Search neon.com: docs, API reference, PostgreSQL tutorial, guides, FAQs, changelog, and blog";
|
|
34
|
+
const builder = (argv) => argv.usage("$0 docs <sub-command> [options]").command("search <query>", SEARCH_DESCRIBE, (yargs) => yargs.positional("query", {
|
|
35
|
+
describe: "What to search for",
|
|
36
|
+
type: "string"
|
|
37
|
+
}).options({
|
|
38
|
+
category: {
|
|
39
|
+
describe: "Only return pages in these categories (repeatable)",
|
|
40
|
+
type: "string",
|
|
41
|
+
array: true,
|
|
42
|
+
choices: SITE_SEARCH_CATEGORIES
|
|
43
|
+
},
|
|
44
|
+
limit: {
|
|
45
|
+
describe: `Maximum number of results (1-40)`,
|
|
46
|
+
type: "number",
|
|
47
|
+
default: DEFAULT_LIMIT
|
|
48
|
+
},
|
|
49
|
+
url: {
|
|
50
|
+
describe: "Override the site search URL",
|
|
51
|
+
type: "string",
|
|
52
|
+
hidden: true,
|
|
53
|
+
coerce: single("url")
|
|
54
|
+
}
|
|
55
|
+
}).strict().check((args) => {
|
|
56
|
+
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
57
|
+
if (query === "") throw new Error("<query> needs a value");
|
|
58
|
+
if (query.length > 500) throw new Error(`<query> must be 500 characters or fewer`);
|
|
59
|
+
const limit = args.limit;
|
|
60
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 40) throw new Error(`--limit must be a whole number from 1 to 40`);
|
|
61
|
+
return true;
|
|
62
|
+
}).example("$0 docs search \"reset a branch\"", SEARCH_DESCRIBE).example("$0 docs search \"create branch\" --category api --category docs", "Search only the API reference and the docs"), (args) => search(args));
|
|
63
|
+
function resolveSiteSearchUrl(opts) {
|
|
64
|
+
const fromFlag = opts.url?.trim();
|
|
65
|
+
if (fromFlag) return fromFlag;
|
|
66
|
+
const fromEnv = opts.envUrl?.trim();
|
|
67
|
+
if (fromEnv) return fromEnv;
|
|
68
|
+
return DEFAULT_SITE_SEARCH_URL;
|
|
69
|
+
}
|
|
70
|
+
const search = async (props) => {
|
|
71
|
+
const query = props.query.trim();
|
|
72
|
+
const hits = await searchSite({
|
|
73
|
+
url: resolveSiteSearchUrl({
|
|
74
|
+
url: props.url,
|
|
75
|
+
envUrl: process.env.NEON_SITE_SEARCH_URL
|
|
76
|
+
}),
|
|
77
|
+
query,
|
|
78
|
+
limit: props.limit,
|
|
79
|
+
categories: props.category ? [...new Set(props.category)] : void 0
|
|
80
|
+
});
|
|
81
|
+
writer(props).end(hits, {
|
|
82
|
+
fields: [
|
|
83
|
+
"title",
|
|
84
|
+
"heading",
|
|
85
|
+
"url"
|
|
86
|
+
],
|
|
87
|
+
emptyMessage: `No results for "${query}".`,
|
|
88
|
+
renderColumns: { heading: (hit) => sectionOf(hit) }
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
function sectionOf(hit) {
|
|
92
|
+
const parts = hit.heading.split(" > ");
|
|
93
|
+
return (parts[0] === hit.title ? parts.slice(1) : parts).join(" > ");
|
|
94
|
+
}
|
|
95
|
+
function isTimeout(error) {
|
|
96
|
+
return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
97
|
+
}
|
|
98
|
+
async function searchSite(opts) {
|
|
99
|
+
const signal = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
|
|
100
|
+
try {
|
|
101
|
+
const response = await fetch(opts.url, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: { "Content-Type": "application/json" },
|
|
104
|
+
body: JSON.stringify({
|
|
105
|
+
query: opts.query,
|
|
106
|
+
limit: opts.limit,
|
|
107
|
+
...opts.categories ? { categories: opts.categories } : {}
|
|
108
|
+
}),
|
|
109
|
+
signal
|
|
110
|
+
});
|
|
111
|
+
const raw = await response.text();
|
|
112
|
+
if (!response.ok) throw new Error(searchErrorMessage(raw, response.status));
|
|
113
|
+
return parseSearchHits(raw);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (isTimeout(error)) throw new Error("Neon site search did not respond in time.");
|
|
116
|
+
if (isNetworkError(error)) throw new Error("Could not reach Neon site search. Check your internet connection and try again.");
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function parseJson(raw) {
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(raw);
|
|
123
|
+
} catch {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function searchErrorMessage(raw, status) {
|
|
128
|
+
const body = parseJson(raw);
|
|
129
|
+
if (status < 500 && typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" && body.error.trim() !== "") return body.error;
|
|
130
|
+
return `Neon site search failed (HTTP ${status}).`;
|
|
131
|
+
}
|
|
132
|
+
const UNEXPECTED_RESPONSE = "Neon site search returned an unexpected response.";
|
|
133
|
+
function stringField(fields, key) {
|
|
134
|
+
const field = fields[key];
|
|
135
|
+
if (typeof field !== "string") throw new Error(UNEXPECTED_RESPONSE);
|
|
136
|
+
return field;
|
|
137
|
+
}
|
|
138
|
+
function parseSearchHits(raw) {
|
|
139
|
+
const body = parseJson(raw);
|
|
140
|
+
if (typeof body !== "object" || body === null || !("hits" in body) || !Array.isArray(body.hits)) throw new Error(UNEXPECTED_RESPONSE);
|
|
141
|
+
return body.hits.map((hit) => {
|
|
142
|
+
if (typeof hit !== "object" || hit === null) throw new Error(UNEXPECTED_RESPONSE);
|
|
143
|
+
const fields = Object.fromEntries(Object.entries(hit));
|
|
144
|
+
const breadcrumb = fields.breadcrumb;
|
|
145
|
+
if (!Array.isArray(breadcrumb) || !breadcrumb.every((part) => typeof part === "string")) throw new Error(UNEXPECTED_RESPONSE);
|
|
146
|
+
return {
|
|
147
|
+
title: stringField(fields, "title"),
|
|
148
|
+
url: stringField(fields, "url"),
|
|
149
|
+
category: stringField(fields, "category"),
|
|
150
|
+
breadcrumb,
|
|
151
|
+
heading: stringField(fields, "heading"),
|
|
152
|
+
excerpt: stringField(fields, "excerpt")
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
export { DEFAULT_SITE_SEARCH_URL, MAX_LIMIT, MAX_QUERY_LENGTH, SITE_SEARCH_CATEGORIES, builder, command, describe, parseSearchHits, resolveSiteSearchUrl, docs_exports as t };
|
package/dist/commands/env.js
CHANGED
|
@@ -7,9 +7,9 @@ import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOpt
|
|
|
7
7
|
import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
8
8
|
import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
|
|
9
9
|
import { ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagValue, ownedEnvServiceKeys, parseEnvPullKeys } from "../env_services.js";
|
|
10
|
+
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-BNm69Qh8.js";
|
|
10
11
|
import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
11
12
|
import { warnAiGateway } from "../utils/ai_gateway_notice.js";
|
|
12
|
-
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-BNm69Qh8.js";
|
|
13
13
|
import { formatPulledEnv, pulledBranch } from "./env_output.js";
|
|
14
14
|
import { existsSync } from "node:fs";
|
|
15
15
|
import chalk from "chalk";
|
package/dist/commands/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { t as databases_exports } from "./databases.js";
|
|
|
21
21
|
import { t as deploy_exports } from "./deploy.js";
|
|
22
22
|
import { t as dev_exports } from "./dev.js";
|
|
23
23
|
import { t as diff_exports } from "./diff.js";
|
|
24
|
+
import { t as docs_exports } from "./docs.js";
|
|
24
25
|
import { t as feedback_exports } from "./feedback.js";
|
|
25
26
|
import { t as functions_exports } from "./functions.js";
|
|
26
27
|
import { t as git_exports } from "./git.js";
|
|
@@ -75,6 +76,7 @@ var commands_default = [
|
|
|
75
76
|
skills_exports,
|
|
76
77
|
ask_exports,
|
|
77
78
|
feedback_exports,
|
|
79
|
+
docs_exports,
|
|
78
80
|
data_api_exports,
|
|
79
81
|
realtime_exports,
|
|
80
82
|
functions_exports,
|
|
@@ -181,6 +181,11 @@ const commandManifest = [
|
|
|
181
181
|
describe: "Send feedback to Neon",
|
|
182
182
|
load: () => import("./feedback.js")
|
|
183
183
|
},
|
|
184
|
+
{
|
|
185
|
+
command: "docs",
|
|
186
|
+
describe: "Search neon.com docs, guides, and more",
|
|
187
|
+
load: () => import("./docs.js")
|
|
188
|
+
},
|
|
184
189
|
{
|
|
185
190
|
command: "data-api",
|
|
186
191
|
describe: "Manage the Neon Data API for a database",
|
package/dist/commands/profile.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { t as writeSecretFile } from "../_chunks/secure_file-BucZj4yQ.js";
|
|
3
|
-
import { c as describeScope, d as scopeOf, l as interpretCredentials, n as KEYRING_SERVICE, o as apiKeyCredentials, r as keyringAccount, s as credentialLabel, t as storeFor } from "../_chunks/credential_io-
|
|
3
|
+
import { c as describeScope, d as scopeOf, l as interpretCredentials, n as KEYRING_SERVICE, o as apiKeyCredentials, r as keyringAccount, s as credentialLabel, t as storeFor } from "../_chunks/credential_io-BS-0Pmwc.js";
|
|
4
4
|
import { isCi } from "../env.js";
|
|
5
5
|
import { log } from "../log.js";
|
|
6
6
|
import { getApiClient, isNeonApiError } from "../api.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { writer } from "../writer.js";
|
|
2
|
-
import { branchIdFromProps } from "../utils/enrichers.js";
|
|
3
2
|
import { o as resolveNeonEnvVars } from "../_chunks/env-BNm69Qh8.js";
|
|
3
|
+
import { branchIdFromProps } from "../utils/enrichers.js";
|
|
4
4
|
import YAML from "yaml";
|
|
5
5
|
//#region src/commands/subscribe.ts
|
|
6
6
|
const builder = (argv) => argv.usage("$0 realtime subscribe \"<query>\" [options]").positional("query", {
|
package/dist/context.js
CHANGED
|
@@ -90,6 +90,8 @@ const isPluginsCommand = (args) => args._[0] === "plugins" || args._[0] === "plu
|
|
|
90
90
|
const isAskCommand = (args) => args._[0] === "ask";
|
|
91
91
|
/** Feedback is anonymous, so it must never read credentials or `.neon` context. */
|
|
92
92
|
const isFeedbackCommand = (args) => args._[0] === "feedback";
|
|
93
|
+
/** Docs search calls public neon.com search, so it needs no credentials or `.neon` context. */
|
|
94
|
+
const isDocsCommand = (args) => args._[0] === "docs";
|
|
93
95
|
/**
|
|
94
96
|
* `inspect db --db-url` talks to Postgres with that URI. Skip only when the
|
|
95
97
|
* parsed value is nonempty: empty `--db-url` still goes through project
|
|
@@ -177,7 +179,7 @@ const enrichFromContext = (args) => {
|
|
|
177
179
|
if (isProfileCommand(args)) return;
|
|
178
180
|
if (isMcpCommand(args)) return;
|
|
179
181
|
if (isSkillsCommand(args) || isPluginsCommand(args)) return;
|
|
180
|
-
if (isAskCommand(args) || isFeedbackCommand(args)) return;
|
|
182
|
+
if (isAskCommand(args) || isFeedbackCommand(args) || isDocsCommand(args)) return;
|
|
181
183
|
if (isClaimCommand(args)) return;
|
|
182
184
|
const context = readContextFile(args.contextFile);
|
|
183
185
|
if (!args.orgId) args.orgId = context.orgId;
|
|
@@ -357,4 +359,4 @@ const globToRegExp = (pattern) => {
|
|
|
357
359
|
return new RegExp(`^${source}$`);
|
|
358
360
|
};
|
|
359
361
|
//#endregion
|
|
360
|
-
export { CONTEXT_FILE, applyContext, contextBranch, currentContextFile, enrichFromContext, ensureDirectoryGitignored, ensureGitignored, gitBranchMap, gitBranchMapping, isApiKeysCommand, isAskCommand, isClaimCommand, isConfigAdd, isConfigInit, isCurrentBranchProbe, isFeedbackCommand, isGitLocalCommand, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, isUnfollowedGitHookSync, readContextFile, setContext, setGitBranchMap, setGitBranchMapping, setGitFollow, updateContextFile, walkContextFile };
|
|
362
|
+
export { CONTEXT_FILE, applyContext, contextBranch, currentContextFile, enrichFromContext, ensureDirectoryGitignored, ensureGitignored, gitBranchMap, gitBranchMapping, isApiKeysCommand, isAskCommand, isClaimCommand, isConfigAdd, isConfigInit, isCurrentBranchProbe, isDocsCommand, isFeedbackCommand, isGitLocalCommand, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, isUnfollowedGitHookSync, readContextFile, setContext, setGitBranchMap, setGitBranchMapping, setGitFollow, updateContextFile, walkContextFile };
|
package/dist/credential_io.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as storeFor } from "./_chunks/credential_io-
|
|
1
|
+
export { t as storeFor } from "./_chunks/credential_io-BS-0Pmwc.js";
|
package/dist/init/detect.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as storeFor } from "../_chunks/credential_io-
|
|
1
|
+
import { t as storeFor } from "../_chunks/credential_io-BS-0Pmwc.js";
|
|
2
2
|
import { isCi } from "../env.js";
|
|
3
3
|
import { h as resolveProfile, u as locationOf } from "../_chunks/profiles-DIB1av2Q.js";
|
|
4
4
|
import { i as selectCredential, t as credentialInputs } from "../_chunks/auth_selection-YX5_KZgD.js";
|
package/dist/keyring.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
//#region src/keyring.ts
|
|
3
|
+
/** Windows Credential Manager stores at most 2560 bytes per item: 1280 UTF-16 code units. */
|
|
4
|
+
const keyringPasswordLimit = (platform) => platform === "win32" ? 1280 : void 0;
|
|
3
5
|
const isPackaged = () => process.pkg !== void 0;
|
|
4
6
|
const isMissingItem = (err) => {
|
|
5
7
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -13,7 +15,9 @@ const tryLoadKeyring = () => {
|
|
|
13
15
|
if (isPackaged()) return null;
|
|
14
16
|
try {
|
|
15
17
|
const { Entry } = createRequire(import.meta.url)(["@napi-rs", "keyring"].join("/"));
|
|
18
|
+
const maxPasswordCodeUnits = keyringPasswordLimit(process.platform);
|
|
16
19
|
return {
|
|
20
|
+
...maxPasswordCodeUnits !== void 0 ? { maxPasswordCodeUnits } : {},
|
|
17
21
|
get(service, account) {
|
|
18
22
|
try {
|
|
19
23
|
return new Entry(service, account).getPassword();
|
|
@@ -39,4 +43,4 @@ const tryLoadKeyring = () => {
|
|
|
39
43
|
}
|
|
40
44
|
};
|
|
41
45
|
//#endregion
|
|
42
|
-
export { tryLoadKeyring };
|
|
46
|
+
export { keyringPasswordLimit, tryLoadKeyring };
|
package/dist/psql/io/history.js
CHANGED
package/dist/refresh_lock.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as OAUTH, d as scopeOf, i as API_KEY, l as interpretCredentials, t as storeFor, u as isSameCredential } from "./_chunks/credential_io-
|
|
1
|
+
import { a as OAUTH, d as scopeOf, i as API_KEY, l as interpretCredentials, t as storeFor, u as isSameCredential } from "./_chunks/credential_io-BS-0Pmwc.js";
|
|
2
2
|
import { log } from "./log.js";
|
|
3
3
|
import { getApiClient } from "./api.js";
|
|
4
4
|
import { revokeToken } from "./auth.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.10.0",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
"yaml": "^2.9.0",
|
|
71
71
|
"yargs": "17.7.2",
|
|
72
72
|
"yoctocolors": "^2.1.2",
|
|
73
|
-
"@neon/config": "1.9.
|
|
74
|
-
"@neon/
|
|
75
|
-
"@neon/
|
|
76
|
-
"@neon/
|
|
73
|
+
"@neon/config": "1.9.3",
|
|
74
|
+
"@neon/realtime": "0.1.2",
|
|
75
|
+
"@neon/sdk": "7.4.0",
|
|
76
|
+
"@neon/config-runtime": "1.10.2"
|
|
77
77
|
},
|
|
78
78
|
"optionalDependencies": {
|
|
79
79
|
"@napi-rs/keyring": "1.3.0",
|
|
@@ -110,9 +110,9 @@
|
|
|
110
110
|
"typescript": "^5.9.0",
|
|
111
111
|
"vitest": "^3.0.9",
|
|
112
112
|
"@neon-internals/cli-core": "0.0.0",
|
|
113
|
-
"@neon-internals/env-core": "0.0.
|
|
113
|
+
"@neon-internals/env-core": "0.0.34",
|
|
114
114
|
"@neon/e2e-harness": "0.0.0",
|
|
115
|
-
"@neon/env": "1.6.
|
|
115
|
+
"@neon/env": "1.6.4",
|
|
116
116
|
"@neon/functions": "0.11.0"
|
|
117
117
|
},
|
|
118
118
|
"publishConfig": {
|