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,180 +1,149 @@
1
1
  import { log } from "../log.js";
2
+ //#region src/utils/ai_gateway_notice.ts
2
3
  /**
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
- */
4
+ * Friendly guidance shown when a branch enables the AI Gateway (`preview.aiGateway`).
5
+ *
6
+ * The gateway is credential-gated, not provisioned: enabling it always mints a working
7
+ * branch credential, so `apply` / `checkout` / `env pull` succeed regardless of plan. The
8
+ * two things that *do* gate the gateway happen at serving time and are invisible in the
9
+ * provisioning result, so we surface them as a courtesy notice instead:
10
+ *
11
+ * - **Free plan** — credentials provision, but the gateway does not *serve* model requests.
12
+ * The account needs to upgrade to a paid plan.
13
+ * - **Reduced model set** — on a paid plan, an account still ramping up on the beta gets a
14
+ * trimmed model catalog (flagship models are listed but `enabled: false` on `/v1/models`).
15
+ * They can request access to more models.
16
+ *
17
+ * This is deliberately phrased for the user: it never mentions account "verification" or any
18
+ * other internal gating mechanism.
19
+ */
19
20
  /**
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"]);
21
+ * `BillingSubscriptionType` values that mean the account is on a Free plan (the gateway
22
+ * won't serve requests). Everything else — `launch`, `scale`, `business`, the `*_v3`
23
+ * variants, marketplace plans — is treated as paid.
24
+ */
25
+ const FREE_SUBSCRIPTION_TYPES = /* @__PURE__ */ new Set(["free_v2", "free_v3"]);
25
26
  /**
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";
27
+ * The Neon Console billing page to upgrade a Free-plan account so the gateway can serve. It's
28
+ * org-scoped when the project belongs to an org; projects on a personal account (which is
29
+ * where Free plans usually live) have no org id, so we fall back to the account-level page.
30
+ */
31
+ const aiGatewayUpgradeUrl = (orgId) => orgId ? `https://console.neon.tech/app/${orgId}/billing` : "https://console.neon.tech/app/billing";
33
32
  /**
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);
33
+ * The branch's AI Gateway page in the Neon Console — where a paid user with a reduced model
34
+ * set requests access to more models. It's branch-scoped, so it's built from the linked
35
+ * project and branch ids.
36
+ */
37
+ const aiGatewayModelsUrl = (projectId, branchId) => `https://console.neon.tech/app/projects/${projectId}/branches/${branchId}/ai-gateway`;
38
+ const isFreePlan = (subscriptionType) => subscriptionType !== void 0 && FREE_SUBSCRIPTION_TYPES.has(subscriptionType);
41
39
  /**
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"));
40
+ * Whether the model catalog includes at least one flagship model enabled. Flagship models
41
+ * (Anthropic Opus, OpenAI Codex / `*-pro`) are the first to be held back for an account still
42
+ * ramping up on the beta, so their total absence from a non-empty enabled set is the signal
43
+ * that the account has a reduced model set. Matched by id substring so it survives model
44
+ * version bumps (e.g. `claude-opus-4-8`).
45
+ */
46
+ const hasFlagshipModels = (modelIds) => modelIds.some((id) => id.includes("opus") || id.includes("codex") || id.endsWith("-pro"));
49
47
  /**
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}`;
48
+ * The message shown when a `neon.ts` that enables the AI Gateway is applied on a Free plan.
49
+ * Provisioning is refused up front (see {@link assertAiGatewayProvisionable}) because the
50
+ * gateway won't serve model requests until the account is on a paid plan. `upgradeUrl` is the
51
+ * org-scoped Console billing page (see {@link aiGatewayUpgradeUrl}).
52
+ */
53
+ const freePlanBlockMessage = (upgradeUrl) => `This neon.ts enables the AI Gateway, which isn't available on the Free plan — the gateway won't serve model requests. Upgrade to a paid plan and re-run, or remove \`preview.aiGateway\` from neon.ts. Upgrade here: ${upgradeUrl}`;
58
54
  /**
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;
55
+ * Build the AI Gateway courtesy notice for an account's plan and (optionally) its live model
56
+ * catalog, or `null` when nothing needs saying.
57
+ *
58
+ * `modelIds` is `undefined` when the catalog wasn't probed (e.g. a dry-run `plan`, or a probe
59
+ * that failed); in that case only the plan-based (Free) notice can be produced — never a
60
+ * false "reduced models" warning. `upgradeUrl` is the org-scoped Console billing page
61
+ * (Free notice); `moreModelsUrl` is the branch's Console AI Gateway page
62
+ * (see {@link aiGatewayModelsUrl}), shown when the catalog is reduced.
63
+ */
64
+ const buildAiGatewayNotice = ({ subscriptionType, modelIds, upgradeUrl, moreModelsUrl }) => {
65
+ if (isFreePlan(subscriptionType)) return {
66
+ level: "warning",
67
+ message: `AI Gateway is enabled, but the gateway does not serve model requests on the Free plan. Upgrade to a paid plan to start making requests: ${upgradeUrl}`
68
+ };
69
+ if (modelIds !== void 0 && modelIds.length > 0 && !hasFlagshipModels(modelIds)) return {
70
+ level: "warning",
71
+ message: `AI Gateway is in public beta and not every model is enabled for your account yet, so some models are missing from the catalog. Request access to more models here: ${moreModelsUrl}`
72
+ };
73
+ return null;
87
74
  };
