neonctl 2.27.0 → 2.28.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 (151) hide show
  1. package/LICENSE.md +178 -0
  2. package/README.md +33 -1
  3. package/{analytics.js → dist/analytics.js} +21 -5
  4. package/dist/api.js +665 -0
  5. package/dist/cli.js +9 -0
  6. package/{commands → dist/commands}/auth.js +7 -0
  7. package/{commands → dist/commands}/branches.js +7 -4
  8. package/{commands → dist/commands}/bucket.js +69 -37
  9. package/{commands → dist/commands}/checkout.js +3 -3
  10. package/{commands → dist/commands}/config.js +22 -0
  11. package/{commands → dist/commands}/connection_string.js +1 -1
  12. package/{commands → dist/commands}/data_api.js +5 -6
  13. package/{commands → dist/commands}/databases.js +6 -3
  14. package/{commands → dist/commands}/functions.js +7 -9
  15. package/{commands → dist/commands}/index.js +2 -0
  16. package/{commands → dist/commands}/link.js +10 -17
  17. package/{commands → dist/commands}/neon_auth.js +8 -11
  18. package/{commands → dist/commands}/projects.js +4 -4
  19. package/{commands → dist/commands}/psql.js +1 -1
  20. package/{commands → dist/commands}/roles.js +6 -3
  21. package/{commands → dist/commands}/schema_diff.js +3 -4
  22. package/dist/commands/status.js +40 -0
  23. package/{context.js → dist/context.js} +16 -0
  24. package/dist/current_branch_fast_path.js +55 -0
  25. package/dist/errors.js +80 -0
  26. package/{functions_api.js → dist/functions_api.js} +1 -1
  27. package/{index.js → dist/index.js} +21 -20
  28. package/{parameters.gen.js → dist/parameters.gen.js} +14 -14
  29. package/{psql → dist/psql}/cli.js +0 -0
  30. package/{storage_api.js → dist/storage_api.js} +7 -8
  31. package/{test_utils → dist/test_utils}/fixtures.js +45 -15
  32. package/dist/utils/api_enums.js +33 -0
  33. package/{utils → dist/utils}/branch_picker.js +1 -1
  34. package/{utils → dist/utils}/enrichers.js +11 -4
  35. package/package.json +64 -67
  36. package/api.js +0 -35
  37. package/cli.js +0 -2
  38. package/errors.js +0 -17
  39. /package/{auth.js → dist/auth.js} +0 -0
  40. /package/{callback.html → dist/callback.html} +0 -0
  41. /package/{commands → dist/commands}/bootstrap.js +0 -0
  42. /package/{commands → dist/commands}/deploy.js +0 -0
  43. /package/{commands → dist/commands}/dev.js +0 -0
  44. /package/{commands → dist/commands}/env.js +0 -0
  45. /package/{commands → dist/commands}/init.js +0 -0
  46. /package/{commands → dist/commands}/ip_allow.js +0 -0
  47. /package/{commands → dist/commands}/operations.js +0 -0
  48. /package/{commands → dist/commands}/orgs.js +0 -0
  49. /package/{commands → dist/commands}/set_context.js +0 -0
  50. /package/{commands → dist/commands}/user.js +0 -0
  51. /package/{commands → dist/commands}/vpc_endpoints.js +0 -0
  52. /package/{config.js → dist/config.js} +0 -0
  53. /package/{config_format.js → dist/config_format.js} +0 -0
  54. /package/{dev → dist/dev}/env.js +0 -0
  55. /package/{dev → dist/dev}/functions.js +0 -0
  56. /package/{dev → dist/dev}/inputs.js +0 -0
  57. /package/{dev → dist/dev}/runtime.js +0 -0
  58. /package/{env.js → dist/env.js} +0 -0
  59. /package/{env_file.js → dist/env_file.js} +0 -0
  60. /package/{help.js → dist/help.js} +0 -0
  61. /package/{log.js → dist/log.js} +0 -0
  62. /package/{pkg.js → dist/pkg.js} +0 -0
  63. /package/{psql → dist/psql}/command/cmd_cond.js +0 -0
  64. /package/{psql → dist/psql}/command/cmd_connect.js +0 -0
  65. /package/{psql → dist/psql}/command/cmd_copy.js +0 -0
  66. /package/{psql → dist/psql}/command/cmd_describe.js +0 -0
  67. /package/{psql → dist/psql}/command/cmd_format.js +0 -0
  68. /package/{psql → dist/psql}/command/cmd_io.js +0 -0
  69. /package/{psql → dist/psql}/command/cmd_lo.js +0 -0
  70. /package/{psql → dist/psql}/command/cmd_meta.js +0 -0
  71. /package/{psql → dist/psql}/command/cmd_misc.js +0 -0
  72. /package/{psql → dist/psql}/command/cmd_pipeline.js +0 -0
  73. /package/{psql → dist/psql}/command/cmd_restrict.js +0 -0
  74. /package/{psql → dist/psql}/command/cmd_show.js +0 -0
  75. /package/{psql → dist/psql}/command/dispatch.js +0 -0
  76. /package/{psql → dist/psql}/command/inputQueue.js +0 -0
  77. /package/{psql → dist/psql}/command/shared.js +0 -0
  78. /package/{psql → dist/psql}/complete/filenames.js +0 -0
  79. /package/{psql → dist/psql}/complete/index.js +0 -0
  80. /package/{psql → dist/psql}/complete/matcher.js +0 -0
  81. /package/{psql → dist/psql}/complete/psqlVars.js +0 -0
  82. /package/{psql → dist/psql}/complete/queries.js +0 -0
  83. /package/{psql → dist/psql}/complete/rules.js +0 -0
  84. /package/{psql → dist/psql}/core/common.js +0 -0
  85. /package/{psql → dist/psql}/core/help.js +0 -0
  86. /package/{psql → dist/psql}/core/mainloop.js +0 -0
  87. /package/{psql → dist/psql}/core/prompt.js +0 -0
  88. /package/{psql → dist/psql}/core/settings.js +0 -0
  89. /package/{psql → dist/psql}/core/sqlHelp.js +0 -0
  90. /package/{psql → dist/psql}/core/startup.js +0 -0
  91. /package/{psql → dist/psql}/core/syncVars.js +0 -0
  92. /package/{psql → dist/psql}/core/variables.js +0 -0
  93. /package/{psql → dist/psql}/describe/formatters.js +0 -0
  94. /package/{psql → dist/psql}/describe/processNamePattern.js +0 -0
  95. /package/{psql → dist/psql}/describe/queries.js +0 -0
  96. /package/{psql → dist/psql}/describe/versionGate.js +0 -0
  97. /package/{psql → dist/psql}/index.js +0 -0
  98. /package/{psql → dist/psql}/io/history.js +0 -0
  99. /package/{psql → dist/psql}/io/input.js +0 -0
  100. /package/{psql → dist/psql}/io/lineEditor/buffer.js +0 -0
  101. /package/{psql → dist/psql}/io/lineEditor/complete.js +0 -0
  102. /package/{psql → dist/psql}/io/lineEditor/filename.js +0 -0
  103. /package/{psql → dist/psql}/io/lineEditor/index.js +0 -0
  104. /package/{psql → dist/psql}/io/lineEditor/keymap.js +0 -0
  105. /package/{psql → dist/psql}/io/lineEditor/vt100.js +0 -0
  106. /package/{psql → dist/psql}/io/pgpass.js +0 -0
  107. /package/{psql → dist/psql}/io/pgservice.js +0 -0
  108. /package/{psql → dist/psql}/io/psqlrc.js +0 -0
  109. /package/{psql → dist/psql}/print/aligned.js +0 -0
  110. /package/{psql → dist/psql}/print/asciidoc.js +0 -0
  111. /package/{psql → dist/psql}/print/crosstab.js +0 -0
  112. /package/{psql → dist/psql}/print/csv.js +0 -0
  113. /package/{psql → dist/psql}/print/html.js +0 -0
  114. /package/{psql → dist/psql}/print/json.js +0 -0
  115. /package/{psql → dist/psql}/print/latex.js +0 -0
  116. /package/{psql → dist/psql}/print/pager.js +0 -0
  117. /package/{psql → dist/psql}/print/troff.js +0 -0
  118. /package/{psql → dist/psql}/print/unaligned.js +0 -0
  119. /package/{psql → dist/psql}/print/units.js +0 -0
  120. /package/{psql → dist/psql}/scanner/slash.js +0 -0
  121. /package/{psql → dist/psql}/scanner/sql.js +0 -0
  122. /package/{psql → dist/psql}/scanner/stringutils.js +0 -0
  123. /package/{psql → dist/psql}/types/backslash.js +0 -0
  124. /package/{psql → dist/psql}/types/connection.js +0 -0
  125. /package/{psql → dist/psql}/types/index.js +0 -0
  126. /package/{psql → dist/psql}/types/printer.js +0 -0
  127. /package/{psql → dist/psql}/types/repl.js +0 -0
  128. /package/{psql → dist/psql}/types/scanner.js +0 -0
  129. /package/{psql → dist/psql}/types/settings.js +0 -0
  130. /package/{psql → dist/psql}/types/variables.js +0 -0
  131. /package/{psql → dist/psql}/wire/connection.js +0 -0
  132. /package/{psql → dist/psql}/wire/copy.js +0 -0
  133. /package/{psql → dist/psql}/wire/notify.js +0 -0
  134. /package/{psql → dist/psql}/wire/pipeline.js +0 -0
  135. /package/{psql → dist/psql}/wire/protocol.js +0 -0
  136. /package/{psql → dist/psql}/wire/sasl.js +0 -0
  137. /package/{psql → dist/psql}/wire/tls.js +0 -0
  138. /package/{test_utils → dist/test_utils}/oauth_server.js +0 -0
  139. /package/{types.js → dist/types.js} +0 -0
  140. /package/{utils → dist/utils}/auth.js +0 -0
  141. /package/{utils → dist/utils}/branch_notice.js +0 -0
  142. /package/{utils → dist/utils}/compute_units.js +0 -0
  143. /package/{utils → dist/utils}/esbuild.js +0 -0
  144. /package/{utils → dist/utils}/formats.js +0 -0
  145. /package/{utils → dist/utils}/middlewares.js +0 -0
  146. /package/{utils → dist/utils}/point_in_time.js +0 -0
  147. /package/{utils → dist/utils}/psql.js +0 -0
  148. /package/{utils → dist/utils}/string.js +0 -0
  149. /package/{utils → dist/utils}/ui.js +0 -0
  150. /package/{utils → dist/utils}/zip.js +0 -0
  151. /package/{writer.js → dist/writer.js} +0 -0
