neonctl 2.24.1 → 2.25.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
@@ -60,7 +60,29 @@ For information about obtaining an Neon API key, see [Authentication](https://ap
60
60
 
61
61
  ## Connect with psql
62
62
 
63
- Several commands accept a `--psql` flag that opens a psql session against the resolved endpoint:
63
+ ### The `psql` command
64
+
65
+ `neonctl psql [branch]` opens a psql session against a branch. It builds the connection string for the branch and launches psql — a shortcut for `neonctl connection-string --psql`. See [Neon CLI commands — psql](https://neon.com/docs/reference/cli-psql) for the full reference.
66
+
67
+ ```bash
68
+ neonctl psql # default branch
69
+ neonctl psql main # a specific branch
70
+ neonctl psql main@2024-01-01T00:00:00Z # point-in-time (branch@timestamp or branch@lsn)
71
+ neonctl psql --pooled # use the pooled connection
72
+ ```
73
+
74
+ Arguments after `--` are forwarded to psql:
75
+
76
+ ```bash
77
+ neonctl psql main -- -c "SELECT version()"
78
+ neonctl psql main -- -f script.sql --csv
79
+ ```
80
+
81
+ Options: `--project-id`, `--role-name`, `--database-name`, `--pooled`, `--endpoint-type` (`read_only` | `read_write`), `--ssl`, plus the [global options](#global-options).
82
+
83
+ ### The `--psql` flag
84
+
85
+ Several other commands accept a `--psql` flag that opens a psql session against the resolved endpoint:
64
86
 
65
87
  ```bash
66
88
  neonctl connection-string --psql --project-id <id>
@@ -138,9 +160,17 @@ The Neon CLI supports autocompletion, which you can configure in a few easy step
138
160
 
139
161
  ## Linking a project
140
162
 
141
- `neonctl link` is a Vercel-style command that binds the current directory to a Neon project. It picks (or creates) an organization, picks (or creates) a project, resolves the project's default branch, and writes a `.neon` file with `{ "orgId", "projectId", "branchId" }`. Subsequent commands run in this directory (or any sub-directory) automatically pick up that context.
163
+ `neonctl 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.
164
+
165
+ `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:
166
+
167
+ - **org** is inferred from the project (so `--project-id` alone is enough); it's omitted only when the project has no organization (personal account).
168
+ - **project** is taken from `--project-id` (or chosen interactively / via `--agent`).
169
+ - **branch** is left to an explicit [`neonctl checkout <branch>`](#checkout) — `link` never silently pins a project's default branch (that would make later commands quietly target, say, production). It only records a branch when you pass `--branch`, when one is already pinned for the same project (preserved), when you pick one in the interactive picker, or for a freshly **created** project (whose single branch is unambiguous).
170
+
171
+ 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 `neonctl checkout`. Pass `--no-env-pull` to skip the pull (for example when injecting env at runtime with `neon-env run` or `neonctl dev`).
142
172
 
143
- Once the branch is pinned, `link` also runs [`env pull`](#env-pull) for you so the branch's Neon env vars (`DATABASE_URL`, ) land in a local `.env` and the project is immediately ready for local dev. Pass `--no-env-pull` to skip it (for example when injecting env at runtime with `neon-env run` or `neonctl dev`).
173
+ > **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.
144
174
 
145
175
  There are three modes:
146
176
 
@@ -154,34 +184,53 @@ $ neonctl link
154
184
  ? Which region should the new project run in? › AWS US East (Ohio) (aws-us-east-2)
155
185
  Created project polished-snowflake-12345678 ("my-app") in aws-us-east-2.
156
186
  Linked .neon:
157
- orgId: org-abc123
187
+ orgId: org-abc123
158
188
  projectId: polished-snowflake-12345678
159
- branchId: br-main-branch-87654321
189
+ branch: main
160
190
  ```
161
191
 
162
- When you link an **existing** project that has more than one branch, `link` adds a final
163
- step to pick which branch to pin — the same `+ Create a new branch…` + list selector used by
164
- `neonctl checkout` (a single-branch project is pinned automatically, no prompt):
192
+ When you link an **existing** project that has more than one branch, the interactive flow adds a
193
+ final step to pick which branch to pin — the same `+ Create a new branch…` + list selector used by
194
+ `neonctl checkout` (a single-branch project is pinned automatically, no prompt). Non-interactive
195
+ `link --project-id …` does **not** prompt or default a branch; it links org + project and leaves
196
+ branch selection to `neonctl checkout`:
165
197
 
166
198
  ```bash
167
199
  ? Which organization would you like to link? › Personal Org (org-abc123)
168
200
  ? Which project would you like to link? › my-app (polished-snowflake-12345678)
169
- ? Which branch would you like to link? › main (br-main-branch-87654321)
201
+ ? Which branch would you like to link? › [default] main (br-main-branch-87654321)
170
202
  ```
171
203
 
172
204
  **Non-interactive (flags or `--params` JSON)** — for scripts and CI:
173
205
 
174
206
  ```bash
175
- # Link to an existing project
176
- neonctl link --org-id org-abc123 --project-id polished-snowflake-12345678
207
+ # Link to an existing project (org is inferred from the project; no branch pinned)
208
+ neonctl link --project-id polished-snowflake-12345678
177
209
 
178
- # Create a new project and link
210
+ # Same, but also pin a branch (name or id — resolved and stored as its name)
211
+ neonctl link --project-id polished-snowflake-12345678 --branch main
212
+
213
+ # Pin/switch the branch in the already-linked project
214
+ neonctl link --branch main # alias: --branch-id
215
+
216
+ # Create a new project and link it (pins the new project's default branch)
179
217
  neonctl link --org-id org-abc123 --project-name my-app --region-id aws-us-east-2
180
218
 
181
219
  # Same payload, one JSON blob
182
220
  neonctl link --params '{"orgId":"org-abc123","projectName":"my-app","regionId":"aws-us-east-2"}'
221
+
222
+ # Record just the default org (preserves any existing project/branch)
223
+ neonctl link --org-id org-abc123
224
+
225
+ # Forget the current context
226
+ neonctl link --clear
227
+
228
+ # Offline write — no API calls, no verification (see --no-checks below)
229
+ neonctl link --no-checks --org-id org-abc123 --project-id polished-snowflake-12345678
183
230
  ```
184
231
 
232
+ 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: …`.
233
+
185
234
  **Agent mode (`--agent`)** — a JSON state machine designed for AI coding assistants. Each invocation returns a single JSON object with a `status` discriminator describing the next step, the available options, and the exact follow-up command to run.
186
235
 
187
236
  ```bash
@@ -216,15 +265,14 @@ $ neonctl link --agent --org-id org-abc123 --project-id polished-snowflake-12345
216
265
  "context_file": "/path/to/cwd/.neon",
217
266
  "context": {
218
267
  "orgId": "org-abc123",
219
- "projectId": "polished-snowflake-12345678",
220
- "branchId": "br-main-branch-87654321"
268
+ "projectId": "polished-snowflake-12345678"
221
269
  },
222
270
  "project": { "id": "polished-snowflake-12345678" },
223
- "message": "Linked /path/to/cwd/.neon to project polished-snowflake-12345678 (org org-abc123) on branch br-main-branch-87654321."
271
+ "message": "Linked /path/to/cwd/.neon to project polished-snowflake-12345678 (org org-abc123). No branch pinned — run `neonctl checkout <branch>` (omit the branch to list options) to pin one and pull its env vars."
224
272
  }
225
273
  ```
226
274
 
227
- The agent flow also handles project creation. If the agent sends `--project-name` without `--region-id`, the next response is `needs_project_details` with the list of supported regions.
275
+ The `linked` response omits `branch` unless one was pinned (via `--branch`, an existing pin, or project creation); pass `--branch <name|id>` to include it. The agent flow also handles project creation: if the agent sends `--project-name` without `--region-id`, the next response is `needs_project_details` with the list of supported regions.
228
276
 
229
277
  **Organization-scoped API keys** (those created at the organization level rather than the user level) cannot list user organizations or call the regions endpoint. `link` handles this transparently:
230
278
 
@@ -242,11 +290,31 @@ The agent flow also handles project creation. If the agent sends `--project-name
242
290
  }
243
291
  ```
244
292
 
245
- `link` is a thin wrapper around `set-context`: both write to the same `.neon` file via a shared `applyContext` helper, so anything `link` can write, `set-context` can write too (including the newly-supported `--branch-id` flag).
293
+ **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:
294
+
295
+ ```bash
296
+ neonctl link --no-checks --org-id org-abc123 --project-id polished-snowflake-12345678 --branch main
297
+ ```
298
+
299
+ #### `set-context` is deprecated
300
+
301
+ `set-context` is **deprecated** in favor of `link` and prints a deprecation warning (to stderr, so it never pollutes stdout or scripts). For backward compatibility its behavior is **unchanged**: it's still a raw, offline write of exactly the fields you pass (no org inference, no verification, no env pull), and bare `set-context` still clears the file. Nothing breaks today — but new work should use `link`, and `set-context` will be removed in a future major release.
302
+
303
+ How today's `set-context` uses map onto `link`:
304
+
305
+ | `set-context` (deprecated) | Recommended `link` equivalent |
306
+ | --------------------------------------- | ----------------------------------------------------------------------------- |
307
+ | `neonctl set-context --project-id <id>` | `neonctl link --project-id <id>` (infers org + verifies; branch via checkout) |
308
+ | `neonctl set-context --org-id <id>` | `neonctl link --org-id <id>` |
309
+ | `neonctl set-context --branch-id <id>` | `neonctl link --branch <name\|id>` |
310
+ | `neonctl set-context` (clear) | `neonctl link --clear` |
311
+ | a raw local write (no network) | `neonctl link --no-checks --org-id <id> --project-id <id>` |
312
+
313
+ 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`.
246
314
 
247
315
  ### checkout
248
316
 
249
- `checkout [id|name]` pins a branch in the local context so subsequent commands target it — it's a focused helper over `set-context` for the common "switch the branch I'm working on" case. It resolves the branch (by name or id) against the project, then **heals** the `.neon` file: it always (re)writes `projectId`, `branchId`, and `orgId` (when the project has one), so a `.neon` that was missing fields or drifted ends up complete and consistent. When `orgId` isn't already known (from `--org-id` or the existing `.neon`), it's looked up from the project itself.
317
+ `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.
250
318
 
251
319
  The branch argument is **optional**: run `neonctl 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.
252
320
 
@@ -263,7 +331,7 @@ The project is resolved through the standard neonctl chain, each entry winning o
263
331
 
264
332
  If none of those resolve a project, `checkout` prints a telling error explaining the chain above. In an interactive terminal it then offers to run `neonctl link` in the current folder so you can pick (or create) a project on the spot; once linked, it continues and pins the requested branch. In non-interactive contexts (CI or no TTY) it exits with a non-zero code and the same guidance instead of prompting.
265
333
 
266
- The resolved branch id is then written to the same `.neon` file that `link` and `set-context` use:
334
+ The resolved branch is then written (by name) to the same `.neon` file `link` uses:
267
335
 
268
336
  ```bash
269
337
  $ neonctl checkout main --project-id polished-snowflake-12345678
@@ -273,7 +341,7 @@ $ cat .neon
273
341
  {
274
342
  "orgId": "org-abc123",
275
343
  "projectId": "polished-snowflake-12345678",
276
- "branchId": "br-main-branch-87654321"
344
+ "branch": "main"
277
345
  }
278
346
  ```
279
347
 
@@ -295,7 +363,7 @@ neonctl env pull --branch preview --file .env.preview
295
363
 
296
364
  If you'd rather not keep env vars on disk, inject them at runtime instead with `neon-env run -- <your dev command>` (from `@neondatabase/env`) or `neonctl dev`, and pass `--no-env-pull` to `link` / `checkout`.
297
365
 
298
- **Where `.neon` lives**: `link` (and `set-context`) write `.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>`.
366
+ **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>`.
299
367
 
300
368
  **`.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.
301
369
 
@@ -356,29 +424,46 @@ neon deploy --branch my-feature --update-existing
356
424
 
357
425
  Function deploys declared under `preview.functions` are bundled by neonctl's own esbuild helper and uploaded as part of `apply`, so the policy stays declarative and the packaged CLI never has to embed esbuild's native binary.
358
426
 
427
+ ## Scaffold a project (`bootstrap`)
428
+
429
+ `neonctl 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. It requires no Neon login: it just downloads files from GitHub.
430
+
431
+ 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>`.
432
+
433
+ ```bash
434
+ # Pick a template interactively and scaffold it into ./my-app
435
+ $ neonctl bootstrap my-app
436
+
437
+ # Scaffold a specific template into the current directory (no prompts)
438
+ $ neonctl bootstrap . --template hono
439
+ ```
440
+
441
+ The target directory must be empty unless you pass `--force` (a lone `.git` is ignored, so a freshly `git init`ed folder is fine). Symlinks and executable bits in the template are preserved.
442
+
359
443
  ## Commands
360
444
 
361
- | Command | Subcommands | Description |
362
- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------- |
363
- | [auth](https://neon.com/docs/reference/cli-auth) | | Authenticate |
364
- | [projects](https://neon.com/docs/reference/cli-projects) | `list`, `create`, `update`, `delete`, `get` | Manage projects |
365
- | [ip-allow](https://neon.com/docs/reference/cli-ip-allow) | `list`, `add`, `remove`, `reset` | Manage IP Allow |
366
- | [me](https://neon.com/docs/reference/cli-me) | | Show current user |
367
- | [branches](https://neon.com/docs/reference/cli-branches) | `list`, `create`, `rename`, `add-compute`, `set-default`, `set-expiration`, `delete`, `get` | Manage branches |
368
- | [databases](https://neon.com/docs/reference/cli-databases) | `list`, `create`, `delete` | Manage databases |
369
- | functions | `deploy`, `list`, `get`, `delete` | Manage Neon Functions |
370
- | [roles](https://neon.com/docs/reference/cli-roles) | `list`, `create`, `delete` | Manage roles |
371
- | [operations](https://neon.com/docs/reference/cli-operations) | `list` | Manage operations |
372
- | [connection-string](https://neon.com/docs/reference/cli-connection-string) | | Get connection string |
373
- | psql | | Connect to a database via psql |
374
- | [set-context](https://neon.com/docs/reference/cli-set-context) | | Set context for session |
375
- | env | `pull` | Manage a branch's env vars |
376
- | checkout | | Pin a branch in `.neon` |
377
- | [link](https://neon.com/docs/reference/cli-link) | | Link a directory to a project |
378
- | config | `status`, `plan`, `apply` | Drive a branch from `neon.ts` |
379
- | deploy | | Alias for `config apply` |
380
- | bucket | `create`, `list`, `delete`, `object list`, `object get`, `object delete` (incl. `--recursive`) | Manage buckets and their objects |
381
- | [completion](https://neon.com/docs/reference/cli-completion) | | Generate a completion script |
445
+ | Command | Subcommands | Description |
446
+ | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------- |
447
+ | [auth](https://neon.com/docs/reference/cli-auth) | | Authenticate |
448
+ | [projects](https://neon.com/docs/reference/cli-projects) | `list`, `create`, `update`, `delete`, `get` | Manage projects |
449
+ | [ip-allow](https://neon.com/docs/reference/cli-ip-allow) | `list`, `add`, `remove`, `reset` | Manage IP Allow |
450
+ | [me](https://neon.com/docs/reference/cli-me) | | Show current user |
451
+ | [branches](https://neon.com/docs/reference/cli-branches) | `list`, `create`, `rename`, `add-compute`, `set-default`, `set-expiration`, `delete`, `get` | Manage branches |
452
+ | [databases](https://neon.com/docs/reference/cli-databases) | `list`, `create`, `delete` | Manage databases |
453
+ | functions | `deploy`, `list`, `get`, `delete` | Manage Neon Functions |
454
+ | [roles](https://neon.com/docs/reference/cli-roles) | `list`, `create`, `delete` | Manage roles |
455
+ | [operations](https://neon.com/docs/reference/cli-operations) | `list` | Manage operations |
456
+ | [connection-string](https://neon.com/docs/reference/cli-connection-string) | | Get connection string |
457
+ | [psql](https://neon.com/docs/reference/cli-psql) | | Connect to a database via psql |
458
+ | set-context | | Deprecated; use `link` |
459
+ | env | `pull` | Manage a branch's env vars |
460
+ | checkout | | Pin a branch in `.neon` |
461
+ | [link](https://neon.com/docs/reference/cli-link) | | Link a directory to a project |
462
+ | config | `status`, `plan`, `apply` | Drive a branch from `neon.ts` |
463
+ | deploy | | Alias for `config apply` |
464
+ | bootstrap | | Scaffold a project from a template |
465
+ | bucket | `create`, `list`, `delete`, `object list`, `object get`, `object put`, `object delete` (incl. `--recursive`) | Manage buckets and their objects |
466
+ | [completion](https://neon.com/docs/reference/cli-completion) | | Generate a completion script |
382
467
 
383
468
  ## Global options
384
469
 
package/commands/auth.js CHANGED
@@ -105,6 +105,11 @@ export const ensureAuth = async (props) => {
105
105
  // interactive login: use an API key or existing stored credentials if
106
106
  // present, otherwise run with no API client (env injection is skipped).
107
107
  const isLocalDev = props._[0] === 'dev';
108
+ // `bootstrap` only copies a public template repo; it never calls the Neon
109
+ // API, so it must work without credentials and must never pop a browser
110
+ // login. It uses an API key / stored credentials when present (harmless),
111
+ // otherwise it proceeds with no API client.
112
+ const isBootstrap = props._[0] === 'bootstrap';
108
113
  // Use existing API key or handle auth command
109
114
  if (props.apiKey || props._[0] === 'auth') {
110
115
  if (props.apiKey) {
@@ -153,6 +158,10 @@ export const ensureAuth = async (props) => {
153
158
  log.debug('dev: no usable credentials; running without env injection');
154
159
  return;
155
160
  }
161
+ if (isBootstrap) {
162
+ log.debug('bootstrap: no usable credentials; continuing without auth');
163
+ return;
164
+ }
156
165
  // Start new auth flow if no valid token exists or refresh failed
157
166
  const apiKey = await authFlow(props);
158
167
  props.apiKey = apiKey;