neon 8.0.1 → 8.0.3
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/commands/auth.js +2 -2
- package/dist/commands/feedback.js +88 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/manifest.js +5 -0
- package/dist/commands/neon_auth.js +83 -63
- package/dist/commands/roles.js +25 -8
- package/dist/context.js +4 -2
- package/dist/index.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -943,6 +943,17 @@ neon ask --prompt "How do schema-only branches work?" --output json
|
|
|
943
943
|
|
|
944
944
|
Default table output is the assistant's text. On a TTY that is a spinner, then the streamed reply. `--output json` and `--output yaml` print `{ "text": "…" }` after the full response.
|
|
945
945
|
|
|
946
|
+
## Send feedback (`feedback`)
|
|
947
|
+
|
|
948
|
+
`neon feedback --message` sends feedback to Neon. It does not log in, and it sends only your message, not your account, project, or branch.
|
|
949
|
+
|
|
950
|
+
```bash
|
|
951
|
+
neon feedback --message "The branch docs were unclear"
|
|
952
|
+
neon feedback --message "The branch docs were unclear" --output json
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
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.
|
|
956
|
+
|
|
946
957
|
## Install the Neon plugin (`plugins`)
|
|
947
958
|
|
|
948
959
|
`neon plugins` installs the Neon agent plugin (`neon-postgres`) by running `npx plugins add`. It does not call the Neon API.
|
|
@@ -1410,6 +1421,7 @@ Id Name Project Created At Last Used At Last
|
|
|
1410
1421
|
| plugins | | Install the Neon plugin |
|
|
1411
1422
|
| skills | `update` | Install Neon agent skills |
|
|
1412
1423
|
| ask | | Ask a question about Neon |
|
|
1424
|
+
| feedback | | Send feedback to Neon |
|
|
1413
1425
|
| bucket | `create`, `list`, `delete`, `object list`, `object get`, `object put`, `object delete` (incl. `--recursive`) | Manage buckets and their objects |
|
|
1414
1426
|
| [completion](https://neon.com/docs/reference/cli-completion) | | Generate a completion script |
|
|
1415
1427
|
|
package/dist/commands/auth.js
CHANGED
|
@@ -5,7 +5,7 @@ import { log } from "../log.js";
|
|
|
5
5
|
import { getApiClient, isNeonApiError } from "../api.js";
|
|
6
6
|
import { setAuthContext } from "../auth_context.js";
|
|
7
7
|
import { a as isOwnedCredentialPath } from "../_chunks/paths-DMq0Lt7a.js";
|
|
8
|
-
import { currentContextFile, isAskCommand, isClaimCommand, isConfigAdd, isConfigInit, isCurrentBranchProbe, isGitLocalCommand, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, isUnfollowedGitHookSync, readContextFile } from "../context.js";
|
|
8
|
+
import { currentContextFile, isAskCommand, isClaimCommand, isConfigAdd, isConfigInit, isCurrentBranchProbe, isFeedbackCommand, isGitLocalCommand, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, isUnfollowedGitHookSync, readContextFile } from "../context.js";
|
|
9
9
|
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-CvnFEQyd.js";
|
|
10
10
|
import { extendTokenSet } from "../utils/auth.js";
|
|
11
11
|
import { AuthRefreshError, auth, classifyRefreshFailure, refreshToken } from "../auth.js";
|
|
@@ -304,7 +304,7 @@ const ensureAuth = async (props) => {
|
|
|
304
304
|
if (isInspectDbUrl(props)) return;
|
|
305
305
|
if (props._[0] === "open") return;
|
|
306
306
|
if (isSkillsCommand(props) || isPluginsCommand(props)) return;
|
|
307
|
-
if (isAskCommand(props)) return;
|
|
307
|
+
if (isAskCommand(props) || isFeedbackCommand(props)) return;
|
|
308
308
|
if (props._[0] === "init") {
|
|
309
309
|
selectCredential({
|
|
310
310
|
...credentialInputs(),
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { noPassthrough, single } from "../utils/flags.js";
|
|
5
|
+
//#region src/commands/feedback.ts
|
|
6
|
+
var feedback_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
DEFAULT_FEEDBACK_URL: () => DEFAULT_FEEDBACK_URL,
|
|
8
|
+
MAX_FEEDBACK_LENGTH: () => MAX_FEEDBACK_LENGTH,
|
|
9
|
+
builder: () => builder,
|
|
10
|
+
command: () => command,
|
|
11
|
+
describe: () => describe,
|
|
12
|
+
handler: () => handler,
|
|
13
|
+
resolveFeedbackUrl: () => resolveFeedbackUrl
|
|
14
|
+
});
|
|
15
|
+
const DEFAULT_FEEDBACK_URL = "https://feedback.neon.tech/";
|
|
16
|
+
/** The feedback service truncates longer messages, so the CLI rejects them instead. */
|
|
17
|
+
const MAX_FEEDBACK_LENGTH = 1e4;
|
|
18
|
+
const FEEDBACK_SOURCE = "neon_cli";
|
|
19
|
+
const FEEDBACK_TIMEOUT_MS = 3e4;
|
|
20
|
+
const command = "feedback";
|
|
21
|
+
const describe = "Send feedback to Neon";
|
|
22
|
+
const builder = (argv) => argv.usage("$0 feedback --message <feedback>").option("message", {
|
|
23
|
+
describe: "The feedback to send. Do not include passwords, API keys, or connection strings",
|
|
24
|
+
type: "string",
|
|
25
|
+
demandOption: true,
|
|
26
|
+
coerce: single("message", { required: true })
|
|
27
|
+
}).option("url", {
|
|
28
|
+
describe: "Override the feedback URL",
|
|
29
|
+
type: "string",
|
|
30
|
+
hidden: true,
|
|
31
|
+
coerce: single("url")
|
|
32
|
+
}).strict().check(noPassthrough("feedback")).check((args) => {
|
|
33
|
+
if ((typeof args.message === "string" ? args.message : "").trim().length > 1e4) throw new Error(`--message must be ${MAX_FEEDBACK_LENGTH.toLocaleString("en-US")} characters or fewer`);
|
|
34
|
+
return true;
|
|
35
|
+
}).example("$0 feedback --message \"The branch docs were unclear\"", describe);
|
|
36
|
+
function resolveFeedbackUrl(opts) {
|
|
37
|
+
const fromFlag = opts.url?.trim();
|
|
38
|
+
if (fromFlag) return fromFlag;
|
|
39
|
+
const fromEnv = opts.envUrl?.trim();
|
|
40
|
+
if (fromEnv) return fromEnv;
|
|
41
|
+
return DEFAULT_FEEDBACK_URL;
|
|
42
|
+
}
|
|
43
|
+
const handler = async (props) => {
|
|
44
|
+
await sendFeedback({
|
|
45
|
+
message: props.message.trim(),
|
|
46
|
+
url: resolveFeedbackUrl({
|
|
47
|
+
url: props.url,
|
|
48
|
+
envUrl: process.env.NEON_FEEDBACK_URL
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
52
|
+
writer(props).end({ received: true }, { fields: ["received"] });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
writer(props).text("Feedback received. Thank you!\n");
|
|
56
|
+
};
|
|
57
|
+
function isTimeout(error) {
|
|
58
|
+
return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
59
|
+
}
|
|
60
|
+
async function feedbackErrorMessage(response) {
|
|
61
|
+
if (response.status === 429) return "Too many feedback requests. Wait a minute and try again.";
|
|
62
|
+
try {
|
|
63
|
+
const body = await response.json();
|
|
64
|
+
if (typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" && body.error.trim() !== "") return body.error;
|
|
65
|
+
} catch {}
|
|
66
|
+
return `The Neon feedback service returned ${response.status}.`;
|
|
67
|
+
}
|
|
68
|
+
async function sendFeedback(opts) {
|
|
69
|
+
let response;
|
|
70
|
+
try {
|
|
71
|
+
response = await fetch(opts.url, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: { "Content-Type": "application/json" },
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
feedback: opts.message,
|
|
76
|
+
source: FEEDBACK_SOURCE
|
|
77
|
+
}),
|
|
78
|
+
signal: AbortSignal.timeout(FEEDBACK_TIMEOUT_MS)
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (isTimeout(error)) throw new Error("The Neon feedback service did not respond in time.");
|
|
82
|
+
if (isNetworkError(error)) throw new Error("Could not reach the Neon feedback service. Check your internet connection and try again.");
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
if (!response.ok) throw new Error(await feedbackErrorMessage(response));
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { DEFAULT_FEEDBACK_URL, MAX_FEEDBACK_LENGTH, builder, command, describe, handler, resolveFeedbackUrl, feedback_exports as t };
|
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 feedback_exports } from "./feedback.js";
|
|
24
25
|
import { t as functions_exports } from "./functions.js";
|
|
25
26
|
import { t as git_exports } from "./git.js";
|
|
26
27
|
import { t as init_exports } from "./init.js";
|
|
@@ -72,6 +73,7 @@ var commands_default = [
|
|
|
72
73
|
plugins_exports,
|
|
73
74
|
skills_exports,
|
|
74
75
|
ask_exports,
|
|
76
|
+
feedback_exports,
|
|
75
77
|
data_api_exports,
|
|
76
78
|
functions_exports,
|
|
77
79
|
triggers_exports,
|
|
@@ -176,6 +176,11 @@ const commandManifest = [
|
|
|
176
176
|
describe: "Ask a question about Neon",
|
|
177
177
|
load: () => import("./ask.js")
|
|
178
178
|
},
|
|
179
|
+
{
|
|
180
|
+
command: "feedback",
|
|
181
|
+
describe: "Send feedback to Neon",
|
|
182
|
+
load: () => import("./feedback.js")
|
|
183
|
+
},
|
|
179
184
|
{
|
|
180
185
|
command: "data-api",
|
|
181
186
|
describe: "Manage the Neon Data API for a database",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { codeFromBody, isNeonApiError, retryOnLock } from "../api.js";
|
|
3
|
+
import { stripAnsi } from "../human_table.js";
|
|
3
4
|
import { writer } from "../writer.js";
|
|
4
5
|
import { noPassthrough, single } from "../utils/flags.js";
|
|
5
6
|
import { NeonAuthEmailVerificationMethod, NeonAuthOauthProviderId, NeonAuthOauthProviderType, NeonAuthSupportedAuthProvider } from "../utils/api_enums.js";
|
|
6
|
-
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
7
|
+
import { branchIdFromProps, fillSingleProject, resolveBranchFromProps } from "../utils/enrichers.js";
|
|
7
8
|
import chalk from "chalk";
|
|
8
9
|
//#region src/commands/neon_auth.ts
|
|
9
10
|
var neon_auth_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -12,13 +13,20 @@ var neon_auth_exports = /* @__PURE__ */ __exportAll({
|
|
|
12
13
|
describe: () => describe,
|
|
13
14
|
handler: () => handler
|
|
14
15
|
});
|
|
16
|
+
const writeStyled = (text, stream = process.stdout) => {
|
|
17
|
+
stream.write(process.env.NO_COLOR ? stripAnsi(text) : text);
|
|
18
|
+
};
|
|
15
19
|
const printKvBlock = (title, entries) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
const labels = entries.map(([key]) => key.trimEnd());
|
|
21
|
+
const width = Math.max(0, ...labels.map((label) => label.length));
|
|
22
|
+
let text = `\n${chalk.green(title)}\n`;
|
|
23
|
+
entries.forEach(([, value], i) => {
|
|
24
|
+
text += ` ${chalk.green(labels[i].padEnd(width))} ${value ?? ""}\n`;
|
|
25
|
+
});
|
|
26
|
+
writeStyled(`${text}\n`);
|
|
19
27
|
};
|
|
20
28
|
const printMessage = (message) => {
|
|
21
|
-
|
|
29
|
+
writeStyled(`\n${chalk.green(message)}\n\n`);
|
|
22
30
|
};
|
|
23
31
|
const INTEGRATION_RESPONSE_FIELDS = [
|
|
24
32
|
"auth_provider",
|
|
@@ -356,6 +364,15 @@ const handler = (args) => {
|
|
|
356
364
|
const resolveBranch = async (props) => {
|
|
357
365
|
return props.branchId ?? await branchIdFromProps(props);
|
|
358
366
|
};
|
|
367
|
+
/** The branch name is only known when it was resolved from a listing, not from an id. */
|
|
368
|
+
const resolveBranchWithName = async (props) => {
|
|
369
|
+
if (props.branchId) return { branchId: props.branchId };
|
|
370
|
+
const { branchId, branch } = await resolveBranchFromProps(props);
|
|
371
|
+
return {
|
|
372
|
+
branchId,
|
|
373
|
+
branchName: branch?.name
|
|
374
|
+
};
|
|
375
|
+
};
|
|
359
376
|
const enable = async (props) => {
|
|
360
377
|
const branchId = await resolveBranch(props);
|
|
361
378
|
let data;
|
|
@@ -378,19 +395,19 @@ const enable = async (props) => {
|
|
|
378
395
|
const d = data;
|
|
379
396
|
printKvBlock(alreadyEnabled ? "Neon Auth is already enabled" : "Neon Auth enabled", [
|
|
380
397
|
["Auth Provider:", data.auth_provider],
|
|
381
|
-
...d.db_name ? [["Database:
|
|
382
|
-
["Base URL:
|
|
383
|
-
...d.schema_name ? [["Schema Name:
|
|
384
|
-
...d.table_name ? [["Table Name:
|
|
385
|
-
["JWKS URL:
|
|
398
|
+
...d.db_name ? [["Database:", d.db_name]] : [],
|
|
399
|
+
["Base URL:", data.base_url],
|
|
400
|
+
...d.schema_name ? [["Schema Name:", d.schema_name]] : [],
|
|
401
|
+
...d.table_name ? [["Table Name:", d.table_name]] : [],
|
|
402
|
+
["JWKS URL:", data.jwks_url]
|
|
386
403
|
]);
|
|
387
404
|
if (data.base_url) {
|
|
388
|
-
|
|
389
|
-
|
|
405
|
+
writeStyled(` ${chalk.green("Set this environment variable in your application:")}\n`);
|
|
406
|
+
writeStyled(` NEON_AUTH_BASE_URL=${data.base_url}\n\n`);
|
|
390
407
|
}
|
|
391
408
|
};
|
|
392
409
|
const status = async (props) => {
|
|
393
|
-
const branchId = await
|
|
410
|
+
const { branchId, branchName } = await resolveBranchWithName(props);
|
|
394
411
|
let data;
|
|
395
412
|
try {
|
|
396
413
|
({data} = await props.apiClient.getNeonAuth(props.projectId, branchId));
|
|
@@ -407,11 +424,11 @@ const status = async (props) => {
|
|
|
407
424
|
}
|
|
408
425
|
printKvBlock("Neon Auth status", [
|
|
409
426
|
["Auth Provider:", data.auth_provider],
|
|
410
|
-
["Branch
|
|
411
|
-
["Database:
|
|
412
|
-
["Base URL:
|
|
413
|
-
["Created At:
|
|
414
|
-
["JWKS URL:
|
|
427
|
+
["Branch:", branchName ? `${branchName} (${data.branch_id})` : data.branch_id],
|
|
428
|
+
["Database:", data.db_name],
|
|
429
|
+
["Base URL:", data.base_url],
|
|
430
|
+
["Created At:", data.created_at],
|
|
431
|
+
["JWKS URL:", data.jwks_url]
|
|
415
432
|
]);
|
|
416
433
|
};
|
|
417
434
|
const disable = async (props) => {
|
|
@@ -428,10 +445,11 @@ const oauthProviderList = async (props) => {
|
|
|
428
445
|
return;
|
|
429
446
|
}
|
|
430
447
|
writer(props).end(data.providers, { fields: OAUTH_PROVIDER_FIELDS });
|
|
431
|
-
if (data.providers.some((p) => p.type === NeonAuthOauthProviderType.Shared) && props.output === "table")
|
|
448
|
+
if (data.providers.some((p) => p.type === NeonAuthOauthProviderType.Shared) && props.output === "table") writeStyled(`\n${chalk.yellow("Caution:")} ${SHARED_PROVIDER_DISCLAIMER}\n\n`);
|
|
432
449
|
};
|
|
433
450
|
const oauthProviderAdd = async (props) => {
|
|
434
451
|
const branchId = await resolveBranch(props);
|
|
452
|
+
const baseUrlRequest = callbackBaseUrl(props, branchId, props.providerId);
|
|
435
453
|
let data;
|
|
436
454
|
try {
|
|
437
455
|
({data} = await props.apiClient.addBranchNeonAuthOauthProvider(props.projectId, branchId, {
|
|
@@ -445,14 +463,15 @@ const oauthProviderAdd = async (props) => {
|
|
|
445
463
|
}
|
|
446
464
|
if (props.output === "json" || props.output === "yaml") writer(props).end(data, { fields: OAUTH_PROVIDER_FIELDS });
|
|
447
465
|
else printKvBlock("OAuth provider added", [
|
|
448
|
-
["ID:
|
|
449
|
-
["Type:
|
|
450
|
-
...data.client_id ? [["Client ID:
|
|
466
|
+
["ID:", data.id],
|
|
467
|
+
["Type:", data.type],
|
|
468
|
+
...data.client_id ? [["Client ID:", data.client_id]] : []
|
|
451
469
|
]);
|
|
452
|
-
|
|
470
|
+
printCallbackInstructions(props.providerId, await baseUrlRequest);
|
|
453
471
|
};
|
|
454
472
|
const oauthProviderUpdate = async (props) => {
|
|
455
473
|
const branchId = await resolveBranch(props);
|
|
474
|
+
const baseUrlRequest = callbackBaseUrl(props, branchId, props.providerId);
|
|
456
475
|
let data;
|
|
457
476
|
try {
|
|
458
477
|
({data} = await props.apiClient.updateBranchNeonAuthOauthProvider(props.projectId, branchId, props.providerId, {
|
|
@@ -465,11 +484,11 @@ const oauthProviderUpdate = async (props) => {
|
|
|
465
484
|
}
|
|
466
485
|
if (props.output === "json" || props.output === "yaml") writer(props).end(data, { fields: OAUTH_PROVIDER_FIELDS });
|
|
467
486
|
else printKvBlock("OAuth provider updated", [
|
|
468
|
-
["ID:
|
|
469
|
-
["Type:
|
|
470
|
-
...data.client_id ? [["Client ID:
|
|
487
|
+
["ID:", data.id],
|
|
488
|
+
["Type:", data.type],
|
|
489
|
+
...data.client_id ? [["Client ID:", data.client_id]] : []
|
|
471
490
|
]);
|
|
472
|
-
|
|
491
|
+
printCallbackInstructions(props.providerId, await baseUrlRequest);
|
|
473
492
|
};
|
|
474
493
|
const CALLBACK_INSTRUCTIONS = {
|
|
475
494
|
github: {
|
|
@@ -485,20 +504,21 @@ const CALLBACK_INSTRUCTIONS = {
|
|
|
485
504
|
urlLabel: "callback/google"
|
|
486
505
|
}
|
|
487
506
|
};
|
|
488
|
-
|
|
507
|
+
/**
|
|
508
|
+
* Starts the base-URL read for the callback instructions, or resolves to undefined when none
|
|
509
|
+
* apply. It runs alongside the provider write (adding a provider does not change the base URL)
|
|
510
|
+
* and never rejects: a failed read prints no instructions, as before.
|
|
511
|
+
*/
|
|
512
|
+
const callbackBaseUrl = (props, branchId, providerId) => {
|
|
513
|
+
if (!CALLBACK_INSTRUCTIONS[providerId]) return Promise.resolve(void 0);
|
|
514
|
+
if (props.output === "json" || props.output === "yaml") return Promise.resolve(void 0);
|
|
515
|
+
return props.apiClient.getNeonAuth(props.projectId, branchId).then(({ data }) => data.base_url, () => void 0);
|
|
516
|
+
};
|
|
517
|
+
const printCallbackInstructions = (providerId, baseUrl) => {
|
|
489
518
|
const instructions = CALLBACK_INSTRUCTIONS[providerId];
|
|
490
|
-
if (!instructions) return;
|
|
491
|
-
if (props.output === "json" || props.output === "yaml") return;
|
|
492
|
-
let baseUrl;
|
|
493
|
-
try {
|
|
494
|
-
const { data } = await props.apiClient.getNeonAuth(props.projectId, branchId);
|
|
495
|
-
baseUrl = data.base_url;
|
|
496
|
-
} catch {
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
if (!baseUrl) return;
|
|
519
|
+
if (!instructions || !baseUrl) return;
|
|
500
520
|
const callbackUrl = `${baseUrl.replace(/\/$/, "")}/${instructions.urlLabel}`;
|
|
501
|
-
printKvBlock(instructions.lead, [["URL:
|
|
521
|
+
printKvBlock(instructions.lead, [["URL:", callbackUrl]]);
|
|
502
522
|
};
|
|
503
523
|
const oauthProviderDelete = async (props) => {
|
|
504
524
|
const branchId = await resolveBranch(props);
|
|
@@ -563,13 +583,13 @@ const allowLocalhostDisable = async (props) => {
|
|
|
563
583
|
printMessage("Localhost connections restricted");
|
|
564
584
|
};
|
|
565
585
|
const printEmailPasswordEntries = (data) => [
|
|
566
|
-
["Enabled:
|
|
567
|
-
["Verification Method:
|
|
568
|
-
["Require Verification:
|
|
586
|
+
["Enabled:", String(data.enabled)],
|
|
587
|
+
["Verification Method:", data.email_verification_method],
|
|
588
|
+
["Require Verification:", String(data.require_email_verification)],
|
|
569
589
|
["Auto Sign In After Verify: ", String(data.auto_sign_in_after_verification)],
|
|
570
590
|
["Send Email On Sign Up: ", String(data.send_verification_email_on_sign_up)],
|
|
571
591
|
["Send Email On Sign In: ", String(data.send_verification_email_on_sign_in)],
|
|
572
|
-
["Disable Sign Up:
|
|
592
|
+
["Disable Sign Up:", String(data.disable_sign_up)]
|
|
573
593
|
];
|
|
574
594
|
const emailPasswordGet = async (props) => {
|
|
575
595
|
const branchId = await resolveBranch(props);
|
|
@@ -598,14 +618,14 @@ const emailPasswordUpdate = async (props) => {
|
|
|
598
618
|
printKvBlock("Email & password auth configuration updated", printEmailPasswordEntries(data));
|
|
599
619
|
};
|
|
600
620
|
const printEmailProviderEntries = (data) => [
|
|
601
|
-
["Type:
|
|
621
|
+
["Type:", data.type],
|
|
602
622
|
...data.type === "standard" ? [
|
|
603
|
-
["Host:
|
|
623
|
+
["Host:", data.host],
|
|
604
624
|
["Port: ", data.port != null ? String(data.port) : void 0],
|
|
605
|
-
["Username:
|
|
625
|
+
["Username:", data.username]
|
|
606
626
|
] : [],
|
|
607
|
-
["Sender Email:
|
|
608
|
-
["Sender Name:
|
|
627
|
+
["Sender Email:", data.sender_email],
|
|
628
|
+
["Sender Name:", data.sender_name]
|
|
609
629
|
];
|
|
610
630
|
const emailProviderGet = async (props) => {
|
|
611
631
|
const branchId = await resolveBranch(props);
|
|
@@ -634,7 +654,7 @@ const emailProviderUpdate = async (props) => {
|
|
|
634
654
|
const { data } = await props.apiClient.updateNeonAuthEmailProvider(props.projectId, branchId, config);
|
|
635
655
|
if (props.output === "json" || props.output === "yaml") writer(props).end(data, { fields: EMAIL_PROVIDER_FIELDS });
|
|
636
656
|
else printKvBlock("Email provider configuration updated", printEmailProviderEntries(data));
|
|
637
|
-
if (warnSharedSender)
|
|
657
|
+
if (warnSharedSender) writeStyled(`${chalk.yellow("Warning:")} --sender-email and --sender-name are ignored for the shared email provider. These values only take effect with --type standard.\n\n`, process.stderr);
|
|
638
658
|
};
|
|
639
659
|
const emailProviderTest = async (props) => {
|
|
640
660
|
const branchId = await resolveBranch(props);
|
|
@@ -655,9 +675,9 @@ const emailProviderTestFailureMessage = (recipientEmail, errorMessage) => {
|
|
|
655
675
|
return errorMessage ? `${header}\nUpstream error: ${errorMessage}` : header;
|
|
656
676
|
};
|
|
657
677
|
const printOrganizationEntries = (data) => [
|
|
658
|
-
["Enabled:
|
|
659
|
-
["Org Limit:
|
|
660
|
-
["Creator Role:
|
|
678
|
+
["Enabled:", String(data.enabled)],
|
|
679
|
+
["Org Limit:", String(data.organization_limit)],
|
|
680
|
+
["Creator Role:", data.creator_role]
|
|
661
681
|
];
|
|
662
682
|
const organizationGet = async (props) => {
|
|
663
683
|
const branchId = await resolveBranch(props);
|
|
@@ -691,9 +711,9 @@ const organizationUpdate = async (props) => {
|
|
|
691
711
|
printKvBlock("Organization configuration updated", printOrganizationEntries(data));
|
|
692
712
|
};
|
|
693
713
|
const printWebhookEntries = (data) => [
|
|
694
|
-
["Enabled:
|
|
695
|
-
["URL:
|
|
696
|
-
["Events:
|
|
714
|
+
["Enabled:", String(data.enabled)],
|
|
715
|
+
["URL:", data.webhook_url ?? ""],
|
|
716
|
+
["Events:", (data.enabled_events ?? []).join(", ")],
|
|
697
717
|
["Timeout (sec): ", data.timeout_seconds != null ? String(data.timeout_seconds) : ""]
|
|
698
718
|
];
|
|
699
719
|
const webhookGet = async (props) => {
|
|
@@ -746,7 +766,7 @@ const pluginsList = async (props) => {
|
|
|
746
766
|
}
|
|
747
767
|
return JSON.stringify(value);
|
|
748
768
|
};
|
|
749
|
-
const entries = Object.entries(data).map(([key, value]) => [key
|
|
769
|
+
const entries = Object.entries(data).map(([key, value]) => [key, summarize(value)]);
|
|
750
770
|
printKvBlock("Neon Auth plugins", entries);
|
|
751
771
|
};
|
|
752
772
|
const pluginsGet = async (props) => {
|
|
@@ -763,12 +783,12 @@ const pluginsGet = async (props) => {
|
|
|
763
783
|
return;
|
|
764
784
|
}
|
|
765
785
|
if (typeof plugin === "object" && !Array.isArray(plugin) && plugin != null) {
|
|
766
|
-
const entries = Object.entries(plugin).map(([k, v]) => [`${k}
|
|
786
|
+
const entries = Object.entries(plugin).map(([k, v]) => [`${k}:`, formatValue(v)]);
|
|
767
787
|
printKvBlock(pluginTitle(props.pluginName), entries);
|
|
768
788
|
} else if (Array.isArray(plugin)) {
|
|
769
|
-
const entries = plugin.map((item, i) => [`[${i}]
|
|
789
|
+
const entries = plugin.map((item, i) => [`[${i}]:`, typeof item === "object" ? JSON.stringify(item) : String(item)]);
|
|
770
790
|
printKvBlock(pluginTitle(props.pluginName), entries);
|
|
771
|
-
} else printKvBlock(pluginTitle(props.pluginName), [["Value:"
|
|
791
|
+
} else printKvBlock(pluginTitle(props.pluginName), [["Value:", String(plugin)]]);
|
|
772
792
|
};
|
|
773
793
|
const userCreate = async (props) => {
|
|
774
794
|
const branchId = await resolveBranch(props);
|
|
@@ -779,9 +799,9 @@ const userCreate = async (props) => {
|
|
|
779
799
|
const { data } = await props.apiClient.createBranchNeonAuthNewUser(props.projectId, branchId, requestBody);
|
|
780
800
|
const displayName = requestBody.name !== props.email ? requestBody.name : void 0;
|
|
781
801
|
printKvBlock("User created", [
|
|
782
|
-
["ID:
|
|
783
|
-
["Email:
|
|
784
|
-
...displayName ? [["Name:
|
|
802
|
+
["ID:", data.id],
|
|
803
|
+
["Email:", requestBody.email],
|
|
804
|
+
...displayName ? [["Name:", displayName]] : []
|
|
785
805
|
]);
|
|
786
806
|
};
|
|
787
807
|
const userDelete = async (props) => {
|
|
@@ -792,7 +812,7 @@ const userDelete = async (props) => {
|
|
|
792
812
|
const userSetRole = async (props) => {
|
|
793
813
|
const branchId = await resolveBranch(props);
|
|
794
814
|
const { data } = await props.apiClient.updateNeonAuthUserRole(props.projectId, branchId, props.userId, { roles: props.roles });
|
|
795
|
-
printKvBlock("Roles updated", [["User ID:
|
|
815
|
+
printKvBlock("Roles updated", [["User ID:", data.id], ["Roles:", props.roles.join(", ")]]);
|
|
796
816
|
};
|
|
797
817
|
//#endregion
|
|
798
818
|
export { builder, command, describe, handler, neon_auth_exports as t };
|
package/dist/commands/roles.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { retryOnLock } from "../api.js";
|
|
3
3
|
import { writer } from "../writer.js";
|
|
4
|
-
import {
|
|
4
|
+
import { fillSingleProject, resolveBranchFromProps } from "../utils/enrichers.js";
|
|
5
5
|
//#region src/commands/roles.ts
|
|
6
6
|
var roles_exports = /* @__PURE__ */ __exportAll({
|
|
7
7
|
aliases: () => aliases,
|
|
@@ -14,6 +14,11 @@ var roles_exports = /* @__PURE__ */ __exportAll({
|
|
|
14
14
|
list: () => list
|
|
15
15
|
});
|
|
16
16
|
const ROLES_FIELDS = ["name", "created_at"];
|
|
17
|
+
const CREATED_ROLE_FIELDS = [
|
|
18
|
+
"name",
|
|
19
|
+
"password",
|
|
20
|
+
"created_at"
|
|
21
|
+
];
|
|
17
22
|
const command = "roles";
|
|
18
23
|
const describe = "Manage roles";
|
|
19
24
|
const aliases = ["role"];
|
|
@@ -41,22 +46,34 @@ const handler = (args) => {
|
|
|
41
46
|
return args;
|
|
42
47
|
};
|
|
43
48
|
const list = async (props) => {
|
|
44
|
-
const branchId = await
|
|
49
|
+
const { branchId, branch } = await resolveBranchFromProps(props);
|
|
45
50
|
const { data } = await props.apiClient.listProjectBranchRoles(props.projectId, branchId);
|
|
46
|
-
|
|
51
|
+
const branchLabel = branch?.name ?? branchId;
|
|
52
|
+
writer(props).end(data.roles, {
|
|
53
|
+
fields: ROLES_FIELDS,
|
|
54
|
+
humanTitle: `Roles on ${branchLabel}`,
|
|
55
|
+
emptyMessage: `No roles on ${branchLabel}.`
|
|
56
|
+
});
|
|
47
57
|
};
|
|
48
58
|
const create = async (props) => {
|
|
49
|
-
const
|
|
59
|
+
const noLogin = props["no-login"] === true || props.login === false;
|
|
60
|
+
const { branchId, branch } = await resolveBranchFromProps(props);
|
|
50
61
|
const { data } = await retryOnLock(() => props.apiClient.createProjectBranchRole(props.projectId, branchId, { role: {
|
|
51
62
|
name: props.name,
|
|
52
|
-
no_login:
|
|
63
|
+
...noLogin ? { no_login: true } : {}
|
|
53
64
|
} }));
|
|
54
|
-
writer(props).end(data.role, {
|
|
65
|
+
writer(props).end(data.role, {
|
|
66
|
+
fields: CREATED_ROLE_FIELDS,
|
|
67
|
+
humanTitle: `Role created on ${branch?.name ?? branchId}`
|
|
68
|
+
});
|
|
55
69
|
};
|
|
56
70
|
const deleteRole = async (props) => {
|
|
57
|
-
const branchId = await
|
|
71
|
+
const { branchId, branch } = await resolveBranchFromProps(props);
|
|
58
72
|
const { data } = await retryOnLock(() => props.apiClient.deleteProjectBranchRole(props.projectId, branchId, props.role));
|
|
59
|
-
if (data) writer(props).end(data.role, {
|
|
73
|
+
if (data) writer(props).end(data.role, {
|
|
74
|
+
fields: ROLES_FIELDS,
|
|
75
|
+
humanTitle: `Role deleted from ${branch?.name ?? branchId}`
|
|
76
|
+
});
|
|
60
77
|
};
|
|
61
78
|
//#endregion
|
|
62
79
|
export { aliases, builder, command, create, deleteRole, describe, handler, list, roles_exports as t };
|
package/dist/context.js
CHANGED
|
@@ -88,6 +88,8 @@ const isUnfollowedGitHookSync = (args, cwd = process.cwd()) => {
|
|
|
88
88
|
const isSkillsCommand = (args) => args._[0] === "skills" || args._[0] === "skill";
|
|
89
89
|
const isPluginsCommand = (args) => args._[0] === "plugins" || args._[0] === "plugin";
|
|
90
90
|
const isAskCommand = (args) => args._[0] === "ask";
|
|
91
|
+
/** Feedback is anonymous, so it must never read credentials or `.neon` context. */
|
|
92
|
+
const isFeedbackCommand = (args) => args._[0] === "feedback";
|
|
91
93
|
/**
|
|
92
94
|
* `inspect db --db-url` talks to Postgres with that URI. Skip only when the
|
|
93
95
|
* parsed value is nonempty: empty `--db-url` still goes through project
|
|
@@ -175,7 +177,7 @@ const enrichFromContext = (args) => {
|
|
|
175
177
|
if (isProfileCommand(args)) return;
|
|
176
178
|
if (isMcpCommand(args)) return;
|
|
177
179
|
if (isSkillsCommand(args) || isPluginsCommand(args)) return;
|
|
178
|
-
if (isAskCommand(args)) return;
|
|
180
|
+
if (isAskCommand(args) || isFeedbackCommand(args)) return;
|
|
179
181
|
if (isClaimCommand(args)) return;
|
|
180
182
|
const context = readContextFile(args.contextFile);
|
|
181
183
|
if (!args.orgId) args.orgId = context.orgId;
|
|
@@ -355,4 +357,4 @@ const globToRegExp = (pattern) => {
|
|
|
355
357
|
return new RegExp(`^${source}$`);
|
|
356
358
|
};
|
|
357
359
|
//#endregion
|
|
358
|
-
export { CONTEXT_FILE, applyContext, contextBranch, currentContextFile, enrichFromContext, ensureDirectoryGitignored, ensureGitignored, gitBranchMap, gitBranchMapping, isApiKeysCommand, isAskCommand, isClaimCommand, isConfigAdd, isConfigInit, isCurrentBranchProbe, isGitLocalCommand, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, isUnfollowedGitHookSync, readContextFile, setContext, setGitBranchMap, setGitBranchMapping, setGitFollow, updateContextFile, walkContextFile };
|
|
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 };
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.3",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -100,8 +100,8 @@
|
|
|
100
100
|
"typescript": "^5.9.0",
|
|
101
101
|
"vitest": "^3.0.9",
|
|
102
102
|
"@neon-internals/cli-core": "0.0.0",
|
|
103
|
-
"@neon/e2e-harness": "0.0.0",
|
|
104
103
|
"@neon-internals/env-core": "0.0.26",
|
|
104
|
+
"@neon/e2e-harness": "0.0.0",
|
|
105
105
|
"@neon/functions": "0.11.0"
|
|
106
106
|
},
|
|
107
107
|
"publishConfig": {
|