neonctl 1.35.0 → 1.37.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.
@@ -187,17 +187,16 @@ const list = async (props) => {
187
187
  });
188
188
  };
189
189
  const create = async (props) => {
190
- const parentProps = await (() => {
190
+ const branches = await props.apiClient
191
+ .listProjectBranches(props.projectId)
192
+ .then(({ data }) => data.branches);
193
+ const parentProps = (() => {
191
194
  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
- });
195
+ const branch = branches.find((b) => b.default);
196
+ if (!branch) {
197
+ throw new Error('No default branch found');
198
+ }
199
+ return { parent_id: branch.id };
201
200
  }
202
201
  if (looksLikeLSN(props.parent)) {
203
202
  return { parent_lsn: props.parent };
@@ -208,15 +207,11 @@ const create = async (props) => {
208
207
  if (looksLikeBranchId(props.parent)) {
209
208
  return { parent_id: props.parent };
210
209
  }
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
- });
210
+ const branch = branches.find((b) => b.name === props.parent);
211
+ if (!branch) {
212
+ throw new Error(`Branch ${props.parent} not found`);
213
+ }
214
+ return { parent_id: branch.id };
220
215
  })();
221
216
  const { data } = await retryOnLock(() => props.apiClient.createProjectBranch(props.projectId, {
222
217
  branch: {
@@ -236,6 +231,10 @@ const create = async (props) => {
236
231
  ? JSON.parse(props.annotation)
237
232
  : undefined,
238
233
  }));
234
+ const parent = branches.find((b) => b.id === data.branch.parent_id);
235
+ if (parent?.protected) {
236
+ log.warning('The parent branch is protected; a unique role password has been generated for the new branch.');
237
+ }
239
238
  const out = writer(props);
240
239
  out.write(data.branch, {
241
240
  fields: BRANCH_FIELDS,
@@ -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';
@@ -24,7 +26,12 @@ export const aliases = ['project'];
24
26
  export const builder = (argv) => {
25
27
  return argv
26
28
  .usage('$0 projects <sub-command> [options]')
27
- .command('list', 'List projects', (yargs) => yargs, async (args) => {
29
+ .command('list', 'List projects', (yargs) => yargs.options({
30
+ 'org-id': {
31
+ describe: 'List projects of a given organization',
32
+ type: 'string',
33
+ },
34
+ }), async (args) => {
28
35
  await list(args);
29
36
  })
30
37
  .command('create', 'Create a project', (yargs) => yargs.options({
@@ -36,6 +43,10 @@ export const builder = (argv) => {
36
43
  describe: `The region ID. Possible values: ${REGIONS.join(', ')}`,
37
44
  type: 'string',
38
45
  },
46
+ 'org-id': {
47
+ describe: "The project's organization ID",
48
+ type: 'string',
49
+ },
39
50
  psql: {
40
51
  type: 'boolean',
41
52
  describe: 'Connect to a new project via psql',
@@ -103,6 +114,7 @@ const list = async (props) => {
103
114
  while (!end) {
104
115
  const { data } = await fn({
105
116
  limit: PROJECTS_LIST_LIMIT,
117
+ org_id: props.orgId,
106
118
  cursor,
107
119
  });
108
120
  result.push(...data.projects);
@@ -114,19 +126,21 @@ const list = async (props) => {
114
126
  }
115
127
  return result;
116
128
  };
117
- const [ownedProjects, sharedProjects] = await Promise.all([
118
- getList(props.apiClient.listProjects),
119
- getList(props.apiClient.listSharedProjects),
120
- ]);
129
+ const ownedProjects = getList(props.apiClient.listProjects);
130
+ const sharedProjects = props.orgId
131
+ ? undefined
132
+ : getList(props.apiClient.listSharedProjects);
121
133
  const out = writer(props);
122
- out.write(ownedProjects, {
134
+ out.write(await ownedProjects, {
123
135
  fields: PROJECT_FIELDS,
124
136
  title: 'Projects',
125
137
  });
126
- out.write(sharedProjects, {
127
- fields: PROJECT_FIELDS,
128
- title: 'Shared with me',
129
- });
138
+ if (sharedProjects) {
139
+ out.write(await sharedProjects, {
140
+ fields: PROJECT_FIELDS,
141
+ title: 'Shared with you',
142
+ });
143
+ }
130
144
  out.end();
131
145
  };
132
146
  const create = async (props) => {
@@ -137,6 +151,9 @@ const create = async (props) => {
137
151
  if (props.regionId) {
138
152
  project.region_id = props.regionId;
139
153
  }
154
+ if (props.orgId) {
155
+ project.org_id = props.orgId;
156
+ }
140
157
  project.branch = {};
141
158
  if (props.database) {
142
159
  project.branch.database_name = props.database;
@@ -7,9 +7,22 @@ describe('projects', () => {
7
7
  test('list', async ({ testCliCommand }) => {
8
8
  await testCliCommand(['projects', 'list']);
9
9
  });
10
+ test('list with org id', async ({ testCliCommand }) => {
11
+ await testCliCommand(['projects', 'list', '--org-id', 'org-2']);
12
+ });
10
13
  test('create', async ({ testCliCommand }) => {
11
14
  await testCliCommand(['projects', 'create', '--name', 'test_project']);
12
15
  });
16
+ test('create with org id', async ({ testCliCommand }) => {
17
+ await testCliCommand([
18
+ 'projects',
19
+ 'create',
20
+ '--name',
21
+ 'test_project',
22
+ '--org-id',
23
+ 'org-2',
24
+ ]);
25
+ });
13
26
  test('create with database and role', async ({ testCliCommand }) => {
14
27
  await testCliCommand([
15
28
  'projects',
@@ -6,10 +6,15 @@ export const builder = (argv) => argv.usage('$0 set-context [options]').options(
6
6
  describe: 'Project ID',
7
7
  type: 'string',
8
8
  },
9
+ 'org-id': {
10
+ describe: 'Organization ID',
11
+ type: 'string',
12
+ },
9
13
  });
10
14
  export const handler = (props) => {
11
15
  const context = {
12
16
  projectId: props.projectId,
17
+ orgId: props.orgId,
13
18
  };
14
19
  updateContextFile(props.contextFile, context);
15
20
  };
@@ -28,7 +28,7 @@ const test = originalTest.extend({
28
28
  },
29
29
  });
30
30
  describe('set_context', () => {
31
- describe('should set the context', () => {
31
+ describe('should set the context to project', () => {
32
32
  test('set-context', async ({ testCliCommand, readFile }) => {
33
33
  await testCliCommand([
34
34
  'set-context',
@@ -93,4 +93,67 @@ describe('set_context', () => {
93
93
  });
94
94
  });
95
95
  });
96
+ describe('should set the context to organization', () => {
97
+ test('set-context', async ({ testCliCommand, readFile }) => {
98
+ await testCliCommand([
99
+ 'set-context',
100
+ '--org-id',
101
+ 'org-2',
102
+ '--context-file',
103
+ CONTEXT_FILE,
104
+ ]);
105
+ expect(readFile(CONTEXT_FILE)).toMatchSnapshot();
106
+ });
107
+ test('list projects selecting organization from the context', async ({ testCliCommand, writeFile, }) => {
108
+ writeFile(CONTEXT_FILE, {
109
+ orgId: 'org-2',
110
+ });
111
+ await testCliCommand([
112
+ 'projects',
113
+ 'list',
114
+ '--context-file',
115
+ CONTEXT_FILE,
116
+ ]);
117
+ });
118
+ test('list projects with explicit org id overrides context', async ({ testCliCommand, writeFile, }) => {
119
+ writeFile(CONTEXT_FILE, {
120
+ orgId: 'org-2',
121
+ });
122
+ await testCliCommand([
123
+ 'project',
124
+ 'list',
125
+ '--org-id',
126
+ 'org-3',
127
+ '--context-file',
128
+ CONTEXT_FILE,
129
+ ]);
130
+ });
131
+ test('create projects selecting organization from the context', async ({ testCliCommand, writeFile, }) => {
132
+ writeFile(CONTEXT_FILE, {
133
+ orgId: 'org-2',
134
+ });
135
+ await testCliCommand([
136
+ 'projects',
137
+ 'create',
138
+ '--name',
139
+ 'test_project',
140
+ '--context-file',
141
+ CONTEXT_FILE,
142
+ ]);
143
+ });
144
+ });
145
+ describe('can set the context to project and organization at the same time', () => {
146
+ test('set-context', async ({ testCliCommand, readFile }) => {
147
+ await testCliCommand([
148
+ 'set-context',
149
+ '--project-id',
150
+ 'test_project',
151
+ '--org-id',
152
+ 'org-2',
153
+ '--context-file',
154
+ CONTEXT_FILE,
155
+ ]);
156
+ expect(readFile(CONTEXT_FILE)).toMatchSnapshot();
157
+ });
158
+ });
96
159
  });
package/context.js CHANGED
@@ -36,6 +36,9 @@ export const enrichFromContext = (args) => {
36
36
  return;
37
37
  }
38
38
  const context = readContextFile(args.contextFile);
39
+ if (!args.orgId) {
40
+ args.orgId = context.orgId;
41
+ }
39
42
  if (!args.projectId) {
40
43
  args.projectId = context.projectId;
41
44
  }
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.35.0",
8
+ "version": "1.37.0",
9
9
  "description": "CLI tool for NeonDB Cloud management",
10
10
  "main": "index.js",
11
11
  "author": "NeonDB",