neonctl 2.9.2 → 2.11.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/commands/auth.js +1 -0
- package/commands/projects.js +93 -2
- package/commands/projects.test.js +12 -0
- package/commands/schema_diff.js +3 -4
- package/package.json +6 -6
- package/parameters.gen.js +46 -4
package/commands/auth.js
CHANGED
|
@@ -41,6 +41,7 @@ export const authFlow = async ({ configDir, oauthHost, clientId, apiHost, forceA
|
|
|
41
41
|
const preserveCredentials = async (path, credentials, apiClient) => {
|
|
42
42
|
const { data: { id }, } = await apiClient.getCurrentUserInfo();
|
|
43
43
|
const contents = JSON.stringify({
|
|
44
|
+
// Making the linter happy by explicitly confirming we don't care about @typescript-eslint/no-misused-spread
|
|
44
45
|
...credentials,
|
|
45
46
|
user_id: id,
|
|
46
47
|
});
|
package/commands/projects.js
CHANGED
|
@@ -4,6 +4,9 @@ import { writer } from '../writer.js';
|
|
|
4
4
|
import { psql } from '../utils/psql.js';
|
|
5
5
|
import { updateContextFile } from '../context.js';
|
|
6
6
|
import { getComputeUnits } from '../utils/compute_units.js';
|
|
7
|
+
import { isAxiosError } from 'axios';
|
|
8
|
+
import prompts from 'prompts';
|
|
9
|
+
import { isCi } from '../env.js';
|
|
7
10
|
export const PROJECT_FIELDS = [
|
|
8
11
|
'id',
|
|
9
12
|
'name',
|
|
@@ -36,7 +39,7 @@ export const builder = (argv) => {
|
|
|
36
39
|
type: 'string',
|
|
37
40
|
},
|
|
38
41
|
}), async (args) => {
|
|
39
|
-
await
|
|
42
|
+
await handleMissingOrgId(args, list);
|
|
40
43
|
})
|
|
41
44
|
.command('create', 'Create a project', (yargs) => yargs.options({
|
|
42
45
|
'block-public-connections': {
|
|
@@ -49,6 +52,10 @@ export const builder = (argv) => {
|
|
|
49
52
|
.description,
|
|
50
53
|
type: 'boolean',
|
|
51
54
|
},
|
|
55
|
+
hipaa: {
|
|
56
|
+
describe: projectCreateRequest['project.settings.hipaa'].description,
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
},
|
|
52
59
|
name: {
|
|
53
60
|
describe: projectCreateRequest['project.name'].description,
|
|
54
61
|
type: 'string',
|
|
@@ -84,7 +91,7 @@ export const builder = (argv) => {
|
|
|
84
91
|
type: 'string',
|
|
85
92
|
},
|
|
86
93
|
}), async (args) => {
|
|
87
|
-
await
|
|
94
|
+
await handleMissingOrgId(args, create);
|
|
88
95
|
})
|
|
89
96
|
.command('update <id>', 'Update a project', (yargs) => yargs.options({
|
|
90
97
|
'block-vpc-connections': {
|
|
@@ -99,6 +106,10 @@ export const builder = (argv) => {
|
|
|
99
106
|
' Use --block-public-connections=false to set the value to false.',
|
|
100
107
|
type: 'boolean',
|
|
101
108
|
},
|
|
109
|
+
hipaa: {
|
|
110
|
+
describe: projectUpdateRequest['project.settings.hipaa'].description,
|
|
111
|
+
type: 'boolean',
|
|
112
|
+
},
|
|
102
113
|
cu: {
|
|
103
114
|
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").',
|
|
104
115
|
type: 'string',
|
|
@@ -161,6 +172,12 @@ const list = async (props) => {
|
|
|
161
172
|
};
|
|
162
173
|
const create = async (props) => {
|
|
163
174
|
const project = {};
|
|
175
|
+
if (props.hipaa !== undefined) {
|
|
176
|
+
if (!project.settings) {
|
|
177
|
+
project.settings = {};
|
|
178
|
+
}
|
|
179
|
+
project.settings.hipaa = props.hipaa;
|
|
180
|
+
}
|
|
164
181
|
if (props.blockPublicConnections !== undefined) {
|
|
165
182
|
if (!project.settings) {
|
|
166
183
|
project.settings = {};
|
|
@@ -223,6 +240,12 @@ const deleteProject = async (props) => {
|
|
|
223
240
|
};
|
|
224
241
|
const update = async (props) => {
|
|
225
242
|
const project = {};
|
|
243
|
+
if (props.hipaa !== undefined) {
|
|
244
|
+
if (!project.settings) {
|
|
245
|
+
project.settings = {};
|
|
246
|
+
}
|
|
247
|
+
project.settings.hipaa = props.hipaa;
|
|
248
|
+
}
|
|
226
249
|
if (props.blockPublicConnections !== undefined) {
|
|
227
250
|
if (!project.settings) {
|
|
228
251
|
project.settings = {};
|
|
@@ -252,3 +275,71 @@ const get = async (props) => {
|
|
|
252
275
|
const { data } = await props.apiClient.getProject(props.id);
|
|
253
276
|
writer(props).end(data.project, { fields: PROJECT_FIELDS });
|
|
254
277
|
};
|
|
278
|
+
const handleMissingOrgId = async (args, cmd) => {
|
|
279
|
+
try {
|
|
280
|
+
await cmd(args);
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
if (!isCi() && isOrgIdError(err)) {
|
|
284
|
+
const orgId = await selectOrg(args);
|
|
285
|
+
await cmd({ ...args, orgId });
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
const isOrgIdError = (err) => {
|
|
293
|
+
return (isAxiosError(err) &&
|
|
294
|
+
err.response?.status == 400 &&
|
|
295
|
+
err.response?.data?.message?.includes('org_id is required'));
|
|
296
|
+
};
|
|
297
|
+
const selectOrg = async (props) => {
|
|
298
|
+
const { data: { organizations }, } = await props.apiClient.getCurrentUserOrganizations();
|
|
299
|
+
if (!organizations?.length) {
|
|
300
|
+
throw new Error(`You don't belong to any organizations. Please create an organization first.`);
|
|
301
|
+
}
|
|
302
|
+
const { orgId } = await prompts({
|
|
303
|
+
onState: onPromptState,
|
|
304
|
+
type: 'select',
|
|
305
|
+
name: 'orgId',
|
|
306
|
+
message: `What organization would you like to use?`,
|
|
307
|
+
choices: organizations.map((org) => ({
|
|
308
|
+
title: `${org.name} (${org.id})`,
|
|
309
|
+
value: org.id,
|
|
310
|
+
})),
|
|
311
|
+
initial: 0,
|
|
312
|
+
});
|
|
313
|
+
const { save } = await prompts({
|
|
314
|
+
onState: onPromptState,
|
|
315
|
+
type: 'confirm',
|
|
316
|
+
name: 'save',
|
|
317
|
+
message: `Would you like to use this organization by default?`,
|
|
318
|
+
initial: true,
|
|
319
|
+
});
|
|
320
|
+
if (save) {
|
|
321
|
+
updateContextFile(props.contextFile, { orgId });
|
|
322
|
+
writer(props).text(`
|
|
323
|
+
The organization ID has been saved in ${props.contextFile}
|
|
324
|
+
|
|
325
|
+
If you'd like to change the default organization later, use
|
|
326
|
+
|
|
327
|
+
neonctl set-context --org-id <org_id>
|
|
328
|
+
|
|
329
|
+
Or to clear the context file and forget the default organization
|
|
330
|
+
|
|
331
|
+
neonctl set-context
|
|
332
|
+
|
|
333
|
+
`);
|
|
334
|
+
}
|
|
335
|
+
return orgId;
|
|
336
|
+
};
|
|
337
|
+
const onPromptState = (state) => {
|
|
338
|
+
if (state.aborted) {
|
|
339
|
+
// If we don't re-enable the terminal cursor before exiting
|
|
340
|
+
// the program, the cursor will remain hidden
|
|
341
|
+
process.stdout.write('\x1B[?25h');
|
|
342
|
+
process.stdout.write('\n');
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
@@ -13,6 +13,15 @@ describe('projects', () => {
|
|
|
13
13
|
test('create', async ({ testCliCommand }) => {
|
|
14
14
|
await testCliCommand(['projects', 'create', '--name', 'test_project']);
|
|
15
15
|
});
|
|
16
|
+
test('create with hipaa flag', async ({ testCliCommand }) => {
|
|
17
|
+
await testCliCommand([
|
|
18
|
+
'projects',
|
|
19
|
+
'create',
|
|
20
|
+
'--name',
|
|
21
|
+
'test_project',
|
|
22
|
+
'--hipaa',
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
16
25
|
test('create with org id', async ({ testCliCommand }) => {
|
|
17
26
|
await testCliCommand([
|
|
18
27
|
'projects',
|
|
@@ -102,6 +111,9 @@ describe('projects', () => {
|
|
|
102
111
|
'test_project_new_name',
|
|
103
112
|
]);
|
|
104
113
|
});
|
|
114
|
+
test('update hipaa flag', async ({ testCliCommand }) => {
|
|
115
|
+
await testCliCommand(['projects', 'update', 'test', '--hipaa']);
|
|
116
|
+
});
|
|
105
117
|
test('update project with default fixed size CU', async ({ testCliCommand, }) => {
|
|
106
118
|
await testCliCommand([
|
|
107
119
|
'projects',
|
package/commands/schema_diff.js
CHANGED
|
@@ -59,14 +59,13 @@ const createSchemaDiff = async (baseBranch, pointInTime, database, props) => {
|
|
|
59
59
|
};
|
|
60
60
|
const fetchSchema = async (pointInTime, database, props) => {
|
|
61
61
|
try {
|
|
62
|
-
|
|
63
|
-
.getProjectBranchSchema({
|
|
62
|
+
const response = await props.apiClient.getProjectBranchSchema({
|
|
64
63
|
projectId: props.projectId,
|
|
65
64
|
branchId: pointInTime.branchId,
|
|
66
65
|
db_name: database.name,
|
|
67
66
|
...pointInTimeParams(pointInTime),
|
|
68
|
-
})
|
|
69
|
-
|
|
67
|
+
});
|
|
68
|
+
return response.data.sql ?? '';
|
|
70
69
|
}
|
|
71
70
|
catch (error) {
|
|
72
71
|
if (isAxiosError(error)) {
|
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.11.0",
|
|
9
9
|
"description": "CLI tool for NeonDB Cloud management",
|
|
10
10
|
"main": "index.js",
|
|
11
11
|
"author": "NeonDB",
|
|
@@ -22,7 +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.
|
|
25
|
+
"@eslint/js": "^9.23.0",
|
|
26
26
|
"@rollup/plugin-commonjs": "^25.0.2",
|
|
27
27
|
"@rollup/plugin-json": "^6.0.0",
|
|
28
28
|
"@rollup/plugin-node-resolve": "^15.1.0",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@types/which": "^3.0.0",
|
|
40
40
|
"@types/yargs": "^17.0.24",
|
|
41
41
|
"emocks": "^3.0.1",
|
|
42
|
-
"eslint": "^9.
|
|
42
|
+
"eslint": "^9.23.0",
|
|
43
43
|
"express": "^4.18.2",
|
|
44
44
|
"husky": "^8.0.3",
|
|
45
45
|
"lint-staged": "^13.0.3",
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
"semantic-release": "^23.0.8",
|
|
51
51
|
"strip-ansi": "^7.1.0",
|
|
52
52
|
"typescript": "^4.7.4",
|
|
53
|
-
"typescript-eslint": "
|
|
54
|
-
"vitest": "^1.6.
|
|
53
|
+
"typescript-eslint": "8.28.0",
|
|
54
|
+
"vitest": "^1.6.0"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@neondatabase/api-client": "1.
|
|
57
|
+
"@neondatabase/api-client": "2.1.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
|
@@ -22,7 +22,7 @@ export const projectCreateRequest = {
|
|
|
22
22
|
},
|
|
23
23
|
'project.settings.quota.logical_size_bytes': {
|
|
24
24
|
type: "number",
|
|
25
|
-
description: "Limit on the logical size of every project's branch.\n",
|
|
25
|
+
description: "Limit on the logical size of every project's branch.\n\nIf a branch exceeds its `logical_size_bytes` quota, computes can still be started,\nbut write operations will fail—allowing data to be deleted to free up space.\nComputes on other branches are not affected.\n\nSetting `logical_size_bytes` overrides any lower value set by the `neon.max_cluster_size` Postgres setting.\n",
|
|
26
26
|
demandOption: false,
|
|
27
27
|
},
|
|
28
28
|
'project.settings.allowed_ips.ips': {
|
|
@@ -65,9 +65,30 @@ export const projectCreateRequest = {
|
|
|
65
65
|
description: "When set, connections using VPC endpoints are disallowed.\nThis parameter is under active development and its semantics may change in the future.\n",
|
|
66
66
|
demandOption: false,
|
|
67
67
|
},
|
|
68
|
+
'project.settings.audit_log_level': {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: undefined,
|
|
71
|
+
demandOption: false,
|
|
72
|
+
choices: ["base", "extended", "full"],
|
|
73
|
+
},
|
|
74
|
+
'project.settings.hipaa': {
|
|
75
|
+
type: "boolean",
|
|
76
|
+
description: undefined,
|
|
77
|
+
demandOption: false,
|
|
78
|
+
},
|
|
79
|
+
'project.settings.preload_libraries.use_defaults': {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
description: undefined,
|
|
82
|
+
demandOption: false,
|
|
83
|
+
},
|
|
84
|
+
'project.settings.preload_libraries.enabled_libraries': {
|
|
85
|
+
type: "array",
|
|
86
|
+
description: undefined,
|
|
87
|
+
demandOption: false,
|
|
88
|
+
},
|
|
68
89
|
'project.name': {
|
|
69
90
|
type: "string",
|
|
70
|
-
description: "The project name",
|
|
91
|
+
description: "The project name. If not specified, the name will be identical to the generated project ID",
|
|
71
92
|
demandOption: false,
|
|
72
93
|
},
|
|
73
94
|
'project.branch.name': {
|
|
@@ -144,7 +165,7 @@ export const projectUpdateRequest = {
|
|
|
144
165
|
},
|
|
145
166
|
'project.settings.quota.logical_size_bytes': {
|
|
146
167
|
type: "number",
|
|
147
|
-
description: "Limit on the logical size of every project's branch.\n",
|
|
168
|
+
description: "Limit on the logical size of every project's branch.\n\nIf a branch exceeds its `logical_size_bytes` quota, computes can still be started,\nbut write operations will fail—allowing data to be deleted to free up space.\nComputes on other branches are not affected.\n\nSetting `logical_size_bytes` overrides any lower value set by the `neon.max_cluster_size` Postgres setting.\n",
|
|
148
169
|
demandOption: false,
|
|
149
170
|
},
|
|
150
171
|
'project.settings.allowed_ips.ips': {
|
|
@@ -187,6 +208,27 @@ export const projectUpdateRequest = {
|
|
|
187
208
|
description: "When set, connections using VPC endpoints are disallowed.\nThis parameter is under active development and its semantics may change in the future.\n",
|
|
188
209
|
demandOption: false,
|
|
189
210
|
},
|
|
211
|
+
'project.settings.audit_log_level': {
|
|
212
|
+
type: "string",
|
|
213
|
+
description: undefined,
|
|
214
|
+
demandOption: false,
|
|
215
|
+
choices: ["base", "extended", "full"],
|
|
216
|
+
},
|
|
217
|
+
'project.settings.hipaa': {
|
|
218
|
+
type: "boolean",
|
|
219
|
+
description: undefined,
|
|
220
|
+
demandOption: false,
|
|
221
|
+
},
|
|
222
|
+
'project.settings.preload_libraries.use_defaults': {
|
|
223
|
+
type: "boolean",
|
|
224
|
+
description: undefined,
|
|
225
|
+
demandOption: false,
|
|
226
|
+
},
|
|
227
|
+
'project.settings.preload_libraries.enabled_libraries': {
|
|
228
|
+
type: "array",
|
|
229
|
+
description: undefined,
|
|
230
|
+
demandOption: false,
|
|
231
|
+
},
|
|
190
232
|
'project.name': {
|
|
191
233
|
type: "string",
|
|
192
234
|
description: "The project name",
|
|
@@ -241,7 +283,7 @@ export const branchCreateRequest = {
|
|
|
241
283
|
},
|
|
242
284
|
'branch.init_source': {
|
|
243
285
|
type: "string",
|
|
244
|
-
description: "The
|
|
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",
|
|
245
287
|
demandOption: false,
|
|
246
288
|
},
|
|
247
289
|
};
|