@capawesome/cli 4.1.0-dev.f5ef8ba.1771047437 → 4.2.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/commands/apps/builds/cancel.js +6 -41
  3. package/dist/commands/apps/builds/create.js +10 -55
  4. package/dist/commands/apps/builds/download.js +6 -41
  5. package/dist/commands/apps/builds/logs.js +6 -41
  6. package/dist/commands/apps/channels/create.js +6 -36
  7. package/dist/commands/apps/channels/create.test.js +5 -15
  8. package/dist/commands/apps/channels/delete.js +6 -36
  9. package/dist/commands/apps/channels/delete.test.js +6 -18
  10. package/dist/commands/apps/channels/get.js +3 -7
  11. package/dist/commands/apps/channels/list.js +6 -36
  12. package/dist/commands/apps/channels/list.test.js +4 -1
  13. package/dist/commands/apps/channels/pause.js +6 -40
  14. package/dist/commands/apps/channels/resume.js +6 -40
  15. package/dist/commands/apps/channels/update.js +6 -36
  16. package/dist/commands/apps/channels/update.test.js +14 -31
  17. package/dist/commands/apps/create.js +5 -23
  18. package/dist/commands/apps/create.test.js +13 -31
  19. package/dist/commands/apps/delete.js +6 -35
  20. package/dist/commands/apps/delete.test.js +11 -35
  21. package/dist/commands/apps/deployments/cancel.js +6 -41
  22. package/dist/commands/apps/deployments/create.js +6 -41
  23. package/dist/commands/apps/deployments/logs.js +6 -41
  24. package/dist/commands/apps/devices/delete.js +6 -36
  25. package/dist/commands/apps/devices/delete.test.js +14 -36
  26. package/dist/commands/apps/environments/create.js +6 -36
  27. package/dist/commands/apps/environments/delete.js +6 -36
  28. package/dist/commands/apps/environments/list.js +6 -36
  29. package/dist/commands/apps/environments/set.js +6 -36
  30. package/dist/commands/apps/environments/unset.js +6 -36
  31. package/dist/commands/apps/liveupdates/register.js +6 -40
  32. package/dist/commands/apps/liveupdates/register.test.js +2 -1
  33. package/dist/commands/apps/liveupdates/rollback.js +6 -41
  34. package/dist/commands/apps/liveupdates/rollout.js +6 -41
  35. package/dist/commands/apps/liveupdates/set-native-versions.js +3 -7
  36. package/dist/commands/apps/liveupdates/upload.js +6 -40
  37. package/dist/commands/apps/liveupdates/upload.test.js +2 -1
  38. package/dist/commands/organizations/create.js +3 -7
  39. package/dist/commands/organizations/create.test.js +3 -1
  40. package/dist/utils/auth.js +27 -0
  41. package/dist/utils/prompt.js +56 -11
  42. package/dist/utils/signature.js +2 -1
  43. package/package.json +6 -2
@@ -1,9 +1,7 @@
1
1
  import appChannelsService from '../../../services/app-channels.js';
2
- import appsService from '../../../services/apps.js';
3
- import authorizationService from '../../../services/authorization-service.js';
4
- import organizationsService from '../../../services/organizations.js';
2
+ import { withAuth } from '../../../utils/auth.js';
5
3
  import { isInteractive } from '../../../utils/environment.js';
6
- import { prompt } from '../../../utils/prompt.js';
4
+ import { promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
7
5
  import { defineCommand, defineOptions } from '@robingenz/zli';
8
6
  import consola from 'consola';
9
7
  import { z } from 'zod';
@@ -15,43 +13,15 @@ export default defineCommand({
15
13
  limit: z.coerce.number().optional().describe('Limit for pagination.'),
16
14
  offset: z.coerce.number().optional().describe('Offset for pagination.'),
17
15
  })),
