neonctl 2.37.0 → 2.38.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 (160) hide show
  1. package/{LICENSE.md → LICENSE} +27 -3
  2. package/README.md +15 -700
  3. package/bin/cli.js +3 -0
  4. package/package.json +8 -75
  5. package/dist/analytics.js +0 -168
  6. package/dist/api.js +0 -720
  7. package/dist/auth.js +0 -125
  8. package/dist/callback.html +0 -51
  9. package/dist/cli.js +0 -9
  10. package/dist/commands/api.js +0 -278
  11. package/dist/commands/auth.js +0 -214
  12. package/dist/commands/bootstrap.js +0 -481
  13. package/dist/commands/branches.js +0 -488
  14. package/dist/commands/bucket.js +0 -549
  15. package/dist/commands/checkout.js +0 -321
  16. package/dist/commands/config.js +0 -626
  17. package/dist/commands/connection_string.js +0 -172
  18. package/dist/commands/data_api.js +0 -284
  19. package/dist/commands/databases.js +0 -82
  20. package/dist/commands/deploy.js +0 -26
  21. package/dist/commands/dev.js +0 -698
  22. package/dist/commands/diff.js +0 -222
  23. package/dist/commands/env.js +0 -189
  24. package/dist/commands/functions.js +0 -373
  25. package/dist/commands/index.js +0 -62
  26. package/dist/commands/init.js +0 -73
  27. package/dist/commands/inspect.js +0 -65
  28. package/dist/commands/ip_allow.js +0 -137
  29. package/dist/commands/link.js +0 -1121
  30. package/dist/commands/neon_auth.js +0 -1028
  31. package/dist/commands/operations.js +0 -28
  32. package/dist/commands/orgs.js +0 -24
  33. package/dist/commands/projects.js +0 -413
  34. package/dist/commands/psql.js +0 -62
  35. package/dist/commands/roles.js +0 -65
  36. package/dist/commands/schema_diff.js +0 -151
  37. package/dist/commands/set_context.js +0 -29
  38. package/dist/commands/snapshots.js +0 -455
  39. package/dist/commands/status.js +0 -40
  40. package/dist/commands/user.js +0 -15
  41. package/dist/commands/vpc_endpoints.js +0 -134
  42. package/dist/config.js +0 -11
  43. package/dist/config_format.js +0 -72
  44. package/dist/context.js +0 -231
  45. package/dist/current_branch_fast_path.js +0 -55
  46. package/dist/dev/env.js +0 -244
  47. package/dist/dev/functions.js +0 -70
  48. package/dist/dev/inputs.js +0 -63
  49. package/dist/dev/runtime.js +0 -146
  50. package/dist/env.js +0 -36
  51. package/dist/env_file.js +0 -159
  52. package/dist/errors.js +0 -80
  53. package/dist/functions_api.js +0 -48
  54. package/dist/help.js +0 -146
  55. package/dist/index.js +0 -240
  56. package/dist/log.js +0 -18
  57. package/dist/parameters.gen.js +0 -480
  58. package/dist/pkg.js +0 -25
  59. package/dist/psql/cli.js +0 -53
  60. package/dist/psql/command/cmd_cond.js +0 -437
  61. package/dist/psql/command/cmd_connect.js +0 -820
  62. package/dist/psql/command/cmd_copy.js +0 -1035
  63. package/dist/psql/command/cmd_describe.js +0 -1815
  64. package/dist/psql/command/cmd_format.js +0 -948
  65. package/dist/psql/command/cmd_io.js +0 -2193
  66. package/dist/psql/command/cmd_lo.js +0 -393
  67. package/dist/psql/command/cmd_meta.js +0 -969
  68. package/dist/psql/command/cmd_misc.js +0 -187
  69. package/dist/psql/command/cmd_pipeline.js +0 -1148
  70. package/dist/psql/command/cmd_restrict.js +0 -171
  71. package/dist/psql/command/cmd_show.js +0 -766
  72. package/dist/psql/command/dispatch.js +0 -343
  73. package/dist/psql/command/inputQueue.js +0 -42
  74. package/dist/psql/command/shared.js +0 -71
  75. package/dist/psql/complete/filenames.js +0 -139
  76. package/dist/psql/complete/index.js +0 -104
  77. package/dist/psql/complete/matcher.js +0 -315
  78. package/dist/psql/complete/psqlVars.js +0 -249
  79. package/dist/psql/complete/queries.js +0 -493
  80. package/dist/psql/complete/rules.js +0 -2424
  81. package/dist/psql/core/common.js +0 -1253
  82. package/dist/psql/core/help.js +0 -576
  83. package/dist/psql/core/mainloop.js +0 -1360
  84. package/dist/psql/core/prompt.js +0 -439
  85. package/dist/psql/core/settings.js +0 -686
  86. package/dist/psql/core/sqlHelp.js +0 -1066
  87. package/dist/psql/core/startup.js +0 -846
  88. package/dist/psql/core/syncVars.js +0 -116
  89. package/dist/psql/core/variables.js +0 -287
  90. package/dist/psql/describe/formatters.js +0 -1330
  91. package/dist/psql/describe/processNamePattern.js +0 -270
  92. package/dist/psql/describe/queries.js +0 -2452
  93. package/dist/psql/describe/versionGate.js +0 -44
  94. package/dist/psql/index.js +0 -2030
  95. package/dist/psql/io/history.js +0 -299
  96. package/dist/psql/io/input.js +0 -120
  97. package/dist/psql/io/lineEditor/buffer.js +0 -325
  98. package/dist/psql/io/lineEditor/complete.js +0 -227
  99. package/dist/psql/io/lineEditor/filename.js +0 -159
  100. package/dist/psql/io/lineEditor/index.js +0 -893
  101. package/dist/psql/io/lineEditor/keymap.js +0 -745
  102. package/dist/psql/io/lineEditor/vt100.js +0 -363
  103. package/dist/psql/io/pgpass.js +0 -202
  104. package/dist/psql/io/pgservice.js +0 -194
  105. package/dist/psql/io/psqlrc.js +0 -422
  106. package/dist/psql/print/aligned.js +0 -1748
  107. package/dist/psql/print/asciidoc.js +0 -230
  108. package/dist/psql/print/crosstab.js +0 -463
  109. package/dist/psql/print/csv.js +0 -76
  110. package/dist/psql/print/html.js +0 -240
  111. package/dist/psql/print/json.js +0 -96
  112. package/dist/psql/print/latex.js +0 -379
  113. package/dist/psql/print/pager.js +0 -267
  114. package/dist/psql/print/troff.js +0 -240
  115. package/dist/psql/print/unaligned.js +0 -99
  116. package/dist/psql/print/units.js +0 -188
  117. package/dist/psql/scanner/slash.js +0 -515
  118. package/dist/psql/scanner/sql.js +0 -914
  119. package/dist/psql/scanner/stringutils.js +0 -394
  120. package/dist/psql/types/backslash.js +0 -1
  121. package/dist/psql/types/connection.js +0 -1
  122. package/dist/psql/types/index.js +0 -7
  123. package/dist/psql/types/printer.js +0 -1
  124. package/dist/psql/types/repl.js +0 -1
  125. package/dist/psql/types/scanner.js +0 -24
  126. package/dist/psql/types/settings.js +0 -1
  127. package/dist/psql/types/variables.js +0 -1
  128. package/dist/psql/wire/connection.js +0 -2857
  129. package/dist/psql/wire/copy.js +0 -108
  130. package/dist/psql/wire/notify.js +0 -59
  131. package/dist/psql/wire/pipeline.js +0 -521
  132. package/dist/psql/wire/protocol.js +0 -466
  133. package/dist/psql/wire/sasl.js +0 -294
  134. package/dist/psql/wire/tls.js +0 -602
  135. package/dist/storage_api.js +0 -147
  136. package/dist/test_utils/fixtures.js +0 -121
  137. package/dist/test_utils/oauth_server.js +0 -9
  138. package/dist/types.js +0 -1
  139. package/dist/utils/ai_gateway_notice.js +0 -180
  140. package/dist/utils/api_enums.js +0 -33
  141. package/dist/utils/auth.js +0 -5
  142. package/dist/utils/branch_notice.js +0 -22
  143. package/dist/utils/branch_picker.js +0 -103
  144. package/dist/utils/compute_units.js +0 -28
  145. package/dist/utils/config_diff.js +0 -185
  146. package/dist/utils/enrichers.js +0 -161
  147. package/dist/utils/esbuild.js +0 -158
  148. package/dist/utils/formats.js +0 -18
  149. package/dist/utils/git_diff.js +0 -90
  150. package/dist/utils/inspect_db.js +0 -153
  151. package/dist/utils/inspect_queries.js +0 -372
  152. package/dist/utils/middlewares.js +0 -20
  153. package/dist/utils/openapi.js +0 -114
  154. package/dist/utils/package_manager.js +0 -68
  155. package/dist/utils/point_in_time.js +0 -56
  156. package/dist/utils/psql.js +0 -120
  157. package/dist/utils/string.js +0 -5
  158. package/dist/utils/ui.js +0 -59
  159. package/dist/utils/zip.js +0 -4
  160. package/dist/writer.js +0 -97
