neon 3.0.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.
Files changed (198) hide show
  1. package/README.md +54 -0
  2. package/dist/_shared/auth_selection.js +76 -79
  3. package/dist/_shared/credentials.js +166 -187
  4. package/dist/_shared/env-core/env.js +354 -517
  5. package/dist/_shared/env-core/reuse-secrets.js +159 -203
  6. package/dist/_shared/paths.js +129 -126
  7. package/dist/_shared/profiles.js +192 -242
  8. package/dist/_shared/secure_file.js +36 -38
  9. package/dist/_virtual/_rolldown/runtime.js +13 -0
  10. package/dist/analytics.js +163 -207
  11. package/dist/api.js +815 -758
  12. package/dist/auth.js +121 -141
  13. package/dist/auth_context.js +39 -53
  14. package/dist/cli.js +4 -7
  15. package/dist/commands/api.js +220 -250
  16. package/dist/commands/api_keys.js +251 -314
  17. package/dist/commands/auth.js +283 -328
  18. package/dist/commands/bootstrap.js +372 -437
  19. package/dist/commands/branches.js +304 -455
  20. package/dist/commands/bucket.js +374 -514
  21. package/dist/commands/checkout.js +213 -298
  22. package/dist/commands/config.js +573 -690
  23. package/dist/commands/connection_string.js +137 -165
  24. package/dist/commands/data_api.js +238 -260
  25. package/dist/commands/databases.js +67 -76
  26. package/dist/commands/deploy.js +31 -25
  27. package/dist/commands/dev.js +639 -719
  28. package/dist/commands/diff.js +156 -200
  29. package/dist/commands/env.js +243 -303
  30. package/dist/commands/functions.js +275 -355
  31. package/dist/commands/index.js +70 -65
  32. package/dist/commands/init.js +84 -119
  33. package/dist/commands/inspect.js +55 -55
  34. package/dist/commands/ip_allow.js +88 -120
  35. package/dist/commands/link.js +874 -1019
  36. package/dist/commands/logs.js +291 -0
  37. package/dist/commands/neon_auth.js +725 -933
  38. package/dist/commands/operations.js +34 -25
  39. package/dist/commands/orgs.js +28 -18
  40. package/dist/commands/profile.js +614 -845
  41. package/dist/commands/projects.js +313 -373
  42. package/dist/commands/psql.js +60 -58
  43. package/dist/commands/roles.js +55 -58
  44. package/dist/commands/schema_diff.js +87 -131
  45. package/dist/commands/set_context.js +34 -26
  46. package/dist/commands/snapshots.js +288 -413
  47. package/dist/commands/status.js +41 -37
  48. package/dist/commands/user.js +21 -10
  49. package/dist/commands/vpc_endpoints.js +85 -113
  50. package/dist/config.js +7 -6
  51. package/dist/config_format.js +50 -66
  52. package/dist/config_template.js +128 -157
  53. package/dist/context.js +183 -235
  54. package/dist/current_branch_fast_path.js +40 -49
  55. package/dist/dev/env.js +313 -394
  56. package/dist/dev/functions.js +54 -68
  57. package/dist/dev/inputs.js +46 -58
  58. package/dist/dev/runtime.js +135 -164
  59. package/dist/dev/websocket.js +766 -959
  60. package/dist/env.js +27 -33
  61. package/dist/env_file.js +118 -132
  62. package/dist/env_services.js +36 -38
  63. package/dist/errors.js +57 -68
  64. package/dist/functions_api.js +45 -43
  65. package/dist/help.js +189 -140
  66. package/dist/index.js +182 -257
  67. package/dist/init/agents.js +137 -118
  68. package/dist/init/auth.js +58 -68
  69. package/dist/init/bootstrap.js +325 -396
  70. package/dist/init/build_config.js +4 -2
  71. package/dist/init/detect_agent.js +56 -101
  72. package/dist/init/editors.js +35 -52
  73. package/dist/init/enrich_output.js +51 -66
  74. package/dist/init/extension.js +134 -171
  75. package/dist/init/inspect.js +179 -266
  76. package/dist/init/interactive.js +510 -622
  77. package/dist/init/neonctl.js +117 -168
  78. package/dist/init/orchestrate.js +157 -173
  79. package/dist/init/phases/auth.js +188 -202
  80. package/dist/init/phases/cleanup.js +23 -23
  81. package/dist/init/phases/db.js +251 -277
  82. package/dist/init/phases/getting_started.js +213 -223
  83. package/dist/init/phases/mcp.js +174 -224
  84. package/dist/init/phases/migrations.js +247 -248
  85. package/dist/init/phases/neon_auth.js +114 -133
  86. package/dist/init/phases/setup.js +546 -703
  87. package/dist/init/phases/skills.js +75 -86
  88. package/dist/init/phases/status.js +72 -67
  89. package/dist/init/resolve_context.js +102 -99
  90. package/dist/init/route_command.js +91 -98
  91. package/dist/init/skills.js +174 -218
  92. package/dist/init/vsix.js +77 -99
  93. package/dist/log.js +17 -16
  94. package/dist/neon_services.js +104 -129
  95. package/dist/parameters.gen.js +481 -471
  96. package/dist/pkg.js +17 -19
  97. package/dist/profile_keys.js +44 -47
  98. package/dist/psql/cli.js +44 -47
  99. package/dist/psql/command/cmd_cond.js +231 -406
  100. package/dist/psql/command/cmd_connect.js +557 -764
  101. package/dist/psql/command/cmd_copy.js +727 -983
  102. package/dist/psql/command/cmd_describe.js +1499 -1688
  103. package/dist/psql/command/cmd_format.js +733 -905
  104. package/dist/psql/command/cmd_io.js +1293 -2082
  105. package/dist/psql/command/cmd_lo.js +297 -359
  106. package/dist/psql/command/cmd_meta.js +727 -878
  107. package/dist/psql/command/cmd_misc.js +138 -172
  108. package/dist/psql/command/cmd_pipeline.js +547 -1099
  109. package/dist/psql/command/cmd_restrict.js +119 -155
  110. package/dist/psql/command/cmd_show.js +529 -688
  111. package/dist/psql/command/dispatch.js +261 -325
  112. package/dist/psql/command/inputQueue.js +35 -33
  113. package/dist/psql/command/shared.js +49 -63
  114. package/dist/psql/complete/filenames.js +90 -133
  115. package/dist/psql/complete/index.js +59 -97
  116. package/dist/psql/complete/matcher.js +236 -300
  117. package/dist/psql/complete/psqlVars.js +218 -223
  118. package/dist/psql/complete/queries.js +159 -177
  119. package/dist/psql/complete/rules.js +1493 -2299
  120. package/dist/psql/core/common.js +762 -1180
  121. package/dist/psql/core/help.js +456 -546
  122. package/dist/psql/core/mainloop.js +692 -1302
  123. package/dist/psql/core/prompt.js +391 -408
  124. package/dist/psql/core/settings.js +429 -644
  125. package/dist/psql/core/sqlHelp.js +480 -554
  126. package/dist/psql/core/startup.js +626 -815
  127. package/dist/psql/core/syncVars.js +67 -110
  128. package/dist/psql/core/variables.js +156 -278
  129. package/dist/psql/describe/formatters.js +884 -1285
  130. package/dist/psql/describe/processNamePattern.js +173 -260
  131. package/dist/psql/describe/queries.js +1368 -2403
  132. package/dist/psql/describe/versionGate.js +32 -41
  133. package/dist/psql/index.js +1414 -1927
  134. package/dist/psql/io/history.js +232 -271
  135. package/dist/psql/io/input.js +103 -108
  136. package/dist/psql/io/lineEditor/buffer.js +238 -319
  137. package/dist/psql/io/lineEditor/complete.js +135 -213
  138. package/dist/psql/io/lineEditor/filename.js +139 -148
  139. package/dist/psql/io/lineEditor/index.js +653 -870
  140. package/dist/psql/io/lineEditor/keymap.js +544 -702
  141. package/dist/psql/io/lineEditor/vt100.js +294 -341
  142. package/dist/psql/io/pgpass.js +158 -187
  143. package/dist/psql/io/pgservice.js +146 -183
  144. package/dist/psql/io/psqlrc.js +328 -403
  145. package/dist/psql/print/aligned.js +1020 -1683
  146. package/dist/psql/print/asciidoc.js +180 -214
  147. package/dist/psql/print/crosstab.js +281 -442
  148. package/dist/psql/print/csv.js +48 -70
  149. package/dist/psql/print/html.js +195 -226
  150. package/dist/psql/print/json.js +75 -88
  151. package/dist/psql/print/latex.js +291 -364
  152. package/dist/psql/print/pager.js +171 -242
  153. package/dist/psql/print/troff.js +194 -226
  154. package/dist/psql/print/unaligned.js +69 -95
  155. package/dist/psql/print/units.js +167 -169
  156. package/dist/psql/scanner/slash.js +428 -483
  157. package/dist/psql/scanner/sql.js +445 -889
  158. package/dist/psql/scanner/stringutils.js +309 -379
  159. package/dist/psql/types/index.js +2 -7
  160. package/dist/psql/types/scanner.js +25 -22
  161. package/dist/psql/wire/connection.js +2042 -2803
  162. package/dist/psql/wire/copy.js +84 -100
  163. package/dist/psql/wire/notify.js +39 -59
  164. package/dist/psql/wire/pipeline.js +305 -518
  165. package/dist/psql/wire/protocol.js +349 -417
  166. package/dist/psql/wire/sasl.js +180 -265
  167. package/dist/psql/wire/tls.js +400 -561
  168. package/dist/storage_api.js +115 -129
  169. package/dist/test_utils/fixtures.js +94 -113
  170. package/dist/test_utils/oauth_server.js +10 -7
  171. package/dist/test_utils/project_dir.js +33 -0
  172. package/dist/utils/ai_gateway_notice.js +131 -162
  173. package/dist/utils/api_enums.js +21 -28
  174. package/dist/utils/auth.js +10 -4
  175. package/dist/utils/branch_notice.js +20 -19
  176. package/dist/utils/branch_picker.js +83 -89
  177. package/dist/utils/cli_name.js +15 -12
  178. package/dist/utils/compute_units.js +20 -27
  179. package/dist/utils/config_diff.js +127 -158
  180. package/dist/utils/enrichers.js +95 -148
  181. package/dist/utils/esbuild.js +130 -189
  182. package/dist/utils/flags.js +35 -47
  183. package/dist/utils/formats.js +8 -15
  184. package/dist/utils/git_diff.js +69 -80
  185. package/dist/utils/inspect_db.js +101 -143
  186. package/dist/utils/inspect_queries.js +179 -142
  187. package/dist/utils/middlewares.js +37 -44
  188. package/dist/utils/openapi.js +87 -99
  189. package/dist/utils/package_manager.js +312 -110
  190. package/dist/utils/point_in_time.js +49 -53
  191. package/dist/utils/psql.js +89 -106
  192. package/dist/utils/service_picker.js +55 -58
  193. package/dist/utils/string.js +5 -5
  194. package/dist/utils/ui.js +38 -55
  195. package/dist/utils/write_sync.js +26 -35
  196. package/dist/utils/zip.js +4 -3
  197. package/dist/writer.js +67 -87
  198. package/package.json +7 -5
