neon 7.0.8 → 8.0.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 CHANGED
@@ -76,6 +76,8 @@ neon projects list --api-key <neon_api_key>
76
76
 
77
77
  For information about obtaining an Neon API key, see [Authentication](https://neon.com/docs/reference/api/get-started), in the _Neon API Reference_.
78
78
 
79
+ To check which account and credential a command uses, run `neon me` with the same `--profile` or `--api-key` option and environment as that command. The `Authentication` row names the source, such as `OAuth (profile DEFAULT)`, `API key (profile work)`, `API key (--api-key)`, or `API key (NEON_API_KEY)`.
80
+
79
81
  ## Create a project without an account
80
82
 
81
83
  `neon claim create` provisions a temporary Claimable Neon project for an agent without
@@ -594,7 +596,9 @@ If you'd rather not keep env vars on disk, inject them at runtime instead with `
594
596
 
595
597
  **`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.…`). `dev` writes nothing, but it does *read* your `.env` / `.env.local` to reuse the branch credential behind the AI Gateway and object storage. 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.
596
598
 
597
- **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>`.
599
+ **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.
600
+
601
+ **The project directory** is the directory holding that `.neon`, or the current directory when there is none. The project's `neon.ts` and the default `.env` / `.env.local` live there and nowhere else: a `neon.ts` in a parent directory never applies. An explicit `--config` or `--file` path still wins.
598
602
 
599
603
  **`.gitignore` scaffolding**: when `.neon` is **created** for the first time, the CLI also makes sure a `.gitignore` sits alongside it listing `.neon`. If `.gitignore` doesn't exist it's created with a single `.neon` line; if it does exist, `.neon` is appended only when missing (no duplicates, your other entries are left alone). On subsequent updates to an existing `.neon`, `.gitignore` is left untouched — so if you deliberately un-ignore `.neon` (e.g. to commit shared context), the entry is not re-added on every command.
600
604
 
@@ -732,7 +736,7 @@ neon deploy
732
736
  2. `projectId` from the closest `.neon` file (found by walking up from the current directory — see "Where `.neon` lives" above)
733
737
  3. If still unresolved and the API key maps to exactly one project, that project is auto-detected
734
738
 
735
- The branch is chosen with `--branch <id|name>`; without it the project's default branch is used. The policy itself is found by walking up from the current directory for a `neon.ts`, or pass `--config <path>` to point at one explicitly.
739
+ The branch is chosen with `--branch <id|name>`; without it the project's default branch is used. The policy is the `neon.ts` in the project directory (next to `.neon`, or the current directory without one); pass `--config <path>` to point at another file.
736
740
 
737
741
  **Apply-only flags** (also available on `deploy`):
738
742
 
@@ -1,10 +1,11 @@
1
1
  import { log } from "../log.js";
2
2
  import { getCliName } from "../utils/cli_name.js";
3
3
  import { declaredNeonServices } from "../config_services.js";
4
+ import { loadProjectConfig } from "../project.js";
4
5
  import { a as defaultAiGatewayCredential, c as fetchEnvKeysState, d as isLiveCredential, h as toEntries, i as credentialName, l as functionBaseUrlKey, m as resolveBranchPolicy, n as createApiFromOptions, o as defaultStorageCredential, p as policyEnvKeys, r as credentialEnvKeys, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./env-N19A7qND.js";
5
6
  import { ENV_PULL_SERVICES, envKeysForSelection, serviceForEnvKey, servicesForEnvKeys } from "../env_services.js";
6
7
  import { formatInstallCommand, resolvePackageManager } from "../utils/package_manager.js";
7
- import { ErrorCode, createNeonApiFromOptions, isPlatformError, loadConfigFromFile } from "@neon/config";
8
+ import { ErrorCode, createNeonApiFromOptions, isPlatformError } from "@neon/config";
8
9
  import { credentialScopesSatisfied } from "@neon/config/v1";
9
10
  import { plan, pullConfig } from "@neon/config-runtime";
10
11
  //#region ../../internals/env-core/dist/reuse-secrets.js
@@ -708,8 +709,9 @@ const looksLikeMissingDependency = (err) => {
708
709
  };
709
710
  const loadNeonConfig = async (ctx) => {
710
711
  try {
711
- const { config } = await loadConfigFromFile({
712
+ const { config } = await loadProjectConfig({
712
713
  cwd: ctx.cwd,
714
+ ...ctx.contextFile ? { contextFile: ctx.contextFile } : {},
713
715
  ...ctx.config ? { path: ctx.config } : {},
714
716
  ...ctx.omitUnsetFunctionEnv ? { unsetFunctionEnv: "omit" } : {}
715
717
  });
@@ -366,6 +366,7 @@ const ensureAuth = async (props) => {
366
366
  log.debug("Using an API key to authorize requests");
367
367
  setAuthContext({
368
368
  source: "api-key",
369
+ apiKeyFrom: selection.source === "explicit-api-key" ? "flag" : "env",
369
370
  configDir: props.configDir
370
371
  });
371
372
  props.apiClient = getApiClient({
@@ -77,7 +77,7 @@ const handler = async (props) => {
77
77
  const cwd = process.cwd();
78
78
  const git = readGitContext(cwd);
79
79
  const event = buildCheckoutEvent(git, props.id);
80
- const config = await loadHookConfig(cwd);
80
+ const config = await loadHookConfig(cwd, props.contextFile);
81
81
  const hooks = config?.experimental?.hooks;
82
82
  if (props.id) {
83
83
  const renamed = await runCheckoutBeforeHook({
@@ -108,6 +108,7 @@ const handler = async (props) => {
108
108
  projectId,
109
109
  branchId,
110
110
  branchName,
111
+ contextFile: props.contextFile,
111
112
  ...props.apiKey ? { apiKey: props.apiKey } : {},
112
113
  ...props.apiHost ? { apiHost: props.apiHost } : {},
113
114
  ...props.color !== void 0 ? { color: props.color } : {},
@@ -239,6 +240,7 @@ const createCheckoutBranch = async (props, projectId, name, branches, hookCtx) =
239
240
  const fromPolicy = await createBranchFromPolicyOnCheckout({
240
241
  projectId,
241
242
  branchName: name,
243
+ contextFile: props.contextFile,
242
244
  ...props.apiKey ? { apiKey: props.apiKey } : {},
243
245
  ...props.apiHost ? { apiHost: props.apiHost } : {},
244
246
  ...props.color !== void 0 ? { color: props.color } : {},
@@ -8,13 +8,12 @@ import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
8
8
  import { assertionHasExpired, listClaimableCredentials, readClaimableCredentials, readLinkedClaimableCredentials, removeClaimableCredentials, writeClaimableCredentials } from "../claimable/state.js";
9
9
  import { NEON_SERVICES, deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
10
10
  import { claimableDataApiCreateBody, declaredNeonServices } from "../config_services.js";
11
+ import { projectConfigPath } from "../project.js";
11
12
  import { resolveEnvFilePath } from "../env_file.js";
12
13
  import { writer } from "../writer.js";
13
14
  import { noPassthrough } from "../utils/flags.js";
14
15
  import { pull } from "./env.js";
15
16
  import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
16
- import { dirname, join, resolve } from "node:path";
17
- import { homedir } from "node:os";
18
17
  import open from "open";
19
18
  import prompts from "prompts";
20
19
  import { loadConfigFromFile } from "@neon/config-runtime";
@@ -29,7 +28,6 @@ var claim_exports = /* @__PURE__ */ __exportAll({
29
28
  command: () => command,
30
29
  create: () => create,
31
30
  describe: () => describe,
32
- findNeonConfig: () => findNeonConfig,
33
31
  handler: () => handler
34
32
  });
35
33
  const CAPABILITY_FOR_SERVICE = {
@@ -63,28 +61,8 @@ const claimableCapabilities = (services) => {
63
61
  for (const service of services) requested.add(CAPABILITY_FOR_SERVICE[service]);
64
62
  return CAPABILITY_ORDER.filter((capability) => requested.has(capability));
65
63
  };
66
- const CONFIG_FILENAMES = [
67
- "neon.ts",
68
- "neon.mts",
69
- "neon.js",
70
- "neon.mjs"
71
- ];
72
- const findNeonConfig = (cwd = process.cwd()) => {
73
- let current = resolve(cwd);
74
- const stop = resolve(homedir());
75
- while (true) {
76
- for (const name of CONFIG_FILENAMES) {
77
- const candidate = join(current, name);
78
- if (existsSync(candidate)) return candidate;
79
- }
80
- if (existsSync(join(current, ".git")) || current === stop) return void 0;
81
- const parent = dirname(current);
82
- if (parent === current) return void 0;
83
- current = parent;
84
- }
85
- };
86
- const loadCreatePolicy = async (explicitPath) => {
87
- const path = explicitPath ?? findNeonConfig();
64
+ const loadCreatePolicy = async (explicitPath, contextFile) => {
65
+ const path = explicitPath ?? projectConfigPath(contextFile ? { contextFile } : {});
88
66
  if (!path) return void 0;
89
67
  const { config } = await loadConfigFromFile({ path });
90
68
  return {
@@ -135,7 +113,7 @@ const builder = (argv) => argv.usage("$0 claim <sub-command> [options]").option(
135
113
  describe: "Target dotenv file. Defaults to an existing .env, otherwise .env.local",
136
114
  type: "string"
137
115
  }).option("config", {
138
- describe: "Path to neon.ts for registration and the bundled env pull. Defaults to walking up from the current directory",
116
+ describe: "Path to neon.ts for registration and the bundled env pull. Defaults to the neon.ts in the project directory (next to .neon, or cwd without one)",
139
117
  type: "string"
140
118
  }).option("env-pull", {
141
119
  describe: "Write the same Neon env vars `env pull` would",
@@ -148,7 +126,7 @@ const builder = (argv) => argv.usage("$0 claim <sub-command> [options]").option(
148
126
  flag: "--service",
149
127
  onDeprecated: (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical))
150
128
  }) : [];
151
- const policy = await loadCreatePolicy(typeof args.config === "string" ? args.config : void 0);
129
+ const policy = await loadCreatePolicy(typeof args.config === "string" ? args.config : void 0, typeof args.contextFile === "string" ? args.contextFile : void 0);
152
130
  const configuredServices = policy ? declaredNeonServices(policy.config) : [];
153
131
  const dataApi = policy ? claimableDataApiCreateBody(policy.config) : void 0;
154
132
  await create({
@@ -217,7 +195,10 @@ const create = async (props) => {
217
195
  if (existing.projectId || existing.orgId) throw new Error(`${props.contextFile} already links this directory to a Neon project. Run \`neon claim create\` from an unlinked directory.`);
