neonctl 2.25.0 → 2.26.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 +11 -6
- package/commands/functions.js +28 -11
- package/commands/init.js +29 -35
- package/commands/link.js +7 -0
- package/package.json +5 -5
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`.
|
|
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(
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
|
package/commands/functions.js
CHANGED
|
@@ -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.
|
|
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. --
|
|
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
|
|
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
|
|
168
|
-
if (
|
|
169
|
-
throw new Error(
|
|
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/init.js
CHANGED
|
@@ -1,27 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { interactiveInit, orchestrate } from 'neon-init';
|
|
2
2
|
import { sendError } from '../analytics.js';
|
|
3
3
|
import { log } from '../log.js';
|
|
4
|
-
const AGENT_FLAG_VALUES = ['cursor', 'copilot', 'claude'];
|
|
5
|
-
function parseAgentToEditor(value) {
|
|
6
|
-
const normalized = value.trim().toLowerCase();
|
|
7
|
-
switch (normalized) {
|
|
8
|
-
case 'cursor':
|
|
9
|
-
return 'Cursor';
|
|
10
|
-
case 'github-copilot':
|
|
11
|
-
case 'copilot':
|
|
12
|
-
case 'vs code':
|
|
13
|
-
case 'vscode':
|
|
14
|
-
case 'vs-code':
|
|
15
|
-
return 'VS Code';
|
|
16
|
-
case 'claude-code':
|
|
17
|
-
case 'claude cli':
|
|
18
|
-
case 'claude-cli':
|
|
19
|
-
case 'claude':
|
|
20
|
-
return 'Claude CLI';
|
|
21
|
-
default:
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
4
|
export const command = 'init';
|
|
26
5
|
export const describe = 'Initialize a project with Neon using your AI coding assistant';
|
|
27
6
|
export const builder = (yargs) => yargs
|
|
@@ -31,23 +10,38 @@ export const builder = (yargs) => yargs
|
|
|
31
10
|
.option('agent', {
|
|
32
11
|
alias: 'a',
|
|
33
12
|
type: 'string',
|
|
34
|
-
describe: 'Agent to configure (cursor, copilot,
|
|
13
|
+
describe: 'Agent to configure (cursor, copilot, claude, etc.).',
|
|
14
|
+
})
|
|
15
|
+
.option('skip-neon-auth', {
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
default: false,
|
|
18
|
+
describe: 'Skip the Neon Auth setup phase.',
|
|
19
|
+
})
|
|
20
|
+
.option('skip-migrations', {
|
|
21
|
+
type: 'boolean',
|
|
22
|
+
default: false,
|
|
23
|
+
describe: 'Skip the migrations phase.',
|
|
24
|
+
})
|
|
25
|
+
.option('preview', {
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
default: false,
|
|
28
|
+
describe: 'Enable preview features (e.g. project bootstrapping from templates).',
|
|
35
29
|
})
|
|
36
30
|
.strict(false);
|
|
37
31
|
export const handler = async (argv) => {
|
|
38
|
-
let options;
|
|
39
|
-
const agentArg = argv.agent;
|
|
40
|
-
if (agentArg !== undefined) {
|
|
41
|
-
const editor = parseAgentToEditor(agentArg);
|
|
42
|
-
if (editor === null) {
|
|
43
|
-
log.error(`Invalid --agent value: "${agentArg}". Supported: ${AGENT_FLAG_VALUES.join(', ')}`);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
options = { agent: editor };
|
|
48
|
-
}
|
|
49
32
|
try {
|
|
50
|
-
|
|
33
|
+
if (argv.agent !== undefined) {
|
|
34
|
+
const result = await orchestrate({
|
|
35
|
+
agent: argv.agent || undefined,
|
|
36
|
+
skipNeonAuth: argv.skipNeonAuth,
|
|
37
|
+
skipMigrations: argv.skipMigrations,
|
|
38
|
+
preview: argv.preview,
|
|
39
|
+
});
|
|
40
|
+
log.info(JSON.stringify(result, null, 2));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
await interactiveInit({ preview: argv.preview });
|
|
44
|
+
}
|
|
51
45
|
}
|
|
52
46
|
catch {
|
|
53
47
|
const exitError = new Error(`failed to run neon-init`);
|
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.
|
|
8
|
+
"version": "2.26.1",
|
|
9
9
|
"description": "CLI tool for NeonDB Cloud management",
|
|
10
10
|
"main": "index.js",
|
|
11
11
|
"author": "NeonDB",
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@hono/node-server": "2.0.4",
|
|
61
61
|
"@neondatabase/api-client": "2.7.1",
|
|
62
|
-
"@neondatabase/config": "0.7.
|
|
63
|
-
"@neondatabase/config-runtime": "0.7.
|
|
64
|
-
"@neondatabase/env": "0.5.
|
|
62
|
+
"@neondatabase/config": "0.7.1",
|
|
63
|
+
"@neondatabase/config-runtime": "0.7.1",
|
|
64
|
+
"@neondatabase/env": "0.5.1",
|
|
65
65
|
"@segment/analytics-node": "1.3.0",
|
|
66
66
|
"axios": "1.7.2",
|
|
67
67
|
"axios-debug-log": "1.0.0",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"cliui": "8.0.1",
|
|
72
72
|
"diff": "5.2.0",
|
|
73
73
|
"fflate": "^0.8.3",
|
|
74
|
-
"neon-init": "0.
|
|
74
|
+
"neon-init": "0.16.1",
|
|
75
75
|
"open": "10.1.0",
|
|
76
76
|
"openid-client": "6.8.1",
|
|
77
77
|
"pg-protocol": "^1.14.0",
|