@capawesome/cli 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [3.2.0](https://github.com/capawesome-team/cli/compare/v3.1.0...v3.2.0) (2025-09-21)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **apps:channels:create:** add `--expires-in-days` option ([#69](https://github.com/capawesome-team/cli/issues/69)) ([ef2dd36](https://github.com/capawesome-team/cli/commit/ef2dd3638653e35a3ee3390ad336f39d368cd7ca))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **apps:bundles:create:** add error handling for invalid path types ([1fae8c6](https://github.com/capawesome-team/cli/commit/1fae8c6a831bdc0f5bb1e80f2dea4fa3a981e625))
|
|
16
|
+
|
|
5
17
|
## [3.1.0](https://github.com/capawesome-team/cli/compare/v3.0.0...v3.1.0) (2025-09-17)
|
|
6
18
|
|
|
7
19
|
|
|
@@ -5,10 +5,10 @@ import appsService from '../../../services/apps.js';
|
|
|
5
5
|
import authorizationService from '../../../services/authorization-service.js';
|
|
6
6
|
import organizationsService from '../../../services/organizations.js';
|
|
7
7
|
import { createBufferFromPath, createBufferFromReadStream, createBufferFromString, isPrivateKeyContent, } from '../../../utils/buffer.js';
|
|
8
|
-
import { formatPrivateKey } from '../../../utils/private-key.js';
|
|
9
8
|
import { fileExistsAtPath, getFilesInDirectoryAndSubdirectories, isDirectory } from '../../../utils/file.js';
|
|
10
9
|
import { createHash } from '../../../utils/hash.js';
|
|
11
10
|
import { generateManifestJson } from '../../../utils/manifest.js';
|
|
11
|
+
import { formatPrivateKey } from '../../../utils/private-key.js';
|
|
12
12
|
import { prompt } from '../../../utils/prompt.js';
|
|
13
13
|
import { createSignature } from '../../../utils/signature.js';
|
|
14
14
|
import zip from '../../../utils/zip.js';
|
|
@@ -94,7 +94,7 @@ export default defineCommand({
|
|
|
94
94
|
consola.error('You must be logged in to run this command.');
|
|
95
95
|
process.exit(1);
|
|
96
96
|
}
|
|
97
|
-
//
|
|
97
|
+
// Calculate the expiration date
|
|
98
98
|
let expiresAt;
|
|
99
99
|
if (expiresInDays) {
|
|
100
100
|
const expiresAtDate = new Date();
|
|
@@ -128,6 +128,13 @@ export default defineCommand({
|
|
|
128
128
|
process.exit(1);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
+
else if (zip.isZipped(path)) {
|
|
132
|
+
// No-op
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
consola.error('The path must be either a folder or a zip file.');
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
131
138
|
}
|
|
132
139
|
// Check that the path is a directory when creating a bundle with an artifact type
|
|
133
140
|
if (artifactType === 'manifest' && path) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_API_BASE_URL } from '../../../config/consts.js';
|
|
2
2
|
import authorizationService from '../../../services/authorization-service.js';
|
|
3
|
-
import { fileExistsAtPath, isDirectory } from '../../../utils/file.js';
|
|
3
|
+
import { fileExistsAtPath, getFilesInDirectoryAndSubdirectories, isDirectory } from '../../../utils/file.js';
|
|
4
4
|
import userConfig from '../../../utils/user-config.js';
|
|
5
5
|
import consola from 'consola';
|
|
6
6
|
import nock from 'nock';
|
|
@@ -20,6 +20,7 @@ describe('apps-bundles-create', () => {
|
|
|
20
20
|
const mockUserConfig = vi.mocked(userConfig);
|
|
21
21
|
const mockAuthorizationService = vi.mocked(authorizationService);
|
|
22
22
|
const mockFileExistsAtPath = vi.mocked(fileExistsAtPath);
|
|
23
|
+
const mockGetFilesInDirectoryAndSubdirectories = vi.mocked(getFilesInDirectoryAndSubdirectories);
|
|
23
24
|
const mockIsDirectory = vi.mocked(isDirectory);
|
|
24
25
|
const mockConsola = vi.mocked(consola);
|
|
25
26
|
beforeEach(() => {
|
|
@@ -100,6 +101,9 @@ describe('apps-bundles-create', () => {
|
|
|
100
101
|
};
|
|
101
102
|
mockFileExistsAtPath.mockResolvedValue(true);
|
|
102
103
|
mockIsDirectory.mockResolvedValue(false);
|
|
104
|
+
// Mock zip utility to return true so path validation passes
|
|
105
|
+
const mockZip = await import('../../../utils/zip.js');
|
|
106
|
+
vi.mocked(mockZip.default.isZipped).mockReturnValue(true);
|
|
103
107
|
await expect(createBundleCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
104
108
|
expect(mockConsola.error).toHaveBeenCalledWith('The path must be a folder when creating a bundle with an artifact type of `manifest`.');
|
|
105
109
|
});
|
|
@@ -249,6 +253,10 @@ describe('apps-bundles-create', () => {
|
|
|
249
253
|
return Promise.resolve(false);
|
|
250
254
|
return Promise.resolve(true);
|
|
251
255
|
});
|
|
256
|
+
mockIsDirectory.mockResolvedValue(true);
|
|
257
|
+
mockGetFilesInDirectoryAndSubdirectories.mockResolvedValue([
|
|
258
|
+
{ href: 'index.html', mimeType: 'text/html', name: 'index.html', path: 'index.html' },
|
|
259
|
+
]);
|
|
252
260
|
// Mock utility functions
|
|
253
261
|
const mockBuffer = await import('../../../utils/buffer.js');
|
|
254
262
|
vi.mocked(mockBuffer.isPrivateKeyContent).mockReturnValue(false);
|
|
@@ -267,6 +275,9 @@ describe('apps-bundles-create', () => {
|
|
|
267
275
|
};
|
|
268
276
|
mockFileExistsAtPath.mockResolvedValue(true);
|
|
269
277
|
mockIsDirectory.mockResolvedValue(false);
|
|
278
|
+
// Mock zip utility to pass path validation
|
|
279
|
+
const mockZip = await import('../../../utils/zip.js');
|
|
280
|
+
vi.mocked(mockZip.default.isZipped).mockReturnValue(true);
|
|
270
281
|
// Mock utility functions
|
|
271
282
|
const mockBuffer = await import('../../../utils/buffer.js');
|
|
272
283
|
vi.mocked(mockBuffer.isPrivateKeyContent).mockReturnValue(false);
|
|
@@ -15,15 +15,31 @@ export default defineCommand({
|
|
|
15
15
|
.number()
|
|
16
16
|
.optional()
|
|
17
17
|
.describe('Maximum number of bundles that can be assigned to the channel. If more bundles are assigned, the oldest bundles will be automatically deleted.'),
|
|
18
|
+
expiresInDays: z.coerce
|
|
19
|
+
.number({
|
|
20
|
+
message: 'Expiration days must be an integer.',
|
|
21
|
+
})
|
|
22
|
+
.int({
|
|
23
|
+
message: 'Expiration days must be an integer.',
|
|
24
|
+
})
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('The number of days until the channel is automatically deleted.'),
|
|
18
27
|
ignoreErrors: z.boolean().optional().describe('Whether to ignore errors or not.'),
|
|
19
28
|
name: z.string().optional().describe('Name of the channel.'),
|
|
20
29
|
})),
|
|
21
30
|
action: async (options, args) => {
|
|
22
|
-
let { appId, bundleLimit, ignoreErrors, name } = options;
|
|
31
|
+
let { appId, bundleLimit, expiresInDays, ignoreErrors, name } = options;
|
|
23
32
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
24
33
|
consola.error('You must be logged in to run this command.');
|
|
25
34
|
process.exit(1);
|
|
26
35
|
}
|
|
36
|
+
// Calculate the expiration date
|
|
37
|
+
let expiresAt;
|
|
38
|
+
if (expiresInDays) {
|
|
39
|
+
const expiresAtDate = new Date();
|
|
40
|
+
expiresAtDate.setDate(expiresAtDate.getDate() + expiresInDays);
|
|
41
|
+
expiresAt = expiresAtDate.toISOString();
|
|
42
|
+
}
|
|
27
43
|
// Validate the app ID
|
|
28
44
|
if (!appId) {
|
|
29
45
|
const organizations = await organizationsService.findAll();
|
|
@@ -62,6 +78,7 @@ export default defineCommand({
|
|
|
62
78
|
appId,
|
|
63
79
|
name,
|
|
64
80
|
totalAppBundleLimit: bundleLimit,
|
|
81
|
+
expiresAt,
|
|
65
82
|
});
|
|
66
83
|
consola.success('Channel created successfully.');
|
|
67
84
|
consola.info(`Channel ID: ${response.id}`);
|
|
@@ -116,4 +116,32 @@ describe('apps-channels-create', () => {
|
|
|
116
116
|
await expect(createChannelCommand.action(options, undefined)).rejects.toThrow();
|
|
117
117
|
expect(scope.isDone()).toBe(true);
|
|
118
118
|
});
|
|
119
|
+
it('should create channel with expiresInDays option', async () => {
|
|
120
|
+
const appId = 'app-123';
|
|
121
|
+
const channelName = 'production';
|
|
122
|
+
const expiresInDays = 30;
|
|
123
|
+
const channelId = 'channel-456';
|
|
124
|
+
const testToken = 'test-token';
|
|
125
|
+
const options = { appId, name: channelName, expiresInDays };
|
|
126
|
+
// Calculate expected expiration date
|
|
127
|
+
const expectedExpiresAt = new Date();
|
|
128
|
+
expectedExpiresAt.setDate(expectedExpiresAt.getDate() + expiresInDays);
|
|
129
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
130
|
+
.post(`/v1/apps/${appId}/channels`, (body) => {
|
|
131
|
+
// Verify the request includes expiresAt and it's approximately correct (within 1 minute)
|
|
132
|
+
const actualExpiresAt = new Date(body.expiresAt);
|
|
133
|
+
const timeDiff = Math.abs(actualExpiresAt.getTime() - expectedExpiresAt.getTime());
|
|
134
|
+
const oneMinute = 60 * 1000;
|
|
135
|
+
return (body.appId === appId &&
|
|
136
|
+
body.name === channelName &&
|
|
137
|
+
body.totalAppBundleLimit === undefined &&
|
|
138
|
+
timeDiff < oneMinute);
|
|
139
|
+
})
|
|
140
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
141
|
+
.reply(201, { id: channelId, name: channelName });
|
|
142
|
+
await createChannelCommand.action(options, undefined);
|
|
143
|
+
expect(scope.isDone()).toBe(true);
|
|
144
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Channel created successfully.');
|
|
145
|
+
expect(mockConsola.info).toHaveBeenCalledWith(`Channel ID: ${channelId}`);
|
|
146
|
+
});
|
|
119
147
|
});
|