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,346 +1,301 @@
1
- import { createHash } from "node:crypto";
2
- import { existsSync, rmSync } from "node:fs";
3
- import { credentialInputs, displacedProfileWarning, selectCredential, } from "../_shared/auth_selection.js";
4
- import { API_KEY, interpretCredentials, OAUTH, readCredentials, writeCredentials, } from "../_shared/credentials.js";
5
- import { assertProfilesUsable, assertValidProfileName, DEFAULT_PROFILE, newProfileCredentialsPath, readProfiles, resolveProfile, selectProfileName, upsertProfile, } from "../_shared/profiles.js";
6
- import { getApiClient } from "../api.js";
7
- import { auth, refreshToken } from "../auth.js";
8
- import { setAuthContext } from "../auth_context.js";
9
- import { credentialsPath as defaultCredentialsPath } from "../config.js";
10
- import { isConfigInit, isCurrentBranchProbe, isProfileCommand, } from "../context.js";
1
+ import { t as __exportAll } from "../_chunks/rolldown-runtime-D7D4PA-g.js";
2
+ import { d as writeCredentials, l as readCredentials, n as OAUTH, s as interpretCredentials, t as API_KEY } from "../_chunks/credentials-MYdHdKah.js";
11
3
  import { isCi } from "../env.js";
12
4
  import { log } from "../log.js";
5
+ import { getApiClient } from "../api.js";
6
+ import { n as credentialsPath } from "../_chunks/paths-DMq0Lt7a.js";
7
+ import "../config.js";
8
+ import { setAuthContext } from "../auth_context.js";
9
+ import { isConfigInit, isCurrentBranchProbe, isProfileCommand } from "../context.js";
13
10
  import { extendTokenSet } from "../utils/auth.js";