@@ -1,147 +0,0 @@
1
- // Typed client helpers for the branch object-storage (bucket/object) API.
2
- //
3
- // These endpoints are part of the Neon object-storage surface (the "Buckets"
4
- // tag in the public API). They are not yet exposed as typed methods on
5
- // `@neon/sdk`, so the request/response types and the thin call helpers live
6
- // here. They are implemented on top of the API client's low-level `request()`
7
- // method, which reuses the exact same authentication, base URL, headers and
8
- // retry behaviour as every other neonctl command. When the SDK gains these
9
- // methods, the call sites in `src/commands/bucket.ts` can switch over with no
10
- // behavioural change.
11
- const bucketsPath = (projectId, branchId) => `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/buckets`;
12
- const bucketPath = (projectId, branchId, bucketName) => `${bucketsPath(projectId, branchId)}/${encodeURIComponent(bucketName)}`;
13
- /**
14
- * Create a bucket on a branch.
15
- *
16
- * @request POST /projects/{project_id}/branches/{branch_id}/buckets
17
- */
18
- export const createProjectBranchBucket = (apiClient, { projectId, branchId, name, accessLevel, }) => {
19
- const body = { name };
20
- // Omit access_level entirely so the server default (`private`) applies.
21
- if (accessLevel !== undefined) {
22
- body.access_level = accessLevel;
23
- }
24
- return apiClient.request({
25
- path: bucketsPath(projectId, branchId),
26
- method: "POST",
27
- body,
28
- format: "json",
29
- secure: true,
30
- });
31
- };
32
- /**
33
- * List the buckets on a branch.
34
- *
35
- * @request GET /projects/{project_id}/branches/{branch_id}/buckets
36
- */
37
- export const listProjectBranchBuckets = (apiClient, { projectId, branchId }) => apiClient.request({
38
- path: bucketsPath(projectId, branchId),
39
- method: "GET",
40
- format: "json",
41
- secure: true,
42
- });
43
- /**
44
- * Delete a bucket from a branch.
45
- *
46
- * @request DELETE /projects/{project_id}/branches/{branch_id}/buckets/{bucket_name}
47
- */
48
- export const deleteProjectBranchBucket = (apiClient, { projectId, branchId, bucketName, }) => apiClient.request({
49
- path: bucketPath(projectId, branchId, bucketName),
50
- method: "DELETE",
51
- secure: true,
52
- });
53
- /**
54
- * List objects (and collapsed folders) in a bucket on a branch.
55
- *
56
- * @request GET /projects/{project_id}/branches/{branch_id}/buckets/{bucket_name}/objects
57
- */
58
- export const listProjectBranchBucketObjects = (apiClient, { projectId, branchId, bucketName, ...query }) => apiClient.request({
59
- path: `${bucketPath(projectId, branchId, bucketName)}/objects`,
60
- method: "GET",
61
- query,
62
- format: "json",
63
- secure: true,
64
- });
65
- /**
66
- * Download an object's raw bytes from a bucket on a branch.
67
- *
68
- * The server returns the body as `application/octet-stream` with a
69
- * `Content-Disposition: attachment` header; the helper requests the body as a
70
- * stream (`responseType: 'stream'`), so `.data` is a Node `Readable` the caller
71
- * can pipe straight to disk without buffering the whole object in memory. The
72
- * response headers are returned alongside so the caller can derive a filename
73
- * from `Content-Disposition`.
74
- *
75
- * The object key may contain `/`; it is percent-encoded into a single path
76
- * segment so nested keys are routed to the `{object_key}` parameter.
77
- *
78
- * @request GET /projects/{project_id}/branches/{branch_id}/buckets/{bucket_name}/objects/{object_key}/download
79
- */
80
- export const getProjectBranchBucketObject = (apiClient, { projectId, branchId, bucketName, objectKey, }) => apiClient.request({
81
- path: `${bucketPath(projectId, branchId, bucketName)}/objects/${encodeURIComponent(objectKey)}/download`,
82
- method: "GET",
83
- format: "stream",
84
- secure: true,
85
- });
86
- /**
87
- * Delete an object from a bucket on a branch.
88
- *
89
- * The object key may contain `/`; it is percent-encoded into a single path
90
- * segment so nested keys are routed to the `{object_key}` parameter.
91
- *
92
- * @request DELETE /projects/{project_id}/branches/{branch_id}/buckets/{bucket_name}/objects/{object_key}
93
- */
94
- export const deleteProjectBranchBucketObject = (apiClient, { projectId, branchId, bucketName, objectKey, }) => apiClient.request({
95
- path: `${bucketPath(projectId, branchId, bucketName)}/objects/${encodeURIComponent(objectKey)}`,
96
- method: "DELETE",
97
- secure: true,
98
- });
99
- /**
100
- * Delete every object under a key prefix (folder) in a bucket on a branch.
101
- *
102
- * `prefix` must be non-empty and end with `/`; every object on this branch
103
- * whose key starts with the prefix is soft-deleted in a single call.
104
- *
105
- * @request DELETE /projects/{project_id}/branches/{branch_id}/buckets/{bucket_name}/objects-by-prefix
106
- */
107
- export const deleteProjectBranchBucketObjectsByPrefix = (apiClient, { projectId, branchId, bucketName, prefix, }) => apiClient.request({
108
- path: `${bucketPath(projectId, branchId, bucketName)}/objects-by-prefix`,
109
- method: "DELETE",
110
- query: { prefix },
111
- format: "json",
112
- secure: true,
113
- });
114
- /**
115
- * Request a presigned PUT URL for uploading an object to a bucket on a branch.
116
- *
117
- * Returns the URL, the headers that must accompany the PUT for the signature to
118
- * verify, and the expiry. The actual upload (a `PUT` to the returned `url` with
119
- * the returned `headers` and the file stream) is performed by the caller, NOT
120
- * through this api-client, since it targets the branch S3 data-plane endpoint
121
- * rather than the console API. No SigV4 or credential handling happens here.
122
- *
123
- * The object key may contain `/`; it is percent-encoded into a single path
124
- * segment so nested keys are routed to the `{object_key}` parameter.
125
- *
126
- * This targets the unified presign endpoint, passing `operation: "upload"` to
127
- * request a presigned PUT. (The same endpoint also serves `download`, but that
128
- * path is API-only and has no neonctl command.)
129
- *
130
- * @request POST /projects/{project_id}/branches/{branch_id}/buckets/{bucket_name}/objects/{object_key}/presign
131
- */
132
- export const presignUpload = (apiClient, { projectId, branchId, bucketName, objectKey, contentType, expiresInSeconds, }) => {
133
- const body = { operation: "upload" };
134
- if (contentType !== undefined) {
135
- body.content_type = contentType;
136
- }
137
- if (expiresInSeconds !== undefined) {
138
- body.expires_in_seconds = expiresInSeconds;
139
- }
140
- return apiClient.request({
141
- path: `${bucketPath(projectId, branchId, bucketName)}/objects/${encodeURIComponent(objectKey)}/presign`,
142
- method: "POST",
143
- body,
144
- format: "json",
145
- secure: true,
146
- });
147
- };
@@ -1,121 +0,0 @@
1
- import { fork } from "node:child_process";
2
- import { createServer } from "node:http";
3
- import { join } from "node:path";
4
- import emocks from "emocks";
5
- import express from "express";
6
- import strip from "strip-ansi";
7
- import { expect, test as originalTest } from "vitest";
8
- import { log } from "../log";
9
- /**
10
- * Reserve a localhost port and close its listener, returning a URL that is guaranteed to
11
- * refuse connections right now. Lets a test drive the CLI into a real `ECONNREFUSED`
12
- * (the same shape a network blip produces) deterministically and fast.
13
- */
14
- const reserveClosedPort = () => new Promise((resolve, reject) => {
15
- const probe = createServer();
16
- probe.on("error", reject);
17
- probe.listen(0, "127.0.0.1", () => {
18
- const { port } = probe.address();
19
- probe.close((err) => {
20
- if (err) {
21
- reject(err);
22
- }
23
- else {
24
- resolve(`http://127.0.0.1:${port}`);
25
- }
26
- });
27
- });
28
- });
29
- export const test = originalTest.extend({
30
- runMockServer: async ({}, use) => {
31
- let startedServer;
32
- await use(async (mockDir) => {
33
- const app = express();
34
- app.use(express.json());
35
- app.use("/", emocks(join(process.cwd(), "mocks", mockDir), {
36
- "404": (_req, res) => res.status(404).send({ message: "Not Found" }),
37
- }));
38
- const server = await new Promise((resolve) => {
39
- const s = app.listen(0, () => {
40
- log.debug("Mock server listening at %d", s.address().port);
41
- resolve(s);
42
- });
43
- });
44
- startedServer = server;
45
- return server;
46
- });
47
- // `unreachableHost` tests never start the server, so only close it when it ran.
48
- const server = startedServer;
49
- if (server) {
50
- await new Promise((resolve, reject) => {
51
- server.close((err) => {
52
- if (err) {
53
- reject(err instanceof Error ? err : new Error(String(err)));
54
- }
55
- else {
56
- resolve();
57
- }
58
- });
59
- });
60
- }
61
- },
62
- testCliCommand: async ({ runMockServer }, use) => {
63
- await use(async (args, options = {}) => {
64
- const apiHost = options.unreachableHost
65
- ? await reserveClosedPort()
66
- : `http://localhost:${(await runMockServer(options.mockDir || "main")).address().port}`;
67
- let output = "";
68
- let error = "";
69
- const cp = fork(join(process.cwd(), "./dist/index.js"), [
70
- "--api-host",
71
- apiHost,
72
- "--output",
73
- options.output ?? (options.outputTable ? "table" : "yaml"),
74
- "--api-key",
75
- "test-key",
76
- "--no-analytics",
77
- ...args,
78
- ], {
79
- stdio: "pipe",
80
- ...(options.cwd ? { cwd: options.cwd } : {}),
81
- env: {
82
- PATH: `${join(process.cwd(), "mocks/bin")}:${process.env.PATH}`,
83
- ...(options.env ?? {}),
84
- },
85
- });
86
- return new Promise((resolve, reject) => {
87
- cp.stdout?.on("data", (data) => {
88
- output += data.toString();
89
- });
90
- cp.stderr?.on("data", (data) => {
91
- error += data.toString();
92
- log.error(data.toString());
93
- });
94
- cp.on("error", (err) => {
95
- log.error(err);
96
- throw err;
97
- });
98
- cp.on("close", (code) => {
99
- try {
100
- expect(code).toBe(options?.code ?? 0);
101
- expect(output).toMatchSnapshot();
102
- if (options.stderr !== undefined) {
103
- expect(strip(error).replace(/\s+/g, " ").trim()).toEqual(typeof options.stderr === "string"
104
- ? options.stderr
105
- .toString()
106
- .replace(/\s+/g, " ")
107
- : options.stderr);
108
- }
109
- resolve();
110
- }
111
- catch (err) {
112
- reject(err instanceof Error ? err : new Error(String(err)));
113
- }
114
- });
115
- }).catch((err) => {
116
- log.error(err);
117
- throw err;
118
- });
119
- });
120
- },
121
- });
@@ -1,9 +0,0 @@
1
- import { OAuth2Server } from "oauth2-mock-server";
2
- import { log } from "../log";
3
- export const startOauthServer = async () => {
4
- const server = new OAuth2Server();
5
- await server.issuer.keys.generate("RS256");
6
- await server.start(0, "localhost");
7
- log.debug("Started OAuth server on port %d", server.address().port);
8
- return server;
9
- };
package/dist/types.js DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,180 +0,0 @@
1
- import { log } from "../log.js";
2
- /**
3
- * Friendly guidance shown when a branch enables the AI Gateway (`preview.aiGateway`).
4
- *
5
- * The gateway is credential-gated, not provisioned: enabling it always mints a working
6
- * branch credential, so `apply` / `checkout` / `env pull` succeed regardless of plan. The
7
- * two things that *do* gate the gateway happen at serving time and are invisible in the
8
- * provisioning result, so we surface them as a courtesy notice instead:
9
- *
10
- * - **Free plan** — credentials provision, but the gateway does not *serve* model requests.
11
- * The account needs to upgrade to a paid plan.
12
- * - **Reduced model set** — on a paid plan, an account still ramping up on the beta gets a
13
- * trimmed model catalog (flagship models are listed but `enabled: false` on `/v1/models`).
14
- * They can request access to more models.
15
- *
16
- * This is deliberately phrased for the user: it never mentions account "verification" or any
17
- * other internal gating mechanism.
18
- */
19
- /**
20
- * `BillingSubscriptionType` values that mean the account is on a Free plan (the gateway
21
- * won't serve requests). Everything else — `launch`, `scale`, `business`, the `*_v3`
22
- * variants, marketplace plans — is treated as paid.
23
- */
24
- const FREE_SUBSCRIPTION_TYPES = new Set(["free_v2", "free_v3"]);
25
- /**
26
- * The Neon Console billing page to upgrade a Free-plan account so the gateway can serve. It's
27
- * org-scoped when the project belongs to an org; projects on a personal account (which is
28
- * where Free plans usually live) have no org id, so we fall back to the account-level page.
29
- */
30
- export const aiGatewayUpgradeUrl = (orgId) => orgId
31
- ? `https://console.neon.tech/app/${orgId}/billing`
32
- : "https://console.neon.tech/app/billing";
33
- /**
34
- * The branch's AI Gateway page in the Neon Console — where a paid user with a reduced model
35
- * set requests access to more models. It's branch-scoped, so it's built from the linked
36
- * project and branch ids.
37
- */
38
- export const aiGatewayModelsUrl = (projectId, branchId) => `https://console.neon.tech/app/projects/${projectId}/branches/${branchId}/ai-gateway`;
39
- export const isFreePlan = (subscriptionType) => subscriptionType !== undefined &&
40
- FREE_SUBSCRIPTION_TYPES.has(subscriptionType);
41
- /**
42
- * Whether the model catalog includes at least one flagship model enabled. Flagship models
43
- * (Anthropic Opus, OpenAI Codex / `*-pro`) are the first to be held back for an account still
44
- * ramping up on the beta, so their total absence from a non-empty enabled set is the signal
45
- * that the account has a reduced model set. Matched by id substring so it survives model
46
- * version bumps (e.g. `claude-opus-4-8`).
47
- */
48
- export const hasFlagshipModels = (modelIds) => modelIds.some((id) => id.includes("opus") || id.includes("codex") || id.endsWith("-pro"));
49
- /**
50
- * The message shown when a `neon.ts` that enables the AI Gateway is applied on a Free plan.
51
- * Provisioning is refused up front (see {@link assertAiGatewayProvisionable}) because the
52
- * gateway won't serve model requests until the account is on a paid plan. `upgradeUrl` is the
53
- * org-scoped Console billing page (see {@link aiGatewayUpgradeUrl}).
54
- */
55
- export const freePlanBlockMessage = (upgradeUrl) => "This neon.ts enables the AI Gateway, which isn't available on the Free plan — the " +
56
- "gateway won't serve model requests. Upgrade to a paid plan and re-run, or remove " +
57
- `\`preview.aiGateway\` from neon.ts. Upgrade here: ${upgradeUrl}`;
58
- /**
59
- * Build the AI Gateway courtesy notice for an account's plan and (optionally) its live model
60
- * catalog, or `null` when nothing needs saying.
61
- *
62
- * `modelIds` is `undefined` when the catalog wasn't probed (e.g. a dry-run `plan`, or a probe
63
- * that failed); in that case only the plan-based (Free) notice can be produced — never a
64
- * false "reduced models" warning. `upgradeUrl` is the org-scoped Console billing page
65
- * (Free notice); `moreModelsUrl` is the branch's Console AI Gateway page
66
- * (see {@link aiGatewayModelsUrl}), shown when the catalog is reduced.
67
- */
68
- export const buildAiGatewayNotice = ({ subscriptionType, modelIds, upgradeUrl, moreModelsUrl, }) => {
69
- if (isFreePlan(subscriptionType)) {
70
- return {
71
- level: "warning",
72
- message: "AI Gateway is enabled, but the gateway does not serve model requests on the " +
73
- `Free plan. Upgrade to a paid plan to start making requests: ${upgradeUrl}`,
74
- };
75
- }
76
- if (modelIds !== undefined &&
77
- modelIds.length > 0 &&
78
- !hasFlagshipModels(modelIds)) {
79
- return {
80
- level: "warning",
81
- message: "AI Gateway is in public beta and not every model is enabled for your account " +
82
- "yet, so some models are missing from the catalog. Request access to more " +
83
- `models here: ${moreModelsUrl}`,
84
- };
85
- }
86
- return null;
87
- };
88
- const isRecord = (value) => typeof value === "object" && value !== null;
89
- /**
90
- * Pull the `id`s of enabled models out of an OpenAI-compatible
91
- * `{ object: "list", data: [{ id, enabled, ... }] }` body. The gateway lists every model in
92
- * the catalog but marks ones the account can't serve yet with `enabled: false`; only the
93
- * enabled set is used to detect a reduced catalog.
94
- */
95
- export const extractEnabledModelIds = (body) => {
96
- if (!isRecord(body) || !Array.isArray(body.data))
97
- return null;
98
- const ids = [];
99
- for (const entry of body.data) {
100
- if (isRecord(entry) &&
101
- typeof entry.id === "string" &&
102
- entry.enabled === true) {
103
- ids.push(entry.id);
104
- }
105
- }
106
- return ids;
107
- };
108
- /**
109
- * `GET {baseUrl}/v1/models` → the enabled model ids, or `null` if the catalog can't be read
110
- * (network / HTTP / parse failure). Returning `null` keeps the notice silent rather than
111
- * risking a false "reduced models" warning. `/v1/models` is served only on the unified
112
- * dialect (the `/openai/v1` Responses dialect returns 404).
113
- */
114
- export const fetchGatewayModelIds = async (baseUrl, token) => {
115
- try {
116
- const res = await fetch(`${baseUrl.replace(/\/+$/, "")}/v1/models`, {
117
- headers: { Authorization: `Bearer ${token}` },
118
- });
119
- if (!res.ok)
120
- return null;
121
- return extractEnabledModelIds(await res.json());
122
- }
123
- catch {
124
- return null;
125
- }
126
- };
127
- /**
128
- * Refuse to provision the AI Gateway on a Free plan. Called before the `neon.ts` lifecycle
129
- * commands provision a branch (`config apply` / `deploy`, `checkout`), so a Free-plan user
130
- * gets a clear "upgrade first" error instead of a credential that can't serve requests.
131
- *
132
- * Best-effort on the plan lookup: if the plan can't be determined (network / API failure) we
133
- * do NOT block, so a transient error never wrongly refuses a paid user's deploy. Only a
134
- * positively-identified Free plan throws.
135
- */
136
- export const assertAiGatewayProvisionable = async (params) => {
137
- let subscriptionType;
138
- let orgId;
139
- try {
140
- const { data } = await params.apiClient.getProject(params.projectId);
141
- subscriptionType = data.project.owner?.subscription_type;
142
- orgId = data.project.org_id ?? undefined;
143
- }
144
- catch {
145
- return; // Can't determine the plan — don't block.
146
- }
147
- if (isFreePlan(subscriptionType)) {
148
- throw new Error(freePlanBlockMessage(aiGatewayUpgradeUrl(orgId)));
149
- }
150
- };
151
- /**
152
- * Resolve the account's plan (and, when gateway credentials are on hand, its live model
153
- * catalog) and print the AI Gateway courtesy notice — used by the `neon.ts` lifecycle and
154
- * `env pull` whenever a branch has the gateway enabled.
155
- *
156
- * Pass `gateway` (base URL + token) to enable the reduced-model-set check; omit it (e.g. a
157
- * dry-run `plan`) to get only the Free-plan notice. This is best-effort: any failure while
158
- * fetching the plan or catalog is swallowed so it can never break the underlying command.
159
- */
160
- export const warnAiGateway = async (params) => {
161
- try {
162
- const { data } = await params.apiClient.getProject(params.projectId);
163
- const subscriptionType = data.project.owner?.subscription_type;
164
- const orgId = data.project.org_id ?? undefined;
165
- const modelIds = params.gateway
166
- ? ((await fetchGatewayModelIds(params.gateway.baseUrl, params.gateway.token)) ?? undefined)
167
- : undefined;
168
- const notice = buildAiGatewayNotice({
169
- subscriptionType,
170
- modelIds,
171
- upgradeUrl: aiGatewayUpgradeUrl(orgId),
172
- moreModelsUrl: aiGatewayModelsUrl(params.projectId, params.branchId),
173
- });
174
- if (notice)
175
- log.warning(notice.message);
176
- }
177
- catch {
178
- // A courtesy notice must never break the command that triggered it.
179
- }
180
- };
@@ -1,33 +0,0 @@
1
- // Runtime enum-like constants for Neon API string unions.
2
- //
3
- // `@neondatabase/api-client` generated TypeScript `enum`s (real runtime objects)
4
- // for fields like the compute endpoint type. `@neon/sdk` instead models these as
5
- // plain string-literal union *types*, which have no runtime value — so code that
6
- // read `EndpointType.ReadWrite` or `Object.values(EndpointType)` no longer works.
7
- //
8
- // These `as const` objects restore that runtime surface, and each is paired with
9
- // a same-named type whose union is identical to the corresponding `@neon/sdk`
10
- // type, so values stay assignable in both directions.
11
- export const EndpointType = {
12
- ReadOnly: "read_only",
13
- ReadWrite: "read_write",
14
- };
15
- export const NeonAuthOauthProviderId = {
16
- Google: "google",
17
- Github: "github",
18
- Microsoft: "microsoft",
19
- Vercel: "vercel",
20
- };
21
- export const NeonAuthOauthProviderType = {
22
- Standard: "standard",
23
- Shared: "shared",
24
- };
25
- export const NeonAuthSupportedAuthProvider = {
26
- Mock: "mock",
27
- Stack: "stack",
28
- BetterAuth: "better_auth",
29
- };
30
- export const NeonAuthEmailVerificationMethod = {
31
- Link: "link",
32
- Otp: "otp",
33
- };
@@ -1,5 +0,0 @@
1
- export const extendTokenSet = (tokenSet) => {
2
- const exp = new Date();
3
- exp.setSeconds(exp.getSeconds() + (tokenSet.expires_in ?? 0));
4
- return { ...tokenSet, expires_at: exp.getTime() };
5
- };
@@ -1,22 +0,0 @@
1
- import chalk from "chalk";
2
- import { log } from "../log.js";
3
- /**
4
- * Print a one-line "this command is targeting <branch>" notice to **stderr** so
5
- * the user can sanity-check they're acting on the branch they think they are —
6
- * before a `status` / `plan` / `apply` / `env pull` does its work. This is the
7
- * cheap guardrail that catches "I planned against the wrong branch" / "I pulled
8
- * env from the wrong branch" before it bites.
9
- *
10
- * - Skipped for machine-readable output (`--output json|yaml`) so it never has
11
- * to be reasoned about by a script; it's stderr-only regardless, keeping
12
- * `--output table` stdout clean for piping too.
13
- * - `verb` is the leading phrase, e.g. `'Planning against branch'` →
14
- * `→ Planning against branch main (br-…)`.
15
- */
16
- export const announceTargetBranch = (props, branch, verb) => {
17
- if (props.output === "json" || props.output === "yaml") {
18
- return;
19
- }
20
- const suffix = branch.usedDefault ? chalk.dim(" · project default") : "";
21
- log.info("%s %s %s %s%s", chalk.dim("→"), verb, chalk.cyan.bold(branch.branchName), chalk.dim(`(${branch.branchId})`), suffix);
22
- };
@@ -1,103 +0,0 @@
1
- import prompts from "prompts";
2
- import { retryOnLock } from "../api.js";
3
- import { isCi } from "../env.js";
4
- import { log } from "../log.js";
5
- import { EndpointType } from "./api_enums.js";
6
- /** Sentinel `value` for the "create a new branch" choice (no branch id can collide). */
7
- const CREATE_BRANCH_CHOICE = Symbol("create-branch");
8
- /**
9
- * Render a branch's display name with the same word labels as `neonctl branch list`
10
- * (`[default]`, `[protected]`) instead of symbols, so the picker reads clearly.
11
- */
12
- const branchLabel = (branch) => {
13
- const labels = [];
14
- if (branch.default) {
15
- labels.push("[default]");
16
- }
17
- if (branch.protected) {
18
- labels.push("[protected]");
19
- }
20
- labels.push(branch.name);
21
- return labels.join(" ");
22
- };
23
- /**
24
- * Prompt the user to pick a branch from `branches`, with a "+ Create a new branch…" option
25
- * pinned to the top (mirroring the project/org pickers). The default selection is the
26
- * project's default branch (the create option sits at index 0, so the default index is
27
- * offset by one).
28
- *
29
- * Throws `opts.nonInteractiveMessage` when there is no TTY (or in CI): the caller knows the
30
- * right guidance for its command, so the message is supplied rather than hard-coded here.
31
- */
32
- export const pickBranchInteractively = async (branches, opts) => {
33
- if (isCi() || !process.stdout.isTTY) {
34
- throw new Error(opts.nonInteractiveMessage);
35
- }
36
- const defaultBranchIndex = branches.findIndex((b) => b.default);
37
- const initial = defaultBranchIndex >= 0 ? defaultBranchIndex + 1 : 0;
38
- const { choice } = await prompts({
39
- type: "select",
40
- name: "choice",
41
- message: opts.message,
42
- choices: [
43
- { title: "+ Create a new branch…", value: CREATE_BRANCH_CHOICE },
44
- ...branches.map((b) => ({
45
- title: `${branchLabel(b)} (${b.id})`,
46
- value: b.id,
47
- })),
48
- ],
49
- initial,
50
- });
51
- if (choice === undefined) {
52
- throw new Error("Aborted: no branch selected.");
53
- }
54
- if (choice === CREATE_BRANCH_CHOICE) {
55
- return { kind: "create", name: await promptNewBranchName(branches) };
56
- }
57
- return { kind: "existing", branchId: choice };
58
- };
59
- /**
60
- * Prompt for a new branch name, rejecting empty input and names already taken on the
61
- * project (so we never silently select a different, pre-existing branch).
62
- */
63
- export const promptNewBranchName = async (branches) => {
64
- const existing = new Set(branches.map((b) => b.name));
65
- const { name } = await prompts({
66
- type: "text",
67
- name: "name",
68
- message: "New branch name:",
69
- validate: (value) => {
70
- const trimmed = value.trim();
71
- if (trimmed === "")
72
- return "Branch name cannot be empty.";
73
- if (existing.has(trimmed))
74
- return `A branch named "${trimmed}" already exists.`;
75
- return true;
76
- },
77
- });
78
- const trimmed = typeof name === "string" ? name.trim() : "";
79
- if (trimmed === "") {
80
- throw new Error("Aborted: no branch name provided.");
81
- }
82
- return trimmed;
83
- };
84
- /**
85
- * Create a branch with the same defaults as `neonctl branch create --name <name>`:
86
- * branched from the project's default branch with a read-write compute endpoint. Returns
87
- * the new branch id.
88
- */
89
- export const createBranch = async (apiClient, projectId, name, branches) => {
90
- const defaultBranch = branches.find((b) => b.default);
91
- if (!defaultBranch) {
92
- throw new Error("No default branch found");
93
- }
94
- const { data } = await retryOnLock(() => apiClient.createProjectBranch(projectId, {
95
- branch: { name, parent_id: defaultBranch.id },
96
- endpoints: [{ type: EndpointType.ReadWrite }],
97
- }));
98
- if (defaultBranch.protected) {
99
- log.warning("The parent branch is protected; a unique role password has been generated for the new branch.");
100
- }
101
- log.info("Created branch %s (%s).", data.branch.name, data.branch.id);
102
- return data.branch.id;
103
- };
@@ -1,28 +0,0 @@
1
- export const getComputeUnits = (autoscaling) => {
2
- const fixedSizeAutoscaling = Number(autoscaling);
3
- if (!isNaN(fixedSizeAutoscaling)) {
4
- return {
5
- autoscaling_limit_min_cu: fixedSizeAutoscaling,
6
- autoscaling_limit_max_cu: fixedSizeAutoscaling,
7
- };
8
- }
9
- if (!autoscaling.includes("-")) {
10
- throw new Error('Autoscaling should be either fixed size (e.g. 2) or min and max sizes delimited with a dash (e.g. "0.5-1")');
11
- }
12
- const [min, max] = autoscaling.split("-");
13
- if (!min || !max) {
14
- throw new Error('Autoscaling should be either fixed size (e.g. 2) or min and max sizes delimited with a dash (e.g. "0.5-1")');
15
- }
16
- const minAutoscaling = Number(min);
17
- const maxAutoscaling = Number(max);
18
- if (isNaN(minAutoscaling)) {
19
- throw new Error("Autoscaling min should be a number");
20
- }
21
- if (isNaN(maxAutoscaling)) {
22
- throw new Error("Autoscaling max should be a number");
23
- }
24
- return {
25
- autoscaling_limit_min_cu: minAutoscaling,
26
- autoscaling_limit_max_cu: maxAutoscaling,
27
- };
28
- };