neon 3.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +70 -5
  2. package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
  3. package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
  4. package/dist/_chunks/credentials-MYdHdKah.js +188 -0
  5. package/dist/_chunks/env-NbA61JR3.js +585 -0
  6. package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
  7. package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
  8. package/dist/_chunks/profiles-Ir29rqns.js +217 -0
  9. package/dist/_chunks/psql-DWH-kc69.js +2169 -0
  10. package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
  11. package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
  12. package/dist/analytics.js +163 -207
  13. package/dist/api.js +815 -758
  14. package/dist/auth.js +121 -141
  15. package/dist/auth_context.js +39 -53
  16. package/dist/cli.js +4 -7
  17. package/dist/commands/api.js +220 -250
  18. package/dist/commands/api_keys.js +251 -314
  19. package/dist/commands/auth.js +283 -328
  20. package/dist/commands/bootstrap.js +372 -437
  21. package/dist/commands/branches.js +304 -455
  22. package/dist/commands/bucket.js +374 -514
  23. package/dist/commands/checkout.js +213 -298
  24. package/dist/commands/config.js +573 -690
  25. package/dist/commands/connection_string.js +137 -165
  26. package/dist/commands/data_api.js +238 -260
  27. package/dist/commands/databases.js +67 -76
  28. package/dist/commands/deploy.js +31 -25
  29. package/dist/commands/dev.js +639 -719
  30. package/dist/commands/diff.js +156 -200
  31. package/dist/commands/env.js +255 -305
  32. package/dist/commands/functions.js +275 -355
  33. package/dist/commands/index.js +70 -65
  34. package/dist/commands/init.js +84 -119
  35. package/dist/commands/inspect.js +55 -55
  36. package/dist/commands/ip_allow.js +88 -120
  37. package/dist/commands/link.js +874 -1019
  38. package/dist/commands/logs.js +291 -0
  39. package/dist/commands/neon_auth.js +725 -933
  40. package/dist/commands/operations.js +34 -25
  41. package/dist/commands/orgs.js +28 -18
  42. package/dist/commands/profile.js +615 -846
  43. package/dist/commands/projects.js +313 -373
  44. package/dist/commands/psql.js +60 -58
  45. package/dist/commands/roles.js +55 -58
  46. package/dist/commands/schema_diff.js +87 -131
  47. package/dist/commands/set_context.js +34 -26
  48. package/dist/commands/snapshots.js +288 -413
  49. package/dist/commands/status.js +41 -37
  50. package/dist/commands/user.js +21 -10
  51. package/dist/commands/vpc_endpoints.js +85 -113
  52. package/dist/config.js +7 -6
  53. package/dist/config_format.js +50 -66
  54. package/dist/config_template.js +128 -157
  55. package/dist/context.js +183 -235
  56. package/dist/current_branch_fast_path.js +40 -49
  57. package/dist/dev/env.js +2 -446
  58. package/dist/dev/functions.js +54 -68
  59. package/dist/dev/inputs.js +46 -58
  60. package/dist/dev/runtime.js +135 -164
  61. package/dist/dev/websocket.js +766 -959
  62. package/dist/env.js +27 -33
  63. package/dist/env_file.js +118 -132
  64. package/dist/env_services.js +2 -51
  65. package/dist/errors.js +57 -68
  66. package/dist/functions_api.js +45 -43
  67. package/dist/help.js +189 -140
  68. package/dist/index.js +182 -257
  69. package/dist/init/agents.js +137 -118
  70. package/dist/init/auth.js +58 -68
  71. package/dist/init/bootstrap.js +325 -396
  72. package/dist/init/build_config.js +4 -2
  73. package/dist/init/detect_agent.js +56 -101
  74. package/dist/init/editors.js +35 -52
  75. package/dist/init/enrich_output.js +51 -66
  76. package/dist/init/extension.js +134 -171
  77. package/dist/init/inspect.js +179 -266
  78. package/dist/init/interactive.js +510 -622
  79. package/dist/init/neonctl.js +117 -168
  80. package/dist/init/orchestrate.js +157 -173
  81. package/dist/init/phases/auth.js +188 -202
  82. package/dist/init/phases/cleanup.js +23 -23
  83. package/dist/init/phases/db.js +251 -277
  84. package/dist/init/phases/getting_started.js +213 -223
  85. package/dist/init/phases/mcp.js +174 -224
  86. package/dist/init/phases/migrations.js +247 -248
  87. package/dist/init/phases/neon_auth.js +114 -133
  88. package/dist/init/phases/setup.js +546 -703
  89. package/dist/init/phases/skills.js +75 -86
  90. package/dist/init/phases/status.js +72 -67
  91. package/dist/init/resolve_context.js +102 -99
  92. package/dist/init/route_command.js +91 -98
  93. package/dist/init/skills.js +174 -218
  94. package/dist/init/vsix.js +77 -99
  95. package/dist/log.js +17 -16
  96. package/dist/neon_services.js +104 -129
  97. package/dist/parameters.gen.js +481 -471
  98. package/dist/pkg.js +17 -19
  99. package/dist/profile_keys.js +44 -47
  100. package/dist/psql/cli.js +44 -47
  101. package/dist/psql/command/cmd_cond.js +231 -406
  102. package/dist/psql/command/cmd_connect.js +557 -764
  103. package/dist/psql/command/cmd_copy.js +728 -984
  104. package/dist/psql/command/cmd_describe.js +1499 -1688
  105. package/dist/psql/command/cmd_format.js +733 -905
  106. package/dist/psql/command/cmd_io.js +2 -2193
  107. package/dist/psql/command/cmd_lo.js +297 -359
  108. package/dist/psql/command/cmd_meta.js +727 -878
  109. package/dist/psql/command/cmd_misc.js +138 -172
  110. package/dist/psql/command/cmd_pipeline.js +2 -1148
  111. package/dist/psql/command/cmd_restrict.js +119 -155
  112. package/dist/psql/command/cmd_show.js +529 -688
  113. package/dist/psql/command/dispatch.js +260 -325
  114. package/dist/psql/command/inputQueue.js +35 -33
  115. package/dist/psql/command/shared.js +49 -63
  116. package/dist/psql/complete/filenames.js +90 -133
  117. package/dist/psql/complete/index.js +59 -97
  118. package/dist/psql/complete/matcher.js +236 -300
  119. package/dist/psql/complete/psqlVars.js +218 -223
  120. package/dist/psql/complete/queries.js +159 -177
  121. package/dist/psql/complete/rules.js +1493 -2299
  122. package/dist/psql/core/common.js +2 -1253
  123. package/dist/psql/core/help.js +456 -546
  124. package/dist/psql/core/mainloop.js +692 -1303
  125. package/dist/psql/core/prompt.js +391 -408
  126. package/dist/psql/core/settings.js +429 -644
  127. package/dist/psql/core/sqlHelp.js +480 -554
  128. package/dist/psql/core/startup.js +2 -846
  129. package/dist/psql/core/syncVars.js +67 -110
  130. package/dist/psql/core/variables.js +156 -278
  131. package/dist/psql/describe/formatters.js +884 -1285
  132. package/dist/psql/describe/processNamePattern.js +173 -260
  133. package/dist/psql/describe/queries.js +1368 -2403
  134. package/dist/psql/describe/versionGate.js +32 -41
  135. package/dist/psql/index.js +2 -2030
  136. package/dist/psql/io/history.js +232 -271
  137. package/dist/psql/io/input.js +103 -108
  138. package/dist/psql/io/lineEditor/buffer.js +238 -319
  139. package/dist/psql/io/lineEditor/complete.js +135 -213
  140. package/dist/psql/io/lineEditor/filename.js +139 -148
  141. package/dist/psql/io/lineEditor/index.js +653 -870
  142. package/dist/psql/io/lineEditor/keymap.js +544 -702
  143. package/dist/psql/io/lineEditor/vt100.js +294 -341
  144. package/dist/psql/io/pgpass.js +158 -187
  145. package/dist/psql/io/pgservice.js +146 -183
  146. package/dist/psql/io/psqlrc.js +328 -403
  147. package/dist/psql/print/aligned.js +1020 -1683
  148. package/dist/psql/print/asciidoc.js +180 -214
  149. package/dist/psql/print/crosstab.js +281 -442
  150. package/dist/psql/print/csv.js +48 -70
  151. package/dist/psql/print/html.js +195 -226
  152. package/dist/psql/print/json.js +75 -88
  153. package/dist/psql/print/latex.js +291 -364
  154. package/dist/psql/print/pager.js +171 -242
  155. package/dist/psql/print/troff.js +194 -226
  156. package/dist/psql/print/unaligned.js +69 -95
  157. package/dist/psql/print/units.js +167 -169
  158. package/dist/psql/scanner/slash.js +428 -483
  159. package/dist/psql/scanner/sql.js +445 -889
  160. package/dist/psql/scanner/stringutils.js +309 -379
  161. package/dist/psql/types/index.js +8 -7
  162. package/dist/psql/types/scanner.js +25 -22
  163. package/dist/psql/wire/connection.js +2042 -2803
  164. package/dist/psql/wire/copy.js +84 -100
  165. package/dist/psql/wire/notify.js +39 -59
  166. package/dist/psql/wire/pipeline.js +305 -518
  167. package/dist/psql/wire/protocol.js +349 -417
  168. package/dist/psql/wire/sasl.js +180 -265
  169. package/dist/psql/wire/tls.js +400 -561
  170. package/dist/storage_api.js +115 -129
  171. package/dist/test_utils/fixtures.js +94 -113
  172. package/dist/test_utils/oauth_server.js +10 -7
  173. package/dist/test_utils/project_dir.js +33 -0
  174. package/dist/utils/ai_gateway_notice.js +131 -162
  175. package/dist/utils/api_enums.js +21 -28
  176. package/dist/utils/auth.js +10 -4
  177. package/dist/utils/branch_notice.js +20 -19
  178. package/dist/utils/branch_picker.js +83 -89
  179. package/dist/utils/cli_name.js +15 -12
  180. package/dist/utils/compute_units.js +20 -27
  181. package/dist/utils/config_diff.js +127 -158
  182. package/dist/utils/enrichers.js +95 -148
  183. package/dist/utils/esbuild.js +130 -189
  184. package/dist/utils/flags.js +35 -47
  185. package/dist/utils/formats.js +8 -15
  186. package/dist/utils/git_diff.js +69 -80
  187. package/dist/utils/inspect_db.js +101 -143
  188. package/dist/utils/inspect_queries.js +179 -142
  189. package/dist/utils/middlewares.js +39 -45
  190. package/dist/utils/openapi.js +87 -99
  191. package/dist/utils/package_manager.js +312 -110
  192. package/dist/utils/point_in_time.js +49 -53
  193. package/dist/utils/psql.js +89 -106
  194. package/dist/utils/service_picker.js +55 -58
  195. package/dist/utils/string.js +5 -5
  196. package/dist/utils/ui.js +38 -55
  197. package/dist/utils/write_sync.js +26 -35
  198. package/dist/utils/zip.js +4 -3
  199. package/dist/writer.js +67 -87
  200. package/package.json +11 -6
  201. package/dist/_shared/auth_selection.js +0 -86
  202. package/dist/_shared/credentials.js +0 -209
  203. package/dist/_shared/env-core/env.js +0 -558
  204. package/dist/_shared/env-core/reuse-secrets.js +0 -223
  205. package/dist/_shared/paths.js +0 -148
  206. package/dist/_shared/profiles.js +0 -276
  207. package/dist/_shared/secure_file.js +0 -43