218
196
  const contextFileExisted = existsSync(props.contextFile);
219
197
  const cwd = props.cwd ?? process.cwd();
220
- const envFile = props.envPull ? resolveEnvFilePath(cwd, props.file) : void 0;
198
+ const envFile = props.envPull ? resolveEnvFilePath({
199
+ cwd,
200
+ contextFile: props.contextFile
201
+ }, props.file) : void 0;
221
202
  const envFileExisted = envFile ? existsSync(envFile) : false;
222
203
  const previousEnv = envFileExisted && envFile ? readFileSync(envFile) : void 0;
223
204
  const client = new ClaimableClient(props.claimableHost);
@@ -482,4 +463,4 @@ const finishClaimedContext = (props, projectId, contextMatches) => {
482
463
  log.info("Dropped the local identity assertion. The next command needs `neon auth` or `neon link`.");
483
464
  };
484
465
  //#endregion
485
- export { CLAIM_CREATE_FIELDS, CLAIM_LIST_FIELDS, aliases, builder, claimCreateFields, claimableCapabilities, command, create, describe, findNeonConfig, handler, claim_exports as t };
466
+ export { CLAIM_CREATE_FIELDS, CLAIM_LIST_FIELDS, aliases, builder, claimCreateFields, claimableCapabilities, command, create, describe, handler, claim_exports as t };
@@ -9,6 +9,7 @@ import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES, CONFIG_INIT_UNAVAILABLE,
9
9
  import { editNeonConfig } from "../config_edit.js";
10
10
  import { toNeonConfigView } from "../config_format.js";
11
11
  import { declaredNeonServices } from "../config_services.js";
