neonctl 1.30.0 → 1.31.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
@@ -70,7 +70,7 @@ The Neon CLI supports autocompletion, which you can configure in a few easy step
70
70
  | [projects](https://neon.tech/docs/reference/cli-projects) | `list`, `create`, `update`, `delete`, `get` | Manage projects |
71
71
  | [ip-allow](https://neon.tech/docs/reference/cli-ip-allow) | `list`, `add`, `remove`, `reset` | Manage IP Allow |
72
72
  | [me](https://neon.tech/docs/reference/cli-me) | | Show current user |
73
- | [branches](https://neon.tech/docs/reference/cli-branches) | `list`, `create`, `rename`, `add-compute`, `set-primary`, `delete`, `get` | Manage branches |
73
+ | [branches](https://neon.tech/docs/reference/cli-branches) | `list`, `create`, `rename`, `add-compute`, `set-default`, `delete`, `get` | Manage branches |
74
74
  | [databases](https://neon.tech/docs/reference/cli-databases) | `list`, `create`, `delete` | Manage databases |
75
75
  | [roles](https://neon.tech/docs/reference/cli-roles) | `list`, `create`, `delete` | Manage roles |
76
76
  | [operations](https://neon.tech/docs/reference/cli-operations) | `list` | Manage operations |
@@ -12,6 +12,7 @@ const BRANCH_FIELDS = [
12
12
  'id',
13
13
  'name',
14
14
  'primary',
15
+ 'default',
15
16
  'created_at',
16
17
  'updated_at',
17
18
  ];
@@ -19,6 +20,7 @@ const BRANCH_FIELDS_RESET = [
19
20
  'id',
20
21
  'name',
21
22
  'primary',
23
+ 'default',
22
24
  'created_at',
23
25
  'last_reset_at',
24
26
  ];
@@ -38,7 +40,7 @@ export const builder = (argv) => argv
38
40
  .command('create', 'Create a branch', (yargs) => yargs.options({
39
41
  name: branchCreateRequest['branch.name'],
40
42
  parent: {
41
- describe: 'Parent branch name or id or timestamp or LSN. Defaults to the primary branch',
43
+ describe: 'Parent branch name or id or timestamp or LSN. Defaults to the default branch',
42
44
  type: 'string',
43
45
  },
44
46
  compute: {
@@ -108,7 +110,8 @@ export const builder = (argv) => argv
108
110
  ],
109
111
  ]), async (args) => await restore(args))
110
112
  .command('rename <id|name> <new-name>', 'Rename a branch', (yargs) => yargs, async (args) => await rename(args))
111
- .command('set-primary <id|name>', 'Set a branch as primary', (yargs) => yargs, async (args) => await setPrimary(args))
113
+ .command('set-primary <id|name>', 'DEPRECATED: Use set-default. Set a branch as primary', (yargs) => yargs, async (args) => await setDefault(args))
114
+ .command('set-default <id|name>', 'Set a branch as default', (yargs) => yargs, async (args) => await setDefault(args))
112
115
  .command('add-compute <id|name>', 'Add a compute to a branch', (yargs) => yargs.options({
113
116
  type: {
114
117
  type: 'string',
@@ -153,7 +156,7 @@ export const builder = (argv) => argv
153
156
  ],
154
157
  [
155
158
  '$0 branches schema-diff',
156
- "If a branch is specified in 'set-context', compares this branch to its parent. Otherwise, compares the primary branch to its parent.",
159
+ "If a branch is specified in 'set-context', compares this branch to its parent. Otherwise, compares the default branch to its parent.",
157
160
  ],
158
161
  ]);
159
162
  },
@@ -174,9 +177,9 @@ const create = async (props) => {
174
177
  return props.apiClient
175
178
  .listProjectBranches(props.projectId)
176
179
  .then(({ data }) => {
177
- const branch = data.branches.find((b) => b.primary);
180
+ const branch = data.branches.find((b) => b.default);
178
181
  if (!branch) {
179
- throw new Error('No primary branch found');
182
+ throw new Error('No default branch found');
180
183
  }
181
184
  return { parent_id: branch.id };
182
185
  });
@@ -252,9 +255,9 @@ const rename = async (props) => {
252
255
  fields: BRANCH_FIELDS,
253
256
  });
254
257
  };
255
- const setPrimary = async (props) => {
258
+ const setDefault = async (props) => {
256
259
  const branchId = await branchIdFromProps(props);
257
- const { data } = await retryOnLock(() => props.apiClient.setPrimaryProjectBranch(props.projectId, branchId));
260
+ const { data } = await retryOnLock(() => props.apiClient.setDefaultProjectBranch(props.projectId, branchId));
258
261
  writer(props).end(data.branch, {
259
262
  fields: BRANCH_FIELDS,
260
263
  });
@@ -195,6 +195,20 @@ describe('branches', () => {
195
195
  snapshot: true,
196
196
  },
197
197
  });
198
+ /* set default */
199
+ testCliCommand({
200
+ name: 'set default by id',
201
+ args: [
202
+ 'branches',
203
+ 'set-default',
204
+ 'br-sunny-branch-123456',
205
+ '--project-id',
206
+ 'test',
207
+ ],
208
+ expected: {
209
+ snapshot: true,
210
+ },
211
+ });
198
212
  /* get */
199
213
  testCliCommand({
200
214
  name: 'get by id',
@@ -14,7 +14,7 @@ export const builder = (argv) => {
14
14
  .example('$0 cs main@2024-01-01T00:00:00Z', 'Get connection string for the main branch at a specific point in time')
15
15
  .example('$0 cs main@0/234235', 'Get connection string for the main branch at a specific LSN')
16
16
  .positional('branch', {
17
- describe: `Branch name or id. Defaults to the primary branch if omitted. Can be written in the point-in-time format: "branch@timestamp" or "branch@lsn"`,
17
+ describe: `Branch name or id. Defaults to the default branch if omitted. Can be written in the point-in-time format: "branch@timestamp" or "branch@lsn"`,
18
18
  type: 'string',
19
19
  })
20
20
  .options({
package/commands/index.js CHANGED
@@ -2,6 +2,7 @@ import * as auth from './auth.js';
2
2
  import * as projects from './projects.js';
3
3
  import * as ipAllow from './ip_allow.js';
4
4
  import * as users from './user.js';
5
+ import * as orgs from './orgs.js';
5
6
  import * as branches from './branches.js';
6
7
  import * as databases from './databases.js';
7
8
  import * as roles from './roles.js';
@@ -11,6 +12,7 @@ import * as setContext from './set_context.js';
11
12
  export default [
12
13
  auth,
13
14
  users,
15
+ orgs,
14
16
  projects,
15
17
  ipAllow,
16
18
  branches,
@@ -0,0 +1,23 @@
1
+ import { writer } from '../writer.js';
2
+ const ORG_FIELDS = ['id', 'name'];
3
+ export const command = 'orgs';
4
+ export const describe = 'Manage organizations';
5
+ export const aliases = ['org'];
6
+ export const builder = (argv) => {
7
+ return argv.usage('$0 orgs <sub-command> [options]').command('list', 'List organizations', (yargs) => yargs, async (args) => {
8
+ // @ts-expect-error: TODO - Assert `args` is `CommonProps`
9
+ await list(args);
10
+ });
11
+ };
12
+ export const handler = (args) => {
13
+ return args;
14
+ };
15
+ const list = async (props) => {
16
+ const out = writer(props);
17
+ const { data: { organizations }, } = await props.apiClient.getCurrentUserOrganizations();
18
+ out.write(organizations, {
19
+ fields: ORG_FIELDS,
20
+ title: 'Organizations',
21
+ });
22
+ out.end();
23
+ };
@@ -136,7 +136,6 @@ const create = async (props) => {
136
136
  if (props.setContext) {
137
137
  updateContextFile(props.contextFile, {
138
138
  projectId: data.project.id,
139
- branchId: data.branch.id,
140
139
  });
141
140
  }
142
141
  const out = writer(props);
@@ -118,7 +118,7 @@ const generateHeader = (pointInTime) => {
118
118
  /*
119
119
  The command has two positional optional arguments - [base-branch] and [compare-source]
120
120
  If only one argument is specified, we should consider it as `compare-source`
121
- and `base-branch` will be either read from context or the primary branch of project.
121
+ and `base-branch` will be either read from context or the default branch of project.
122
122
  If no branches are specified, compare the context branch with its parent
123
123
  */
124
124
  export const parseSchemaDiffParams = async (props) => {
@@ -138,11 +138,11 @@ export const parseSchemaDiffParams = async (props) => {
138
138
  }
139
139
  else {
140
140
  const { data } = await props.apiClient.listProjectBranches(props.projectId);
141
- const primaryBranch = data.branches.find((b) => b.primary);
142
- if (primaryBranch?.parent_id == undefined) {
143
- throw new Error('No branch specified. Include a base branch or add a set-context branch to continue. Your primary branch has no parent, so no comparison is possible.');
141
+ const defaultBranch = data.branches.find((b) => b.default);
142
+ if (defaultBranch?.parent_id == undefined) {
143
+ throw new Error('No branch specified. Include a base branch or add a set-context branch to continue. Your default branch has no parent, so no comparison is possible.');
144
144
  }
145
- log.info(`No branches specified. Comparing primary branch with its parent`);
145
+ log.info(`No branches specified. Comparing default branch with its parent`);
146
146
  props.compareSource = '^parent';
147
147
  }
148
148
  }
@@ -1,5 +1,4 @@
1
1
  import { updateContextFile } from '../context.js';
2
- import { branchIdFromProps } from '../utils/enrichers.js';
3
2
  export const command = 'set-context';
4
3
  export const describe = 'Set the current context';
5
4
  export const builder = (argv) => argv.usage('$0 set-context [options]').options({
@@ -7,16 +6,10 @@ export const builder = (argv) => argv.usage('$0 set-context [options]').options(
7
6
  describe: 'Project ID',
8
7
  type: 'string',
9
8
  },
10
- branch: {
11
- describe: 'Branch ID or name',
12
- type: 'string',
13
- },
14
9
  });
15
10
  export const handler = async (props) => {
16
- const branchId = await branchIdFromProps(props);
17
11
  const context = {
18
12
  projectId: props.projectId,
19
- branchId,
20
13
  };
21
14
  updateContextFile(props.contextFile, context);
22
15
  };
@@ -28,24 +28,64 @@ describe('set_context', () => {
28
28
  });
29
29
  const overrideContextFile = join(tmpdir(), `neon_override_ctx_${Date.now()}`);
30
30
  testCliCommand({
31
- name: 'get branch id overrides context set branch',
31
+ name: 'get project id overrides context set project',
32
32
  before: async () => {
33
33
  writeFileSync(overrideContextFile, JSON.stringify({
34
- projectId: 'test',
35
- branchId: 'br-cloudy-branch-12345678',
34
+ projectId: 'new-project id',
36
35
  }));
37
36
  },
38
37
  after: async () => {
39
38
  rmSync(overrideContextFile);
40
39
  },
41
40
  args: [
42
- 'branches',
41
+ 'project',
43
42
  'get',
44
- 'br-sunny-branch-123456',
43
+ 'project-id-123',
44
+ '--context-file',
45
+ overrideContextFile,
46
+ ],
47
+ expected: {
48
+ snapshot: true,
49
+ },
50
+ });
51
+ testCliCommand({
52
+ name: 'set the branchId and projectId is from context',
53
+ before: async () => {
54
+ writeFileSync(overrideContextFile, JSON.stringify({
55
+ projectId: 'test',
56
+ branchId: 'test_branch',
57
+ }));
58
+ },
59
+ after: async () => {
60
+ rmSync(overrideContextFile);
61
+ },
62
+ args: ['databases', 'list', '--context-file', overrideContextFile],
63
+ expected: {
64
+ snapshot: true,
65
+ },
66
+ });
67
+ testCliCommand({
68
+ name: 'should not set branchId from context for non-context projectId',
69
+ before: async () => {
70
+ writeFileSync(overrideContextFile, JSON.stringify({
71
+ projectId: 'project-id-123',
72
+ branchId: 'test_branch',
73
+ }));
74
+ },
75
+ after: async () => {
76
+ rmSync(overrideContextFile);
77
+ },
78
+ args: [
79
+ 'databases',
80
+ 'list',
81
+ '--project-id',
82
+ 'test',
45
83
  '--context-file',
46
84
  overrideContextFile,
47
85
  ],
48
86
  expected: {
87
+ code: 1,
88
+ stderr: 'ERROR: Not Found',
49
89
  snapshot: true,
50
90
  },
51
91
  });
package/context.js CHANGED
@@ -36,12 +36,15 @@ export const enrichFromContext = (args) => {
36
36
  return;
37
37
  }
38
38
  const context = readContextFile(args.contextFile);
39
- if (!args.branch && !args.id && !args.name) {
40
- args.branch = context.branchId;
41
- }
42
39
  if (!args.projectId) {
43
40
  args.projectId = context.projectId;
44
41
  }
42
+ if (!args.branch &&
43
+ !args.id &&
44
+ !args.name &&
45
+ context.projectId === args.projectId) {
46
+ args.branch = context.branchId;
47
+ }
45
48
  };
46
49
  export const updateContextFile = (file, context) => {
47
50
  writeFileSync(file, JSON.stringify(context, null, 2));
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": "1.30.0",
8
+ "version": "1.31.1",
9
9
  "description": "CLI tool for NeonDB Cloud management",
10
10
  "main": "index.js",
11
11
  "author": "NeonDB",
@@ -54,7 +54,7 @@
54
54
  "typescript": "^4.7.4"
55
55
  },
56
56
  "dependencies": {
57
- "@neondatabase/api-client": "1.7.0",
57
+ "@neondatabase/api-client": "1.9.0",
58
58
  "@segment/analytics-node": "^1.0.0-beta.26",
59
59
  "axios": "^1.4.0",
60
60
  "axios-debug-log": "^1.0.0",
package/parameters.gen.js CHANGED
@@ -37,7 +37,7 @@ export const projectCreateRequest = {
37
37
  },
38
38
  'project.settings.allowed_ips.primary_branch_only': {
39
39
  type: "boolean",
40
- description: "If true, the list will be applied only to the primary branch.",
40
+ description: "If true, the list will be applied only to the default branch.",
41
41
  demandOption: false,
42
42
  },
43
43
  'project.settings.enable_logical_replication': {
@@ -140,7 +140,7 @@ export const projectUpdateRequest = {
140
140
  },
141
141
  'project.settings.allowed_ips.primary_branch_only': {
142
142
  type: "boolean",
143
- description: "If true, the list will be applied only to the primary branch.",
143
+ description: "If true, the list will be applied only to the default branch.",
144
144
  demandOption: false,
145
145
  },
146
146
  'project.settings.enable_logical_replication': {
@@ -172,7 +172,7 @@ export const branchCreateRequest = {
172
172
  },
173
173
  'branch.parent_id': {
174
174
  type: "string",
175
- description: "The `branch_id` of the parent branch. If omitted or empty, the branch will be created from the project's primary branch.\n",
175
+ description: "The `branch_id` of the parent branch. If omitted or empty, the branch will be created from the project's default branch.\n",
176
176
  demandOption: false,
177
177
  },
178
178
  'branch.name': {
@@ -25,11 +25,11 @@ export const branchIdFromProps = async (props) => {
25
25
  });
26
26
  }
27
27
  const { data } = await props.apiClient.listProjectBranches(props.projectId);
28
- const primaryBranch = data.branches.find((b) => b.primary);
29
- if (primaryBranch) {
30
- return primaryBranch.id;
28
+ const defaultBranch = data.branches.find((b) => b.default);
29
+ if (defaultBranch) {
30
+ return defaultBranch.id;
31
31
  }
32
- throw new Error('No primary branch found');
32
+ throw new Error('No default branch found');
33
33
  };
34
34
  export const fillSingleProject = async (props) => {
35
35
  if (props.projectId) {