@@ -1,331 +1,281 @@
1
- import { existsSync } from "node:fs";
2
- import chalk from "chalk";
3
- import { NEON_ENV_VAR_KEYS } from "../_shared/env-core/env.js";
1
+ import { t as __exportAll } from "../_chunks/rolldown-runtime-D7D4PA-g.js";
2
+ import { log } from "../log.js";
4
3
  import { ensureGitignored } from "../context.js";
5
- import { resolveNeonEnvVars } from "../dev/env.js";
4
+ import { getCliName } from "../utils/cli_name.js";
5
+ import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
6
6
  import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
7
- import { ENV_PULL_SERVICES, ENV_PULL_UNAVAILABLE, envServiceKeys, ownedEnvServiceKeys, } from "../env_services.js";
8
- import { log } from "../log.js";
9
- import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption, } from "../neon_services.js";
7
+ import { a as envKeysForSelection, c as ownedEnvServiceKeys, f as NEON_ENV_VAR_KEYS, i as ENV_PULL_UNAVAILABLE, l as parseEnvPullKeys, n as ENV_PULL_KEYS, o as envPullFlagValue, r as ENV_PULL_SERVICES } from "../_chunks/env_services-Tz9G4JeT.js";
8
+ import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
10
9
  import { warnAiGateway } from "../utils/ai_gateway_notice.js";
