neonctl 1.37.0 → 2.1.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/analytics.js CHANGED
@@ -3,7 +3,7 @@ import { join } from 'node:path';
3
3
  import { Analytics } from '@segment/analytics-node';
4
4
  import { isAxiosError } from 'axios';
5
5
  import { CREDENTIALS_FILE } from './config.js';
6
- import { isCi } from './env.js';
6
+ import { getGithubEnvVars, isCi } from './env.js';
7
7
  import { log } from './log.js';
8
8
  import pkg from './pkg.js';
9
9
  import { getApiClient } from './api.js';
@@ -55,6 +55,7 @@ export const analyticsMiddleware = async (args) => {
55
55
  output: args.output,
56
56
  },
57
57
  ci: isCi(),
58
+ githubEnvVars: getGithubEnvVars(process.env),
58
59
  },
59
60
  });
60
61
  };
@@ -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: {
@@ -239,17 +236,20 @@ const create = async (props) => {
239
236
  out.write(data.branch, {
240
237
  fields: BRANCH_FIELDS,
241
238
  title: 'branch',
239
+ emptyMessage: 'No branches have been found.',
242
240
  });
243
241
  if (data.endpoints?.length > 0) {
244
242
  out.write(data.endpoints, {
245
243
  fields: ['id', 'created_at'],
246
244
  title: 'endpoints',
245
+ emptyMessage: 'No endpoints have been found.',
247
246
  });
248
247
  }
249
248
  if (data.connection_uris?.length) {
250
249
  out.write(data.connection_uris, {
251
250
  fields: ['connection_uri'],
252
251
  title: 'connection_uris',
252
+ emptyMessage: 'No connection uris have been found',
253
253
  });
254
254
  }
255
255
  out.end();
@@ -352,6 +352,7 @@ const restore = async (props) => {
352
352
  const writeInst = writer(props).write(data.branch, {
353
353
  title: 'Restored branch',
354
354
  fields: ['id', 'name', 'last_reset_at'],
355
+ emptyMessage: 'No branches have been restored.',
355
356
  });
356
357
  const parentId = data.branch.parent_id;
357
358
  if (props.preserveUnderName && parentId) {
@@ -359,6 +360,7 @@ const restore = async (props) => {
359
360
  writeInst.write(data.branch, {
360
361
  title: 'Backup branch',
361
362
  fields: ['id', 'name'],
363
+ emptyMessage: 'Backup branch has not been found.',
362
364
  });
363
365
  }
364
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';
@@ -77,18 +77,6 @@ export const builder = (argv) => {
77
77
  describe: projectCreateRequest['project.name'].description,
78
78
  type: 'string',
79
79
  },
80
- 'ip-allow': {
81
- describe: projectUpdateRequest['project.settings.allowed_ips.ips']
82
- .description,
83
- type: 'string',
84
- array: true,
85
- deprecated: "Deprecated. Use 'ip-allow' command",
86
- },
87
- 'ip-primary-only': {
88
- describe: projectUpdateRequest['project.settings.allowed_ips.primary_branch_only'].description,
89
- type: 'boolean',
90
- deprecated: "Deprecated. Use 'ip-allow' command",
91
- },
92
80
  cu: {
93
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").',
94
82
  type: 'string',
@@ -126,19 +114,21 @@ const list = async (props) => {
126
114
  }
127
115
  return result;
128
116
  };
129
- const ownedProjects = getList(props.apiClient.listProjects);
117
+ const ownedProjects = await getList(props.apiClient.listProjects);
130
118
  const sharedProjects = props.orgId
131
- ? undefined
132
- : getList(props.apiClient.listSharedProjects);
119
+ ? []
120
+ : await getList(props.apiClient.listSharedProjects);
133
121
  const out = writer(props);
134
- out.write(await ownedProjects, {
122
+ out.write(ownedProjects, {
135
123
  fields: PROJECT_FIELDS,
136
124
  title: 'Projects',
125
+ emptyMessage: "You don't have any projects yet. See how to create a new project:\n> neonctl projects create --help",
137
126
  });
138
- if (sharedProjects) {
139
- out.write(await sharedProjects, {
127
+ if (!props.orgId) {
128
+ out.write(sharedProjects, {
140
129
  fields: PROJECT_FIELDS,
141
130
  title: 'Shared with you',
131
+ emptyMessage: 'No projects have been shared with you',
142
132
  });
143
133
  }
144
134
  out.end();
@@ -198,18 +188,6 @@ const update = async (props) => {
198
188
  if (props.name) {
199
189
  project.name = props.name;
200
190
  }
201
- if (props.ipAllow || props.ipPrimaryOnly != undefined) {
202
- const { data } = await props.apiClient.getProject(props.id);
203
- const existingAllowedIps = data.project.settings?.allowed_ips;
204
- project.settings = {
205
- allowed_ips: {
206
- ips: props.ipAllow ?? existingAllowedIps?.ips ?? [],
207
- primary_branch_only: props.ipPrimaryOnly ??
208
- existingAllowedIps?.primary_branch_only ??
209
- false,
210
- },
211
- };
212
- }
213
191
  if (props.cu) {
214
192
  project.default_endpoint_settings = props.cu
215
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/env.js CHANGED
@@ -4,3 +4,39 @@ export const isCi = () => {
4
4
  export const isDebug = () => {
5
5
  return Boolean(process.env.DEBUG);
6
6
  };
7
+ export const getGithubEnvVars = (env) => {
8
+ const vars = [
9
+ // github action info
10
+ 'GITHUB_ACTION',
11
+ 'GITHUB_ACTION_PATH',
12
+ 'GITHUB_ACTION_REPOSITORY',
13
+ // source github repository and actor info
14
+ 'GITHUB_REF_TYPE',
15
+ 'GITHUB_REF',
16
+ 'GITHUB_REF_NAME',
17
+ 'GITHUB_BASE_REF',
18
+ 'GITHUB_HEAD_REF',
19
+ 'GITHUB_JOB',
20
+ 'GITHUB_SHA',
21
+ 'GITHUB_REPOSITORY',
22
+ 'GITHUB_REPOSITORY_ID',
23
+ 'GITHUB_REPOSITORY_OWNER',
24
+ 'GITHUB_REPOSITORY_OWNER_ID',
25
+ 'GITHUB_TRIGGERING_ACTOR',
26
+ 'GITHUB_ACTOR',
27
+ 'GITHUB_ACTOR_ID',
28
+ 'GITHUB_EVENT_NAME',
29
+ 'GITHUB_RUN_NUMBER',
30
+ // reusable workflow info
31
+ 'GITHUB_WORKFLOW',
32
+ 'GITHUB_WORKFLOW_REF',
33
+ 'GITHUB_WORKFLOW_SHA',
34
+ ];
35
+ const map = new Map();
36
+ vars.forEach((v) => {
37
+ if (env[v]) {
38
+ map.set(v, env[v]);
39
+ }
40
+ });
41
+ return Object.fromEntries(map);
42
+ };
package/env.test.js ADDED
@@ -0,0 +1,56 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { getGithubEnvVars } from './env';
3
+ describe('getGithubEnvVars', () => {
4
+ it('success all keys', () => {
5
+ const env = {
6
+ GITHUB_ACTION: '1',
7
+ GITHUB_ACTION_PATH: '2',
8
+ GITHUB_ACTION_REPOSITORY: '3',
9
+ GITHUB_REF_TYPE: '4',
10
+ GITHUB_REF: '5',
11
+ GITHUB_REF_NAME: '6',
12
+ GITHUB_BASE_REF: '7',
13
+ GITHUB_HEAD_REF: '8',
14
+ GITHUB_JOB: '9',
15
+ GITHUB_SHA: '10',
16
+ GITHUB_REPOSITORY: '11',
17
+ GITHUB_REPOSITORY_ID: '12',
18
+ GITHUB_REPOSITORY_OWNER: '13',
19
+ GITHUB_REPOSITORY_OWNER_ID: '14',
20
+ GITHUB_TRIGGERING_ACTOR: '15',
21
+ GITHUB_ACTOR: '16',
22
+ GITHUB_ACTOR_ID: '17',
23
+ GITHUB_EVENT_NAME: '18',
24
+ GITHUB_RUN_NUMBER: '19',
25
+ GITHUB_WORKFLOW: '20',
26
+ GITHUB_WORKFLOW_REF: '21',
27
+ GITHUB_WORKFLOW_SHA: '22',
28
+ unrelated: 'unrelated',
29
+ };
30
+ const ret = {
31
+ GITHUB_ACTION: '1',
32
+ GITHUB_ACTION_PATH: '2',
33
+ GITHUB_ACTION_REPOSITORY: '3',
34
+ GITHUB_REF_TYPE: '4',
35
+ GITHUB_REF: '5',
36
+ GITHUB_REF_NAME: '6',
37
+ GITHUB_BASE_REF: '7',
38
+ GITHUB_HEAD_REF: '8',
39
+ GITHUB_JOB: '9',
40
+ GITHUB_SHA: '10',
41
+ GITHUB_REPOSITORY: '11',
42
+ GITHUB_REPOSITORY_ID: '12',
43
+ GITHUB_REPOSITORY_OWNER: '13',
44
+ GITHUB_REPOSITORY_OWNER_ID: '14',
45
+ GITHUB_TRIGGERING_ACTOR: '15',
46
+ GITHUB_ACTOR: '16',
47
+ GITHUB_ACTOR_ID: '17',
48
+ GITHUB_EVENT_NAME: '18',
49
+ GITHUB_RUN_NUMBER: '19',
50
+ GITHUB_WORKFLOW: '20',
51
+ GITHUB_WORKFLOW_REF: '21',
52
+ GITHUB_WORKFLOW_SHA: '22',
53
+ };
54
+ expect(getGithubEnvVars(env)).toEqual(ret);
55
+ });
56
+ });
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.37.0",
8
+ "version": "2.1.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: {