neon 4.22.0 → 5.0.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 CHANGED
@@ -298,15 +298,15 @@ The Neon CLI supports autocompletion, which you can configure in a few easy step
298
298
 
299
299
  ## Linking a project
300
300
 
301
- `neon link` is a Vercel-style command that binds the current directory to a Neon project. It picks (or creates) an organization and a project and writes a `.neon` file (`{ "orgId", "projectId", "branch" }`) that subsequent commands run in this directory (or any sub-directory) pick up automatically.
301
+ `neon link` is a Vercel-style command that binds the current directory to a Neon project. It picks (or creates) an organization and a project and writes a `.neon` file (`{ "orgId", "projectId", "branch" }`) that subsequent commands run in this directory (or any sub-directory) pick up automatically. Personal-account projects omit `orgId`.
302
302
 
303
- `link` resolves what it can and **verifies every identifier you pass** before writing, so a `.neon` is never left half-written or pointing at something that doesn't exist:
303
+ `link` resolves what it can and **verifies every identifier you pass** before writing, so a successful `.neon` always has a project and a branch (and an organization when the project has one):
304
304
 
305
- - **org** is inferred from the project (so `--project-id` alone is enough); it's omitted only when the project has no organization (personal account).
306
- - **project** is taken from `--project-id` (or chosen interactively).
307
- - **branch** is taken from `--branch`, an existing pin for the same project, or the project's branch list: one branch is pinned automatically; several prompt in a TTY, pin the default with `-y`, or stay unpinned for [`neon checkout <branch>`](#checkout). A project with no branches is linked without a pin and says so.
305
+ - **org** is inferred from the project (so `--project-id` alone is enough); it's omitted only when the project has no organization (personal account). `--org-id` that the project does not confirm is an error.
306
+ - **project** is taken from `--project-id` (or chosen interactively). `--org-id` without a project opens the project picker in a TTY. Without a TTY it needs `-y` (select the only project, or print IDs) or `--project-id`.
307
+ - **branch** is taken from `--branch`, a still-valid pin for the same project, or the project's branch list: one branch is pinned automatically; several prompt in a TTY, or `-y` pins the default. Zero branches, a stale pin, or several branches with no TTY and no `-y` fail without writing.
308
308
 