11
10
  import { announceTargetBranch } from "../utils/branch_notice.js";
12
- import { getCliName } from "../utils/cli_name.js";
13
- import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
14
- export const command = "env";
15
- export const describe = "Manage a branch's Neon env variables locally";
11
+ import { a as resolveNeonEnvVars } from "../_chunks/env-NbA61JR3.js";
12
+ import { existsSync } from "node:fs";
13
+ import chalk from "chalk";
14
+ //#region src/commands/env.ts
15
+ var env_exports = /* @__PURE__ */ __exportAll({
16
+ ENV_PULL_SKIPPED_HINT: () => ENV_PULL_SKIPPED_HINT,
17
+ autoPullEnvAfterPin: () => autoPullEnvAfterPin,
18
+ builder: () => builder,
19
+ command: () => "env",
20
+ describe: () => describe,
21
+ handler: () => handler,
22
+ pull: () => pull,
23
+ renderAgentPullNote: () => renderAgentPullNote
24
+ });
25
+ const command = "env";
26
+ const describe = "Manage a branch's Neon env variables locally";
16
27
  /**
17
- * Shown (to stderr) when `link` / `checkout` skip the bundled env pull because the user passed
18
- * `--no-env-pull`. Names the two ways to get the branch's vars without an on-disk file written
19
- * eagerly: an explicit `neonctl env pull`, or runtime injection via `neon-env run`.
20
- */
21
- export const ENV_PULL_SKIPPED_HINT = `Skipped env pull (--no-env-pull). Run \`${getCliName()} env pull\` to write this branch’s env vars ` +
22
- "(DATABASE_URL, …) into a local .env, or inject them at runtime with `neon-env run -- <your dev command>`.";
23
- export const builder = (argv) => argv
24
- .usage("$0 env <sub-command> [options]")
25
- .options({
26
- "project-id": { describe: "Project ID", type: "string" },
27
- branch: { describe: "Branch ID or name", type: "string" },
28
- })
29
- .middleware(fillSingleProject)
30
- .command("pull", "Write the branch's Neon env variables to a local .env file", (yargs) => yargs
31
- .usage("$0 env pull [options]")
32
- .options({
33
- file: {
34
- describe: "Target .env file to write. Defaults to an existing .env, " +
35
- "otherwise .env.local. Only Neon variables are updated; other " +
36
- "lines are preserved.",
37
- type: "string",
38
- },
39
- service: servicesOption({
40
- key: "service",
41
- allowed: ENV_PULL_SERVICES,
42
- describe: "Pull only these services' variables",
43
- also: "Overrides neon.ts, and prunes only within the services you name.",
44
- }),
45
- })
46
- .epilogue([
47
- "",
48
- "What gets pulled, in precedence order:",
49
- " 1. --service, when given — exactly those, ignoring neon.ts.",
50
- " 2. neon.ts, when this directory has one.",
51
- " 3. Otherwise everything the branch has, plus the AI Gateway —",
52
- " which mints a branch credential for it.",
53
- "",
54
- "The pull bundled into link / checkout / config apply follows 2 and 3",
55
- "without the AI Gateway, so it never mints a credential you did not ask",
56
- "for. Run `env pull` to add it.",
57
- ].join("\n"))
58
- .example("$0 env pull", "Write the linked branch's Neon vars into .env.local (or .env if present)")
59
- .example("$0 env pull --branch preview --file .env.preview", "Pull a specific branch into a specific file")
60
- .example("$0 env pull -s ai-gateway -s postgres", "Pull only the AI Gateway and Postgres variables"), async (args) => {
61
- const raw = servicesFlagValue(args.service);
62
- // Explicit `env pull` announces the branch it's reading from up front so the user
63
- // can catch "pulled env from the wrong branch" before it overwrites their .env. The
64
- // bundled auto-pull (link / checkout / apply) stays quiet — those already report the
65
- // branch they pinned/applied to.
66
- //
67
- // It also implies the AI Gateway when there is no neon.ts, so a bare `env pull`
68
- // really does write everything the branch can give you. The bundled auto-pull does
69
- // not: minting a credential for a service the user never named is not something a
70
- // side effect of `link` / `checkout` / `apply` should do.
71
- await pull({
72
- ...args,
73
- ...(raw
74
- ? {
75
- services: parseServices(raw, {
76
- allowed: ENV_PULL_SERVICES,
77
- whyUnavailable: ENV_PULL_UNAVAILABLE,
78
- flag: "--service",
79
- onDeprecated: (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical)),
80
- }),
81
- }
82
- : {}),
83
- }, { announce: true, implyAiGateway: raw === undefined });
84
- })
85
- .demandCommand(1);
86
- export const handler = (args) => args;
28
+ * Shown (to stderr) when `link` / `checkout` skip the bundled env pull because the user passed
29
+ * `--no-env-pull`. Names the two ways to get the branch's vars without an on-disk file written
30
+ * eagerly: an explicit `neonctl env pull`, or runtime injection via `neon-env run`.
31
+ */
32
+ const ENV_PULL_SKIPPED_HINT = `Skipped env pull (--no-env-pull). Run \`${getCliName()} env pull\` to write this branch’s env vars (DATABASE_URL, …) into a local .env, or inject them at runtime with \`neon-env run -- <your dev command>\`.`;
33
+ const builder = (argv) => argv.usage("$0 env <sub-command> [options]").options({
34
+ "project-id": {
35
+ describe: "Project ID",
36
+ type: "string"
37
+ },
38
+ branch: {
39
+ describe: "Branch ID or name",
40
+ type: "string"
41
+ }
42
+ }).middleware(fillSingleProject).command("pull", "Write the branch's Neon env variables to a local .env file", (yargs) => yargs.usage("$0 env pull [options]").options({
43
+ file: {
44
+ describe: "Target .env file to write. Defaults to an existing .env, otherwise .env.local. Only Neon variables are updated; other lines are preserved.",
45
+ type: "string"
46
+ },
47
+ service: servicesOption({
48
+ key: "service",
49
+ allowed: ENV_PULL_SERVICES,
50
+ describe: "Pull only these services' variables",
51
+ also: "Overrides neon.ts, and prunes only within the services you name. Combines with --env as a union."
52
+ }),
53
+ env: {
54
+ alias: "e",
55
+ describe: `Pull only these individual variables: ${ENV_PULL_KEYS.join(", ")}. Repeat the flag or comma-separate. Overrides neon.ts. Combines with --service as a union; it never narrows a service bundle.`,
56
+ type: "array",
57
+ string: true
58
+ }
59
+ }).epilogue([
60
+ "",
61
+ "What gets pulled, in precedence order:",
62
+ " 1. --service / --env, when given — their union, ignoring neon.ts.",
63
+ " 2. neon.ts, when this directory has one.",
64
+ " 3. Otherwise everything the branch has, plus the AI Gateway —",
65
+ " which mints a branch credential for it.",
66
+ "",
67
+ "The pull bundled into link / checkout / config apply follows 2 and 3",
68
+ "without the AI Gateway, so it never mints a credential you did not ask",
69
+ "for. Run `env pull` to add it."
70
+ ].join("\n")).example("$0 env pull", "Write the linked branch's Neon vars into .env.local (or .env if present)").example("$0 env pull --branch preview --file .env.preview", "Pull a specific branch into a specific file").example("$0 env pull -s ai-gateway -s postgres", "Pull only the AI Gateway and Postgres variables").example("$0 env pull -e DATABASE_URL", "Pull only the pooled Postgres connection string").example("$0 env pull -s auth -e DATABASE_URL", "Pull all Auth variables plus DATABASE_URL").strict(), async (args) => {
71
+ const rawServices = servicesFlagValue(args.service);
72
+ const rawEnvKeys = envPullFlagValue(args.env);
73
+ const services = rawServices ? parseServices(rawServices, {
74
+ allowed: ENV_PULL_SERVICES,
75
+ whyUnavailable: ENV_PULL_UNAVAILABLE,
76
+ flag: "--service",
77
+ onDeprecated: (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical))
78
+ }) : void 0;
79
+ const envKeys = rawEnvKeys ? parseEnvPullKeys(rawEnvKeys, "--env") : void 0;
80
+ await pull({
81
+ ...args,
82
+ ...services ? { services } : {},
83
+ ...envKeys ? { envKeys } : {}
84
+ }, {
85
+ announce: true,
86
+ implyAiGateway: rawServices === void 0 && rawEnvKeys === void 0
87
+ });
88
+ }).demandCommand(1);
89
+ const handler = (args) => args;
87
90
  /** Every OS-level env var name `@neon/env` can emit, used only for reporting. */