12
+ import { loadProjectConfig, neonConfigFilename, projectConfigPath, projectDir } from "../project.js";
12
13
  import { loadEnvFileIntoProcess } from "../env_file.js";
13
14
  import { formatInstallCommand, installArgs, resolvePackageManager, runCommand } from "../utils/package_manager.js";
14
15
  import { writer } from "../writer.js";
@@ -24,10 +25,9 @@ import { zipBundle } from "../utils/zip.js";
24
25
  import { autoPullEnvAfterPin } from "./env.js";
25
26
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
26
27
  import { dirname, join, relative, resolve } from "node:path";
27
- import { homedir } from "node:os";
28
28
  import chalk from "chalk";
29
29
  import { packagesToStage, previewGaWarningForConfig, resolveConfig } from "@neon/config";
30
- import { PushConflictError, apply, assertZipWithinLimits, bundleAsIs, createBranch, describeNativeFinding, enforceLimits, findUndeclaredNativePackages, inspect, isPartialBranchCreateError, loadConfigFromFile as loadConfigFromFile$1, plan, resolveEsbuildEntry, traceNativePackages, zipFunctionBundle } from "@neon/config-runtime";
30
+ import { PushConflictError, apply, assertZipWithinLimits, bundleAsIs, createBranch, describeNativeFinding, enforceLimits, findUndeclaredNativePackages, inspect, isPartialBranchCreateError, plan, resolveEsbuildEntry, traceNativePackages, zipFunctionBundle } from "@neon/config-runtime";
31
31
  //#region src/commands/config.ts