309
- When a branch ends up pinned, `link` also runs [`env pull`](#env-pull) so the branch's Neon env vars (`DATABASE_URL`, …) land in a local `.env`. With no branch pinned there is nothing to pull, so `link` instead nudges you to run `neon checkout`. Pass `--no-env-pull` to skip the pull (for example when injecting env at runtime with `neon-env run` or `neon dev`).
309
+ When a branch is pinned, `link` also runs [`env pull`](#env-pull) so the branch's Neon env vars (`DATABASE_URL`, …) land in a local `.env`. Pass `--no-env-pull` to skip the pull (for example when injecting env at runtime with `neon-env run` or `neon dev`).
310
310
 
311
311
  > **Migrating from `set-context`?** `set-context` is **deprecated** in favor of `link` (see [below](#set-context-is-deprecated)). It still works exactly as before for now (a raw write), it just prints a deprecation warning. The `.neon` `branchId` field is also superseded by `branch` (which stores the branch **name** when known); old `branchId` files are still read and are upgraded to `branch` the next time `link`/`checkout` writes the context.
312
312
 
@@ -339,19 +339,24 @@ $ neon link
339
339
  ```
340
340
 
341
341
  `link --project-id …` skips org and project. One branch is pinned with no prompt. Several branches
342
- in a TTY show the branch prompt; `-y` pins the default; no TTY leaves the pin empty for
343
- `neon checkout`:
342
+ in a TTY show the branch prompt; `-y` pins the default; no TTY without `-y` or `--branch` exits 1:
344
343
 
345
344
  ```bash
346
345
  $ neon link --project-id polished-snowflake-12345678
347
346
  ? Which branch would you like to link? › [default] main (br-main-branch-87654321)
348
347
  ```
349
348
 
350
- **Non-interactive (flags or `--params` JSON)** — for scripts and CI:
349
+ **Non-interactive (`-y`, flags, or `--params` JSON)** — for scripts, CI, and agents:
351
350
 
352
351
  ```bash
352
+ # One organization and one project: link them. Several: print IDs and exit 1.
353
+ neon link -y
354
+
355
+ # After choosing an organization from that list, discover its project the same way
356
+ neon link -y --org-id org-abc123
357
+
353
358
  # Link to an existing project (org is inferred). Pins the only branch;
354
- # several branches prompt in a TTY, or stay unpinned without one.
359
+ # several branches prompt in a TTY, or -y pins the default.
355
360
  neon link --project-id polished-snowflake-12345678
356
361
 
357
362
  # Same, pin the project's default branch when several exist
@@ -369,21 +374,21 @@ neon link --org-id org-abc123 --project-name my-app --region-id aws-us-east-2
369
374
  # Same payload, one JSON blob
370
375
  neon link --params '{"orgId":"org-abc123","projectName":"my-app","regionId":"aws-us-east-2"}'
371
376
 
372
- # Record just the default org (preserves any existing project/branch)
373
- neon link --org-id org-abc123
374
-
375
377
  # Forget the current context
376
378
  neon link --clear
377
379
 
378
380
  # Offline write — no API calls, no verification (see --no-checks below)
379
- neon link --no-checks --org-id org-abc123 --project-id polished-snowflake-12345678
381
+ neon link --no-checks --org-id org-abc123 --project-id polished-snowflake-12345678 --branch main
380
382
  ```
381
383
 
382
384
  Every supplied identifier is checked before anything is written, with actionable errors — e.g. `Project '…' not found`, `You don't have access to project '…'`, `Organization '…' not found, or your API key doesn't have access to it`, `Project '…' belongs to organization 'A', not 'B'`, or `Branch '…' not found in project '…'. Available branches: …`.
383
385
 
384
- **Agents and scripts (no TTY):** List, then link. `neon link --help` prints the same recipe.
386
+ **Agents and scripts:** `neon link -y` selects the only organization and project. If several exist, it prints their IDs (human table, or `--output json` / `--output yaml`) and names the flag to pass. `--org-id` alone, without `-y` and without a TTY, is not a completed link — pass `--project-id` or `-y`. `neon link --help` prints the explicit-flag recipe.
385
387
 
386
388
  ```bash
389
+ neon link -y
390
+ neon link -y --org-id <org-id>
391
+ neon link -y --project-id <project-id>
387
392
  neon orgs list --output json
388
393
  neon projects list --org-id <org-id> --output json
389
394
  neon link --project-id <project-id> [--branch <name> | -y]
@@ -397,7 +402,7 @@ Organization-scoped API keys cannot list user organizations (`orgs list`) or cal
397
402
  - If no projects exist yet, interactive `link` errors pointing at `--org-id`.
398
403
  - When the regions endpoint is not allowed, interactive create falls back to a built-in static region list. Non-interactive create already requires `--region-id`.
399
404
 
400
- **Offline writes (`--no-checks`)** — write the `.neon` with no API calls at all: no org inference, no existence/access verification, no env pull. Because nothing can be resolved offline, it requires both `--org-id` and `--project-id` (`--branch` optional, stored verbatim). Handy for scripted/CI setups or re-creating a `.neon` from values you already trust:
405
+ **Offline writes (`--no-checks`)** — write the `.neon` with no API calls at all: no org inference, no existence/access verification, no env pull. Because nothing can be resolved offline, it requires `--org-id`, `--project-id`, and `--branch` (stored verbatim). Handy for scripted/CI setups or re-creating a `.neon` from values you already trust:
401
406
 
402
407
  ```bash
403
408
  neon link --no-checks --org-id org-abc123 --project-id polished-snowflake-12345678 --branch main
@@ -411,13 +416,13 @@ How today's `set-context` uses map onto `link`:
411
416
 
412
417
  | `set-context` (deprecated) | Recommended `link` equivalent |
413
418
  | --------------------------------------- | ----------------------------------------------------------------------------- |
414
- | `neon set-context --project-id <id>` | `neon link --project-id <id>` (infers org + verifies; pins the only branch) |
415
- | `neon set-context --org-id <id>` | `neon link --org-id <id>` |
416
- | `neon set-context --branch-id <id>` | `neon link --branch <name\|id>` |
419
+ | `neon set-context --project-id <id>` | `neon link --project-id <id>` (infers org + verifies; pins the only branch, or `-y` for the default) |
420
+ | `neon set-context --org-id <id>` | Not a completed link. Pass `--project-id` / `-y`, or an `--org-id` flag on the org-scoped command |
421
+ | `neon set-context --branch-id <id>` | `neon link --branch <name\|id>` or [`neon checkout <branch>`](#checkout) |
417
422
  | `neon set-context` (clear) | `neon link --clear` |
418
- | a raw local write (no network) | `neon link --no-checks --org-id <id> --project-id <id>` |
423
+ | a raw local write (no network) | `neon link --no-checks --org-id <id> --project-id <id> --branch <name>` |
419
424
 
420
- The key difference: `link` resolves and **verifies** before writing (so you never get a half-written or stale `.neon`), whereas `set-context` writes whatever you give it verbatim. The closest like-for-like replacement for the old raw write is `link --no-checks`.
425
+ The key difference: `link` resolves and **verifies** before writing a complete context, whereas `set-context` writes whatever you give it verbatim. The closest like-for-like replacement for the old raw write is `link --no-checks` with org, project, and branch.
421
426
 
422
427
  ### open
423
428
 
@@ -434,7 +439,7 @@ A branch pinned in `.neon` does not change the destination. `.neon` stores the b
434
439
 
435
440
  ### checkout
436
441
 
437
- `checkout [id|name]` pins a branch in the local context so subsequent commands target it — it's the focused companion to `link` for the common "switch the branch I'm working on" case (`link` resolves org + project; `checkout` pins the branch). It resolves the branch (by name or id) against the project, then **heals** the `.neon` file: it always (re)writes `projectId`, `branch`, and `orgId` (when the project has one), so a `.neon` that was missing fields or drifted ends up complete and consistent. The branch is stored as its **name** when known (matching `link`). When `orgId` isn't already known (from `--org-id` or the existing `.neon`), it's looked up from the project itself.
442
+ `checkout [id|name]` switches the pinned branch in a linked folder. It resolves the branch (by name or id) against the project, then **heals** the `.neon` file: it always (re)writes `projectId`, `branch`, and `orgId` (when the project has one), so a `.neon` that was missing fields or drifted ends up complete and consistent. The branch is stored as its **name** when known (matching `link`). When `orgId` isn't already known (from `--org-id` or the existing `.neon`), it's looked up from the project itself.
438
443
 
439
444
  The branch argument is **optional**: run `neon checkout` with no branch in an interactive terminal to fetch the project's branches and pick one from a list. In a non-interactive context (CI or no TTY), a branch must be passed explicitly.
440
445
 
@@ -749,7 +754,7 @@ When a package cannot be bundled — a native addon with no esbuild loader, or a
749
754
 
750
755
  `neon bootstrap` copies a Neon starter template into a new (or current) directory — conceptually like `degit`, but it only pulls from a small set of templates we maintain in the public [`neondatabase/examples`](https://github.com/neondatabase/examples) repo. The template copy needs no Neon login: it downloads files from GitHub.
751
756
 
752
- After scaffolding, an interactive terminal asks about dependency install and git, then finishes agent setup before asking whether to link a Neon project. Dependency install is last, except when the template has a `neon.ts` and you chose to link — then install runs first so the link flow can pull env. `--default` / `-y` skips the template, install, git, and agent pickers, then installs agent tooling for project folders, else the host CLI agent. If none are found, it exits: pass `--agent <name>`, run from a supported agent, or omit `--default` / `-y` in a terminal to pick. `--agent` / `-a` names coding agents, skips agent selection, and is forwarded to `plugins`, or to `skills` and `mcp`, not both. Project selection may still be required. `--no-agent-setup` and `--no-link` skip those steps. Non-interactive without `--default` prints next steps and does not install, set up agents, or link.
757
+ After scaffolding, an interactive terminal asks about dependency install and git, then finishes agent setup before asking whether to link a Neon project. Dependency install is last, except when the template has a `neon.ts` and you chose to link — then install runs first so the link flow can pull env. `--default` / `-y` skips the template, install, git, and agent pickers, then installs agent tooling for project folders, else the host CLI agent. If none are found, it exits: pass `--agent <name>`, run from a supported agent, or omit `--default` / `-y` in a terminal to pick. `--agent` / `-a` names coding agents, skips agent selection, and is forwarded to `plugins`, or to `skills` and `mcp`, not both. Linking uses `neon link -y`: the only organization and project are selected, or their IDs are printed and the command exits. `--no-agent-setup` and `--no-link` skip those steps. Non-interactive without `--default` prints next steps and does not install, set up agents, or link.
753
758
 
754
759
  Pass a target directory (or `.` for the current one). In an interactive terminal you pick the template from a list; in CI / non-interactive contexts pass `--template <id>`.
755
760
 
@@ -802,7 +807,7 @@ Without a TTY, pass `-y`. `--agent` skips agent selection but does not replace `
802
807
 
803
808
  `--config`, `--no-config`, and `--services` apply on the existing-app and `--skip-template` path. A template's own `neon.ts` is left as the template shipped it. Passing those flags while scaffolding prints a warning and still copies the template as shipped. After a new `neon.ts` on a pinned branch, init runs `env pull`.
804
809
 
805
- `-y` forwards `-y` to `plugins` or `skills`/`mcp`, uses default choices in the built-in link flow, passes `--default` to nested bootstrap, and forwards `--services none` to `config init`. `--agent` is forwarded to agent setup. `mcp -y` is the global install. Default linking skips the "already linked" confirmation; it can still require project selection.
810
+ `-y` forwards `-y` to `plugins` or `skills`/`mcp`, uses `link -y` (auto-select or print IDs and exit), passes `--default` to nested bootstrap, and forwards `--services none` to `config init`. `--agent` is forwarded to agent setup. `mcp -y` is the global install. Default linking skips the "already linked" confirmation.
806
811
 
807
812
  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.
808
813
 
@@ -1015,7 +1020,7 @@ neon inspect db stalled-queries --output json
1015
1020
 
1016
1021
  ## Logs (`logs`)
1017
1022
 
1018
- `neon logs` reads the log records the services on a branch emit — Neon Functions, object storage, and Postgres computes. **Logs require Neon Platform Beta and are currently available only for projects in `aws-us-east-2`, `aws-us-east-1`, `aws-eu-central-1`, and `aws-ap-southeast-1`.**
1023
+ `neon logs` reads the log records the services on a branch emit — Neon Functions, object storage, and Postgres computes. **Logs are currently available only for projects in `aws-us-east-2`, `aws-us-east-1`, `aws-eu-central-1`, and `aws-ap-southeast-1`.**
1019
1024
 
1020
1025
  Every sub-command resolves the project through the standard chain (`--project-id`, then the `.neon` context file, then a single-project auto-detect), and takes `--branch <id|name>`, defaulting to the project's default branch. `logs query` searches the previous hour by default; `logs field-values` searches the previous six hours. The maximum time window is seven days.
1021
1026
 
@@ -14,6 +14,8 @@ import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
14
14
  import { assertionHasExpired, claimableCredentialsExist, claimableCredentialsPath, readLinkedClaimableCredentials, shouldUseClaimableCredentials } from "../claimable/state.js";
15
15
  import { withExclusiveLock } from "../refresh_lock.js";
16
16
  import { readOutgoingCredential, retirePreviousCredential } from "../retire_credential.js";
17
+ import { getCliName } from "../utils/cli_name.js";
18
+ import { helpEpilogue, helpWidth, wrapHelpText } from "../utils/help_text.js";
17
19
  import { existsSync } from "node:fs";
18
20
  import { join } from "node:path";
19
21
  import { createHash } from "node:crypto";
@@ -45,11 +47,16 @@ const locationForAuth = (configDir, name, keyring, options = {}) => {
45
47
  };
46
48
  const command = "auth";
47
49
  const aliases = ["login"];
48
- const describe = "Authenticate";
50
+ const describe = "Sign in with a browser. See --help for API keys and profiles";
51
+ const wrapAuthHelp = (text) => wrapHelpText(text, helpWidth());
52
+ const authHelpEpilogue = () => {
53
+ const cli = getCliName();
54
+ return helpEpilogue(wrapAuthHelp("Non-interactive (CI, scripts, agents):"), wrapAuthHelp("Use an existing API key or an API-key profile."), ` ${cli} projects list --profile agent`, wrapAuthHelp(`${cli} profile create <name> --mint needs a browser during setup, then stores an API key and drops the session. It cannot run in CI. Default key is account-wide; --org-id or --project-id (not both) restricts it.`), wrapAuthHelp(`A profile holds a browser session or an API key. Select it with --profile <name> or NEON_PROFILE. List profiles with ${cli} profile list.`), wrapAuthHelp("On commands that use a credential, --api-key or --profile overrides NEON_API_KEY and NEON_PROFILE. Pass either --api-key or --profile, not both. When neither flag is passed and both NEON_API_KEY and NEON_PROFILE are set, the key wins and stderr names the ignored profile. When no flag or environment variable selects a credential, DEFAULT is used."), wrapAuthHelp(`${cli} profile create <name> replaces an existing profile of that name and tries to revoke the credential it held. An imported key with no recorded id stays live. Pick a new name to keep the old profile.`), wrapAuthHelp(`See ${cli} profile --help and ${cli} profile create --help.`));
55
+ };
49
56
  const builder = (yargs) => yargs.option("context-file", { hidden: true }).option("keyring", {
50
57
  describe: "Store the credential in the OS keyring. Per profile; later auth without the flag stays there. See `neon profile list`.",
51
58
  type: "boolean"
52
- });
59
+ }).example("$0 auth", "Sign in with a browser").example("$0 auth --profile work", "Save a browser session in profile work").example("$0 projects list --api-key \"$KEY\"", "Use an existing API key for this command").example("NEON_API_KEY=\"$KEY\" $0 projects list", "Use an existing API key from the environment").example("$0 profile create agent --api-key \"$KEY\"", "Store an existing API key").example("echo \"$KEY\" | $0 profile create agent --api-key -", "Store a key read from stdin").example("$0 profile create ci --mint", "Sign in once in a browser and store a new API key").epilogue(authHelpEpilogue());
53
60
  const handler = async (args) => {
54
61
  await authFlow(args);
55
62
  };
@@ -49,7 +49,7 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
49
49
  },
50
50
  default: {
51
51
  alias: "y",
52
- describe: "Quick start: scaffold the default template (or --template), then install, git, agent tooling (project folders, else the host CLI agent; if none, pass --agent or omit --default in a terminal), and link with defaults. Project selection may still be required",
52
+ describe: "Quick start: scaffold the default template (or --template), then install, git, agent tooling (project folders, else the host CLI agent; if none, pass --agent or omit --default in a terminal), and link with defaults. If several organizations or projects exist, link prints IDs and exits",
53
53
  type: "boolean",
54
54
  default: false
55
55
  },
@@ -80,7 +80,7 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
80
80
  describe: "Coding agent to install into (repeatable). Forwarded to plugins, or to skills and mcp. Skips agent selection. Values listed below",
81
81
  coerce: coerceAgentFlag
82
82
  }