88
91
  const NEON_VAR_NAMES = Object.values(NEON_ENV_VAR_KEYS).flatMap((group) => Object.values(group));
89
92
  /**
90
- * The Neon env vars `env pull` *owns*, so it removes any that the branch no longer has when
91
- * it reconciles the local `.env` (see {@link pull}). Scoped to the unambiguously Neon-named
92
- * vars — the `NEON_*` aliases plus `DATABASE_URL[_UNPOOLED]` — so switching a working
93
- * directory to a project/branch without Auth / the Data API drops the now-stale
94
- * `NEON_AUTH_*` / `NEON_DATA_API_*` lines instead of leaving credentials for features that
95
- * aren't enabled.
96
- *
97
- * Deliberately **excludes** the storage vars Neon projects onto third-party SDK names
98
- * (`AWS_*`): those collide with credentials a user may set by hand, so `env pull` only ever
99
- * writes them, never prunes them. The AI Gateway is emitted solely under its Neon-branded
100
- * vars (`NEON_AI_GATEWAY_*`), which are owned and pruned.
101
- */
93
+ * The Neon env vars `env pull` *owns*, so it removes any that the branch no longer has when
94
+ * it reconciles the local `.env` (see {@link pull}). Scoped to the unambiguously Neon-named
95
+ * vars — the `NEON_*` aliases plus `DATABASE_URL[_UNPOOLED]` — so switching a working
96
+ * directory to a project/branch without Auth / the Data API drops the now-stale
97
+ * `NEON_AUTH_*` / `NEON_DATA_API_*` lines instead of leaving credentials for features that
98
+ * aren't enabled.
99
+ *
100
+ * Deliberately **excludes** the storage vars Neon projects onto third-party SDK names
101
+ * (`AWS_*`): those collide with credentials a user may set by hand, so `env pull` only ever
102
+ * writes them, never prunes them. The AI Gateway is emitted solely under its Neon-branded
103
+ * vars (`NEON_AI_GATEWAY_*`), which are owned and pruned.
104
+ */
102
105
  const NEON_OWNED_ENV_KEYS = [
103
- ...Object.values(NEON_ENV_VAR_KEYS.postgres),
104
- ...Object.values(NEON_ENV_VAR_KEYS.auth),
105
- ...Object.values(NEON_ENV_VAR_KEYS.dataApi),
106
- ...Object.values(NEON_ENV_VAR_KEYS.aiGateway),
106
+ ...Object.values(NEON_ENV_VAR_KEYS.postgres),
107
+ ...Object.values(NEON_ENV_VAR_KEYS.auth),
108
+ ...Object.values(NEON_ENV_VAR_KEYS.dataApi),
109
+ ...Object.values(NEON_ENV_VAR_KEYS.aiGateway)
107
110
  ];