@@ -1,349 +1,286 @@
1
- import { isNeonApiError } from "../api.js";
1
+ import { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
2
  import { log } from "../log.js";
3
- import { noPassthrough, single } from "../utils/flags.js";
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
- "id",
7
- "name",
8
- "created_at",
9
- "last_used_at",
10
- "last_used_from_addr",
16
+ "id",
17
+ "name",
18
+ "created_at",
19
+ "last_used_at",
20
+ "last_used_from_addr"
11
21
  ];
12
22
  /**
13
- * Table view of an org listing. `project` is the rendered column; the raw `project_id` is
14
- * what structured output keeps. See {@link list} for why the two differ.
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
- "id",
18
- "name",
19
- "project",
20
- "created_at",
21
- "last_used_at",
22
- "last_used_from_addr",
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
- "id",
26
- "name",
27
- "project_id",
28
- "created_at",
29
- "last_used_at",
30
- "last_used_from_addr",
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 = ["id", "name", "project"];
36
- const CREATE_FIELDS_SCOPED = ["id", "name", "project_id", "key"];
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
- * "This key should carry no project scope."
41
- *
42
- * A symbol rather than a string, because project ids are `^[a-z0-9-]{1,60}$` — any sentinel
43
- * spelled as a string is a legal project id, and a project genuinely called that would have
44
- * its correctly-scoped key rejected and revoked.
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
- export const command = "api-keys";
48
- export const aliases = ["api-key"];
49
- export const describe = "Manage API keys";
50
- export const builder = (argv) => argv
51
- .usage("$0 api-keys <sub-command> [options]")
52
- .command("list", "List API keys for your account, or for an organization", (yargs) => yargs
53
- .options({
54
- "org-id": {
55
- describe: "List the organization's keys instead of your account's",
56
- type: "string",
57
- coerce: single("org-id"),
58
- },
59
- })
60
- .strict()
61
- .check(noPassthrough("api-keys")), async (args) => await list(args))
62
- .command("create", "Create an API key. The key is shown once and cannot be retrieved again", (yargs) => yargs
63
- .options({
64
- name: {
65
- describe: "A name to identify the key later",
66
- type: "string",
67
- demandOption: true,
68
- coerce: single("name", { required: true }),
69
- },
70
- "org-id": {
71
- describe: "Create a key for this organization instead of your account",
72
- type: "string",
73
- coerce: single("org-id"),
74
- },
75
- "project-id": {
76
- describe: "Create a key that can access only this project. Its organization is looked up from the project",
77
- type: "string",
78
- coerce: single("project-id"),
79
- },
80
- })
81
- // A key scoped to a project is already an organization key, so naming
82
- // both is contradictory rather than redundant — the org is derived from
83
- // the project and cannot be chosen independently.
84
- .conflicts("org-id", "project-id")
85
- .strict()
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
- const out = writer(props);
125
- if (props.orgId) {
126
- const { data } = await props.apiClient.listOrgApiKeys(props.orgId);
127
- // `writeTable` drops any column empty in every row, so an all-absent `project_id`
128
- // would take the whole column with it — hiding the answer exactly when it is "none
129
- // of them are scoped". Fill it in for the table, but leave structured output alone:
130
- // json/yaml serialize the whole object, so a synthetic field there would change the
131
- // machine-readable shape and duplicate `project_id` under a second name.
132
- if (props.output === "table") {
133
- out.write(data.map((key) => ({
134
- ...key,
135
- project: key.project_id ?? ALL_PROJECTS,
136
- })), {
137
- fields: ORG_TABLE_FIELDS,
138
- title: `API keys in ${props.orgId}`,
139
- emptyMessage: `No API keys in ${props.orgId}.`,
140
- });
141
- }
142
- else {
143
- out.write(data, {
144
- fields: ORG_FIELDS,
145
- title: `API keys in ${props.orgId}`,
146
- emptyMessage: `No API keys in ${props.orgId}.`,
147
- });
148
- }
149
- out.end();
150
- return;
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
- const { name, projectId, orgId } = props;
167
- // Neither flag: an account key, matching `POST /api_keys`. `api-keys` is exempt from
168
- // `.neon` enrichment (see `isApiKeysCommand`), so reaching this branch means the user
169
- // really did ask for account scope rather than inheriting a checked-out project.
170
- if (!projectId && !orgId) {
171
- const { data } = await props.apiClient.createApiKey({ key_name: name });
172
- await assertUsable(props, data, { orgId: null, expect: NO_PROJECT });
173
- report(props, data, CREATE_FIELDS, CREATE_TABLE_FIELDS);
174
- // The only key here that reaches everything, and the only one that used to say
175
- // nothing about its reach.
176
- log.warning("This key reaches everything your account can, in every organization. Pass --org-id or --project-id to narrow it.");
177
- return;
178
- }
179
- if (!projectId && orgId) {
180
- const { data } = await props.apiClient.createOrgApiKey(orgId, {
181
- key_name: name,
182
- });
183
- await assertUsable(props, data, { orgId, expect: NO_PROJECT });
184
- report(props, data, CREATE_FIELDS, CREATE_TABLE_FIELDS);
185
- log.warning("This key reaches every project in %s, including ones created later. Pass --project-id instead to restrict it to one.", orgId);
186
- return;
187
- }
188
- const scopeTo = projectId;
189
- // Project-scoped keys exist only on the organization endpoint, so an org is required.
190
- // Resolve it from the project rather than asking for both: `--project-id` alone would
191
- // otherwise fail for a reason that isn't visible from the command line.
192
- const resolvedOrgId = await orgIdForProject(props.apiClient, scopeTo);
193
- const { data } = await props.apiClient.createOrgApiKey(resolvedOrgId, {
194
- key_name: name,
195
- project_id: scopeTo,
196
- });
197
- // `project_id` is optional on the response. Printing a key and calling it scoped
198
- // without checking would hand over a credential whose reach we never confirmed — the
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
- * Print the issued key.
209
- *
210
- * In a terminal the secret goes on its own line rather than into a table cell: `cli-table`
211
- * neither wraps nor truncates, so a 50-odd character key makes the row wider than most
212
- * terminals and the wrapped remainder ends up beside box-drawing characters. Since this is
213
- * the only time the key is ever shown, it has to be selectable in one gesture. Structured
214
- * output keeps the key in the object, where a script expects it.
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
- const out = writer(props);
218
- if (props.output === "table") {
219
- // `project` mirrors the column name `list` uses. Added here rather than by the
220
- // caller so structured output keeps the API's own `project_id` and gains no
221
- // duplicate under a second name.
222
- const row = typeof data.project_id === "string"
223
- ? { ...data, project: data.project_id }
224
- : data;
225
- out.write(row, {
226
- fields: tableFields,
227
- title: "API key",
228
- });
229
- out.end();
230
- // Blank line so the key is visually detached from the table border, and so
231
- // `| tail -1` on stdout yields exactly the key (both notices go to stderr).
232
- out.text(`\n${data.key}\n`);
233
- }
234
- else {
235
- out.write(data, { fields: fields, title: "API key" });
236
- out.end();
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
- * Refuse to report a key that isn't what we asked for, and take it back.
242
- *
243
- * Two ways a 2xx can still be wrong: no `key` in the body, which leaves a live credential
244
- * the user can never see or use; and a `project_id` that doesn't match the requested
245
- * project, which would mean announcing a scope the key does not have. Both withdraw the key
246
- * before throwing.
247
- *
248
- * The thrown message states whether the withdrawal actually succeeded. Saying "the key has
249
- * been revoked" when the revoke itself failed would be worse than saying nothing — it would
250
- * leave an unverified credential live while telling the user it is gone.
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
- // `expect` is always stated, never inferred from a missing field: "no project" has to be
254
- // checked as deliberately as an exact project, or a key that came back narrower than
255
- // requested would be reported as reaching the whole organization.
256
- const wanted = scope.expect === NO_PROJECT ? undefined : scope.expect;
257
- const problem = typeof data.key !== "string" || data.key.trim() === ""
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
- * Revoke, turning the most likely mistake into a usable message.
275
- *
276
- * Account and organization keys live on different endpoints, and `api-keys list --org-id X`
277
- * shows ids that the account endpoint cannot see. Copying one and forgetting the flag — or
278
- * leaving it on for an account key — is the easy error, and a bare "API key not found" sends
279
- * the user looking for a deleted key rather than a misplaced flag.
280
- *
281
- * Only the key id is worth second-guessing here: an org id that is wrong or not yours does
282
- * not reach this path at all, because the API answers "not an organization member" (verified
283
- * against production) rather than a 404.
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
- try {
287
- return props.orgId
288
- ? await props.apiClient.revokeOrgApiKey(props.orgId, props.id)
289
- : await props.apiClient.revokeApiKey(props.id);
290
- }
291
- catch (err) {
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
- if (!Number.isSafeInteger(keyId) || keyId <= 0)
303
- return false;
304
- try {
305
- const { data } = orgId
306
- ? await props.apiClient.revokeOrgApiKey(orgId, keyId)
307
- : await props.apiClient.revokeApiKey(keyId);
308
- // Check which key the response names: a `revoked: true` for some other id is not
309
- // evidence that the one we issued is gone.
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
- const out = writer(props);
319
- const { data } = await revokeOrExplain(props);
320
- out.write(data, {
321
- fields: ["id", "name", "revoked", "last_used_at"],
322
- title: "API key",
323
- });
324
- out.end();
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
- * The organization a project belongs to. A project outside any organization cannot have a
328
- * scoped key — the endpoint that accepts `project_id` is org-only — so that case fails here
329
- * with the reason, rather than as a 404 from a URL the user never typed.
330
- */
331
- export const orgIdForProject = async (client, projectId) => {
332
- let orgId;
333
- try {
334
- const { data: { project }, } = await client.getProject(projectId);
335
- orgId = project.org_id;
336
- }
337
- catch (err) {
338
- if (isNeonApiError(err) && err.status === 404) {
339
- throw new Error(projectId.startsWith("org-")
340
- ? `Project ${projectId} not found. That looks like an organization id. Pass it as --org-id instead.`
341
- : `Project ${projectId} not found. Check the id with \`neon projects list\`.`);
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 };