neonctl 1.36.0 → 2.0.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.
@@ -495,7 +495,7 @@ const bootstrap = async (props) => {
495
495
  let projectCreateRequest;
496
496
  let project;
497
497
  let devConnectionString;
498
- const devBranchName = `dev-${cryptoRandomString({
498
+ const devBranchName = `dev/${cryptoRandomString({
499
499
  length: 10,
500
500
  type: 'url-safe',
501
501
  })}`;
@@ -12,7 +12,6 @@ import { getComputeUnits } from '../utils/compute_units.js';
12
12
  export const BRANCH_FIELDS = [
13
13
  'id',
14
14
  'name',
15
- 'primary',
16
15
  'default',
17
16
  'created_at',
18
17
  'updated_at',
@@ -20,7 +19,6 @@ export const BRANCH_FIELDS = [
20
19
  const BRANCH_FIELDS_RESET = [
21
20
  'id',
22
21
  'name',
23
- 'primary',
24
22
  'default',
25
23
  'created_at',
26
24
  'last_reset_at',
@@ -121,7 +119,6 @@ export const builder = (argv) => argv
121
119
  ],
122
120
  ]), (args) => restore(args))
123
121
  .command('rename <id|name> <new-name>', 'Rename a branch', (yargs) => yargs, (args) => rename(args))
124
- .command('set-primary <id|name>', 'DEPRECATED: Use set-default. Set a branch as primary', (yargs) => yargs, (args) => setDefault(args))
125
122
  .command('set-default <id|name>', 'Set a branch as default', (yargs) => yargs, (args) => setDefault(args))
126
123
  .command('add-compute <id|name>', 'Add a compute to a branch', (yargs) => yargs.options({
127
124
  type: {
@@ -187,17 +184,16 @@ const list = async (props) => {
187
184
  });
188
185
  };
189
186
  const create = async (props) => {
190
- const parentProps = await (() => {
187
+ const branches = await props.apiClient
188
+ .listProjectBranches(props.projectId)
189
+ .then(({ data }) => data.branches);
190
+ const parentProps = (() => {
191
191
  if (!props.parent) {
192
- return props.apiClient
193
- .listProjectBranches(props.projectId)
194
- .then(({ data }) => {
195
- const branch = data.branches.find((b) => b.default);
196
- if (!branch) {
197
- throw new Error('No default branch found');
198
- }
199
- return { parent_id: branch.id };
200
- });
192
+ const branch = branches.find((b) => b.default);
193
+ if (!branch) {
194
+ throw new Error('No default branch found');
195
+ }
196
+ return { parent_id: branch.id };
201
197
  }
202
198
  if (looksLikeLSN(props.parent)) {
203
199
  return { parent_lsn: props.parent };
@@ -208,15 +204,11 @@ const create = async (props) => {
208
204
  if (looksLikeBranchId(props.parent)) {
209
205
  return { parent_id: props.parent };
210
206
  }
211
- return props.apiClient
212
- .listProjectBranches(props.projectId)
213
- .then(({ data }) => {
214
- const branch = data.branches.find((b) => b.name === props.parent);
215
- if (!branch) {
216
- throw new Error(`Branch ${props.parent} not found`);
217
- }
218
- return { parent_id: branch.id };
219
- });
207
+ const branch = branches.find((b) => b.name === props.parent);
208
+ if (!branch) {
209
+ throw new Error(`Branch ${props.parent} not found`);
210
+ }
211
+ return { parent_id: branch.id };
220
212
  })();
221
213
  const { data } = await retryOnLock(() => props.apiClient.createProjectBranch(props.projectId, {
222
214
  branch: {
@@ -236,21 +228,28 @@ const create = async (props) => {
236
228
  ? JSON.parse(props.annotation)
237
229
  : undefined,
238
230
  }));
231
+ const parent = branches.find((b) => b.id === data.branch.parent_id);
232
+ if (parent?.protected) {
233
+ log.warning('The parent branch is protected; a unique role password has been generated for the new branch.');
234
+ }
239
235
  const out = writer(props);
240
236
  out.write(data.branch, {
241
237
  fields: BRANCH_FIELDS,
242
238
  title: 'branch',
239
+ emptyMessage: 'No branches have been found.',
243
240
  });
244
241
  if (data.endpoints?.length > 0) {
245
242
  out.write(data.endpoints, {
246
243
  fields: ['id', 'created_at'],
247
244
  title: 'endpoints',
245
+ emptyMessage: 'No endpoints have been found.',
248
246
  });
249
247
  }
250
248
  if (data.connection_uris?.length) {
251
249
  out.write(data.connection_uris, {
252
250
  fields: ['connection_uri'],
253
251
  title: 'connection_uris',
252
+ emptyMessage: 'No connection uris have been found',
254
253
  });
255
254
  }
256
255
  out.end();
@@ -353,6 +352,7 @@ const restore = async (props) => {
353
352
  const writeInst = writer(props).write(data.branch, {
354
353
  title: 'Restored branch',
355
354
  fields: ['id', 'name', 'last_reset_at'],
355
+ emptyMessage: 'No branches have been restored.',
356
356
  });
357
357
  const parentId = data.branch.parent_id;
358
358
  if (props.preserveUnderName && parentId) {
@@ -360,6 +360,7 @@ const restore = async (props) => {
360
360
  writeInst.write(data.branch, {
361
361
  title: 'Backup branch',
362
362
  fields: ['id', 'name'],
363
+ emptyMessage: 'Backup branch has not been found.',
363
364
  });
364
365
  }
365
366
  writeInst.end();
@@ -157,16 +157,6 @@ describe('branches', () => {
157
157
  'test',
158
158
  ]);
159
159
  });
160
- /* set primary */
161
- test('set primary by id', async ({ testCliCommand }) => {
162
- await testCliCommand([
163
- 'branches',
164
- 'set-primary',
165
- 'br-sunny-branch-123456',
166
- '--project-id',
167
- 'test',
168
- ]);
169
- });
170
160
  /* set default */
171
161
  test('set default by id', async ({ testCliCommand }) => {
172
162
  await testCliCommand([
@@ -36,13 +36,6 @@ export const builder = (argv) => {
36
36
  describe: projectUpdateRequest['project.settings.allowed_ips.protected_branches_only'].description,
37
37
  type: 'boolean',
38
38
  },
39
- })
40
- .options({
41
- 'primary-only': {
42
- describe: projectUpdateRequest['project.settings.allowed_ips.primary_branch_only'].description,
43
- type: 'boolean',
44
- deprecated: 'See --protected-only',
45
- },
46
39
  }), async (args) => {
47
40
  await add(args);
48
41
  })
@@ -83,7 +76,6 @@ const add = async (props) => {
83
76
  project.settings = {
84
77
  allowed_ips: {
85
78
  ips: [...new Set(props.ips.concat(existingAllowedIps?.ips ?? []))],
86
- primary_branch_only: props.primaryOnly ?? existingAllowedIps?.primary_branch_only ?? false,
87
79
  protected_branches_only: props.protectedOnly ??
88
80
  existingAllowedIps?.protected_branches_only ??
89
81
  false,
@@ -106,7 +98,6 @@ const remove = async (props) => {
106
98
  project.settings = {
107
99
  allowed_ips: {
108
100
  ips: existingAllowedIps?.ips?.filter((ip) => !props.ips.includes(ip)) ?? [],
109
- primary_branch_only: existingAllowedIps?.primary_branch_only ?? false,
110
101
  protected_branches_only: existingAllowedIps?.protected_branches_only ?? false,
111
102
  },
112
103
  };
@@ -122,7 +113,6 @@ const reset = async (props) => {
122
113
  project.settings = {
123
114
  allowed_ips: {
124
115
  ips: props.ips,
125
- primary_branch_only: false,
126
116
  protected_branches_only: false,
127
117
  },
128
118
  };
@@ -142,7 +132,6 @@ const parse = (project) => {
142
132
  id: project.id,
143
133
  name: project.name,
144
134
  IP_addresses: ips,
145
- primary_branch_only: project.settings?.allowed_ips?.primary_branch_only ?? false,
146
135
  protected_branches_only: project.settings?.allowed_ips?.protected_branches_only ?? false,
147
136
  };
148
137
  };
@@ -16,17 +16,6 @@ describe('ip-allow', () => {
16
16
  Example: neonctl ip-allow add 192.168.1.1, 192.168.1.20-192.168.1.50, 192.168.1.0/24 --project-id <id>`,
17
17
  });
