neonctl 2.25.0 → 2.25.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
@@ -369,17 +369,22 @@ If you'd rather not keep env vars on disk, inject them at runtime instead with `
369
369
 
370
370
  ## Config as code (`config` / `deploy`)
371
371
 
372
- Describe a branch's desired state in a `neon.ts` policy and reconcile it from the CLI — the Neon equivalent of `terraform status` / `plan` / `apply`. The policy is a function of the branch it's being evaluated for; you switch on the branch (`name`, `isDefault`, ) and return the config you want (auth, Data API, compute settings, TTL, protection, and Preview features like Functions and buckets):
372
+ Describe a branch's desired state in a `neon.ts` policy and reconcile it from the CLI — the Neon equivalent of `terraform status` / `plan` / `apply`. A policy splits into a **static** existential set top-level `auth` / `dataApi` toggles and the beta `preview` block (Functions, buckets, AI Gateway) that decide what _exists_ — and a **dynamic** `branch` closure that tunes each branch (compute settings, TTL, protection, `parent`) based on the branch it's evaluated for (`name`, `isDefault`, …):
373
373
 
374
374
  ```ts
375
375
  // neon.ts
376
376
  import { defineConfig } from '@neondatabase/config/v1';
377
377
 
378
- export default defineConfig((branch) => {
379
- if (branch.isDefault) {
380
- return { protected: true, auth: {} };
381
- }
382
- return { parent: 'main', ttl: '7d' };
378
+ export default defineConfig({
379
+ // Static: what exists on every branch (drives the typed env).
380
+ auth: true,
381
+ // Dynamic: per-branch tuning only — cannot add/remove services.
382
+ branch: (branch) => {
383
+ if (branch.isDefault) {
384
+ return { protected: true };
385
+ }
386
+ return { parent: 'main', ttl: '7d' };
387
+ },
383
388
  });
384
389
  ```
385
390
 
@@ -1,4 +1,4 @@
1
- import { existsSync } from 'node:fs';
1
+ import { existsSync, statSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { isAxiosError } from 'axios';
4
4
  import { retryOnLock } from '../api.js';
@@ -40,6 +40,8 @@ const writeDeploymentErrorSection = (out, dep) => {
40
40
  };
41
41
  const SLUG_PATTERN = /^[a-z0-9]{1,20}$/;
42
42
  const SLUG_HELP = 'Use 1-20 lowercase letters and digits (no hyphens or other characters).';
43
+ // Entry-point discovery order inside --src.
44
+ const ENTRY_CANDIDATES = ['index.ts', 'index.mjs', 'index.js'];
43
45
  // Overridable so tests can poll fast; defaults to 2s in real use.
44
46
  const POLL_INTERVAL_MS = Number(process.env.NEON_FUNCTIONS_POLL_INTERVAL_MS) || 2000;
45
47
  // Upper bound on --wait polling so the CLI never hangs (e.g. if our deployment
@@ -69,13 +71,19 @@ export const builder = (argv) => argv
69
71
  demandOption: true,
70
72
  })
71
73
  .options({
74
+ src: {
75
+ describe: 'Function source: a directory containing index.ts, index.mjs, or index.js, or a path to the entry file',
76
+ type: 'string',
77
+ },
78
+ // Removed flags, kept hidden so old invocations fail loudly instead
79
+ // of being silently ignored (the CLI has no .strictOptions()).
72
80
  path: {
73
- describe: 'Base directory for the function (resolves --entry)',
74
81
  type: 'string',
82
+ hidden: true,
75
83
  },
76
84
  entry: {
77
- describe: 'Entry file to bundle, relative to --path',
78
85
  type: 'string',
86
+ hidden: true,
79
87
  },
80
88
  runtime: {
81
89
  describe: 'Function runtime',
@@ -146,27 +154,36 @@ const isTransient = (err) => isAxiosError(err) &&
146
154
  err.response.status === 404 ||
147
155
  err.response.status >= 500);
148
156
  const deploy = async (props) => {
157
+ if (props.path !== undefined || props.entry !== undefined) {
158
+ throw new Error('--path and --entry were removed. Use --src <dir>; the entry point ' +
159
+ 'is discovered as index.ts, index.mjs, or index.js in that directory.');
160
+ }
149
161
  // At least one deploy option must be passed (--wait is excluded: it controls
150
162
  // output, not what gets deployed).
151
- const hasOption = props.path !== undefined ||
152
- props.entry !== undefined ||
163
+ const hasOption = props.src !== undefined ||
153
164
  props.env !== undefined ||
154
165
  props.runtime !== undefined;
155
166
  if (!hasOption) {
156
- throw new Error('Provide at least one option to deploy, e.g. --path, --entry, or --env. ' +
167
+ throw new Error('Provide at least one option to deploy, e.g. --src or --env. ' +
157
168
  'See: neonctl functions deploy --help.');
158
169
  }
159
170
  // Cheap, offline validation first - fail before any network round-trip.
160
171
  if (!SLUG_PATTERN.test(props.slug)) {
161
172
  throw new Error(`Invalid function slug "${props.slug}". ${SLUG_HELP}`);
162
173
  }
163
- const path = props.path ?? '.';
164
- const entry = props.entry ?? 'index.ts';
174
+ const src = props.src ?? '.';
165
175
  const runtime = props.runtime ?? 'nodejs24';
166
176
  const environment = parseEnv(props.env);
167
- const source = join(path, entry);
168
- if (!existsSync(source)) {
169
- throw new Error(`Entry file not found: ${source}. Pass --entry to point at your function's entry file (defaults to index.ts).`);
177
+ const srcStat = statSync(src, { throwIfNoEntry: false });
178
+ if (srcStat === undefined) {
179
+ throw new Error(`--src path not found: ${src}.`);
180
+ }
181
+ // A file is used as the entry point directly; a directory triggers discovery.
182
+ const source = srcStat.isFile()
183
+ ? src
184
+ : ENTRY_CANDIDATES.map((name) => join(src, name)).find((p) => existsSync(p));
185
+ if (source === undefined) {
186
+ throw new Error(`No entry file found in ${src}. Expected one of: ${ENTRY_CANDIDATES.join(', ')}.`);
170
187
  }
171
188
  // Bundle before any network round-trip so a bundling failure fails fast.
172
189
  const zip = zipBundle(await bundleEntry(source));
package/commands/link.js CHANGED
@@ -586,6 +586,13 @@ const promptOrgFromList = async (orgs) => {
586
586
  if (!orgs.length) {
587
587
  throw new Error(`You don't belong to any organizations. Create one in the Neon Console first: https://console.neon.tech/`);
588
588
  }
589
+ // A single organization leaves nothing to choose, so skip the prompt and link
590
+ // it directly — go straight on to the project step.
591
+ if (orgs.length === 1) {
592
+ const [only] = orgs;
593
+ log.info(`Linking organization ${only.name} (${only.id}).`);
594
+ return only.id;
595
+ }
589
596
  const { orgId } = await prompts({
590
597
  onState: onPromptState,
591
598
  type: 'select',
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "git+ssh://git@github.com/neondatabase/neonctl.git"
6
6
  },
7
7
  "type": "module",
8
- "version": "2.25.0",
8
+ "version": "2.25.1",
9
9
  "description": "CLI tool for NeonDB Cloud management",
10
10
  "main": "index.js",
11
11
  "author": "NeonDB",