neon 2.47.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +395 -0
  5. package/dist/_shared/env-core/reuse-secrets.js +179 -0
  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 +575 -658
  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 +642 -681
  28. package/dist/commands/diff.js +156 -200
  29. package/dist/commands/env.js +243 -303
  30. package/dist/commands/functions.js +275 -341
  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 -64
  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 +133 -147
  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 +9 -7
@@ -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 { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
+ import { API_KEY, OAUTH, interpretCredentials, readCredentials, writeCredentials } from "../_shared/credentials.js";
11
3
  import { isCi } from "../env.js";
12
4
  import { log } from "../log.js";
5
+ import { getApiClient } from "../api.js";
6
+ import { credentialsPath } from "../_shared/paths.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 { DEFAULT_PROFILE, assertProfilesUsable, assertValidProfileName, newProfileCredentialsPath, readProfiles, resolveProfile, selectProfileName, upsertProfile } from "../_shared/profiles.js";
13
+ import { credentialInputs, displacedProfileWarning, selectCredential } from "../_shared/auth_selection.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, auth_exports, builder, command, credentialsPathForName, deleteCredentialsAt, describe, ensureAuth, handler, usableCredential };