11
+ import { auth, refreshToken } from "../auth.js";
12
+ import { a as newProfileCredentialsPath, c as readProfiles, d as upsertProfile, l as resolveProfile, n as assertProfilesUsable, r as assertValidProfileName, t as DEFAULT_PROFILE, u as selectProfileName } from "../_chunks/profiles-Ir29rqns.js";
13
+ import { i as selectCredential, n as displacedProfileWarning, t as credentialInputs } from "../_chunks/auth_selection-DGgq6ifc.js";
14
+ import { existsSync, rmSync } from "node:fs";
15
+ import { createHash } from "node:crypto";
16
+ //#region src/commands/auth.ts
17
+ var auth_exports = /* @__PURE__ */ __exportAll({
18
+ aliases: () => aliases,
19
+ authFlow: () => authFlow,
20
+ builder: () => builder,
21
+ command: () => command,
22
+ credentialsPathForName: () => credentialsPathForName,
23
+ deleteCredentialsAt: () => deleteCredentialsAt,
24
+ describe: () => describe,
25
+ ensureAuth: () => ensureAuth,
26
+ handler: () => handler,
27
+ usableCredential: () => usableCredential
28
+ });
14
29
  /**
15
- * The credentials file a named profile resolves to, falling back to plain `credentials.json`
16
- * when the name is `DEFAULT` and nothing is declared. An unknown profile name is a hard error
17
- * rather than a silent write to the default file — a typo must not authenticate the wrong
18
- * account.
19
- */
20
- export const credentialsPathForName = (configDir, name) => {
21
- if (name === DEFAULT_PROFILE && !readProfiles(configDir, log.warning))
22
- return defaultCredentialsPath(configDir);
23
- return resolveProfile(configDir, name).credentialsPath;
30
+ * The credentials file a named profile resolves to, falling back to plain `credentials.json`
31
+ * when the name is `DEFAULT` and nothing is declared. An unknown profile name is a hard error
32
+ * rather than a silent write to the default file — a typo must not authenticate the wrong
33
+ * account.
34
+ */
35
+ const credentialsPathForName = (configDir, name) => {
36
+ if (name === "DEFAULT" && !readProfiles(configDir, log.warning)) return credentialsPath(configDir);
37
+ return resolveProfile(configDir, name).credentialsPath;
24
38
  };
25
- const credentialsPathFor = ({ configDir, profile, }) => credentialsPathForName(configDir, selectProfileName(profile));
26
- export const command = "auth";
27
- export const aliases = ["login"];
28
- export const describe = "Authenticate";
29
- export const builder = (yargs) => yargs.option("context-file", {
30
- hidden: true,
31
- });
32
- export const handler = async (args) => {
33
- await authFlow(args);
39
+ const credentialsPathFor = ({ configDir, profile }) => credentialsPathForName(configDir, selectProfileName(profile));
40
+ const command = "auth";
41
+ const aliases = ["login"];
42
+ const describe = "Authenticate";
43
+ const builder = (yargs) => yargs.option("context-file", { hidden: true });
44
+ const handler = async (args) => {
45
+ await authFlow(args);
34
46
  };
35
- export const authFlow = async ({ configDir, oauthHost, clientId, apiHost, forceAuth, "force-auth": forceAuthKebab, allowUnsafeTls, profile, }) => {
36
- const allowInteractiveAuth = forceAuth ?? forceAuthKebab;
37
- if (!allowInteractiveAuth && isCi()) {
38
- throw new Error("Cannot run interactive auth in CI");
39
- }
40
- // A named profile that doesn't exist yet is created here rather than erroring: `neon
41
- // auth --profile work` is how you make one, so it must work before there is anything
42
- // to look up.
43
- const profileName = selectProfileName(profile);
44
- const isNamed = profileName !== DEFAULT_PROFILE;
45
- if (isNamed)
46
- assertValidProfileName(profileName);
47
- // Both checks belong before the browser opens. Signing in and then refusing costs a real
48
- // sign-in, and worse, the write in between lands on a path chosen from metadata this
49
- // refuses to trust.
50
- assertProfilesUsable(configDir, profileName);
51
- const tokenSet = await auth({
52
- oauthHost: oauthHost,
53
- clientId: clientId,
54
- allowUnsafeTls,
55
- });
56
- const credentialsPath = isNamed && !readProfiles(configDir, log.warning)?.profiles[profileName]
57
- ? newProfileCredentialsPath(configDir, profileName)
58
- : credentialsPathFor({ configDir, profile });
59
- let identity = {};
60
- try {
61
- identity = await preserveCredentials(credentialsPath, tokenSet, getApiClient({
62
- apiKey: tokenSet.access_token || "",
63
- apiHost,
64
- }));
65
- }
66
- catch {
67
- log.error("Failed to save credentials");
68
- return "";
69
- }
70
- if (isNamed) {
71
- upsertProfile(configDir, profileName, {
72
- credentials: credentialsPath,
73
- ...(identity.email ? { label: identity.email } : {}),
74
- ...(identity.id ? { userId: identity.id } : {}),
75
- });
76
- log.info('Saved profile "%s" (%s)', profileName, credentialsPath);
77
- }
78
- log.info("Auth complete");
79
- return tokenSet.access_token || "";
47
+ const authFlow = async ({ configDir, oauthHost, clientId, apiHost, forceAuth, "force-auth": forceAuthKebab, allowUnsafeTls, profile }) => {
48
+ if (!(forceAuth ?? forceAuthKebab) && isCi()) throw new Error("Cannot run interactive auth in CI");
49
+ const profileName = selectProfileName(profile);
50
+ const isNamed = profileName !== DEFAULT_PROFILE;
51
+ if (isNamed) assertValidProfileName(profileName);
52
+ assertProfilesUsable(configDir, profileName);
53
+ const tokenSet = await auth({
54
+ oauthHost,
55
+ clientId,
56
+ allowUnsafeTls
57
+ });
58
+ const credentialsPath = isNamed && !readProfiles(configDir, log.warning)?.profiles[profileName] ? newProfileCredentialsPath(configDir, profileName) : credentialsPathFor({
59
+ configDir,
60
+ profile
61
+ });
62
+ let identity = {};
63
+ try {
64
+ identity = await preserveCredentials(credentialsPath, tokenSet, getApiClient({
65
+ apiKey: tokenSet.access_token || "",
66
+ apiHost
67
+ }));
68
+ } catch {
69
+ log.error("Failed to save credentials");
70
+ return "";
71
+ }
72
+ if (isNamed) {
73
+ upsertProfile(configDir, profileName, {
74
+ credentials: credentialsPath,
75
+ ...identity.email ? { label: identity.email } : {},
76
+ ...identity.id ? { userId: identity.id } : {}
77
+ });
78
+ log.info("Saved profile \"%s\" (%s)", profileName, credentialsPath);
79
+ }
80
+ log.info("Auth complete");
81
+ return tokenSet.access_token || "";
80
82
  };
81
83
  /**
82
- * Persist the token set and return the account it belongs to, so a named profile can be
83
- * labelled with an email. The credentials file records only `user_id` — a UUID with no
84
- * email — which is why identifying a stored profile offline is otherwise impossible.
85
- */
84
+ * Persist the token set and return the account it belongs to, so a named profile can be
85
+ * labelled with an email. The credentials file records only `user_id` — a UUID with no
86
+ * email — which is why identifying a stored profile offline is otherwise impossible.
87
+ */
86
88
  const preserveCredentials = async (path, credentials, apiClient) => {
87
- const { data: { id, email }, } = await apiClient.getCurrentUserInfo();
88
- // Replace rather than merge, and declare the kind. Signing in makes this an OAuth
89
- // credential and nothing of a previous one is carried over: a retained API key would leave
90
- // the file holding two credentials, possibly for two different accounts, with a single
91
- // field deciding which one is live.
92
- const stored = {
93
- ...credentials,
94
- type: OAUTH,
95
- ...(id !== undefined ? { user_id: id } : {}),
96
- };
97
- writeCredentials(path, stored);
98
- log.debug("Saved credentials to %s", path);
99
- log.debug("Credentials MD5 hash: %s", md5hash(JSON.stringify(stored)));
100
- return { ...(id ? { id } : {}), ...(email ? { email } : {}) };
89
+ const { data: { id, email } } = await apiClient.getCurrentUserInfo();
90
+ const stored = {
91
+ ...credentials,
92
+ type: OAUTH,
93
+ ...id !== void 0 ? { user_id: id } : {}
94
+ };
95
+ writeCredentials(path, stored);
96
+ log.debug("Saved credentials to %s", path);
97
+ log.debug("Credentials MD5 hash: %s", md5hash(JSON.stringify(stored)));
98
+ return {
99
+ ...id ? { id } : {},
100
+ ...email ? { email } : {}
101
+ };
101
102
  };
102
103
  const handleExistingToken = async (tokenSet, props, credentialsPath) => {
103
- // Use existing access_token, if present and valid
104
- if (tokenSet.access_token && tokenSet.expires_at > Date.now()) {
105
- log.debug("Using existing valid access_token");
106
- const apiClient = getApiClient({
107
- apiKey: tokenSet.access_token,
108
- apiHost: props.apiHost,
109
- });
110
- return { apiKey: tokenSet.access_token, apiClient };
111
- }
112
- // Either access_token is missing or its expired. Refresh the token
113
- log.debug(tokenSet.expires_at < Date.now()
114
- ? "Token is expired, attempting refresh"
115
- : "Token is missing access_token, attempting refresh");
116
- if (!tokenSet.refresh_token) {
117
- log.debug("TokenSet is missing refresh_token, starting authentication");
118
- return null;
119
- }
120
- try {
121
- const refreshedTokenSet = await refreshToken({
122
- oauthHost: props.oauthHost,
123
- clientId: props.clientId,
124
- allowUnsafeTls: props.allowUnsafeTls,
125
- }, tokenSet);
126
- // Extend the token set with expires_at
127
- const extendedTokenSet = extendTokenSet(refreshedTokenSet);
128
- const apiKey = extendedTokenSet.access_token;
129
- const apiClient = getApiClient({
130
- apiKey,
131
- apiHost: props.apiHost,
132
- });
133
- await preserveCredentials(credentialsPath, extendedTokenSet, apiClient);
134
- log.debug("Token refresh successful");
135
- return { apiKey, apiClient };
136
- }
137
- catch (err) {
138
- const typedErr = err instanceof Error ? err : new Error("Unknown error");
139
- log.debug("Failed to refresh token: %s", typedErr.message);
140
- throw new Error("AUTH_REFRESH_FAILED");
141
- }
104
+ if (tokenSet.access_token && tokenSet.expires_at > Date.now()) {
105
+ log.debug("Using existing valid access_token");
106
+ const apiClient = getApiClient({
107
+ apiKey: tokenSet.access_token,
108
+ apiHost: props.apiHost
109
+ });
110
+ return {
111
+ apiKey: tokenSet.access_token,
112
+ apiClient
113
+ };
114
+ }
115
+ log.debug(tokenSet.expires_at < Date.now() ? "Token is expired, attempting refresh" : "Token is missing access_token, attempting refresh");
116
+ if (!tokenSet.refresh_token) {
117
+ log.debug("TokenSet is missing refresh_token, starting authentication");
118
+ return null;
119
+ }
120
+ try {
121
+ const refreshedTokenSet = await refreshToken({
122
+ oauthHost: props.oauthHost,
123
+ clientId: props.clientId,
124
+ allowUnsafeTls: props.allowUnsafeTls
125
+ }, tokenSet);
126
+ const extendedTokenSet = extendTokenSet(refreshedTokenSet);
127
+ const apiKey = extendedTokenSet.access_token;
128
+ const apiClient = getApiClient({
129
+ apiKey,
130
+ apiHost: props.apiHost
131
+ });
132
+ await preserveCredentials(credentialsPath, extendedTokenSet, apiClient);
133
+ log.debug("Token refresh successful");
134
+ return {
135
+ apiKey,
136
+ apiClient
137
+ };
138
+ } catch (err) {
139
+ const typedErr = err instanceof Error ? err : /* @__PURE__ */ new Error("Unknown error");
140
+ log.debug("Failed to refresh token: %s", typedErr.message);
141
+ throw new Error("AUTH_REFRESH_FAILED");
142
+ }
142
143
  };
143
144
  /**
144
- * The credential a stored file can authenticate with right now — its API key, or an OAuth
145
- * access token, refreshed first when it has expired. Never launches a browser.
146
- *
147
- * The `profile` subcommands need exactly this. They deliberately skip `ensureAuth`, but
148
- * `rotate-key` still has to call the API as the account it is rotating, and falling through to
149
- * an interactive login would be authenticating something other than what the command is about.
150
- */
151
- export const usableCredential = async (props, at) => {
152
- const credentialsPath = at.path;
153
- const stored = readCredentials(at);
154
- if (stored === null)
155
- return null;
156
- // A file that declares an unusable kind throws, rather than being reported as "no
157
- // credential" — the user needs to know it is broken, not that it is absent.
158
- const credential = interpretCredentials(stored, at);
159
- if (credential.kind === API_KEY) {
160
- return { apiKey: credential.apiKey, kind: API_KEY };
161
- }
162
- try {
163
- const result = await handleExistingToken(stored, props, credentialsPath);
164
- return result ? { apiKey: result.apiKey, kind: OAUTH } : null;
165
- }
166
- catch (err) {
167
- log.debug("Could not use the stored OAuth session at %s: %s", credentialsPath, err instanceof Error ? err.message : "unknown error");
168
- return null;
169
- }
145
+ * The credential a stored file can authenticate with right now — its API key, or an OAuth
146
+ * access token, refreshed first when it has expired. Never launches a browser.
147
+ *
148
+ * The `profile` subcommands need exactly this. They deliberately skip `ensureAuth`, but
149
+ * `rotate-key` still has to call the API as the account it is rotating, and falling through to
150
+ * an interactive login would be authenticating something other than what the command is about.
151
+ */
152
+ const usableCredential = async (props, at) => {
153
+ const credentialsPath = at.path;
154
+ const stored = readCredentials(at);
155
+ if (stored === null) return null;
156
+ const credential = interpretCredentials(stored, at);
157
+ if (credential.kind === "api_key") return {
158
+ apiKey: credential.apiKey,
159
+ kind: API_KEY
160
+ };
161
+ try {
162
+ const result = await handleExistingToken(stored, props, credentialsPath);
163
+ return result ? {
164
+ apiKey: result.apiKey,
165
+ kind: OAUTH
166
+ } : null;
167
+ } catch (err) {
168
+ log.debug("Could not use the stored OAuth session at %s: %s", credentialsPath, err instanceof Error ? err.message : "unknown error");
169
+ return null;
170
+ }
170
171
  };
171
- export const ensureAuth = async (props) => {
172
- // Skip auth for help command or no command
173
- if (props._.length === 0 || props.help) {
174
- return;
175
- }
176
- // `(config) status --current-branch` is a purely-local read of `.neon`; it must
177
- // never refresh a token or pop a browser login. Skip auth entirely (the handler
178
- // doesn't use an API client in this mode).
179
- if (isCurrentBranchProbe(props)) {
180
- return;
181
- }
182
- // `config init` only scaffolds a neon.ts and installs npm packages locally; it
183
- // never calls the Neon API, so skip auth entirely — no token refresh, no login.
184
- if (isConfigInit(props)) {
185
- return;
186
- }
187
- // `profile` reads and edits credential files on disk. Authenticating first would mean
188
- // a browser login just to list profiles, and would make a lapsed profile unremovable.
189
- if (isProfileCommand(props)) {
190
- return;
191
- }
192
- // `dev` runs a function locally. It injects the selected branch's env vars
193
- // when credentials happen to be available, but must never trigger an
194
- // interactive login: use an API key or existing stored credentials if
195
- // present, otherwise run with no API client (env injection is skipped).
196
- const isLocalDev = props._[0] === "dev";
197
- // `bootstrap` only copies a public template repo; it never calls the Neon
198
- // API, so it must work without credentials and must never pop a browser
199
- // login. It uses an API key / stored credentials when present (harmless),
200
- // otherwise it proceeds with no API client.
201
- const isBootstrap = props._[0] === "bootstrap";
202
- // `init` manages its own auth flow (asks the user if they have an account,
203
- // then triggers OAuth at the right time). Skip the global auth middleware.
204
- const isInit = props._[0] === "init";
205
- // `auth` writes a credential rather than using one, and reads `--profile` as the
206
- // destination to write it to. Running selection here would reject the flag pair it
207
- // accepts, and could resolve a stored key that this command must not authenticate with.
208
- if (props._[0] === "auth") {
209
- props.apiClient = getApiClient({
210
- apiKey: props.apiKey,
211
- apiHost: props.apiHost,
212
- });
213
- return;
214
- }
215
- // Throws when `--api-key` and `--profile` are both passed.
216
- const selection = selectCredential({
217
- ...credentialInputs(),
218
- profileFlag: props.profile,
219
- });
220
- const displaced = displacedProfileWarning(selection);
221
- if (displaced !== null) {
222
- log.warning(displaced);
223
- }
224
- if (selection.source !== "profile") {
225
- props.apiKey = selection.apiKey;
226
- log.debug("Using an API key to authorize requests");
227
- setAuthContext({
228
- source: "api-key",
229
- configDir: props.configDir,
230
- });
231
- props.apiClient = getApiClient({
232
- apiKey: props.apiKey,
233
- apiHost: props.apiHost,
234
- });
235
- return;
236
- }
237
- // An explicit `--profile` outranks an exported `NEON_API_KEY`, which the middleware has
238
- // already folded into `apiKey`. Clear it so nothing downstream — analytics, `dev`'s env
239
- // runtime, a re-exec — authenticates with a key this invocation decided against.
240
- props.apiKey = "";
241
- const credentialsPath = credentialsPathForName(props.configDir, selection.profile);
242
- const at = { path: credentialsPath, profile: selection.profile };
243
- const stored = readCredentials(at);
244
- if (stored !== null) {
245
- log.debug("Trying to read credentials from %s", credentialsPath);
246
- // Throws on a file whose declared kind is unusable. That is deliberate: falling
247
- // through to a browser login would replace the credential the user is fixing.
248
- const credential = interpretCredentials(stored, at);
249
- if (credential.kind === API_KEY) {
250
- log.debug('Using profile "%s"\'s API key to authorize requests', selection.profile);
251
- props.apiKey = credential.apiKey;
252
- setAuthContext({
253
- source: "profile-api-key",
254
- configDir: props.configDir,
255
- profile: selection.profile,
256
- credentialsPath,
257
- });
258
- props.apiClient = getApiClient({
259
- apiKey: credential.apiKey,
260
- apiHost: props.apiHost,
261
- });
262
- return;
263
- }
264
- try {
265
- const result = await handleExistingToken(stored, props, credentialsPath);
266
- if (result) {
267
- props.apiKey = result.apiKey;
268
- props.apiClient = result.apiClient;
269
- setAuthContext({
270
- source: "stored-credentials",
271
- configDir: props.configDir,
272
- profile: selection.profile,
273
- credentialsPath,
274
- });
275
- return;
276
- }
277
- }
278
- catch (err) {
279
- if (!(err instanceof Error && err.message === "AUTH_REFRESH_FAILED")) {
280
- throw err;
281
- }
282
- // A refresh that failed is recoverable by logging in again.
283
- log.debug("Ensure auth failed, starting authentication", err);
284
- }
285
- }
286
- else {
287
- log.debug("No usable credentials at %s, starting authentication", credentialsPath);
288
- }
289
- // `dev` never launches the interactive browser flow. With no usable
290
- // credentials it proceeds without an API client; env injection is skipped
291
- // and the function still runs locally.
292
- if (isLocalDev) {
293
- log.debug("dev: no usable credentials; running without env injection");
294
- return;
295
- }
296
- if (isBootstrap) {
297
- log.debug("bootstrap: no usable credentials; continuing without auth");
298
- return;
299
- }
300
- if (isInit) {
301
- log.debug("init: skipping global auth; init manages its own auth flow");
302
- return;
303
- }
304
- // Start new auth flow if no valid token exists or refresh failed. Pass the resolved
305
- // profile rather than the raw flag, so a name that came from `NEON_PROFILE` is written to
306
- // its own file instead of overwriting `DEFAULT`'s.
307
- const apiKey = await authFlow({ ...props, profile: selection.profile });
308
- props.apiKey = apiKey;
309
- props.apiClient = getApiClient({
310
- apiKey,
311
- apiHost: props.apiHost,
312
- });
313
- setAuthContext({
314
- source: "stored-credentials",
315
- configDir: props.configDir,
316
- profile: selection.profile,
317
- credentialsPath,
318
- });
172
+ const ensureAuth = async (props) => {
173
+ if (props._.length === 0 || props.help) return;
174
+ if (isCurrentBranchProbe(props)) return;
175
+ if (isConfigInit(props)) return;
176
+ if (isProfileCommand(props)) return;
177
+ const isLocalDev = props._[0] === "dev";
178
+ const isBootstrap = props._[0] === "bootstrap";
179
+ const isInit = props._[0] === "init";
180
+ if (props._[0] === "auth") {
181
+ props.apiClient = getApiClient({
182
+ apiKey: props.apiKey,
183
+ apiHost: props.apiHost
184
+ });
185
+ return;
186
+ }
187
+ const selection = selectCredential({
188
+ ...credentialInputs(),
189
+ profileFlag: props.profile
190
+ });
191
+ const displaced = displacedProfileWarning(selection);
192
+ if (displaced !== null) log.warning(displaced);
193
+ if (selection.source !== "profile") {
194
+ props.apiKey = selection.apiKey;
195
+ log.debug("Using an API key to authorize requests");
196
+ setAuthContext({
197
+ source: "api-key",
198
+ configDir: props.configDir
199
+ });
200
+ props.apiClient = getApiClient({
201
+ apiKey: props.apiKey,
202
+ apiHost: props.apiHost
203
+ });
204
+ return;
205
+ }
206
+ props.apiKey = "";
207
+ const credentialsPath = credentialsPathForName(props.configDir, selection.profile);
208
+ const at = {
209
+ path: credentialsPath,
210
+ profile: selection.profile
211
+ };
212
+ const stored = readCredentials(at);
213
+ if (stored !== null) {
214
+ log.debug("Trying to read credentials from %s", credentialsPath);
215
+ const credential = interpretCredentials(stored, at);
216
+ if (credential.kind === "api_key") {
217
+ log.debug("Using profile \"%s\"'s API key to authorize requests", selection.profile);
218
+ props.apiKey = credential.apiKey;
219
+ setAuthContext({
220
+ source: "profile-api-key",
221
+ configDir: props.configDir,
222
+ profile: selection.profile,
223
+ credentialsPath
224
+ });
225
+ props.apiClient = getApiClient({
226
+ apiKey: credential.apiKey,
227
+ apiHost: props.apiHost
228
+ });
229
+ return;
230
+ }
231
+ try {
232
+ const result = await handleExistingToken(stored, props, credentialsPath);
233
+ if (result) {
234
+ props.apiKey = result.apiKey;
235
+ props.apiClient = result.apiClient;
236
+ setAuthContext({
237
+ source: "stored-credentials",
238
+ configDir: props.configDir,
239
+ profile: selection.profile,
240
+ credentialsPath
241
+ });
242
+ return;
243
+ }
244
+ } catch (err) {
245
+ if (!(err instanceof Error && err.message === "AUTH_REFRESH_FAILED")) throw err;
246
+ log.debug("Ensure auth failed, starting authentication", err);
247
+ }
248
+ } else log.debug("No usable credentials at %s, starting authentication", credentialsPath);
249
+ if (isLocalDev) {
250
+ log.debug("dev: no usable credentials; running without env injection");
251
+ return;
252
+ }
253
+ if (isBootstrap) {
254
+ log.debug("bootstrap: no usable credentials; continuing without auth");
255
+ return;
256
+ }
257
+ if (isInit) {
258
+ log.debug("init: skipping global auth; init manages its own auth flow");
259
+ return;
260
+ }
261
+ const apiKey = await authFlow({
262
+ ...props,
263
+ profile: selection.profile
264
+ });
265
+ props.apiKey = apiKey;
266
+ props.apiClient = getApiClient({
267
+ apiKey,
268
+ apiHost: props.apiHost
269
+ });
270
+ setAuthContext({
271
+ source: "stored-credentials",
272
+ configDir: props.configDir,
273
+ profile: selection.profile,
274
+ credentialsPath
275
+ });
319
276
  };
320
277
  /**
321
- * Delete one credentials file — used by the 401 handler to clear an OAuth token set the API
322
- * has rejected, so the next command logs in again instead of failing the same way.
323
- *
324
- * It takes the exact path rather than a directory and an optional profile. Deriving the path
325
- * here meant re-running selection from partial state, and the 401 handler has no parsed
326
- * arguments: it passed only the config directory, so a rejected token on a
327
- * `--profile`-selected account deleted whatever `DEFAULT` pointed at — signing the user out
328
- * of an account whose credentials the failed request had never touched.
329
- */
330
- export const deleteCredentialsAt = (credentialsPath) => {
331
- try {
332
- if (existsSync(credentialsPath)) {
333
- rmSync(credentialsPath);
334
- log.info("Deleted credentials from %s", credentialsPath);
335
- }
336
- else {
337
- log.debug("Credentials file %s does not exist", credentialsPath);
338
- }
339
- }
340
- catch (err) {
341
- const typedErr = err instanceof Error ? err : new Error("Unknown error");
342
- log.error("Failed to delete credentials: %s", typedErr.message);
343
- throw new Error("CREDENTIALS_DELETE_FAILED");
344
- }
278
+ * Delete one credentials file — used by the 401 handler to clear an OAuth token set the API
279
+ * has rejected, so the next command logs in again instead of failing the same way.
280
+ *
281
+ * It takes the exact path rather than a directory and an optional profile. Deriving the path
282
+ * here meant re-running selection from partial state, and the 401 handler has no parsed
283
+ * arguments: it passed only the config directory, so a rejected token on a
284
+ * `--profile`-selected account deleted whatever `DEFAULT` pointed at — signing the user out
285
+ * of an account whose credentials the failed request had never touched.
286
+ */
287
+ const deleteCredentialsAt = (credentialsPath) => {
288
+ try {
289
+ if (existsSync(credentialsPath)) {
290
+ rmSync(credentialsPath);
291
+ log.info("Deleted credentials from %s", credentialsPath);
292
+ } else log.debug("Credentials file %s does not exist", credentialsPath);
293
+ } catch (err) {
294
+ const typedErr = err instanceof Error ? err : /* @__PURE__ */ new Error("Unknown error");
295
+ log.error("Failed to delete credentials: %s", typedErr.message);
296
+ throw new Error("CREDENTIALS_DELETE_FAILED");
297
+ }
345
298
  };
346
299
  const md5hash = (s) => createHash("md5").update(s).digest("hex");
300
+ //#endregion
301
+ export { aliases, authFlow, builder, command, credentialsPathForName, deleteCredentialsAt, describe, ensureAuth, handler, auth_exports as t, usableCredential };