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.
- package/{LICENSE.md → LICENSE} +27 -3
- package/README.md +15 -700
- package/bin/cli.js +3 -0
- package/package.json +8 -75
- package/dist/analytics.js +0 -168
- package/dist/api.js +0 -720
- package/dist/auth.js +0 -125
- package/dist/callback.html +0 -51
- package/dist/cli.js +0 -9
- package/dist/commands/api.js +0 -278
- package/dist/commands/auth.js +0 -214
- package/dist/commands/bootstrap.js +0 -481
- package/dist/commands/branches.js +0 -488
- package/dist/commands/bucket.js +0 -549
- package/dist/commands/checkout.js +0 -321
- package/dist/commands/config.js +0 -626
- package/dist/commands/connection_string.js +0 -172
- package/dist/commands/data_api.js +0 -284
- package/dist/commands/databases.js +0 -82
- package/dist/commands/deploy.js +0 -26
- package/dist/commands/dev.js +0 -698
- package/dist/commands/diff.js +0 -222
- package/dist/commands/env.js +0 -189
- package/dist/commands/functions.js +0 -373
- package/dist/commands/index.js +0 -62
- package/dist/commands/init.js +0 -73
- package/dist/commands/inspect.js +0 -65
- package/dist/commands/ip_allow.js +0 -137
- package/dist/commands/link.js +0 -1121
- package/dist/commands/neon_auth.js +0 -1028
- package/dist/commands/operations.js +0 -28
- package/dist/commands/orgs.js +0 -24
- package/dist/commands/projects.js +0 -413
- package/dist/commands/psql.js +0 -62
- package/dist/commands/roles.js +0 -65
- package/dist/commands/schema_diff.js +0 -151
- package/dist/commands/set_context.js +0 -29
- package/dist/commands/snapshots.js +0 -455
- package/dist/commands/status.js +0 -40
- package/dist/commands/user.js +0 -15
- package/dist/commands/vpc_endpoints.js +0 -134
- package/dist/config.js +0 -11
- package/dist/config_format.js +0 -72
- package/dist/context.js +0 -231
- package/dist/current_branch_fast_path.js +0 -55
- package/dist/dev/env.js +0 -244
- package/dist/dev/functions.js +0 -70
- package/dist/dev/inputs.js +0 -63
- package/dist/dev/runtime.js +0 -146
- package/dist/env.js +0 -36
- package/dist/env_file.js +0 -159
- package/dist/errors.js +0 -80
- package/dist/functions_api.js +0 -48
- package/dist/help.js +0 -146
- package/dist/index.js +0 -240
- package/dist/log.js +0 -18
- package/dist/parameters.gen.js +0 -480
- package/dist/pkg.js +0 -25
- package/dist/psql/cli.js +0 -53
- package/dist/psql/command/cmd_cond.js +0 -437
- package/dist/psql/command/cmd_connect.js +0 -820
- package/dist/psql/command/cmd_copy.js +0 -1035
- package/dist/psql/command/cmd_describe.js +0 -1815
- package/dist/psql/command/cmd_format.js +0 -948
- package/dist/psql/command/cmd_io.js +0 -2193
- package/dist/psql/command/cmd_lo.js +0 -393
- package/dist/psql/command/cmd_meta.js +0 -969
- package/dist/psql/command/cmd_misc.js +0 -187
- package/dist/psql/command/cmd_pipeline.js +0 -1148
- package/dist/psql/command/cmd_restrict.js +0 -171
- package/dist/psql/command/cmd_show.js +0 -766
- package/dist/psql/command/dispatch.js +0 -343
- package/dist/psql/command/inputQueue.js +0 -42
- package/dist/psql/command/shared.js +0 -71
- package/dist/psql/complete/filenames.js +0 -139
- package/dist/psql/complete/index.js +0 -104
- package/dist/psql/complete/matcher.js +0 -315
- package/dist/psql/complete/psqlVars.js +0 -249
- package/dist/psql/complete/queries.js +0 -493
- package/dist/psql/complete/rules.js +0 -2424
- package/dist/psql/core/common.js +0 -1253
- package/dist/psql/core/help.js +0 -576
- package/dist/psql/core/mainloop.js +0 -1360
- package/dist/psql/core/prompt.js +0 -439
- package/dist/psql/core/settings.js +0 -686
- package/dist/psql/core/sqlHelp.js +0 -1066
- package/dist/psql/core/startup.js +0 -846
- package/dist/psql/core/syncVars.js +0 -116
- package/dist/psql/core/variables.js +0 -287
- package/dist/psql/describe/formatters.js +0 -1330
- package/dist/psql/describe/processNamePattern.js +0 -270
- package/dist/psql/describe/queries.js +0 -2452
- package/dist/psql/describe/versionGate.js +0 -44
- package/dist/psql/index.js +0 -2030
- package/dist/psql/io/history.js +0 -299
- package/dist/psql/io/input.js +0 -120
- package/dist/psql/io/lineEditor/buffer.js +0 -325
- package/dist/psql/io/lineEditor/complete.js +0 -227
- package/dist/psql/io/lineEditor/filename.js +0 -159
- package/dist/psql/io/lineEditor/index.js +0 -893
- package/dist/psql/io/lineEditor/keymap.js +0 -745
- package/dist/psql/io/lineEditor/vt100.js +0 -363
- package/dist/psql/io/pgpass.js +0 -202
- package/dist/psql/io/pgservice.js +0 -194
- package/dist/psql/io/psqlrc.js +0 -422
- package/dist/psql/print/aligned.js +0 -1748
- package/dist/psql/print/asciidoc.js +0 -230
- package/dist/psql/print/crosstab.js +0 -463
- package/dist/psql/print/csv.js +0 -76
- package/dist/psql/print/html.js +0 -240
- package/dist/psql/print/json.js +0 -96
- package/dist/psql/print/latex.js +0 -379
- package/dist/psql/print/pager.js +0 -267
- package/dist/psql/print/troff.js +0 -240
- package/dist/psql/print/unaligned.js +0 -99
- package/dist/psql/print/units.js +0 -188
- package/dist/psql/scanner/slash.js +0 -515
- package/dist/psql/scanner/sql.js +0 -914
- package/dist/psql/scanner/stringutils.js +0 -394
- package/dist/psql/types/backslash.js +0 -1
- package/dist/psql/types/connection.js +0 -1
- package/dist/psql/types/index.js +0 -7
- package/dist/psql/types/printer.js +0 -1
- package/dist/psql/types/repl.js +0 -1
- package/dist/psql/types/scanner.js +0 -24
- package/dist/psql/types/settings.js +0 -1
- package/dist/psql/types/variables.js +0 -1
- package/dist/psql/wire/connection.js +0 -2857
- package/dist/psql/wire/copy.js +0 -108
- package/dist/psql/wire/notify.js +0 -59
- package/dist/psql/wire/pipeline.js +0 -521
- package/dist/psql/wire/protocol.js +0 -466
- package/dist/psql/wire/sasl.js +0 -294
- package/dist/psql/wire/tls.js +0 -602
- package/dist/storage_api.js +0 -147
- package/dist/test_utils/fixtures.js +0 -121
- package/dist/test_utils/oauth_server.js +0 -9
- package/dist/types.js +0 -1
- package/dist/utils/ai_gateway_notice.js +0 -180
- package/dist/utils/api_enums.js +0 -33
- package/dist/utils/auth.js +0 -5
- package/dist/utils/branch_notice.js +0 -22
- package/dist/utils/branch_picker.js +0 -103
- package/dist/utils/compute_units.js +0 -28
- package/dist/utils/config_diff.js +0 -185
- package/dist/utils/enrichers.js +0 -161
- package/dist/utils/esbuild.js +0 -158
- package/dist/utils/formats.js +0 -18
- package/dist/utils/git_diff.js +0 -90
- package/dist/utils/inspect_db.js +0 -153
- package/dist/utils/inspect_queries.js +0 -372
- package/dist/utils/middlewares.js +0 -20
- package/dist/utils/openapi.js +0 -114
- package/dist/utils/package_manager.js +0 -68
- package/dist/utils/point_in_time.js +0 -56
- package/dist/utils/psql.js +0 -120
- package/dist/utils/string.js +0 -5
- package/dist/utils/ui.js +0 -59
- package/dist/utils/zip.js +0 -4
- package/dist/writer.js +0 -97
package/dist/commands/diff.js
DELETED
|
@@ -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
|
-
};
|
package/dist/commands/env.js
DELETED
|
@@ -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
|
-
};
|