neonctl 2.13.1 → 2.14.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/README.md +13 -13
- package/commands/branches.js +42 -0
- package/commands/branches.test.js +100 -0
- package/index.js +2 -2
- package/package.json +2 -2
- package/parameters.gen.js +20 -0
- package/test_utils/fixtures.js +4 -0
package/README.md
CHANGED
|
@@ -64,19 +64,19 @@ The Neon CLI supports autocompletion, which you can configure in a few easy step
|
|
|
64
64
|
|
|
65
65
|
## Commands
|
|
66
66
|
|
|
67
|
-
| Command
|
|
68
|
-
|
|
|
69
|
-
| [auth](https://neon.
|
|
70
|
-
| [projects](https://neon.
|
|
71
|
-
| [ip-allow](https://neon.
|
|
72
|
-
| [me](https://neon.
|
|
73
|
-
| [branches](https://neon.
|
|
74
|
-
| [databases](https://neon.
|
|
75
|
-
| [roles](https://neon.
|
|
76
|
-
| [operations](https://neon.
|
|
77
|
-
| [connection-string](https://neon.
|
|
78
|
-
| [set-context](https://neon.
|
|
79
|
-
| [completion](https://neon.
|
|
67
|
+
| Command | Subcommands | Description |
|
|
68
|
+
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------- |
|
|
69
|
+
| [auth](https://neon.com/docs/reference/cli-auth) | | Authenticate |
|
|
70
|
+
| [projects](https://neon.com/docs/reference/cli-projects) | `list`, `create`, `update`, `delete`, `get` | Manage projects |
|
|
71
|
+
| [ip-allow](https://neon.com/docs/reference/cli-ip-allow) | `list`, `add`, `remove`, `reset` | Manage IP Allow |
|
|
72
|
+
| [me](https://neon.com/docs/reference/cli-me) | | Show current user |
|
|
73
|
+
| [branches](https://neon.com/docs/reference/cli-branches) | `list`, `create`, `rename`, `add-compute`, `set-default`, `set-expiration`, `delete`, `get` | Manage branches |
|
|
74
|
+
| [databases](https://neon.com/docs/reference/cli-databases) | `list`, `create`, `delete` | Manage databases |
|
|
75
|
+
| [roles](https://neon.com/docs/reference/cli-roles) | `list`, `create`, `delete` | Manage roles |
|
|
76
|
+
| [operations](https://neon.com/docs/reference/cli-operations) | `list` | Manage operations |
|
|
77
|
+
| [connection-string](https://neon.com/docs/reference/cli-connection-string) | | Get connection string |
|
|
78
|
+
| [set-context](https://neon.com/docs/reference/cli-set-context) | | Set context for session |
|
|
79
|
+
| [completion](https://neon.com/docs/reference/cli-completion) | | Generate a completion script |
|
|
80
80
|
|
|
81
81
|
## Global options
|
|
82
82
|
|
package/commands/branches.js
CHANGED
|
@@ -15,6 +15,7 @@ export const BRANCH_FIELDS = [
|
|
|
15
15
|
'default',
|
|
16
16
|
'current_state',
|
|
17
17
|
'created_at',
|
|
18
|
+
'expires_at',
|
|
18
19
|
];
|
|
19
20
|
const BRANCH_FIELDS_RESET = [
|
|
20
21
|
'id',
|
|
@@ -85,6 +86,11 @@ export const builder = (argv) => argv
|
|
|
85
86
|
type: 'boolean',
|
|
86
87
|
default: false,
|
|
87
88
|
},
|
|
89
|
+
'expires-at': {
|
|
90
|
+
describe: '[PRIVATE-PREVIEW] Set an expiration date for the branch. Accepts a date string (e.g., 2024-12-31T23:59:59Z).',
|
|
91
|
+
type: 'string',
|
|
92
|
+
requiresArg: true,
|
|
93
|
+
},
|
|
88
94
|
}), (args) => create(args))
|
|
89
95
|
.command('reset <id|name>', 'Reset a branch', (yargs) => yargs.options({
|
|
90
96
|
parent: {
|
|
@@ -131,6 +137,21 @@ export const builder = (argv) => argv
|
|
|
131
137
|
]), (args) => restore(args))
|
|
132
138
|
.command('rename <id|name> <new-name>', 'Rename a branch', (yargs) => yargs, (args) => rename(args))
|
|
133
139
|
.command('set-default <id|name>', 'Set a branch as default', (yargs) => yargs, (args) => setDefault(args))
|
|
140
|
+
.command({
|
|
141
|
+
command: 'set-expiration <id|name>',
|
|
142
|
+
/**
|
|
143
|
+
* @info setting `describe` to `false` hides from the `help` command
|
|
144
|
+
*/
|
|
145
|
+
describe: false,
|
|
146
|
+
builder: (yargs) => yargs.options({
|
|
147
|
+
'expires-at': {
|
|
148
|
+
describe: 'Set a expiration date for the branch. If omitted, expiration will be removed. Format [RFC3339]: 2024-12-31T23:59:59Z',
|
|
149
|
+
type: 'string',
|
|
150
|
+
requiresArg: false,
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
handler: (args) => setExpiration(args),
|
|
154
|
+
})
|
|
134
155
|
.command('add-compute <id|name>', 'Add a compute to a branch', (yargs) => yargs.options({
|
|
135
156
|
type: {
|
|
136
157
|
type: 'string',
|
|
@@ -241,6 +262,9 @@ const create = async (props) => {
|
|
|
241
262
|
name: props.name,
|
|
242
263
|
...parentProps,
|
|
243
264
|
...(props.schemaOnly ? { init_source: 'schema-only' } : {}),
|
|
265
|
+
...(props['expires-at']
|
|
266
|
+
? { expires_at: new Date(props['expires-at']).toISOString() }
|
|
267
|
+
: {}),
|
|
244
268
|
},
|
|
245
269
|
endpoints: props.compute
|
|
246
270
|
? [
|
|
@@ -394,3 +418,21 @@ const restore = async (props) => {
|
|
|
394
418
|
}
|
|
395
419
|
writeInst.end();
|
|
396
420
|
};
|
|
421
|
+
const setExpiration = async (props) => {
|
|
422
|
+
// Accept either branch name or id
|
|
423
|
+
const branchId = await branchIdFromProps({
|
|
424
|
+
...props,
|
|
425
|
+
id: props.branchId,
|
|
426
|
+
});
|
|
427
|
+
const expiresAt = typeof props.expiresAt === 'string'
|
|
428
|
+
? new Date(props.expiresAt).toISOString()
|
|
429
|
+
: null;
|
|
430
|
+
const { data } = await retryOnLock(() => props.apiClient.updateProjectBranch(props.projectId, branchId, {
|
|
431
|
+
branch: {
|
|
432
|
+
expires_at: expiresAt,
|
|
433
|
+
},
|
|
434
|
+
}));
|
|
435
|
+
writer(props).end(data.branch, {
|
|
436
|
+
fields: BRANCH_FIELDS,
|
|
437
|
+
});
|
|
438
|
+
};
|
|
@@ -180,6 +180,18 @@ describe('branches', () => {
|
|
|
180
180
|
stderr: 'ERROR: Schema-only branches require a read-write compute endpoint',
|
|
181
181
|
});
|
|
182
182
|
});
|
|
183
|
+
test('create with expiration date', async ({ testCliCommand }) => {
|
|
184
|
+
await testCliCommand([
|
|
185
|
+
'branches',
|
|
186
|
+
'create',
|
|
187
|
+
'--project-id',
|
|
188
|
+
'test',
|
|
189
|
+
'--name',
|
|
190
|
+
'test_branch',
|
|
191
|
+
'--expiration-date',
|
|
192
|
+
'2021-01-01T00:00:00.000Z',
|
|
193
|
+
]);
|
|
194
|
+
});
|
|
183
195
|
/* delete */
|
|
184
196
|
test('delete by id', async ({ testCliCommand }) => {
|
|
185
197
|
await testCliCommand([
|
|
@@ -352,4 +364,92 @@ describe('branches', () => {
|
|
|
352
364
|
Available branches: self-tolsn-123456, any-branch, parent-tots, another-branch`,
|
|
353
365
|
});
|
|
354
366
|
});
|
|
367
|
+
/* set expiration */
|
|
368
|
+
test('set expiration date', async ({ testCliCommand }) => {
|
|
369
|
+
await testCliCommand([
|
|
370
|
+
'branches',
|
|
371
|
+
'set-expiration',
|
|
372
|
+
'br-sunny-branch-123456',
|
|
373
|
+
'--project-id',
|
|
374
|
+
'test',
|
|
375
|
+
'--expires-at',
|
|
376
|
+
'2024-12-31T23:59:59Z',
|
|
377
|
+
]);
|
|
378
|
+
});
|
|
379
|
+
test('remove expiration date', async ({ testCliCommand }) => {
|
|
380
|
+
await testCliCommand([
|
|
381
|
+
'branches',
|
|
382
|
+
'set-expiration',
|
|
383
|
+
'br-sunny-branch-123456',
|
|
384
|
+
'--project-id',
|
|
385
|
+
'test',
|
|
386
|
+
]);
|
|
387
|
+
});
|
|
388
|
+
test('set expiration by branch name', async ({ testCliCommand }) => {
|
|
389
|
+
await testCliCommand([
|
|
390
|
+
'branches',
|
|
391
|
+
'set-expiration',
|
|
392
|
+
'test_branch',
|
|
393
|
+
'--project-id',
|
|
394
|
+
'test',
|
|
395
|
+
'--expires-at',
|
|
396
|
+
'2024-12-31T23:59:59Z',
|
|
397
|
+
]);
|
|
398
|
+
});
|
|
399
|
+
test('set expiration fails on default branch', async ({ testCliCommand }) => {
|
|
400
|
+
await testCliCommand([
|
|
401
|
+
'branches',
|
|
402
|
+
'set-expiration',
|
|
403
|
+
'br-main-branch-123456',
|
|
404
|
+
'--project-id',
|
|
405
|
+
'test',
|
|
406
|
+
'--expires-at',
|
|
407
|
+
'2024-12-31T23:59:59Z',
|
|
408
|
+
], {
|
|
409
|
+
code: 1,
|
|
410
|
+
stderr: 'ERROR: Default branch cannot have an expiration date',
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
test('set expiration fails on default branch by name', async ({ testCliCommand, }) => {
|
|
414
|
+
await testCliCommand([
|
|
415
|
+
'branches',
|
|
416
|
+
'set-expiration',
|
|
417
|
+
'main',
|
|
418
|
+
'--project-id',
|
|
419
|
+
'test',
|
|
420
|
+
'--expires-at',
|
|
421
|
+
'2024-12-31T23:59:59Z',
|
|
422
|
+
], {
|
|
423
|
+
code: 1,
|
|
424
|
+
stderr: 'ERROR: Default branch cannot have an expiration date',
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
test('set expiration fails on protected branch', async ({ testCliCommand, }) => {
|
|
428
|
+
await testCliCommand([
|
|
429
|
+
'branches',
|
|
430
|
+
'set-expiration',
|
|
431
|
+
'br-protected-branch-123456',
|
|
432
|
+
'--project-id',
|
|
433
|
+
'test',
|
|
434
|
+
'--expires-at',
|
|
435
|
+
'2024-12-31T23:59:59Z',
|
|
436
|
+
], {
|
|
437
|
+
code: 1,
|
|
438
|
+
stderr: 'ERROR: Protected branch cannot have an expiration date',
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
test('set expiration fails on protected branch by name', async ({ testCliCommand, }) => {
|
|
442
|
+
await testCliCommand([
|
|
443
|
+
'branches',
|
|
444
|
+
'set-expiration',
|
|
445
|
+
'protected_branch',
|
|
446
|
+
'--project-id',
|
|
447
|
+
'test',
|
|
448
|
+
'--expires-at',
|
|
449
|
+
'2024-12-31T23:59:59Z',
|
|
450
|
+
], {
|
|
451
|
+
code: 1,
|
|
452
|
+
stderr: 'ERROR: Protected branch cannot have an expiration date',
|
|
453
|
+
});
|
|
454
|
+
});
|
|
355
455
|
});
|
package/index.js
CHANGED
|
@@ -141,7 +141,7 @@ builder = builder
|
|
|
141
141
|
.alias('version', 'v')
|
|
142
142
|
.completion()
|
|
143
143
|
.scriptName(basename(process.argv[1]) === 'neon' ? 'neon' : 'neonctl')
|
|
144
|
-
.epilog('For more information, visit https://neon.
|
|
144
|
+
.epilog('For more information, visit https://neon.com/docs/reference/neon-cli')
|
|
145
145
|
.wrap(null)
|
|
146
146
|
.fail(false);
|
|
147
147
|
async function handleError(msg, err) {
|
|
@@ -208,7 +208,7 @@ void (async () => {
|
|
|
208
208
|
process.exit(0);
|
|
209
209
|
}
|
|
210
210
|
await closeAnalytics();
|
|
211
|
-
|
|
211
|
+
break;
|
|
212
212
|
}
|
|
213
213
|
catch (err) {
|
|
214
214
|
attempts++;
|
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.14.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": "2.
|
|
57
|
+
"@neondatabase/api-client": "2.2.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
|
@@ -286,6 +286,11 @@ export const branchCreateRequest = {
|
|
|
286
286
|
description: "The source of initialization for the branch. Valid values are `schema-only` and `parent-data` (default).\n * `schema-only` - creates a new root branch containing only the schema. Use `parent_id` to specify the source branch. Optionally, you can provide `parent_lsn` or `parent_timestamp` to branch from a specific point in time or LSN. These fields define which branch to copy the schema from and at what point—they do not establish a parent-child relationship between the `parent_id` branch and the new schema-only branch.\n * `parent-data` - creates the branch with both schema and data from the parent.\n",
|
|
287
287
|
demandOption: false,
|
|
288
288
|
},
|
|
289
|
+
'branch.expires_at': {
|
|
290
|
+
type: "string",
|
|
291
|
+
description: "The timestamp when the branch is scheduled to expire and be automatically deleted. Must be set by the client following the [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6) format with precision up to seconds (such as 2025-06-09T18:02:16Z). Deletion is performed by a background job and may not occur exactly at the specified time.\n\nThis feature is still under development and not yet generally available.\n",
|
|
292
|
+
demandOption: false,
|
|
293
|
+
},
|
|
289
294
|
};
|
|
290
295
|
export const branchCreateRequestEndpointOptions = {
|
|
291
296
|
'type': {
|
|
@@ -316,6 +321,11 @@ export const branchUpdateRequest = {
|
|
|
316
321
|
description: undefined,
|
|
317
322
|
demandOption: false,
|
|
318
323
|
},
|
|
324
|
+
'branch.expires_at': {
|
|
325
|
+
type: "string",
|
|
326
|
+
description: "The timestamp when the branch is scheduled to expire and be automatically deleted. Must be set by the client following the [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339#section-5.6) format with precision up to seconds (such as 2025-06-09T18:02:16Z). Deletion is performed by a background job and may not occur exactly at the specified time. If this field is set to null, the expiration timestamp is removed.\n\nThis feature is still under development and not yet generally available.\n",
|
|
327
|
+
demandOption: false,
|
|
328
|
+
},
|
|
319
329
|
};
|
|
320
330
|
export const endpointCreateRequest = {
|
|
321
331
|
'endpoint.branch_id': {
|
|
@@ -365,6 +375,11 @@ export const endpointCreateRequest = {
|
|
|
365
375
|
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",
|
|
366
376
|
demandOption: false,
|
|
367
377
|
},
|
|
378
|
+
'endpoint.name': {
|
|
379
|
+
type: "string",
|
|
380
|
+
description: "Optional name of the compute endpoint\n",
|
|
381
|
+
demandOption: false,
|
|
382
|
+
},
|
|
368
383
|
};
|
|
369
384
|
export const endpointUpdateRequest = {
|
|
370
385
|
'endpoint.branch_id': {
|
|
@@ -403,6 +418,11 @@ export const endpointUpdateRequest = {
|
|
|
403
418
|
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",
|
|
404
419
|
demandOption: false,
|
|
405
420
|
},
|
|
421
|
+
'endpoint.name': {
|
|
422
|
+
type: "string",
|
|
423
|
+
description: "Optional name of the compute endpoint\n",
|
|
424
|
+
demandOption: false,
|
|
425
|
+
},
|
|
406
426
|
};
|
|
407
427
|
export const databaseCreateRequest = {
|
|
408
428
|
'database.name': {
|
package/test_utils/fixtures.js
CHANGED
|
@@ -61,6 +61,7 @@ export const test = originalTest.extend({
|
|
|
61
61
|
log.error(data.toString());
|
|
62
62
|
});
|
|
63
63
|
cp.on('error', (err) => {
|
|
64
|
+
log.error(err);
|
|
64
65
|
throw err;
|
|
65
66
|
});
|
|
66
67
|
cp.on('close', (code) => {
|
|
@@ -78,6 +79,9 @@ export const test = originalTest.extend({
|
|
|
78
79
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
79
80
|
}
|
|
80
81
|
});
|
|
82
|
+
}).catch((err) => {
|
|
83
|
+
log.error(err);
|
|
84
|
+
throw err;
|
|
81
85
|
});
|
|
82
86
|
});
|
|
83
87
|
},
|