108
- export const pull = async (props, opts = {}) => {
109
- const cwd = props.cwd ?? process.cwd();
110
- const branch = await resolveBranchRef(props);
111
- if (opts.announce) {
112
- announceTargetBranch(props, branch, "Pulling env from branch");
113
- }
114
- const branchId = branch.branchId;
115
- // Resolve the target file first and layer its current contents under the resolver's env
116
- // source. This lets `fetchEnv` reuse one-time secrets that are already on disk — Neon Auth
117
- // keys and the unified branch credential's `api_token` / `s3_secret_access_key`, which the
118
- // API returns exactly once — instead of minting a fresh credential on every pull.
119
- const targetPath = resolveEnvFilePath(cwd, props.file);
120
- const fileExisted = existsSync(targetPath);
121
- const existingEnv = fileExisted ? readEnvFile(targetPath) : {};
122
- // Reuse `neon dev`'s tiered resolver (neon.ts policy -> plan gate -> fetchEnv, else
123
- // pullConfig -> fetchEnv). Unlike dev, an unresolved context or failure is surfaced —
124
- // `env pull` is an explicit action, so it should error rather than write nothing.
125
- const { vars, credential, skipped } = await resolveNeonEnvVars({
126
- cwd,
127
- projectId: props.projectId,
128
- branchId,
129
- env: { ...process.env, ...existingEnv },
130
- ...(props.services ? { services: props.services } : {}),
131
- ...(opts.implyAiGateway ? { implyAiGateway: true } : {}),
132
- ...(props.apiKey ? { apiKey: props.apiKey } : {}),
133
- ...(props.apiHost ? { apiHost: props.apiHost } : {}),
134
- ...(props.runtimeApi ? { api: props.runtimeApi } : {}),
135
- });
136
- const neonVars = pickServiceVars(pickNeonVars(vars), props.services);
137
- if (Object.keys(neonVars).length === 0) {
138
- log.info("No Neon env variables to pull for this branch (no DATABASE_URL or " +
139
- "enabled Auth / Data API).");
140
- return { status: "empty" };
141
- }
142
- // Reconcile rather than blindly merge: write the branch's current Neon vars and prune any
143
- // Neon-owned vars the branch no longer has (e.g. NEON_AUTH_* / NEON_DATA_API_* carried over
144
- // from a previous project/branch). Non-Neon lines are always preserved.
145
- const { written, removed } = mergeEnvFile(targetPath, neonVars, {
146
- managedKeys: managedKeysFor(props.services, unreachedButCurrent(skipped, existingEnv, branchId)),
147
- });
148
- log.info("Pulled %d Neon variable%s into %s: %s", written.length, written.length === 1 ? "" : "s", targetPath, written.join(", "));
149
- if (removed.length > 0) {
150
- log.info("Removed %d stale Neon variable%s not enabled on this branch: %s", removed.length, removed.length === 1 ? "" : "s", removed.join(", "));
151
- }
152
- // A new credential means the values that back object storage / the AI Gateway just
153
- // changed, so anything else holding the old ones (a deployed preview, a second checkout)
154
- // needs the new values. Name the keys rather than leaving the user to diff the file.
155
- if (credential?.issued) {
156
- log.info("Issued a new branch credential — these now hold fresh values: %s", credential.keys.join(", "));
157
- if (credential.revoked.length > 0) {
158
- log.info("Revoked the credential it replaced (%s).", credential.revoked.join(", "));
159
- }
160
- else if (credential.superseded.length > 0) {
161
- // An unscoped pull revokes what it supersedes and says so above. A scoped one
162
- // cannot — it may not be the only service on that credential — so it leaves the
163
- // old one live. Say that too, rather than letting the identical-looking output
164
- // imply the branch is not accumulating credentials. Driven by what the resolver
165
- // actually declined to revoke, so a first pull (which supersedes nothing) does
166
- // not send the user hunting for a credential that was never there.
167
- log.info("Left the credential it replaced live (%s): a pull scoped with --service " +
168
- "can't tell which other services still use it. Revoke it in the Neon " +
169
- "Console if nothing does.", credential.superseded.join(", "));
170
- }
171
- }
172
- // A dotenv file *we* create holds live branch credentials (DATABASE_URL, Auth keys, service
173
- // tokens), so ignore it the same way the `.neon` context file is — otherwise a fresh repo is
174
- // one `git add -A` away from committing them. Only on creation: re-adding the entry on every
175
- // pull would fight a user who deliberately un-ignored a file they want to commit.
176
- if (!fileExisted) {
177
- ensureGitignored(targetPath);
178
- }
179
- // When the branch has the AI Gateway enabled, the pulled credentials always work, but
180
- // serving is plan-gated and the model set can be reduced on the beta — surface that as a
181
- // courtesy notice (best-effort; never fails the pull). The freshly pulled token lets us
182
- // probe the branch's own /v1/models to detect a reduced catalog.
183
- const gatewayBaseUrl = neonVars.NEON_AI_GATEWAY_BASE_URL;
184
- const gatewayToken = neonVars.NEON_AI_GATEWAY_TOKEN;
185
- if (gatewayBaseUrl && gatewayToken) {
186
- await warnAiGateway({
187
- apiClient: props.apiClient,
188
- projectId: props.projectId,
189
- branchId,
190
- gateway: { baseUrl: gatewayBaseUrl, token: gatewayToken },
191
- });
192
- }
193
- return {
194
- status: "written",
195
- written,
196
- file: targetPath,
197
- ...(credential && credential.keys.length > 0 ? { credential } : {}),
198
- ...(skipped && skipped.length > 0 ? { skipped } : {}),
199
- };
111
+ const pull = async (props, opts = {}) => {
112
+ const cwd = props.cwd ?? process.cwd();
113
+ const selectedKeys = props.services !== void 0 || props.envKeys !== void 0 ? envKeysForSelection(props.services ?? [], props.envKeys ?? []) : void 0;
114
+ const branch = await resolveBranchRef(props);
115
+ if (opts.announce) announceTargetBranch(props, branch, "Pulling env from branch");
116
+ const branchId = branch.branchId;
117
+ const targetPath = resolveEnvFilePath(cwd, props.file);
118
+ const fileExisted = existsSync(targetPath);
119
+ const existingEnv = fileExisted ? readEnvFile(targetPath) : {};
120
+ const { vars, credential, skipped } = await resolveNeonEnvVars({
121
+ cwd,
122
+ projectId: props.projectId,
123
+ branchId,
124
+ env: {
125
+ ...process.env,
126
+ ...existingEnv
127
+ },
128
+ ...props.services ? { services: props.services } : {},
129
+ ...props.envKeys ? { envKeys: props.envKeys } : {},
130
+ ...opts.implyAiGateway ? { implyAiGateway: true } : {},
131
+ ...props.apiKey ? { apiKey: props.apiKey } : {},
132
+ ...props.apiHost ? { apiHost: props.apiHost } : {},
133
+ ...props.runtimeApi ? { api: props.runtimeApi } : {}
134
+ });
135
+ const neonVars = pickSelectedVars(pickNeonVars(vars), selectedKeys);
136
+ if (Object.keys(neonVars).length === 0) {
137
+ log.info("No Neon env variables to pull for this branch (no DATABASE_URL or enabled Auth / Data API).");
138
+ return { status: "empty" };
139
+ }
140
+ const { written, removed } = mergeEnvFile(targetPath, neonVars, { managedKeys: managedKeysFor(selectedKeys, unreachedButCurrent(skipped, existingEnv, branchId)) });
141
+ log.info("Pulled %d Neon variable%s into %s: %s", written.length, written.length === 1 ? "" : "s", targetPath, written.join(", "));
142
+ if (removed.length > 0) log.info("Removed %d stale Neon variable%s not enabled on this branch: %s", removed.length, removed.length === 1 ? "" : "s", removed.join(", "));
143
+ if (credential?.issued) {
144
+ log.info("Issued a new branch credential — these now hold fresh values: %s", credential.keys.join(", "));
145
+ if (credential.revoked.length > 0) log.info("Revoked the credential it replaced (%s).", credential.revoked.join(", "));
146
+ else if (credential.superseded.length > 0) log.info("Left the credential it replaced live (%s): an explicitly scoped pull can't tell which other variables still use it. Revoke it in the Neon Console if nothing does.", credential.superseded.join(", "));
147
+ }
148
+ if (!fileExisted) ensureGitignored(targetPath);
149
+ const gatewayBaseUrl = neonVars.NEON_AI_GATEWAY_BASE_URL;
150
+ const gatewayToken = neonVars.NEON_AI_GATEWAY_TOKEN;
151
+ if (gatewayBaseUrl && gatewayToken) await warnAiGateway({
152
+ apiClient: props.apiClient,
153
+ projectId: props.projectId,
154
+ branchId,
155
+ gateway: {
156
+ baseUrl: gatewayBaseUrl,
157
+ token: gatewayToken
158
+ }
159
+ });
160
+ return {
161
+ status: "written",
162
+ written,
163
+ file: targetPath,
164
+ ...credential && credential.keys.length > 0 ? { credential } : {},
165
+ ...skipped && skipped.length > 0 ? { skipped } : {}
166
+ };
200
167
  };
201
168
  /**
202
- * The keys this pull is allowed to prune, i.e. the ones it is authoritative for.
203
- *
204
- * A `--service` selection narrows that to the services it named: `env pull -s ai-gateway`
205
- * says nothing about `DATABASE_URL`, so it must not read that variable's absence from this
206
- * pull as "the branch no longer has it". `unreached` is subtracted for the same reason — see
207
- * {@link unreachedButCurrent}.
208
- */
209
- const managedKeysFor = (services, unreached) => {
210
- const owned = services
211
- ? ownedEnvServiceKeys(services)
212
- : [...NEON_OWNED_ENV_KEYS];
213
- if (unreached.length === 0)
214
- return owned;
215
- const keep = new Set(ownedEnvServiceKeys(unreached));
216
- return owned.filter((key) => !keep.has(key));
169
+ * The keys this pull is allowed to prune, i.e. the ones it is authoritative for.
170
+ *
171
+ * An explicit selection narrows that to the service bundles and individual keys it named:
172
+ * `env pull -s ai-gateway` and `env pull -e NEON_AI_GATEWAY_TOKEN` both say nothing about
173
+ * `DATABASE_URL`. `unreached` is subtracted for the same reason — see
174
+ * {@link unreachedButCurrent}.
175
+ */
176
+ const managedKeysFor = (selectedKeys, unreached) => {
177
+ const owned = selectedKeys ? NEON_OWNED_ENV_KEYS.filter((key) => selectedKeys.includes(key)) : [...NEON_OWNED_ENV_KEYS];
178
+ if (unreached.length === 0) return owned;
179
+ const keep = new Set(ownedEnvServiceKeys(unreached));
180
+ return owned.filter((key) => !keep.has(key));
217
181
  };
218
182
  /**
219
- * Of the services this pull could not reach, the ones whose variables already on disk belong
220
- * to the branch being pulled — the only ones worth keeping.
221
- *
222
- * Failing to reach a service is not evidence that the branch stopped having it:
223
- * `PLATFORM_FEATURE_UNAVAILABLE` covers a transient incident as well as a project that
224
- * genuinely lacks the feature, and pruning would delete a token whose secret exists nowhere
225
- * else and strand the live credential behind it. But that only argues for keeping *this
226
- * branch's* values. Variables left over from another branch are stale by definition, and
227
- * keeping those would leave an app pointed at the wrong branch's gateway — a worse failure
228
- * than losing a token, because it is silent.
229
- *
230
- * The gateway is the only service that can be unreached (only it is implied rather than
231
- * observed), and its base URL is branch-scoped, so the persisted URL is what tells the two
232
- * cases apart. Anything that does not resolve to this branch's gateway host is pruned, which
233
- * is the safe direction: a stale entry costs a re-pull, a wrongly-kept one silently misroutes
234
- * traffic.
235
- */
183
+ * Of the services this pull could not reach, the ones whose variables already on disk belong
184
+ * to the branch being pulled — the only ones worth keeping.
185
+ *
186
+ * Failing to reach a service is not evidence that the branch stopped having it:
187
+ * `PLATFORM_FEATURE_UNAVAILABLE` covers a transient incident as well as a project that
188
+ * genuinely lacks the feature, and pruning would delete a token whose secret exists nowhere
189
+ * else and strand the live credential behind it. But that only argues for keeping *this
190
+ * branch's* values. Variables left over from another branch are stale by definition, and
191
+ * keeping those would leave an app pointed at the wrong branch's gateway — a worse failure
192
+ * than losing a token, because it is silent.
193
+ *
194
+ * The gateway is the only service that can be unreached (only it is implied rather than
195
+ * observed), and its base URL is branch-scoped, so the persisted URL is what tells the two
196
+ * cases apart. Anything that does not resolve to this branch's gateway host is pruned, which
197
+ * is the safe direction: a stale entry costs a re-pull, a wrongly-kept one silently misroutes
198
+ * traffic.
199
+ */
236
200
  const unreachedButCurrent = (skipped, existingEnv, branchId) => {
237
- if (!skipped?.includes("ai-gateway"))
238
- return [];
239
- const baseUrl = existingEnv[NEON_ENV_VAR_KEYS.aiGateway.baseUrl];
240
- return baseUrl !== undefined && isBranchGatewayUrl(baseUrl, branchId)
241
- ? ["ai-gateway"]
242
- : [];
201
+ if (!skipped?.includes("ai-gateway")) return [];
202
+ const baseUrl = existingEnv[NEON_ENV_VAR_KEYS.aiGateway.baseUrl];
203
+ return baseUrl !== void 0 && isBranchGatewayUrl(baseUrl, branchId) ? ["ai-gateway"] : [];
243
204
  };
244
205
  /**
245
- * Whether a persisted `NEON_AI_GATEWAY_BASE_URL` addresses `branchId`'s gateway.
246
- *
247
- * Checks the parsed **hostname** against the shape `@neon/env` builds
248
- * (`<branchId>-api.ai.<host suffix>`), not the raw string: a prefix comparison is satisfied
249
- * by a URL whose userinfo carries the branch id (`https://<branchId>-api.ai.@other-host/`)
250
- * while the request actually goes elsewhere. An unparseable value is not this branch's
251
- * gateway either, which is an answer rather than a swallowed failure.
252
- */
253
- const isBranchGatewayUrl = (baseUrl, branchId) => URL.canParse(baseUrl) &&
254
- new URL(baseUrl).hostname.startsWith(`${branchId}-api.ai.`);
206
+ * Whether a persisted `NEON_AI_GATEWAY_BASE_URL` addresses `branchId`'s gateway.
207
+ *
208
+ * Checks the parsed **hostname** against the shape `@neon/env` builds
209
+ * (`<branchId>-api.ai.<host suffix>`), not the raw string: a prefix comparison is satisfied
210
+ * by a URL whose userinfo carries the branch id (`https://<branchId>-api.ai.@other-host/`)
211
+ * while the request actually goes elsewhere. An unparseable value is not this branch's
212
+ * gateway either, which is an answer rather than a swallowed failure.
213
+ */
214
+ const isBranchGatewayUrl = (baseUrl, branchId) => URL.canParse(baseUrl) && new URL(baseUrl).hostname.startsWith(`${branchId}-api.ai.`);
255
215
  /**
256
- * Narrow the resolved vars to the selected services (plus `NEON_BRANCH`, which every pull
257
- * refreshes). Needed because the two `DATABASE_URL*` vars are always resolved — `fetchEnv`
258
- * reads both connection URIs regardless, since the AI Gateway host is derived from the direct
259
- * one — so `--service ai-gateway` has to drop them here rather than avoid fetching them.
260
- */
261
- const pickServiceVars = (vars, services) => {
262
- if (!services)
263
- return vars;
264
- const wanted = envServiceKeys(services);
265
- return Object.fromEntries(Object.entries(vars).filter(([key]) => wanted.has(key)));
216
+ * Narrow the resolved vars to the explicit service/key union. `fetchEnv` may resolve
217
+ * supporting values that were not selected (the direct connection URI derives the AI Gateway
218
+ * host, for example); those must not land in the target file.
219
+ */
220
+ const pickSelectedVars = (vars, selectedKeys) => {
221
+ if (!selectedKeys) return vars;
222
+ const wanted = new Set(selectedKeys);
223
+ return Object.fromEntries(Object.entries(vars).filter(([key]) => wanted.has(key)));
266
224
  };
267
225
  /**
268
- * Pull a freshly-pinned branch's Neon env vars into a local `.env`, bundled into `link` and
269
- * `checkout` so the branch-first loop is just *link + checkout* — `env pull` runs for you.
270
- *
271
- * On by default; `--no-env-pull` opts out (e.g. when env is injected at runtime via
272
- * `neon-env run` / `neon dev`, or to keep secrets out of the working tree). The pin is the
273
- * command's primary effect and has already succeeded by the time this runs, so a pull failure
274
- * degrades to a warning rather than failing the command. Returns what happened so
275
- * `link --agent` can fold an accurate note into its JSON message.
276
- */
277
- export const autoPullEnvAfterPin = async (props) => {
278
- if (!props.envPull) {
279
- log.info(chalk.dim(ENV_PULL_SKIPPED_HINT));
280
- return { status: "skipped" };
281
- }
282
- try {
283
- return await pull(props);
284
- }
285
- catch (err) {
286
- const message = err instanceof Error ? err.message : String(err);
287
- log.warning("Branch pinned, but pulling its Neon env vars failed: %s\n" +
288
- `Run \`${getCliName()} env pull\` once resolved (e.g. \`${getCliName()} deploy\` if a declared service ` +
289
- "is missing), or inject them at runtime with `neon-env run -- <your dev command>`.", message);
290
- return { status: "failed", message };
291
- }
226
+ * Pull a freshly-pinned branch's Neon env vars into a local `.env`, bundled into `link` and
227
+ * `checkout` so the branch-first loop is just *link + checkout* — `env pull` runs for you.
228
+ *
229
+ * On by default; `--no-env-pull` opts out (e.g. when env is injected at runtime via
230
+ * `neon-env run` / `neon dev`, or to keep secrets out of the working tree). The pin is the
231
+ * command's primary effect and has already succeeded by the time this runs, so a pull failure
232
+ * degrades to a warning rather than failing the command. Returns what happened so
233
+ * `link --agent` can fold an accurate note into its JSON message.
234
+ */
235
+ const autoPullEnvAfterPin = async (props) => {
236
+ if (!props.envPull) {
237
+ log.info(chalk.dim(ENV_PULL_SKIPPED_HINT));
238
+ return { status: "skipped" };
239
+ }
240
+ try {
241
+ return await pull(props);
242
+ } catch (err) {
243
+ const message = err instanceof Error ? err.message : String(err);
244
+ log.warning(`Branch pinned, but pulling its Neon env vars failed: %s
245
+ Run \`${getCliName()} env pull\` once resolved (e.g. \`${getCliName()} deploy\` if a declared service is missing), or inject them at runtime with \`neon-env run -- <your dev command>\`.`, message);
246
+ return {
247
+ status: "failed",
248
+ message
249
+ };
250
+ }
292
251
  };
293
252
  /**
294
- * Render the one-line env-pull note appended to `link --agent`'s JSON `message`, so an agent
295
- * reading the structured output knows whether its branch env is already on disk.
296
- */
297
- export const renderAgentPullNote = (result) => {
298
- switch (result.status) {
299
- case "written": {
300
- // Call out a re-issued credential: an agent that already wrote the old storage /
301
- // gateway values somewhere else has to update them.
302
- const credential = result.credential?.issued
303
- ? ` Issued a new branch credential, so ${result.credential.keys.join(", ")} changed.`
304
- : "";
305
- // No `skipped` note: only the implied AI Gateway can be skipped, and the auto-pull
306
- // this renders never implies it.
307
- return ` Pulled ${result.written.length} Neon env var${result.written.length === 1 ? "" : "s"} into ${result.file}.${credential}`;
308
- }
309
- case "empty":
310
- return " No Neon env vars to pull for this branch yet.";
311
- case "skipped":
312
- return (` Skipped env pull (--no-env-pull); run \`${getCliName()} env pull\` later, ` +
313
- "or inject env at runtime with `neon-env run -- <your dev command>`.");
314
- case "failed":
315
- return ` Could not pull env vars (${result.message}); run \`${getCliName()} env pull\` once resolved.`;
316
- }
253
+ * Render the one-line env-pull note appended to `link --agent`'s JSON `message`, so an agent
254
+ * reading the structured output knows whether its branch env is already on disk.
255
+ */
256
+ const renderAgentPullNote = (result) => {
257
+ switch (result.status) {
258
+ case "written": {
259
+ const credential = result.credential?.issued ? ` Issued a new branch credential, so ${result.credential.keys.join(", ")} changed.` : "";
260
+ return ` Pulled ${result.written.length} Neon env var${result.written.length === 1 ? "" : "s"} into ${result.file}.${credential}`;
261
+ }
262
+ case "empty": return " No Neon env vars to pull for this branch yet.";
263
+ case "skipped": return ` Skipped env pull (--no-env-pull); run \`${getCliName()} env pull\` later, or inject env at runtime with \`neon-env run -- <your dev command>\`.`;
264
+ case "failed": return ` Could not pull env vars (${result.message}); run \`${getCliName()} env pull\` once resolved.`;
265
+ }
317
266
  };
318
267
  /**
319
- * Keep only the recognized Neon variables from the resolved set, so a stray inherited
320
- * value never lands in the user's `.env` file. (Today `resolveNeonEnvVars` only emits Neon
321
- * vars, but filtering keeps the contract explicit and future-proof.)
322
- */
268
+ * Keep only the recognized Neon variables from the resolved set, so a stray inherited
269
+ * value never lands in the user's `.env` file. (Today `resolveNeonEnvVars` only emits Neon
270
+ * vars, but filtering keeps the contract explicit and future-proof.)
271
+ */
323
272
  const pickNeonVars = (vars) => {
324
- const out = {};
325
- for (const name of NEON_VAR_NAMES) {
326
- const value = vars[name];
327
- if (value !== undefined)
328
- out[name] = value;
329
- }
330
- return out;
273
+ const out = {};
274
+ for (const name of NEON_VAR_NAMES) {
275
+ const value = vars[name];
276
+ if (value !== void 0) out[name] = value;
277
+ }
278
+ return out;
331
279
  };
280
+ //#endregion
281
+ export { ENV_PULL_SKIPPED_HINT, autoPullEnvAfterPin, builder, command, describe, handler, pull, renderAgentPullNote, env_exports as t };