neonctl 2.5.0 → 2.7.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
@@ -48,15 +48,7 @@ export const analyticsMiddleware = async (args) => {
48
48
  client.track({
49
49
  userId: userId ?? 'anonymous',
50
50
  event: 'CLI Started',
51
- properties: {
52
- version: pkg.version,
53
- command: args._.join(' '),
54
- flags: {
55
- output: args.output,
56
- },
57
- ci: isCi(),
58
- githubEnvVars: getGithubEnvVars(process.env),
59
- },
51
+ properties: getAnalyticsEventProperties(args),
60
52
  context: {
61
53
  direct: true,
62
54
  },
@@ -97,3 +89,12 @@ export const trackEvent = (event, properties) => {
97
89
  });
98
90
  log.debug('Sent CLI event: %s', event);
99
91
  };
92
+ export const getAnalyticsEventProperties = (args) => ({
93
+ version: pkg.version,
94
+ command: args._.join(' '),
95
+ flags: {
96
+ output: args.output,
97
+ },
98
+ ci: isCi(),
99
+ githubEnvVars: getGithubEnvVars(process.env),
100
+ });
@@ -36,6 +36,10 @@ export const builder = (argv) => argv
36
36
  },
37
37
  })
38
38
  .middleware(fillSingleProject)
39
+ .middleware((args) => {
40
+ // Provide alias for analytics
41
+ args.branchId ?? (args.branchId = args.id);
42
+ })
39
43
  .command('list', 'List branches', (yargs) => yargs, (args) => list(args))
40
44
  .command('create', 'Create a branch', (yargs) => yargs.options({
41
45
  name: branchCreateRequest['branch.name'],
@@ -76,6 +80,11 @@ export const builder = (argv) => argv
76
80
  hidden: true,
77
81
  default: '{}',
78
82
  },
83
+ 'schema-only': {
84
+ describe: 'Create a schema-only branch. Requires exactly one read-write compute.',
85
+ type: 'boolean',
86
+ default: false,
87
+ },
79
88
  }), (args) => create(args))
