@capawesome/cli 4.26.1 → 4.27.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
|
+
## [4.27.0](https://github.com/capawesome-team/cli/compare/v4.26.1...v4.27.0) (2026-10-09)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **apps:** add `--apple-api-key-id` to `apps:destinations:create` ([#249](https://github.com/capawesome-team/cli/issues/249)) ([f8efdd8](https://github.com/capawesome-team/cli/commit/f8efdd8ff401ab4dbb1eb954db5ca044b6819501))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **apps:** upload Apple API key file only after all destination options are validated ([#250](https://github.com/capawesome-team/cli/issues/250)) ([ad0fadf](https://github.com/capawesome-team/cli/commit/ad0fadfa9b38792764d962ad7a5803448082ff1b))
|
|
16
|
+
|
|
5
17
|
## [4.26.1](https://github.com/capawesome-team/cli/compare/v4.26.0...v4.26.1) (2026-10-07)
|
|
6
18
|
|
|
7
19
|
|
|
@@ -34,6 +34,7 @@ export default defineCommand({
|
|
|
34
34
|
appleTeamId: z.string().optional().describe('Apple Team ID for the destination.'),
|
|
35
35
|
appleAppPassword: z.string().optional().describe('Apple app-specific password for the destination.'),
|
|
36
36
|
appleApiKeyFile: z.string().optional().describe('Path to the Apple API key (.p8) file.'),
|
|
37
|
+
appleApiKeyId: z.string().optional().describe('Apple API Key ID for the destination.'),
|
|
37
38
|
appleIssuerId: z.string().optional().describe('Apple Issuer ID for the destination.'),
|
|
38
39
|
appleBetaGroup: z
|
|
39
40
|
.array(z.string())
|
|
@@ -77,12 +78,11 @@ export default defineCommand({
|
|
|
77
78
|
defaultLanguage: stringOption.describe('Language of the default release notes text, e.g. `en-US` (Google Play and Huawei AppGallery only).'),
|
|
78
79
|
})),
|
|
79
80
|
action: withAuth(async (options, args) => {
|
|
80
|
-
let { appId, json, name, platform, type, appleId, appleAppId, appleTeamId, appleAppPassword, appleApiKeyFile, appleIssuerId, appleBetaGroup, appleSubmitForReview, appleReleaseType, appleRejectIfPossible, androidPackageName, androidBuildArtifactType, androidReleaseStatus, googleServiceAccountKeyFile, googlePlayTrack, firebaseAppId, firebaseTesterGroup, huaweiAppId, huaweiClientId, huaweiClientSecret, defaultLanguage, } = options;
|
|
81
|
+
let { appId, json, name, platform, type, appleId, appleAppId, appleTeamId, appleAppPassword, appleApiKeyFile, appleApiKeyId, appleIssuerId, appleBetaGroup, appleSubmitForReview, appleReleaseType, appleRejectIfPossible, androidPackageName, androidBuildArtifactType, androidReleaseStatus, googleServiceAccountKeyFile, googlePlayTrack, firebaseAppId, firebaseTesterGroup, huaweiAppId, huaweiClientId, huaweiClientSecret, defaultLanguage, } = options;
|
|
81
82
|
if (type === 'app-store-connect') {
|
|
82
83
|
consola.warn('The destination type `app-store-connect` is deprecated. Use `apple-app-store-connect` instead.');
|
|
83
84
|
type = 'apple-app-store-connect';
|
|
84
85
|
}
|
|
85
|
-
let appleApiKeyId;
|
|
86
86
|
let appAppleApiKeyId;
|
|
87
87
|
let appGoogleServiceAccountKeyId;
|
|
88
88
|
// 1. Select organization and app
|
|
@@ -231,7 +231,7 @@ export default defineCommand({
|
|
|
231
231
|
if (type === 'apple-app-store-connect') {
|
|
232
232
|
// 9. Ask for authentication method
|
|
233
233
|
let authMethod;
|
|
234
|
-
if (appleApiKeyFile || appleIssuerId) {
|
|
234
|
+
if (appleApiKeyFile || appleApiKeyId || appleIssuerId) {
|
|
235
235
|
authMethod = 'apiKey';
|
|
236
236
|
}
|
|
237
237
|
else if (appleId || appleAppId || appleAppPassword) {
|
|
@@ -252,7 +252,7 @@ export default defineCommand({
|
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
else {
|
|
255
|
-
consola.error('You must provide authentication options when running in non-interactive environment. Either pass --apple-api-key-file and --apple-issuer-id for API Key authentication or --apple-id, --apple-app-id, and --apple-app-password for Password authentication.');
|
|
255
|
+
consola.error('You must provide authentication options when running in non-interactive environment. Either pass --apple-api-key-file, --apple-api-key-id, and --apple-issuer-id for API Key authentication or --apple-id, --apple-app-id, and --apple-app-password for Password authentication.');
|
|
256
256
|
process.exit(1);
|
|
257
257
|
}
|
|
258
258
|
if (authMethod === 'apiKey') {
|
|
@@ -270,20 +270,6 @@ export default defineCommand({
|
|
|
270
270
|
process.exit(1);
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
|
-
// Upload Apple API key file
|
|
274
|
-
const appleApiKeyFileReadable = await isReadable(appleApiKeyFile);
|
|
275
|
-
if (!appleApiKeyFileReadable) {
|
|
276
|
-
consola.error(`The Apple API key file does not exist or is not accessible: ${appleApiKeyFile}`);
|
|
277
|
-
process.exit(1);
|
|
278
|
-
}
|
|
279
|
-
const buffer = fs.readFileSync(appleApiKeyFile);
|
|
280
|
-
const fileName = path.basename(appleApiKeyFile);
|
|
281
|
-
const key = await appAppleApiKeysService.create({
|
|
282
|
-
appId,
|
|
283
|
-
buffer,
|
|
284
|
-
fileName,
|
|
285
|
-
});
|
|
286
|
-
appAppleApiKeyId = key.id;
|
|
287
273
|
// 11. Ask for key ID
|
|
288
274
|
if (!appleApiKeyId) {
|
|
289
275
|
if (!isInteractive()) {
|
|
@@ -359,6 +345,9 @@ export default defineCommand({
|
|
|
359
345
|
process.exit(1);
|
|
360
346
|
}
|
|
361
347
|
}
|
|
348
|
+
if (appleApiKeyFile) {
|
|
349
|
+
appAppleApiKeyId = await uploadAppleApiKeyFile(appId, appleApiKeyFile);
|
|
350
|
+
}
|
|
362
351
|
}
|
|
363
352
|
const response = await appDestinationsService.create({
|
|
364
353
|
appId,
|
|
@@ -491,3 +480,18 @@ const uploadGoogleServiceAccountKeyFile = async (appId, googleServiceAccountKeyF
|
|
|
491
480
|
});
|
|
492
481
|
return key.id;
|
|
493
482
|
};
|
|
483
|
+
const uploadAppleApiKeyFile = async (appId, appleApiKeyFile) => {
|
|
484
|
+
const appleApiKeyFileReadable = await isReadable(appleApiKeyFile);
|
|
485
|
+
if (!appleApiKeyFileReadable) {
|
|
486
|
+
consola.error(`The Apple API key file does not exist or is not accessible: ${appleApiKeyFile}`);
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
const buffer = fs.readFileSync(appleApiKeyFile);
|
|
490
|
+
const fileName = path.basename(appleApiKeyFile);
|
|
491
|
+
const key = await appAppleApiKeysService.create({
|
|
492
|
+
appId,
|
|
493
|
+
buffer,
|
|
494
|
+
fileName,
|
|
495
|
+
});
|
|
496
|
+
return key.id;
|
|
497
|
+
};
|
|
@@ -25,7 +25,7 @@ describe('apps-destinations-create', () => {
|
|
|
25
25
|
const appId = 'app-123';
|
|
26
26
|
const destinationId = 'destination-456';
|
|
27
27
|
const googleServiceAccountKeyId = 'key-789';
|
|
28
|
-
const
|
|
28
|
+
const appAppleApiKeyId = 'apple-key-012';
|
|
29
29
|
const testToken = 'test-token';
|
|
30
30
|
let tempDirectory;
|
|
31
31
|
let googleServiceAccountKeyFile;
|
|
@@ -100,7 +100,7 @@ describe('apps-destinations-create', () => {
|
|
|
100
100
|
const keyScope = nock(DEFAULT_API_BASE_URL)
|
|
101
101
|
.post(`/v1/apps/${appId}/apple-api-keys`)
|
|
102
102
|
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
103
|
-
.reply(201, { id:
|
|
103
|
+
.reply(201, { id: appAppleApiKeyId });
|
|
104
104
|
const destinationScope = nock(DEFAULT_API_BASE_URL)
|
|
105
105
|
.post(`/v1/apps/${appId}/destinations`, {
|
|
106
106
|
appId,
|
|
@@ -110,7 +110,7 @@ describe('apps-destinations-create', () => {
|
|
|
110
110
|
appleTeamId: 'team-id',
|
|
111
111
|
appleApiKeyId: 'ABC123DEFG',
|
|
112
112
|
appleIssuerId: 'issuer-id',
|
|
113
|
-
appAppleApiKeyId
|
|
113
|
+
appAppleApiKeyId,
|
|
114
114
|
appleBetaGroups: ['External Testers', 'QA'],
|
|
115
115
|
appleRejectIfPossible: false,
|
|
116
116
|
appleReleaseType: 'after-approval',
|
|
@@ -123,6 +123,70 @@ describe('apps-destinations-create', () => {
|
|
|
123
123
|
expect(destinationScope.isDone()).toBe(true);
|
|
124
124
|
expect(mockPrompt).toHaveBeenCalledTimes(1);
|
|
125
125
|
});
|
|
126
|
+
it('should create an App Store Connect destination with the Apple API key ID non-interactively', async () => {
|
|
127
|
+
const appleApiKeyFile = path.join(tempDirectory, 'AuthKey.p8');
|
|
128
|
+
await fs.writeFile(appleApiKeyFile, 'private-key');
|
|
129
|
+
const options = {
|
|
130
|
+
appId,
|
|
131
|
+
name: 'App Store',
|
|
132
|
+
platform: 'ios',
|
|
133
|
+
type: 'apple-app-store-connect',
|
|
134
|
+
appleApiKeyFile,
|
|
135
|
+
appleApiKeyId: 'ABC123DEFG',
|
|
136
|
+
appleIssuerId: 'issuer-id',
|
|
137
|
+
appleTeamId: 'team-id',
|
|
138
|
+
};
|
|
139
|
+
const keyScope = nock(DEFAULT_API_BASE_URL)
|
|
140
|
+
.post(`/v1/apps/${appId}/apple-api-keys`)
|
|
141
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
142
|
+
.reply(201, { id: appAppleApiKeyId });
|
|
143
|
+
const destinationScope = nock(DEFAULT_API_BASE_URL)
|
|
144
|
+
.post(`/v1/apps/${appId}/destinations`, {
|
|
145
|
+
appId,
|
|
146
|
+
name: 'App Store',
|
|
147
|
+
platform: 'ios',
|
|
148
|
+
type: 'apple-app-store-connect',
|
|
149
|
+
appleTeamId: 'team-id',
|
|
150
|
+
appleApiKeyId: 'ABC123DEFG',
|
|
151
|
+
appleIssuerId: 'issuer-id',
|
|
152
|
+
appAppleApiKeyId,
|
|
153
|
+
})
|
|
154
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
155
|
+
.reply(201, { id: destinationId });
|
|
156
|
+
await createDestinationCommand.action(options, undefined);
|
|
157
|
+
expect(keyScope.isDone()).toBe(true);
|
|
158
|
+
expect(destinationScope.isDone()).toBe(true);
|
|
159
|
+
expect(mockPrompt).not.toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
it('should require the Apple API key file when only the Apple API key ID is provided non-interactively', async () => {
|
|
162
|
+
const options = {
|
|
163
|
+
appId,
|
|
164
|
+
name: 'App Store',
|
|
165
|
+
platform: 'ios',
|
|
166
|
+
type: 'apple-app-store-connect',
|
|
167
|
+
appleApiKeyId: 'ABC123DEFG',
|
|
168
|
+
};
|
|
169
|
+
await expect(createDestinationCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
170
|
+
expect(mockConsola.error).toHaveBeenCalledWith('You must provide the Apple API key file when running in non-interactive environment.');
|
|
171
|
+
});
|
|
172
|
+
it('should not upload the Apple API key file when required options are missing non-interactively', async () => {
|
|
173
|
+
const appleApiKeyFile = path.join(tempDirectory, 'AuthKey.p8');
|
|
174
|
+
await fs.writeFile(appleApiKeyFile, 'private-key');
|
|
175
|
+
const options = {
|
|
176
|
+
appId,
|
|
177
|
+
name: 'App Store',
|
|
178
|
+
platform: 'ios',
|
|
179
|
+
type: 'apple-app-store-connect',
|
|
180
|
+
appleApiKeyFile,
|
|
181
|
+
appleApiKeyId: 'ABC123DEFG',
|
|
182
|
+
appleIssuerId: 'issuer-id',
|
|
183
|
+
};
|
|
184
|
+
const keyScope = nock(DEFAULT_API_BASE_URL)
|
|
185
|
+
.post(`/v1/apps/${appId}/apple-api-keys`)
|
|
186
|
+
.reply(201, { id: appAppleApiKeyId });
|
|
187
|
+
await expect(createDestinationCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
188
|
+
expect(keyScope.isDone()).toBe(false);
|
|
189
|
+
});
|
|
126
190
|
it('should map the deprecated destination type app-store-connect to apple-app-store-connect', async () => {
|
|
127
191
|
mockIsInteractive.mockReturnValue(true);
|
|
128
192
|
mockPrompt.mockResolvedValueOnce('ABC123DEFG');
|
|
@@ -144,7 +208,7 @@ describe('apps-destinations-create', () => {
|
|
|
144
208
|
const keyScope = nock(DEFAULT_API_BASE_URL)
|
|
145
209
|
.post(`/v1/apps/${appId}/apple-api-keys`)
|
|
146
210
|
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
147
|
-
.reply(201, { id:
|
|
211
|
+
.reply(201, { id: appAppleApiKeyId });
|
|
148
212
|
const destinationScope = nock(DEFAULT_API_BASE_URL)
|
|
149
213
|
.post(`/v1/apps/${appId}/destinations`, {
|
|
150
214
|
appId,
|
|
@@ -154,7 +218,7 @@ describe('apps-destinations-create', () => {
|
|
|
154
218
|
appleTeamId: 'team-id',
|
|
155
219
|
appleApiKeyId: 'ABC123DEFG',
|
|
156
220
|
appleIssuerId: 'issuer-id',
|
|
157
|
-
appAppleApiKeyId
|
|
221
|
+
appAppleApiKeyId,
|
|
158
222
|
appleBetaGroups: ['External Testers', 'QA'],
|
|
159
223
|
appleRejectIfPossible: false,
|
|
160
224
|
appleReleaseType: 'after-approval',
|