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.
- package/README.md +70 -5
- package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
- package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
- package/dist/_chunks/credentials-MYdHdKah.js +188 -0
- package/dist/_chunks/env-NbA61JR3.js +585 -0
- package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
- package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
- package/dist/_chunks/profiles-Ir29rqns.js +217 -0
- package/dist/_chunks/psql-DWH-kc69.js +2169 -0
- package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/_chunks/secure_file-BucZj4yQ.js +39 -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 +573 -690
- 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 +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +255 -305
- package/dist/commands/functions.js +275 -355
- 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 +615 -846
- 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 +2 -446
- package/dist/dev/functions.js +54 -68
- 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 +2 -51
- 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 +728 -984
- 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 +2 -2193
- 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 +2 -1148
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +260 -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 +2 -1253
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1303
- 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 +2 -846
- 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 +2 -2030
- 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 +8 -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 +130 -189
- 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 +39 -45
- 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 +11 -6
- package/dist/_shared/auth_selection.js +0 -86
- package/dist/_shared/credentials.js +0 -209
- package/dist/_shared/env-core/env.js +0 -558
- package/dist/_shared/env-core/reuse-secrets.js +0 -223
- package/dist/_shared/paths.js +0 -148
- package/dist/_shared/profiles.js +0 -276
- package/dist/_shared/secure_file.js +0 -43
package/dist/commands/env.js
CHANGED
|
@@ -1,331 +1,281 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
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 {
|
|
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 { 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 {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const managedKeysFor = (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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 };
|