neon 8.5.0 → 8.6.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 +11 -2
- package/dist/commands/dev.js +92 -26
- package/dist/commands/env.js +1 -1
- package/dist/commands/link.js +1 -1
- package/dist/commands/subscribe.js +1 -1
- package/dist/dev/runtime.js +8 -0
- package/dist/env_file.js +9 -1
- package/dist/init/auth.js +1 -4
- package/dist/init/choices.js +21 -7
- package/dist/init/copy.js +21 -7
- package/dist/init/run.js +24 -14
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -623,7 +623,16 @@ Console), or drop auth from --service.
|
|
|
623
623
|
|
|
624
624
|
If you'd rather not keep env vars on disk, inject them at runtime instead with `neon-env run -- <your dev command>` (from `@neon/env`) or `neon dev`, and pass `--no-env-pull` to `link` / `checkout`.
|
|
625
625
|
|
|
626
|
-
**`neon dev` resolves the same set, by the same rules** — including the AI Gateway on a branch with no `neon.ts`. A function running locally gets the same Postgres / Auth / Data API / storage / AI Gateway vars a deploy would inject. Each `NEON_FUNCTION_<SLUG>_BASE_URL` this process is serving is rewritten to `http://localhost:<port>`: every neon.ts function, or the matching slug when `--source` names one. `neon env pull` and `neon-env run` write the production URL (`https://<branchId>-<slug>.compute.…`).
|
|
626
|
+
**`neon dev` resolves the same set, by the same rules** — including the AI Gateway on a branch with no `neon.ts`. A function running locally gets the same Postgres / Auth / Data API / storage / AI Gateway vars a deploy would inject. Each `NEON_FUNCTION_<SLUG>_BASE_URL` this process is serving is rewritten to `http://localhost:<port>`: every neon.ts function, or the matching slug when `--source` names one. `neon env pull` and `neon-env run` write the production URL (`https://<branchId>-<slug>.compute.…`).
|
|
627
|
+
|
|
628
|
+
Before `neon.ts` is evaluated, `dev` loads the project directory's `.env`, then `.env.local`, which overrides it, into the environment, so Function env values written as `process.env.X!` resolve the same way they do for `neon deploy --env`. A variable already set in the shell wins over both. Local Functions inherit every loaded variable, including keys `neon.ts` doesn't declare; the deployed runtime gets only declared and Neon-injected env. `--env <file>` loads only that file, resolved from the current directory, matching `neon deploy --env`:
|
|
629
|
+
|
|
630
|
+
```bash
|
|
631
|
+
neon dev # loads .env, then .env.local (overrides .env)
|
|
632
|
+
neon dev --env .env.staging # loads only .env.staging
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
`dev` writes nothing. To reuse the branch credential behind the AI Gateway and object storage, it reads only the file `env pull` writes (`.env` when it exists, otherwise `.env.local`), so an override in the other file cannot pair one credential's key ID with another's secret. Without a file to read from it issues one on every start and leaves the last one live — it has nowhere to keep it, and so cannot name it to revoke it. It says so when it happens; run `env pull` (or just `link` / `checkout`) once and restarts reuse the credential instead.
|
|
627
636
|
|
|
628
637
|
**Where `.neon` lives**: `link` writes `.neon` into the **current working directory** by default. If an existing `.neon` is found in any parent directory, that file is reused — so commands run from a sub-directory of a linked project still pick up the project's context. To pin the location explicitly, pass `--context-file <path>`. `neon init` always writes `.neon` into the current directory.
|
|
629
638
|
|
|
@@ -890,7 +899,7 @@ Without a TTY, pass `-y` or enough flags to answer every question. `-y` alone is
|
|
|
890
899
|
|
|
891
900
|
`--project-id`, `--org-id`, `--project-name`, `--region-id`, and `--branch` are forwarded to `link`. They select linking. A linked directory is not relinked unless you pass one of those flags.
|
|
892
901
|
|
|
893
|
-
|
|
902
|
+
Init pulls the branch's env vars once, after the `neon.ts` step, so the pull includes what `neon.ts` declares. The AI Gateway and Functions need no deploy and are pulled immediately. When `neon.ts` declares Auth, the Data API, Object Storage, or Realtime, init pulls Postgres, plus the AI Gateway and the scaffolded function's URL when declared. `neon deploy` provisions the rest and pulls their env vars. An existing `neon.ts` is pulled by `link` as usual.
|
|
894
903
|
|
|
895
904
|
A failed step stops the rest. `--profile` and `--config-dir` are forwarded to each child. `--output json` and `--output yaml` are refused; the commands init runs print their own output.
|
|
896
905
|
|
package/dist/commands/dev.js
CHANGED
|
@@ -2,13 +2,14 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
|
2
2
|
import { log } from "../log.js";
|
|
3
3
|
import { isClaimableEnvTarget } from "../claimable/state.js";
|
|
4
4
|
import { getCliName } from "../utils/cli_name.js";
|
|
5
|
-
import { readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
5
|
+
import { defaultDevEnvFiles, readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
6
6
|
import { l as functionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
|
|
7
|
-
import { branchIdResolve } from "../utils/enrichers.js";
|
|
8
|
-
import { bundleEntry } from "../utils/esbuild.js";
|
|
9
7
|
import { a as resolveDevEnv } from "../_chunks/env-BNm69Qh8.js";
|
|
10
8
|
import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
|
|
11
9
|
import { resolveWatchInputs } from "../dev/inputs.js";
|
|
10
|
+
import { branchIdResolve } from "../utils/enrichers.js";
|
|
11
|
+
import { bundleEntry } from "../utils/esbuild.js";
|
|
12
|
+
import { envFlag } from "./config.js";
|
|
12
13
|
import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
13
14
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
14
15
|
import { fileURLToPath } from "node:url";
|
|
@@ -20,13 +21,16 @@ import { createServer } from "node:net";
|
|
|
20
21
|
//#region src/commands/dev.ts
|
|
21
22
|
var dev_exports = /* @__PURE__ */ __exportAll({
|
|
22
23
|
builder: () => builder,
|
|
24
|
+
captureShellEnv: () => captureShellEnv,
|
|
23
25
|
command: () => "dev",
|
|
24
26
|
describe: () => describe,
|
|
25
27
|
devBundleDir: () => devBundleDir,
|
|
26
28
|
devEnvContext: () => devEnvContext,
|
|
27
29
|
diffUnits: () => diffUnits,
|
|
30
|
+
formatEnvFiles: () => formatEnvFiles,
|
|
28
31
|
formatEnvSummary: () => formatEnvSummary,
|
|
29
32
|
handler: () => handler,
|
|
33
|
+
loadDevEnvFiles: () => loadDevEnvFiles,
|
|
30
34
|
localFunctionUrlEnv: () => localFunctionUrlEnv,
|
|
31
35
|
planFunctionsToUnits: () => planFunctionsToUnits,
|
|
32
36
|
reportDevCredential: () => reportDevCredential,
|
|
@@ -42,7 +46,8 @@ const builder = (argv) => argv.usage("$0 dev [--source <path>] [options]").examp
|
|
|
42
46
|
port: {
|
|
43
47
|
describe: "Port to listen on (single-function mode only, with --source). Fails if taken. Without it (and without a PORT env var) a free port is chosen automatically.",
|
|
44
48
|
type: "number"
|
|
45
|
-
}
|
|
49
|
+
},
|
|
50
|
+
...envFlag
|
|
46
51
|
}).epilogue([
|
|
47
52
|
"",
|
|
48
53
|
"Functions run with the linked branch's Neon env injected, the same set the",
|
|
@@ -55,9 +60,14 @@ const builder = (argv) => argv.usage("$0 dev [--source <path>] [options]").examp
|
|
|
55
60
|
"this process is serving (every neon.ts function, or the matching slug when",
|
|
56
61
|
"`--source` names one).",
|
|
57
62
|
"",
|
|
58
|
-
"`dev`
|
|
59
|
-
"
|
|
60
|
-
"
|
|
63
|
+
"Before neon.ts is evaluated, `dev` loads the project's .env, then .env.local,",
|
|
64
|
+
"which overrides it, into the environment, so Function env values that read",
|
|
65
|
+
"process.env resolve. A variable already set in the shell wins. `--env <file>`",
|
|
66
|
+
"loads only that file instead. Functions inherit every loaded variable.",
|
|
67
|
+
"",
|
|
68
|
+
"`dev` reuses the branch credential behind the AI Gateway and object storage from",
|
|
69
|
+
"the file `env pull` writes, and never writes to it. With no such file it issues a",
|
|
70
|
+
"credential on every start, so run `env pull` once if you restart often."
|
|
61
71
|
].join("\n")).strict();
|
|
62
72
|
/**
|
|
63
73
|
* The resolver context for a `neon dev` run.
|
|
@@ -74,7 +84,7 @@ const builder = (argv) => argv.usage("$0 dev [--source <path>] [options]").examp
|
|
|
74
84
|
* last one live — one orphan per restart. `neon-env run` already reads the file for this
|
|
75
85
|
* reason; `dev` was the one that didn't.
|
|
76
86
|
*/
|
|
77
|
-
const devEnvContext = (props, branchId, cwd) => {
|
|
87
|
+
const devEnvContext = (props, branchId, cwd, baseEnv = process.env) => {
|
|
78
88
|
const location = {
|
|
79
89
|
cwd,
|
|
80
90
|
...props.contextFile ? { contextFile: props.contextFile } : {}
|
|
@@ -84,7 +94,7 @@ const devEnvContext = (props, branchId, cwd) => {
|
|
|
84
94
|
...location,
|
|
85
95
|
implyAiGateway: true,
|
|
86
96
|
env: {
|
|
87
|
-
...
|
|
97
|
+
...baseEnv,
|
|
88
98
|
...existsSync(envFile) ? readEnvFile(envFile) : {}
|
|
89
99
|
},
|
|
90
100
|
...props.projectId ? { projectId: props.projectId } : {},
|
|
@@ -114,22 +124,70 @@ const reportDevCredential = (credential) => {
|
|
|
114
124
|
log.warning(`Resolved credential secrets for this run (%s) without writing them to a file. Run \`${getCliName()} env pull\` once so restarts reuse them instead of revealing or minting again.`, credential.keys.join(", "));
|
|
115
125
|
};
|
|
116
126
|
const handler = async (props) => {
|
|
127
|
+
const shellEnv = captureShellEnv();
|
|
128
|
+
const envFiles = formatEnvFiles(loadDevEnvFiles(props, process.cwd()), process.cwd());
|
|
117
129
|
if (props.source !== void 0) {
|
|
118
|
-
await runSingleSource(props);
|
|
130
|
+
await runSingleSource(props, shellEnv, envFiles);
|
|
119
131
|
return;
|
|
120
132
|
}
|
|
121
133
|
if (props.port !== void 0) throw new Error("--port can only be used with --source. To set ports for the functions in neon.ts, give each one a `dev.port` in its config.");
|
|
122
|
-
await runFromConfig(props);
|
|
134
|
+
await runFromConfig(props, shellEnv, envFiles);
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Copy of the shell env before any dotenv file loads. PORT is read through `process.env`,
|
|
138
|
+
* which is case-insensitive on Windows, so a shell `Port` still sets the port. Exported for
|
|
139
|
+
* unit testing.
|
|
140
|
+
*/
|
|
141
|
+
const captureShellEnv = () => ({
|
|
142
|
+
...process.env,
|
|
143
|
+
PORT: process.env.PORT
|
|
144
|
+
});
|
|
145
|
+
/**
|
|
146
|
+
* Load dotenv files into process.env before neon.ts is evaluated: `--env <file>` alone
|
|
147
|
+
* (relative to cwd), else the project's {@link defaultDevEnvFiles}. A later file overrides
|
|
148
|
+
* an earlier one; a key set before loading (the shell) is never overridden.
|
|
149
|
+
*/
|
|
150
|
+
const loadDevEnvFiles = (props, cwd) => {
|
|
151
|
+
const files = props.env ? [resolve(cwd, props.env)] : defaultDevEnvFiles({
|
|
152
|
+
cwd,
|
|
153
|
+
...props.contextFile ? { contextFile: props.contextFile } : {}
|
|
154
|
+
});
|
|
155
|
+
const name = (key) => process.platform === "win32" ? key.toUpperCase() : key;
|
|
156
|
+
const preset = new Set(Object.keys(process.env).filter((key) => process.env[key] !== void 0).map(name));
|
|
157
|
+
return files.map((path) => {
|
|
158
|
+
const applied = [];
|
|
159
|
+
for (const [key, value] of Object.entries(readEnvFile(path))) {
|
|
160
|
+
if (preset.has(name(key))) continue;
|
|
161
|
+
process.env[key] = value;
|
|
162
|
+
applied.push(key);
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
path,
|
|
166
|
+
applied
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* The banner's account of the dotenv files, in load order, e.g.
|
|
172
|
+
* `.env.local (4 vars) → .env (2 vars)`. Counts are the keys each file set. Exported for
|
|
173
|
+
* unit testing.
|
|
174
|
+
*/
|
|
175
|
+
const formatEnvFiles = (files, cwd) => {
|
|
176
|
+
if (files.length === 0) return "none (no .env.local or .env)";
|
|
177
|
+
return files.map(({ path, applied }) => {
|
|
178
|
+
const count = `${applied.length} var${applied.length === 1 ? "" : "s"}`;
|
|
179
|
+
return `${relative(cwd, path) || path} (${count})`;
|
|
180
|
+
}).join(" → ");
|
|
123
181
|
};
|
|
124
182
|
/** Single-function mode: serve exactly the `--source` path (legacy behavior). */
|
|
125
|
-
const runSingleSource = async (props) => {
|
|
183
|
+
const runSingleSource = async (props, shellEnv, envFiles) => {
|
|
126
184
|
const source = resolve(process.cwd(), props.source);
|
|
127
185
|
if (!existsSync(source)) throw new Error(`Source file not found: ${source}`);
|
|
128
186
|
const branchId = await resolveBranchId(props);
|
|
129
|
-
const { vars: neonEnv, skipped, credential } = await resolveDevEnv(devEnvContext(props, branchId, process.cwd()));
|
|
187
|
+
const { vars: neonEnv, skipped, credential } = await resolveDevEnv(devEnvContext(props, branchId, process.cwd(), shellEnv));
|
|
130
188
|
reportDevCredential(credential);
|
|
131
189
|
const match = await findConfigFunctionBySource(process.cwd(), source, props.contextFile);
|
|
132
|
-
const { overlay, port } = await sourceFunctionUrlOverlay(match, props.port);
|
|
190
|
+
const { overlay, port } = await sourceFunctionUrlOverlay(match, props.port, shellEnv);
|
|
133
191
|
const unit = {
|
|
134
192
|
slug: null,
|
|
135
193
|
source,
|
|
@@ -144,19 +202,22 @@ const runSingleSource = async (props) => {
|
|
|
144
202
|
fn: []
|
|
145
203
|
}
|
|
146
204
|
};
|
|
147
|
-
await runSupervisor([unit], {
|
|
205
|
+
await runSupervisor([unit], {
|
|
206
|
+
envFiles,
|
|
207
|
+
...skipped ? { envNote: skipped.reason } : {}
|
|
208
|
+
});
|
|
148
209
|
};
|
|
149
210
|
/**
|
|
150
211
|
* Multi-function mode: serve every function declared in neon.ts. Requires a neon.ts
|
|
151
212
|
* (there is no single source to fall back on), one dev server per function.
|
|
152
213
|
*/
|
|
153
|
-
const runFromConfig = async (props) => {
|
|
214
|
+
const runFromConfig = async (props, shellEnv, envFiles) => {
|
|
154
215
|
const branchId = await resolveBranchId(props);
|
|
155
216
|
const resolved = await resolveFunctionsFromConfig(process.cwd(), void 0, props.contextFile);
|
|
156
217
|
if (resolved === null) throw new Error("No --source given and no neon.ts found. Pass --source <path> to run a single function, or add a neon.ts that declares functions under `functions`.");
|
|
157
218
|
const { configPath, functions } = resolved;
|
|
158
219
|
if (functions.length === 0) throw new Error("neon.ts has no functions to serve. Add at least one under `functions`, or pass --source <path>.");
|
|
159
|
-
const { vars: neonEnv, skipped, credential } = await resolveDevEnv(devEnvContext(props, branchId, process.cwd()));
|
|
220
|
+
const { vars: neonEnv, skipped, credential } = await resolveDevEnv(devEnvContext(props, branchId, process.cwd(), shellEnv));
|
|
160
221
|
reportDevCredential(credential);
|
|
161
222
|
const units = await planFunctionsToUnits(functions, neonEnv, DEFAULT_PORT_BASE);
|
|
162
223
|
const replan = async (searchBase, keepPorts) => {
|
|
@@ -169,6 +230,7 @@ const runFromConfig = async (props) => {
|
|
|
169
230
|
configPath,
|
|
170
231
|
replan
|
|
171
232
|
},
|
|
233
|
+
envFiles,
|
|
172
234
|
...skipped ? { envNote: skipped.reason } : {}
|
|
173
235
|
});
|
|
174
236
|
};
|
|
@@ -231,8 +293,8 @@ const localFunctionUrlEnv = (functions) => {
|
|
|
231
293
|
* `--source` serves one file. If that file is a neon.ts function, rewrite only that
|
|
232
294
|
* slug to localhost — this process is not listening for the others.
|
|
233
295
|
*/
|
|
234
|
-
const sourceFunctionUrlOverlay = async (match, cliPort) => {
|
|
235
|
-
const portSpec = portFromProps(cliPort);
|
|
296
|
+
const sourceFunctionUrlOverlay = async (match, cliPort, shellEnv = process.env) => {
|
|
297
|
+
const portSpec = portFromProps(cliPort, shellEnv);
|
|
236
298
|
if (match === void 0) return {
|
|
237
299
|
overlay: {},
|
|
238
300
|
port: portSpec
|
|
@@ -298,14 +360,14 @@ const resolveBranchId = async (props) => {
|
|
|
298
360
|
}
|
|
299
361
|
};
|
|
300
362
|
const DEFAULT_PORT_BASE = 8787;
|
|
301
|
-
const portFromProps = (port) => {
|
|
363
|
+
const portFromProps = (port, shellEnv) => {
|
|
302
364
|
if (port !== void 0) return {
|
|
303
365
|
mode: "explicit",
|
|
304
366
|
port
|
|
305
367
|
};
|
|
306
|
-
if (
|
|
368
|
+
if (shellEnv.PORT !== void 0 && shellEnv.PORT !== "") return {
|
|
307
369
|
mode: "explicit",
|
|
308
|
-
port: Number(
|
|
370
|
+
port: Number(shellEnv.PORT)
|
|
309
371
|
};
|
|
310
372
|
return {
|
|
311
373
|
mode: "search",
|
|
@@ -387,7 +449,7 @@ const READY_PATTERN = /neon-dev:ready (\d+)/;
|
|
|
387
449
|
* in place; siblings are untouched.
|
|
388
450
|
*/
|
|
389
451
|
const runSupervisor = async (units, options = {}) => {
|
|
390
|
-
const { reload, envNote } = options;
|
|
452
|
+
const { reload, envNote, envFiles } = options;
|
|
391
453
|
const runtimePath = resolveRuntimePath();
|
|
392
454
|
let shuttingDown = false;
|
|
393
455
|
const running = units.map(makeRunningUnit);
|
|
@@ -456,7 +518,7 @@ const runSupervisor = async (units, options = {}) => {
|
|
|
456
518
|
await Promise.all(running.map((r) => stopUnit(r)));
|
|
457
519
|
throw new Error("No function started. See the output above for details.");
|
|
458
520
|
}
|
|
459
|
-
printBanner(running, envNote);
|
|
521
|
+
printBanner(running, envNote, envFiles);
|
|
460
522
|
let configWatcher = null;
|
|
461
523
|
if (reload) {
|
|
462
524
|
const ops = {
|
|
@@ -709,10 +771,14 @@ const pipeChildOutput = (child, label) => {
|
|
|
709
771
|
forward("stdout");
|
|
710
772
|
forward("stderr");
|
|
711
773
|
};
|
|
712
|
-
const printBanner = (running, envNote) => {
|
|
774
|
+
const printBanner = (running, envNote, envFiles) => {
|
|
713
775
|
log.info("");
|
|
714
776
|
log.info(chalk.green.bold(" Neon Functions dev server"));
|
|
715
777
|
log.info("");
|
|
778
|
+
if (envFiles) {
|
|
779
|
+
log.info(` ${chalk.dim("env files".padEnd(20))} ${chalk.dim(envFiles)}`);
|
|
780
|
+
log.info("");
|
|
781
|
+
}
|
|
716
782
|
for (const r of running) {
|
|
717
783
|
const name = r.unit.label ?? "function";
|
|
718
784
|
const url = urlFor(r.boundPort);
|
|
@@ -876,4 +942,4 @@ const resolveRuntimePath = () => {
|
|
|
876
942
|
return candidate;
|
|
877
943
|
};
|
|
878
944
|
//#endregion
|
|
879
|
-
export { builder, command, describe, devBundleDir, devEnvContext, diffUnits, formatEnvSummary, handler, localFunctionUrlEnv, planFunctionsToUnits, reportDevCredential, sourceFunctionUrlOverlay, dev_exports as t };
|
|
945
|
+
export { builder, captureShellEnv, command, describe, devBundleDir, devEnvContext, diffUnits, formatEnvFiles, formatEnvSummary, handler, loadDevEnvFiles, localFunctionUrlEnv, planFunctionsToUnits, reportDevCredential, sourceFunctionUrlOverlay, dev_exports as t };
|
package/dist/commands/env.js
CHANGED
|
@@ -7,9 +7,9 @@ import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOpt
|
|
|
7
7
|
import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
8
8
|
import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
|
|
9
9
|
import { ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagValue, ownedEnvServiceKeys, parseEnvPullKeys } from "../env_services.js";
|
|
10
|
+
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-BNm69Qh8.js";
|
|
10
11
|
import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
11
12
|
import { warnAiGateway } from "../utils/ai_gateway_notice.js";
|
|
12
|
-
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-BNm69Qh8.js";
|
|
13
13
|
import { formatPulledEnv, pulledBranch } from "./env_output.js";
|
|
14
14
|
import { existsSync } from "node:fs";
|
|
15
15
|
import chalk from "chalk";
|
package/dist/commands/link.js
CHANGED
|
@@ -864,7 +864,7 @@ const printSummary = (props, summary) => {
|
|
|
864
864
|
*/
|
|
865
865
|
const finalizeLink = async (props, summary) => {
|
|
866
866
|
printSummary(props, summary);
|
|
867
|
-
if (!summary.branch || !summary.projectId || summary.noChecks) return;
|
|
867
|
+
if (!summary.branch || !summary.projectId || summary.noChecks || props.deferEnvPull === true) return;
|
|
868
868
|
const { config: _offerConfig, ...rest } = props;
|
|
869
869
|
await autoPullEnvAfterPin({
|
|
870
870
|
...rest,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { writer } from "../writer.js";
|
|
2
|
-
import { branchIdFromProps } from "../utils/enrichers.js";
|
|
3
2
|
import { o as resolveNeonEnvVars } from "../_chunks/env-BNm69Qh8.js";
|
|
3
|
+
import { branchIdFromProps } from "../utils/enrichers.js";
|
|
4
4
|
import YAML from "yaml";
|
|
5
5
|
//#region src/commands/subscribe.ts
|
|
6
6
|
const builder = (argv) => argv.usage("$0 realtime subscribe \"<query>\" [options]").positional("query", {
|
package/dist/dev/runtime.js
CHANGED
|
@@ -121,6 +121,14 @@ const portSelectionFromEnv = (env) => {
|
|
|
121
121
|
mode: "explicit",
|
|
122
122
|
port: parsePort(explicit, "NEON_DEV_PORT")
|
|
123
123
|
};
|
|
124
|
+
const searchBase = env.NEON_DEV_PORT_BASE;
|
|
125
|
+
if (searchBase !== void 0 && searchBase !== "") {
|
|
126
|
+
const from = Number(searchBase);
|
|
127
|
+
return {
|
|
128
|
+
mode: "search",
|
|
129
|
+
from: Number.isInteger(from) ? from : DEFAULT_SEARCH_BASE
|
|
130
|
+
};
|
|
131
|
+
}
|
|
124
132
|
const injected = env.PORT;
|
|
125
133
|
if (injected !== void 0 && injected !== "") return {
|
|
126
134
|
mode: "explicit",
|
package/dist/env_file.js
CHANGED
|
@@ -15,6 +15,14 @@ const resolveEnvFilePath = (location, file) => {
|
|
|
15
15
|
return join(dir, ".env.local");
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
|
+
* The dotenv files `neon dev` loads by default, in load order: the project directory's
|
|
19
|
+
* `.env`, then `.env.local`, whose values override it. Missing files are skipped.
|
|
20
|
+
*/
|
|
21
|
+
const defaultDevEnvFiles = (location) => {
|
|
22
|
+
const dir = projectDir(location);
|
|
23
|
+
return [join(dir, ".env"), join(dir, ".env.local")].filter((path) => existsSync(path));
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
18
26
|
* Merge `updates` into the dotenv content at `path`, preserving every other line
|
|
19
27
|
* (comments, blank lines, unrelated keys) and the file's existing order. Keys present in
|
|
20
28
|
* both are updated in place; keys only in `updates` are appended. A non-existent file is
|
|
@@ -145,4 +153,4 @@ const trimTrailingBlank = (lines) => {
|
|
|
145
153
|
return out;
|
|
146
154
|
};
|
|
147
155
|
//#endregion
|
|
148
|
-
export { loadEnvFileIntoProcess, mergeEnvContent, mergeEnvFile, readEnvFile, resolveEnvFilePath };
|
|
156
|
+
export { defaultDevEnvFiles, loadEnvFileIntoProcess, mergeEnvContent, mergeEnvFile, readEnvFile, resolveEnvFilePath };
|
package/dist/init/auth.js
CHANGED
|
@@ -61,10 +61,7 @@ const runAuthenticatedMcp = async (options) => {
|
|
|
61
61
|
const pullInitEnv = async (props) => {
|
|
62
62
|
const authenticated = authContext(props, ["env", "pull"]);
|
|
63
63
|
await ensureAuth(authenticated);
|
|
64
|
-
const opts = {
|
|
65
|
-
announce: true,
|
|
66
|
-
implyAiGateway: true
|
|
67
|
-
};
|
|
64
|
+
const opts = { announce: true };
|
|
68
65
|
try {
|
|
69
66
|
return await pull(authenticated, opts);
|
|
70
67
|
} catch (error) {
|
package/dist/init/choices.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { FUNCTION_SLUG } from "../config_template.js";
|
|
2
|
+
import { l as functionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
|
|
1
3
|
//#region src/init/choices.ts
|
|
2
4
|
const INIT_CONFIG_SERVICES_CONFLICT = "--no-config cannot be combined with --services.";
|
|
3
5
|
const resolveInitConfigChoice = (input) => {
|
|
@@ -21,11 +23,23 @@ const configPlanFromResolution = (resolution, accepted) => {
|
|
|
21
23
|
if (resolution.kind === "skip") return { kind: "skip" };
|
|
22
24
|
return resolution;
|
|
23
25
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
/**
|
|
27
|
+
* A neon.ts policy pull fails while the branch lacks a declared service. These two need
|
|
28
|
+
* no `deploy`: the AI Gateway exists on every branch, and function URLs derive from the
|
|
29
|
+
* branch host.
|
|
30
|
+
*/
|
|
31
|
+
const DEPLOY_FREE_SERVICES = /* @__PURE__ */ new Set(["ai-gateway", "functions"]);
|
|
32
|
+
const needsDeploy = (declared) => declared.some((service) => !DEPLOY_FREE_SERVICES.has(service));
|
|
33
|
+
const initEnvPullAfterConfig = (input) => {
|
|
34
|
+
if (!input.projectId || !input.branch) return { kind: "skip" };
|
|
35
|
+
if (!input.linkDeferredPull && !input.wroteNewFile) return { kind: "skip" };
|
|
36
|
+
if (input.claimable && input.declared.length > 0) return { kind: "skip" };
|
|
37
|
+
if (!needsDeploy(input.declared)) return { kind: "policy" };
|
|
38
|
+
return {
|
|
39
|
+
kind: "services",
|
|
40
|
+
services: input.declared.includes("ai-gateway") ? ["postgres", "ai-gateway"] : ["postgres"],
|
|
41
|
+
envKeys: input.declared.includes("functions") ? [functionBaseUrlKey(FUNCTION_SLUG)] : []
|
|
42
|
+
};
|
|
43
|
+
};
|
|
30
44
|
//#endregion
|
|
31
|
-
export { INIT_CONFIG_SERVICES_CONFLICT, configPlanFromResolution,
|
|
45
|
+
export { INIT_CONFIG_SERVICES_CONFLICT, configPlanFromResolution, initEnvPullAfterConfig, resolveInitConfigChoice };
|
package/dist/init/copy.js
CHANGED
|
@@ -99,16 +99,22 @@ const offlinePinNext = (input) => [
|
|
|
99
99
|
...input.envPull !== void 0 ? ["Then pull the branch's environment variables:", input.envPull] : []
|
|
100
100
|
];
|
|
101
101
|
const skippedLinkNext = () => ["To connect this directory to a Neon project:", `${getCliName()} link`];
|
|
102
|
-
const extraServicesNext = () => [
|
|
103
|
-
"Review neon.ts and confirm the linked project and branch before deploying.",
|
|
102
|
+
const extraServicesNext = (input) => [
|
|
104
103
|
"Preview the changes:",
|
|
105
104
|
`${getCliName()} config plan`,
|
|
106
105
|
"",
|
|
107
|
-
"Deploy when the changes and target are correct:",
|
|
106
|
+
"Deploy when the changes and target are correct. Deploy also pulls the branch's environment variables:",
|
|
108
107
|
`${getCliName()} deploy`,
|
|
109
108
|
"",
|
|
110
|
-
"
|
|
111
|
-
|
|
109
|
+
"Optional, once deployed:",
|
|
110
|
+
...input.functions ? [
|
|
111
|
+
"Run Neon Functions locally:",
|
|
112
|
+
`${getCliName()} dev`,
|
|
113
|
+
""
|
|
114
|
+
] : [],
|
|
115
|
+
"Work on a development branch created from neon.ts:",
|
|
116
|
+
`${getCliName()} checkout dev --create`,
|
|
117
|
+
...input.skills ? ["", "Set up the services declared in neon.ts with your coding agent. The Neon agent skills cover best practices for each one."] : []
|
|
112
118
|
];
|
|
113
119
|
const existingConfigNext = () => ["The existing Neon config was preserved. Refresh environment variables when its declared services are available:", `${getCliName()} env pull`];
|
|
114
120
|
const claimableNext = (expiresAt) => [
|
|
@@ -128,10 +134,18 @@ const claimableThenServicesNext = (expiresAt) => [
|
|
|
128
134
|
"Refresh local environment variables:",
|
|
129
135
|
`${getCliName()} env pull`
|
|
130
136
|
];
|
|
131
|
-
const installFailedNext = (command) => ({
|
|
137
|
+
const installFailedNext = (command, envPull) => ({
|
|
132
138
|
heading: HEADING_FAILED,
|
|
133
139
|
body: "Could not install the Neon dependencies.",
|
|
134
|
-
next: [
|
|
140
|
+
next: [
|
|
141
|
+
"Install the dependencies, then run neon init again:",
|
|
142
|
+
command,
|
|
143
|
+
...envPull ? [
|
|
144
|
+
"",
|
|
145
|
+
"Then pull the branch's environment variables:",
|
|
146
|
+
`${getCliName()} env pull`
|
|
147
|
+
] : []
|
|
148
|
+
]
|
|
135
149
|
});
|
|
136
150
|
const MCP_API_KEY_NEEDS_AUTH = "API-key MCP setup needs a Neon credential to mint the key. Sign in with `neon login` or pass a personal API key.";
|
|
137
151
|
/** `mcp` is the `neon mcp` invocation without an auth flag. */
|
package/dist/init/run.js
CHANGED
|
@@ -26,7 +26,7 @@ import { InitCancelled, raceSigint, restoreCursor } from "./cancelled.js";
|
|
|
26
26
|
import { pickAgentSetupInteractively, pickInitAgentsInteractively, pickInitConfigInteractively, pickInitMcpAuthInteractively, pickInitMcpConfigLocationInteractively, pickInitModeInteractively, pickInitPackageManagerInteractively, pickInitProjectSetupInteractively, pickInitServicesInteractively, pickInitSkillsInteractively } from "./wizard.js";
|
|
27
27
|
import { runToolingSteps } from "./tooling.js";
|
|
28
28
|
import { create } from "../commands/claim.js";
|
|
29
|
-
import { INIT_CONFIG_SERVICES_CONFLICT, configPlanFromResolution,
|
|
29
|
+
import { INIT_CONFIG_SERVICES_CONFLICT, configPlanFromResolution, initEnvPullAfterConfig, resolveInitConfigChoice } from "./choices.js";
|
|
30
30
|
import { detectInitEnvironment } from "./detect.js";
|
|
31
31
|
import { createInitFunnel, emitInitStart, finishInitFunnel, initStartProperties } from "./funnel.js";
|
|
32
32
|
import { assertAgentSetupFlags, hasInitMcpFlags, inferInitAgentSetup, resolveInitMode } from "./mode.js";
|
|
@@ -369,7 +369,6 @@ const runInit = async (props) => {
|
|
|
369
369
|
});
|
|
370
370
|
if (toolingSteps.length > 0) await runTooling(toolingSteps, earlyTooling);
|
|
371
371
|
let projectSetup = "skip";
|
|
372
|
-
let linkedNow = false;
|
|
373
372
|
let claimExpiresAt;
|
|
374
373
|
const noLink = props.link === false;
|
|
375
374
|
const linkByDefault = detection.authenticated || hasExplicitLinkInputs && !yes;
|
|
@@ -394,6 +393,7 @@ const runInit = async (props) => {
|
|
|
394
393
|
funnel.link = "skipped";
|
|
395
394
|
} else if (props.pickProjectSetup !== void 0 || detection.interactive) projectSetup = await (props.pickProjectSetup ?? pickInitProjectSetupInteractively)();
|
|
396
395
|
else throw new Error(NON_TTY_LINK_NEEDS_AUTH);
|
|
396
|
+
const deferLinkPull = !existingConfig && props.config !== false;
|
|
397
397
|
const linkProps = {
|
|
398
398
|
apiClient: props.apiClient,
|
|
399
399
|
apiKey: props.apiKey,
|
|
@@ -404,6 +404,7 @@ const runInit = async (props) => {
|
|
|
404
404
|
clear: false,
|
|
405
405
|
checks: true,
|
|
406
406
|
envPull: true,
|
|
407
|
+
deferEnvPull: deferLinkPull,
|
|
407
408
|
config: false,
|
|
408
409
|
cwd,
|
|
409
410
|
...linkInputs,
|
|
@@ -425,13 +426,11 @@ const runInit = async (props) => {
|
|
|
425
426
|
apiKey: props.apiKey,
|
|
426
427
|
...props.profile !== void 0 ? { profile: props.profile } : {}
|
|
427
428
|
});
|
|
428
|
-
linkedNow = true;
|
|
429
429
|
funnel.link = "claimable";
|
|
430
430
|
claimExpiresAt = readLinkedClaimableCredentials(configDir, readContextFile(contextFile))?.expiresAt;
|
|
431
431
|
} else if (projectSetup === "link") {
|
|
432
432
|
printInitProgress(detection.authenticated ? PROGRESS.link : PROGRESS.auth);
|
|
433
433
|
await (props.linkProject ?? runAuthenticatedLink)(linkProps);
|
|
434
|
-
linkedNow = true;
|
|
435
434
|
funnel.link = "linked";
|
|
436
435
|
}
|
|
437
436
|
const pinnedOffline = funnel.link === "skipped" && !noLink && linkInputs.orgId !== void 0 && linkInputs.projectId !== void 0 && linkInputs.branch !== void 0 && linkInputs.projectName === void 0 && linkInputs.regionId === void 0;
|
|
@@ -441,7 +440,6 @@ const runInit = async (props) => {
|
|
|
441
440
|
checks: false,
|
|
442
441
|
envPull: false
|
|
443
442
|
});
|
|
444
|
-
linkedNow = true;
|
|
445
443
|
funnel.link = "linked";
|
|
446
444
|
}
|
|
447
445
|
if (delayMcp && tooling.setup !== "skip" && props.mcpProjectScoped === true && funnel.link === "skipped" && !noLink) mcpState.deferredPin = linkInputs.projectId !== void 0 ? quoteFlagValue(linkInputs.projectId) : "<project-id>";
|
|
@@ -501,11 +499,13 @@ const runInit = async (props) => {
|
|
|
501
499
|
};
|
|
502
500
|
let wroteNewFile = false;
|
|
503
501
|
let extraServices = false;
|
|
502
|
+
let declared = [];
|
|
504
503
|
let selectedServices = null;
|
|
504
|
+
const linkDeferredPull = deferLinkPull && projectSetup === "link" && funnel.link === "linked";
|
|
505
505
|
if (configPlan.kind === "write") {
|
|
506
506
|
const planned = existingConfig ? void 0 : configPlan.services ?? ["postgres"];
|
|
507
507
|
if (planned !== void 0) {
|
|
508
|
-
|
|
508
|
+
declared = parseConfigInitServices(planned);
|
|
509
509
|
extraServices = declared.length > 0;
|
|
510
510
|
selectedServices = expandTelemetryServices(declared);
|
|
511
511
|
funnel.services = selectedServices;
|
|
@@ -528,7 +528,7 @@ const runInit = async (props) => {
|
|
|
528
528
|
});
|
|
529
529
|
} catch (error) {
|
|
530
530
|
if (error instanceof ConfigInstallFailed) {
|
|
531
|
-
const failed = installFailedNext(error.command);
|
|
531
|
+
const failed = installFailedNext(error.command, linkDeferredPull);
|
|
532
532
|
printInitDone(formatInitDone({
|
|
533
533
|
heading: failed.heading,
|
|
534
534
|
body: failed.body,
|
|
@@ -546,14 +546,15 @@ const runInit = async (props) => {
|
|
|
546
546
|
const context = readContextFile(contextFile);
|
|
547
547
|
const branch = contextBranch(context);
|
|
548
548
|
const linked = isLinked(contextFile);
|
|
549
|
-
const
|
|
550
|
-
|
|
549
|
+
const envPullPlan = funnel.link === "skipped" || pinnedOffline ? { kind: "skip" } : initEnvPullAfterConfig({
|
|
550
|
+
linkDeferredPull,
|
|
551
551
|
wroteNewFile,
|
|
552
|
-
|
|
553
|
-
|
|
552
|
+
declared,
|
|
553
|
+
claimable: funnel.link === "claimable",
|
|
554
554
|
...typeof context.projectId === "string" ? { projectId: context.projectId } : {},
|
|
555
555
|
...branch !== void 0 ? { branch } : {}
|
|
556
|
-
})
|
|
556
|
+
});
|
|
557
|
+
if (envPullPlan.kind !== "skip") try {
|
|
557
558
|
if (typeof context.projectId !== "string" || typeof branch !== "string") throw new Error("Env pull requires a linked project and branch.");
|
|
558
559
|
printInitProgress(PROGRESS.env);
|
|
559
560
|
await raceSigint((props.envPull ?? pullInitEnv)({
|
|
@@ -561,7 +562,11 @@ const runInit = async (props) => {
|
|
|
561
562
|
output: props.output,
|
|
562
563
|
cwd,
|
|
563
564
|
projectId: context.projectId,
|
|
564
|
-
branch
|
|
565
|
+
branch,
|
|
566
|
+
...envPullPlan.kind === "services" ? {
|
|
567
|
+
services: envPullPlan.services,
|
|
568
|
+
envKeys: envPullPlan.envKeys
|
|
569
|
+
} : {}
|
|
565
570
|
}));
|
|
566
571
|
} catch (error) {
|
|
567
572
|
if (error instanceof InitCancelled) throw error;
|
|
@@ -617,7 +622,10 @@ const runInit = async (props) => {
|
|
|
617
622
|
next.push(...claimableNext(claimExpiresAt));
|
|
618
623
|
} else if (pendingServices) {
|
|
619
624
|
if (next.length > 0) next.push("");
|
|
620
|
-
next.push(...extraServicesNext(
|
|
625
|
+
next.push(...extraServicesNext({
|
|
626
|
+
skills: installsSkills(reportedTooling()),
|
|
627
|
+
functions: declared.includes("functions")
|
|
628
|
+
}));
|
|
621
629
|
} else if (pendingExisting) {
|
|
622
630
|
if (next.length > 0) next.push("");
|
|
623
631
|
next.push(...existingConfigNext());
|
|
@@ -698,6 +706,8 @@ const withoutMcp = (tooling) => {
|
|
|
698
706
|
};
|
|
699
707
|
return tooling;
|
|
700
708
|
};
|
|
709
|
+
/** The Neon plugin bundles the skills. */
|
|
710
|
+
const installsSkills = (tooling) => tooling.setup === "skills-mcp" ? tooling.skillsAgents.length > 0 : tooling.setup !== "skip";
|
|
701
711
|
const agentsFromTooling = (tooling) => {
|
|
702
712
|
switch (tooling.setup) {
|
|
703
713
|
case "skip": return [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.6.0",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
"yaml": "^2.9.0",
|
|
71
71
|
"yargs": "17.7.2",
|
|
72
72
|
"yoctocolors": "^2.1.2",
|
|
73
|
-
"@neon/
|
|
74
|
-
"@neon/realtime": "0.1.0",
|
|
73
|
+
"@neon/config": "1.9.1",
|
|
75
74
|
"@neon/config-runtime": "1.10.0",
|
|
76
|
-
"@neon/
|
|
75
|
+
"@neon/realtime": "0.1.0",
|
|
76
|
+
"@neon/sdk": "7.2.0"
|
|
77
77
|
},
|
|
78
78
|
"optionalDependencies": {
|
|
79
79
|
"@napi-rs/keyring": "1.3.0",
|
|
@@ -111,8 +111,8 @@
|
|
|
111
111
|
"vitest": "^3.0.9",
|
|
112
112
|
"@neon-internals/cli-core": "0.0.0",
|
|
113
113
|
"@neon-internals/env-core": "0.0.32",
|
|
114
|
-
"@neon/env": "1.6.2",
|
|
115
114
|
"@neon/e2e-harness": "0.0.0",
|
|
115
|
+
"@neon/env": "1.6.2",
|
|
116
116
|
"@neon/functions": "0.11.0"
|
|
117
117
|
},
|
|
118
118
|
"publishConfig": {
|