@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,65 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import AuthTokensGenerateCommand from '../../../../src/commands/token/generate.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('AuthTokensGenerateCommand', () => {
|
|
9
|
+
let command: AuthTokensGenerateCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
auth: {
|
|
17
|
+
generateToken: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new AuthTokensGenerateCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'success').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'warn').mockImplementation(() => { });
|
|
25
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should generate token successfully', async () => {
|
|
29
|
+
mockClient.auth.generateToken.mockResolvedValue({
|
|
30
|
+
name: 'My Token',
|
|
31
|
+
token: 'secret-token-value'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await command.run({ name: 'My Token', scopes: 'read,write' });
|
|
35
|
+
|
|
36
|
+
expect(mockClient.auth.generateToken).toHaveBeenCalledWith({
|
|
37
|
+
name: 'My Token',
|
|
38
|
+
scopes: ['read', 'write'],
|
|
39
|
+
});
|
|
40
|
+
expect(command.success).toHaveBeenCalledWith('Token "My Token" generated!');
|
|
41
|
+
expect(command.warn).toHaveBeenCalledWith('Token: secret-token-value');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should generate token without scopes', async () => {
|
|
45
|
+
mockClient.auth.generateToken.mockResolvedValue({
|
|
46
|
+
name: 'My Token',
|
|
47
|
+
token: 'secret-token-value'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await command.run({ name: 'My Token' });
|
|
51
|
+
|
|
52
|
+
expect(mockClient.auth.generateToken).toHaveBeenCalledWith({
|
|
53
|
+
name: 'My Token',
|
|
54
|
+
scopes: undefined,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should handle generation failure', async () => {
|
|
59
|
+
mockClient.auth.generateToken.mockRejectedValue(new Error('Limit reached'));
|
|
60
|
+
|
|
61
|
+
await command.run({ name: 'My Token' });
|
|
62
|
+
|
|
63
|
+
expect(command.error).toHaveBeenCalledWith('Failed to generate token: Limit reached');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import AuthTokensListCommand from '../../../../src/commands/token/list.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('AuthTokensListCommand', () => {
|
|
9
|
+
let command: AuthTokensListCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
auth: {
|
|
17
|
+
listTokens: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new AuthTokensListCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'info').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should list tokens successfully', async () => {
|
|
28
|
+
mockClient.auth.listTokens.mockResolvedValue({
|
|
29
|
+
tokens: [
|
|
30
|
+
{ name: 'Token 1', tokenPrefix: 'abc', expiresAt: '2023-01-01' },
|
|
31
|
+
{ name: 'Token 2', tokenPrefix: 'def' }
|
|
32
|
+
]
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await command.run();
|
|
36
|
+
|
|
37
|
+
expect(mockClient.auth.listTokens).toHaveBeenCalled();
|
|
38
|
+
expect(command.info).toHaveBeenCalledWith('Your API Tokens:');
|
|
39
|
+
expect(command.info).toHaveBeenCalledWith('- Token 1 (abc...) [Expires: 2023-01-01]');
|
|
40
|
+
expect(command.info).toHaveBeenCalledWith('- Token 2 (def...) [Expires: Never]');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should handle empty list', async () => {
|
|
44
|
+
mockClient.auth.listTokens.mockResolvedValue({ tokens: [] });
|
|
45
|
+
|
|
46
|
+
await command.run();
|
|
47
|
+
|
|
48
|
+
expect(command.info).toHaveBeenCalledWith('No API tokens found.');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should handle list failure', async () => {
|
|
52
|
+
mockClient.auth.listTokens.mockRejectedValue(new Error('Network error'));
|
|
53
|
+
|
|
54
|
+
await command.run();
|
|
55
|
+
|
|
56
|
+
expect(command.error).toHaveBeenCalledWith('Failed to list tokens: Network error');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import AuthTokensRevokeCommand from '../../../../src/commands/token/revoke.js';
|
|
4
|
+
import { getClient } from '../../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('AuthTokensRevokeCommand', () => {
|
|
9
|
+
let command: AuthTokensRevokeCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
auth: {
|
|
17
|
+
revokeToken: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new AuthTokensRevokeCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'success').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should revoke token successfully', async () => {
|
|
28
|
+
mockClient.auth.revokeToken.mockResolvedValue({});
|
|
29
|
+
|
|
30
|
+
await command.run({ id: '123' });
|
|
31
|
+
|
|
32
|
+
expect(mockClient.auth.revokeToken).toHaveBeenCalledWith(123);
|
|
33
|
+
expect(command.success).toHaveBeenCalledWith('Token 123 revoked.');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should validate ID', async () => {
|
|
37
|
+
await command.run({ id: 'invalid' });
|
|
38
|
+
expect(command.error).toHaveBeenCalledWith('Token ID must be a number.');
|
|
39
|
+
expect(mockClient.auth.revokeToken).not.toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle revocation failure', async () => {
|
|
43
|
+
mockClient.auth.revokeToken.mockRejectedValue(new Error('Token not found'));
|
|
44
|
+
|
|
45
|
+
await command.run({ id: '123' });
|
|
46
|
+
|
|
47
|
+
expect(command.error).toHaveBeenCalledWith('Failed to revoke token: Token not found');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import WhoamiCommand from '../../../src/commands/whoami.js';
|
|
4
|
+
import { getClient } from '../../../src/utils/nexical-client.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../src/utils/nexical-client.js');
|
|
7
|
+
|
|
8
|
+
describe('WhoamiCommand', () => {
|
|
9
|
+
let command: WhoamiCommand;
|
|
10
|
+
let mockClient: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
|
|
15
|
+
mockClient = {
|
|
16
|
+
users: {
|
|
17
|
+
me: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
vi.mocked(getClient).mockReturnValue(mockClient);
|
|
21
|
+
|
|
22
|
+
command = new WhoamiCommand([], {} as any);
|
|
23
|
+
vi.spyOn(command, 'info').mockImplementation(() => { });
|
|
24
|
+
vi.spyOn(command, 'error').mockImplementation(() => { });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should display user info when logged in', async () => {
|
|
28
|
+
mockClient.users.me.mockResolvedValue({
|
|
29
|
+
fullName: 'Test User',
|
|
30
|
+
email: 'test@example.com',
|
|
31
|
+
id: 'user-123'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await command.run();
|
|
35
|
+
|
|
36
|
+
expect(command.info).toHaveBeenCalledWith('Logged in as:');
|
|
37
|
+
expect(command.info).toHaveBeenCalledWith(' Name: Test User');
|
|
38
|
+
expect(command.info).toHaveBeenCalledWith(' Email: test@example.com');
|
|
39
|
+
expect(command.info).toHaveBeenCalledWith(' ID: user-123');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should display error when not logged in', async () => {
|
|
43
|
+
mockClient.users.me.mockRejectedValue(new Error('Unauthorized'));
|
|
44
|
+
|
|
45
|
+
await command.run();
|
|
46
|
+
|
|
47
|
+
expect(command.error).toHaveBeenCalledWith('Not logged in or token expired. Run `astrical login`.');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import { getConfig, saveToken, getClient } from '../../../src/utils/nexical-client.js';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import { NexicalClient } from '@nexical/sdk';
|
|
8
|
+
|
|
9
|
+
vi.mock('node:fs');
|
|
10
|
+
vi.mock('node:fs');
|
|
11
|
+
vi.mock('node:os', () => ({
|
|
12
|
+
default: {
|
|
13
|
+
homedir: () => '/home/user'
|
|
14
|
+
}
|
|
15
|
+
}));
|
|
16
|
+
vi.mock('@nexical/sdk');
|
|
17
|
+
|
|
18
|
+
describe('nexical-client', () => {
|
|
19
|
+
const mockHomeDir = '/home/user';
|
|
20
|
+
const configDir = path.join(mockHomeDir, '.nexical');
|
|
21
|
+
const configFile = path.join(configDir, 'config.json');
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.resetAllMocks();
|
|
25
|
+
// os.homedir is already mocked by factory
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('getConfig', () => {
|
|
29
|
+
it('should return empty object if config file does not exist', () => {
|
|
30
|
+
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
31
|
+
expect(getConfig()).toEqual({});
|
|
32
|
+
expect(fs.existsSync).toHaveBeenCalledWith(configFile);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return parsed config if file exists', () => {
|
|
36
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
37
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ token: 'test-token' }));
|
|
38
|
+
expect(getConfig()).toEqual({ token: 'test-token' });
|
|
39
|
+
expect(fs.readFileSync).toHaveBeenCalledWith(configFile, 'utf-8');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return empty object on JSON parse error', () => {
|
|
43
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
44
|
+
vi.mocked(fs.readFileSync).mockReturnValue('invalid-json');
|
|
45
|
+
expect(getConfig()).toEqual({});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('saveToken', () => {
|
|
50
|
+
it('should create directory if it does not exist', () => {
|
|
51
|
+
vi.mocked(fs.existsSync)
|
|
52
|
+
.mockReturnValueOnce(false) // config dir check
|
|
53
|
+
.mockReturnValueOnce(false); // config file check (inside getConfig)
|
|
54
|
+
|
|
55
|
+
saveToken('new-token');
|
|
56
|
+
|
|
57
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith(configDir, { recursive: true });
|
|
58
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
59
|
+
configFile,
|
|
60
|
+
JSON.stringify({ token: 'new-token' }, null, 2)
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should update existing config', () => {
|
|
65
|
+
vi.mocked(fs.existsSync)
|
|
66
|
+
.mockReturnValueOnce(true) // config dir check
|
|
67
|
+
.mockReturnValueOnce(true); // config file check (inside getConfig)
|
|
68
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ other: 'value' }));
|
|
69
|
+
|
|
70
|
+
saveToken('new-token');
|
|
71
|
+
|
|
72
|
+
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
|
73
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
74
|
+
configFile,
|
|
75
|
+
JSON.stringify({ other: 'value', token: 'new-token' }, null, 2)
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('getClient', () => {
|
|
81
|
+
it('should return NexicalClient instance with token from config', () => {
|
|
82
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
83
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ token: 'saved-token' }));
|
|
84
|
+
|
|
85
|
+
getClient();
|
|
86
|
+
|
|
87
|
+
expect(NexicalClient).toHaveBeenCalledWith({ token: 'saved-token' });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should return NexicalClient instance without token if config missing', () => {
|
|
91
|
+
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
92
|
+
|
|
93
|
+
getClient();
|
|
94
|
+
|
|
95
|
+
expect(NexicalClient).toHaveBeenCalledWith({ token: undefined });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
// Constants
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
export const CLI_BIN = path.resolve(__dirname, "../../dist/index.js");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Runs the CLI command against the compiled binary (E2E style)
|
|
11
|
+
*/
|
|
12
|
+
export async function runCLI(args: string[], cwd: string, options: any = {}) {
|
|
13
|
+
return execa("node", [CLI_BIN, ...args], {
|
|
14
|
+
cwd,
|
|
15
|
+
...options,
|
|
16
|
+
env: {
|
|
17
|
+
...process.env,
|
|
18
|
+
...options.env,
|
|
19
|
+
},
|
|
20
|
+
reject: false, // Allow checking exit code in tests
|
|
21
|
+
});
|
|
22
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ESNext"
|
|
8
|
+
],
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"outDir": "./dist"
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"src/commands/**/*",
|
|
18
|
+
"src/utils/**/*",
|
|
19
|
+
"test/**/*",
|
|
20
|
+
"index.ts"
|
|
21
|
+
],
|
|
22
|
+
"exclude": [
|
|
23
|
+
"node_modules",
|
|
24
|
+
"dist"
|
|
25
|
+
]
|
|
26
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Options } from 'tsup';
|
|
2
|
+
|
|
3
|
+
export default <Options>{
|
|
4
|
+
entry: ['index.ts', 'src/**/*.ts'],
|
|
5
|
+
format: ['esm'],
|
|
6
|
+
target: 'node18',
|
|
7
|
+
clean: true,
|
|
8
|
+
bundle: true,
|
|
9
|
+
sourcemap: true,
|
|
10
|
+
dts: true,
|
|
11
|
+
minify: false,
|
|
12
|
+
splitting: true,
|
|
13
|
+
outDir: 'dist',
|
|
14
|
+
shims: true, // Enable shims (including __require shim for legacy deps)
|
|
15
|
+
banner: {
|
|
16
|
+
js: 'import { createRequire } from "module"; const require = createRequire(import.meta.url);'
|
|
17
|
+
},
|
|
18
|
+
};
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'node',
|
|
7
|
+
include: ['test/unit/**/*.test.ts'],
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: 'v8',
|
|
10
|
+
reporter: ['text', 'json', 'html'],
|
|
11
|
+
include: ['src/**/*.ts'],
|
|
12
|
+
exclude: ['index.ts', '**/*.d.ts']
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'node',
|
|
7
|
+
include: ['test/integration/**/*.test.ts'],
|
|
8
|
+
// Increase timeout for integration tests as they do real IO
|
|
9
|
+
testTimeout: 60000,
|
|
10
|
+
fileParallelism: false,
|
|
11
|
+
server: {
|
|
12
|
+
deps: {
|
|
13
|
+
inline: ['@nexical/cli-core'],
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
coverage: {
|
|
17
|
+
provider: 'v8',
|
|
18
|
+
reporter: ['text', 'json', 'html'],
|
|
19
|
+
include: ['src/**/*.ts'],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|