18
- action: async (options, args) => {
16
+ action: withAuth(async (options, args) => {
19
17
  let { appId, json, limit, offset } = options;
20
- if (!authorizationService.hasAuthorizationToken()) {
21
- consola.error('You must be logged in to run this command. Please run the `login` command first.');
22
- process.exit(1);
23
- }
24
18
  if (!appId) {
25
19
  if (!isInteractive()) {
26
20
  consola.error('You must provide an app ID when running in non-interactive environment.');
27
21
  process.exit(1);
28
22
  }
29
- const organizations = await organizationsService.findAll();
30
- if (organizations.length === 0) {
31
- consola.error('You must create an organization before listing channels.');
32
- process.exit(1);
33
- }
34
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
35
- const organizationId = await prompt('Select the organization of the app for which you want to list channels.', {
36
- type: 'select',
37
- options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
38
- });
39
- if (!organizationId) {
40
- consola.error('You must select the organization of an app for which you want to list channels.');
41
- process.exit(1);
42
- }
43
- const apps = await appsService.findAll({
44
- organizationId,
45
- });
46
- if (!apps.length) {
47
- consola.error('You must create an app before listing channels.');
48
- process.exit(1);
49
- }
50
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
51
- appId = await prompt('Which app do you want to list the channels for?', {
52
- type: 'select',
53
- options: apps.map((app) => ({ label: app.name, value: app.id })),
54
- });
23
+ const organizationId = await promptOrganizationSelection();
24
+ appId = await promptAppSelection(organizationId);
55
25
  }
56
26
  const foundChannels = await appChannelsService.findAll({
57
27
  appId,
@@ -65,5 +35,5 @@ export default defineCommand({
65
35
  console.table(foundChannels);
66
36
  consola.success('Channels retrieved successfully.');
67
37
  }
68
- },
38
+ }),
69
39
  });
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_API_BASE_URL } from '../../../config/consts.js';
2
2
  import authorizationService from '../../../services/authorization-service.js';
3
+ import { prompt } from '../../../utils/prompt.js';
3
4
  import userConfig from '../../../utils/user-config.js';
4
5
  import consola from 'consola';
5
6
  import nock from 'nock';
@@ -7,10 +8,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
8
  import listChannelsCommand from './list.js';
8
9
  // Mock dependencies
9
10
  vi.mock('@/utils/user-config.js');
11
+ vi.mock('@/utils/prompt.js');
10
12
  vi.mock('@/services/authorization-service.js');
11
13
  vi.mock('consola');