83
- }).example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template").example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory").example("$0 bootstrap my-app --default", "Skip the pickers; project selection may still be required").example("$0 bootstrap --list-templates --output json", "Print the template catalog as JSON").example("$0 bootstrap my-app --agent cursor --agent claude-code", "Skip agent selection; install the plugin for those agents").epilogue(helpEpilogue("--agent / -a is forwarded to plugins, or to skills and mcp, not both. It skips agent selection, including with --default.", helpCsv("Plugin agents", initPluginAgents()), helpCsv("Skills and MCP agents", initSkillsMcpAgents()))).strict();
83
+ }).example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template").example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory").example("$0 bootstrap my-app --default", "Skip the pickers; if several orgs or projects exist, link prints IDs and exits").example("$0 bootstrap --list-templates --output json", "Print the template catalog as JSON").example("$0 bootstrap my-app --agent cursor --agent claude-code", "Skip agent selection; install the plugin for those agents").epilogue(helpEpilogue("--agent / -a is forwarded to plugins, or to skills and mcp, not both. It skips agent selection, including with --default.", helpCsv("Plugin agents", initPluginAgents()), helpCsv("Skills and MCP agents", initSkillsMcpAgents()))).strict();
84
84
  const handler = async (props) => {
85
85
  if (props.listTemplates) {
86
86
  const templates = await fetchTemplates();
@@ -244,7 +244,7 @@ const runPostScaffoldSteps = async (props, targetDir, interactive, template, nam
244
244
  };
245
245
  const installPrompt = (inferred) => inferred ? `Install dependencies with ${inferred}?` : "Install dependencies?";
246
246
  const runDefaultSteps = async (props, targetDir, pm, neonConfig, template, named) => {
247
- log.info("Quick start (--default): skipping the template, install, git, and agent pickers. Project selection may still be required.");
247
+ log.info("Quick start (--default): skipping the template, install, git, and agent pickers. If several organizations or projects exist, link prints IDs and exits.");
248
248
  const wantGit = props.git && !isGitRepo(targetDir);
249
249
  const agentSetup = props.agentSetup === false ? "skip" : named.length > 0 ? chooseYesAgentTooling(named).setup : "skills-mcp";
250
250
  const outcome = await executePostScaffold(props, targetDir, {
@@ -6,7 +6,7 @@ import { applyContext, contextBranch, readContextFile } from "../context.js";
6
6
  import { getCliName } from "../utils/cli_name.js";
7
7
  import { createBranch, pickBranchInteractively } from "../utils/branch_picker.js";
8
8
  import { looksLikeBranchId } from "../utils/formats.js";
9
- import { fillSingleProject } from "../utils/enrichers.js";
9
+ import { fillSingleProject, listAllProjectBranches } from "../utils/enrichers.js";
10
10
  import { autoPullEnvAfterPin } from "./env.js";
11
11
  import { applyPolicyOnCreate, createBranchFromPolicyOnCheckout, envFlag } from "./config.js";
12
12
  import { handler as handler$1 } from "./link.js";
@@ -110,7 +110,7 @@ const applyPolicyOrDescribeFailure = async (props) => {
110
110
  }
111
111
  };
112
112
  const resolveBranchId = async (props, projectId) => {
113
- const branches = (await props.apiClient.listProjectBranches({ projectId })).data.branches;
113
+ const branches = await listAllProjectBranches(props.apiClient, projectId);
114
114
  if (!props.id) {
115
115
  const picked = await pickBranchInteractively(branches, {
116
116
  message: "Which branch would you like to check out?",
@@ -10,10 +10,10 @@ import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOpt
10
10
  import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES, CONFIG_INIT_UNAVAILABLE, FUNCTION_FILENAME, FUNCTION_SLUG, FUNCTION_TEMPLATE, REQUIRED_PACKAGES, renderNeonConfig, renderNeonConfigFromView } from "../config_template.js";
11
11
  import { loadEnvFileIntoProcess } from "../env_file.js";
12
12
  import { writer } from "../writer.js";
13
+ import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
13
14
  import { assertAiGatewayProvisionable, warnAiGateway } from "../utils/ai_gateway_notice.js";
14
15
  import { announceTargetBranch } from "../utils/branch_notice.js";
15
16
  import { renderAppliedChanges, renderBranchSettingConflicts, renderCustomDomainFollowup } from "../utils/config_diff.js";
16
- import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
17
17
  import { formatInstallCommand, installArgs, resolvePackageManager, runCommand } from "../utils/package_manager.js";
18
18
  import { bundleEntry } from "../utils/esbuild.js";
19
19
  import { pickServicesInteractively } from "../utils/service_picker.js";
@@ -21,8 +21,8 @@ import { zipBundle } from "../utils/zip.js";
21
21
  import { autoPullEnvAfterPin } from "./env.js";
22
22
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
23
23
  import { dirname, join } from "node:path";
24
- import { packagesToStage, previewGaWarningForConfig, resolveConfig } from "@neon/config";
25
24
  import chalk from "chalk";
25
+ import { packagesToStage, previewGaWarningForConfig, resolveConfig } from "@neon/config";
26
26
  import { PushConflictError, apply, assertZipWithinLimits, bundleAsIs, createBranch, describeNativeFinding, enforceLimits, findUndeclaredNativePackages, inspect, isPartialBranchCreateError, loadConfigFromFile as loadConfigFromFile$1, plan, resolveEsbuildEntry, traceNativePackages, zipFunctionBundle } from "@neon/config-runtime";
27
27
  //#region src/commands/config.ts
28
28
  var config_exports = /* @__PURE__ */ __exportAll({
@@ -3,7 +3,7 @@ import { log } from "../log.js";
3
3
  import { isNeonApiError } from "../api.js";
4
4
  import { writer } from "../writer.js";
5
5
  import { looksLikeBranchId } from "../utils/formats.js";
6
- import { fillSingleProject } from "../utils/enrichers.js";
6
+ import { fillSingleProject, listAllProjectBranches } from "../utils/enrichers.js";
7
7
  import { renderDatabaseSchemaDiff, renderSchemaDiffReport } from "../utils/git_diff.js";
8
8
  import chalk from "chalk";
9
9
  //#region src/commands/diff.ts
@@ -43,7 +43,7 @@ const builder = (argv) => argv.usage("$0 diff [compare-branch] [options]").posit
43
43
  ["$0 diff main --db neondb", "Diff only the neondb database against main"]
44
44
  ]);
45
45
  const handler = async (props) => {
46
- const branches = (await props.apiClient.listProjectBranches({ projectId: props.projectId })).data.branches;
46
+ const branches = await listAllProjectBranches(props.apiClient, props.projectId);
47
47
  const after = resolveAfterBranch(branches, props.branch);
48
48
  const before = resolveBeforeBranch(branches, after, props.compareBranch);
49
49
  if (before.branchId === after.branchId) throw new Error(`Nothing to compare: both sides resolve to branch ${after.branchName} (${after.branchId}).`);
@@ -6,9 +6,9 @@ import { getCliName } from "../utils/cli_name.js";
6
6
  import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
7
7
  import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
8
8
  import { a as envKeysForSelection, b as isFunctionBaseUrlKey, c as ownedEnvServiceKeys, f as NEON_ENV_VAR_KEYS, i as ENV_PULL_SERVICES, l as parseEnvPullKeys, o as envPullFlagValue, r as ENV_PULL_KEY_HELP } from "../_chunks/env_services-DlXECBPi.js";
9
+ import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
9
10
  import { warnAiGateway } from "../utils/ai_gateway_notice.js";
10
11
  import { announceTargetBranch } from "../utils/branch_notice.js";
11
- import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
12
12
  import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-DRpFX-_d.js";
13
13
  import { existsSync } from "node:fs";
14
14
  import chalk from "chalk";
@@ -4,9 +4,9 @@ import { contextBranch, readContextFile } from "../context.js";
4
4
  import { recordCommandSuccessExtras } from "../analytics.js";
5
5
  import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
6
6
  import { getCliName } from "../utils/cli_name.js";
7
+ import { helpCsv, helpEpilogue } from "../utils/help_text.js";
7
8
  import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
8
9
  import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES, CONFIG_INIT_UNAVAILABLE } from "../config_template.js";
9
- import { helpCsv, helpEpilogue } from "../utils/help_text.js";
10
10
  import { fetchTemplates } from "../init/bootstrap.js";
11
11
  import { initChildEnv, spawnCliChild } from "../init/child.js";
12
12
  import { agentSetupLabel, configSummaryLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
@@ -36,7 +36,7 @@ const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file
36
36
  alias: "y",
37
37
  type: "boolean",
38
38
  default: false,
39
- describe: "Empty dir: scaffold the default template. --skip-template: plugin, or skills and MCP, for project folders, else the host CLI agent. Exits if none. Then link with defaults and create the bare neon.ts policy. Project selection may still be required"
39
+ describe: "Empty dir: scaffold the default template. --skip-template: plugin, or skills and MCP, for project folders, else the host CLI agent. Exits if none. Then link with defaults and create the bare neon.ts policy. If several organizations or projects exist, link prints IDs and exits"
40
40
  }).option("skip-template", {
41
41
  type: "boolean",
42
42
  default: false,
@@ -86,7 +86,7 @@ const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file
86
86
  alias: "o",
87
87
  hidden: true,
88
88
  describe: "Not supported; the commands init runs print their own output"
89
- }).example("$0 init", "Set up this directory (template picker if empty)").example("$0 init --skip-template", "Empty dir: agents, link, and neon.ts — no template files").example("$0 init -y", "Default template, or existing-app defaults").example("$0 init --agent cursor --agent claude-code", "Skip agent selection; install the plugin for those agents").epilogue(helpEpilogue("Empty directory: pick a starter template, or skip scaffolding and only set up agents, a Neon project, and neon.ts. That skip is not on `neon bootstrap`.", "Interactive agent setup: plugin (recommended), skills and MCP separately, or skip agent setup. Never both plugin and skills+MCP.", "neon.ts is optional when you skip the template or set up an existing app. Saying no skips the services picker and does not write the file. Scaffolding a template keeps that template's neon.ts.", "-y installs the plugin when Cursor, Claude Code, or Codex is in project folders, else the host CLI agent. Otherwise skills and MCP. If none are found, it exits: pass --agent <name>, run from a supported agent, or omit -y in a terminal to pick. Then link unless already linked. Project selection may still be required.", "--agent / -a is forwarded to plugins, or to skills and mcp, not both. It skips agent selection, including with -y.", helpCsv("Plugin agents", initPluginAgents()), helpCsv("Skills and MCP agents", initSkillsMcpAgents()))).check((argv) => {
89
+ }).example("$0 init", "Set up this directory (template picker if empty)").example("$0 init --skip-template", "Empty dir: agents, link, and neon.ts — no template files").example("$0 init -y", "Default template, or existing-app defaults").example("$0 init --agent cursor --agent claude-code", "Skip agent selection; install the plugin for those agents").epilogue(helpEpilogue("Empty directory: pick a starter template, or skip scaffolding and only set up agents, a Neon project, and neon.ts. That skip is not on `neon bootstrap`.", "Interactive agent setup: plugin (recommended), skills and MCP separately, or skip agent setup. Never both plugin and skills+MCP.", "neon.ts is optional when you skip the template or set up an existing app. Saying no skips the services picker and does not write the file. Scaffolding a template keeps that template's neon.ts.", "-y installs the plugin when Cursor, Claude Code, or Codex is in project folders, else the host CLI agent. Otherwise skills and MCP. If none are found, it exits: pass --agent <name>, run from a supported agent, or omit -y in a terminal to pick. Then link unless already linked. If several organizations or projects exist, link prints IDs and exits.", "--agent / -a is forwarded to plugins, or to skills and mcp, not both. It skips agent selection, including with -y.", helpCsv("Plugin agents", initPluginAgents()), helpCsv("Skills and MCP agents", initSkillsMcpAgents()))).check((argv) => {
90
90
  if (argv.data !== void 0) throw new Error(removedProtocol());
91
91
  if (argv.help !== true && (argv.output === "json" || argv.output === "yaml")) throw new Error(`\`${getCliName()} init\` does not support --output. The commands it runs print their own output.`);
92
92
  return true;
@@ -2,10 +2,15 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { isCi } from "../env.js";
3
3
  import { log } from "../log.js";
4
4
  import { isNeonApiError, messageFromBody } from "../api.js";
5
+ import { r as defaultDir } from "../_chunks/paths-DMq0Lt7a.js";
6
+ import "../config.js";
5
7
  import { applyContext, contextBranch, readContextFile, setContext, updateContextFile } from "../context.js";
6
8
  import { getCliName } from "../utils/cli_name.js";
7
9
  import { helpEpilogue } from "../utils/help_text.js";
10
+ import { writer } from "../writer.js";
8
11
  import { createBranch, pickBranchInteractively } from "../utils/branch_picker.js";
12
+ import { looksLikeBranchId } from "../utils/formats.js";
13
+ import { listAllProjectBranches } from "../utils/enrichers.js";
9
14
  import { autoPullEnvAfterPin } from "./env.js";
10
15
  import { hasNeonConfigFile, initCmd } from "./config.js";
11
16
  import { REGIONS } from "./projects.js";
@@ -25,6 +30,7 @@ const canPromptInteractively = () => !isCi() && Boolean(process.stdin.isTTY) &&
25
30
  const nonInteractiveLinkCommands = () => {
26
31
  const cli = getCliName();
27
32
  return [
33
+ `${cli} link -y`,
28
34
  `${cli} orgs list --output json`,
29
35
  `${cli} projects list --org-id <org-id> --output json`,
30
36
  `${cli} link --project-id <project-id> [--branch <name> | -y]`,
@@ -63,7 +69,7 @@ const builder = (argv) => argv.usage("$0 link [options]").options({
63
69
  },
64
70
  yes: {
65
71
  alias: "y",
66
- describe: "Skip the \"already linked\" confirmation, and pin the project's default branch when linking a project that has more than one.",
72
+ describe: "Skip prompts. Select the only organization and project, or print IDs and the flag to pass. Pin the default branch when several exist. Does not create a project unless --project-name and --region-id are set.",
67
73
  type: "boolean",
68
74
  default: false
69
75
  },
@@ -73,7 +79,7 @@ const builder = (argv) => argv.usage("$0 link [options]").options({
73
79
  default: false
74
80
  },
75
81
  checks: {
76
- describe: "Verify the org/project/branch exist (and resolve the org from the project) before writing. On by default; use --no-checks to write the context offline with no API calls — it then requires --org-id and --project-id (--branch optional) and skips env pull.",
82
+ describe: "Verify the org/project/branch exist (and resolve the org from the project) before writing. On by default; use --no-checks to write the context offline with no API calls — it then requires --org-id, --project-id, and --branch, and skips env pull.",
77
83
  type: "boolean",
78
84
  default: true
79
85
  },
@@ -88,11 +94,12 @@ const builder = (argv) => argv.usage("$0 link [options]").options({
88
94
  default: true
89
95
  }
90
96
  }).example([
91
- ["$0 link --project-id polished-snowflake-12345678", "Link an existing project (org is inferred). Pins the only branch; several prompt in a TTY"],
97
+ ["$0 link -y", "Select the only organization and project, or print the --org-id / --project-id to pass"],
98
+ ["$0 link --project-id polished-snowflake-12345678", "Link an existing project (org is inferred). Pins the only branch; several prompt in a TTY, or -y pins the default"],
92
99
  ["$0 link --project-id polished-snowflake-12345678 -y", "Same, pinning the project's default branch when several exist"],
93
100
  ["$0 link --org-id org-… --project-name my-app --region-id aws-us-east-2", "Create a new project and link it"],
94
101
  ["$0 link --branch-id br-…", "Pin a branch in the already-linked project"],
95
- ["$0 link --no-checks --org-id org-… --project-id polished-snowflake-12345678", "Write the context offline (no API calls, no verification)"],
102
+ ["$0 link --no-checks --org-id org-… --project-id polished-snowflake-12345678 --branch main", "Write the context offline (no API calls, no verification)"],
96
103
  ["$0 link --clear", "Forget the current org/project/branch context"]
97
104
  ]).epilogue(helpEpilogue("Non-interactive (CI, scripts, agents):", ...nonInteractiveLinkCommands().map((command) => ` ${command}`), orgScopedKeyHint)).strict();
98
105
  const runLink = async (props) => {
@@ -107,17 +114,26 @@ const runLink = async (props) => {
107
114
  const inputs = parseInputs(props);
108
115
  validateInputs(inputs);
109
116
  const existing = readContextFile(props.contextFile);
117
+ if (props.yes && hasIncompleteCreationInputs(inputs)) throw incompleteCreationError(props, inputs);
110
118
  if (canResolveNonInteractively(inputs, existing)) {
111
119
  await runNonInteractive(props, inputs, existing);
112
120
  return;
113
121
  }
114
- if (!canPromptInteractively()) throw new LinkInputError([
115
- "Missing inputs and no interactive terminal for prompts.",
116
- "",
117
- "Use:",
118
- nonInteractiveLinkHelp(),
119
- orgScopedKeyHint
120
- ].join("\n"));
122
+ if (props.yes) {
123
+ const resolved = await resolveYesInputs(props, inputs);
124
+ await runNonInteractive(props, resolved, existing);
125
+ return;
126
+ }
127
+ if (!canPromptInteractively()) {
128
+ if (missingProjectForOrg(inputs)) throw orgNeedsProjectError(props, inputs);
129
+ throw new LinkInputError([
130
+ "Missing inputs and no interactive terminal for prompts.",
131
+ "",
132
+ "Use:",
133
+ nonInteractiveLinkHelp(),
134
+ orgScopedKeyHint
135
+ ].join("\n"));
136
+ }
121
137
  await runInteractive(props, inputs);
122
138
  };
123
139
  const handler = runLink;
@@ -162,6 +178,39 @@ const validateInputs = (inputs) => {
162
178
  if (inputs.projectId && (inputs.projectName || inputs.regionId)) throw new Error("Conflicting inputs: --project-id selects an existing project; --project-name and --region-id describe a new one. Pass only one set.");
163
179
  if (inputs.projectName && inputs.branch) throw new Error(`Conflicting inputs: --branch pins a branch of an existing project, but --project-name creates a new one. Create the project first, then \`${getCliName()} checkout <branch>\`.`);
164
180
  };
181
+ const hasIncompleteCreationInputs = (inputs) => Boolean(inputs.projectName) !== Boolean(inputs.regionId);
182
+ const missingProjectForOrg = (inputs) => Boolean(inputs.orgId) && !inputs.projectId && !inputs.projectName;
183
+ const orgNeedsProjectError = (props, inputs) => {
184
+ const orgId = inputs.orgId ?? "<org-id>";
185
+ const branchFlag = inputs.branch ? ` --branch ${quoteFlagValue(inputs.branch)}` : "";
186
+ const session = sessionRetryFlags(props);
187
+ return new LinkInputError([
188
+ "No project selected. Pass --project-id, or use -y to select the only project:",
189
+ ` ${getCliName()} link -y --org-id ${quoteFlagValue(orgId)}${branchFlag}${session}`,
190
+ ` ${getCliName()} link --org-id ${quoteFlagValue(orgId)} --project-id <project-id>${inputs.branch ? ` --branch ${quoteFlagValue(inputs.branch)}` : " --branch <name-or-id>"}${session}`
191
+ ].join("\n"));
192
+ };
193
+ const quoteFlagValue = (value) => {
194
+ if (/^[A-Za-z0-9_./:@-]+$/.test(value)) return value;
195
+ return `'${value.replace(/'/g, `'\\''`)}'`;
196
+ };
197
+ const sessionRetryFlags = (props) => {
198
+ const flags = [];
199
+ if (props.contextFile) flags.push(`--context-file ${quoteFlagValue(props.contextFile)}`);
200
+ if (props.configDir && props.configDir !== defaultDir) flags.push(`--config-dir ${quoteFlagValue(props.configDir)}`);
201
+ if (props.output !== "table") flags.push(`--output ${props.output}`);
202
+ if (!props.envPull) flags.push("--no-env-pull");
203
+ if (props.profile) flags.push(`--profile ${quoteFlagValue(props.profile)}`);
204
+ return flags.length > 0 ? ` ${flags.join(" ")}` : "";
205
+ };
206
+ const incompleteCreationError = (props, inputs) => {
207
+ const orgFlag = inputs.orgId ? `--org-id ${quoteFlagValue(inputs.orgId)}` : "--org-id <org-id>";
208
+ const nameFlag = inputs.projectName ? `--project-name ${quoteFlagValue(inputs.projectName)}` : "--project-name <name>";
209
+ const regionFlag = inputs.regionId ? `--region-id ${quoteFlagValue(inputs.regionId)}` : "--region-id aws-us-east-2";
210
+ const example = `${getCliName()} link -y ${orgFlag} ${nameFlag} ${regionFlag}${sessionRetryFlags(props)}`;
211
+ if (inputs.projectName) return new LinkInputError(`--project-name requires --region-id. Example:\n ${example}`);
212
+ return new LinkInputError(`--region-id requires --project-name. Example:\n ${example}`);
213
+ };
165
214
  /**
166
215
  * Branch selection may still prompt after org and project resolve without the
167
216
  * org/project wizard.
@@ -170,7 +219,6 @@ const canResolveNonInteractively = (inputs, existing) => {
170
219
  if (inputs.projectId) return true;
171
220
  if (inputs.orgId && inputs.projectName && inputs.regionId) return true;
172
221
  if (inputs.branch && existing.projectId) return true;
173
- if (inputs.orgId && !inputs.projectName && !inputs.branch) return true;
174
222
  return false;
175
223
  };
176
224
  const clearContext = (contextFile) => {
@@ -180,16 +228,15 @@ const clearContext = (contextFile) => {
180
228
  /**
181
229
  * `--no-checks`: write the context offline. Makes no API calls — so no org
182
230
  * inference, no existence/access verification, and no env pull — which means
183
- * the caller must supply both `--org-id` and `--project-id` (the org can't be
184
- * inferred without the network). `--branch` stays optional. This is the CLI
185
- * surface over {@link setContext}, useful for scripted/offline setups and for
186
- * re-creating a `.neon` from values you already trust.
231
+ * the caller must supply `--org-id`, `--project-id`, and `--branch`. This is
232
+ * the CLI surface over {@link setContext}, useful for scripted/offline setups
233
+ * and for re-creating a `.neon` from values you already trust.
187
234
  */
188
235
  const runWithoutChecks = (props) => {
189
236
  const inputs = parseInputs(props);
190
237
  validateInputs(inputs);
191
238
  if (inputs.projectName) throw new Error("--no-checks can't create a project (that needs API access). Pass --org-id and --project-id for an existing project, or drop --no-checks.");
192
- if (!inputs.orgId || !inputs.projectId) throw new Error("--no-checks writes the context with no API calls, so it needs both --org-id and --project-id (--branch is optional).");
239
+ if (!inputs.orgId || !inputs.projectId || !inputs.branch) throw new Error("--no-checks requires --org-id, --project-id, and --branch because identifiers cannot be resolved offline.");
193
240
  setContext(props.contextFile, {
194
241
  orgId: inputs.orgId,
195
242
  projectId: inputs.projectId,
@@ -242,107 +289,105 @@ const verifyOrgAccess = async (props, orgId) => {
242
289
  throw err;
243
290
  }
244
291
  };
292
+ const failWithBranchCandidates = (props, projectId, branches, reason) => {
293
+ writer(props).end(branches.map((b) => ({
294
+ id: b.id,
295
+ name: b.name ?? b.id
296
+ })), {
297
+ fields: ["id", "name"],
298
+ title: "Branches"
299
+ });
300
+ const orgFlag = props.orgId ? ` --org-id ${quoteFlagValue(props.orgId)}` : "";
301
+ throw new LinkInputError(`${reason}\n ${getCliName()} link -y${orgFlag} --project-id ${quoteFlagValue(projectId)} --branch <name-or-id>${sessionRetryFlags(props)}`);
302
+ };
303
+ const listAllBranches = async (props, projectId) => listAllProjectBranches(props.apiClient, projectId);
245
304
  /**
246
305
  * Resolve a branch reference (name *or* id) to the matching branch, while
247
306
  * confirming it actually exists in the project. Unlike the shared
248
307
  * `branchIdResolve`, this also verifies references that already look like ids
249
- * (so a typo'd `br-…` doesn't silently get written), and surfaces the available
250
- * branches when nothing matches so the user can correct it (or run `checkout`).
308
+ * (so a typo'd `br-…` doesn't silently get written).
251
309
  */
252
310
  const resolveBranchRef = async (props, projectId, branchRef) => {
253
- const { data } = await props.apiClient.listProjectBranches({ projectId });
254
- const match = data.branches.find((b) => b.id === branchRef) ?? data.branches.find((b) => b.name === branchRef);
311
+ const branches = await listAllBranches(props, projectId);
312
+ const match = branches.find((b) => b.id === branchRef) ?? branches.find((b) => b.name === branchRef);
255
313
  if (match) return match;
256
- throw new LinkInputError(`Branch '${branchRef}' not found in project '${projectId}'. Available branches: ${data.branches.length > 0 ? data.branches.map((b) => `${b.id}${b.name ? ` (${b.name})` : ""}`).join(", ") : "(none)"}. Pin one with \`${getCliName()} checkout <branch>\`.`);
314
+ return failWithBranchCandidates(props, projectId, branches, `Branch '${branchRef}' not found in project '${projectId}'. Pass --branch with a name or ID from the list:`);
257
315
  };
258
316
  /**
259
- * The value to persist for a branch: prefer its human-readable **name** (nicer
260
- * to read in `.neon`, and still resolvable by every command), falling back to
261
- * the id when the branch has no name.
317
+ * Persist the name when later commands will look it up as a name. A name that
318
+ * already looks like a branch id is trusted as an id without listing, so keep
319
+ * the real id in that case.
262
320
  */
263
- const branchPersistValue = (branch) => branch.name ?? branch.id;
321
+ const branchPersistValue = (branch) => {
322
+ if (branch.name && !looksLikeBranchId(branch.name)) return branch.name;
323
+ return branch.id;
324
+ };
264
325
  /**
265
326
  * Verify the project (and the org, when supplied) and resolve the org id to
266
327
  * persist.
267
328
  *
268
329
  * The project is always fetched, which both validates it and yields its
269
330
  * `org_id`. When `--org-id` is passed too: if the project reports an org it must
270
- * match (else a clear mismatch error); if it reports none, the supplied org is
271
- * verified on its own. Without `--org-id` the project's own org is used, falling
272
- * back to the org already recorded for the *same* project in `.neon`. Projects
273
- * on a personal account have no org, so `undefined` is a valid result — the
274
- * field is simply omitted.
331
+ * match (else a clear mismatch error); if it reports none, fail rather than
332
+ * attaching an unrelated org. Without `--org-id` the project's own org is used.
333
+ * Projects on a personal account have no org, so `undefined` is a valid result —
334
+ * the field is simply omitted.
275
335
  */
276
- const resolveOrgForProject = async (props, inputs, existing, projectId) => {
336
+ const resolveOrgForProject = async (props, inputs, projectId) => {
277
337
  const projectOrg = (await fetchProjectOrThrow(props, projectId)).org_id ?? void 0;
278
338
  if (inputs.orgId) {
279
339
  if (projectOrg && projectOrg !== inputs.orgId) throw new LinkInputError(`Project '${projectId}' belongs to organization '${projectOrg}', not '${inputs.orgId}'. Omit --org-id to use the project's own org, or pass the matching ID.`);
280
- if (!projectOrg) await verifyOrgAccess(props, inputs.orgId);
340
+ if (!projectOrg) throw new LinkInputError(`Project '${projectId}' does not report an organization matching --org-id ${inputs.orgId}. Omit --org-id to link using the project's own context.`);
281
341
  return inputs.orgId;
282
342
  }
283
343
  if (projectOrg) return projectOrg;
284
- if (projectId === existing.projectId && existing.orgId) return existing.orgId;
285
344
  };
286
345
  /**
287
- * Keep an existing pin when re-linking the same project so `-y` does not reset
288
- * a checkout.
346
+ * Keep a same-project pin only after it still resolves on the project.
289
347
  */
290
348
  const resolvePinnedBranch = async (props, inputs, existing, projectId) => {
291
349
  if (inputs.branch) {
292
350
  const branch = await resolveBranchRef(props, projectId, inputs.branch);
293
- return {
294
- branch: branchPersistValue(branch),
295
- empty: false
296
- };
351
+ return { branch: branchPersistValue(branch) };
297
352
  }
298
353
  if (projectId === existing.projectId) {
299
354
  const pinned = contextBranch(existing);
300
- if (pinned) return {
301
- branch: pinned,
302
- empty: false
303
- };
355
+ if (pinned) {
356
+ const branch = await resolveBranchRef(props, projectId, pinned);
357
+ return { branch: branchPersistValue(branch) };
358
+ }
304
359
  }
305
360
  return resolveBranchFromList(props, projectId);
306
361
  };
307
362
  const resolveBranchFromList = async (props, projectId) => {
308
- const { data } = await props.apiClient.listProjectBranches({ projectId });
309
- const branches = data.branches;
310
- if (branches.length === 0) return { empty: true };
363
+ const branches = await listAllBranches(props, projectId);
364
+ if (branches.length === 0) throw new LinkInputError(`Project '${projectId}' has no branches to link. Create or restore a branch before retrying, or select another project with --project-id.`);
311
365
  if (branches.length === 1) {
312
366
  const [only] = branches;
313
- if (!only) return { empty: true };
314
- return {
315
- branch: branchPersistValue(only),
316
- empty: false
317
- };
367
+ if (!only) throw new LinkInputError(`Project '${projectId}' has no branches to link. Create or restore a branch before retrying, or select another project with --project-id.`);
368
+ return { branch: branchPersistValue(only) };
318
369
  }
319
370
  if (props.yes) {
320
371
  const def = branches.find((b) => b.default);
321
- if (!def) throw new Error(`Project '${projectId}' has no default branch. Pass --branch <name> to pin one.`);
322
- return {
323
- branch: branchPersistValue(def),
324
- empty: false
325
- };
372
+ if (!def) return failWithBranchCandidates(props, projectId, branches, `Project '${projectId}' has no default branch. Pass --branch with a name or ID from the list:`);
373
+ return { branch: branchPersistValue(def) };
326
374
  }
327
375
  if (canPromptInteractively()) {
328
376
  const picked = await pickBranchInteractively(branches, {
329
377
  message: "Which branch would you like to link?",
330
- nonInteractiveMessage: `No branch could be selected without an interactive terminal. Re-run \`${getCliName()} link\` interactively, or \`${getCliName()} checkout <branch>\` to pin one.`
378
+ nonInteractiveMessage: "No branch could be selected without an interactive terminal. Pass --branch <name-or-id>, or -y to pin the default branch."
331
379
  });
332
380
  if (picked.kind === "existing") {
333
381
  const existing = branches.find((b) => b.id === picked.branchId);
334
- return {
335
- branch: existing ? branchPersistValue(existing) : picked.branchId,
336
- empty: false
337
- };
382
+ return { branch: existing ? branchPersistValue(existing) : picked.branchId };
338
383
  }
339
- await createBranch(props.apiClient, projectId, picked.name, branches);
340
- return {
341
- branch: picked.name,
342
- empty: false
343
- };
384
+ const created = await createBranch(props.apiClient, projectId, picked.name, branches);
385
+ return { branch: branchPersistValue({
386
+ id: created,
387
+ name: picked.name
388
+ }) };
344
389
  }
345
- return { empty: false };
390
+ return failWithBranchCandidates(props, projectId, branches, `Project '${projectId}' has multiple branches. Pass --branch <name-or-id>, or -y to pin its default branch.`);
346
391
  };
347
392
  const runNonInteractive = async (props, inputs, existing) => {
348
393
  if (inputs.projectName) {
@@ -370,7 +415,7 @@ const runNonInteractive = async (props, inputs, existing) => {
370
415
  return;
371
416
  }
372
417
  if (inputs.projectId) {
373
- const orgId = await resolveOrgForProject(props, inputs, existing, inputs.projectId);
418
+ const orgId = await resolveOrgForProject(props, inputs, inputs.projectId);
374
419
  const resolved = await resolvePinnedBranch(props, inputs, existing, inputs.projectId);
375
420
  applyContext(props.contextFile, {
376
421
  orgId,
@@ -382,14 +427,13 @@ const runNonInteractive = async (props, inputs, existing) => {
382
427
  orgId,
383
428
  projectId: inputs.projectId,
384
429
  branch: resolved.branch,
385
- created: false,
386
- empty: resolved.empty
430
+ created: false
387
431
  });
388
432
  return;
389
433
  }
390
434
  if (inputs.branch && existing.projectId) {
391
435
  const projectId = existing.projectId;
392
- const orgId = await resolveOrgForProject(props, inputs, existing, projectId);
436
+ const orgId = await resolveOrgForProject(props, inputs, projectId);
393
437
  const resolved = await resolvePinnedBranch(props, inputs, existing, projectId);
394
438
  applyContext(props.contextFile, {
395
439
  orgId,
@@ -401,31 +445,11 @@ const runNonInteractive = async (props, inputs, existing) => {
401
445
  orgId,
402
446
  projectId,
403
447
  branch: resolved.branch,
404
- created: false,
405
- empty: resolved.empty
406
- });
407
- return;
408
- }
409
- if (inputs.orgId) {
410
- const orgId = inputs.orgId;
411
- await verifyOrgAccess(props, orgId);
412
- const projectId = existing.projectId;
413
- const branch = projectId ? contextBranch(existing) : void 0;
414
- applyContext(props.contextFile, {
415
- orgId,
416
- projectId,
417
- branch
418
- });
419
- printSummary(props, {
420
- contextFile: props.contextFile,
421
- orgId,
422
- projectId,
423
- branch,
424
- created: false,
425
- orgOnly: true
448
+ created: false
426
449
  });
427
450
  return;
428
451
  }
452
+ throw orgNeedsProjectError(props, inputs);
429
453
  };
430
454
  const runInteractive = async (props, inputs) => {
431
455
  if (!props.yes) {
@@ -439,6 +463,7 @@ const runInteractive = async (props, inputs) => {
439
463
  } else if (orgResolution.orgKeyLimited) throw new Error("This API key is organization-scoped, so the CLI cannot list your organizations, and no existing project was found in this org to auto-detect the ID. Re-run with `--org-id <your_org_id>` (find it in the Neon Console under Settings).");
440
464
  else orgId = await promptOrgFromList(orgResolution.orgs);
441
465
  if (inputs.projectName && inputs.regionId) {
466
+ if (inputs.branch) throw new Error(`Conflicting inputs: --branch pins a branch of an existing project, but --project-name creates a new one. Create the project first, then \`${getCliName()} checkout <branch>\`.`);
442
467
  const created = await createProject(props, {
443
468
  orgId,
444
469
  name: inputs.projectName,
@@ -461,9 +486,10 @@ const runInteractive = async (props, inputs) => {
461
486
  return;
462
487
  }
463
488
  const projects = await listAllProjects(props, orgId);
464
- const action = await promptProjectChoice(projects, inputs.projectName);
489
+ const action = await promptProjectChoice(projects, inputs.projectName, { allowCreate: !inputs.branch });
465
490
  if (action.type === "existing") {
466
- const resolved = await resolveBranchFromList(props, action.projectId);
491
+ const existing = readContextFile(props.contextFile);
492
+ const resolved = await resolvePinnedBranch(props, inputs, existing, action.projectId);
467
493
  applyContext(props.contextFile, {
468
494
  orgId,
469
495
  projectId: action.projectId,
@@ -476,11 +502,11 @@ const runInteractive = async (props, inputs) => {
476
502
  branch: resolved.branch,
477
503
  created: false,
478
504
  projectName: action.name,
479
- regionId: action.regionId,
480
- empty: resolved.empty
505
+ regionId: action.regionId
481
506
  });
482
507
  return;
483
508
  }
509
+ if (inputs.branch) throw new Error(`Conflicting inputs: --branch pins a branch of an existing project, but creating a project starts from its default branch. Link an existing project, or omit --branch and create first.`);
484
510
  const projectName = inputs.projectName ?? await promptProjectName(action.suggestedName);
485
511
  const regionId = inputs.regionId ?? await promptRegion(props);
486
512
  const created = await createProject(props, {
@@ -505,12 +531,12 @@ const runInteractive = async (props, inputs) => {
505
531
  };
506
532
  const confirmRelinkIfNeeded = async (props) => {
507
533
  const existing = readContextFile(props.contextFile);
508
- if (!existing.orgId || !existing.projectId) return true;
534
+ if (!existing.projectId) return true;
509
535
  const { proceed } = await prompts({
510
536
  onState: onPromptState,
511
537
  type: "confirm",
512
538
  name: "proceed",
513
- message: `${props.contextFile} is already linked to project ${existing.projectId} (org ${existing.orgId}). Re-link?`,
539
+ message: existing.orgId ? `${props.contextFile} is already linked to project ${existing.projectId} (org ${existing.orgId}). Re-link?` : `${props.contextFile} is already linked to project ${existing.projectId}. Re-link?`,
514
540
  initial: true
515
541
  });
516
542
  if (!proceed) {
@@ -539,21 +565,23 @@ const promptOrgFromList = async (orgs) => {
539
565
  });
540
566
  return orgId;
541
567
  };
542
- const promptProjectChoice = async (projects, suggestedName) => {
543
- const choices = [{
568
+ const promptProjectChoice = async (projects, suggestedName, opts) => {
569
+ const choices = [...opts.allowCreate ? [{
544
570
  title: "+ Create new project…",
545
571
  value: CREATE_NEW_SENTINEL
546
- }, ...projects.map((project) => ({
572
+ }] : [], ...projects.map((project) => ({
547
573
  title: `${project.name} (${project.id})`,
548
574
  value: project.id
549
575
  }))];
576
+ if (choices.length === 0) throw new LinkInputError("No projects are available to link. Omit --branch to create a project, or pass --project-id for an existing one.");
577
+ const createOffset = opts.allowCreate ? 1 : 0;
550
578
  const { selection } = await prompts({
551
579
  onState: onPromptState,
552
580
  type: "select",
553
581
  name: "selection",
554
582
  message: "Which project would you like to link?",
555
583
  choices,
556
- initial: projects.length > 0 ? 1 : 0
584
+ initial: projects.length > 0 ? createOffset : 0
557
585
  });
558
586
  if (selection === CREATE_NEW_SENTINEL) return {
559
587
  type: "create",
@@ -666,6 +694,71 @@ const listAllProjects = async (props, orgId) => {
666
694
  }
667
695
  return result;
668
696
  };
697
+ const CANDIDATE_FIELDS = ["id", "name"];
698
+ const extraYesFlags = (props, inputs) => {
699
+ const flags = [];
700
+ if (inputs.projectName) flags.push(`--project-name ${quoteFlagValue(inputs.projectName)}`);
701
+ if (inputs.regionId) flags.push(`--region-id ${quoteFlagValue(inputs.regionId)}`);
702
+ if (inputs.branch) flags.push(`--branch ${quoteFlagValue(inputs.branch)}`);
703
+ return `${flags.length > 0 ? ` ${flags.join(" ")}` : ""}${sessionRetryFlags(props)}`;
704
+ };
705
+ const yesProjectIdCommand = (props, inputs, orgId = inputs.orgId) => {
706
+ const orgFlag = orgId ? ` --org-id ${quoteFlagValue(orgId)}` : "";
707
+ const branchFlag = inputs.branch ? ` --branch ${quoteFlagValue(inputs.branch)}` : "";
708
+ return `${getCliName()} link -y${orgFlag} --project-id <project-id>${branchFlag}${sessionRetryFlags(props)}`;
709
+ };
710
+ const printNamedCandidates = (props, title, items) => {
711
+ writer(props).end(items, {
712
+ fields: CANDIDATE_FIELDS,
713
+ title
714
+ });
715
+ };
716
+ const orgScopedKeyNeedsOrgId = () => new LinkInputError("This API key is organization-scoped, so the CLI cannot list your organizations, and no existing project was found in this org to auto-detect the ID. Re-run with `--org-id <your_org_id>` (find it in the Neon Console under Settings).");
717
+ const resolveYesOrgId = async (props, inputs) => {
718
+ const orgResolution = await resolveOrg(props, inputs.orgId);
719
+ if (orgResolution.kind === "resolved") {
720
+ if (orgResolution.autoDetected) log.info(`Detected organization ${orgResolution.orgId} from your existing projects (organization-scoped API key).`);
721
+ return orgResolution.orgId;
722
+ }
723
+ if (orgResolution.orgKeyLimited) throw orgScopedKeyNeedsOrgId();
724
+ const orgs = orgResolution.orgs;
725
+ if (orgs.length === 0) throw new LinkInputError(["No organizations were returned for this account. Pass --project-id for a project you can access:", ` ${yesProjectIdCommand(props, inputs)}`].join("\n"));
726
+ if (orgs.length === 1) {
727
+ const [only] = orgs;
728
+ return only.id;
729
+ }
730
+ printNamedCandidates(props, "Organizations", orgs.map((org) => ({
731
+ id: org.id,
732
+ name: org.name
733
+ })));
734
+ throw new LinkInputError(["Multiple organizations are available. Pass --org-id with an ID from the list:", ` ${getCliName()} link -y --org-id <org-id>${extraYesFlags(props, inputs)}`].join("\n"));
735
+ };
736
+ const resolveYesInputs = async (props, inputs) => {
737
+ const orgId = await resolveYesOrgId(props, inputs);
738
+ if (inputs.projectName && inputs.regionId) return {
739
+ ...inputs,
740
+ orgId
741
+ };
742
+ const projects = await listAllProjects(props, orgId);
743
+ if (projects.length === 0) throw new LinkInputError([
744
+ `No projects are available in organization '${orgId}'.`,
745
+ "To create and link a project, pass --project-name and --region-id:",
746
+ ` ${getCliName()} link -y --org-id ${quoteFlagValue(orgId)} --project-name <name> --region-id aws-us-east-2${sessionRetryFlags(props)}`
747
+ ].join("\n"));
748
+ if (projects.length === 1) {
749
+ const [only] = projects;
750
+ return {
751
+ ...inputs,
752
+ orgId,
753
+ projectId: only.id
754
+ };
755
+ }
756
+ printNamedCandidates(props, "Projects", projects.map((project) => ({
757
+ id: project.id,
758
+ name: project.name
759
+ })));
760
+ throw new LinkInputError([`Multiple projects are available in organization '${orgId}'. Pass --project-id with an ID from the list:`, ` ${yesProjectIdCommand(props, inputs, orgId)}`].join("\n"));
761
+ };
669
762
  const fetchRegions = async (props) => {
670
763
  try {
671
764
  const { data } = await props.apiClient.getActiveRegions();
@@ -708,30 +801,26 @@ const createProject = async (props, args) => {
708
801
  const printSummary = (_props, summary) => {
709
802
  const lines = [];
710
803
  if (summary.created) lines.push(`Created project ${summary.projectId}${summary.projectName ? ` ("${summary.projectName}")` : ""}${summary.regionId ? ` in ${summary.regionId}` : ""}.`);
711
- lines.push(`${summary.orgOnly ? "Updated" : "Linked"} ${summary.contextFile}:`);
804
+ lines.push(`Linked ${summary.contextFile}:`);
712
805
  if (summary.orgId) lines.push(` orgId: ${summary.orgId}`);
713
- if (summary.projectId) lines.push(` projectId: ${summary.projectId}`);
714
- if (summary.branch) lines.push(` branch: ${summary.branch}`);
806
+ lines.push(` projectId: ${summary.projectId}`);
807
+ lines.push(` branch: ${summary.branch}`);
715
808
  if (summary.noChecks) {
716
809
  lines.push("");
717
810
  lines.push("Written offline (--no-checks): nothing was verified.");
718
- } else if (summary.projectId && !summary.branch && !summary.orgOnly) {
719
- lines.push("");
720
- if (summary.empty) lines.push(`This project has no branches, so none was pinned. Create a branch, then run \`${getCliName()} checkout <branch>\` to pin it and pull its env vars.`);
721
- else lines.push(`No branch pinned. Run \`${getCliName()} checkout <branch>\` to pin a branch and pull its env vars.`);
722
811
  }
723
812
  lines.push("");
724
813
  process.stdout.write(`${lines.join("\n")}\n`);
725
814
  };
726
815
  /**
727
- * Print the link summary, then run the bundled `env pull` so a human `link` that pinned a
728
- * branch ends with the branch's connection string already on disk. When no branch was pinned
729
- * there is nothing to pull, so env pull is skipped and the summary nudges `checkout` instead.
730
- * `--no-env-pull` opts out (env pull's own status / skip hint is logged to stderr).
816
+ * Print the link summary, then run the bundled `env pull` so a completed `link` ends with
817
+ * the pinned branch's connection string on disk. `--no-checks` skips the pull because
818
+ * nothing was verified. `--no-env-pull` opts out (env pull's own status / skip hint is
819
+ * logged to stderr).
731
820
  */
732
821
  const finalizeLink = async (props, summary) => {
733
822
  printSummary(props, summary);
734
- if (!summary.branch || !summary.projectId) return;
823
+ if (!summary.branch || !summary.projectId || summary.noChecks) return;
735
824
  const { config: _offerConfig, ...rest } = props;
736
825
  await autoPullEnvAfterPin({
737
826
  ...rest,
@@ -16,7 +16,7 @@ var logs_exports = /* @__PURE__ */ __exportAll({
16
16
  handler: () => handler
17
17
  });
18
18
  const LOGS_EPILOG = `
19
- Logs require Neon Platform Beta and are currently available only for projects in aws-us-east-2, aws-us-east-1, aws-eu-central-1, and aws-ap-southeast-1.
19
+ Logs are currently available only for projects in aws-us-east-2, aws-us-east-1, aws-eu-central-1, and aws-ap-southeast-1.
20
20
 
21
21
  For more information, visit https://neon.com/docs/reference/neon-cli`;
22
22
  const LOG_SOURCES = [
@@ -2,7 +2,7 @@ import { log } from "../log.js";
2
2
  import { isNeonApiError, messageFromBody } from "../api.js";
3
3
  import { sendError } from "../analytics.js";
4
4
  import { writer } from "../writer.js";
5
- import { branchIdFromProps } from "../utils/enrichers.js";
5
+ import { branchIdFromProps, listAllProjectBranches } from "../utils/enrichers.js";
6
6
  import { parsePointInTime } from "../utils/point_in_time.js";
7
7
  import chalk from "chalk";
8
8
  import { createPatch } from "diff";
@@ -92,13 +92,11 @@ const parseSchemaDiffParams = async (props) => {
92
92
  props.compareSource = props.baseBranch;
93
93
  props.baseBranch = props.branch;
94
94
  } else if (props.branch) {
95
- const { data } = await props.apiClient.listProjectBranches({ projectId: props.projectId });
96
- if (data.branches.find((b) => b.id === props.branch || b.name === props.branch)?.parent_id == void 0) throw new Error(`No branch specified. Your context branch (${props.branch}) has no parent, so no comparison is possible.`);
95
+ if ((await listAllProjectBranches(props.apiClient, props.projectId)).find((b) => b.id === props.branch || b.name === props.branch)?.parent_id == void 0) throw new Error(`No branch specified. Your context branch (${props.branch}) has no parent, so no comparison is possible.`);
97
96
  log.info(`No branches specified. Comparing your context branch '${props.branch}' with its parent`);
98
97
  props.compareSource = "^parent";
99
98
  } else {
100
- const { data } = await props.apiClient.listProjectBranches({ projectId: props.projectId });
101
- if (data.branches.find((b) => b.default)?.parent_id == void 0) throw new Error("No branch specified. Include a base branch or add a set-context branch to continue. Your default branch has no parent, so no comparison is possible.");
99
+ if ((await listAllProjectBranches(props.apiClient, props.projectId)).find((b) => b.default)?.parent_id == void 0) throw new Error("No branch specified. Include a base branch or add a set-context branch to continue. Your default branch has no parent, so no comparison is possible.");
102
100
  log.info(`No branches specified. Comparing default branch with its parent`);
103
101
  props.compareSource = "^parent";
104
102
  }
@@ -26,7 +26,7 @@ const builder = (argv) => argv.usage("$0 set-context [options]").options({
26
26
  }
27
27
  });
28
28
  const handler = (props) => {
29
- log.warning(`\`${getCliName()} set-context\` is deprecated and will be removed in a future release. Use \`${getCliName()} link\` instead — it verifies inputs and infers the org for you (or \`${getCliName()} link --no-checks\` for the same write-without-checks behavior).`);
29
+ log.warning(`\`${getCliName()} set-context\` is deprecated and will be removed in a future release. Use \`${getCliName()} link\` instead — it verifies inputs and writes a complete context (or \`${getCliName()} link --no-checks --org-id … --project-id … --branch …\` for an offline write).`);
30
30
  const context = {
31
31
  projectId: props.projectId,
32
32
  orgId: props.orgId,
package/dist/index.js CHANGED
@@ -8,9 +8,9 @@ import { currentContextFile, enrichFromContext } from "./context.js";
8
8
  import { analyticsMiddleware, closeAnalytics, commandSuccessProperties, initAnalyticsClientMiddleware, sendError, takeCommandSuccessExtras, trackEvent } from "./analytics.js";
9
9
  import { NETWORK_ERROR_MESSAGE, isNetworkError, matchErrorCode } from "./errors.js";
10
10
  import { AuthRefreshError, defaultClientID } from "./auth.js";
11
+ import { getCliName } from "./utils/cli_name.js";
11
12
  import { ensureAuth } from "./commands/auth.js";
12
13
  import { recoverFrom401 } from "./auth_recovery.js";
13
- import { getCliName } from "./utils/cli_name.js";
14
14
  import { showHelp } from "./help.js";
15
15
  import { rewriteUnknownAgentArg } from "./init/plan.js";
16
16
  import commands_default from "./commands/index.js";
@@ -2,12 +2,32 @@ import { isNeonApiError, messageFromBody } from "../api.js";
2
2
  import { isConfigInit, isCurrentBranchProbe } from "../context.js";
3
3
  import { looksLikeBranchId } from "./formats.js";
4
4
  //#region src/utils/enrichers.ts
5
+ const BRANCHES_LIST_LIMIT = 100;
6
+ /**
7
+ * Branch list pages cap at 100. A name stored in `.neon` can live on a later
8
+ * page, so resolution has to walk `pagination.next` before treating it as missing.
9
+ */
10
+ const listAllProjectBranches = async (apiClient, projectId) => {
11
+ const result = [];
12
+ let cursor;
13
+ while (true) {
14
+ const { data } = await apiClient.listProjectBranches({
15
+ projectId,
16
+ limit: BRANCHES_LIST_LIMIT,
17
+ cursor
18
+ });
19
+ result.push(...data.branches);
20
+ cursor = data.pagination?.next;
21
+ if (!cursor || data.branches.length === 0) break;
22
+ }
23
+ return result;
24
+ };
5
25
  const branchIdResolve = async ({ branch, apiClient, projectId }) => {
6
26
  branch = branch.toString();
7
27
  if (looksLikeBranchId(branch)) return branch;
8
- const { data } = await apiClient.listProjectBranches({ projectId });
9
- const branchData = data.branches.find((b) => b.name === branch);
10
- if (!branchData) throw new Error(`Branch ${branch} not found.\nAvailable branches: ${data.branches.map((b) => b.name).join(", ")}`);
28
+ const branches = await listAllProjectBranches(apiClient, projectId);
29
+ const branchData = branches.find((b) => b.name === branch);
30
+ if (!branchData) throw new Error(`Branch ${branch} not found.\nAvailable branches: ${branches.map((b) => b.name).join(", ")}`);
11
31
  return branchData.id;
12
32
  };
13
33
  const getBranchIdFromProps = async (props) => {
@@ -17,8 +37,7 @@ const getBranchIdFromProps = async (props) => {
17
37
  apiClient: props.apiClient,
18
38
  projectId: props.projectId
19
39
  });
20
- const { data } = await props.apiClient.listProjectBranches({ projectId: props.projectId });
21
- const defaultBranch = data.branches.find((b) => b.default);
40
+ const defaultBranch = (await listAllProjectBranches(props.apiClient, props.projectId)).find((b) => b.default);
22
41
  if (defaultBranch) return defaultBranch.id;
23
42
  throw new Error("No default branch found");
24
43
  };
@@ -28,8 +47,7 @@ const branchIdFromProps = async (props) => {
28
47
  };
29
48
  const resolveBranchRef = async (props) => {
30
49
  const branch = typeof props.branch === "string" ? props.branch : props.id;
31
- const { data } = await props.apiClient.listProjectBranches({ projectId: props.projectId });
32
- const branches = data.branches;
50
+ const branches = await listAllProjectBranches(props.apiClient, props.projectId);
33
51
  const listingFields = (listed) => ({
34
52
  isDefault: listed.default === true,
35
53
  isProtected: listed.protected === true,
@@ -111,4 +129,4 @@ const fillSingleOrg = async (props) => {
111
129
  };
112
130
  };
113
131
  //#endregion
114
- export { branchIdFromProps, branchIdResolve, fillSingleOrg, fillSingleProject, resolveBranchRef, resolveSingleDatabase };
132
+ export { branchIdFromProps, branchIdResolve, fillSingleOrg, fillSingleProject, listAllProjectBranches, resolveBranchRef, resolveSingleDatabase };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "4.22.0",
3
+ "version": "5.0.1",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -63,9 +63,9 @@
63
63
  "yaml": "^2.9.0",
64
64
  "yargs": "17.7.2",
65
65
  "yoctocolors": "^2.1.2",
66
- "@neon/config": "1.7.1",
67
- "@neon/config-runtime": "1.6.1",
68
- "@neon/sdk": "6.0.0"
66
+ "@neon/config": "1.7.2",
67
+ "@neon/sdk": "6.0.0",
68
+ "@neon/config-runtime": "1.6.2"
69
69
  },
70
70
  "optionalDependencies": {
71
71
  "@napi-rs/keyring": "1.3.0",
@@ -98,7 +98,7 @@
98
98
  "typescript": "^5.9.0",
99
99
  "vitest": "^3.0.9",
100
100
  "@neon-internals/cli-core": "0.0.0",
101
- "@neon-internals/env-core": "0.0.20",
101
+ "@neon-internals/env-core": "0.0.21",
102
102
  "@neon/e2e-harness": "0.0.0",
103
103
  "@neon/functions": "0.11.0"
104
104
  },