neonctl 2.34.1 → 2.35.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
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neonctl",
3
- "version": "2.34.1",
3
+ "version": "2.35.0",
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
56
  "@neon/config-runtime": "0.9.5",
56
- "@neon/env": "0.11.4",
57
- "@neon/sdk": "1.2.0"
57
+ "@neon/env": "0.11.4"
58
58
  },
59
59
  "optionalDependencies": {
60
60
  "esbuild": "0.28.1"