88
75
  const isRecord = (value) => typeof value === "object" && value !== null;
89
76
  /**
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;
77
+ * Pull the `id`s of enabled models out of an OpenAI-compatible
78
+ * `{ object: "list", data: [{ id, enabled, ... }] }` body. The gateway lists every model in
79
+ * the catalog but marks ones the account can't serve yet with `enabled: false`; only the
80
+ * enabled set is used to detect a reduced catalog.
81
+ */
82
+ const extractEnabledModelIds = (body) => {
83
+ if (!isRecord(body) || !Array.isArray(body.data)) return null;
84
+ const ids = [];
85
+ for (const entry of body.data) if (isRecord(entry) && typeof entry.id === "string" && entry.enabled === true) ids.push(entry.id);
86
+ return ids;
107
87
  };
108
88
  /**
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
- }
89
+ * `GET {baseUrl}/v1/models` → the enabled model ids, or `null` if the catalog can't be read
90
+ * (network / HTTP / parse failure). Returning `null` keeps the notice silent rather than
91
+ * risking a false "reduced models" warning. `/v1/models` is served only on the unified
92
+ * dialect (the `/openai/v1` Responses dialect returns 404).
93
+ */
94
+ const fetchGatewayModelIds = async (baseUrl, token) => {
95
+ try {
96
+ const res = await fetch(`${baseUrl.replace(/\/+$/, "")}/v1/models`, { headers: { Authorization: `Bearer ${token}` } });
97
+ if (!res.ok) return null;
98
+ return extractEnabledModelIds(await res.json());
99
+ } catch {
100
+ return null;
101
+ }
126
102
  };
127
103
  /**
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
- }
104
+ * Refuse to provision the AI Gateway on a Free plan. Called before the `neon.ts` lifecycle
105
+ * commands provision a branch (`config apply` / `deploy`, `checkout`), so a Free-plan user
106
+ * gets a clear "upgrade first" error instead of a credential that can't serve requests.
107
+ *
108
+ * Best-effort on the plan lookup: if the plan can't be determined (network / API failure) we
109
+ * do NOT block, so a transient error never wrongly refuses a paid user's deploy. Only a
110
+ * positively-identified Free plan throws.
111
+ */
112
+ const assertAiGatewayProvisionable = async (params) => {
113
+ let subscriptionType;
114
+ let orgId;
115
+ try {
116
+ const { data } = await params.apiClient.getProject(params.projectId);
117
+ subscriptionType = data.project.owner?.subscription_type;
118
+ orgId = data.project.org_id ?? void 0;
119
+ } catch {
120
+ return;
121
+ }
122
+ if (isFreePlan(subscriptionType)) throw new Error(freePlanBlockMessage(aiGatewayUpgradeUrl(orgId)));
150
123
  };
151
124
  /**
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
- }
125
+ * Resolve the account's plan (and, when gateway credentials are on hand, its live model
126
+ * catalog) and print the AI Gateway courtesy notice — used by the `neon.ts` lifecycle and
127
+ * `env pull` whenever a branch has the gateway enabled.
128
+ *
129
+ * Pass `gateway` (base URL + token) to enable the reduced-model-set check; omit it (e.g. a
130
+ * dry-run `plan`) to get only the Free-plan notice. This is best-effort: any failure while
131
+ * fetching the plan or catalog is swallowed so it can never break the underlying command.
132
+ */
133
+ const warnAiGateway = async (params) => {
134
+ try {
135
+ const { data } = await params.apiClient.getProject(params.projectId);
136
+ const subscriptionType = data.project.owner?.subscription_type;
137
+ const orgId = data.project.org_id ?? void 0;
138
+ const modelIds = params.gateway ? await fetchGatewayModelIds(params.gateway.baseUrl, params.gateway.token) ?? void 0 : void 0;
139
+ const notice = buildAiGatewayNotice({
140
+ subscriptionType,
141
+ modelIds,
142
+ upgradeUrl: aiGatewayUpgradeUrl(orgId),
143
+ moreModelsUrl: aiGatewayModelsUrl(params.projectId, params.branchId)
144
+ });
145
+ if (notice) log.warning(notice.message);
146
+ } catch {}
180
147
  };
