@nexical/cli 0.1.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/.github/workflows/deploy.yml +34 -0
- package/LICENSE +201 -0
- package/README.md +183 -0
- package/dist/chunk-FDJVHO4O.js +41 -0
- package/dist/chunk-FDJVHO4O.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/src/commands/admin/create-user.d.ts +15 -0
- package/dist/src/commands/admin/create-user.js +49 -0
- package/dist/src/commands/admin/create-user.js.map +1 -0
- package/dist/src/commands/branch/create.d.ts +19 -0
- package/dist/src/commands/branch/create.js +59 -0
- package/dist/src/commands/branch/create.js.map +1 -0
- package/dist/src/commands/branch/delete.d.ts +15 -0
- package/dist/src/commands/branch/delete.js +50 -0
- package/dist/src/commands/branch/delete.js.map +1 -0
- package/dist/src/commands/branch/get.d.ts +15 -0
- package/dist/src/commands/branch/get.js +53 -0
- package/dist/src/commands/branch/get.js.map +1 -0
- package/dist/src/commands/branch/list.d.ts +15 -0
- package/dist/src/commands/branch/list.js +51 -0
- package/dist/src/commands/branch/list.js.map +1 -0
- package/dist/src/commands/job/get.d.ts +15 -0
- package/dist/src/commands/job/get.js +62 -0
- package/dist/src/commands/job/get.js.map +1 -0
- package/dist/src/commands/job/list.d.ts +15 -0
- package/dist/src/commands/job/list.js +57 -0
- package/dist/src/commands/job/list.js.map +1 -0
- package/dist/src/commands/job/logs.d.ts +15 -0
- package/dist/src/commands/job/logs.js +67 -0
- package/dist/src/commands/job/logs.js.map +1 -0
- package/dist/src/commands/job/trigger.d.ts +19 -0
- package/dist/src/commands/job/trigger.js +74 -0
- package/dist/src/commands/job/trigger.js.map +1 -0
- package/dist/src/commands/login.d.ts +8 -0
- package/dist/src/commands/login.js +31 -0
- package/dist/src/commands/login.js.map +1 -0
- package/dist/src/commands/project/create.d.ts +24 -0
- package/dist/src/commands/project/create.js +63 -0
- package/dist/src/commands/project/create.js.map +1 -0
- package/dist/src/commands/project/delete.d.ts +20 -0
- package/dist/src/commands/project/delete.js +58 -0
- package/dist/src/commands/project/delete.js.map +1 -0
- package/dist/src/commands/project/get.d.ts +15 -0
- package/dist/src/commands/project/get.js +49 -0
- package/dist/src/commands/project/get.js.map +1 -0
- package/dist/src/commands/project/list.d.ts +15 -0
- package/dist/src/commands/project/list.js +45 -0
- package/dist/src/commands/project/list.js.map +1 -0
- package/dist/src/commands/project/update.d.ts +19 -0
- package/dist/src/commands/project/update.js +66 -0
- package/dist/src/commands/project/update.js.map +1 -0
- package/dist/src/commands/team/create.d.ts +19 -0
- package/dist/src/commands/team/create.js +45 -0
- package/dist/src/commands/team/create.js.map +1 -0
- package/dist/src/commands/team/delete.d.ts +20 -0
- package/dist/src/commands/team/delete.js +52 -0
- package/dist/src/commands/team/delete.js.map +1 -0
- package/dist/src/commands/team/get.d.ts +15 -0
- package/dist/src/commands/team/get.js +42 -0
- package/dist/src/commands/team/get.js.map +1 -0
- package/dist/src/commands/team/list.d.ts +8 -0
- package/dist/src/commands/team/list.js +30 -0
- package/dist/src/commands/team/list.js.map +1 -0
- package/dist/src/commands/team/member/invite.d.ts +20 -0
- package/dist/src/commands/team/member/invite.js +54 -0
- package/dist/src/commands/team/member/invite.js.map +1 -0
- package/dist/src/commands/team/member/remove.d.ts +15 -0
- package/dist/src/commands/team/member/remove.js +43 -0
- package/dist/src/commands/team/member/remove.js.map +1 -0
- package/dist/src/commands/team/update.d.ts +19 -0
- package/dist/src/commands/team/update.js +55 -0
- package/dist/src/commands/team/update.js.map +1 -0
- package/dist/src/commands/token/generate.d.ts +19 -0
- package/dist/src/commands/token/generate.js +48 -0
- package/dist/src/commands/token/generate.js.map +1 -0
- package/dist/src/commands/token/list.d.ts +8 -0
- package/dist/src/commands/token/list.js +31 -0
- package/dist/src/commands/token/list.js.map +1 -0
- package/dist/src/commands/token/revoke.d.ts +15 -0
- package/dist/src/commands/token/revoke.js +38 -0
- package/dist/src/commands/token/revoke.js.map +1 -0
- package/dist/src/commands/whoami.d.ts +8 -0
- package/dist/src/commands/whoami.js +26 -0
- package/dist/src/commands/whoami.js.map +1 -0
- package/dist/src/utils/nexical-client.d.ts +10 -0
- package/dist/src/utils/nexical-client.js +12 -0
- package/dist/src/utils/nexical-client.js.map +1 -0
- package/index.ts +14 -0
- package/package.json +32 -0
- package/src/commands/admin/create-user.ts +46 -0
- package/src/commands/branch/create.ts +57 -0
- package/src/commands/branch/delete.ts +47 -0
- package/src/commands/branch/get.ts +50 -0
- package/src/commands/branch/list.ts +50 -0
- package/src/commands/job/get.ts +59 -0
- package/src/commands/job/list.ts +56 -0
- package/src/commands/job/logs.ts +67 -0
- package/src/commands/job/trigger.ts +73 -0
- package/src/commands/login.ts +31 -0
- package/src/commands/project/create.ts +61 -0
- package/src/commands/project/delete.ts +56 -0
- package/src/commands/project/get.ts +46 -0
- package/src/commands/project/list.ts +44 -0
- package/src/commands/project/update.ts +63 -0
- package/src/commands/team/create.ts +43 -0
- package/src/commands/team/delete.ts +50 -0
- package/src/commands/team/get.ts +39 -0
- package/src/commands/team/list.ts +26 -0
- package/src/commands/team/member/invite.ts +56 -0
- package/src/commands/team/member/remove.ts +40 -0
- package/src/commands/team/update.ts +53 -0
- package/src/commands/token/generate.ts +45 -0
- package/src/commands/token/list.ts +27 -0
- package/src/commands/token/revoke.ts +35 -0
- package/src/commands/whoami.ts +21 -0
- package/src/utils/nexical-client.ts +40 -0
- package/test/e2e/.gitkeep +0 -0
- package/test/integration/commands/admin/create-user.test.ts +51 -0
- package/test/integration/commands/branch/create.test.ts +51 -0
- package/test/integration/commands/branch/delete.test.ts +43 -0
- package/test/integration/commands/branch/get.test.ts +49 -0
- package/test/integration/commands/branch/list.test.ts +47 -0
- package/test/integration/commands/job/get.test.ts +54 -0
- package/test/integration/commands/job/list.test.ts +47 -0
- package/test/integration/commands/job/logs.test.ts +47 -0
- package/test/integration/commands/job/trigger.test.ts +57 -0
- package/test/integration/commands/login.test.ts +62 -0
- package/test/integration/commands/project/create.test.ts +53 -0
- package/test/integration/commands/project/delete.test.ts +43 -0
- package/test/integration/commands/project/get.test.ts +51 -0
- package/test/integration/commands/project/list.test.ts +47 -0
- package/test/integration/commands/project/update.test.ts +53 -0
- package/test/integration/commands/team/create.test.ts +53 -0
- package/test/integration/commands/team/delete.test.ts +43 -0
- package/test/integration/commands/team/get.test.ts +50 -0
- package/test/integration/commands/team/list.test.ts +47 -0
- package/test/integration/commands/team/member/invite.test.ts +46 -0
- package/test/integration/commands/team/member/remove.test.ts +43 -0
- package/test/integration/commands/team/update.test.ts +50 -0
- package/test/integration/commands/token/generate.test.ts +51 -0
- package/test/integration/commands/token/list.test.ts +47 -0
- package/test/integration/commands/token/revoke.test.ts +43 -0
- package/test/integration/commands/whoami.test.ts +49 -0
- package/test/unit/commands/admin/create-user.test.ts +51 -0
- package/test/unit/commands/branch/create.test.ts +57 -0
- package/test/unit/commands/branch/delete.test.ts +49 -0
- package/test/unit/commands/branch/get.test.ts +67 -0
- package/test/unit/commands/branch/list.test.ts +62 -0
- package/test/unit/commands/job/get.test.ts +76 -0
- package/test/unit/commands/job/list.test.ts +62 -0
- package/test/unit/commands/job/logs.test.ts +60 -0
- package/test/unit/commands/job/trigger.test.ts +75 -0
- package/test/unit/commands/login.test.ts +64 -0
- package/test/unit/commands/project/create.test.ts +64 -0
- package/test/unit/commands/project/delete.test.ts +72 -0
- package/test/unit/commands/project/get.test.ts +73 -0
- package/test/unit/commands/project/list.test.ts +62 -0
- package/test/unit/commands/project/update.test.ts +58 -0
- package/test/unit/commands/team/create.test.ts +68 -0
- package/test/unit/commands/team/delete.test.ts +71 -0
- package/test/unit/commands/team/get.test.ts +70 -0
- package/test/unit/commands/team/list.test.ts +56 -0
- package/test/unit/commands/team/member/invite.test.ts +52 -0
- package/test/unit/commands/team/member/remove.test.ts +49 -0
- package/test/unit/commands/team/update.test.ts +63 -0
- package/test/unit/commands/token/generate.test.ts +65 -0
- package/test/unit/commands/token/list.test.ts +58 -0
- package/test/unit/commands/token/revoke.test.ts +49 -0
- package/test/unit/commands/whoami.test.ts +49 -0
- package/test/unit/utils/nexical-client.test.ts +98 -0
- package/test/utils/integration-helpers.ts +22 -0
- package/tsconfig.json +26 -0
- package/tsup.config.ts +18 -0
- package/vitest.config.ts +15 -0
- package/vitest.e2e.config.ts +10 -0
- package/vitest.integration.config.ts +22 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import BranchesDeleteCommand from '../../../../src/commands/branch/delete.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('BranchesDeleteCommand', () => {
|
|
9
|
+
let command: BranchesDeleteCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
branches: {
|
|
17
|
+
delete: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new BranchesDeleteCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'success').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should delete branch successfully', async () => {
|
|
28
|
+
mockClient.branches.delete.mockResolvedValue({});
|
|
29
|
+
|
|
30
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3' });
|
|
31
|
+
|
|
32
|
+
expect(mockClient.branches.delete).toHaveBeenCalledWith(1, 2, 3);
|
|
33
|
+
expect(command.success).toHaveBeenCalledWith('Branch 3 deleted.');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should validate IDs', async () => {
|
|
37
|
+
await command.run({ teamId: '1', projectId: 'invalid', branchId: '3' });
|
|
38
|
+
expect(command.error).toHaveBeenCalledWith('IDs must be numbers.');
|
|
39
|
+
expect(mockClient.branches.delete).not.toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle deletion failure', async () => {
|
|
43
|
+
mockClient.branches.delete.mockRejectedValue(new Error('Not found'));
|
|
44
|
+
|
|
45
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3' });
|
|
46
|
+
|
|
47
|
+
expect(command.error).toHaveBeenCalledWith('Failed to delete branch: Not found');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import BranchesGetCommand from '../../../../src/commands/branch/get.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('BranchesGetCommand', () => {
|
|
9
|
+
let command: BranchesGetCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
branches: {
|
|
17
|
+
get: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new BranchesGetCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'info').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should get branch details successfully', async () => {
|
|
28
|
+
mockClient.branches.get.mockResolvedValue({
|
|
29
|
+
id: '100',
|
|
30
|
+
name: 'main',
|
|
31
|
+
previewUrl: 'http://test.com'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3' });
|
|
35
|
+
|
|
36
|
+
expect(mockClient.branches.get).toHaveBeenCalledWith(1, 2, 3);
|
|
37
|
+
expect(command.info).toHaveBeenCalledWith('Branch Details:');
|
|
38
|
+
expect(command.info).toHaveBeenCalledWith(' ID: 100');
|
|
39
|
+
expect(command.info).toHaveBeenCalledWith(' Name: main');
|
|
40
|
+
expect(command.info).toHaveBeenCalledWith(' Preview: http://test.com');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should show N/A for missing preview', async () => {
|
|
44
|
+
mockClient.branches.get.mockResolvedValue({
|
|
45
|
+
id: '100',
|
|
46
|
+
name: 'main'
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3' });
|
|
50
|
+
|
|
51
|
+
expect(command.info).toHaveBeenCalledWith(' Preview: N/A');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should validate IDs', async () => {
|
|
55
|
+
await command.run({ teamId: '1', projectId: 'invalid', branchId: '3' });
|
|
56
|
+
expect(command.error).toHaveBeenCalledWith('IDs must be numbers.');
|
|
57
|
+
expect(mockClient.branches.get).not.toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should handle get failure', async () => {
|
|
61
|
+
mockClient.branches.get.mockRejectedValue(new Error('Not found'));
|
|
62
|
+
|
|
63
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3' });
|
|
64
|
+
|
|
65
|
+
expect(command.error).toHaveBeenCalledWith('Failed to get branch: Not found');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import BranchesListCommand from '../../../../src/commands/branch/list.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('BranchesListCommand', () => {
|
|
9
|
+
let command: BranchesListCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
branches: {
|
|
17
|
+
list: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new BranchesListCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'info').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should list branches successfully', async () => {
|
|
28
|
+
mockClient.branches.list.mockResolvedValue([
|
|
29
|
+
{ id: '100', name: 'main' },
|
|
30
|
+
{ id: '101', name: 'dev' }
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
await command.run({ teamId: '1', projectId: '2' });
|
|
34
|
+
|
|
35
|
+
expect(mockClient.branches.list).toHaveBeenCalledWith(1, 2);
|
|
36
|
+
expect(command.info).toHaveBeenCalledWith('Branches for Project 2:');
|
|
37
|
+
expect(command.info).toHaveBeenCalledWith('- main (ID: 100)');
|
|
38
|
+
expect(command.info).toHaveBeenCalledWith('- dev (ID: 101)');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should handle empty list', async () => {
|
|
42
|
+
mockClient.branches.list.mockResolvedValue([]);
|
|
43
|
+
|
|
44
|
+
await command.run({ teamId: '1', projectId: '2' });
|
|
45
|
+
|
|
46
|
+
expect(command.info).toHaveBeenCalledWith('No branches found.');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should validate IDs', async () => {
|
|
50
|
+
await command.run({ teamId: 'invalid', projectId: '2' });
|
|
51
|
+
expect(command.error).toHaveBeenCalledWith('IDs must be numbers.');
|
|
52
|
+
expect(mockClient.branches.list).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should handle list failure', async () => {
|
|
56
|
+
mockClient.branches.list.mockRejectedValue(new Error('Network error'));
|
|
57
|
+
|
|
58
|
+
await command.run({ teamId: '1', projectId: '2' });
|
|
59
|
+
|
|
60
|
+
expect(command.error).toHaveBeenCalledWith('Failed to list branches: Network error');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import JobsGetCommand from '../../../../src/commands/job/get.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('JobsGetCommand', () => {
|
|
9
|
+
let command: JobsGetCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
jobs: {
|
|
17
|
+
get: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new JobsGetCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'info').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should get job details successfully', async () => {
|
|
28
|
+
mockClient.jobs.get.mockResolvedValue({
|
|
29
|
+
id: '100',
|
|
30
|
+
type: 'preview',
|
|
31
|
+
status: 'completed',
|
|
32
|
+
startedAt: '2023-01-01',
|
|
33
|
+
completedAt: '2023-01-02',
|
|
34
|
+
queue: 'default'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3', jobId: '4' });
|
|
38
|
+
|
|
39
|
+
expect(mockClient.jobs.get).toHaveBeenCalledWith(1, 2, 3, 4);
|
|
40
|
+
expect(command.info).toHaveBeenCalledWith('Job Details:');
|
|
41
|
+
expect(command.info).toHaveBeenCalledWith(' ID: 100');
|
|
42
|
+
expect(command.info).toHaveBeenCalledWith(' Type: preview');
|
|
43
|
+
expect(command.info).toHaveBeenCalledWith(' Status: completed');
|
|
44
|
+
expect(command.info).toHaveBeenCalledWith(' Started: 2023-01-01');
|
|
45
|
+
expect(command.info).toHaveBeenCalledWith(' Ended: 2023-01-02');
|
|
46
|
+
expect(command.info).toHaveBeenCalledWith(' Queue: default');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should display defaults for missing times', async () => {
|
|
50
|
+
mockClient.jobs.get.mockResolvedValue({
|
|
51
|
+
id: '100',
|
|
52
|
+
type: 'preview',
|
|
53
|
+
status: 'pending',
|
|
54
|
+
queue: 'default'
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3', jobId: '4' });
|
|
58
|
+
|
|
59
|
+
expect(command.info).toHaveBeenCalledWith(' Started: Waiting');
|
|
60
|
+
expect(command.info).toHaveBeenCalledWith(' Ended: Running');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should validate IDs', async () => {
|
|
64
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3', jobId: 'invalid' });
|
|
65
|
+
expect(command.error).toHaveBeenCalledWith('IDs must be numbers.');
|
|
66
|
+
expect(mockClient.jobs.get).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should handle get failure', async () => {
|
|
70
|
+
mockClient.jobs.get.mockRejectedValue(new Error('Job not found'));
|
|
71
|
+
|
|
72
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3', jobId: '4' });
|
|
73
|
+
|
|
74
|
+
expect(command.error).toHaveBeenCalledWith('Failed to get job: Job not found');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import JobsListCommand from '../../../../src/commands/job/list.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('JobsListCommand', () => {
|
|
9
|
+
let command: JobsListCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
jobs: {
|
|
17
|
+
list: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new JobsListCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'info').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should list jobs successfully', async () => {
|
|
28
|
+
mockClient.jobs.list.mockResolvedValue([
|
|
29
|
+
{ id: '100', type: 'preview', status: 'completed', startedAt: '2023-01-01' },
|
|
30
|
+
{ id: '101', type: 'deploy', status: 'running' }
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3' });
|
|
34
|
+
|
|
35
|
+
expect(mockClient.jobs.list).toHaveBeenCalledWith(1, 2, 3);
|
|
36
|
+
expect(command.info).toHaveBeenCalledWith('Jobs for Branch 3:');
|
|
37
|
+
expect(command.info).toHaveBeenCalledWith('100 - preview [completed] (Started: 2023-01-01)');
|
|
38
|
+
expect(command.info).toHaveBeenCalledWith('101 - deploy [running] (Started: Waiting)');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should handle empty list', async () => {
|
|
42
|
+
mockClient.jobs.list.mockResolvedValue([]);
|
|
43
|
+
|
|
44
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3' });
|
|
45
|
+
|
|
46
|
+
expect(command.info).toHaveBeenCalledWith('No jobs found.');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should validate IDs', async () => {
|
|
50
|
+
await command.run({ teamId: '1', projectId: '2', branchId: 'invalid' });
|
|
51
|
+
expect(command.error).toHaveBeenCalledWith('IDs must be numbers.');
|
|
52
|
+
expect(mockClient.jobs.list).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should handle list failure', async () => {
|
|
56
|
+
mockClient.jobs.list.mockRejectedValue(new Error('Network error'));
|
|
57
|
+
|
|
58
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3' });
|
|
59
|
+
|
|
60
|
+
expect(command.error).toHaveBeenCalledWith('Failed to list jobs: Network error');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import JobsLogsCommand from '../../../../src/commands/job/logs.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('JobsLogsCommand', () => {
|
|
9
|
+
let command: JobsLogsCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
jobs: {
|
|
17
|
+
getLogs: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new JobsLogsCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'info').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'warn').mockImplementation(() => { });
|
|
25
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should display logs successfully', async () => {
|
|
29
|
+
mockClient.jobs.getLogs.mockResolvedValue([
|
|
30
|
+
{ timestamp: '2023-01-01T10:00:00Z', level: 'info', message: 'Starting job' },
|
|
31
|
+
{ timestamp: '2023-01-01T10:00:01Z', level: 'warn', message: 'Slow network' },
|
|
32
|
+
{ timestamp: '2023-01-01T10:00:02Z', level: 'error', message: 'Failed to connect' }
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3', jobId: '4' });
|
|
36
|
+
|
|
37
|
+
expect(mockClient.jobs.getLogs).toHaveBeenCalledWith(1, 2, 3, 4);
|
|
38
|
+
expect(command.info).toHaveBeenCalledWith('Logs for Job 4:');
|
|
39
|
+
|
|
40
|
+
// Timestamps will be converted to locale string, so exact match depends on timezone.
|
|
41
|
+
// We can just check that info, warn, error were called.
|
|
42
|
+
expect(command.info).toHaveBeenCalledWith(expect.stringContaining('Starting job'));
|
|
43
|
+
expect(command.warn).toHaveBeenCalledWith(expect.stringContaining('Slow network'));
|
|
44
|
+
expect(command.error).toHaveBeenCalledWith(expect.stringContaining('Failed to connect'));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should validate IDs', async () => {
|
|
48
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3', jobId: 'invalid' });
|
|
49
|
+
expect(command.error).toHaveBeenCalledWith('IDs must be numbers.');
|
|
50
|
+
expect(mockClient.jobs.getLogs).not.toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should handle logs failure', async () => {
|
|
54
|
+
mockClient.jobs.getLogs.mockRejectedValue(new Error('Logs not available'));
|
|
55
|
+
|
|
56
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3', jobId: '4' });
|
|
57
|
+
|
|
58
|
+
expect(command.error).toHaveBeenCalledWith('Failed to get logs: Logs not available');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import JobsTriggerCommand from '../../../../src/commands/job/trigger.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('JobsTriggerCommand', () => {
|
|
9
|
+
let command: JobsTriggerCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
jobs: {
|
|
17
|
+
create: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new JobsTriggerCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'info').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'success').mockImplementation(() => { });
|
|
25
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should trigger job successfully', async () => {
|
|
29
|
+
mockClient.jobs.create.mockResolvedValue({
|
|
30
|
+
id: '100',
|
|
31
|
+
status: 'pending'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await command.run({
|
|
35
|
+
teamId: '1',
|
|
36
|
+
projectId: '2',
|
|
37
|
+
branchId: '3',
|
|
38
|
+
type: 'deploy',
|
|
39
|
+
input: '{"env":"prod"}'
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(mockClient.jobs.create).toHaveBeenCalledWith(1, 2, 3, {
|
|
43
|
+
type: 'deploy',
|
|
44
|
+
inputs: { env: 'prod' }
|
|
45
|
+
});
|
|
46
|
+
expect(command.success).toHaveBeenCalledWith('Job 100 triggered successfully!');
|
|
47
|
+
expect(command.info).toHaveBeenCalledWith('Status: pending');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle invalid JSON input', async () => {
|
|
51
|
+
await command.run({
|
|
52
|
+
teamId: '1',
|
|
53
|
+
projectId: '2',
|
|
54
|
+
branchId: '3',
|
|
55
|
+
type: 'deploy',
|
|
56
|
+
input: '{invalid-json'
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(command.error).toHaveBeenCalledWith('Invalid JSON inputs.');
|
|
60
|
+
expect(mockClient.jobs.create).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should validate IDs', async () => {
|
|
64
|
+
await command.run({ teamId: 'invalid', projectId: '2', branchId: '3', type: 'deploy' });
|
|
65
|
+
expect(command.error).toHaveBeenCalledWith('IDs must be numbers.');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should handle trigger failure', async () => {
|
|
69
|
+
mockClient.jobs.create.mockRejectedValue(new Error('Quota exceeded'));
|
|
70
|
+
|
|
71
|
+
await command.run({ teamId: '1', projectId: '2', branchId: '3', type: 'deploy' });
|
|
72
|
+
|
|
73
|
+
expect(command.error).toHaveBeenCalledWith('Failed to trigger job: Quota exceeded');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import LoginCommand from '../../../src/commands/login.js';
|
|
4
|
+
import { getClient, saveToken } from '../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('LoginCommand', () => {
|
|
9
|
+
let command: LoginCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
auth: {
|
|
17
|
+
authenticateDevice: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
users: {
|
|
20
|
+
me: vi.fn(),
|
|
21
|
+
},
|
|
22
|
+
setToken: vi.fn(),
|
|
23
|
+
};
|
|
24
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
25
|
+
|
|
26
|
+
command = new LoginCommand([], {} as any);
|
|
27
|
+
vi.spyOn(command, 'info').mockImplementation(() => { });
|
|
28
|
+
vi.spyOn(command, 'notice').mockImplementation(() => { });
|
|
29
|
+
vi.spyOn(command, 'success').mockImplementation(() => { });
|
|
30
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should authenticate successfully', async () => {
|
|
34
|
+
mockClient.auth.authenticateDevice.mockImplementation(async (_clientId: string, callback: any) => {
|
|
35
|
+
callback('USER-CODE', 'https://verify.url');
|
|
36
|
+
return 'new-token';
|
|
37
|
+
});
|
|
38
|
+
mockClient.users.me.mockResolvedValue({ fullName: 'Test User', email: 'test@example.com' });
|
|
39
|
+
|
|
40
|
+
await command.run();
|
|
41
|
+
|
|
42
|
+
expect(command.info).toHaveBeenCalledWith('Starting device authentication...');
|
|
43
|
+
expect(mockClient.auth.authenticateDevice).toHaveBeenCalledWith('nexical-cli', expect.any(Function));
|
|
44
|
+
|
|
45
|
+
// Verify callback interaction via implicit check or spy if feasible,
|
|
46
|
+
// but here we know the callback is called because we mocked implementation calling it.
|
|
47
|
+
expect(command.notice).toHaveBeenCalledWith('Please visit: https://verify.url');
|
|
48
|
+
expect(command.notice).toHaveBeenCalledWith('And enter code: USER-CODE');
|
|
49
|
+
|
|
50
|
+
expect(saveToken).toHaveBeenCalledWith('new-token');
|
|
51
|
+
expect(mockClient.setToken).toHaveBeenCalledWith('new-token');
|
|
52
|
+
expect(mockClient.users.me).toHaveBeenCalled();
|
|
53
|
+
expect(command.success).toHaveBeenCalledWith('Successfully logged in as Test User (test@example.com)');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle authentication failure', async () => {
|
|
57
|
+
mockClient.auth.authenticateDevice.mockRejectedValue(new Error('Auth failed'));
|
|
58
|
+
|
|
59
|
+
await command.run();
|
|
60
|
+
|
|
61
|
+
expect(command.error).toHaveBeenCalledWith('Login failed: Auth failed');
|
|
62
|
+
expect(saveToken).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import ProjectsCreateCommand from '../../../../src/commands/project/create.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('ProjectsCreateCommand', () => {
|
|
9
|
+
let command: ProjectsCreateCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
projects: {
|
|
17
|
+
create: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new ProjectsCreateCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'info').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'success').mockImplementation(() => { });
|
|
25
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should create project successfully', async () => {
|
|
29
|
+
mockClient.projects.create.mockResolvedValue({
|
|
30
|
+
name: 'New Project',
|
|
31
|
+
id: '200'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await command.run({
|
|
35
|
+
teamId: '1',
|
|
36
|
+
name: 'New Project',
|
|
37
|
+
repo: 'http://repo.git',
|
|
38
|
+
prod: 'http://prod.url',
|
|
39
|
+
mode: 'managed'
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(mockClient.projects.create).toHaveBeenCalledWith(1, {
|
|
43
|
+
name: 'New Project',
|
|
44
|
+
repoUrl: 'http://repo.git',
|
|
45
|
+
productionUrl: 'http://prod.url'
|
|
46
|
+
});
|
|
47
|
+
expect(command.success).toHaveBeenCalledWith('Project "New Project" set up successfully!');
|
|
48
|
+
expect(command.info).toHaveBeenCalledWith('ID: 200');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should validate Team ID', async () => {
|
|
52
|
+
await command.run({ teamId: 'invalid', name: 'New Project' });
|
|
53
|
+
expect(command.error).toHaveBeenCalledWith('Team ID must be a number.');
|
|
54
|
+
expect(mockClient.projects.create).not.toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should handle creation failure', async () => {
|
|
58
|
+
mockClient.projects.create.mockRejectedValue(new Error('Name taken'));
|
|
59
|
+
|
|
60
|
+
await command.run({ teamId: '1', name: 'New Project' });
|
|
61
|
+
|
|
62
|
+
expect(command.error).toHaveBeenCalledWith('Failed to create project: Name taken');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import ProjectsDeleteCommand from '../../../../src/commands/project/delete.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('ProjectsDeleteCommand', () => {
|
|
9
|
+
let command: ProjectsDeleteCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
projects: {
|
|
17
|
+
delete: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new ProjectsDeleteCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'info').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'success').mockImplementation(() => { });
|
|
25
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
26
|
+
// Mock prompt method which is likely on BaseCommand prototype or instance
|
|
27
|
+
(command as any).prompt = vi.fn();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should delete project successfully with confirmation', async () => {
|
|
31
|
+
mockClient.projects.delete.mockResolvedValue({});
|
|
32
|
+
(command as any).prompt.mockResolvedValue('yes');
|
|
33
|
+
|
|
34
|
+
await command.run({ teamId: '1', projectId: '2', confirm: false });
|
|
35
|
+
|
|
36
|
+
expect((command as any).prompt).toHaveBeenCalled();
|
|
37
|
+
expect(mockClient.projects.delete).toHaveBeenCalledWith(1, 2);
|
|
38
|
+
expect(command.success).toHaveBeenCalledWith('Project 2 deleted.');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should delete project successfully with --confirm flag', async () => {
|
|
42
|
+
mockClient.projects.delete.mockResolvedValue({});
|
|
43
|
+
|
|
44
|
+
await command.run({ teamId: '1', projectId: '2', confirm: true });
|
|
45
|
+
|
|
46
|
+
expect((command as any).prompt).not.toHaveBeenCalled();
|
|
47
|
+
expect(mockClient.projects.delete).toHaveBeenCalledWith(1, 2);
|
|
48
|
+
expect(command.success).toHaveBeenCalledWith('Project 2 deleted.');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should abort deletion if user says no', async () => {
|
|
52
|
+
(command as any).prompt.mockResolvedValue('no');
|
|
53
|
+
|
|
54
|
+
await command.run({ teamId: '1', projectId: '2', confirm: false });
|
|
55
|
+
|
|
56
|
+
expect(mockClient.projects.delete).not.toHaveBeenCalled();
|
|
57
|
+
expect(command.info).toHaveBeenCalledWith('Aborted.');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should validate IDs', async () => {
|
|
61
|
+
await command.run({ teamId: 'invalid', projectId: '2' });
|
|
62
|
+
expect(command.error).toHaveBeenCalledWith('IDs must be numbers.');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle deletion failure', async () => {
|
|
66
|
+
mockClient.projects.delete.mockRejectedValue(new Error('Project not found'));
|
|
67
|
+
|
|
68
|
+
await command.run({ teamId: '1', projectId: '2', confirm: true });
|
|
69
|
+
|
|
70
|
+
expect(command.error).toHaveBeenCalledWith('Failed to delete project: Project not found');
|
|
71
|
+
});
|
|
72
|
+
});
|