neonctl 2.24.2 → 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 +126 -41
- package/commands/auth.js +9 -0
- package/commands/bootstrap.js +603 -0
- package/commands/branches.js +6 -4
- package/commands/bucket.js +118 -5
- package/commands/checkout.js +25 -8
- package/commands/config.js +98 -10
- package/commands/deploy.js +2 -1
- package/commands/dev.js +11 -57
- package/commands/env.js +9 -2
- package/commands/functions.js +53 -5
- package/commands/index.js +2 -0
- package/commands/link.js +441 -108
- package/commands/projects.js +2 -2
- package/commands/set_context.js +5 -1
- package/config_format.js +8 -2
- package/context.js +33 -5
- package/dev/env.js +38 -0
- package/dev/functions.js +2 -4
- package/dev/runtime.js +2 -2
- package/index.js +1 -0
- package/package.json +5 -5
- package/storage_api.js +34 -0
- package/utils/bootstrap.js +243 -0
- package/utils/esbuild.js +11 -2
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
|
-
|
|
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
|
|
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
|
-
|
|
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,14 +184,16 @@ $ 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:
|
|
187
|
+
orgId: org-abc123
|
|
158
188
|
projectId: polished-snowflake-12345678
|
|
159
|
-
|
|
189
|
+
branch: main
|
|
160
190
|
```
|
|
161
191
|
|
|
162
|
-
When you link an **existing** project that has more than one branch,
|
|
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)
|
|
@@ -172,16 +204,33 @@ step to pick which branch to pin — the same `+ Create a new branch…` + lis
|
|
|
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 --
|
|
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
|
-
#
|
|
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)
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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`
|
|
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
|
|
362
|
-
| -------------------------------------------------------------------------- |
|
|
363
|
-
| [auth](https://neon.com/docs/reference/cli-auth) |
|
|
364
|
-
| [projects](https://neon.com/docs/reference/cli-projects) | `list`, `create`, `update`, `delete`, `get`
|
|
365
|
-
| [ip-allow](https://neon.com/docs/reference/cli-ip-allow) | `list`, `add`, `remove`, `reset`
|
|
366
|
-
| [me](https://neon.com/docs/reference/cli-me) |
|
|
367
|
-
| [branches](https://neon.com/docs/reference/cli-branches) | `list`, `create`, `rename`, `add-compute`, `set-default`, `set-expiration`, `delete`, `get`
|
|
368
|
-
| [databases](https://neon.com/docs/reference/cli-databases) | `list`, `create`, `delete`
|
|
369
|
-
| functions | `deploy`, `list`, `get`, `delete`
|
|
370
|
-
| [roles](https://neon.com/docs/reference/cli-roles) | `list`, `create`, `delete`
|
|
371
|
-
| [operations](https://neon.com/docs/reference/cli-operations) | `list`
|
|
372
|
-
| [connection-string](https://neon.com/docs/reference/cli-connection-string) |
|
|
373
|
-
| psql
|
|
374
|
-
|
|
|
375
|
-
| env | `pull`
|
|
376
|
-
| checkout |
|
|
377
|
-
| [link](https://neon.com/docs/reference/cli-link) |
|
|
378
|
-
| config | `status`, `plan`, `apply`
|
|
379
|
-
| deploy |
|
|
380
|
-
|
|
|
381
|
-
|
|
|
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;
|