package/dist/api.js ADDED
@@ -0,0 +1,665 @@
1
+ // Thin fetch-based client layer over `@neon/sdk` (the official, fetch-native
2
+ // Neon SDK). neonctl was originally built on the axios-based
3
+ // `@neondatabase/api-client`, whose generated `Api` object exposes one
4
+ // positional method per endpoint and resolves to an `AxiosResponse`. This module
5
+ // reproduces exactly the subset of `Api` methods neonctl uses, backed by the
6
+ // tree-shakeable `@neon/sdk/raw` functions, and returns a small
7
+ // `{ data, status, headers }` envelope the call sites destructure.
8
+ //
9
+ // On a non-2xx response (or a network/timeout failure) it throws a single typed
10
+ // {@link NeonApiError}; every call site narrows failures with
11
+ // {@link isNeonApiError} and reads `error.status` / `error.data`. There is no
12
+ // axios anywhere in neonctl: requests go through the global `fetch`, and this is
13
+ // the one place HTTP errors are shaped.
14
+ import { Readable } from 'node:stream';
15
+ import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici';
16
+ import { createClient, createConfig } from '@neon/sdk/raw';
17
+ import * as raw from '@neon/sdk/raw';
18
+ import { log } from './log.js';
19
+ import pkg from './pkg.js';
20
+ // Node's global `fetch` (undici) ignores HTTP_PROXY / HTTPS_PROXY / NO_PROXY,
21
+ // whereas the axios-based client neonctl used previously honored them. Restore
22
+ // that behaviour by installing a proxy-aware global dispatcher — but only when a
23
+ // proxy is actually configured, so the default (no-proxy) path stays untouched.
24
+ // This covers every `fetch` neonctl makes, including the direct S3 upload.
25
+ const PROXY_ENV_VARS = [
26
+ 'HTTP_PROXY',
27
+ 'http_proxy',
28
+ 'HTTPS_PROXY',
29
+ 'https_proxy',
30
+ 'ALL_PROXY',
31
+ 'all_proxy',
32
+ ];
33
+ if (PROXY_ENV_VARS.some((name) => process.env[name])) {
34
+ setGlobalDispatcher(new EnvHttpProxyAgent());
35
+ }
36
+ const DEFAULT_API_HOST = 'https://console.neon.tech/api/v2';
37
+ const REQUEST_TIMEOUT_MS = 60000;
38
+ const USER_AGENT = `neonctl v${pkg.version}`;
39
+ /** Mirrors the api-client `ContentType` enum used by the `request()` escape hatch. */
40
+ export var ContentType;
41
+ (function (ContentType) {
42
+ ContentType["Json"] = "application/json";
43
+ ContentType["FormData"] = "multipart/form-data";
44
+ ContentType["UrlEncoded"] = "application/x-www-form-urlencoded";
45
+ ContentType["Text"] = "text/plain";
46
+ })(ContentType || (ContentType = {}));
47
+ /**
48
+ * The single error type thrown by the neonctl API layer. It carries the HTTP
49
+ * status and parsed body for a non-2xx response, or a `code` (e.g. `ETIMEDOUT`)
50
+ * for a network/timeout failure. Call sites narrow with {@link isNeonApiError}
51
+ * and read `status` / `data` rather than reaching into an axios-shaped object.
52
+ */
53
+ export class NeonApiError extends Error {
54
+ constructor(message, init = {}) {
55
+ super(message);
56
+ this.name = 'NeonApiError';
57
+ this.status = init.status;
58
+ this.statusText = init.statusText;
59
+ this.data = init.data;
60
+ this.headers = init.headers;
61
+ this.requestPath = init.requestPath;
62
+ this.code = init.code;
63
+ }
64
+ }
65
+ /** Narrow an unknown error to a {@link NeonApiError}. */
66
+ export function isNeonApiError(err) {
67
+ return err instanceof NeonApiError;
68
+ }
69
+ /** Extract a `message` string from a parsed error body, if present. */
70
+ export function messageFromBody(body) {
71
+ if (body && typeof body === 'object' && 'message' in body) {
72
+ const message = body.message;
73
+ if (typeof message === 'string')
74
+ return message;
75
+ }
76
+ return undefined;
77
+ }
78
+ /** Extract a machine-readable `code` string from a parsed error body, if present. */
79
+ export function codeFromBody(body) {
80
+ if (body && typeof body === 'object' && 'code' in body) {
81
+ const code = body.code;
82
+ if (typeof code === 'string')
83
+ return code;
84
+ }
85
+ return undefined;
86
+ }
87
+ /**
88
+ * Adapt a WHATWG `ReadableStream` (what `fetch` gives us as `response.body`) to
89
+ * a Node `Readable`, so callers can `pipeline()` the body straight to disk. Done
90
+ * by hand rather than `Readable.fromWeb` to sidestep the DOM-vs-`node:stream/web`
91
+ * `ReadableStream` type friction without an unsafe cast.
92
+ */
93
+ function webStreamToNodeReadable(body) {
94
+ const reader = body.getReader();
95
+ return new Readable({
96
+ async read() {
97
+ try {
98
+ const { done, value } = await reader.read();
99
+ this.push(done ? null : Buffer.from(value));
100
+ }
101
+ catch (err) {
102
+ this.destroy(err instanceof Error ? err : new Error(String(err)));
103
+ }
104
+ },
105
+ });
106
+ }
107
+ function headersToObject(headers) {
108
+ const out = {};
109
+ headers.forEach((value, key) => {
110
+ out[key] = value;
111
+ });
112
+ return out;
113
+ }
114
+ function isAbortError(err) {
115
+ return (err instanceof Error &&
116
+ (err.name === 'AbortError' || err.name === 'TimeoutError'));
117
+ }
118
+ /**
119
+ * Walk an error's `cause` chain to find the underlying socket/DNS `code` (e.g.
120
+ * `ECONNREFUSED`, `ENOTFOUND`) — `fetch` surfaces these as the `cause` of a bare
121
+ * `TypeError: fetch failed`. Preserving the code lets `isNetworkError` classify
122
+ * the resulting {@link NeonApiError} as a connectivity failure.
123
+ */
124
+ function readSocketCode(err) {
125
+ let current = err;
126
+ for (let depth = 0; depth < 6 && current != null; depth++) {
127
+ if (typeof current === 'object' && 'code' in current) {
128
+ const code = current.code;
129
+ if (typeof code === 'string')
130
+ return code;
131
+ }
132
+ current = current.cause;
133
+ }
134
+ return undefined;
135
+ }
136
+ /** Build a {@link NeonApiError} from a non-2xx `Response` and its parsed body. */
137
+ function httpError(response, body) {
138
+ let requestPath;
139
+ try {
140
+ requestPath = new URL(response.url).pathname;
141
+ }
142
+ catch {
143
+ // response.url may be empty in some runtimes; the path is best-effort.
144
+ }
145
+ return new NeonApiError(messageFromBody(body) ??
146
+ `Request failed with status code ${response.status}`, {
147
+ status: response.status,
148
+ statusText: response.statusText,
149
+ data: body,
150
+ headers: headersToObject(response.headers),
151
+ requestPath,
152
+ });
153
+ }
154
+ /**
155
+ * Translate a thrown `fetch` failure into a {@link NeonApiError}. A request
156
+ * timeout uses `ECONNABORTED` (matching the old axios behaviour, and excluded
157
+ * from `isNetworkError`'s connectivity codes so it's still reported as a
158
+ * timeout). A connection failure keeps its real socket code (e.g.
159
+ * `ECONNREFUSED`) so `isNetworkError` recognises it as a connectivity problem.
160
+ */
161
+ function networkError(err) {
162
+ if (isAbortError(err)) {
163
+ return new NeonApiError('Request timed out', { code: 'ECONNABORTED' });
164
+ }
165
+ return new NeonApiError(err instanceof Error ? err.message : String(err), {
166
+ code: readSocketCode(err) ?? 'ENETWORK',
167
+ });
168
+ }
169
+ /**
170
+ * `fetch` with neonctl's request timeout applied (preserving any caller signal)
171
+ * and lightweight debug logging of the request line + response status — the
172
+ * fetch-native replacement for the old `axios-debug-log` wiring.
173
+ */
174
+ const timedFetch = async (input, init) => {
175
+ const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
176
+ const signal = init?.signal
177
+ ? AbortSignal.any([init.signal, timeout])
178
+ : timeout;
179
+ const method = init?.method ?? (input instanceof Request ? input.method : 'GET');
180
+ const url = input instanceof Request ? input.url : String(input);
181
+ log.debug('%s %s', method.toUpperCase(), url);
182
+ const response = await fetch(input, { ...init, signal });
183
+ log.debug('%d %s', response.status, response.statusText);
184
+ return response;
185
+ };
186
+ const RETRY_COUNT = 5;
187
+ const RETRY_DELAY = 3000;
188
+ /**
189
+ * Retry a call while the API answers 423 (Locked) — Neon's "a prior mutation on
190
+ * this resource is still in flight" signal.
191
+ */
192
+ export const retryOnLock = async (fn) => {
193
+ let attempt = 0;
194
+ let errOut;
195
+ while (attempt < RETRY_COUNT) {
196
+ try {
197
+ return await fn();
198
+ }
199
+ catch (err) {
200
+ errOut = err;
201
+ if (isNeonApiError(err) && err.status === 423) {
202
+ attempt++;
203
+ log.info(`Resource is locked. Waiting ${RETRY_DELAY}ms before retrying...`);
204
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
205
+ }
206
+ else {
207
+ throw err;
208
+ }
209
+ }
210
+ }
211
+ throw errOut;
212
+ };
213
+ function buildUrl(apiHost, path, query) {
214
+ const url = new URL(`${apiHost.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`);
215
+ if (query) {
216
+ for (const [key, value] of Object.entries(query)) {
217
+ if (value === undefined || value === null)
218
+ continue;
219
+ url.searchParams.set(key, String(value));
220
+ }
221
+ }
222
+ return url.toString();
223
+ }
224
+ async function readJsonBody(response) {
225
+ const text = await response.text();
226
+ if (text.trim() === '')
227
+ return undefined;
228
+ try {
229
+ return JSON.parse(text);
230
+ }
231
+ catch {
232
+ // Match axios' `responseType: 'json'` behaviour: a body that isn't valid
233
+ // JSON is surfaced as the raw string rather than coerced into an object, so
234
+ // callers' `body?.message` checks correctly see "no structured message".
235
+ return text;
236
+ }
237
+ }
238
+ export const getApiClient = ({ apiKey, apiHost }) => {
239
+ const baseUrl = apiHost ?? DEFAULT_API_HOST;
240
+ const client = createClient(createConfig({
241
+ auth: () => apiKey,
242
+ baseUrl,
243
+ fetch: timedFetch,
244
+ headers: { 'User-Agent': USER_AGENT },
245
+ }));
246
+ /** Await a raw call, unwrap to a `{ data, status, headers }` envelope, or throw {@link NeonApiError}. */
247
+ async function call(run) {
248
+ let result;
249
+ try {
250
+ result = await run();
251
+ }
252
+ catch (err) {
253
+ throw networkError(err);
254
+ }
255
+ const response = result.response;
256
+ if (!response) {
257
+ throw networkError(result.error ?? new Error('No response from Neon API'));
258
+ }
259
+ if (!response.ok) {
260
+ throw httpError(response, result.error ?? result.data);
261
+ }
262
+ return {
263
+ data: result.data,
264
+ status: response.status,
265
+ statusText: response.statusText,
266
+ headers: headersToObject(response.headers),
267
+ };
268
+ }
269
+ /**
270
+ * Low-level request used by the object-storage and functions helpers for
271
+ * endpoints not (yet) modeled as typed SDK functions. Mirrors the api-client
272
+ * `HttpClient.request()`: `format: 'json'` parses the body, `format: 'stream'`
273
+ * returns a Node `Readable`, and `type: ContentType.FormData` sends multipart.
274
+ */
275
+ async function request(params) {
276
+ const url = buildUrl(baseUrl, params.path, params.query);
277
+ const headers = { 'User-Agent': USER_AGENT };
278
+ if (params.secure !== false) {
279
+ headers.Authorization = `Bearer ${apiKey}`;
280
+ }
281
+ let payload;
282
+ if (params.body instanceof FormData ||
283
+ params.type === ContentType.FormData) {
284
+ payload = params.body;
285
+ }
286
+ else if (params.body !== undefined) {
287
+ headers['Content-Type'] = ContentType.Json;
288
+ payload = JSON.stringify(params.body);
289
+ }
290
+ let response;
291
+ try {
292
+ response = await timedFetch(url, {
293
+ method: params.method,
294
+ headers,
295
+ ...(payload !== undefined ? { body: payload } : {}),
296
+ });
297
+ }
298
+ catch (err) {
299
+ throw networkError(err);
300
+ }
301
+ if (!response.ok) {
302
+ // For a streamed download the error body arrives as a stream too; hand it
303
+ // back as a Node `Readable` so the caller can drain it for a message.
304
+ const errorBody = params.format === 'stream' && response.body
305
+ ? webStreamToNodeReadable(response.body)
306
+ : await readJsonBody(response);
307
+ throw httpError(response, errorBody);
308
+ }
309
+ let data;
310
+ if (params.format === 'stream') {
311
+ data = response.body
312
+ ? webStreamToNodeReadable(response.body)
313
+ : undefined;
314
+ }
315
+ else {
316
+ data = await readJsonBody(response);
317
+ }
318
+ return {
319
+ data: data,
320
+ status: response.status,
321
+ statusText: response.statusText,
322
+ headers: headersToObject(response.headers),
323
+ };
324
+ }
325
+ return {
326
+ request,
327
+ // ─── Account / user ──────────────────────────────────────────────────
328
+ getCurrentUserInfo: () => call(() => raw.getCurrentUserInfo({ client })),
329
+ getCurrentUserOrganizations: () => call(() => raw.getCurrentUserOrganizations({ client })),
330
+ getAuthDetails: () => call(() => raw.getAuthDetails({ client })),
331
+ getActiveRegions: () => call(() => raw.getActiveRegions({ client })),
332
+ // ─── Projects ────────────────────────────────────────────────────────
333
+ listProjects: (query = {}) => call(() => raw.listProjects({ client, query })),
334
+ listSharedProjects: (query = {}) => call(() => raw.listSharedProjects({
335
+ client,
336
+ query: {
337
+ ...(query.cursor !== undefined
338
+ ? { cursor: query.cursor }
339
+ : {}),
340
+ ...(query.limit !== undefined
341
+ ? { limit: query.limit }
342
+ : {}),
343
+ ...(query.search !== undefined
344
+ ? { search: query.search }
345
+ : {}),
346
+ },
347
+ })),
348
+ getProject: (projectId) => call(() => raw.getProject({ client, path: { project_id: projectId } })),
349
+ createProject: (data) => call(() => raw.createProject({ client, body: data })),
350
+ updateProject: (projectId, data) => call(() => raw.updateProject({
351
+ client,
352
+ path: { project_id: projectId },
353
+ body: data,
354
+ })),
355
+ deleteProject: (projectId) => call(() => raw.deleteProject({ client, path: { project_id: projectId } })),
356
+ recoverProject: (projectId) => call(() => raw.recoverProject({ client, path: { project_id: projectId } })),
357
+ listProjectOperations: ({ projectId, ...query }) => call(() => raw.listProjectOperations({
358
+ client,
359
+ path: { project_id: projectId },
360
+ query,
361
+ })),
362
+ // ─── Branches ────────────────────────────────────────────────────────
363
+ listProjectBranches: ({ projectId, ...query }) => call(() => raw.listProjectBranches({
364
+ client,
365
+ path: { project_id: projectId },
366
+ query,
367
+ })),
368
+ createProjectBranch: (projectId, data) => call(() => raw.createProjectBranch({
369
+ client,
370
+ path: { project_id: projectId },
371
+ ...(data !== undefined ? { body: data } : {}),
372
+ })),
373
+ getProjectBranch: (projectId, branchId) => call(() => raw.getProjectBranch({
374
+ client,
375
+ path: { project_id: projectId, branch_id: branchId },
376
+ })),
377
+ updateProjectBranch: (projectId, branchId, data) => call(() => raw.updateProjectBranch({
378
+ client,
379
+ path: { project_id: projectId, branch_id: branchId },
380
+ body: data,
381
+ })),
382
+ deleteProjectBranch: (projectId, branchId) => call(() => raw.deleteProjectBranch({
383
+ client,
384
+ path: { project_id: projectId, branch_id: branchId },
385
+ })),
386
+ restoreProjectBranch: (projectId, branchId, data) => call(() => raw.restoreProjectBranch({
387
+ client,
388
+ path: { project_id: projectId, branch_id: branchId },
389
+ body: data,
390
+ })),
391
+ setDefaultProjectBranch: (projectId, branchId) => call(() => raw.setDefaultProjectBranch({
392
+ client,
393
+ path: { project_id: projectId, branch_id: branchId },
394
+ })),
395
+ getProjectBranchSchema: ({ projectId, branchId, ...query }) => call(() => raw.getProjectBranchSchema({
396
+ client,
397
+ path: { project_id: projectId, branch_id: branchId },
398
+ query,
399
+ })),
400
+ listProjectBranchEndpoints: (projectId, branchId) => call(() => raw.listProjectBranchEndpoints({
401
+ client,
402
+ path: { project_id: projectId, branch_id: branchId },
403
+ })),
404
+ createProjectEndpoint: (projectId, data) => call(() => raw.createProjectEndpoint({
405
+ client,
406
+ path: { project_id: projectId },
407
+ body: data,
408
+ })),
409
+ // ─── Databases ───────────────────────────────────────────────────────
410
+ listProjectBranchDatabases: (projectId, branchId) => call(() => raw.listProjectBranchDatabases({
411
+ client,
412
+ path: { project_id: projectId, branch_id: branchId },
413
+ })),
414
+ createProjectBranchDatabase: (projectId, branchId, data) => call(() => raw.createProjectBranchDatabase({
415
+ client,
416
+ path: { project_id: projectId, branch_id: branchId },
417
+ body: data,
418
+ })),
419
+ deleteProjectBranchDatabase: (projectId, branchId, databaseName) => call(() => raw.deleteProjectBranchDatabase({
420
+ client,
421
+ path: {
422
+ project_id: projectId,
423
+ branch_id: branchId,
424
+ database_name: databaseName,
425
+ },
426
+ })),
427
+ // ─── Roles ───────────────────────────────────────────────────────────
428
+ listProjectBranchRoles: (projectId, branchId) => call(() => raw.listProjectBranchRoles({
429
+ client,
430
+ path: { project_id: projectId, branch_id: branchId },
431
+ })),
432
+ createProjectBranchRole: (projectId, branchId, data) => call(() => raw.createProjectBranchRole({
433
+ client,
434
+ path: { project_id: projectId, branch_id: branchId },
435
+ body: data,
436
+ })),
437
+ deleteProjectBranchRole: (projectId, branchId, roleName) => call(() => raw.deleteProjectBranchRole({
438
+ client,
439
+ path: {
440
+ project_id: projectId,
441
+ branch_id: branchId,
442
+ role_name: roleName,
443
+ },
444
+ })),
445
+ getProjectBranchRolePassword: (projectId, branchId, roleName) => call(() => raw.getProjectBranchRolePassword({
446
+ client,
447
+ path: {
448
+ project_id: projectId,
449
+ branch_id: branchId,
450
+ role_name: roleName,
451
+ },
452
+ })),
453
+ // ─── Data API ────────────────────────────────────────────────────────
454
+ createProjectBranchDataApi: (projectId, branchId, databaseName, data) => call(() => raw.createProjectBranchDataApi({
455
+ client,
456
+ path: {
457
+ project_id: projectId,
458
+ branch_id: branchId,
459
+ database_name: databaseName,
460
+ },
461
+ body: data,
462
+ })),
463
+ updateProjectBranchDataApi: (projectId, branchId, databaseName, data) => call(() => raw.updateProjectBranchDataApi({
464
+ client,
465
+ path: {
466
+ project_id: projectId,
467
+ branch_id: branchId,
468
+ database_name: databaseName,
469
+ },
470
+ body: data,
471
+ })),
472
+ deleteProjectBranchDataApi: (projectId, branchId, databaseName) => call(() => raw.deleteProjectBranchDataApi({
473
+ client,
474
+ path: {
475
+ project_id: projectId,
476
+ branch_id: branchId,
477
+ database_name: databaseName,
478
+ },
479
+ })),
480
+ getProjectBranchDataApi: (projectId, branchId, databaseName) => call(() => raw.getProjectBranchDataApi({
481
+ client,
482
+ path: {
483
+ project_id: projectId,
484
+ branch_id: branchId,
485
+ database_name: databaseName,
486
+ },
487
+ })),
488
+ // ─── VPC endpoints (project + organization) ──────────────────────────
489
+ listProjectVpcEndpoints: (projectId) => call(() => raw.listProjectVpcEndpoints({
490
+ client,
491
+ path: { project_id: projectId },
492
+ })),
493
+ assignProjectVpcEndpoint: (projectId, vpcEndpointId, data) => call(() => raw.assignProjectVpcEndpoint({
494
+ client,
495
+ path: {
496
+ project_id: projectId,
497
+ vpc_endpoint_id: vpcEndpointId,
498
+ },
499
+ body: data,
500
+ })),
501
+ deleteProjectVpcEndpoint: (projectId, vpcEndpointId) => call(() => raw.deleteProjectVpcEndpoint({
502
+ client,
503
+ path: {
504
+ project_id: projectId,
505
+ vpc_endpoint_id: vpcEndpointId,
506
+ },
507
+ })),
508
+ listOrganizationVpcEndpoints: (orgId, regionId) => call(() => raw.listOrganizationVpcEndpoints({
509
+ client,
510
+ path: { org_id: orgId, region_id: regionId },
511
+ })),
512
+ getOrganizationVpcEndpointDetails: (orgId, regionId, vpcEndpointId) => call(() => raw.getOrganizationVpcEndpointDetails({
513
+ client,
514
+ path: {
515
+ org_id: orgId,
516
+ region_id: regionId,
517
+ vpc_endpoint_id: vpcEndpointId,
518
+ },
519
+ })),
520
+ assignOrganizationVpcEndpoint: (orgId, regionId, vpcEndpointId, data) => call(() => raw.assignOrganizationVpcEndpoint({
521
+ client,
522
+ path: {
523
+ org_id: orgId,
524
+ region_id: regionId,
525
+ vpc_endpoint_id: vpcEndpointId,
526
+ },
527
+ body: data,
528
+ })),
529
+ deleteOrganizationVpcEndpoint: (orgId, regionId, vpcEndpointId) => call(() => raw.deleteOrganizationVpcEndpoint({
530
+ client,
531
+ path: {
532
+ org_id: orgId,
533
+ region_id: regionId,
534
+ vpc_endpoint_id: vpcEndpointId,
535
+ },
536
+ })),
537
+ // ─── Neon Auth ───────────────────────────────────────────────────────
538
+ getNeonAuth: (projectId, branchId) => call(() => raw.getNeonAuth({
539
+ client,
540
+ path: { project_id: projectId, branch_id: branchId },
541
+ })),
542
+ createNeonAuth: (projectId, branchId, data) => call(() => raw.createNeonAuth({
543
+ client,
544
+ path: { project_id: projectId, branch_id: branchId },
545
+ body: data,
546
+ })),
547
+ disableNeonAuth: (projectId, branchId, data) => call(() => raw.disableNeonAuth({
548
+ client,
549
+ path: { project_id: projectId, branch_id: branchId },
550
+ body: data,
551
+ })),
552
+ listBranchNeonAuthOauthProviders: (projectId, branchId) => call(() => raw.listBranchNeonAuthOauthProviders({
553
+ client,
554
+ path: { project_id: projectId, branch_id: branchId },
555
+ })),
556
+ addBranchNeonAuthOauthProvider: (projectId, branchId, data) => call(() => raw.addBranchNeonAuthOauthProvider({
557
+ client,
558
+ path: { project_id: projectId, branch_id: branchId },
559
+ body: data,
560
+ })),
561
+ updateBranchNeonAuthOauthProvider: (projectId, branchId, oauthProviderId, data) => call(() => raw.updateBranchNeonAuthOauthProvider({
562
+ client,
563
+ path: {
564
+ project_id: projectId,
565
+ branch_id: branchId,
566
+ oauth_provider_id: oauthProviderId,
567
+ },
568
+ body: data,
569
+ })),
570
+ deleteBranchNeonAuthOauthProvider: (projectId, branchId, oauthProviderId) => call(() => raw.deleteBranchNeonAuthOauthProvider({
571
+ client,
572
+ path: {
573
+ project_id: projectId,
574
+ branch_id: branchId,
575
+ oauth_provider_id: oauthProviderId,
576
+ },
577
+ })),
578
+ listBranchNeonAuthTrustedDomains: (projectId, branchId) => call(() => raw.listBranchNeonAuthTrustedDomains({
579
+ client,
580
+ path: { project_id: projectId, branch_id: branchId },
581
+ })),
582
+ addBranchNeonAuthTrustedDomain: (projectId, branchId, data) => call(() => raw.addBranchNeonAuthTrustedDomain({
583
+ client,
584
+ path: { project_id: projectId, branch_id: branchId },
585
+ body: data,
586
+ })),
587
+ deleteBranchNeonAuthTrustedDomain: (projectId, branchId, data) => call(() => raw.deleteBranchNeonAuthTrustedDomain({
588
+ client,
589
+ path: { project_id: projectId, branch_id: branchId },
590
+ body: data,
591
+ })),
592
+ getNeonAuthAllowLocalhost: (projectId, branchId) => call(() => raw.getNeonAuthAllowLocalhost({
593
+ client,
594
+ path: { project_id: projectId, branch_id: branchId },
595
+ })),
596
+ updateNeonAuthAllowLocalhost: (projectId, branchId, data) => call(() => raw.updateNeonAuthAllowLocalhost({
597
+ client,
598
+ path: { project_id: projectId, branch_id: branchId },
599
+ body: data,
600
+ })),
601
+ getNeonAuthEmailAndPasswordConfig: (projectId, branchId) => call(() => raw.getNeonAuthEmailAndPasswordConfig({
602
+ client,
603
+ path: { project_id: projectId, branch_id: branchId },
604
+ })),
605
+ updateNeonAuthEmailAndPasswordConfig: (projectId, branchId, data) => call(() => raw.updateNeonAuthEmailAndPasswordConfig({
606
+ client,
607
+ path: { project_id: projectId, branch_id: branchId },
608
+ body: data,
609
+ })),
610
+ getNeonAuthEmailProvider: (projectId, branchId) => call(() => raw.getNeonAuthEmailProvider({
611
+ client,
612
+ path: { project_id: projectId, branch_id: branchId },
613
+ })),
614
+ updateNeonAuthEmailProvider: (projectId, branchId, data) => call(() => raw.updateNeonAuthEmailProvider({
615
+ client,
616
+ path: { project_id: projectId, branch_id: branchId },
617
+ body: data,
618
+ })),
619
+ sendNeonAuthTestEmail: (projectId, branchId, data) => call(() => raw.sendNeonAuthTestEmail({
620
+ client,
621
+ path: { project_id: projectId, branch_id: branchId },
622
+ body: data,
623
+ })),
624
+ getNeonAuthPluginConfigs: (projectId, branchId) => call(() => raw.getNeonAuthPluginConfigs({
625
+ client,
626
+ path: { project_id: projectId, branch_id: branchId },
627
+ })),
628
+ updateNeonAuthOrganizationPlugin: (projectId, branchId, data) => call(() => raw.updateNeonAuthOrganizationPlugin({
629
+ client,
630
+ path: { project_id: projectId, branch_id: branchId },
631
+ body: data,
632
+ })),
633
+ getNeonAuthWebhookConfig: (projectId, branchId) => call(() => raw.getNeonAuthWebhookConfig({
634
+ client,
635
+ path: { project_id: projectId, branch_id: branchId },
636
+ })),
637
+ updateNeonAuthWebhookConfig: (projectId, branchId, data) => call(() => raw.updateNeonAuthWebhookConfig({
638
+ client,
639
+ path: { project_id: projectId, branch_id: branchId },
640
+ body: data,
641
+ })),
642
+ createBranchNeonAuthNewUser: (projectId, branchId, data) => call(() => raw.createBranchNeonAuthNewUser({
643
+ client,
644
+ path: { project_id: projectId, branch_id: branchId },
645
+ body: data,
646
+ })),
647
+ deleteBranchNeonAuthUser: (projectId, branchId, authUserId) => call(() => raw.deleteBranchNeonAuthUser({
648
+ client,
649
+ path: {
650
+ project_id: projectId,
651
+ branch_id: branchId,
652
+ auth_user_id: authUserId,
653
+ },
654
+ })),
655
+ updateNeonAuthUserRole: (projectId, branchId, authUserId, data) => call(() => raw.updateNeonAuthUserRole({
656
+ client,
657
+ path: {
658
+ project_id: projectId,
659
+ branch_id: branchId,
660
+ auth_user_id: authUserId,
661
+ },
662
+ body: data,
663
+ })),
664
+ };
665
+ };
package/dist/cli.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { tryCurrentBranchFastPath } from './current_branch_fast_path.js';
3
+ // Fast path for the offline `(config) status --current-branch` probe (used by shell
4
+ // prompts): read the pinned branch from `.neon` without loading the full command tree,
5
+ // api-client, and yargs (~200ms). Falls through to the full CLI for everything else, so
6
+ // the heavy `index.js` is imported lazily and only when actually needed.
7
+ if (!tryCurrentBranchFastPath(process.argv)) {
8
+ void import('./index.js');
9
+ }
@@ -3,6 +3,7 @@ import { join } from 'node:path';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { getApiClient } from '../api.js';
5
5
  import { auth, refreshToken } from '../auth.js';
6
+ import { isCurrentBranchProbe } from '../context.js';
6
7
  import { CREDENTIALS_FILE } from '../config.js';
7
8
  import { isCi } from '../env.js';
8
9
  import { log } from '../log.js';
@@ -100,6 +101,12 @@ export const ensureAuth = async (props) => {
100
101
  if (props._.length === 0 || props.help) {
101
102
  return;
102
103
  }
104
+ // `(config) status --current-branch` is a purely-local read of `.neon`; it must
105
+ // never refresh a token or pop a browser login. Skip auth entirely (the handler
106
+ // doesn't use an API client in this mode).
107
+ if (isCurrentBranchProbe(props)) {
108
+ return;
109
+ }
103
110
  // `dev` runs a function locally. It injects the selected branch's env vars
104
111
  // when credentials happen to be available, but must never trigger an
105
112
  // interactive login: use an API key or existing stored credentials if