@capawesome/cli 1.14.0 → 2.0.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/CHANGELOG.md +27 -0
- package/README.md +7 -3
- package/dist/commands/apps/bundles/create.js +206 -239
- package/dist/commands/apps/bundles/create.test.js +276 -0
- package/dist/commands/apps/bundles/delete.js +35 -60
- package/dist/commands/apps/bundles/delete.test.js +139 -0
- package/dist/commands/apps/bundles/update.js +61 -89
- package/dist/commands/apps/bundles/update.test.js +141 -0
- package/dist/commands/apps/channels/create.js +45 -75
- package/dist/commands/apps/channels/create.test.js +119 -0
- package/dist/commands/apps/channels/delete.js +46 -69
- package/dist/commands/apps/channels/delete.test.js +141 -0
- package/dist/commands/apps/channels/get.js +52 -94
- package/dist/commands/apps/channels/get.test.js +135 -0
- package/dist/commands/apps/channels/list.js +37 -82
- package/dist/commands/apps/channels/list.test.js +121 -0
- package/dist/commands/apps/channels/update.js +39 -83
- package/dist/commands/apps/channels/update.test.js +138 -0
- package/dist/commands/apps/create.js +28 -53
- package/dist/commands/apps/create.test.js +117 -0
- package/dist/commands/apps/delete.js +29 -50
- package/dist/commands/apps/delete.test.js +120 -0
- package/dist/commands/apps/devices/delete.js +35 -60
- package/dist/commands/apps/devices/delete.test.js +139 -0
- package/dist/commands/doctor.js +12 -29
- package/dist/commands/doctor.test.js +52 -0
- package/dist/commands/login.js +50 -71
- package/dist/commands/login.test.js +116 -0
- package/dist/commands/logout.js +13 -31
- package/dist/commands/logout.test.js +47 -0
- package/dist/commands/manifests/generate.js +20 -38
- package/dist/commands/manifests/generate.test.js +60 -0
- package/dist/commands/organizations/create.js +25 -0
- package/dist/commands/organizations/create.test.js +80 -0
- package/dist/commands/whoami.js +20 -31
- package/dist/commands/whoami.test.js +30 -0
- package/dist/config/consts.js +4 -5
- package/dist/config/index.js +1 -17
- package/dist/index.js +54 -80
- package/dist/services/app-bundle-files.js +117 -136
- package/dist/services/app-bundles.js +22 -41
- package/dist/services/app-channels.js +54 -77
- package/dist/services/app-devices.js +10 -25
- package/dist/services/apps.js +25 -43
- package/dist/services/authorization-service.js +4 -8
- package/dist/services/config.js +15 -28
- package/dist/services/organizations.js +19 -26
- package/dist/services/session-code.js +7 -22
- package/dist/services/sessions.js +13 -30
- package/dist/services/update.js +17 -55
- package/dist/services/users.js +11 -26
- package/dist/types/app-bundle-file.js +1 -2
- package/dist/types/app-bundle.js +1 -2
- package/dist/types/app-channel.js +1 -2
- package/dist/types/app-device.js +1 -2
- package/dist/types/app.js +1 -2
- package/dist/types/index.js +8 -24
- package/dist/types/npm-package.js +1 -2
- package/dist/types/organization.js +1 -2
- package/dist/types/session-code.js +1 -2
- package/dist/types/session.js +1 -2
- package/dist/types/user.js +1 -2
- package/dist/utils/buffer.js +12 -43
- package/dist/utils/error.js +24 -14
- package/dist/utils/file.js +22 -41
- package/dist/utils/hash.js +3 -39
- package/dist/utils/http-client.js +27 -53
- package/dist/utils/manifest.js +11 -24
- package/dist/utils/private-key.js +23 -0
- package/dist/utils/prompt.js +9 -26
- package/dist/utils/signature.js +3 -39
- package/dist/utils/user-config.js +12 -0
- package/dist/utils/zip.js +11 -27
- package/package.json +22 -9
- package/dist/utils/ci.js +0 -7
- package/dist/utils/userConfig.js +0 -16
package/dist/commands/doctor.js
CHANGED
|
@@ -1,34 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
const citty_1 = require("citty");
|
|
16
|
-
const consola_1 = __importDefault(require("consola"));
|
|
17
|
-
const package_json_1 = __importDefault(require("../../package.json"));
|
|
18
|
-
const systeminformation_1 = __importDefault(require("systeminformation"));
|
|
19
|
-
exports.default = (0, citty_1.defineCommand)({
|
|
20
|
-
meta: {
|
|
21
|
-
name: 'doctor',
|
|
22
|
-
description: 'Prints out neccessary information for debugging',
|
|
23
|
-
},
|
|
24
|
-
run: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
25
|
-
const osInfo = yield systeminformation_1.default.osInfo();
|
|
26
|
-
const versions = yield systeminformation_1.default.versions('npm, node');
|
|
27
|
-
consola_1.default.box([
|
|
1
|
+
import { defineCommand } from '@robingenz/zli';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import systeminformation from 'systeminformation';
|
|
4
|
+
import pkg from '../../package.json' with { type: 'json' };
|
|
5
|
+
export default defineCommand({
|
|
6
|
+
description: 'Prints out neccessary information for debugging',
|
|
7
|
+
action: async (options, args) => {
|
|
8
|
+
const osInfo = await systeminformation.osInfo();
|
|
9
|
+
const versions = await systeminformation.versions('npm, node');
|
|
10
|
+
consola.box([
|
|
28
11
|
`NodeJS version: ${versions.node}`,
|
|
29
12
|
`NPM version: ${versions.npm}`,
|
|
30
|
-
`CLI version: ${
|
|
13
|
+
`CLI version: ${pkg.version}`,
|
|
31
14
|
`OS: ${osInfo.distro} ${osInfo.release} ${osInfo.codename ? `(${osInfo.codename})` : ''}`,
|
|
32
15
|
].join('\n'));
|
|
33
|
-
}
|
|
16
|
+
},
|
|
34
17
|
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import consola from 'consola';
|
|
2
|
+
import systeminformation from 'systeminformation';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import doctorCommand from './doctor.js';
|
|
5
|
+
// Mock dependencies
|
|
6
|
+
vi.mock('consola');
|
|
7
|
+
vi.mock('systeminformation');
|
|
8
|
+
describe('doctor', () => {
|
|
9
|
+
const mockConsola = vi.mocked(consola);
|
|
10
|
+
const mockSystemInformation = vi.mocked(systeminformation);
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
vi.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
it('should display system information', async () => {
|
|
18
|
+
const mockOsInfo = {
|
|
19
|
+
distro: 'macOS',
|
|
20
|
+
release: '14.0',
|
|
21
|
+
codename: 'Sonoma',
|
|
22
|
+
};
|
|
23
|
+
const mockVersions = {
|
|
24
|
+
node: '18.17.0',
|
|
25
|
+
npm: '9.6.7',
|
|
26
|
+
};
|
|
27
|
+
mockSystemInformation.osInfo.mockResolvedValue(mockOsInfo);
|
|
28
|
+
mockSystemInformation.versions.mockResolvedValue(mockVersions);
|
|
29
|
+
await doctorCommand.action({}, undefined);
|
|
30
|
+
expect(mockSystemInformation.osInfo).toHaveBeenCalled();
|
|
31
|
+
expect(mockSystemInformation.versions).toHaveBeenCalledWith('npm, node');
|
|
32
|
+
expect(mockConsola.box).toHaveBeenCalledWith(expect.stringContaining('NodeJS version: 18.17.0'));
|
|
33
|
+
expect(mockConsola.box).toHaveBeenCalledWith(expect.stringContaining('NPM version: 9.6.7'));
|
|
34
|
+
expect(mockConsola.box).toHaveBeenCalledWith(expect.stringContaining('OS: macOS 14.0 (Sonoma)'));
|
|
35
|
+
});
|
|
36
|
+
it('should handle OS info without codename', async () => {
|
|
37
|
+
const mockOsInfo = {
|
|
38
|
+
distro: 'Ubuntu',
|
|
39
|
+
release: '22.04',
|
|
40
|
+
codename: '',
|
|
41
|
+
};
|
|
42
|
+
const mockVersions = {
|
|
43
|
+
node: '20.0.0',
|
|
44
|
+
npm: '10.0.0',
|
|
45
|
+
};
|
|
46
|
+
mockSystemInformation.osInfo.mockResolvedValue(mockOsInfo);
|
|
47
|
+
mockSystemInformation.versions.mockResolvedValue(mockVersions);
|
|
48
|
+
await doctorCommand.action({}, undefined);
|
|
49
|
+
expect(mockConsola.box).toHaveBeenCalledWith(expect.stringContaining('OS: Ubuntu 22.04'));
|
|
50
|
+
expect(mockConsola.box).toHaveBeenCalledWith(expect.not.stringContaining('()'));
|
|
51
|
+
});
|
|
52
|
+
});
|
package/dist/commands/login.js
CHANGED
|
@@ -1,46 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const session_code_1 = __importDefault(require("../services/session-code"));
|
|
21
|
-
const sessions_1 = __importDefault(require("../services/sessions"));
|
|
22
|
-
const users_1 = __importDefault(require("../services/users"));
|
|
23
|
-
const error_1 = require("../utils/error");
|
|
24
|
-
const prompt_1 = require("../utils/prompt");
|
|
25
|
-
const userConfig_1 = __importDefault(require("../utils/userConfig"));
|
|
26
|
-
exports.default = (0, citty_1.defineCommand)({
|
|
27
|
-
meta: {
|
|
28
|
-
name: 'login',
|
|
29
|
-
description: 'Sign in to the Capawesome Cloud Console.',
|
|
30
|
-
},
|
|
31
|
-
args: {
|
|
32
|
-
token: {
|
|
33
|
-
type: 'string',
|
|
34
|
-
description: 'Token to use for authentication.',
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
run: (ctx) => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
|
-
var _a;
|
|
39
|
-
const consoleBaseUrl = yield config_1.default.getValueForKey('CONSOLE_BASE_URL');
|
|
40
|
-
let sessionIdOrToken = ctx.args.token;
|
|
1
|
+
import configService from '../services/config.js';
|
|
2
|
+
import sessionCodesService from '../services/session-code.js';
|
|
3
|
+
import sessionsService from '../services/sessions.js';
|
|
4
|
+
import usersService from '../services/users.js';
|
|
5
|
+
import { prompt } from '../utils/prompt.js';
|
|
6
|
+
import userConfig from '../utils/user-config.js';
|
|
7
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
8
|
+
import { AxiosError } from 'axios';
|
|
9
|
+
import consola from 'consola';
|
|
10
|
+
import open from 'open';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
export default defineCommand({
|
|
13
|
+
description: 'Sign in to the Capawesome Cloud Console.',
|
|
14
|
+
options: defineOptions(z.object({
|
|
15
|
+
token: z.string().optional().describe('Token to use for authentication.'),
|
|
16
|
+
})),
|
|
17
|
+
action: async (options, args) => {
|
|
18
|
+
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
|
|
19
|
+
let { token: sessionIdOrToken } = options;
|
|
41
20
|
if (sessionIdOrToken === undefined) {
|
|
42
21
|
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
43
|
-
const authenticationMethod =
|
|
22
|
+
const authenticationMethod = await prompt('How would you like to authenticate Capawesome CLI?', {
|
|
44
23
|
type: 'select',
|
|
45
24
|
options: [
|
|
46
25
|
{ label: 'Login with a web browser', value: 'browser' },
|
|
@@ -49,89 +28,89 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
49
28
|
});
|
|
50
29
|
if (authenticationMethod === 'browser') {
|
|
51
30
|
// Create a session code
|
|
52
|
-
const { id: deviceCode, code: userCode } =
|
|
53
|
-
|
|
31
|
+
const { id: deviceCode, code: userCode } = await sessionCodesService.create();
|
|
32
|
+
consola.box(`Copy your one-time code: ${userCode.slice(0, 4)}-${userCode.slice(4)}`);
|
|
54
33
|
// Prompt the user to open the authorization URL in their browser
|
|
55
|
-
const shouldProceed =
|
|
34
|
+
const shouldProceed = await prompt(`Select Yes to continue in your browser or No to cancel the authentication.`, {
|
|
56
35
|
type: 'confirm',
|
|
57
36
|
initial: true,
|
|
58
37
|
});
|
|
59
38
|
if (!shouldProceed) {
|
|
60
|
-
|
|
39
|
+
consola.error('Authentication cancelled.');
|
|
61
40
|
process.exit(1);
|
|
62
41
|
}
|
|
63
42
|
// Open the authorization URL in the user's default browser
|
|
64
|
-
|
|
43
|
+
consola.start('Opening browser...');
|
|
65
44
|
const authorizationUrl = `${consoleBaseUrl}/login/device`;
|
|
66
45
|
try {
|
|
67
|
-
(
|
|
46
|
+
open(authorizationUrl);
|
|
68
47
|
}
|
|
69
48
|
catch (error) {
|
|
70
|
-
|
|
49
|
+
consola.warn(`Could not open browser automatically. Please open the following URL manually: ${authorizationUrl}`);
|
|
71
50
|
}
|
|
72
51
|
// Wait for the user to authenticate
|
|
73
|
-
|
|
74
|
-
const sessionId =
|
|
52
|
+
consola.start('Waiting for authentication...');
|
|
53
|
+
const sessionId = await createSession(deviceCode);
|
|
75
54
|
if (!sessionId) {
|
|
76
|
-
|
|
55
|
+
consola.error('Authentication timed out. Please try again.');
|
|
77
56
|
process.exit(1);
|
|
78
57
|
}
|
|
79
58
|
sessionIdOrToken = sessionId;
|
|
80
59
|
}
|
|
81
60
|
else {
|
|
82
|
-
sessionIdOrToken =
|
|
61
|
+
sessionIdOrToken = await prompt('Please provide your authentication token:', {
|
|
83
62
|
type: 'text',
|
|
84
63
|
});
|
|
85
64
|
if (!sessionIdOrToken) {
|
|
86
|
-
|
|
65
|
+
consola.error('Token must be provided.');
|
|
87
66
|
process.exit(1);
|
|
88
67
|
}
|
|
89
68
|
}
|
|
90
69
|
}
|
|
91
70
|
else if (sessionIdOrToken.length === 0) {
|
|
92
71
|
// No token provided
|
|
93
|
-
|
|
72
|
+
consola.error(`Please provide a valid token. You can create a token at ${consoleBaseUrl}/settings/tokens.`);
|
|
94
73
|
process.exit(1);
|
|
95
74
|
}
|
|
96
75
|
// Sign in with the provided token
|
|
97
|
-
|
|
98
|
-
|
|
76
|
+
consola.start('Signing in...');
|
|
77
|
+
userConfig.write({
|
|
99
78
|
token: sessionIdOrToken,
|
|
100
79
|
});
|
|
101
80
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
81
|
+
await usersService.me();
|
|
82
|
+
consola.success(`Successfully signed in.`);
|
|
104
83
|
}
|
|
105
84
|
catch (error) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
85
|
+
userConfig.write({});
|
|
86
|
+
if (error instanceof AxiosError && error.response?.status === 401) {
|
|
87
|
+
consola.error(`Invalid token. Please provide a valid token. You can create a token at ${consoleBaseUrl}/settings/tokens.`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
throw error;
|
|
110
92
|
}
|
|
111
|
-
consola_1.default.error(message);
|
|
112
|
-
process.exit(1);
|
|
113
93
|
}
|
|
114
|
-
}
|
|
94
|
+
},
|
|
115
95
|
});
|
|
116
|
-
const createSession = (deviceCode) =>
|
|
117
|
-
var _a;
|
|
96
|
+
const createSession = async (deviceCode) => {
|
|
118
97
|
const maxAttempts = 20;
|
|
119
98
|
const interval = 3 * 1000; // 3 seconds
|
|
120
99
|
let attempts = 0;
|
|
121
100
|
let sessionId = null;
|
|
122
101
|
while (attempts < maxAttempts && sessionId === null) {
|
|
123
102
|
try {
|
|
124
|
-
const response =
|
|
103
|
+
const response = await sessionsService.create({
|
|
125
104
|
code: deviceCode,
|
|
126
105
|
provider: 'code',
|
|
127
106
|
});
|
|
128
107
|
sessionId = response.id;
|
|
129
108
|
}
|
|
130
109
|
catch (error) {
|
|
131
|
-
if (error instanceof
|
|
110
|
+
if (error instanceof AxiosError && error.response?.status === 400) {
|
|
132
111
|
// Session not ready yet, wait and try again
|
|
133
112
|
attempts++;
|
|
134
|
-
|
|
113
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
135
114
|
}
|
|
136
115
|
else {
|
|
137
116
|
throw error;
|
|
@@ -139,4 +118,4 @@ const createSession = (deviceCode) => __awaiter(void 0, void 0, void 0, function
|
|
|
139
118
|
}
|
|
140
119
|
}
|
|
141
120
|
return sessionId;
|
|
142
|
-
}
|
|
121
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { DEFAULT_API_BASE_URL, DEFAULT_CONSOLE_BASE_URL } from '../config/consts.js';
|
|
2
|
+
import configService from '../services/config.js';
|
|
3
|
+
import sessionCodesService from '../services/session-code.js';
|
|
4
|
+
import sessionsService from '../services/sessions.js';
|
|
5
|
+
import { prompt } from '../utils/prompt.js';
|
|
6
|
+
import userConfig from '../utils/user-config.js';
|
|
7
|
+
import consola from 'consola';
|
|
8
|
+
import nock from 'nock';
|
|
9
|
+
import open from 'open';
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
11
|
+
import loginCommand from './login.js';
|
|
12
|
+
// Mock dependencies
|
|
13
|
+
vi.mock('@/utils/user-config.js');
|
|
14
|
+
vi.mock('@/services/session-code.js');
|
|
15
|
+
vi.mock('@/services/sessions.js');
|
|
16
|
+
vi.mock('@/services/config.js');
|
|
17
|
+
vi.mock('open');
|
|
18
|
+
vi.mock('consola');
|
|
19
|
+
vi.mock('@/utils/prompt.js');
|
|
20
|
+
describe('login', () => {
|
|
21
|
+
const mockUserConfig = vi.mocked(userConfig);
|
|
22
|
+
const mockSessionCodesService = vi.mocked(sessionCodesService);
|
|
23
|
+
const mockSessionsService = vi.mocked(sessionsService);
|
|
24
|
+
const mockConfigService = vi.mocked(configService);
|
|
25
|
+
const mockOpen = vi.mocked(open);
|
|
26
|
+
const mockConsola = vi.mocked(consola);
|
|
27
|
+
const mockPrompt = vi.mocked(prompt);
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
vi.clearAllMocks();
|
|
30
|
+
mockUserConfig.write.mockImplementation(() => { });
|
|
31
|
+
mockUserConfig.read.mockReturnValue({});
|
|
32
|
+
// Mock config service to return consistent URLs
|
|
33
|
+
mockConfigService.getValueForKey.mockImplementation((key) => {
|
|
34
|
+
if (key === 'CONSOLE_BASE_URL')
|
|
35
|
+
return Promise.resolve(DEFAULT_CONSOLE_BASE_URL);
|
|
36
|
+
if (key === 'API_BASE_URL')
|
|
37
|
+
return Promise.resolve(DEFAULT_API_BASE_URL);
|
|
38
|
+
return Promise.resolve('');
|
|
39
|
+
});
|
|
40
|
+
vi.spyOn(process, 'exit').mockImplementation((code) => {
|
|
41
|
+
throw new Error(`Process exited with code ${code}`);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
nock.cleanAll();
|
|
46
|
+
vi.restoreAllMocks();
|
|
47
|
+
});
|
|
48
|
+
it('should use the provided token for authentication', async () => {
|
|
49
|
+
const testToken = 'valid-token-123';
|
|
50
|
+
const options = { token: testToken };
|
|
51
|
+
// Mock userConfig.read to return our test token after it's written
|
|
52
|
+
mockUserConfig.read.mockReturnValue({ token: testToken });
|
|
53
|
+
// Set up nock to intercept the /v1/users/me request
|
|
54
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
55
|
+
.get('/v1/users/me')
|
|
56
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
57
|
+
.reply(200, { id: 'user-123', email: 'test@example.com' });
|
|
58
|
+
await loginCommand.action(options, undefined);
|
|
59
|
+
expect(mockUserConfig.write).toHaveBeenCalledWith({ token: testToken });
|
|
60
|
+
expect(scope.isDone()).toBe(true);
|
|
61
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed in.');
|
|
62
|
+
});
|
|
63
|
+
it('should open the browser', async () => {
|
|
64
|
+
const options = {};
|
|
65
|
+
mockPrompt
|
|
66
|
+
.mockResolvedValueOnce('browser') // authentication method
|
|
67
|
+
.mockResolvedValueOnce(true); // should proceed
|
|
68
|
+
mockSessionCodesService.create.mockResolvedValue({
|
|
69
|
+
id: 'device-code-123',
|
|
70
|
+
code: 'ABCD1234',
|
|
71
|
+
});
|
|
72
|
+
mockSessionsService.create.mockResolvedValue({ id: 'session-123' });
|
|
73
|
+
// Mock userConfig.read to return the session token
|
|
74
|
+
mockUserConfig.read.mockReturnValue({ token: 'session-123' });
|
|
75
|
+
// Set up nock to intercept the /v1/users/me request
|
|
76
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
77
|
+
.get('/v1/users/me')
|
|
78
|
+
.matchHeader('Authorization', `Bearer session-123`)
|
|
79
|
+
.reply(200, { id: 'user-123', email: 'test@example.com' });
|
|
80
|
+
await loginCommand.action(options, undefined);
|
|
81
|
+
expect(mockPrompt).toHaveBeenCalledWith('How would you like to authenticate Capawesome CLI?', {
|
|
82
|
+
type: 'select',
|
|
83
|
+
options: [
|
|
84
|
+
{ label: 'Login with a web browser', value: 'browser' },
|
|
85
|
+
{ label: 'Paste an authentication token', value: 'token' },
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
expect(mockSessionCodesService.create).toHaveBeenCalled();
|
|
89
|
+
expect(mockConsola.box).toHaveBeenCalledWith('Copy your one-time code: ABCD-1234');
|
|
90
|
+
expect(mockOpen).toHaveBeenCalledWith(`${DEFAULT_CONSOLE_BASE_URL}/login/device`);
|
|
91
|
+
expect(scope.isDone()).toBe(true);
|
|
92
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed in.');
|
|
93
|
+
});
|
|
94
|
+
it('should throw an error because the provided token is empty', async () => {
|
|
95
|
+
const options = { token: '' };
|
|
96
|
+
// This test should exit early without making API calls since empty token is caught in login logic
|
|
97
|
+
await expect(loginCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
98
|
+
expect(mockConsola.error).toHaveBeenCalledWith(`Please provide a valid token. You can create a token at ${DEFAULT_CONSOLE_BASE_URL}/settings/tokens.`);
|
|
99
|
+
});
|
|
100
|
+
it('should throw an error because the provided token is invalid', async () => {
|
|
101
|
+
const invalidToken = 'invalid-token';
|
|
102
|
+
const options = { token: invalidToken };
|
|
103
|
+
// Mock userConfig.read to return our invalid token after it's written
|
|
104
|
+
mockUserConfig.read.mockReturnValue({ token: invalidToken });
|
|
105
|
+
// Set up nock to intercept the /v1/users/me request and return 401
|
|
106
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
107
|
+
.get('/v1/users/me')
|
|
108
|
+
.matchHeader('Authorization', `Bearer ${invalidToken}`)
|
|
109
|
+
.reply(401, { message: 'Unauthorized' });
|
|
110
|
+
await expect(loginCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
111
|
+
expect(mockUserConfig.write).toHaveBeenCalledWith({ token: invalidToken });
|
|
112
|
+
expect(mockUserConfig.write).toHaveBeenCalledWith({}); // Clears token on error
|
|
113
|
+
expect(scope.isDone()).toBe(true);
|
|
114
|
+
expect(mockConsola.error).toHaveBeenCalledWith(`Invalid token. Please provide a valid token. You can create a token at ${DEFAULT_CONSOLE_BASE_URL}/settings/tokens.`);
|
|
115
|
+
});
|
|
116
|
+
});
|
package/dist/commands/logout.js
CHANGED
|
@@ -1,34 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
const citty_1 = require("citty");
|
|
16
|
-
const consola_1 = __importDefault(require("consola"));
|
|
17
|
-
const authorization_service_1 = __importDefault(require("../services/authorization-service"));
|
|
18
|
-
const sessions_1 = __importDefault(require("../services/sessions"));
|
|
19
|
-
const userConfig_1 = __importDefault(require("../utils/userConfig"));
|
|
20
|
-
exports.default = (0, citty_1.defineCommand)({
|
|
21
|
-
meta: {
|
|
22
|
-
name: 'logout',
|
|
23
|
-
description: 'Sign out from the Capawesome Cloud Console.',
|
|
24
|
-
},
|
|
25
|
-
args: {},
|
|
26
|
-
run: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
27
|
-
const token = authorization_service_1.default.getCurrentAuthorizationToken();
|
|
1
|
+
import { defineCommand } from '@robingenz/zli';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import authorizationService from '../services/authorization-service.js';
|
|
4
|
+
import sessionsService from '../services/sessions.js';
|
|
5
|
+
import userConfig from '../utils/user-config.js';
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: 'Sign out from the Capawesome Cloud Console.',
|
|
8
|
+
action: async (options, args) => {
|
|
9
|
+
const token = authorizationService.getCurrentAuthorizationToken();
|
|
28
10
|
if (token && !token.startsWith('ca_')) {
|
|
29
|
-
|
|
11
|
+
await sessionsService.delete({ id: token }).catch(() => { });
|
|
30
12
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
13
|
+
userConfig.write({});
|
|
14
|
+
consola.success('Successfully signed out.');
|
|
15
|
+
},
|
|
34
16
|
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DEFAULT_API_BASE_URL } from '../config/consts.js';
|
|
2
|
+
import authorizationService from '../services/authorization-service.js';
|
|
3
|
+
import userConfig from '../utils/user-config.js';
|
|
4
|
+
import consola from 'consola';
|
|
5
|
+
import nock from 'nock';
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import logoutCommand from './logout.js';
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
vi.mock('@/services/authorization-service.js');
|
|
10
|
+
vi.mock('@/utils/user-config.js');
|
|
11
|
+
vi.mock('consola');
|
|
12
|
+
describe('logout', () => {
|
|
13
|
+
const mockAuthorizationService = vi.mocked(authorizationService);
|
|
14
|
+
const mockUserConfig = vi.mocked(userConfig);
|
|
15
|
+
const mockConsola = vi.mocked(consola);
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
mockUserConfig.write.mockImplementation(() => { });
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
nock.cleanAll();
|
|
22
|
+
vi.restoreAllMocks();
|
|
23
|
+
});
|
|
24
|
+
it('should delete session and clear user config with session token', async () => {
|
|
25
|
+
const sessionToken = 'session-123';
|
|
26
|
+
mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue(sessionToken);
|
|
27
|
+
// Set up nock to intercept the DELETE request
|
|
28
|
+
const scope = nock(DEFAULT_API_BASE_URL).delete('/v1/sessions/session-123').reply(200);
|
|
29
|
+
await logoutCommand.action({}, undefined);
|
|
30
|
+
expect(scope.isDone()).toBe(true);
|
|
31
|
+
expect(mockUserConfig.write).toHaveBeenCalledWith({});
|
|
32
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed out.');
|
|
33
|
+
});
|
|
34
|
+
it('should only clear user config with API token', async () => {
|
|
35
|
+
const apiToken = 'ca_abc123';
|
|
36
|
+
mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue(apiToken);
|
|
37
|
+
await logoutCommand.action({}, undefined);
|
|
38
|
+
expect(mockUserConfig.write).toHaveBeenCalledWith({});
|
|
39
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed out.');
|
|
40
|
+
});
|
|
41
|
+
it('should handle no token gracefully', async () => {
|
|
42
|
+
mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue(null);
|
|
43
|
+
await logoutCommand.action({}, undefined);
|
|
44
|
+
expect(mockUserConfig.write).toHaveBeenCalledWith({});
|
|
45
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed out.');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -1,51 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
const citty_1 = require("citty");
|
|
16
|
-
const consola_1 = __importDefault(require("consola"));
|
|
17
|
-
const file_1 = require("../../utils/file");
|
|
18
|
-
const manifest_1 = require("../../utils/manifest");
|
|
19
|
-
const prompt_1 = require("../../utils/prompt");
|
|
20
|
-
exports.default = (0, citty_1.defineCommand)({
|
|
21
|
-
meta: {
|
|
22
|
-
description: 'Generate a manifest file.',
|
|
23
|
-
},
|
|
24
|
-
args: {
|
|
25
|
-
path: {
|
|
26
|
-
type: 'string',
|
|
27
|
-
description: 'Path to the web assets folder (e.g. `www` or `dist`).',
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
run: (ctx) => __awaiter(void 0, void 0, void 0, function* () {
|
|
31
|
-
let path = ctx.args.path;
|
|
1
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { fileExistsAtPath } from '../../utils/file.js';
|
|
5
|
+
import { generateManifestJson } from '../../utils/manifest.js';
|
|
6
|
+
import { prompt } from '../../utils/prompt.js';
|
|
7
|
+
export default defineCommand({
|
|
8
|
+
description: 'Generate a manifest file.',
|
|
9
|
+
options: defineOptions(z.object({
|
|
10
|
+
path: z.string().optional().describe('Path to the web assets folder (e.g. `www` or `dist`).'),
|
|
11
|
+
})),
|
|
12
|
+
action: async (options, args) => {
|
|
13
|
+
let path = options.path;
|
|
32
14
|
if (!path) {
|
|
33
|
-
path =
|
|
15
|
+
path = await prompt('Enter the path to the web assets folder:', {
|
|
34
16
|
type: 'text',
|
|
35
17
|
});
|
|
36
18
|
if (!path) {
|
|
37
|
-
|
|
19
|
+
consola.error('You must provide a path to the web assets folder.');
|
|
38
20
|
process.exit(1);
|
|
39
21
|
}
|
|
40
22
|
}
|
|
41
23
|
// Check if the path exists
|
|
42
|
-
const pathExists =
|
|
24
|
+
const pathExists = await fileExistsAtPath(path);
|
|
43
25
|
if (!pathExists) {
|
|
44
|
-
|
|
26
|
+
consola.error(`The path does not exist.`);
|
|
45
27
|
process.exit(1);
|
|
46
28
|
}
|
|
47
29
|
// Generate the manifest file
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
30
|
+
await generateManifestJson(path);
|
|
31
|
+
consola.success('Manifest file generated.');
|
|
32
|
+
},
|
|
51
33
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { fileExistsAtPath } from '../../utils/file.js';
|
|
2
|
+
import { generateManifestJson } from '../../utils/manifest.js';
|
|
3
|
+
import { prompt } from '../../utils/prompt.js';
|
|
4
|
+
import consola from 'consola';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import generateManifestCommand from './generate.js';
|
|
7
|
+
// Mock dependencies
|
|
8
|
+
vi.mock('@/utils/file.js');
|
|
9
|
+
vi.mock('@/utils/manifest.js');
|
|
10
|
+
vi.mock('@/utils/prompt.js');
|
|
11
|
+
vi.mock('consola');
|
|
12
|
+
describe('manifests-generate', () => {
|
|
13
|
+
const mockFileExistsAtPath = vi.mocked(fileExistsAtPath);
|
|
14
|
+
const mockGenerateManifestJson = vi.mocked(generateManifestJson);
|
|
15
|
+
const mockPrompt = vi.mocked(prompt);
|
|
16
|
+
const mockConsola = vi.mocked(consola);
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
vi.spyOn(process, 'exit').mockImplementation((code) => {
|
|
20
|
+
throw new Error(`Process exited with code ${code}`);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
vi.restoreAllMocks();
|
|
25
|
+
});
|
|
26
|
+
it('should generate manifest with provided path', async () => {
|
|
27
|
+
const options = { path: './dist' };
|
|
28
|
+
mockFileExistsAtPath.mockResolvedValue(true);
|
|
29
|
+
mockGenerateManifestJson.mockResolvedValue(undefined);
|
|
30
|
+
await generateManifestCommand.action(options, undefined);
|
|
31
|
+
expect(mockFileExistsAtPath).toHaveBeenCalledWith('./dist');
|
|
32
|
+
expect(mockGenerateManifestJson).toHaveBeenCalledWith('./dist');
|
|
33
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Manifest file generated.');
|
|
34
|
+
});
|
|
35
|
+
it('should prompt for path when not provided', async () => {
|
|
36
|
+
const options = {};
|
|
37
|
+
mockPrompt.mockResolvedValueOnce('./www');
|
|
38
|
+
mockFileExistsAtPath.mockResolvedValue(true);
|
|
39
|
+
mockGenerateManifestJson.mockResolvedValue(undefined);
|
|
40
|
+
await generateManifestCommand.action(options, undefined);
|
|
41
|
+
expect(mockPrompt).toHaveBeenCalledWith('Enter the path to the web assets folder:', {
|
|
42
|
+
type: 'text',
|
|
43
|
+
});
|
|
44
|
+
expect(mockFileExistsAtPath).toHaveBeenCalledWith('./www');
|
|
45
|
+
expect(mockGenerateManifestJson).toHaveBeenCalledWith('./www');
|
|
46
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Manifest file generated.');
|
|
47
|
+
});
|
|
48
|
+
it('should handle missing path in prompt', async () => {
|
|
49
|
+
const options = {};
|
|
50
|
+
mockPrompt.mockResolvedValueOnce('');
|
|
51
|
+
await expect(generateManifestCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
52
|
+
expect(mockConsola.error).toHaveBeenCalledWith('You must provide a path to the web assets folder.');
|
|
53
|
+
});
|
|
54
|
+
it('should handle nonexistent path', async () => {
|
|
55
|
+
const options = { path: './nonexistent' };
|
|
56
|
+
mockFileExistsAtPath.mockResolvedValue(false);
|
|
57
|
+
await expect(generateManifestCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
58
|
+
expect(mockConsola.error).toHaveBeenCalledWith('The path does not exist.');
|
|
59
|
+
});
|
|
60
|
+
});
|