neon 3.0.0 → 3.1.1

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 (207) hide show
  1. package/README.md +70 -5
  2. package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
  3. package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
  4. package/dist/_chunks/credentials-MYdHdKah.js +188 -0
  5. package/dist/_chunks/env-NbA61JR3.js +585 -0
  6. package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
  7. package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
  8. package/dist/_chunks/profiles-Ir29rqns.js +217 -0
  9. package/dist/_chunks/psql-DWH-kc69.js +2169 -0
  10. package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
  11. package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
  12. package/dist/analytics.js +163 -207
  13. package/dist/api.js +815 -758
  14. package/dist/auth.js +121 -141
  15. package/dist/auth_context.js +39 -53
  16. package/dist/cli.js +4 -7
  17. package/dist/commands/api.js +220 -250
  18. package/dist/commands/api_keys.js +251 -314
  19. package/dist/commands/auth.js +283 -328
  20. package/dist/commands/bootstrap.js +372 -437
  21. package/dist/commands/branches.js +304 -455
  22. package/dist/commands/bucket.js +374 -514
  23. package/dist/commands/checkout.js +213 -298
  24. package/dist/commands/config.js +573 -690
  25. package/dist/commands/connection_string.js +137 -165
  26. package/dist/commands/data_api.js +238 -260
  27. package/dist/commands/databases.js +67 -76
  28. package/dist/commands/deploy.js +31 -25
  29. package/dist/commands/dev.js +639 -719
  30. package/dist/commands/diff.js +156 -200
  31. package/dist/commands/env.js +255 -305
  32. package/dist/commands/functions.js +275 -355
  33. package/dist/commands/index.js +70 -65
  34. package/dist/commands/init.js +84 -119
  35. package/dist/commands/inspect.js +55 -55
  36. package/dist/commands/ip_allow.js +88 -120
  37. package/dist/commands/link.js +874 -1019
  38. package/dist/commands/logs.js +291 -0
  39. package/dist/commands/neon_auth.js +725 -933
  40. package/dist/commands/operations.js +34 -25
  41. package/dist/commands/orgs.js +28 -18
  42. package/dist/commands/profile.js +615 -846
  43. package/dist/commands/projects.js +313 -373
  44. package/dist/commands/psql.js +60 -58
  45. package/dist/commands/roles.js +55 -58
  46. package/dist/commands/schema_diff.js +87 -131
  47. package/dist/commands/set_context.js +34 -26
  48. package/dist/commands/snapshots.js +288 -413
  49. package/dist/commands/status.js +41 -37
  50. package/dist/commands/user.js +21 -10
  51. package/dist/commands/vpc_endpoints.js +85 -113
  52. package/dist/config.js +7 -6
  53. package/dist/config_format.js +50 -66
  54. package/dist/config_template.js +128 -157
  55. package/dist/context.js +183 -235
  56. package/dist/current_branch_fast_path.js +40 -49
  57. package/dist/dev/env.js +2 -446
  58. package/dist/dev/functions.js +54 -68
  59. package/dist/dev/inputs.js +46 -58
  60. package/dist/dev/runtime.js +135 -164
  61. package/dist/dev/websocket.js +766 -959
  62. package/dist/env.js +27 -33
  63. package/dist/env_file.js +118 -132
  64. package/dist/env_services.js +2 -51
  65. package/dist/errors.js +57 -68
  66. package/dist/functions_api.js +45 -43
  67. package/dist/help.js +189 -140
  68. package/dist/index.js +182 -257
  69. package/dist/init/agents.js +137 -118
  70. package/dist/init/auth.js +58 -68
  71. package/dist/init/bootstrap.js +325 -396
  72. package/dist/init/build_config.js +4 -2
  73. package/dist/init/detect_agent.js +56 -101
  74. package/dist/init/editors.js +35 -52
  75. package/dist/init/enrich_output.js +51 -66
  76. package/dist/init/extension.js +134 -171
  77. package/dist/init/inspect.js +179 -266
  78. package/dist/init/interactive.js +510 -622
  79. package/dist/init/neonctl.js +117 -168
  80. package/dist/init/orchestrate.js +157 -173
  81. package/dist/init/phases/auth.js +188 -202
  82. package/dist/init/phases/cleanup.js +23 -23
  83. package/dist/init/phases/db.js +251 -277
  84. package/dist/init/phases/getting_started.js +213 -223
  85. package/dist/init/phases/mcp.js +174 -224
  86. package/dist/init/phases/migrations.js +247 -248
  87. package/dist/init/phases/neon_auth.js +114 -133
  88. package/dist/init/phases/setup.js +546 -703
  89. package/dist/init/phases/skills.js +75 -86
  90. package/dist/init/phases/status.js +72 -67
  91. package/dist/init/resolve_context.js +102 -99
  92. package/dist/init/route_command.js +91 -98
  93. package/dist/init/skills.js +174 -218
  94. package/dist/init/vsix.js +77 -99
  95. package/dist/log.js +17 -16
  96. package/dist/neon_services.js +104 -129
  97. package/dist/parameters.gen.js +481 -471
  98. package/dist/pkg.js +17 -19
  99. package/dist/profile_keys.js +44 -47
  100. package/dist/psql/cli.js +44 -47
  101. package/dist/psql/command/cmd_cond.js +231 -406
  102. package/dist/psql/command/cmd_connect.js +557 -764
  103. package/dist/psql/command/cmd_copy.js +728 -984
  104. package/dist/psql/command/cmd_describe.js +1499 -1688
  105. package/dist/psql/command/cmd_format.js +733 -905
  106. package/dist/psql/command/cmd_io.js +2 -2193
  107. package/dist/psql/command/cmd_lo.js +297 -359
  108. package/dist/psql/command/cmd_meta.js +727 -878
  109. package/dist/psql/command/cmd_misc.js +138 -172
  110. package/dist/psql/command/cmd_pipeline.js +2 -1148
  111. package/dist/psql/command/cmd_restrict.js +119 -155
  112. package/dist/psql/command/cmd_show.js +529 -688
  113. package/dist/psql/command/dispatch.js +260 -325
  114. package/dist/psql/command/inputQueue.js +35 -33
  115. package/dist/psql/command/shared.js +49 -63
  116. package/dist/psql/complete/filenames.js +90 -133
  117. package/dist/psql/complete/index.js +59 -97
  118. package/dist/psql/complete/matcher.js +236 -300
  119. package/dist/psql/complete/psqlVars.js +218 -223
  120. package/dist/psql/complete/queries.js +159 -177
  121. package/dist/psql/complete/rules.js +1493 -2299
  122. package/dist/psql/core/common.js +2 -1253
  123. package/dist/psql/core/help.js +456 -546
  124. package/dist/psql/core/mainloop.js +692 -1303
  125. package/dist/psql/core/prompt.js +391 -408
  126. package/dist/psql/core/settings.js +429 -644
  127. package/dist/psql/core/sqlHelp.js +480 -554
  128. package/dist/psql/core/startup.js +2 -846
  129. package/dist/psql/core/syncVars.js +67 -110
  130. package/dist/psql/core/variables.js +156 -278
  131. package/dist/psql/describe/formatters.js +884 -1285
  132. package/dist/psql/describe/processNamePattern.js +173 -260
  133. package/dist/psql/describe/queries.js +1368 -2403
  134. package/dist/psql/describe/versionGate.js +32 -41
  135. package/dist/psql/index.js +2 -2030
  136. package/dist/psql/io/history.js +232 -271
  137. package/dist/psql/io/input.js +103 -108
  138. package/dist/psql/io/lineEditor/buffer.js +238 -319
  139. package/dist/psql/io/lineEditor/complete.js +135 -213
  140. package/dist/psql/io/lineEditor/filename.js +139 -148
  141. package/dist/psql/io/lineEditor/index.js +653 -870
  142. package/dist/psql/io/lineEditor/keymap.js +544 -702
  143. package/dist/psql/io/lineEditor/vt100.js +294 -341
  144. package/dist/psql/io/pgpass.js +158 -187
  145. package/dist/psql/io/pgservice.js +146 -183
  146. package/dist/psql/io/psqlrc.js +328 -403
  147. package/dist/psql/print/aligned.js +1020 -1683
  148. package/dist/psql/print/asciidoc.js +180 -214
  149. package/dist/psql/print/crosstab.js +281 -442
  150. package/dist/psql/print/csv.js +48 -70
  151. package/dist/psql/print/html.js +195 -226
  152. package/dist/psql/print/json.js +75 -88
  153. package/dist/psql/print/latex.js +291 -364
  154. package/dist/psql/print/pager.js +171 -242
  155. package/dist/psql/print/troff.js +194 -226
  156. package/dist/psql/print/unaligned.js +69 -95
  157. package/dist/psql/print/units.js +167 -169
  158. package/dist/psql/scanner/slash.js +428 -483
  159. package/dist/psql/scanner/sql.js +445 -889
  160. package/dist/psql/scanner/stringutils.js +309 -379
  161. package/dist/psql/types/index.js +8 -7
  162. package/dist/psql/types/scanner.js +25 -22
  163. package/dist/psql/wire/connection.js +2042 -2803
  164. package/dist/psql/wire/copy.js +84 -100
  165. package/dist/psql/wire/notify.js +39 -59
  166. package/dist/psql/wire/pipeline.js +305 -518
  167. package/dist/psql/wire/protocol.js +349 -417
  168. package/dist/psql/wire/sasl.js +180 -265
  169. package/dist/psql/wire/tls.js +400 -561
  170. package/dist/storage_api.js +115 -129
  171. package/dist/test_utils/fixtures.js +94 -113
  172. package/dist/test_utils/oauth_server.js +10 -7
  173. package/dist/test_utils/project_dir.js +33 -0
  174. package/dist/utils/ai_gateway_notice.js +131 -162
  175. package/dist/utils/api_enums.js +21 -28
  176. package/dist/utils/auth.js +10 -4
  177. package/dist/utils/branch_notice.js +20 -19
  178. package/dist/utils/branch_picker.js +83 -89
  179. package/dist/utils/cli_name.js +15 -12
  180. package/dist/utils/compute_units.js +20 -27
  181. package/dist/utils/config_diff.js +127 -158
  182. package/dist/utils/enrichers.js +95 -148
  183. package/dist/utils/esbuild.js +130 -189
  184. package/dist/utils/flags.js +35 -47
  185. package/dist/utils/formats.js +8 -15
  186. package/dist/utils/git_diff.js +69 -80
  187. package/dist/utils/inspect_db.js +101 -143
  188. package/dist/utils/inspect_queries.js +179 -142
  189. package/dist/utils/middlewares.js +39 -45
  190. package/dist/utils/openapi.js +87 -99
  191. package/dist/utils/package_manager.js +312 -110
  192. package/dist/utils/point_in_time.js +49 -53
  193. package/dist/utils/psql.js +89 -106
  194. package/dist/utils/service_picker.js +55 -58
  195. package/dist/utils/string.js +5 -5
  196. package/dist/utils/ui.js +38 -55
  197. package/dist/utils/write_sync.js +26 -35
  198. package/dist/utils/zip.js +4 -3
  199. package/dist/writer.js +67 -87
  200. package/package.json +11 -6
  201. package/dist/_shared/auth_selection.js +0 -86
  202. package/dist/_shared/credentials.js +0 -209
  203. package/dist/_shared/env-core/env.js +0 -558
  204. package/dist/_shared/env-core/reuse-secrets.js +0 -223
  205. package/dist/_shared/paths.js +0 -148
  206. package/dist/_shared/profiles.js +0 -276
  207. package/dist/_shared/secure_file.js +0 -43
@@ -1,349 +1,286 @@
1
- import { isNeonApiError } from "../api.js";
1
+ import { t as __exportAll } from "../_chunks/rolldown-runtime-D7D4PA-g.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, builder, command, describe, handler, orgIdForProject, api_keys_exports as t };