neonctl 2.34.1 → 2.35.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
@@ -58,6 +58,40 @@ neon projects list --api-key <neon_api_key>
58
58
 
59
59
  For information about obtaining an Neon API key, see [Authentication](https://api-docs.neon.tech/reference/authentication), in the _Neon API Reference_.
60
60
 
61
+ ## Project and branch creation
62
+
63
+ Choose the PostgreSQL version when creating a project:
64
+
65
+ ```bash
66
+ neon projects create --name my-project --pg-version 18
67
+ ```
68
+
69
+ Supported versions are `14` through `19`; version `19` is available only in
70
+ regions where it has been enabled.
71
+
72
+ Create a protected branch when it should not be modified or deleted by routine
73
+ automation:
74
+
75
+ ```bash
76
+ neon branches create --project-id <project-id> --name production --protected
77
+ ```
78
+
79
+ ### Enable logical replication
80
+
81
+ Enable logical replication for every endpoint in an existing project:
82
+
83
+ ```bash
84
+ neon projects update <project-id> --enable-logical-replication
85
+ ```
86
+
87
+ The CLI asks for confirmation because enabling logical replication suspends
88
+ active endpoints and cannot be undone. For non-interactive automation, pass
89
+ `--yes` explicitly:
90
+
91
+ ```bash
92
+ neon projects update <project-id> --enable-logical-replication --yes
93
+ ```
94
+
61
95
  ## Connect with psql
62
96
 
63
97
  ### The `psql` command
@@ -97,6 +97,10 @@ export const builder = (argv) => argv
97
97
  type: "string",
98
98
  requiresArg: true,
99
99
  },
100
+ protected: {
101
+ describe: branchCreateRequest["branch.protected"].description,
102
+ type: "boolean",
103
+ },
100
104
  }), (args) => create(args))