80
89
  .command('reset <id|name>', 'Reset a branch', (yargs) => yargs.options({
81
90
  parent: {
@@ -94,6 +103,7 @@ export const builder = (argv) => argv
94
103
  .middleware((args) => {
95
104
  args.id = args.targetId;
96
105
  args.pointInTime = args['source@(timestamp'];
106
+ args.branchId = args.id; // for analytics
97
107
  })
98
108
  .usage('$0 branches restore <target-id|name> <source>[@(timestamp|lsn)]')
99
109
  .options({
@@ -213,10 +223,20 @@ const create = async (props) => {
213
223
  }
214
224
  return { parent_id: branch.id };
215
225
  })();
226
+ // Validate schema-only branch requirements
227
+ if (props.schemaOnly) {
228
+ if (!props.compute) {
229
+ throw new Error('Schema-only branches require a compute endpoint');
230
+ }
231
+ if (props.type !== EndpointType.ReadWrite) {
232
+ throw new Error('Schema-only branches require a read-write compute endpoint');
233
+ }
234
+ }
216
235
  const { data } = await retryOnLock(() => props.apiClient.createProjectBranch(props.projectId, {
217
236
  branch: {
218
237
  name: props.name,
219
238
  ...parentProps,
239
+ ...(props.schemaOnly ? { init_source: 'schema-only' } : {}),
220
240
  },
221
241
  endpoints: props.compute
222
242
  ? [
@@ -136,6 +136,50 @@ describe('branches', () => {
136
136
  '0.5-2',
137
137
  ]);
138
138
  });
139
+ test('create schema-only branch', async ({ testCliCommand }) => {
140
+ await testCliCommand([
141
+ 'branches',
142
+ 'create',
143
+ '--project-id',
144
+ 'test',
145
+ '--name',
146
+ 'test_branch',
147
+ '--schema-only',
148
+ ]);
149
+ });
150
+ test('create schema-only branch fails without compute', async ({ testCliCommand, }) => {
151
+ await testCliCommand([
152
+ 'branches',
153
+ 'create',
154
+ '--project-id',
155
+ 'test',
156
+ '--name',
157
+ 'test_branch',
158
+ '--schema-only',
159
+ '--no-compute',
160
+ ], {
161
+ mockDir: 'main',
162
+ code: 1,
163
+ stderr: 'ERROR: Schema-only branches require a compute endpoint',
164
+ });
165
+ });
166
+ test('create schema-only branch fails with read-only compute', async ({ testCliCommand, }) => {
167
+ await testCliCommand([
168
+ 'branches',
169
+ 'create',
170
+ '--project-id',
171
+ 'test',
172
+ '--name',
173
+ 'test_branch',
174
+ '--schema-only',
175
+ '--type',
176
+ 'read_only',
177
+ ], {
178
+ mockDir: 'main',
179
+ code: 1,
180
+ stderr: 'ERROR: Schema-only branches require a read-write compute endpoint',
181
+ });
182
+ });
139
183
  /* delete */
140
184
  test('delete by id', async ({ testCliCommand }) => {
141
185
  await testCliCommand([
@@ -1,5 +1,5 @@
1
1
  import { log } from '../log.js';
2
- import { projectCreateRequest } from '../parameters.gen.js';
2
+ import { projectCreateRequest, projectUpdateRequest, } 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';
@@ -26,6 +26,10 @@ export const aliases = ['project'];
26
26
  export const builder = (argv) => {
27
27
  return argv
28
28
  .usage('$0 projects <sub-command> [options]')
29
+ .middleware((args) => {
30
+ // Provide alias for analytics
31
+ args.projectId = args.id;
32
+ })
29
33
  .command('list', 'List projects', (yargs) => yargs.options({
30
34
  'org-id': {
31
35
  describe: 'List projects of a given organization',
@@ -73,14 +77,26 @@ export const builder = (argv) => {
73
77
  await create(args);
74
78
  })
75
79
  .command('update <id>', 'Update a project', (yargs) => yargs.options({
76
- name: {
77
- describe: projectCreateRequest['project.name'].description,
78
- type: 'string',
80
+ 'block-vpc-connections': {
81
+ describe: projectUpdateRequest['project.settings.block_vpc_connections']
82
+ .description +
83
+ ' Use --block-vpc-connections=false to set the value to false.',
84
+ type: 'boolean',
85
+ },
86
+ 'block-public-connections': {
87
+ describe: projectUpdateRequest['project.settings.block_public_connections']
88
+ .description +
89
+ ' Use --block-public-connections=false to set the value to false.',
90
+ type: 'boolean',
79
91
  },
80
92
  cu: {
81
93
  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").',
82
94
  type: 'string',
83
95
  },
96
+ name: {
97
+ describe: projectUpdateRequest['project.name'].description,
98
+ type: 'string',
99
+ },
84
100
  }), async (args) => {
85
101
  await update(args);
86
102
  })
@@ -185,6 +201,18 @@ const deleteProject = async (props) => {
185
201
  };
186
202
  const update = async (props) => {
187
203
  const project = {};
204
+ if (props.blockPublicConnections !== undefined) {
205
+ if (!project.settings) {
206
+ project.settings = {};
207
+ }
208
+ project.settings.block_public_connections = props.blockPublicConnections;
209
+ }
210
+ if (props.blockVpcConnections !== undefined) {
211
+ if (!project.settings) {
212
+ project.settings = {};
213
+ }
214
+ project.settings.block_vpc_connections = props.blockVpcConnections;
215
+ }
188
216
  if (props.name) {
189
217
  project.name = props.name;
190
218
  }
package/commands/roles.js CHANGED
@@ -25,6 +25,10 @@ export const builder = (argv) => argv
25
25
  type: 'string',
26
26
  demandOption: true,
27
27
  },
28
+ 'no-login': {
29
+ describe: 'Create a passwordless role that cannot login',
30
+ boolean: true,
31
+ },
28
32
  }), (args) => create(args))
29
33
  .command('delete <role>', 'Delete a role', (yargs) => yargs, (args) => deleteRole(args));
30
34
  export const handler = (args) => {
@@ -42,6 +46,7 @@ export const create = async (props) => {
42
46
  const { data } = await retryOnLock(() => props.apiClient.createProjectBranchRole(props.projectId, branchId, {
43
47
  role: {
44
48
  name: props.name,
49
+ no_login: props['no-login'],
45
50
  },
46
51
  }));
47
52
  writer(props).end(data.role, {
package/index.js CHANGED
@@ -20,7 +20,7 @@ import { defaultClientID } from './auth.js';
20
20
  import { fillInArgs } from './utils/middlewares.js';
21
21
  import pkg from './pkg.js';
22
22
  import commands from './commands/index.js';
23
- import { analyticsMiddleware, closeAnalytics, sendError } from './analytics.js';
23
+ import { analyticsMiddleware, closeAnalytics, getAnalyticsEventProperties, sendError, trackEvent, } from './analytics.js';
24
24
  import { isAxiosError } from 'axios';
25
25
  import { matchErrorCode } from './errors.js';
26
26
  import { showHelp } from './help.js';
@@ -180,6 +180,11 @@ builder = builder
180
180
  void (async () => {
181
181
  try {
182
182
  const args = await builder.argv;
183
+ trackEvent('cli_command_success', {
184
+ ...getAnalyticsEventProperties(args),
185
+ projectId: args.projectId,
186
+ branchId: args.branchId,
187
+ });
183
188
  if (args._.length === 0 || args.help) {
184
189
  await showHelp(builder);
185
190
  process.exit(0);
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": "2.5.0",
8
+ "version": "2.7.0",
9
9
  "description": "CLI tool for NeonDB Cloud management",
10
10
  "main": "index.js",
11
11
  "author": "NeonDB",
@@ -54,7 +54,7 @@
54
54
  "vitest": "^1.6.0"
55
55
  },
56
56
  "dependencies": {
57
- "@neondatabase/api-client": "1.11.2",
57
+ "@neondatabase/api-client": "1.12.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
@@ -97,7 +97,7 @@ export const projectCreateRequest = {
97
97
  },
98
98
  'project.default_endpoint_settings.suspend_timeout_seconds': {
99
99
  type: "number",
100
- description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the global default.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Auto-suspend configuration](https://neon.tech/docs/manage/endpoints#auto-suspend-configuration).\n",
100
+ description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.tech/docs/manage/endpoints#scale-to-zero-configuration).\n",
101
101
  demandOption: false,
102
102
  },
103
103
  'project.pg_version': {
@@ -194,7 +194,7 @@ export const projectUpdateRequest = {
194
194
  },
195
195
  'project.default_endpoint_settings.suspend_timeout_seconds': {
196
196
  type: "number",
197
- description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the global default.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Auto-suspend configuration](https://neon.tech/docs/manage/endpoints#auto-suspend-configuration).\n",
197
+ description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.tech/docs/manage/endpoints#scale-to-zero-configuration).\n",
198
198
  demandOption: false,
199
199
  },
200
200
  'project.history_retention_seconds': {
@@ -239,11 +239,10 @@ export const branchCreateRequest = {
239
239
  description: "Whether to create the branch as archived\n",
240
240
  demandOption: false,
241
241
  },
242
- 'branch.schema_initialization_type': {
242
+ 'branch.init_source': {
243
243
  type: "string",
244
- description: "The type of schema initialization. Defines how the schema is initialized, currently only empty is supported. This parameter is under\nactive development and may change its semantics in the future.\n",
244
+ description: "The initialization source type for the branch. Valid values are `schema-only` and `parent-data`.\nThis parameter is under active development and may change its semantics in the future.\n",
245
245
  demandOption: false,
246
- choices: ["empty"],
247
246
  },
248
247
  };
249
248
  export const branchCreateRequestEndpointOptions = {
@@ -260,7 +259,7 @@ export const branchCreateRequestEndpointOptions = {
260
259
  },
261
260
  'suspend_timeout_seconds': {
262
261
  type: "number",
263
- description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the global default.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Auto-suspend configuration](https://neon.tech/docs/manage/endpoints#auto-suspend-configuration).\n",
262
+ description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.tech/docs/manage/endpoints#scale-to-zero-configuration).\n",
264
263
  demandOption: false,
265
264
  },
266
265
  };
@@ -321,14 +320,14 @@ export const endpointCreateRequest = {
321
320
  },
322
321
  'endpoint.suspend_timeout_seconds': {
323
322
  type: "number",
324
- description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the global default.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Auto-suspend configuration](https://neon.tech/docs/manage/endpoints#auto-suspend-configuration).\n",
323
+ description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.tech/docs/manage/endpoints#scale-to-zero-configuration).\n",
325
324
  demandOption: false,
326
325
  },
327
326
  };
328
327
  export const endpointUpdateRequest = {
329
328
  'endpoint.branch_id': {
330
329
  type: "string",
331
- description: "DEPRECATED: This field will be removed in a future release.\nThe destination branch ID. The destination branch must not have an exsiting read-write endpoint.\n",
330
+ description: "DEPRECATED: This field will be removed in a future release.\nThe destination branch ID. The destination branch must not have an existing read-write endpoint.\n",
332
331
  demandOption: false,
333
332
  },
334
333
  'endpoint.provisioner': {
@@ -359,14 +358,14 @@ export const endpointUpdateRequest = {
359
358
  },
360
359
  'endpoint.suspend_timeout_seconds': {
361
360
  type: "number",
362
- description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the global default.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Auto-suspend configuration](https://neon.tech/docs/manage/endpoints#auto-suspend-configuration).\n",
361
+ description: "Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the default value.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe minimum value is `60` seconds (1 minute).\nThe maximum value is `604800` seconds (1 week). For more information, see\n[Scale to zero configuration](https://neon.tech/docs/manage/endpoints#scale-to-zero-configuration).\n",
363
362
  demandOption: false,
364
363
  },
365
364
  };
366
365
  export const databaseCreateRequest = {
367
366
  'database.name': {
368
367
  type: "string",
369
- description: "The name of the datbase\n",
368
+ description: "The name of the database\n",
370
369
  demandOption: true,
371
370
  },
372
371
  'database.owner_name': {
@@ -381,4 +380,9 @@ export const roleCreateRequest = {
381
380
  description: "The role name. Cannot exceed 63 bytes in length.\n",
382
381
  demandOption: true,
383
382
  },
383
+ 'role.no_login': {
384
+ type: "boolean",
385
+ description: "Whether to create a role that cannot login.\n",
386
+ demandOption: false,
387
+ },
384
388
  };
@@ -15,7 +15,7 @@ export const branchIdResolve = async ({ branch, apiClient, projectId, }) => {
15
15
  }
16
16
  return branchData.id;
17
17
  };
18
- export const branchIdFromProps = async (props) => {
18
+ const getBranchIdFromProps = async (props) => {
19
19
  const branch = 'branch' in props && typeof props.branch === 'string'
20
20
  ? props.branch
21
21
  : props.id;
@@ -35,11 +35,15 @@ export const branchIdFromProps = async (props) => {
35
35
  }
36
36
  throw new Error('No default branch found');
37
37
  };
38
+ export const branchIdFromProps = async (props) => {
39
+ props.branchId = await getBranchIdFromProps(props);
40
+ return props.branchId;
41
+ };
38
42
  export const fillSingleProject = async (props) => {
39
43
  if (props.projectId) {
40
44
  return props;
41
45
  }
42
- const { data } = await props.apiClient.listProjects({});
46
+ const { data } = await props.apiClient.listProjects({ limit: 2 });
43
47
  if (data.projects.length === 0) {
44
48
  throw new Error('No projects found');
45
49
  }