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 +10 -9
- package/commands/branches.js +20 -0
- package/commands/branches.test.js +44 -0
- package/commands/projects.js +32 -4
- package/commands/roles.js +5 -0
- package/index.js +6 -1
- package/package.json +2 -2
- package/parameters.gen.js +14 -10
- package/utils/enrichers.js +6 -2
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
|
+
});
|
package/commands/branches.js
CHANGED
|
@@ -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([
|
package/commands/projects.js
CHANGED
|
@@ -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
|
-
|
|
77
|
-
describe:
|
|
78
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
242
|
+
'branch.init_source': {
|
|
243
243
|
type: "string",
|
|
244
|
-
description: "The type
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
};
|
package/utils/enrichers.js
CHANGED
|
@@ -15,7 +15,7 @@ export const branchIdResolve = async ({ branch, apiClient, projectId, }) => {
|
|
|
15
15
|
}
|
|
16
16
|
return branchData.id;
|
|
17
17
|
};
|
|
18
|
-
|
|
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
|
}
|