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,222 +0,0 @@
1
- import chalk from "chalk";
2
- import { isNeonApiError } from "../api.js";
3
- import { log } from "../log.js";
4
- import { fillSingleProject } from "../utils/enrichers.js";
5
- import { looksLikeBranchId } from "../utils/formats.js";
6
- import { renderDatabaseSchemaDiff, renderSchemaDiffReport, } from "../utils/git_diff.js";
7
- import { writer } from "../writer.js";
8
- // A top-level shortcut for `branches schema-diff`, framed like `git diff`: it
9
- // compares the branch you're on (pinned in `.neon`, or `--branch`) against the
10
- // branch you name, and prints a git-style unified schema diff. Because it has a
11
- // handler but no subcommands, `diff` is also listed in `NO_SUBCOMMANDS_VERBS`
12
- // (see index.ts) so a bare `neon diff main` isn't intercepted by the help
13
- // fallback.
14
- export const command = "diff [compare-branch]";
15
- export const describe = "Show a git-style schema diff between the current branch and another branch";
16
- export const builder = (argv) => argv
17
- .usage("$0 diff [compare-branch] [options]")
18
- .positional("compare-branch", {
19
- describe: "Branch name or id to compare against (the reference / '---' side). " +
20
- "Defaults to the current branch's parent.",
21
- type: "string",
22
- })
23
- .options({
24
- "project-id": {
25
- describe: "Project ID",
26
- type: "string",
27
- },
28
- branch: {
29
- alias: "b",
30
- describe: "The branch to review (the '+++' side). Defaults to the branch " +
31
- "pinned in the local context (.neon).",
32
- type: "string",
33
- },
34
- database: {
35
- alias: "db",
36
- describe: "Limit the diff to a single database. Defaults to every database on the current branch.",
37
- type: "string",
38
- },
39
- })
40
- .middleware(fillSingleProject)
41
- .middleware((args) => {
42
- // The positional arrives as `compare-branch`; surface it under the
43
- // camelCase name the handler reads, and mirror it to `branchId` for
44
- // analytics (same pattern as the `branches` command group).
45
- const compareBranch = args["compare-branch"];
46
- if (typeof compareBranch === "string") {
47
- args.compareBranch = compareBranch;
48
- }
49
- })
50
- .example([
51
- [
52
- "$0 diff main",
53
- "Diff the current branch's schema against the main branch",
54
- ],
55
- [
56
- "$0 diff",
57
- "Diff the current branch's schema against its parent branch",
58
- ],
59
- [
60
- "$0 diff main --branch feature/checkout",
61
- "Diff an explicit branch against main (ignoring the .neon context)",
62
- ],
63
- [
64
- "$0 diff main --db neondb",
65
- "Diff only the neondb database against main",
66
- ],
67
- ]);
68
- export const handler = async (props) => {
69
- const branches = (await props.apiClient.listProjectBranches({
70
- projectId: props.projectId,
71
- })).data.branches;
72
- const after = resolveAfterBranch(branches, props.branch);
73
- const before = resolveBeforeBranch(branches, after, props.compareBranch);
74
- if (before.branchId === after.branchId) {
75
- throw new Error(`Nothing to compare: both sides resolve to branch ${after.branchName} (${after.branchId}).`);
76
- }
77
- const databases = await resolveDatabases(props, after);
78
- const diffs = [];
79
- for (const database of databases) {
80
- const [beforeSql, afterSql] = await Promise.all([
81
- fetchSchemaSql(props, before.branchId, database),
82
- fetchSchemaSql(props, after.branchId, database),
83
- ]);
84
- diffs.push({
85
- database,
86
- before: { ...before, sql: beforeSql },
87
- after: { ...after, sql: afterSql },
88
- });
89
- }
90
- if (props.output === "json" || props.output === "yaml") {
91
- writeStructured(props, diffs);
92
- return;
93
- }
94
- log.info("%s Comparing schema %s → %s", chalk.dim("→"), chalk.red(`${before.branchName}`), chalk.green(`${after.branchName}`));
95
- const { hasChanges, text } = renderSchemaDiffReport(diffs, {
96
- color: props.color !== false,
97
- });
98
- if (!hasChanges) {
99
- log.info("No schema differences between %s and %s.", before.branchName, after.branchName);
100
- return;
101
- }
102
- writer(props).text(`${text}\n`);
103
- };
104
- /**
105
- * Resolve the branch under review (`+++` side). Prefers the explicit
106
- * `branch`/`--branch` value (name or `br-…` id), falling back to the project's
107
- * default branch. An unknown `br-…` id is trusted as-is (it may be too new to
108
- * appear in the listing); an unknown *name* is a hard error.
109
- */
110
- const resolveAfterBranch = (branches, ref) => {
111
- if (ref) {
112
- return resolveRef(branches, ref);
113
- }
114
- const def = branches.find((b) => b.default);
115
- if (!def) {
116
- throw new Error("No branch specified and no default branch found. Pass --branch <name|id>.");
117
- }
118
- return { branchId: def.id, branchName: def.name ?? def.id };
119
- };
120
- /**
121
- * Resolve the reference branch (`---` side). Uses the `compare-branch`
122
- * positional when given, otherwise the parent of the branch under review — so a
123
- * bare `neon diff` answers "what did I change since branching?".
124
- */
125
- const resolveBeforeBranch = (branches, after, ref) => {
126
- if (ref) {
127
- return resolveRef(branches, ref);
128
- }
129
- const afterBranch = branches.find((b) => b.id === after.branchId);
130
- const parentId = afterBranch?.parent_id;
131
- if (!parentId) {
132
- throw new Error(`Branch "${after.branchName}" has no parent to compare against. ` +
133
- "Pass a branch to compare with, e.g. `neon diff main`.");
134
- }
135
- const parent = branches.find((b) => b.id === parentId);
136
- return {
137
- branchId: parentId,
138
- branchName: parent?.name ?? parentId,
139
- };
140
- };
141
- /** Resolve a branch reference (name or `br-…` id) against the fetched listing. */
142
- const resolveRef = (branches, ref) => {
143
- const found = looksLikeBranchId(ref)
144
- ? branches.find((b) => b.id === ref)
145
- : branches.find((b) => b.name === ref);
146
- if (found) {
147
- return { branchId: found.id, branchName: found.name ?? found.id };
148
- }
149
- // A `br-…` id absent from the listing is still usable as an id; only an
150
- // unresolved name is a genuine error (mirrors resolveBranchRef in enrichers).
151
- if (looksLikeBranchId(ref)) {
152
- return { branchId: ref, branchName: ref };
153
- }
154
- throw new Error(`Branch ${ref} not found.\nAvailable branches: ${branches
155
- .map((b) => b.name)
156
- .join(", ")}`);
157
- };
158
- /**
159
- * The databases to diff: the one passed via `--database` (validated against the
160
- * branch under review), or every database on that branch when none is given.
161
- */
162
- const resolveDatabases = async (props, after) => {
163
- const databases = (await props.apiClient.listProjectBranchDatabases(props.projectId, after.branchId)).data.databases;
164
- if (props.database !== undefined) {
165
- if (!databases.find((d) => d.name === props.database)) {
166
- throw new Error(`Database "${props.database}" not found on branch ${after.branchName}. ` +
167
- `Available: ${databases.map((d) => d.name).join(", ")}`);
168
- }
169
- return [props.database];
170
- }
171
- if (databases.length === 0) {
172
- throw new Error(`No databases found on branch ${after.branchName} (${after.branchId}).`);
173
- }
174
- return databases.map((d) => d.name);
175
- };
176
- /**
177
- * Fetch a branch database's `CREATE …` SQL. A database absent from the branch
178
- * (404) yields an empty schema, so the diff shows it as fully added/removed
179
- * rather than failing — the natural outcome when a database exists on only one
180
- * side of the comparison.
181
- */
182
- const fetchSchemaSql = async (props, branchId, database) => {
183
- try {
184
- const { data } = await props.apiClient.getProjectBranchSchema({
185
- projectId: props.projectId,
186
- branchId,
187
- db_name: database,
188
- });
189
- return data.sql ?? "";
190
- }
191
- catch (err) {
192
- if (isNeonApiError(err) && err.status === 404) {
193
- log.debug("diff: database %s not found on branch %s; treating schema as empty", database, branchId);
194
- return "";
195
- }
196
- throw err;
197
- }
198
- };
199
- /** Machine-readable output for `--output json|yaml`: one entry per database. */
200
- const writeStructured = (props, diffs) => {
201
- const report = diffs.map((diff) => {
202
- const rendered = renderDatabaseSchemaDiff(diff, { color: false });
203
- return {
204
- database: diff.database,
205
- base_branch: diff.before.branchName,
206
- base_branch_id: diff.before.branchId,
207
- compare_branch: diff.after.branchName,
208
- compare_branch_id: diff.after.branchId,
209
- has_changes: rendered.hasChanges,
210
- diff: rendered.text,
211
- };
212
- });
213
- writer(props).end(report, {
214
- fields: [
215
- "database",
216
- "base_branch",
217
- "compare_branch",
218
- "has_changes",
219
- "diff",
220
- ],
221
- });
222
- };
@@ -1,189 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { NEON_ENV_VAR_KEYS } from "@neon/env";
3
- import chalk from "chalk";
4
- import { ensureGitignored } from "../context.js";
5
- import { resolveNeonEnvVars } from "../dev/env.js";
6
- import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
7
- import { log } from "../log.js";
8
- import { warnAiGateway } from "../utils/ai_gateway_notice.js";
9
- import { announceTargetBranch } from "../utils/branch_notice.js";
10
- import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
11
- export const command = "env";
12
- export const describe = "Manage a branch's Neon env variables locally";
13
- /**
14
- * Shown (to stderr) when `link` / `checkout` skip the bundled env pull because the user passed
15
- * `--no-env-pull`. Names the two ways to get the branch's vars without an on-disk file written
16
- * eagerly: an explicit `neonctl env pull`, or runtime injection via `neon-env run`.
17
- */
18
- export const ENV_PULL_SKIPPED_HINT = "Skipped env pull (--no-env-pull). Run `neonctl env pull` to write this branch’s env vars " +
19
- "(DATABASE_URL, …) into a local .env, or inject them at runtime with `neon-env run -- <your dev command>`.";
20
- export const builder = (argv) => argv
21
- .usage("$0 env <sub-command> [options]")
22
- .options({
23
- "project-id": { describe: "Project ID", type: "string" },
24
- branch: { describe: "Branch ID or name", type: "string" },
25
- })
26
- .middleware(fillSingleProject)
27
- .command("pull", "Write the branch's Neon env variables to a local .env file", (yargs) => yargs
28
- .usage("$0 env pull [options]")
29
- .options({
30
- file: {
31
- describe: "Target .env file to write. Defaults to an existing .env, " +
32
- "otherwise .env.local. Only Neon variables are updated; other " +
33
- "lines are preserved.",
34
- type: "string",
35
- },
36
- })
37
- .example("$0 env pull", "Write the linked branch's Neon vars into .env.local (or .env if present)")
38
- .example("$0 env pull --branch preview --file .env.preview", "Pull a specific branch into a specific file"), async (args) => {
39
- // Explicit `env pull` announces the branch it's reading from up front so the user
40
- // can catch "pulled env from the wrong branch" before it overwrites their .env. The
41
- // bundled auto-pull (link / checkout / apply) stays quiet — those already report the
42
- // branch they pinned/applied to.
43
- await pull(args, { announce: true });
44
- })
45
- .demandCommand(1);
46
- export const handler = (args) => args;
47
- /** Every OS-level env var name `@neon/env` can emit, used only for reporting. */
48
- const NEON_VAR_NAMES = Object.values(NEON_ENV_VAR_KEYS).flatMap((group) => Object.values(group));
49
- /**
50
- * The Neon env vars `env pull` *owns*, so it removes any that the branch no longer has when
51
- * it reconciles the local `.env` (see {@link pull}). Scoped to the unambiguously Neon-named
52
- * vars — the `NEON_*` aliases plus `DATABASE_URL[_UNPOOLED]` — so switching a working
53
- * directory to a project/branch without Auth / the Data API drops the now-stale
54
- * `NEON_AUTH_*` / `NEON_DATA_API_*` lines instead of leaving credentials for features that
55
- * aren't enabled.
56
- *
57
- * Deliberately **excludes** the storage vars Neon projects onto third-party SDK names
58
- * (`AWS_*`): those collide with credentials a user may set by hand, so `env pull` only ever
59
- * writes them, never prunes them. The AI Gateway is emitted solely under its Neon-branded
60
- * vars (`NEON_AI_GATEWAY_*`), which are owned and pruned.
61
- */
62
- const NEON_OWNED_ENV_KEYS = [
63
- ...Object.values(NEON_ENV_VAR_KEYS.postgres),
64
- ...Object.values(NEON_ENV_VAR_KEYS.auth),
65
- ...Object.values(NEON_ENV_VAR_KEYS.dataApi),
66
- ...Object.values(NEON_ENV_VAR_KEYS.aiGateway),
67
- ];
68
- export const pull = async (props, opts = {}) => {
69
- const cwd = props.cwd ?? process.cwd();
70
- const branch = await resolveBranchRef(props);
71
- if (opts.announce) {
72
- announceTargetBranch(props, branch, "Pulling env from branch");
73
- }
74
- const branchId = branch.branchId;
75
- // Resolve the target file first and layer its current contents under the resolver's env
76
- // source. This lets `fetchEnv` reuse one-time secrets that are already on disk — Neon Auth
77
- // keys and the unified branch credential's `api_token` / `s3_secret_access_key`, which the
78
- // API returns exactly once — instead of minting a fresh credential on every pull.
79
- const targetPath = resolveEnvFilePath(cwd, props.file);
80
- const fileExisted = existsSync(targetPath);
81
- const existingEnv = fileExisted ? readEnvFile(targetPath) : {};
82
- // Reuse `neon dev`'s tiered resolver (neon.ts policy -> plan gate -> fetchEnv, else
83
- // pullConfig -> fetchEnv). Unlike dev, an unresolved context or failure is surfaced —
84
- // `env pull` is an explicit action, so it should error rather than write nothing.
85
- const vars = await resolveNeonEnvVars({
86
- cwd,
87
- projectId: props.projectId,
88
- branchId,
89
- env: { ...process.env, ...existingEnv },
90
- ...(props.apiKey ? { apiKey: props.apiKey } : {}),
91
- ...(props.apiHost ? { apiHost: props.apiHost } : {}),
92
- ...(props.runtimeApi ? { api: props.runtimeApi } : {}),
93
- });
94
- const neonVars = pickNeonVars(vars);
95
- if (Object.keys(neonVars).length === 0) {
96
- log.info("No Neon env variables to pull for this branch (no DATABASE_URL or " +
97
- "enabled Auth / Data API).");
98
- return { status: "empty" };
99
- }
100
- // Reconcile rather than blindly merge: write the branch's current Neon vars and prune any
101
- // Neon-owned vars the branch no longer has (e.g. NEON_AUTH_* / NEON_DATA_API_* carried over
102
- // from a previous project/branch). Non-Neon lines are always preserved.
103
- const { written, removed } = mergeEnvFile(targetPath, neonVars, {
104
- managedKeys: NEON_OWNED_ENV_KEYS,
105
- });
106
- log.info("Pulled %d Neon variable%s into %s: %s", written.length, written.length === 1 ? "" : "s", targetPath, written.join(", "));
107
- if (removed.length > 0) {
108
- log.info("Removed %d stale Neon variable%s not enabled on this branch: %s", removed.length, removed.length === 1 ? "" : "s", removed.join(", "));
109
- }
110
- // A dotenv file *we* create holds live branch credentials (DATABASE_URL, Auth keys, service
111
- // tokens), so ignore it the same way the `.neon` context file is — otherwise a fresh repo is
112
- // one `git add -A` away from committing them. Only on creation: re-adding the entry on every
113
- // pull would fight a user who deliberately un-ignored a file they want to commit.
114
- if (!fileExisted) {
115
- ensureGitignored(targetPath);
116
- }
117
- // When the branch has the AI Gateway enabled, the pulled credentials always work, but
118
- // serving is plan-gated and the model set can be reduced on the beta — surface that as a
119
- // courtesy notice (best-effort; never fails the pull). The freshly pulled token lets us
120
- // probe the branch's own /v1/models to detect a reduced catalog.
121
- const gatewayBaseUrl = neonVars.NEON_AI_GATEWAY_BASE_URL;
122
- const gatewayToken = neonVars.NEON_AI_GATEWAY_TOKEN;
123
- if (gatewayBaseUrl && gatewayToken) {
124
- await warnAiGateway({
125
- apiClient: props.apiClient,
126
- projectId: props.projectId,
127
- branchId,
128
- gateway: { baseUrl: gatewayBaseUrl, token: gatewayToken },
129
- });
130
- }
131
- return { status: "written", written, file: targetPath };
132
- };
133
- /**
134
- * Pull a freshly-pinned branch's Neon env vars into a local `.env`, bundled into `link` and
135
- * `checkout` so the branch-first loop is just *link + checkout* — `env pull` runs for you.
136
- *
137
- * On by default; `--no-env-pull` opts out (e.g. when env is injected at runtime via
138
- * `neon-env run` / `neon dev`, or to keep secrets out of the working tree). The pin is the
139
- * command's primary effect and has already succeeded by the time this runs, so a pull failure
140
- * degrades to a warning rather than failing the command. Returns what happened so
141
- * `link --agent` can fold an accurate note into its JSON message.
142
- */
143
- export const autoPullEnvAfterPin = async (props) => {
144
- if (!props.envPull) {
145
- log.info(chalk.dim(ENV_PULL_SKIPPED_HINT));
146
- return { status: "skipped" };
147
- }
148
- try {
149
- return await pull(props);
150
- }
151
- catch (err) {
152
- const message = err instanceof Error ? err.message : String(err);
153
- log.warning("Branch pinned, but pulling its Neon env vars failed: %s\n" +
154
- "Run `neonctl env pull` once resolved (e.g. `neonctl deploy` if a declared service " +
155
- "is missing), or inject them at runtime with `neon-env run -- <your dev command>`.", message);
156
- return { status: "failed", message };
157
- }
158
- };
159
- /**
160
- * Render the one-line env-pull note appended to `link --agent`'s JSON `message`, so an agent
161
- * reading the structured output knows whether its branch env is already on disk.
162
- */
163
- export const renderAgentPullNote = (result) => {
164
- switch (result.status) {
165
- case "written":
166
- return ` Pulled ${result.written.length} Neon env var${result.written.length === 1 ? "" : "s"} into ${result.file}.`;
167
- case "empty":
168
- return " No Neon env vars to pull for this branch yet.";
169
- case "skipped":
170
- return (" Skipped env pull (--no-env-pull); run `neonctl env pull` later, " +
171
- "or inject env at runtime with `neon-env run -- <your dev command>`.");
172
- case "failed":
173
- return ` Could not pull env vars (${result.message}); run \`neonctl env pull\` once resolved.`;
174
- }
175
- };
176
- /**
177
- * Keep only the recognized Neon variables from the resolved set, so a stray inherited
178
- * value never lands in the user's `.env` file. (Today `resolveNeonEnvVars` only emits Neon
179
- * vars, but filtering keeps the contract explicit and future-proof.)
180
- */
181
- const pickNeonVars = (vars) => {
182
- const out = {};
183
- for (const name of NEON_VAR_NAMES) {
184
- const value = vars[name];
185
- if (value !== undefined)
186
- out[name] = value;
187
- }
188
- return out;
189
- };