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