101
105
  .command("reset <id|name>", "Reset a branch", (yargs) => yargs.options({
102
106
  parent: {
@@ -299,6 +303,9 @@ const create = async (props) => {
299
303
  expires_at: new Date(props["expires-at"]).toISOString(),
300
304
  }
301
305
  : {}),
306
+ ...(props.protected !== undefined
307
+ ? { protected: props.protected }
308
+ : {}),
302
309
  },
303
310
  endpoints: props.compute
304
311
  ? [
@@ -92,6 +92,11 @@ export const builder = (argv) => {
92
92
  .description,
93
93
  type: "string",
94
94
  },
95
+ "pg-version": {
96
+ describe: "Major PostgreSQL version (14–19). Version 19 is available only in regions where it has been enabled.",
97
+ type: "number",
98
+ choices: [14, 15, 16, 17, 18, 19],
99
+ },
95
100
  "set-context": {
96
101
  type: "boolean",
97
102
  describe: "Set the current context to the new project",
@@ -128,6 +133,15 @@ export const builder = (argv) => {
128
133
  describe: projectUpdateRequest["project.name"].description,
129
134
  type: "string",
130
135
  },
136
+ "enable-logical-replication": {
137
+ describe: "Enable logical replication for all project endpoints. This suspends active endpoints and cannot be disabled.",
138
+ type: "boolean",
139
+ },
140
+ yes: {
141
+ type: "boolean",
142
+ default: false,
143
+ describe: "Skip the confirmation prompt when enabling logical replication.",
144
+ },
131
145
  }), async (args) => {
132
146
  await update(args);
133
147
  })
@@ -226,6 +240,9 @@ const create = async (props) => {
226
240
  if (props.role) {
227
241
  project.branch.role_name = props.role;
228
242
  }
243
+ if (props.pgVersion !== undefined) {
244
+ project.pg_version = props.pgVersion;
245
+ }
229
246
  if (props.cu) {
230
247
  project.default_endpoint_settings = props.cu
231
248
  ? getComputeUnits(props.cu)
@@ -262,6 +279,30 @@ const deleteProject = async (props) => {
262
279
  };
263
280
  const update = async (props) => {
264
281
  const project = {};
282
+ if (props.enableLogicalReplication !== undefined) {
283
+ if (!props.enableLogicalReplication) {
284
+ throw new Error("Logical replication cannot be disabled once it has been enabled.");
285
+ }
286
+ if (!props.yes) {
287
+ if (isCi() || !process.stdin.isTTY) {
288
+ throw new Error("Enabling logical replication requires confirmation. Re-run interactively or pass --yes.");
289
+ }
290
+ const { proceed } = await prompts({
291
+ onState: onPromptState,
292
+ type: "confirm",
293
+ name: "proceed",
294
+ message: "Enable logical replication? This suspends active endpoints and cannot be undone.",
295
+ initial: false,
296
+ });
297
+ if (!proceed) {
298
+ log.info("Logical replication was not enabled.");
299
+ return;
300
+ }
301
+ }
302
+ project.settings = {
303
+ enable_logical_replication: true,
304
+ };
305
+ }
265
306
  if (props.hipaa !== undefined) {
266
307
  if (!project.settings) {
267
308
  project.settings = {};
package/dist/context.js CHANGED
@@ -33,7 +33,37 @@ export const isCurrentBranchProbe = (args) => args.currentBranch === true &&
33
33
  export const isConfigInit = (args) => args._[0] === "config" && args._[1] === "init";
34
34
  const CONTEXT_FILE = ".neon";
35
35
  const GITIGNORE_FILE = ".gitignore";
36
- const wrapWithContextFile = (dir) => resolve(dir, CONTEXT_FILE);
36
+ const canAccessFile = (file) => {
37
+ try {
38
+ accessSync(file);
39
+ return true;
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ };
45
+ /**
46
+ * Walk upward to find an existing `.neon` file.
47
+ *
48
+ * The walk keeps the established home and POSIX-root boundaries, then also
49
+ * stops when resolving a parent makes no progress. That second guard handles
50
+ * Windows drive and UNC-share roots, whose paths do not equal `normalize("/")`.
51
+ */
52
+ export const walkContextFile = (cwd, root, home, resolvePath, canAccess) => {
53
+ let currentDir = cwd;
54
+ while (currentDir !== root && currentDir !== home) {
55
+ const contextFile = resolvePath(currentDir, CONTEXT_FILE);
56
+ if (canAccess(contextFile)) {
57
+ return contextFile;
58
+ }
59
+ const parentDir = resolvePath(currentDir, "..");
60
+ if (parentDir === currentDir) {
61
+ break;
62
+ }
63
+ currentDir = parentDir;
64
+ }
65
+ return resolvePath(cwd, CONTEXT_FILE);
66
+ };
37
67
  /**
38
68
  * Resolve the default `.neon` path for the current working directory.
39
69
  *
@@ -52,22 +82,7 @@ const wrapWithContextFile = (dir) => resolve(dir, CONTEXT_FILE);
52
82
  * `cwd` is overridable so tests can exercise the walk-up without mutating
53
83
  * `process.cwd()` (which would race with other tests running in parallel).
54
84
  */
55
- export const currentContextFile = (cwd = process.cwd()) => {
56
- let currentDir = cwd;
57
- const root = normalize("/");
58
- const home = homedir();
59
- while (currentDir !== root && currentDir !== home) {
60
- try {
61
- accessSync(resolve(currentDir, CONTEXT_FILE));
62
- return wrapWithContextFile(currentDir);
63
- }
64
- catch {
65
- // ignore
66
- }
67
- currentDir = resolve(currentDir, "..");
68
- }
69
- return wrapWithContextFile(cwd);
70
- };
85
+ export const currentContextFile = (cwd = process.cwd()) => walkContextFile(cwd, normalize("/"), homedir(), resolve, canAccessFile);
71
86
  export const readContextFile = (file) => {
72
87
  try {
73
88
  return JSON.parse(readFileSync(file, "utf-8"));
package/dist/dev/env.js CHANGED
@@ -45,8 +45,9 @@ export class MissingBranchContextError extends Error {
45
45
  * the branch is missing, we stop with a {@link DevEnvMismatchError} pointing at
46
46
  * `neonctl deploy`. Otherwise `fetchEnv` evaluates the policy.
47
47
  * 2. no `neon.ts`, but a project + branch are known -> `pullConfig` reads the
48
- * branch's live state (incl. Auth / Data API enablement) into a config, then
49
- * `fetchEnv` resolves what is actually enabled.
48
+ * branch's live state (Auth / Data API enablement plus any object-storage
49
+ * buckets) into a config, then `fetchEnv` resolves what is actually enabled —
50
+ * so a branch with a bucket gets its `AWS_*` storage vars pulled with no policy.
50
51
  * 3. otherwise -> throw {@link MissingBranchContextError}.
51
52
  *
52
53
  * Unlike {@link resolveDevEnv}, this never swallows errors — callers decide how to
@@ -78,8 +79,11 @@ export const resolveNeonEnvVars = async (ctx) => {
78
79
  branchId: ctx.branchId,
79
80
  ...apiOptions(ctx),
80
81
  });
81
- // `pulled.config` is already a `Config` (static auth/dataApi toggles + a branch
82
- // tuning closure), so it feeds straight into fetchEnv — no wrapping needed.
82
+ // `pulled.config` is already a `Config` (static auth/dataApi toggles, any
83
+ // object-storage `preview.buckets`, and a branch tuning closure), so it feeds
84
+ // straight into fetchEnv — no wrapping needed. pullConfig excludes functions and
85
+ // the AI Gateway (neither can be faithfully read back), so fetchEnv never probes
86
+ // the functions API here and only mints a storage credential when a bucket exists.
83
87
  return await fetchAndProject(pulled.config, ctx);
84
88
  }
85
89
  throw new MissingBranchContextError("No project/branch context found. Link a branch (`neonctl link` / " +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neonctl",
3
- "version": "2.34.1",
3
+ "version": "2.35.1",
4
4
  "description": "CLI tool for Neon Serverless Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -51,10 +51,10 @@
51
51
  "which": "3.0.1",
52
52
  "yaml": "^2.9.0",
53
53
  "yargs": "17.7.2",
54
+ "@neon/sdk": "1.2.0",
54
55
  "@neon/config": "0.9.5",
55
- "@neon/config-runtime": "0.9.5",
56
- "@neon/env": "0.11.4",
57
- "@neon/sdk": "1.2.0"
56
+ "@neon/config-runtime": "0.9.6",
57
+ "@neon/env": "0.11.4"
58
58
  },
59
59
  "optionalDependencies": {
60
60
  "esbuild": "0.28.1"