32
32
  var config_exports = /* @__PURE__ */ __exportAll({
33
33
  ConfigInstallFailed: () => ConfigInstallFailed,
@@ -42,9 +42,7 @@ var config_exports = /* @__PURE__ */ __exportAll({
42
42
  envFlag: () => envFlag,
43
43
  envPullFlag: () => envPullFlag,
44
44
  handler: () => handler,
45
- hasNeonConfigFile: () => hasNeonConfigFile,
46
45
  initCmd: () => initCmd,
47
- neonConfigFilename: () => neonConfigFilename,
48
46
  planCmd: () => planCmd,
49
47
  status: () => status
50
48
  });
@@ -117,16 +115,6 @@ const DEPENDENCY_FIELDS = [
117
115
  "peerDependencies",
118
116
  "optionalDependencies"
119
117
  ];
120
- /** Config filenames the runtime loads (mirrors @neon/config's loader). */
121
- const NEON_CONFIG_FILENAMES = [
122
- "neon.ts",
123
- "neon.mts",
124
- "neon.js",
125
- "neon.mjs"
126
- ];
127
- const neonConfigFilename = (dir) => NEON_CONFIG_FILENAMES.find((name) => existsSync(join(dir, name)));
128
- /** Whether `dir` already has a Neon config file the runtime would load. */
129
- const hasNeonConfigFile = (dir) => neonConfigFilename(dir) !== void 0;
130
118
  var ConfigInstallFailed = class extends Error {
131
119
  constructor(command) {
132
120
  super("Could not install the Neon dependencies.");
@@ -216,6 +204,14 @@ const seedFromBranch = async (props) => {
216
204
  branchName: live.branch.name
217
205
  };
218
206
  };
207
+ /** The install command to print, prefixed with a `cd` when the project is not the cwd. */
208
+ const installCommandFrom = (dir, pm, missing) => {
209
+ const command = formatInstallCommand(pm, [...missing]);
210
+ const rel = relative(process.cwd(), dir);
211
+ if (rel === "") return command;
212
+ const target = rel.length <= dir.length ? rel : dir;
213
+ return `cd ${shellQuote(target)} && ${command}`;
214
+ };
219
215
  /** Install the config packages the project is missing, or say how to. */
220
216
  const ensureConfigPackages = async (cwd, props) => {
221
217
  const run = props.run ?? runCommand;
@@ -226,16 +222,21 @@ const ensureConfigPackages = async (cwd, props) => {
226
222
  }
227
223
  const pm = props.packageManager ?? resolvePackageManager(cwd);
228
224
  const args = installArgs(pm, missing);
225
+ const installHint = installCommandFrom(cwd, pm, missing);
229
226
  if (props.install === false) {
230
- if (!props.silent) log.info("Install the Neon config packages to use neon.ts: %s", formatInstallCommand(pm, missing));
227
+ if (!props.silent) log.info("Install the Neon config packages to use neon.ts: %s", installHint);
231
228
  return;
232
229
  }
230
+ const pkgPath = join(cwd, "package.json");
231
+ if (!existsSync(pkgPath)) {
232
+ writeFileSync(pkgPath, "{}\n");
233
+ if (!props.silent) log.info("Created package.json.");
234
+ }
233
235
  ensureDirectoryGitignored(join(cwd, "node_modules"));
234
236
  if (!props.silent) log.info("Installing %s with %s…", missing.join(", "), pm);
235
237
  if (!await run(pm, args, cwd)) {
236
- const command = formatInstallCommand(pm, missing);
237
- if (props.requireInstall) throw new ConfigInstallFailed(command);
238
- log.warning("Could not install the config packages automatically. Run by hand: %s", command);
238
+ if (props.requireInstall) throw new ConfigInstallFailed(installHint);
239
+ log.warning("Could not install the config packages automatically. Run by hand: %s", installHint);
239
240
  }
240
241
  };
241
242
  /**
@@ -244,7 +245,7 @@ const ensureConfigPackages = async (cwd, props) => {
244
245
  * Local-only unless `--from-branch` is set (see {@link isConfigInit}).
245
246
  */
246
247
  const initCmd = async (props) => {
247
- const cwd = props.cwd ?? process.cwd();
248
+ const cwd = props.cwd ?? projectDir(props.contextFile ? { contextFile: props.contextFile } : {});
248
249
  const existing = neonConfigFilename(cwd);
249
250
  if (existing) {
250
251
  if (!props.silent) {
@@ -274,26 +275,11 @@ const initCmd = async (props) => {
274
275
  const FUNCTION_SLUG_PATTERN = /^[a-z0-9]{1,20}$/;
275
276
  const SCRIPT_EXTENSION = /\.(?:ts|mts|cts|js|mjs|cjs)$/;
276
277
  const TYPESCRIPT_EXTENSION = /\.(?:ts|mts|cts)$/;
277
- /**
278
- * The nearest neon.ts, found the way the config loader finds it: walk up from `start`, stop
279
- * at the first directory holding `.git` (or at the home directory).
280
- */
281
- const findNeonConfigUpward = (start) => {
282
- let dir = resolve(start);
283
- for (;;) {
284
- const name = neonConfigFilename(dir);
285
- if (name) return join(dir, name);
286
- if (existsSync(join(dir, ".git")) || dir === homedir()) return void 0;
287
- const parent = dirname(dir);
288
- if (parent === dir) return void 0;
289
- dir = parent;
290
- }
291
- };
292
278
  const asRelativeModulePath = (path) => path.startsWith("./") || path.startsWith("../") || path.startsWith("/") ? path : `./${path}`;
293
279
  const shellQuote = (value) => /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
294
280
  /**
295
281
  * `explicitConfig` is the `--config` the user passed. The commands suggested afterwards must
296
- * carry it, or they would act on whichever `neon.ts` a walk up from cwd finds instead.
282
+ * carry it, or they would act on the project directory's `neon.ts` instead.
297
283
  */
298
284
  const planAdd = (target, configPath, explicitConfig) => {
299
285
  const language = TYPESCRIPT_EXTENSION.test(configPath) ? "ts" : "js";
@@ -346,12 +332,16 @@ const planAdd = (target, configPath, explicitConfig) => {
346
332
  */
347
333
  const addCmd = async (props) => {
348
334
  const cwd = props.cwd ?? process.cwd();
335
+ const location = {
336
+ cwd,
337
+ ...props.contextFile ? { contextFile: props.contextFile } : {}
338
+ };
349
339
  let existing;
350
340
  if (props.config) {
351
341
  existing = resolve(cwd, props.config);
352
342
  if (!existsSync(existing)) throw new Error(`--config ${props.config} does not exist.`);
353
- } else existing = findNeonConfigUpward(cwd);
354
- const configPath = existing ?? join(cwd, "neon.ts");
343
+ } else existing = projectConfigPath(location);
344
+ const configPath = existing ?? join(projectDir(location), "neon.ts");
355
345
  const shown = (path) => relative(cwd, path) || path;
356
346
  const plan = planAdd(props.target, configPath, props.config);
357
347
  const { source, changes } = editNeonConfig(existing ? readFileSync(existing, "utf8") : renderNeonConfig([]), plan.edit);
@@ -385,7 +375,7 @@ const addSharedOptions = (yargs) => yargs.strict().options({
385
375
  "project-id": { hidden: true },
386
376
  branch: { hidden: true },
387
377
  config: {
388
- describe: "Path to the neon.ts to edit (defaults to walking up from cwd; created in cwd when there is none)",
378
+ describe: "Path to the neon.ts to edit (defaults to the neon.ts next to .neon, or in cwd without one; created there when missing)",
389
379
  type: "string"
390
380
  },
391
381
  install: {
@@ -398,7 +388,8 @@ const addSharedOptions = (yargs) => yargs.strict().options({
398
388
  const addProps = (args, target) => ({
399
389
  target,
400
390
  install: args.install,
401
- ...args.config !== void 0 ? { config: args.config } : {}
391
+ ...args.config !== void 0 ? { config: args.config } : {},
392
+ ...args.contextFile !== void 0 ? { contextFile: args.contextFile } : {}
402
393
  });
403
394
  const command = "config";
404
395
  const describe = "Manage a branch with a neon.ts policy";
@@ -424,13 +415,13 @@ const builder = (argv) => argv.usage("$0 config <sub-command> [options]").option
424
415
  }
425
416
  }), (args) => status(args)).command("plan", "Show what `config apply` would change (dry run)", (yargs) => yargs.options({
426
417
  config: {
427
- describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
418
+ describe: "Path to a neon.ts policy (defaults to the neon.ts next to .neon, or in cwd without one)",
428
419
  type: "string"
429
420
  },
430
421
  ...envFlag
431
422
  }), (args) => planCmd(args)).command("apply", "Apply a neon.ts policy to the branch", (yargs) => yargs.options({
432
423
  config: {
433
- describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
424
+ describe: "Path to a neon.ts policy (defaults to the neon.ts next to .neon, or in cwd without one)",
434
425
  type: "string"
435
426
  },
436
427
  ...envFlag,
@@ -539,7 +530,10 @@ const loadEnvFileIfGiven = (env) => {
539
530
  };
540
531
  const loadConfig = async (props) => {
541
532
  loadEnvFileIfGiven(props.env);
542
- const { config } = await loadConfigFromFile$1({ ...props.config ? { path: props.config } : {} });
533
+ const { config } = await loadProjectConfig({
534
+ contextFile: props.contextFile,
535
+ ...props.config ? { path: props.config } : {}
536
+ });
543
537
  return config;
544
538
  };
545
539
  const status = async (props) => {
@@ -849,7 +843,10 @@ const applyPolicyOnCreate = async (props) => {
849
843
  loadEnvFileIfGiven(props.env);
850
844
  let config;
851
845
  try {
852
- ({config} = await loadConfigFromFile$1({ ...props.cwd ? { cwd: props.cwd } : {} }));
846
+ ({config} = await loadProjectConfig({
847
+ ...props.cwd ? { cwd: props.cwd } : {},
848
+ ...props.contextFile ? { contextFile: props.contextFile } : {}
849
+ }));
853
850
  } catch (err) {
854
851
  const message = err instanceof Error ? err.message : String(err);
855
852
  if (/Could not find a Neon config file/i.test(message)) return;
@@ -930,7 +927,10 @@ const createBranchFromPolicyOnCheckout = async (props) => {
930
927
  loadEnvFileIfGiven(props.env);
931
928
  let config;
932
929
  try {
933
- ({config} = await loadConfigFromFile$1({ ...props.cwd ? { cwd: props.cwd } : {} }));
930
+ ({config} = await loadProjectConfig({
931
+ ...props.cwd ? { cwd: props.cwd } : {},
932
+ ...props.contextFile ? { contextFile: props.contextFile } : {}
933
+ }));
934
934
  } catch (err) {
935
935
  const message = err instanceof Error ? err.message : String(err);
936
936
  if (/Could not find a Neon config file/i.test(message)) return null;
@@ -983,4 +983,4 @@ const createBranchFromPolicyOnCheckout = async (props) => {
983
983
  }
984
984
  };
985
985
  //#endregion
986
- export { ConfigInstallFailed, addCmd, applyCmd, applyFlags, applyPolicyOnCreate, builder, command, createBranchFromPolicyOnCheckout, describe, envFlag, envPullFlag, handler, hasNeonConfigFile, initCmd, neonConfigFilename, planCmd, status, config_exports as t };
986
+ export { ConfigInstallFailed, addCmd, applyCmd, applyFlags, applyPolicyOnCreate, builder, command, createBranchFromPolicyOnCheckout, describe, envFlag, envPullFlag, handler, initCmd, planCmd, status, config_exports as t };
@@ -21,7 +21,7 @@ const builder = (argv) => argv.usage("$0 deploy [options]").options({
21
21
  type: "string"
22
22
  },
23
23
  config: {
24
- describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
24
+ describe: "Path to a neon.ts policy (defaults to the neon.ts next to .neon, or in cwd without one)",
25
25
  type: "string"
26
26
  },
27
27
  ...envFlag,
@@ -6,7 +6,7 @@ import { readEnvFile, resolveEnvFilePath } from "../env_file.js";
6
6
  import { l as functionBaseUrlKey } from "../_chunks/env-N19A7qND.js";
7
7
  import { branchIdResolve } from "../utils/enrichers.js";
8
8
  import { bundleEntry } from "../utils/esbuild.js";
9
- import { a as resolveDevEnv } from "../_chunks/env-CtOH9xsl.js";
9
+ import { a as resolveDevEnv } from "../_chunks/env-BgiaRNT1.js";
10
10
  import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
11
11
  import { resolveWatchInputs } from "../dev/inputs.js";
12
12
  import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -75,9 +75,13 @@ const builder = (argv) => argv.usage("$0 dev [--source <path>] [options]").examp
75
75
  * reason; `dev` was the one that didn't.
76
76
  */
77
77
  const devEnvContext = (props, branchId, cwd) => {
78
- const envFile = resolveEnvFilePath(cwd);
79
- return {
78
+ const location = {
80
79
  cwd,
80
+ ...props.contextFile ? { contextFile: props.contextFile } : {}
81
+ };
82
+ const envFile = resolveEnvFilePath(location);
83
+ return {
84
+ ...location,
81
85
  implyAiGateway: true,
82
86
  env: {
83
87
  ...process.env,
@@ -124,7 +128,7 @@ const runSingleSource = async (props) => {
124
128
  const branchId = await resolveBranchId(props);
125
129
  const { vars: neonEnv, skipped, credential } = await resolveDevEnv(devEnvContext(props, branchId, process.cwd()));
126
130
  reportDevCredential(credential);
127
- const match = await findConfigFunctionBySource(process.cwd(), source);
131
+ const match = await findConfigFunctionBySource(process.cwd(), source, props.contextFile);
128
132
  const { overlay, port } = await sourceFunctionUrlOverlay(match, props.port);
129
133
  const unit = {
130
134
  slug: null,
@@ -148,7 +152,7 @@ const runSingleSource = async (props) => {
148
152
  */
149
153
  const runFromConfig = async (props) => {
150
154
  const branchId = await resolveBranchId(props);
151
- const resolved = await resolveFunctionsFromConfig(process.cwd());
155
+ const resolved = await resolveFunctionsFromConfig(process.cwd(), void 0, props.contextFile);
152
156
  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`.");
153
157
  const { configPath, functions } = resolved;
154
158
  if (functions.length === 0) throw new Error("neon.ts has no functions to serve. Add at least one under `functions`, or pass --source <path>.");
@@ -156,7 +160,7 @@ const runFromConfig = async (props) => {
156
160
  reportDevCredential(credential);
157
161
  const units = await planFunctionsToUnits(functions, neonEnv, DEFAULT_PORT_BASE);
158
162
  const replan = async (searchBase, keepPorts) => {
159
- const re = await resolveFunctionsFromConfig(process.cwd());
163
+ const re = await resolveFunctionsFromConfig(process.cwd(), void 0, props.contextFile);
160
164
  if (re === null) return null;
161
165
  return planFunctionsToUnits(re.functions, neonEnv, searchBase, keepPorts);
162
166
  };
@@ -10,7 +10,7 @@ import { ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagV
10
10
  import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
11
11
  import { warnAiGateway } from "../utils/ai_gateway_notice.js";
12
12
  import { announceTargetBranch } from "../utils/branch_notice.js";
13
- import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-CtOH9xsl.js";
13
+ import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-BgiaRNT1.js";
14
14
  import { existsSync } from "node:fs";
15
15
  import chalk from "chalk";
16
16
  //#region src/commands/env.ts
@@ -42,7 +42,7 @@ const builder = (argv) => argv.usage("$0 env <sub-command> [options]").options({
42
42
  }
43
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
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.",
45
+ describe: "Target .env file to write, relative to cwd. Defaults to an existing .env, otherwise .env.local, in the project directory (next to .neon, or cwd without one). Only Neon variables are updated; other lines are preserved.",
46
46
  type: "string"
47
47
  },
48
48
  service: servicesOption({
@@ -58,15 +58,16 @@ const builder = (argv) => argv.usage("$0 env <sub-command> [options]").options({
58
58
  string: true
59
59
  },
60
60
  config: {
61
- describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
61
+ describe: "Path to a neon.ts policy (defaults to the neon.ts next to .neon, or in cwd without one)",
62
62
  type: "string"
63
63
  }
64
64
  }).epilogue([
65
65
  "",
66
66
  "What gets pulled, in precedence order:",
67
67
  " 1. --service / --env, when given — their union, ignoring neon.ts.",
68
- " 2. neon.ts, when this directory has one — including derived function",
69
- " URLs (the function does not have to be deployed).",
68
+ " 2. neon.ts, when the project directory (next to .neon, or cwd",
69
+ " without one) has one — including derived function URLs (the",
70
+ " function does not have to be deployed).",
70
71
  " 3. Otherwise everything the branch has, plus the AI Gateway —",
71
72
  " which pulls the default AI Gateway credential.",
72
73
  "",
@@ -135,11 +136,15 @@ const pull = async (props, opts = {}) => {
135
136
  const branch = await resolveBranchRef(props);
136
137
  if (opts.announce) announceTargetBranch(props, branch, "Pulling env from branch");
137
138
  const branchId = branch.branchId;
138
- const targetPath = resolveEnvFilePath(cwd, props.file);
139
+ const location = {
140
+ cwd,
141
+ contextFile: props.contextFile
142
+ };
143
+ const targetPath = resolveEnvFilePath(location, props.file);
139
144
  const fileExisted = existsSync(targetPath);
140
145
  const existingEnv = fileExisted ? readEnvFile(targetPath) : {};
141
146
  const { vars, credential, skipped } = await resolveNeonEnvVars({
142
- cwd,
147
+ ...location,
143
148
  projectId: props.projectId,
144
149
  branchId,
145
150
  env: {
@@ -17,7 +17,11 @@ var init_exports = /* @__PURE__ */ __exportAll({
17
17
  const command = "init";
18
18
  const describe = "Set up coding agents and this directory for Neon. -y is Recommended. -y with Custom flags applies those answers without prompts.";
19
19
  const removedProtocol = () => `\`${getCliName()} init --data\` was removed. Run \`${getCliName()} init\` or \`${getCliName()} init -y\`.`;
20
- const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file", { hidden: true }).option("yes", {
20
+ const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file", {
21
+ hidden: true,
22
+ type: "string",
23
+ default: ".neon"
24
+ }).option("yes", {
21
25
  alias: "y",
22
26
  type: "boolean",
23
27
  default: false,
@@ -7,12 +7,13 @@ import "../config.js";
7
7
  import { applyContext, contextBranch, readContextFile, setContext, updateContextFile } from "../context.js";
8
8
  import { getCliName } from "../utils/cli_name.js";
9
9
  import { helpEpilogue } from "../utils/help_text.js";
10
+ import { hasNeonConfigFile, projectDir } from "../project.js";
10
11
  import { writer } from "../writer.js";
11
12
  import { createBranch, pickBranchInteractively } from "../utils/branch_picker.js";
12
13
  import { looksLikeBranchId } from "../utils/formats.js";
13
14
  import { listAllProjectBranches } from "../utils/enrichers.js";
14
15
  import { autoPullEnvAfterPin } from "./env.js";
15
- import { hasNeonConfigFile, initCmd } from "./config.js";
16
+ import { initCmd } from "./config.js";
16
17
  import { REGIONS } from "./projects.js";
17
18
  import prompts from "prompts";
18
19
  //#region src/commands/link.ts
@@ -848,9 +849,12 @@ const finalizeInteractiveLink = async (props, summary) => {
848
849
  */
849
850
  const shouldOfferConfigInit = (input) => !input.hasConfig && input.offer;
850
851
  const maybeOfferConfigInit = async (props, summary) => {
851
- const cwd = props.cwd ?? process.cwd();
852
+ const dir = projectDir({
853
+ ...props.cwd ? { cwd: props.cwd } : {},
854
+ contextFile: props.contextFile
855
+ });
852
856
  if (!shouldOfferConfigInit({
853
- hasConfig: hasNeonConfigFile(cwd),
857
+ hasConfig: hasNeonConfigFile(dir),
854
858
  offer: props.config !== false
855
859
  })) return;
856
860
  const { value } = await prompts({
@@ -862,14 +866,13 @@ const maybeOfferConfigInit = async (props, summary) => {
862
866
  });
863
867
  if (value !== true) return;
864
868
  await initCmd({
865
- cwd,
869
+ cwd: dir,
866
870
  install: true
867
871
  });
868
872
  if (summary.branch && summary.projectId) {
869
873
  const { config: _offerConfig, ...rest } = props;
870
874
  await autoPullEnvAfterPin({
871
875
  ...rest,
872
- cwd,
873
876
  projectId: summary.projectId,
874
877
  branch: summary.branch,
875
878
  envPull: props.envPull
@@ -1,11 +1,14 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
+ import { getAuthContext } from "../auth_context.js";
2
3
  import { writer } from "../writer.js";
3
4
  //#region src/commands/user.ts
4
5
  var user_exports = /* @__PURE__ */ __exportAll({
6
+ authenticationLabel: () => authenticationLabel,
5
7
  builder: () => builder,
6
8
  command: () => "me",
7
9
  describe: () => describe,
8
- handler: () => handler
10
+ handler: () => handler,
11
+ userDetails: () => userDetails
9
12
  });
10
13
  const command = "me";
11
14
  const describe = "Show current user";
@@ -15,12 +18,42 @@ const handler = async (args) => {
15
18
  };
16
19
  const me = async (props) => {
17
20
  const { data } = await props.apiClient.getCurrentUserInfo();
18
- writer(props).end(data, { fields: [
21
+ if (props.output === "json" || props.output === "yaml") {
22
+ writer(props).end(data, { fields: [
23
+ "login",
24
+ "email",
25
+ "name",
26
+ "projects_limit"
27
+ ] });
28
+ return;
29
+ }
30
+ writer(props).end(userDetails(data, getAuthContext()), { fields: [
19
31
  "login",
20
32
  "email",
21
33
  "name",
22
- "projects_limit"
34
+ "projects_limit",
35
+ "authentication"
23
36
  ] });
24
37
  };
38
+ /** Table-only view; the field names become the row labels. */
39
+ const userDetails = (user, auth) => ({
40
+ login: user.login,
41
+ email: user.email,
42
+ name: [user.name, user.last_name].filter(Boolean).join(" "),
43
+ projects_limit: user.projects_limit === 0 ? void 0 : user.projects_limit,
44
+ authentication: auth ? authenticationLabel(auth) : void 0
45
+ });
46
+ const authenticationLabel = (auth) => {
47
+ const profile = auth.profile ? ` (profile ${auth.profile})` : "";
48
+ switch (auth.source) {
49
+ case "stored-credentials": return `OAuth${profile}`;
50
+ case "profile-api-key": return `API key${profile}`;
51
+ case "api-key":
52
+ if (auth.apiKeyFrom === "flag") return "API key (--api-key)";
53
+ if (auth.apiKeyFrom === "env") return "API key (NEON_API_KEY)";
54
+ return "API key";
55
+ case "claimable": return "Claimable project";
56
+ }
57
+ };
25
58
  //#endregion
26
- export { builder, command, describe, handler, user_exports as t };
59
+ export { authenticationLabel, builder, command, describe, handler, user_exports as t, userDetails };
package/dist/dev/env.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as resolveDevEnv, i as dropClaimableUnsupported, n as MissingBranchContextError, o as resolveNeonEnvVars, r as ServiceNotOnBranchError, t as DevEnvMismatchError } from "../_chunks/env-CtOH9xsl.js";
1
+ import { a as resolveDevEnv, i as dropClaimableUnsupported, n as MissingBranchContextError, o as resolveNeonEnvVars, r as ServiceNotOnBranchError, t as DevEnvMismatchError } from "../_chunks/env-BgiaRNT1.js";
2
2
  export { DevEnvMismatchError, MissingBranchContextError, ServiceNotOnBranchError, dropClaimableUnsupported, resolveDevEnv, resolveNeonEnvVars };
@@ -1,6 +1,7 @@
1
+ import { loadProjectConfig } from "../project.js";
1
2
  import { existsSync } from "node:fs";
2
3
  import { dirname, isAbsolute, resolve } from "node:path";
3
- import { loadConfigFromFile, resolveConfig } from "@neon/config";
4
+ import { resolveConfig } from "@neon/config";
4
5
  //#region src/dev/functions.ts
5
6
  /**
6
7
  * Load `neon.ts` (if any) and resolve the list of functions it declares into
@@ -11,8 +12,8 @@ import { loadConfigFromFile, resolveConfig } from "@neon/config";
11
12
  * `branchName` is used only to evaluate a policy that switches on `branch.name`; the
12
13
  * function list is otherwise branch-independent, so a placeholder is fine when unknown.
13
14
  */
14
- const resolveFunctionsFromConfig = async (cwd, branchName) => {
15
- const loaded = await loadNeonConfig(cwd);
15
+ const resolveFunctionsFromConfig = async (cwd, branchName, contextFile) => {
16
+ const loaded = await loadNeonConfig(cwd, contextFile);
16
17
  if (!loaded) return null;
17
18
  const { config, configDir, configPath } = loaded;
18
19
  return {
@@ -31,8 +32,8 @@ const resolveFunctionsFromConfig = async (cwd, branchName) => {
31
32
  * The neon.ts function whose `source` is this file, if any. Sibling sources are not
32
33
  * required to exist: `neon dev --source` is serving one file, not the whole set.
33
34
  */
34
- const findConfigFunctionBySource = async (cwd, source) => {
35
- const loaded = await loadNeonConfig(cwd);
35
+ const findConfigFunctionBySource = async (cwd, source, contextFile) => {
36
+ const loaded = await loadNeonConfig(cwd, contextFile);
36
37
  if (!loaded) return void 0;
37
38
  return (resolveConfig(loaded.config, {
38
39
  name: "local",
@@ -63,9 +64,12 @@ const devPort = (dev) => dev?.port;
63
64
  * to resolve each function's relative `source`). Returns `null` when no config file is
64
65
  * found; surfaces real load errors (e.g. a syntax error).
65
66
  */
66
- const loadNeonConfig = async (cwd) => {
67
+ const loadNeonConfig = async (cwd, contextFile) => {
67
68
  try {
68
- const { config, resolvedPath } = await loadConfigFromFile({ cwd });
69
+ const { config, resolvedPath } = await loadProjectConfig({
70
+ cwd,
71
+ ...contextFile ? { contextFile } : {}
72
+ });
69
73
  return {
70
74
  config,
71
75
  configDir: dirname(resolvedPath),
package/dist/env_file.js CHANGED
@@ -1,15 +1,18 @@
1
+ import { projectDir } from "./project.js";
1
2
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
3
  import { join, resolve } from "node:path";
3
4
  //#region src/env_file.ts
4
5
  /**
5
- * Default dotenv file `env pull` writes to: `.env` when one already exists in the working
6
+ * Default dotenv file `env pull` writes to: `.env` when one already exists in the project
6
7
  * directory (update where secrets already live), otherwise `.env.local` — matching the
7
- * `vercel env pull` convention. An explicit `--file` always wins over this.
8
+ * `vercel env pull` convention. An explicit `--file` always wins over this and is
9
+ * relative to `cwd`.
8
10
  */
9
- const resolveEnvFilePath = (cwd, file) => {
10
- if (file) return resolve(cwd, file);
11
- if (existsSync(join(cwd, ".env"))) return join(cwd, ".env");
12
- return join(cwd, ".env.local");
11
+ const resolveEnvFilePath = (location, file) => {
12
+ if (file) return resolve(location.cwd, file);
13
+ const dir = projectDir(location);
14
+ if (existsSync(join(dir, ".env"))) return join(dir, ".env");
15
+ return join(dir, ".env.local");
13
16
  };
14
17
  /**
15
18
  * Merge `updates` into the dotenv content at `path`, preserving every other line
package/dist/init/run.js CHANGED
@@ -8,6 +8,7 @@ import { readLinkedClaimableCredentials } from "../claimable/state.js";
8
8
  import { getCliName } from "../utils/cli_name.js";
9
9
  import { deprecatedServiceMessage, parseServices, servicesFlagValue } from "../neon_services.js";
10
10
  import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES, CONFIG_INIT_UNAVAILABLE } from "../config_template.js";
11
+ import { hasNeonConfigFile, neonConfigFilename } from "../project.js";
11
12
  import { getAgentDisplayName } from "../mcp/agents.js";
12
13
  import { mcpInstallableAgents } from "../mcp/targets.js";
13
14
  import { missingPluginsCommands, pluginsInstallableAgents } from "../plugins/targets.js";
@@ -16,7 +17,7 @@ import { FALLBACK_SKILLS_AGENTS, assertNamedAgentTooling, funnelAgentSetup, plan
16
17
  import { inferPackageManager, resolvePackageManager } from "../utils/package_manager.js";
17
18
  import { CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, MCP_SCOPED_NEEDS_PROJECT, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, PROGRESS, YES_LINK_NEEDS_AUTH, claimableNext, claimableThenServicesNext, envPullFailedNext, existingConfigNext, extraServicesNext, installFailedNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, namedAgentsUnavailable, skippedLinkNext, unattendedUnauthedNext } from "./copy.js";
18
19
  import { agentsRowValue, configRowValue, formatInitDone, headingForKind, printInitBanner, printInitDone, printInitProgress, projectRowValue, shouldPrintInitBanner } from "./chrome.js";
19
- import { ConfigInstallFailed, hasNeonConfigFile, initCmd, neonConfigFilename } from "../commands/config.js";
20
+ import { ConfigInstallFailed, initCmd } from "../commands/config.js";
20
21
  import { runAuthenticatedLink } from "./link.js";
21
22
  import { pullInitEnv } from "./auth.js";
22
23
  import { InitCancelled, raceSigint, restoreCursor } from "./cancelled.js";
@@ -0,0 +1,49 @@
1
+ import { currentContextFile } from "./context.js";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { ConfigLoadError, loadConfigFromFile } from "@neon/config";
5
+ //#region src/project.ts
6
+ /** Config filenames the runtime loads (mirrors @neon/config's loader). */
7
+ const NEON_CONFIG_FILENAMES = [
8
+ "neon.ts",
9
+ "neon.mts",
10
+ "neon.js",
11
+ "neon.mjs"
12
+ ];
13
+ const neonConfigFilename = (dir) => NEON_CONFIG_FILENAMES.find((name) => existsSync(join(dir, name)));
14
+ /** Whether `dir` already has a Neon config file the runtime would load. */
15
+ const hasNeonConfigFile = (dir) => neonConfigFilename(dir) !== void 0;
16
+ /**
17
+ * The project a command acts on: the directory holding its `.neon` (the nearest one at or
18
+ * above cwd unless `--context-file` says otherwise), else cwd. Its `neon.ts` and default
19
+ * `.env.local` live there and nowhere else, so a stray file in a parent directory never
20
+ * applies.
21
+ */
22
+ const projectDir = ({ cwd = process.cwd(), contextFile } = {}) => dirname(contextFile ? resolve(cwd, contextFile) : currentContextFile(cwd));
23
+ /** Absolute path of the project's `neon.ts` (or other supported extension), if any. */
24
+ const projectConfigPath = (location = {}) => {
25
+ const dir = projectDir(location);
26
+ const name = neonConfigFilename(dir);
27
+ return name === void 0 ? void 0 : join(dir, name);
28
+ };
29
+ /**
30
+ * Load the project's Neon config. An explicit `path` (`--config`) wins; otherwise only
31
+ * {@link projectDir} is checked. Callers match "Could not find a Neon config file" to tell
32
+ * a missing config from a broken one.
33
+ */
34
+ const loadProjectConfig = async (options) => {
35
+ const { cwd, unsetFunctionEnv } = options;
36
+ const path = options.path ?? projectConfigPath(options);
37
+ if (path === void 0) throw new ConfigLoadError([
38
+ `Could not find a Neon config file in ${projectDir(options)}.`,
39
+ `Looked for: ${NEON_CONFIG_FILENAMES.join(", ")} next to the project's .neon (or in the current directory when there is no .neon).`,
40
+ "Create one with `neon config init`, or pass `--config <path>`."
41
+ ].join("\n"));
42
+ return await loadConfigFromFile({
43
+ path,
44
+ ...cwd !== void 0 ? { cwd } : {},
45
+ ...unsetFunctionEnv !== void 0 ? { unsetFunctionEnv } : {}
46
+ });
47
+ };
48
+ //#endregion
49
+ export { hasNeonConfigFile, loadProjectConfig, neonConfigFilename, projectConfigPath, projectDir };
@@ -1,6 +1,7 @@
1
1
  import { log } from "../log.js";
2
+ import { loadProjectConfig } from "../project.js";
2
3
  import { s as fetchEnvKeys } from "../_chunks/env-N19A7qND.js";
3
- import { loadConfigFromFile, runHook } from "@neon/config-runtime";
4
+ import { runHook } from "@neon/config-runtime";
4
5
  //#region src/utils/hooks.ts
5
6
  /**
6
7
  * Load the nearest `neon.ts` for hook discovery, or `undefined` when there is none — or when
@@ -13,9 +14,12 @@ import { loadConfigFromFile, runHook } from "@neon/config-runtime";
13
14
  * `.experimental?.hooks` off the result for the hooks block, and pass the whole thing to
14
15
  * {@link resolveHookEnv} so `after` hooks resolve exactly the namespaces the policy declares.
15
16
  */
16
- const loadHookConfig = async (cwd) => {
17
+ const loadHookConfig = async (cwd, contextFile) => {
17
18
  try {
18
- const { config } = await loadConfigFromFile({ cwd });
19
+ const { config } = await loadProjectConfig({
20
+ cwd,
21
+ ...contextFile ? { contextFile } : {}
22
+ });
19
23
  return config;
20
24
  } catch (err) {
21
25
  const message = err instanceof Error ? err.message : String(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "7.0.8",
3
+ "version": "8.0.0",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -64,9 +64,9 @@
64
64
  "yaml": "^2.9.0",
65
65
  "yargs": "17.7.2",
66
66
  "yoctocolors": "^2.1.2",
67
+ "@neon/sdk": "7.0.0",
67
68
  "@neon/config-runtime": "1.7.3",
68
- "@neon/config": "1.8.3",
69
- "@neon/sdk": "7.0.0"
69
+ "@neon/config": "1.8.3"
70
70
  },
71
71
  "optionalDependencies": {
72
72
  "@napi-rs/keyring": "1.3.0",
@@ -99,10 +99,10 @@
99
99
  "tsx": "4.22.3",
100
100
  "typescript": "^5.9.0",
101
101
  "vitest": "^3.0.9",
102
+ "@neon-internals/cli-core": "0.0.0",
102
103
  "@neon-internals/env-core": "0.0.26",
103
104
  "@neon/e2e-harness": "0.0.0",
104
- "@neon/functions": "0.11.0",
105
- "@neon-internals/cli-core": "0.0.0"
105
+ "@neon/functions": "0.11.0"
106
106
  },
107
107
  "publishConfig": {
108
108
  "access": "public",