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 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
  });
@@ -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 list(args);
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 create(args);
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',
@@ -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
- return props.apiClient
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
- .then((response) => response.data.sql ?? '');
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.9.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.6.0",
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.6.0",
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": "v8.0.0-alpha.41",
54
- "vitest": "^1.6.1"
53
+ "typescript-eslint": "8.28.0",
54
+ "vitest": "^1.6.0"
55
55
  },
56
56
  "dependencies": {
57
- "@neondatabase/api-client": "1.12.0",
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 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",
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
  };