18
18
  });
19
- test('Add IP allow - Primary', async ({ testCliCommand }) => {
20
- await testCliCommand([
21
- 'ip-allow',
22
- 'add',
23
- '127.0.0.1',
24
- '192.168.10.1-192.168.10.15',
25
- '--primary-only',
26
- '--project-id',
27
- 'test',
28
- ]);
29
- });
30
19
  test('Add IP allow - Protected', async ({ testCliCommand }) => {
31
20
  await testCliCommand([
32
21
  'ip-allow',
package/commands/orgs.js CHANGED
@@ -18,6 +18,7 @@ const list = async (props) => {
18
18
  out.write(organizations, {
19
19
  fields: ORG_FIELDS,
20
20
  title: 'Organizations',
21
+ emptyMessage: 'You are not a member of any organization.',
21
22
  });
22
23
  out.end();
23
24
  };
@@ -1,5 +1,5 @@
1
1
  import { log } from '../log.js';
2
- import { projectCreateRequest, projectUpdateRequest, } from '../parameters.gen.js';
2
+ import { projectCreateRequest } from '../parameters.gen.js';
3
3
  import { writer } from '../writer.js';
4
4
  import { psql } from '../utils/psql.js';
5
5
  import { updateContextFile } from '../context.js';
@@ -13,9 +13,11 @@ export const PROJECT_FIELDS = [
13
13
  const REGIONS = [
14
14
  'aws-us-west-2',
15
15
  'aws-ap-southeast-1',
16
+ 'aws-ap-southeast-2',
16
17
  'aws-eu-central-1',
17
18
  'aws-us-east-2',
18
19
  'aws-us-east-1',
20
+ 'azure-eastus2',
19
21
  ];
20
22
  const PROJECTS_LIST_LIMIT = 100;
21
23
  export const command = 'projects';
@@ -75,18 +77,6 @@ export const builder = (argv) => {
75
77
  describe: projectCreateRequest['project.name'].description,
76
78
  type: 'string',
77
79
  },
78
- 'ip-allow': {
79
- describe: projectUpdateRequest['project.settings.allowed_ips.ips']
80
- .description,
81
- type: 'string',
82
- array: true,
83
- deprecated: "Deprecated. Use 'ip-allow' command",
84
- },
85
- 'ip-primary-only': {
86
- describe: projectUpdateRequest['project.settings.allowed_ips.primary_branch_only'].description,
87
- type: 'boolean',
88
- deprecated: "Deprecated. Use 'ip-allow' command",
89
- },
90
80
  cu: {
91
81
  describe: 'The number of Compute Units. Could be a fixed size (e.g. "2") or a range delimited by a dash (e.g. "0.5-3").',
92
82
  type: 'string',
@@ -124,19 +114,21 @@ const list = async (props) => {
124
114
  }
125
115
  return result;
126
116
  };
127
- const ownedProjects = getList(props.apiClient.listProjects);
117
+ const ownedProjects = await getList(props.apiClient.listProjects);
128
118
  const sharedProjects = props.orgId
129
- ? undefined
130
- : getList(props.apiClient.listSharedProjects);
119
+ ? []
120
+ : await getList(props.apiClient.listSharedProjects);
131
121
  const out = writer(props);
132
- out.write(await ownedProjects, {
122
+ out.write(ownedProjects, {
133
123
  fields: PROJECT_FIELDS,
134
124
  title: 'Projects',
125
+ emptyMessage: "You don't have any projects yet. See how to create a new project:\n> neonctl projects create --help",
135
126
  });
136
- if (sharedProjects) {
137
- out.write(await sharedProjects, {
127
+ if (!props.orgId) {
128
+ out.write(sharedProjects, {
138
129
  fields: PROJECT_FIELDS,
139
- title: 'Shared with me',
130
+ title: 'Shared with you',
131
+ emptyMessage: 'No projects have been shared with you',
140
132
  });
141
133
  }
142
134
  out.end();
@@ -196,18 +188,6 @@ const update = async (props) => {
196
188
  if (props.name) {
197
189
  project.name = props.name;
198
190
  }
199
- if (props.ipAllow || props.ipPrimaryOnly != undefined) {
200
- const { data } = await props.apiClient.getProject(props.id);
201
- const existingAllowedIps = data.project.settings?.allowed_ips;
202
- project.settings = {
203
- allowed_ips: {
204
- ips: props.ipAllow ?? existingAllowedIps?.ips ?? [],
205
- primary_branch_only: props.ipPrimaryOnly ??
206
- existingAllowedIps?.primary_branch_only ??
207
- false,
208
- },
209
- };
210
- }
211
191
  if (props.cu) {
212
192
  project.default_endpoint_settings = props.cu
213
193
  ? getComputeUnits(props.cu)
@@ -102,29 +102,6 @@ describe('projects', () => {
102
102
  'test_project_new_name',
103
103
  ]);
104
104
  });
105
- test('update ip allow', async ({ testCliCommand }) => {
106
- await testCliCommand([
107
- 'projects',
108
- 'update',
109
- 'test',
110
- '--ip-allow',
111
- '127.0.0.1',
112
- '192.168.1.2/22',
113
- '--ip-primary-only',
114
- ]);
115
- });
116
- test('update ip allow primary only flag', async ({ testCliCommand }) => {
117
- await testCliCommand([
118
- 'projects',
119
- 'update',
120
- 'test',
121
- '--ip-primary-only',
122
- 'false',
123
- ]);
124
- });
125
- test('update ip allow remove', async ({ testCliCommand }) => {
126
- await testCliCommand(['projects', 'update', 'test', '--ip-allow']);
127
- });
128
105
  test('update project with default fixed size CU', async ({ testCliCommand, }) => {
129
106
  await testCliCommand([
130
107
  'projects',
package/index.js CHANGED
@@ -137,6 +137,7 @@ builder = builder
137
137
  default: true,
138
138
  })
139
139
  .middleware(analyticsMiddleware, true)
140
+ .version(pkg.version)
140
141
  .group('version', 'Global options:')
141
142
  .alias('version', 'v')
142
143
  .completion()
package/log.js CHANGED
@@ -6,6 +6,9 @@ export const log = {
6
6
  process.stderr.write(`DEBUG: ${format(...args)}\n`);
7
7
  }
8
8
  },
9
+ warning: (...args) => {
10
+ process.stderr.write(`WARNING: ${format(...args)}\n`);
11
+ },
9
12
  info: (...args) => {
10
13
  process.stderr.write(`INFO: ${format(...args)}\n`);
11
14
  },
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.36.0",
8
+ "version": "2.0.0",
9
9
  "description": "CLI tool for NeonDB Cloud management",
10
10
  "main": "index.js",
11
11
  "author": "NeonDB",
package/writer.js CHANGED
@@ -22,6 +22,10 @@ const writeJson = (chunks) => {
22
22
  const writeTable = (chunks, out) => {
23
23
  chunks.forEach(({ data, config }) => {
24
24
  const arrayData = Array.isArray(data) ? data : [data];
25
+ if (!arrayData.length && config.emptyMessage) {
26
+ out.write('\n' + config.emptyMessage + '\n');
27
+ return;
28
+ }
25
29
  const fields = config.fields.filter((field) => arrayData.some((item) => item[field] !== undefined && item[field] !== ''));
26
30
  const table = new Table({
27
31
  style: {