148
+ //#endregion
149
+ export { aiGatewayModelsUrl, aiGatewayUpgradeUrl, assertAiGatewayProvisionable, buildAiGatewayNotice, extractEnabledModelIds, fetchGatewayModelIds, freePlanBlockMessage, hasFlagshipModels, isFreePlan, warnAiGateway };
@@ -1,33 +1,26 @@
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",
1
+ //#region src/utils/api_enums.ts
2
+ const EndpointType = {
3
+ ReadOnly: "read_only",
4
+ ReadWrite: "read_write"
14
5
  };
15
- export const NeonAuthOauthProviderId = {
16
- Google: "google",
17
- Github: "github",
18
- Microsoft: "microsoft",
19
- Vercel: "vercel",
6
+ const NeonAuthOauthProviderId = {
7
+ Google: "google",
8
+ Github: "github",
9
+ Microsoft: "microsoft",
10
+ Vercel: "vercel"
20
11
  };
21
- export const NeonAuthOauthProviderType = {
22
- Standard: "standard",
23
- Shared: "shared",
12
+ const NeonAuthOauthProviderType = {
13
+ Standard: "standard",
14
+ Shared: "shared"
24
15
  };
25
- export const NeonAuthSupportedAuthProvider = {
26
- Mock: "mock",
27
- Stack: "stack",
28
- BetterAuth: "better_auth",
16
+ const NeonAuthSupportedAuthProvider = {
17
+ Mock: "mock",
18
+ Stack: "stack",
19
+ BetterAuth: "better_auth"
29
20
  };
30
- export const NeonAuthEmailVerificationMethod = {
31
- Link: "link",
32
- Otp: "otp",
21
+ const NeonAuthEmailVerificationMethod = {
22
+ Link: "link",
23
+ Otp: "otp"
33
24
  };
25
+ //#endregion
26
+ export { EndpointType, NeonAuthEmailVerificationMethod, NeonAuthOauthProviderId, NeonAuthOauthProviderType, NeonAuthSupportedAuthProvider };
@@ -1,5 +1,11 @@
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() };
1
+ //#region src/utils/auth.ts
2
+ const extendTokenSet = (tokenSet) => {
3
+ const exp = /* @__PURE__ */ new Date();
4
+ exp.setSeconds(exp.getSeconds() + (tokenSet.expires_in ?? 0));
5
+ return {
6
+ ...tokenSet,
7
+ expires_at: exp.getTime()
8
+ };
5
9
  };
10
+ //#endregion
11
+ export { extendTokenSet };
@@ -1,22 +1,23 @@
1
- import chalk from "chalk";
2
1
  import { log } from "../log.js";
2
+ import chalk from "chalk";
3
+ //#region src/utils/branch_notice.ts
3
4
  /**
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);
5
+ * Print a one-line "this command is targeting <branch>" notice to **stderr** so
6
+ * the user can sanity-check they're acting on the branch they think they are —
7
+ * before a `status` / `plan` / `apply` / `env pull` does its work. This is the
8
+ * cheap guardrail that catches "I planned against the wrong branch" / "I pulled
9
+ * env from the wrong branch" before it bites.
10
+ *
11
+ * - Skipped for machine-readable output (`--output json|yaml`) so it never has
12
+ * to be reasoned about by a script; it's stderr-only regardless, keeping
13
+ * `--output table` stdout clean for piping too.
14
+ * - `verb` is the leading phrase, e.g. `'Planning against branch'` →
15
+ * `→ Planning against branch main (br-…)`.
16
+ */
17
+ const announceTargetBranch = (props, branch, verb) => {
18
+ if (props.output === "json" || props.output === "yaml") return;
19
+ const suffix = branch.usedDefault ? chalk.dim(" · project default") : "";
20
+ log.info("%s %s %s %s%s", chalk.dim("→"), verb, chalk.cyan.bold(branch.branchName), chalk.dim(`(${branch.branchId})`), suffix);
22
21
  };
22
+ //#endregion
23
+ export { announceTargetBranch };
@@ -1,103 +1,97 @@
1
- import prompts from "prompts";
2
- import { retryOnLock } from "../api.js";
3
1
  import { isCi } from "../env.js";
4
2
  import { log } from "../log.js";
3
+ import { retryOnLock } from "../api.js";
5
4
  import { EndpointType } from "./api_enums.js";
5
+ import prompts from "prompts";
6
+ //#region src/utils/branch_picker.ts
6
7
  /** Sentinel `value` for the "create a new branch" choice (no branch id can collide). */
7
8
  const CREATE_BRANCH_CHOICE = Symbol("create-branch");
8
9
  /**
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
- */
10
+ * Render a branch's display name with the same word labels as `neonctl branch list`
11
+ * (`[default]`, `[protected]`) instead of symbols, so the picker reads clearly.
12
+ */
12
13
  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(" ");
14
+ const labels = [];
15
+ if (branch.default) labels.push("[default]");
16
+ if (branch.protected) labels.push("[protected]");
17
+ labels.push(branch.name);
18
+ return labels.join(" ");
22
19
  };
23
20
  /**
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 };
21
+ * Prompt the user to pick a branch from `branches`, with a "+ Create a new branch…" option
22
+ * pinned to the top (mirroring the project/org pickers). The default selection is the
23
+ * project's default branch (the create option sits at index 0, so the default index is
24
+ * offset by one).
25
+ *
26
+ * Throws `opts.nonInteractiveMessage` when there is no TTY (or in CI): the caller knows the
27
+ * right guidance for its command, so the message is supplied rather than hard-coded here.
28
+ */
29
+ const pickBranchInteractively = async (branches, opts) => {
30
+ if (isCi() || !process.stdout.isTTY) throw new Error(opts.nonInteractiveMessage);
31
+ const defaultBranchIndex = branches.findIndex((b) => b.default);
32
+ const initial = defaultBranchIndex >= 0 ? defaultBranchIndex + 1 : 0;
33
+ const { choice } = await prompts({
34
+ type: "select",
35
+ name: "choice",
36
+ message: opts.message,
37
+ choices: [{
38
+ title: "+ Create a new branch…",
39
+ value: CREATE_BRANCH_CHOICE
40
+ }, ...branches.map((b) => ({
41
+ title: `${branchLabel(b)} (${b.id})`,
42
+ value: b.id
43
+ }))],
44
+ initial
45
+ });
46
+ if (choice === void 0) throw new Error("Aborted: no branch selected.");
47
+ if (choice === CREATE_BRANCH_CHOICE) return {
48
+ kind: "create",
49
+ name: await promptNewBranchName(branches)
50
+ };
51
+ return {
52
+ kind: "existing",
53
+ branchId: choice
54
+ };
58
55
  };
59
56
  /**
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;
57
+ * Prompt for a new branch name, rejecting empty input and names already taken on the
58
+ * project (so we never silently select a different, pre-existing branch).
59
+ */
60
+ const promptNewBranchName = async (branches) => {
61
+ const existing = new Set(branches.map((b) => b.name));
62
+ const { name } = await prompts({
63
+ type: "text",
64
+ name: "name",
65
+ message: "New branch name:",
66
+ validate: (value) => {
67
+ const trimmed = value.trim();
68
+ if (trimmed === "") return "Branch name cannot be empty.";
69
+ if (existing.has(trimmed)) return `A branch named "${trimmed}" already exists.`;
70
+ return true;
71
+ }
72
+ });
73
+ const trimmed = typeof name === "string" ? name.trim() : "";
74
+ if (trimmed === "") throw new Error("Aborted: no branch name provided.");
75
+ return trimmed;
83
76
  };
84
77
  /**
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;
78
+ * Create a branch with the same defaults as `neonctl branch create --name <name>`:
79
+ * branched from the project's default branch with a read-write compute endpoint. Returns
80
+ * the new branch id.
81
+ */
82
+ const createBranch = async (apiClient, projectId, name, branches) => {
83
+ const defaultBranch = branches.find((b) => b.default);
84
+ if (!defaultBranch) throw new Error("No default branch found");
85
+ const { data } = await retryOnLock(() => apiClient.createProjectBranch(projectId, {
86
+ branch: {
87
+ name,
88
+ parent_id: defaultBranch.id
89
+ },
90
+ endpoints: [{ type: EndpointType.ReadWrite }]
91
+ }));
92
+ if (defaultBranch.protected) log.warning("The parent branch is protected; a unique role password has been generated for the new branch.");
93
+ log.info("Created branch %s (%s).", data.branch.name, data.branch.id);
94
+ return data.branch.id;
103
95
  };
96
+ //#endregion
97
+ export { createBranch, pickBranchInteractively, promptNewBranchName };