neonctl 2.9.2 → 2.10.1

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.
@@ -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': {
@@ -84,7 +87,7 @@ export const builder = (argv) => {
84
87
  type: 'string',
85
88
  },
86
89
  }), async (args) => {
87
- await create(args);
90
+ await handleMissingOrgId(args, create);
88
91
  })
89
92
  .command('update <id>', 'Update a project', (yargs) => yargs.options({
90
93
  'block-vpc-connections': {
@@ -252,3 +255,71 @@ const get = async (props) => {
252
255
  const { data } = await props.apiClient.getProject(props.id);
253
256
  writer(props).end(data.project, { fields: PROJECT_FIELDS });
254
257
  };
258
+ const handleMissingOrgId = async (args, cmd) => {
259
+ try {
260
+ await cmd(args);
261
+ }
262
+ catch (err) {
263
+ if (!isCi() && isOrgIdError(err)) {
264
+ const orgId = await selectOrg(args);
265
+ await cmd({ ...args, orgId });
266
+ }
267
+ else {
268
+ throw err;
269
+ }
270
+ }
271
+ };
272
+ const isOrgIdError = (err) => {
273
+ return (isAxiosError(err) &&
274
+ err.response?.status == 400 &&
275
+ err.response?.data?.message?.includes('org_id is required'));
276
+ };
277
+ const selectOrg = async (props) => {
278
+ const { data: { organizations }, } = await props.apiClient.getCurrentUserOrganizations();
279
+ if (!organizations?.length) {
280
+ throw new Error(`You don't belong to any organizations. Please create an organization first.`);
281
+ }
282
+ const { orgId } = await prompts({
283
+ onState: onPromptState,
284
+ type: 'select',
285
+ name: 'orgId',
286
+ message: `What organization would you like to use?`,
287
+ choices: organizations.map((org) => ({
288
+ title: `${org.name} (${org.id})`,
289
+ value: org.id,
290
+ })),
291
+ initial: 0,
292
+ });
293
+ const { save } = await prompts({
294
+ onState: onPromptState,
295
+ type: 'confirm',
296
+ name: 'save',
297
+ message: `Would you like to use this organization by default?`,
298
+ initial: true,
299
+ });
300
+ if (save) {
301
+ updateContextFile(props.contextFile, { orgId });
302
+ writer(props).text(`
303
+ The organization ID has been saved in ${props.contextFile}
304
+
305
+ If you'd like to change the default organization later, use
306
+
307
+ neonctl set-context --org-id <org_id>
308
+
309
+ Or to clear the context file and forget the default organization
310
+
311
+ neonctl set-context
312
+
313
+ `);
314
+ }
315
+ return orgId;
316
+ };
317
+ const onPromptState = (state) => {
318
+ if (state.aborted) {
319
+ // If we don't re-enable the terminal cursor before exiting
320
+ // the program, the cursor will remain hidden
321
+ process.stdout.write('\x1B[?25h');
322
+ process.stdout.write('\n');
323
+ process.exit(1);
324
+ }
325
+ };
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.10.1",
9
9
  "description": "CLI tool for NeonDB Cloud management",
10
10
  "main": "index.js",
11
11
  "author": "NeonDB",