12
14
  describe('apps-channels-list', () => {
13
15
  const mockUserConfig = vi.mocked(userConfig);
16
+ const mockPrompt = vi.mocked(prompt);
14
17
  const mockConsola = vi.mocked(consola);
15
18
  const mockAuthorizationService = vi.mocked(authorizationService);
16
19
  beforeEach(() => {
@@ -33,7 +36,7 @@ describe('apps-channels-list', () => {
33
36
  const options = { appId };
34
37
  mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false);
35
38
  await expect(listChannelsCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
36
- expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command. Please run the `login` command first.');
39
+ expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command. Set the `CAPAWESOME_TOKEN` environment variable or use the `--token` option.');
37
40
  });
38
41
  it('should require appId', async () => {
39
42
  const options = { appId: undefined };
@@ -1,9 +1,7 @@
1
1
  import appChannelsService from '../../../services/app-channels.js';
2
- import appsService from '../../../services/apps.js';
3
- import authorizationService from '../../../services/authorization-service.js';
4
- import organizationsService from '../../../services/organizations.js';
2
+ import { withAuth } from '../../../utils/auth.js';
5
3
  import { isInteractive } from '../../../utils/environment.js';
6
- import { prompt } from '../../../utils/prompt.js';
4
+ import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
7
5
  import { defineCommand, defineOptions } from '@robingenz/zli';
8
6
  import consola from 'consola';
9
7
  import { z } from 'zod';
@@ -13,47 +11,15 @@ export default defineCommand({
13
11
  appId: z.string().uuid({ message: 'App ID must be a UUID.' }).optional().describe('ID of the app.'),
14
12
  channel: z.string().optional().describe('Name of the channel to pause.'),
15
13
  })),
16
- action: async (options, args) => {
14
+ action: withAuth(async (options, args) => {
17
15
  let { appId, channel } = options;
18
- if (!authorizationService.hasAuthorizationToken()) {
19
- consola.error('You must be logged in to run this command. Please run the `login` command first.');
20
- process.exit(1);
21
- }
22
16
  if (!appId) {
23
17
  if (!isInteractive()) {
24
18
  consola.error('You must provide an app ID when running in non-interactive environment.');
25
19
  process.exit(1);
26
20
  }
27
- const organizations = await organizationsService.findAll();
28
- if (organizations.length === 0) {
29
- consola.error('You must create an organization before pausing a channel.');
30
- process.exit(1);
31
- }
32
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
33
- const organizationId = await prompt('Select the organization of the app.', {
34
- type: 'select',
35
- options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
36
- });
37
- if (!organizationId) {
38
- consola.error('You must select the organization of the app.');
39
- process.exit(1);
40
- }
41
- const apps = await appsService.findAll({
42
- organizationId,
43
- });
44
- if (!apps.length) {
45
- consola.error('You must create an app before pausing a channel.');
46
- process.exit(1);
47
- }
48
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
49
- appId = await prompt('Which app do you want to pause the channel in?', {
50
- type: 'select',
51
- options: apps.map((app) => ({ label: app.name, value: app.id })),
52
- });
53
- if (!appId) {
54
- consola.error('You must select an app.');
55
- process.exit(1);
56
- }
21
+ const organizationId = await promptOrganizationSelection();
22
+ appId = await promptAppSelection(organizationId);
57
23
  }
58
24
  if (!channel) {
59
25
  if (!isInteractive()) {
@@ -81,5 +47,5 @@ export default defineCommand({
81
47
  }
82
48
  await appChannelsService.pause({ appId, channelId });
83
49
  consola.success('Channel paused successfully.');
84
- },
50
+ }),
85
51
  });
@@ -1,9 +1,7 @@
1
1
  import appChannelsService from '../../../services/app-channels.js';
2
- import appsService from '../../../services/apps.js';
3
- import authorizationService from '../../../services/authorization-service.js';
4
- import organizationsService from '../../../services/organizations.js';
2
+ import { withAuth } from '../../../utils/auth.js';
5
3
  import { isInteractive } from '../../../utils/environment.js';
6
- import { prompt } from '../../../utils/prompt.js';
4
+ import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
7
5
  import { defineCommand, defineOptions } from '@robingenz/zli';
8
6
  import consola from 'consola';
9
7
  import { z } from 'zod';
@@ -13,47 +11,15 @@ export default defineCommand({
13
11
  appId: z.string().uuid({ message: 'App ID must be a UUID.' }).optional().describe('ID of the app.'),
14
12
  channel: z.string().optional().describe('Name of the channel to resume.'),
15
13
  })),
16
- action: async (options, args) => {
14
+ action: withAuth(async (options, args) => {
17
15
  let { appId, channel } = options;
18
- if (!authorizationService.hasAuthorizationToken()) {
19
- consola.error('You must be logged in to run this command. Please run the `login` command first.');
20
- process.exit(1);
21
- }
22
16
  if (!appId) {
23
17
  if (!isInteractive()) {
24
18
  consola.error('You must provide an app ID when running in non-interactive environment.');
25
19
  process.exit(1);
26
20
  }
27
- const organizations = await organizationsService.findAll();
28
- if (organizations.length === 0) {
29
- consola.error('You must create an organization before resuming a channel.');
30
- process.exit(1);
31
- }
32
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
33
- const organizationId = await prompt('Select the organization of the app.', {
34
- type: 'select',
35
- options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
36
- });
37
- if (!organizationId) {
38
- consola.error('You must select the organization of the app.');
39
- process.exit(1);
40
- }
41
- const apps = await appsService.findAll({
42
- organizationId,
43
- });
44
- if (!apps.length) {
45
- consola.error('You must create an app before resuming a channel.');
46
- process.exit(1);
47
- }
48
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
49
- appId = await prompt('Which app do you want to resume the channel in?', {
50
- type: 'select',
51
- options: apps.map((app) => ({ label: app.name, value: app.id })),
52
- });
53
- if (!appId) {
54
- consola.error('You must select an app.');
55
- process.exit(1);
56
- }
21
+ const organizationId = await promptOrganizationSelection();
22
+ appId = await promptAppSelection(organizationId);
57
23
  }
58
24
  if (!channel) {
59
25
  if (!isInteractive()) {
@@ -81,5 +47,5 @@ export default defineCommand({
81
47
  }
82
48
  await appChannelsService.resume({ appId, channelId });
83
49
  consola.success('Channel resumed successfully.');
84
- },
50
+ }),
85
51
  });
@@ -1,9 +1,7 @@
1
1
  import appChannelsService from '../../../services/app-channels.js';
2
- import appsService from '../../../services/apps.js';
3
- import authorizationService from '../../../services/authorization-service.js';
4
- import organizationsService from '../../../services/organizations.js';
2
+ import { withAuth } from '../../../utils/auth.js';
5
3
  import { isInteractive } from '../../../utils/environment.js';
6
- import { prompt } from '../../../utils/prompt.js';
4
+ import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
7
5
  import { defineCommand, defineOptions } from '@robingenz/zli';
8
6
  import consola from 'consola';
9
7
  import { z } from 'zod';
@@ -15,44 +13,16 @@ export default defineCommand({
15
13
  name: z.string().optional().describe('Name of the channel.'),
16
14
  protected: z.boolean().optional().describe('Whether to protect the channel or not.'),
17
15
  })),
18
- action: async (options, args) => {
16
+ action: withAuth(async (options, args) => {
19
17
  let { appId, channelId, name, protected: _protected } = options;
20
- if (!authorizationService.hasAuthorizationToken()) {
21
- consola.error('You must be logged in to run this command. Please run the `login` command first.');
22
- process.exit(1);
23
- }
24
18
  // Prompt app ID if not provided
25
19
  if (!appId) {
26
20
  if (!isInteractive()) {
27
21
  consola.error('You must provide an app ID when running in non-interactive environment.');
28
22
  process.exit(1);
29
23
  }
30
- const organizations = await organizationsService.findAll();
31
- if (organizations.length === 0) {
32
- consola.error('You must create an organization before updating a channel.');
33
- process.exit(1);
34
- }
35
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
36
- const organizationId = await prompt('Select the organization of the app for which you want to update a channel.', {
37
- type: 'select',
38
- options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
39
- });
40
- if (!organizationId) {
41
- consola.error('You must select the organization of an app for which you want to update a channel.');
42
- process.exit(1);
43
- }
44
- const apps = await appsService.findAll({
45
- organizationId,
46
- });
47
- if (!apps.length) {
48
- consola.error('You must create an app before updating a channel.');
49
- process.exit(1);
50
- }
51
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
52
- appId = await prompt('Which app do you want to update the channel for?', {
53
- type: 'select',
54
- options: apps.map((app) => ({ label: app.name, value: app.id })),
55
- });
24
+ const organizationId = await promptOrganizationSelection();
25
+ appId = await promptAppSelection(organizationId);
56
26
  }
57
27
  // Prompt for channel ID if not provided
58
28
  if (!channelId) {
@@ -72,5 +42,5 @@ export default defineCommand({
72
42
  protected: _protected,
73
43
  });
74
44
  consola.success('Channel updated successfully.');
75
- },
45
+ }),
76
46
  });
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_API_BASE_URL } from '../../../config/consts.js';
2
2
  import authorizationService from '../../../services/authorization-service.js';
3
- import { prompt } from '../../../utils/prompt.js';
3
+ import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
4
4
  import userConfig from '../../../utils/user-config.js';
5
5
  import consola from 'consola';
6
6
  import nock from 'nock';
@@ -17,6 +17,8 @@ vi.mock('@/utils/environment.js', () => ({
17
17
  describe('apps-channels-update', () => {
18
18
  const mockUserConfig = vi.mocked(userConfig);
19
19
  const mockPrompt = vi.mocked(prompt);
20
+ const mockPromptOrganizationSelection = vi.mocked(promptOrganizationSelection);
21
+ const mockPromptAppSelection = vi.mocked(promptAppSelection);
20
22
  const mockConsola = vi.mocked(consola);
21
23
  const mockAuthorizationService = vi.mocked(authorizationService);
22
24
  beforeEach(() => {
@@ -37,8 +39,10 @@ describe('apps-channels-update', () => {
37
39
  const channelId = 'channel-456';
38
40
  const options = { appId, channelId };
39
41
  mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false);
42
+ mockPrompt.mockResolvedValueOnce(false);
40
43
  await expect(updateChannelCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
41
- expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command. Please run the `login` command first.');
44
+ expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command.');
45
+ expect(mockConsola.error).toHaveBeenCalledWith('Please run the `login` command first.');
42
46
  });
43
47
  it('should update channel with provided options', async () => {
44
48
  const appId = 'app-123';
@@ -63,29 +67,15 @@ describe('apps-channels-update', () => {
63
67
  const appId = 'app-1';
64
68
  const channelId = 'channel-456';
65
69
  const testToken = 'test-token';
66
- const organization = { id: orgId, name: 'Org 1' };
67
- const app = { id: appId, name: 'App 1' };
68
70
  const options = { channelId };
69
- const orgsScope = nock(DEFAULT_API_BASE_URL)
70
- .get('/v1/organizations')
71
- .matchHeader('Authorization', `Bearer ${testToken}`)
72
- .reply(200, [organization]);
73
- const appsScope = nock(DEFAULT_API_BASE_URL)
74
- .get('/v1/apps')
75
- .query({ organizationId: orgId })
76
- .matchHeader('Authorization', `Bearer ${testToken}`)
77
- .reply(200, [app]);
78
71
  const updateScope = nock(DEFAULT_API_BASE_URL)
79
72
  .patch(`/v1/apps/${appId}/channels/${channelId}`)
80
73
  .matchHeader('Authorization', `Bearer ${testToken}`)
81
74
  .reply(200, { id: channelId });
82
- mockPrompt
83
- .mockResolvedValueOnce(orgId) // organization selection
84
- .mockResolvedValueOnce(appId) // app selection
85
- .mockResolvedValueOnce(channelId); // channel ID input
75
+ mockPromptOrganizationSelection.mockResolvedValueOnce(orgId);
76
+ mockPromptAppSelection.mockResolvedValueOnce(appId);
77
+ mockPrompt.mockResolvedValueOnce(channelId); // channel ID input
86
78
  await updateChannelCommand.action(options, undefined);
87
- expect(orgsScope.isDone()).toBe(true);
88
- expect(appsScope.isDone()).toBe(true);
89
79
  expect(updateScope.isDone()).toBe(true);
90
80
  expect(mockConsola.success).toHaveBeenCalledWith('Channel updated successfully.');
91
81
  });
@@ -116,24 +106,17 @@ describe('apps-channels-update', () => {
116
106
  await expect(updateChannelCommand.action(options, undefined)).rejects.toThrow();
117
107
  expect(scope.isDone()).toBe(true);
118
108
  });
119
- it('should handle error when no organizations exist', async () => {
120
- const testToken = 'test-token';
109
+ it('should exit when promptOrganizationSelection exits', async () => {
121
110
  const options = { name: 'new-name' };
122
- const scope = nock(DEFAULT_API_BASE_URL)
123
- .get('/v1/organizations')
124
- .matchHeader('Authorization', `Bearer ${testToken}`)
125
- .reply(200, []);
126
- const exitSpy = vi.spyOn(process, 'exit').mockImplementation((code) => {
127
- throw new Error(`process.exit called with code ${code}`);
111
+ mockPromptOrganizationSelection.mockImplementation(() => {
112
+ process.exit(1);
113
+ return Promise.resolve('');
128
114
  });
129
115
  try {
130
116
  await updateChannelCommand.action(options, undefined);
131
117
  }
132
118
  catch (error) {
133
- expect(error.message).toBe('process.exit called with code 1');
119
+ expect(error.message).toBe('Process exited with code 1');
134
120
  }
135
- expect(scope.isDone()).toBe(true);
136
- expect(mockConsola.error).toHaveBeenCalledWith('You must create an organization before updating a channel.');
137
- expect(exitSpy).toHaveBeenCalledWith(1);
138
121
  });
139
122
  });
@@ -1,8 +1,7 @@
1
1
  import appsService from '../../services/apps.js';
2
- import authorizationService from '../../services/authorization-service.js';
3
- import organizationsService from '../../services/organizations.js';
2
+ import { withAuth } from '../../utils/auth.js';
4
3
  import { isInteractive } from '../../utils/environment.js';
5
- import { prompt } from '../../utils/prompt.js';
4
+ import { prompt, promptOrganizationSelection } from '../../utils/prompt.js';
6
5
  import { defineCommand, defineOptions } from '@robingenz/zli';
7
6
  import consola from 'consola';
8
7
  import { z } from 'zod';
@@ -12,31 +11,14 @@ export default defineCommand({
12
11
  name: z.string().optional().describe('Name of the app.'),
13
12
  organizationId: z.string().optional().describe('ID of the organization to create the app in.'),
14
13
  })),
15
- action: async (options, args) => {
14
+ action: withAuth(async (options, args) => {
16
15
  let { name, organizationId } = options;
17
- if (!authorizationService.hasAuthorizationToken()) {
18
- consola.error('You must be logged in to run this command. Please run the `login` command first.');
19
- process.exit(1);
20
- }
21
16
  if (!organizationId) {
22
17
  if (!isInteractive()) {
23
18
  consola.error('You must provide the organization ID when running in non-interactive environment.');
24
19
  process.exit(1);
25
20
  }
26
- const organizations = await organizationsService.findAll();
27
- if (organizations.length === 0) {
28
- consola.error('You must create an organization before creating an app.');
29
- process.exit(1);
30
- }
31
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
32
- organizationId = await prompt('Which organization do you want to create the app in?', {
33
- type: 'select',
34
- options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
35
- });
36
- if (!organizationId) {
37
- consola.error('You must select an organization to create the app in.');
38
- process.exit(1);
39
- }
21
+ organizationId = await promptOrganizationSelection({ allowCreate: true });
40
22
  }
41
23
  if (!name) {
42
24
  if (!isInteractive()) {
@@ -48,5 +30,5 @@ export default defineCommand({
48
30
  const response = await appsService.create({ name, organizationId });
49
31
  consola.info(`App ID: ${response.id}`);
50
32
  consola.success('App created successfully.');
51
- },
33
+ }),
52
34
  });
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_API_BASE_URL } from '../../config/consts.js';
2
2
  import authorizationService from '../../services/authorization-service.js';
3
- import { prompt } from '../../utils/prompt.js';
3
+ import { prompt, promptOrganizationSelection } from '../../utils/prompt.js';
4
4
  import userConfig from '../../utils/user-config.js';
5
5
  import consola from 'consola';
6
6
  import nock from 'nock';
@@ -17,6 +17,7 @@ vi.mock('@/utils/environment.js', () => ({
17
17
  describe('apps-create', () => {
18
18
  const mockUserConfig = vi.mocked(userConfig);
19
19
  const mockPrompt = vi.mocked(prompt);
20
+ const mockPromptOrganizationSelection = vi.mocked(promptOrganizationSelection);
20
21
  const mockConsola = vi.mocked(consola);
21
22
  const mockAuthorizationService = vi.mocked(authorizationService);
22
23
  beforeEach(() => {
@@ -49,34 +50,19 @@ describe('apps-create', () => {
49
50
  });
50
51
  it('should prompt for organization when not provided', async () => {
51
52
  const appName = 'Test App';
52
- const orgId1 = 'org-1';
53
- const orgId2 = 'org-2';
53
+ const orgId = 'org-1';
54
54
  const appId = 'app-456';
55
55
  const testToken = 'test-token';
56
- const organizations = [
57
- { id: orgId1, name: 'Org 1' },
58
- { id: orgId2, name: 'Org 2' },
59
- ];
60
56
  const options = { name: appName };
61
- const orgsScope = nock(DEFAULT_API_BASE_URL)
62
- .get('/v1/organizations')
63
- .matchHeader('Authorization', `Bearer ${testToken}`)
64
- .reply(200, organizations);
65
57
  const createScope = nock(DEFAULT_API_BASE_URL)
66
- .post(`/v1/apps?organizationId=${orgId1}`, { name: appName })
58
+ .post(`/v1/apps?organizationId=${orgId}`, { name: appName })
67
59
  .matchHeader('Authorization', `Bearer ${testToken}`)
68
60
  .reply(201, { id: appId, name: appName });
69
- mockPrompt.mockResolvedValueOnce(orgId1);
61
+ mockPromptOrganizationSelection.mockResolvedValueOnce(orgId);
70
62
  await createAppCommand.action(options, undefined);
71
- expect(orgsScope.isDone()).toBe(true);
72
63
  expect(createScope.isDone()).toBe(true);
73
- expect(mockPrompt).toHaveBeenCalledWith('Which organization do you want to create the app in?', {
74
- type: 'select',
75
- options: [
76
- { label: 'Org 1', value: orgId1 },
77
- { label: 'Org 2', value: orgId2 },
78
- ],
79
- });
64
+ expect(mockPromptOrganizationSelection).toHaveBeenCalled();
65
+ expect(mockConsola.success).toHaveBeenCalledWith('App created successfully.');
80
66
  });
81
67
  it('should prompt for app name when not provided', async () => {
82
68
  const organizationId = 'org-123';
@@ -93,17 +79,13 @@ describe('apps-create', () => {
93
79
  expect(scope.isDone()).toBe(true);
94
80
  expect(mockPrompt).toHaveBeenCalledWith('Enter the name of the app:', { type: 'text' });
95
81
  });
96
- it('should handle error when no organizations exist', async () => {
97
- const appName = 'Test App';
98
- const testToken = 'test-token';
99
- const options = { name: appName };
100
- const scope = nock(DEFAULT_API_BASE_URL)
101
- .get('/v1/organizations')
102
- .matchHeader('Authorization', `Bearer ${testToken}`)
103
- .reply(200, []);
82
+ it('should exit when promptOrganizationSelection exits', async () => {
83
+ const options = { name: 'Test App' };
84
+ mockPromptOrganizationSelection.mockImplementation(() => {
85
+ process.exit(1);
86
+ return Promise.resolve('');
87
+ });
104
88
  await expect(createAppCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
105
- expect(scope.isDone()).toBe(true);
106
- expect(mockConsola.error).toHaveBeenCalledWith('You must create an organization before creating an app.');
107
89
  });
108
90
  it('should handle API error during creation', async () => {
109
91
  const appName = 'Test App';
@@ -1,8 +1,7 @@
1
1
  import appsService from '../../services/apps.js';
2
- import authorizationService from '../../services/authorization-service.js';
3
- import organizationsService from '../../services/organizations.js';
2
+ import { withAuth } from '../../utils/auth.js';
4
3
  import { isInteractive } from '../../utils/environment.js';
5
- import { prompt } from '../../utils/prompt.js';
4
+ import { prompt, promptAppSelection, promptOrganizationSelection } from '../../utils/prompt.js';
6
5
  import { defineCommand, defineOptions } from '@robingenz/zli';
7
6
  import consola from 'consola';
8
7
  import { z } from 'zod';
@@ -12,43 +11,15 @@ export default defineCommand({
12
11
  appId: z.string().optional().describe('ID of the app.'),
13
12
  yes: z.boolean().optional().describe('Skip confirmation prompt.'),
14
13
  }), { y: 'yes' }),
15
- action: async (options, args) => {
14
+ action: withAuth(async (options, args) => {
16
15
  let { appId } = options;
17
- if (!authorizationService.hasAuthorizationToken()) {
18
- consola.error('You must be logged in to run this command. Please run the `login` command first.');
19
- process.exit(1);
20
- }
21
16
  if (!appId) {
22
17
  if (!isInteractive()) {
23
18
  consola.error('You must provide the app ID when running in non-interactive environment.');
24
19
  process.exit(1);
25
20
  }
26
- const organizations = await organizationsService.findAll();
27
- if (organizations.length === 0) {
28
- consola.error('You must create an organization before deleting an app.');
29
- process.exit(1);
30
- }
31
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
32
- const organizationId = await prompt('Which organization do you want to delete the app from?', {
33
- type: 'select',
34
- options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
35
- });
36
- if (!organizationId) {
37
- consola.error('You must select an organization to delete the app from.');
38
- process.exit(1);
39
- }
40
- const apps = await appsService.findAll({
41
- organizationId,
42
- });
43
- if (!apps.length) {
44
- consola.error('You must create an app before deleting it.');
45
- process.exit(1);
46
- }
47
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
48
- appId = await prompt('Which app do you want to delete?', {
49
- type: 'select',
50
- options: apps.map((app) => ({ label: app.name, value: app.id })),
51
- });
21
+ const organizationId = await promptOrganizationSelection();
22
+ appId = await promptAppSelection(organizationId);
52
23
  }
53
24
  if (!options.yes && isInteractive()) {
54
25
  const confirmed = await prompt('Are you sure you want to delete this app?', {
@@ -60,5 +31,5 @@ export default defineCommand({
60
31
  }
61
32
  await appsService.delete({ id: appId });
62
33
  consola.success('App deleted successfully.');
63
- },
34
+ }),
64
35
  });