neonctl 1.32.1 → 1.34.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.
@@ -1,27 +1,17 @@
1
- import { afterAll, describe, expect, test } from 'vitest';
1
+ import { describe, expect } from 'vitest';
2
2
  import { readFileSync, rmSync } from 'node:fs';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
- import { testCliCommand } from '../test_utils/test_cli_command.js';
6
- const CONTEXT_FILE = join(tmpdir(), `neon_project_create_ctx_${Date.now()}`);
5
+ import { test } from '../test_utils/fixtures';
7
6
  describe('projects', () => {
8
- testCliCommand({
9
- name: 'list',
10
- args: ['projects', 'list'],
11
- expected: {
12
- snapshot: true,
13
- },
14
- });
15
- testCliCommand({
16
- name: 'create',
17
- args: ['projects', 'create', '--name', 'test_project'],
18
- expected: {
19
- snapshot: true,
20
- },
21
- });
22
- testCliCommand({
23
- name: 'create with database and role',
24
- args: [
7
+ test('list', async ({ testCliCommand }) => {
8
+ await testCliCommand(['projects', 'list']);
9
+ });
10
+ test('create', async ({ testCliCommand }) => {
11
+ await testCliCommand(['projects', 'create', '--name', 'test_project']);
12
+ });
13
+ test('create with database and role', async ({ testCliCommand }) => {
14
+ await testCliCommand([
25
15
  'projects',
26
16
  'create',
27
17
  '--name',
@@ -30,21 +20,19 @@ describe('projects', () => {
30
20
  'test_db',
31
21
  '--role',
32
22
  'test_role',
33
- ],
34
- expected: {
35
- snapshot: true,
36
- },
37
- });
38
- testCliCommand({
39
- name: 'create and connect with psql',
40
- args: ['projects', 'create', '--name', 'test_project', '--psql'],
41
- expected: {
42
- snapshot: true,
43
- },
44
- });
45
- testCliCommand({
46
- name: 'create and connect with psql and psql args',
47
- args: [
23
+ ]);
24
+ });
25
+ test('create and connect with psql', async ({ testCliCommand }) => {
26
+ await testCliCommand([
27
+ 'projects',
28
+ 'create',
29
+ '--name',
30
+ 'test_project',
31
+ '--psql',
32
+ ]);
33
+ });
34
+ test('create and connect with psql and psql args', async ({ testCliCommand, }) => {
35
+ await testCliCommand([
48
36
  'projects',
49
37
  'create',
50
38
  '--name',
@@ -53,14 +41,11 @@ describe('projects', () => {
53
41
  '--',
54
42
  '-c',
55
43
  'SELECT 1',
56
- ],
57
- expected: {
58
- snapshot: true,
59
- },
60
- });
61
- testCliCommand({
62
- name: 'create project with setting the context',
63
- args: [
44
+ ]);
45
+ });
46
+ test('create project with setting the context', async ({ testCliCommand, }) => {
47
+ const CONTEXT_FILE = join(tmpdir(), `neon_project_create_ctx_${Date.now()}`);
48
+ await testCliCommand([
64
49
  'projects',
65
50
  'create',
66
51
  '--name',
@@ -68,62 +53,44 @@ describe('projects', () => {
68
53
  '--context-file',
69
54
  CONTEXT_FILE,
70
55
  '--set-context',
71
- ],
72
- expected: {
73
- snapshot: true,
74
- },
75
- });
76
- testCliCommand({
77
- name: 'create project with default fixed size CU',
78
- args: [
56
+ ]);
57
+ expect(readFileSync(CONTEXT_FILE, 'utf-8')).toContain('new-project-123456');
58
+ rmSync(CONTEXT_FILE);
59
+ });
60
+ test('create project with default fixed size CU', async ({ testCliCommand, }) => {
61
+ await testCliCommand([
79
62
  'projects',
80
63
  'create',
81
64
  '--name',
82
65
  'test_project_with_fixed_cu',
83
66
  '--cu',
84
67
  '2',
85
- ],
86
- expected: {
87
- snapshot: true,
88
- },
89
- });
90
- testCliCommand({
91
- name: 'create project with default autoscaled CU',
92
- args: [
68
+ ]);
69
+ });
70
+ test('create project with default autoscaled CU', async ({ testCliCommand, }) => {
71
+ await testCliCommand([
93
72
  'projects',
94
73
  'create',
95
74
  '--name',
96
75
  'test_project_with_autoscaling',
97
76
  '--cu',
98
77
  '0.5-2',
99
- ],
100
- expected: {
101
- snapshot: true,
102
- },
78
+ ]);
103
79
  });
104
- afterAll(() => {
105
- rmSync(CONTEXT_FILE);
80
+ test('delete', async ({ testCliCommand }) => {
81
+ await testCliCommand(['projects', 'delete', 'test']);
106
82
  });
107
- test('context file should exist and contain the project id', () => {
108
- expect(readFileSync(CONTEXT_FILE, 'utf-8')).toContain('new-project-123456');
83
+ test('update name', async ({ testCliCommand }) => {
84
+ await testCliCommand([
85
+ 'projects',
86
+ 'update',
87
+ 'test',
88
+ '--name',
89
+ 'test_project_new_name',
90
+ ]);
109
91
  });
110
- testCliCommand({
111
- name: 'delete',
112
- args: ['projects', 'delete', 'test'],
113
- expected: {
114
- snapshot: true,
115
- },
116
- });
117
- testCliCommand({
118
- name: 'update name',
119
- args: ['projects', 'update', 'test', '--name', 'test_project_new_name'],
120
- expected: {
121
- snapshot: true,
122
- },
123
- });
124
- testCliCommand({
125
- name: 'update ip allow',
126
- args: [
92
+ test('update ip allow', async ({ testCliCommand }) => {
93
+ await testCliCommand([
127
94
  'projects',
128
95
  'update',
129
96
  'test',
@@ -131,50 +98,39 @@ describe('projects', () => {
131
98
  '127.0.0.1',
132
99
  '192.168.1.2/22',
133
100
  '--ip-primary-only',
134
- ],
135
- expected: {
136
- snapshot: true,
137
- },
138
- });
139
- testCliCommand({
140
- name: 'update ip allow primary only flag',
141
- args: ['projects', 'update', 'test', '--ip-primary-only', 'false'],
142
- expected: {
143
- snapshot: true,
144
- },
145
- });
146
- testCliCommand({
147
- name: 'update ip allow remove',
148
- args: ['projects', 'update', 'test', '--ip-allow'],
149
- expected: {
150
- snapshot: true,
151
- },
152
- });
153
- testCliCommand({
154
- name: 'update project with default fixed size CU',
155
- args: ['projects', 'update', 'test_project_with_fixed_cu', '--cu', '2'],
156
- expected: {
157
- snapshot: true,
158
- },
159
- });
160
- testCliCommand({
161
- name: 'update project with default autoscaled CU',
162
- args: [
101
+ ]);
102
+ });
103
+ test('update ip allow primary only flag', async ({ testCliCommand }) => {
104
+ await testCliCommand([
105
+ 'projects',
106
+ 'update',
107
+ 'test',
108
+ '--ip-primary-only',
109
+ 'false',
110
+ ]);
111
+ });
112
+ test('update ip allow remove', async ({ testCliCommand }) => {
113
+ await testCliCommand(['projects', 'update', 'test', '--ip-allow']);
114
+ });
115
+ test('update project with default fixed size CU', async ({ testCliCommand, }) => {
116
+ await testCliCommand([
117
+ 'projects',
118
+ 'update',
119
+ 'test_project_with_fixed_cu',
120
+ '--cu',
121
+ '2',
122
+ ]);
123
+ });
124
+ test('update project with default autoscaled CU', async ({ testCliCommand, }) => {
125
+ await testCliCommand([
163
126
  'projects',
164
127
  'update',
165
128
  'test_project_with_autoscaling',
166
129
  '--cu',
167
130
  '0.5-2',
168
- ],
169
- expected: {
170
- snapshot: true,
171
- },
172
- });
173
- testCliCommand({
174
- name: 'get',
175
- args: ['projects', 'get', 'test'],
176
- expected: {
177
- snapshot: true,
178
- },
131
+ ]);
132
+ });
133
+ test('get', async ({ testCliCommand }) => {
134
+ await testCliCommand(['projects', 'get', 'test']);
179
135
  });
180
136
  });
package/commands/roles.js CHANGED
@@ -18,15 +18,15 @@ export const builder = (argv) => argv
18
18
  },
19
19
  })
20
20
  .middleware(fillSingleProject)
21
- .command('list', 'List roles', (yargs) => yargs, async (args) => await list(args))
21
+ .command('list', 'List roles', (yargs) => yargs, (args) => list(args))
22
22
  .command('create', 'Create a role', (yargs) => yargs.options({
23
23
  name: {
24
24
  describe: 'Role name',
25
25
  type: 'string',
26
26
  demandOption: true,
27
27
  },
28
- }), async (args) => await create(args))
29
- .command('delete <role>', 'Delete a role', (yargs) => yargs, async (args) => await deleteRole(args));
28
+ }), (args) => create(args))
29
+ .command('delete <role>', 'Delete a role', (yargs) => yargs, (args) => deleteRole(args));
30
30
  export const handler = (args) => {
31
31
  return args;
32
32
  };
@@ -1,16 +1,18 @@
1
1
  import { describe } from 'vitest';
2
- import { testCliCommand } from '../test_utils/test_cli_command.js';
2
+ import { test } from '../test_utils/fixtures';
3
3
  describe('roles', () => {
4
- testCliCommand({
5
- name: 'list',
6
- args: ['roles', 'list', '--project-id', 'test', '--branch', 'test_branch'],
7
- expected: {
8
- snapshot: true,
9
- },
4
+ test('list', async ({ testCliCommand }) => {
5
+ await testCliCommand([
6
+ 'roles',
7
+ 'list',
8
+ '--project-id',
9
+ 'test',
10
+ '--branch',
11
+ 'test_branch',
12
+ ]);
10
13
  });
11
- testCliCommand({
12
- name: 'create',
13
- args: [
14
+ test('create', async ({ testCliCommand }) => {
15
+ await testCliCommand([
14
16
  'roles',
15
17
  'create',
16
18
  '--project-id',
@@ -19,14 +21,10 @@ describe('roles', () => {
19
21
  'test_branch',
20
22
  '--name',
21
23
  'test_role',
22
- ],
23
- expected: {
24
- snapshot: true,
25
- },
24
+ ]);
26
25
  });
27
- testCliCommand({
28
- name: 'delete',
29
- args: [
26
+ test('delete', async ({ testCliCommand }) => {
27
+ await testCliCommand([
30
28
  'roles',
31
29
  'delete',
32
30
  'test_role',
@@ -34,9 +32,6 @@ describe('roles', () => {
34
32
  'test',
35
33
  '--branch',
36
34
  'test_branch',
37
- ],
38
- expected: {
39
- snapshot: true,
40
- },
35
+ ]);
41
36
  });
42
37
  });
@@ -7,7 +7,7 @@ export const builder = (argv) => argv.usage('$0 set-context [options]').options(
7
7
  type: 'string',
8
8
  },
9
9
  });
10
- export const handler = async (props) => {
10
+ export const handler = (props) => {
11
11
  const context = {
12
12
  projectId: props.projectId,
13
13
  };
@@ -1,93 +1,96 @@
1
1
  import { tmpdir } from 'node:os';
2
2
  import { join } from 'node:path';
3
- import { rmSync, writeFileSync } from 'node:fs';
4
- import { afterAll, describe } from 'vitest';
5
- import { testCliCommand } from '../test_utils/test_cli_command';
3
+ import { rmSync, writeFileSync, readFileSync } from 'node:fs';
4
+ import { describe, expect } from 'vitest';
5
+ import { test as originalTest } from '../test_utils/fixtures';
6
6
  const CONTEXT_FILE = join(tmpdir(), `neon_${Date.now()}`);
7
+ const test = originalTest.extend({
8
+ // eslint-disable-next-line no-empty-pattern
9
+ cleanupFile: async ({}, use) => {
10
+ let writtenFilename;
11
+ await use((name) => (writtenFilename = name));
12
+ if (writtenFilename) {
13
+ rmSync(writtenFilename);
14
+ }
15
+ },
16
+ writeFile: async ({ cleanupFile }, use) => {
17
+ await use((name, content) => {
18
+ writeFileSync(name, JSON.stringify(content));
19
+ cleanupFile(name);
20
+ });
21
+ },
22
+ readFile: async ({ cleanupFile }, use) => {
23
+ await use((name) => {
24
+ const content = readFileSync(name, 'utf-8');
25
+ cleanupFile(name);
26
+ return content;
27
+ });
28
+ },
29
+ });
7
30
  describe('set_context', () => {
8
- afterAll(() => {
9
- rmSync(CONTEXT_FILE);
10
- });
11
31
  describe('should set the context', () => {
12
- testCliCommand({
13
- name: 'set-context',
14
- args: [
32
+ test('set-context', async ({ testCliCommand, readFile }) => {
33
+ await testCliCommand([
15
34
  'set-context',
16
35
  '--project-id',
17
36
  'test',
18
37
  '--context-file',
19
38
  CONTEXT_FILE,
20
- ],
39
+ ]);
40
+ expect(readFile(CONTEXT_FILE)).toMatchSnapshot();
21
41
  });
22
- testCliCommand({
23
- name: 'list branches selecting project from the context',
24
- args: ['branches', 'list', '--context-file', CONTEXT_FILE],
25
- expected: {
26
- snapshot: true,
27
- },
42
+ test('list branches selecting project from the context', async ({ testCliCommand, writeFile, }) => {
43
+ writeFile(CONTEXT_FILE, {
44
+ projectId: 'test',
45
+ });
46
+ await testCliCommand([
47
+ 'branches',
48
+ 'list',
49
+ '--context-file',
50
+ CONTEXT_FILE,
51
+ ]);
28
52
  });
29
53
  const overrideContextFile = join(tmpdir(), `neon_override_ctx_${Date.now()}`);
30
- testCliCommand({
31
- name: 'get project id overrides context set project',
32
- before: async () => {
33
- writeFileSync(overrideContextFile, JSON.stringify({
34
- projectId: 'new-project id',
35
- }));
36
- },
37
- after: async () => {
38
- rmSync(overrideContextFile);
39
- },
40
- args: [
54
+ test('get project id overrides context set project', async ({ testCliCommand, writeFile, }) => {
55
+ writeFile(overrideContextFile, {
56
+ projectId: 'new-project id',
57
+ });
58
+ await testCliCommand([
41
59
  'project',
42
60
  'get',
43
61
  'project-id-123',
44
62
  '--context-file',
45
63
  overrideContextFile,
46
- ],
47
- expected: {
48
- snapshot: true,
49
- },
64
+ ]);
50
65
  });
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
+ test('set the branchId and projectId is from context', async ({ testCliCommand, writeFile, }) => {
67
+ writeFile(overrideContextFile, {
68
+ projectId: 'test',
69
+ branchId: 'test_branch',
70
+ });
71
+ await testCliCommand([
72
+ 'databases',
73
+ 'list',
74
+ '--context-file',
75
+ overrideContextFile,
76
+ ]);
66
77
  });
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: [
78
+ test('set the branchId and projectId is from context', async ({ testCliCommand, writeFile, }) => {
79
+ writeFile(overrideContextFile, {
80
+ projectId: 'project-id-123',
81
+ branchId: 'test_branch',
82
+ });
83
+ await testCliCommand([
79
84
  'databases',
80
85
  'list',
81
86
  '--project-id',
82
87
  'test',
83
88
  '--context-file',
84
89
  overrideContextFile,
85
- ],
86
- expected: {
90
+ ], {
87
91
  code: 1,
88
92
  stderr: 'ERROR: Not Found',
89
- snapshot: true,
90
- },
93
+ });
91
94
  });
92
95
  });
93
96
  });
package/config.js CHANGED
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync } from 'node:fs';
4
4
  import { isCi } from './env.js';
5
5
  export const CREDENTIALS_FILE = 'credentials.json';
6
6
  export const defaultDir = join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'neonctl');
7
- export const ensureConfigDir = async ({ 'config-dir': configDir, 'force-auth': forceAuth, }) => {
7
+ export const ensureConfigDir = ({ 'config-dir': configDir, 'force-auth': forceAuth, }) => {
8
8
  if (!existsSync(configDir) && (!isCi() || forceAuth)) {
9
9
  mkdirSync(configDir, { recursive: true });
10
10
  }
package/context.js CHANGED
@@ -15,7 +15,7 @@ export const currentContextFile = () => {
15
15
  accessSync(resolve(currentDir, file));
16
16
  return wrapWithContextFile(currentDir);
17
17
  }
18
- catch (e) {
18
+ catch {
19
19
  // ignore
20
20
  }
21
21
  }
@@ -27,7 +27,7 @@ export const readContextFile = (file) => {
27
27
  try {
28
28
  return JSON.parse(readFileSync(file, 'utf-8'));
29
29
  }
30
- catch (e) {
30
+ catch {
31
31
  return {};
32
32
  }
33
33
  };
package/help.js CHANGED
@@ -22,7 +22,7 @@ const formatHelp = (help) => {
22
22
  width: 0,
23
23
  });
24
24
  commandsBlock.forEach((line) => {
25
- if (line.match(/^\s{3,}/)) {
25
+ if (/^\s{3,}/.exec(line)) {
26
26
  ui.div({
27
27
  text: '',
28
28
  width: SPACE_WIDTH,
package/index.js CHANGED
@@ -108,7 +108,9 @@ builder = builder
108
108
  default: true,
109
109
  },
110
110
  })
111
- .middleware((args) => fillInArgs(args), true)
111
+ .middleware((args) => {
112
+ fillInArgs(args);
113
+ }, true)
112
114
  .help(false)
113
115
  .group('help', 'Global options:')
114
116
  .option('help', {
@@ -168,10 +170,12 @@ builder = builder
168
170
  log.error(msg || err?.message);
169
171
  }
170
172
  await closeAnalytics();
171
- err?.stack && log.debug('Stack: %s', err.stack);
173
+ if (err?.stack) {
174
+ log.debug('Stack: %s', err.stack);
175
+ }
172
176
  process.exit(1);
173
177
  });
174
- (async () => {
178
+ void (async () => {
175
179
  try {
176
180
  const args = await builder.argv;
177
181
  if (args._.length === 0 || args.help) {
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.32.1",
8
+ "version": "1.34.0",
9
9
  "description": "CLI tool for NeonDB Cloud management",
10
10
  "main": "index.js",
11
11
  "author": "NeonDB",
@@ -22,6 +22,7 @@
22
22
  "@apidevtools/swagger-parser": "^10.1.0",
23
23
  "@commitlint/cli": "^17.6.5",
24
24
  "@commitlint/config-conventional": "^17.6.5",
25
+ "@eslint/js": "^9.6.0",
25
26
  "@rollup/plugin-commonjs": "^25.0.2",
26
27
  "@rollup/plugin-json": "^6.0.0",
27
28
  "@rollup/plugin-node-resolve": "^15.1.0",
@@ -30,16 +31,15 @@
30
31
  "@types/bun": "^1.1.4",
31
32
  "@types/cli-table": "^0.3.0",
32
33
  "@types/diff": "^5.2.1",
34
+ "@types/eslint__js": "^8.42.3",
33
35
  "@types/express": "^4.17.17",
34
36
  "@types/node": "^18.7.13",
35
37
  "@types/prompts": "2.4.9",
36
38
  "@types/validate-npm-package-name": "4.0.2",
37
39
  "@types/which": "^3.0.0",
38
40
  "@types/yargs": "^17.0.24",
39
- "@typescript-eslint/eslint-plugin": "^5.34.0",
40
- "@typescript-eslint/parser": "^5.34.0",
41
41
  "emocks": "^3.0.1",
42
- "eslint": "^8.22.0",
42
+ "eslint": "^9.6.0",
43
43
  "express": "^4.18.2",
44
44
  "husky": "^8.0.3",
45
45
  "lint-staged": "^13.0.3",
@@ -50,15 +50,17 @@
50
50
  "semantic-release": "^23.0.8",
51
51
  "strip-ansi": "^7.1.0",
52
52
  "typescript": "^4.7.4",
53
+ "typescript-eslint": "v8.0.0-alpha.41",
53
54
  "vitest": "^1.6.0"
54
55
  },
55
56
  "dependencies": {
56
- "@neondatabase/api-client": "1.9.0",
57
+ "@neondatabase/api-client": "^1.10.0",
57
58
  "@segment/analytics-node": "^1.0.0-beta.26",
58
59
  "axios": "^1.4.0",
59
60
  "axios-debug-log": "^1.0.0",
60
61
  "chalk": "^5.2.0",
61
62
  "cli-table": "^0.3.11",
63
+ "crypto-random-string": "^5.0.0",
62
64
  "diff": "^5.2.0",
63
65
  "inquirer": "^9.2.6",
64
66
  "open": "^10.1.0",
@@ -91,8 +93,8 @@
91
93
  "scripts": {
92
94
  "watch": "tsc --watch",
93
95
  "typecheck": "tsc --noEmit",
94
- "lint": "npm run typecheck && eslint src --ext .ts && prettier --check .",
95
- "lint:fix": "npm run typecheck && eslint src --ext .ts --fix && prettier --w .",
96
+ "lint": "npm run typecheck && eslint src && prettier --check .",
97
+ "lint:fix": "npm run typecheck && eslint src --fix && prettier --w .",
96
98
  "build": "bun generateParams && bun clean && tsc && cp src/*.html package*.json README.md ./dist",
97
99
  "clean": "rm -rf dist",
98
100
  "generateParams": "bun generateOptionsFromSpec.ts",