neon 2.47.0 → 3.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 +54 -0
- package/dist/_shared/auth_selection.js +76 -79
- package/dist/_shared/credentials.js +166 -187
- package/dist/_shared/env-core/env.js +395 -0
- package/dist/_shared/env-core/reuse-secrets.js +179 -0
- package/dist/_shared/paths.js +129 -126
- package/dist/_shared/profiles.js +192 -242
- package/dist/_shared/secure_file.js +36 -38
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +575 -658
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +642 -681
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +243 -303
- package/dist/commands/functions.js +275 -341
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +614 -845
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +313 -394
- package/dist/dev/functions.js +54 -64
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +36 -38
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +727 -983
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +1293 -2082
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +547 -1099
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +261 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +762 -1180
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1302
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +626 -815
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +1414 -1927
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +2 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +133 -147
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +37 -44
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +9 -7
|
@@ -1,349 +1,286 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { __exportAll } from "../_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { log } from "../log.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isNeonApiError } from "../api.js";
|
|
4
4
|
import { writer } from "../writer.js";
|
|
5
|
+
import { noPassthrough, single } from "../utils/flags.js";
|
|
6
|
+
//#region src/commands/api_keys.ts
|
|
7
|
+
var api_keys_exports = /* @__PURE__ */ __exportAll({
|
|
8
|
+
aliases: () => aliases,
|
|
9
|
+
builder: () => builder,
|
|
10
|
+
command: () => command,
|
|
11
|
+
describe: () => describe,
|
|
12
|
+
handler: () => handler,
|
|
13
|
+
orgIdForProject: () => orgIdForProject
|
|
14
|
+
});
|
|
5
15
|
const ACCOUNT_FIELDS = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
"id",
|
|
17
|
+
"name",
|
|
18
|
+
"created_at",
|
|
19
|
+
"last_used_at",
|
|
20
|
+
"last_used_from_addr"
|
|
11
21
|
];
|
|
12
22
|
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
* Table view of an org listing. `project` is the rendered column; the raw `project_id` is
|
|
24
|
+
* what structured output keeps. See {@link list} for why the two differ.
|
|
25
|
+
*/
|
|
16
26
|
const ORG_TABLE_FIELDS = [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
"id",
|
|
28
|
+
"name",
|
|
29
|
+
"project",
|
|
30
|
+
"created_at",
|
|
31
|
+
"last_used_at",
|
|
32
|
+
"last_used_from_addr"
|
|
23
33
|
];
|
|
24
34
|
const ORG_FIELDS = [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
"id",
|
|
36
|
+
"name",
|
|
37
|
+
"project_id",
|
|
38
|
+
"created_at",
|
|
39
|
+
"last_used_at",
|
|
40
|
+
"last_used_from_addr"
|
|
41
|
+
];
|
|
42
|
+
const CREATE_FIELDS = [
|
|
43
|
+
"id",
|
|
44
|
+
"name",
|
|
45
|
+
"key"
|
|
31
46
|
];
|
|
32
|
-
const CREATE_FIELDS = ["id", "name", "key"];
|
|
33
47
|
/** Metadata only: the secret is printed separately so it can be copied cleanly. */
|
|
34
48
|
const CREATE_TABLE_FIELDS = ["id", "name"];
|
|
35
|
-
const CREATE_TABLE_FIELDS_SCOPED = [
|
|
36
|
-
|
|
49
|
+
const CREATE_TABLE_FIELDS_SCOPED = [
|
|
50
|
+
"id",
|
|
51
|
+
"name",
|
|
52
|
+
"project"
|
|
53
|
+
];
|
|
54
|
+
const CREATE_FIELDS_SCOPED = [
|
|
55
|
+
"id",
|
|
56
|
+
"name",
|
|
57
|
+
"project_id",
|
|
58
|
+
"key"
|
|
59
|
+
];
|
|
37
60
|
/** Rendered for an org key that was not narrowed to a project. Table output only. */
|
|
38
61
|
const ALL_PROJECTS = "(all projects)";
|
|
39
62
|
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
* "This key should carry no project scope."
|
|
64
|
+
*
|
|
65
|
+
* A symbol rather than a string, because project ids are `^[a-z0-9-]{1,60}$` — any sentinel
|
|
66
|
+
* spelled as a string is a legal project id, and a project genuinely called that would have
|
|
67
|
+
* its correctly-scoped key rejected and revoked.
|
|
68
|
+
*/
|
|
46
69
|
const NO_PROJECT = Symbol("no-project");
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
.check(noPassthrough("api-keys")), async (args) => await create(args))
|
|
87
|
-
.command("revoke <id>", "Revoke an API key. Anything using it stops working immediately", (yargs) => yargs
|
|
88
|
-
.positional("id", {
|
|
89
|
-
describe: "The API key id, from `api-keys list`",
|
|
90
|
-
// Deliberately not `type: "number"`. That coerces before `coerce`
|
|
91
|
-
// runs, so an unparseable id arrives as NaN and the error can only
|
|
92
|
-
// echo `NaN` — a JavaScript artifact the user never typed. Taking
|
|
93
|
-
// the raw string lets the message name what they actually passed,
|
|
94
|
-
// and stops NaN reaching the API, which answers "not found" and
|
|
95
|
-
// blames the key rather than the input.
|
|
96
|
-
type: "string",
|
|
97
|
-
demandOption: true,
|
|
98
|
-
coerce: (value) => {
|
|
99
|
-
// Digits only, checked before Number(): `Number("0x65")` is 101
|
|
100
|
-
// and `Number("1e3")` is 1000, so either would revoke a key the
|
|
101
|
-
// user never named while the message promises "a numeric id".
|
|
102
|
-
const id = Number(value);
|
|
103
|
-
if (typeof value !== "string" ||
|
|
104
|
-
!/^\d+$/.test(value.trim()) ||
|
|
105
|
-
!Number.isSafeInteger(id) ||
|
|
106
|
-
id <= 0) {
|
|
107
|
-
throw new Error(`api-keys revoke needs a numeric key id, from \`neon api-keys list\`. Got \`${String(value)}\`.`);
|
|
108
|
-
}
|
|
109
|
-
return id;
|
|
110
|
-
},
|
|
111
|
-
})
|
|
112
|
-
.options({
|
|
113
|
-
"org-id": {
|
|
114
|
-
describe: "Revoke an organization key instead of an account key",
|
|
115
|
-
type: "string",
|
|
116
|
-
coerce: single("org-id"),
|
|
117
|
-
},
|
|
118
|
-
})
|
|
119
|
-
.strict()
|
|
120
|
-
.check(noPassthrough("api-keys")), async (args) => await revoke(args))
|
|
121
|
-
.demandCommand(1, "Run `neon api-keys --help` to see the subcommands.");
|
|
122
|
-
export const handler = (args) => args;
|
|
70
|
+
const command = "api-keys";
|
|
71
|
+
const aliases = ["api-key"];
|
|
72
|
+
const describe = "Manage API keys";
|
|
73
|
+
const builder = (argv) => argv.usage("$0 api-keys <sub-command> [options]").command("list", "List API keys for your account, or for an organization", (yargs) => yargs.options({ "org-id": {
|
|
74
|
+
describe: "List the organization's keys instead of your account's",
|
|
75
|
+
type: "string",
|
|
76
|
+
coerce: single("org-id")
|
|
77
|
+
} }).strict().check(noPassthrough("api-keys")), async (args) => await list(args)).command("create", "Create an API key. The key is shown once and cannot be retrieved again", (yargs) => yargs.options({
|
|
78
|
+
name: {
|
|
79
|
+
describe: "A name to identify the key later",
|
|
80
|
+
type: "string",
|
|
81
|
+
demandOption: true,
|
|
82
|
+
coerce: single("name", { required: true })
|
|
83
|
+
},
|
|
84
|
+
"org-id": {
|
|
85
|
+
describe: "Create a key for this organization instead of your account",
|
|
86
|
+
type: "string",
|
|
87
|
+
coerce: single("org-id")
|
|
88
|
+
},
|
|
89
|
+
"project-id": {
|
|
90
|
+
describe: "Create a key that can access only this project. Its organization is looked up from the project",
|
|
91
|
+
type: "string",
|
|
92
|
+
coerce: single("project-id")
|
|
93
|
+
}
|
|
94
|
+
}).conflicts("org-id", "project-id").strict().check(noPassthrough("api-keys")), async (args) => await create(args)).command("revoke <id>", "Revoke an API key. Anything using it stops working immediately", (yargs) => yargs.positional("id", {
|
|
95
|
+
describe: "The API key id, from `api-keys list`",
|
|
96
|
+
type: "string",
|
|
97
|
+
demandOption: true,
|
|
98
|
+
coerce: (value) => {
|
|
99
|
+
const id = Number(value);
|
|
100
|
+
if (typeof value !== "string" || !/^\d+$/.test(value.trim()) || !Number.isSafeInteger(id) || id <= 0) throw new Error(`api-keys revoke needs a numeric key id, from \`neon api-keys list\`. Got \`${String(value)}\`.`);
|
|
101
|
+
return id;
|
|
102
|
+
}
|
|
103
|
+
}).options({ "org-id": {
|
|
104
|
+
describe: "Revoke an organization key instead of an account key",
|
|
105
|
+
type: "string",
|
|
106
|
+
coerce: single("org-id")
|
|
107
|
+
} }).strict().check(noPassthrough("api-keys")), async (args) => await revoke(args)).demandCommand(1, "Run `neon api-keys --help` to see the subcommands.");
|
|
108
|
+
const handler = (args) => args;
|
|
123
109
|
const list = async (props) => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
const { data } = await props.apiClient.listApiKeys();
|
|
153
|
-
out.write(data, {
|
|
154
|
-
fields: ACCOUNT_FIELDS,
|
|
155
|
-
title: "Account API keys",
|
|
156
|
-
emptyMessage: "You have no account API keys.",
|
|
157
|
-
});
|
|
158
|
-
out.end();
|
|
159
|
-
// Organization keys live on a different endpoint, so a heading of plain "API keys"
|
|
160
|
-
// would claim to be everything while showing only half.
|
|
161
|
-
if (props.output === "table") {
|
|
162
|
-
log.info("Organization keys are listed separately: neon api-keys list --org-id <org> (see `neon orgs list`).");
|
|
163
|
-
}
|
|
110
|
+
const out = writer(props);
|
|
111
|
+
if (props.orgId) {
|
|
112
|
+
const { data } = await props.apiClient.listOrgApiKeys(props.orgId);
|
|
113
|
+
if (props.output === "table") out.write(data.map((key) => ({
|
|
114
|
+
...key,
|
|
115
|
+
project: key.project_id ?? ALL_PROJECTS
|
|
116
|
+
})), {
|
|
117
|
+
fields: ORG_TABLE_FIELDS,
|
|
118
|
+
title: `API keys in ${props.orgId}`,
|
|
119
|
+
emptyMessage: `No API keys in ${props.orgId}.`
|
|
120
|
+
});
|
|
121
|
+
else out.write(data, {
|
|
122
|
+
fields: ORG_FIELDS,
|
|
123
|
+
title: `API keys in ${props.orgId}`,
|
|
124
|
+
emptyMessage: `No API keys in ${props.orgId}.`
|
|
125
|
+
});
|
|
126
|
+
out.end();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const { data } = await props.apiClient.listApiKeys();
|
|
130
|
+
out.write(data, {
|
|
131
|
+
fields: ACCOUNT_FIELDS,
|
|
132
|
+
title: "Account API keys",
|
|
133
|
+
emptyMessage: "You have no account API keys."
|
|
134
|
+
});
|
|
135
|
+
out.end();
|
|
136
|
+
if (props.output === "table") log.info("Organization keys are listed separately: neon api-keys list --org-id <org> (see `neon orgs list`).");
|
|
164
137
|
};
|
|
165
138
|
const create = async (props) => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// one thing this command must not get wrong.
|
|
200
|
-
await assertUsable(props, data, {
|
|
201
|
-
orgId: resolvedOrgId,
|
|
202
|
-
expect: scopeTo,
|
|
203
|
-
});
|
|
204
|
-
report(props, data, CREATE_FIELDS_SCOPED, CREATE_TABLE_FIELDS_SCOPED);
|
|
205
|
-
log.info("Limited to %s: it cannot create projects, mint API keys, or read any other project. It can still change and delete everything inside that project.", scopeTo);
|
|
139
|
+
const { name, projectId, orgId } = props;
|
|
140
|
+
if (!projectId && !orgId) {
|
|
141
|
+
const { data } = await props.apiClient.createApiKey({ key_name: name });
|
|
142
|
+
await assertUsable(props, data, {
|
|
143
|
+
orgId: null,
|
|
144
|
+
expect: NO_PROJECT
|
|
145
|
+
});
|
|
146
|
+
report(props, data, CREATE_FIELDS, CREATE_TABLE_FIELDS);
|
|
147
|
+
log.warning("This key reaches everything your account can, in every organization. Pass --org-id or --project-id to narrow it.");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!projectId && orgId) {
|
|
151
|
+
const { data } = await props.apiClient.createOrgApiKey(orgId, { key_name: name });
|
|
152
|
+
await assertUsable(props, data, {
|
|
153
|
+
orgId,
|
|
154
|
+
expect: NO_PROJECT
|
|
155
|
+
});
|
|
156
|
+
report(props, data, CREATE_FIELDS, CREATE_TABLE_FIELDS);
|
|
157
|
+
log.warning("This key reaches every project in %s, including ones created later. Pass --project-id instead to restrict it to one.", orgId);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const scopeTo = projectId;
|
|
161
|
+
const resolvedOrgId = await orgIdForProject(props.apiClient, scopeTo);
|
|
162
|
+
const { data } = await props.apiClient.createOrgApiKey(resolvedOrgId, {
|
|
163
|
+
key_name: name,
|
|
164
|
+
project_id: scopeTo
|
|
165
|
+
});
|
|
166
|
+
await assertUsable(props, data, {
|
|
167
|
+
orgId: resolvedOrgId,
|
|
168
|
+
expect: scopeTo
|
|
169
|
+
});
|
|
170
|
+
report(props, data, CREATE_FIELDS_SCOPED, CREATE_TABLE_FIELDS_SCOPED);
|
|
171
|
+
log.info("Limited to %s: it cannot create projects, mint API keys, or read any other project. It can still change and delete everything inside that project.", scopeTo);
|
|
206
172
|
};
|
|
207
173
|
/**
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
174
|
+
* Print the issued key.
|
|
175
|
+
*
|
|
176
|
+
* In a terminal the secret goes on its own line rather than into a table cell: `cli-table`
|
|
177
|
+
* neither wraps nor truncates, so a 50-odd character key makes the row wider than most
|
|
178
|
+
* terminals and the wrapped remainder ends up beside box-drawing characters. Since this is
|
|
179
|
+
* the only time the key is ever shown, it has to be selectable in one gesture. Structured
|
|
180
|
+
* output keeps the key in the object, where a script expects it.
|
|
181
|
+
*/
|
|
216
182
|
const report = (props, data, fields, tableFields) => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
log.warning("Store this key now: it is not shown again.");
|
|
183
|
+
const out = writer(props);
|
|
184
|
+
if (props.output === "table") {
|
|
185
|
+
const row = typeof data.project_id === "string" ? {
|
|
186
|
+
...data,
|
|
187
|
+
project: data.project_id
|
|
188
|
+
} : data;
|
|
189
|
+
out.write(row, {
|
|
190
|
+
fields: tableFields,
|
|
191
|
+
title: "API key"
|
|
192
|
+
});
|
|
193
|
+
out.end();
|
|
194
|
+
out.text(`\n${data.key}\n`);
|
|
195
|
+
} else {
|
|
196
|
+
out.write(data, {
|
|
197
|
+
fields,
|
|
198
|
+
title: "API key"
|
|
199
|
+
});
|
|
200
|
+
out.end();
|
|
201
|
+
}
|
|
202
|
+
log.warning("Store this key now: it is not shown again.");
|
|
239
203
|
};
|
|
240
204
|
/**
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
205
|
+
* Refuse to report a key that isn't what we asked for, and take it back.
|
|
206
|
+
*
|
|
207
|
+
* Two ways a 2xx can still be wrong: no `key` in the body, which leaves a live credential
|
|
208
|
+
* the user can never see or use; and a `project_id` that doesn't match the requested
|
|
209
|
+
* project, which would mean announcing a scope the key does not have. Both withdraw the key
|
|
210
|
+
* before throwing.
|
|
211
|
+
*
|
|
212
|
+
* The thrown message states whether the withdrawal actually succeeded. Saying "the key has
|
|
213
|
+
* been revoked" when the revoke itself failed would be worse than saying nothing — it would
|
|
214
|
+
* leave an unverified credential live while telling the user it is gone.
|
|
215
|
+
*/
|
|
252
216
|
const assertUsable = async (props, data, scope) => {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
? "Neon returned no key."
|
|
259
|
-
: data.project_id !== wanted
|
|
260
|
-
? `Neon returned a key scoped to ${data.project_id ?? "nothing"} rather than ${wanted ?? "the whole organization"}.`
|
|
261
|
-
: null;
|
|
262
|
-
if (!problem)
|
|
263
|
-
return;
|
|
264
|
-
const withdrawn = await withdraw(props, scope.orgId, data.id);
|
|
265
|
-
throw new Error(`${problem} ${withdrawn
|
|
266
|
-
? "The key has been revoked; nothing was issued."
|
|
267
|
-
: `The key could NOT be revoked and may still be live${data.id === undefined
|
|
268
|
-
? ""
|
|
269
|
-
: `. Remove it with \`neon api-keys revoke ${data.id}${scope.orgId
|
|
270
|
-
? ` --org-id ${scope.orgId}`
|
|
271
|
-
: ""}\``}.`}`);
|
|
217
|
+
const wanted = scope.expect === NO_PROJECT ? void 0 : scope.expect;
|
|
218
|
+
const problem = typeof data.key !== "string" || data.key.trim() === "" ? "Neon returned no key." : data.project_id !== wanted ? `Neon returned a key scoped to ${data.project_id ?? "nothing"} rather than ${wanted ?? "the whole organization"}.` : null;
|
|
219
|
+
if (!problem) return;
|
|
220
|
+
const withdrawn = await withdraw(props, scope.orgId, data.id);
|
|
221
|
+
throw new Error(`${problem} ${withdrawn ? "The key has been revoked; nothing was issued." : `The key could NOT be revoked and may still be live${data.id === void 0 ? "" : `. Remove it with \`neon api-keys revoke ${data.id}${scope.orgId ? ` --org-id ${scope.orgId}` : ""}\``}.`}`);
|
|
272
222
|
};
|
|
273
223
|
/**
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
224
|
+
* Revoke, turning the most likely mistake into a usable message.
|
|
225
|
+
*
|
|
226
|
+
* Account and organization keys live on different endpoints, and `api-keys list --org-id X`
|
|
227
|
+
* shows ids that the account endpoint cannot see. Copying one and forgetting the flag — or
|
|
228
|
+
* leaving it on for an account key — is the easy error, and a bare "API key not found" sends
|
|
229
|
+
* the user looking for a deleted key rather than a misplaced flag.
|
|
230
|
+
*
|
|
231
|
+
* Only the key id is worth second-guessing here: an org id that is wrong or not yours does
|
|
232
|
+
* not reach this path at all, because the API answers "not an organization member" (verified
|
|
233
|
+
* against production) rather than a 404.
|
|
234
|
+
*/
|
|
285
235
|
const revokeOrExplain = async (props) => {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (isNeonApiError(err) && err.status === 404) {
|
|
293
|
-
throw new Error(props.orgId
|
|
294
|
-
? `No API key with id ${props.id} in ${props.orgId}. If it is one of your account's own keys, drop --org-id.`
|
|
295
|
-
: `No account API key with id ${props.id}. If it belongs to an organization, pass --org-id. Organization keys are not visible to your account.`);
|
|
296
|
-
}
|
|
297
|
-
throw err;
|
|
298
|
-
}
|
|
236
|
+
try {
|
|
237
|
+
return props.orgId ? await props.apiClient.revokeOrgApiKey(props.orgId, props.id) : await props.apiClient.revokeApiKey(props.id);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
if (isNeonApiError(err) && err.status === 404) throw new Error(props.orgId ? `No API key with id ${props.id} in ${props.orgId}. If it is one of your account's own keys, drop --org-id.` : `No account API key with id ${props.id}. If it belongs to an organization, pass --org-id. Organization keys are not visible to your account.`);
|
|
240
|
+
throw err;
|
|
241
|
+
}
|
|
299
242
|
};
|
|
300
243
|
/** Best-effort withdrawal of a key we are refusing to report. Never throws. */
|
|
301
244
|
const withdraw = async (props, orgId, keyId) => {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
return data.revoked === true && data.id === keyId;
|
|
311
|
-
}
|
|
312
|
-
catch (err) {
|
|
313
|
-
log.error("Failed to revoke API key %d: %s", keyId, err instanceof Error ? err.message : String(err));
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
245
|
+
if (!Number.isSafeInteger(keyId) || keyId <= 0) return false;
|
|
246
|
+
try {
|
|
247
|
+
const { data } = orgId ? await props.apiClient.revokeOrgApiKey(orgId, keyId) : await props.apiClient.revokeApiKey(keyId);
|
|
248
|
+
return data.revoked === true && data.id === keyId;
|
|
249
|
+
} catch (err) {
|
|
250
|
+
log.error("Failed to revoke API key %d: %s", keyId, err instanceof Error ? err.message : String(err));
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
316
253
|
};
|
|
317
254
|
const revoke = async (props) => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
255
|
+
const out = writer(props);
|
|
256
|
+
const { data } = await revokeOrExplain(props);
|
|
257
|
+
out.write(data, {
|
|
258
|
+
fields: [
|
|
259
|
+
"id",
|
|
260
|
+
"name",
|
|
261
|
+
"revoked",
|
|
262
|
+
"last_used_at"
|
|
263
|
+
],
|
|
264
|
+
title: "API key"
|
|
265
|
+
});
|
|
266
|
+
out.end();
|
|
325
267
|
};
|
|
326
268
|
/**
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
throw err;
|
|
344
|
-
}
|
|
345
|
-
if (!orgId) {
|
|
346
|
-
throw new Error(`Project ${projectId} does not belong to an organization, so it cannot have a project-scoped API key. Omit --project-id to create an account key.`);
|
|
347
|
-
}
|
|
348
|
-
return orgId;
|
|
269
|
+
* The organization a project belongs to. A project outside any organization cannot have a
|
|
270
|
+
* scoped key — the endpoint that accepts `project_id` is org-only — so that case fails here
|
|
271
|
+
* with the reason, rather than as a 404 from a URL the user never typed.
|
|
272
|
+
*/
|
|
273
|
+
const orgIdForProject = async (client, projectId) => {
|
|
274
|
+
let orgId;
|
|
275
|
+
try {
|
|
276
|
+
const { data: { project } } = await client.getProject(projectId);
|
|
277
|
+
orgId = project.org_id;
|
|
278
|
+
} catch (err) {
|
|
279
|
+
if (isNeonApiError(err) && err.status === 404) throw new Error(projectId.startsWith("org-") ? `Project ${projectId} not found. That looks like an organization id. Pass it as --org-id instead.` : `Project ${projectId} not found. Check the id with \`neon projects list\`.`);
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
if (!orgId) throw new Error(`Project ${projectId} does not belong to an organization, so it cannot have a project-scoped API key. Omit --project-id to create an account key.`);
|
|
283
|
+
return orgId;
|
|
349
284
|
};
|
|
285
|
+
//#endregion
|
|
286
|
+
export { aliases, api_keys_exports, builder, command, describe, handler, orgIdForProject };
|