@capawesome/cli 4.18.4 → 4.20.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 +21 -0
- package/README.md +1 -1
- package/dist/commands/apps/automations/create.js +5 -5
- package/dist/commands/apps/automations/create.test.js +2 -2
- package/dist/commands/apps/automations/update.js +6 -5
- package/dist/commands/apps/automations/update.test.js +4 -4
- package/dist/commands/apps/import.js +523 -0
- package/dist/commands/apps/import.test.js +465 -0
- package/dist/index.js +2 -0
- package/dist/services/apps.js +9 -0
- package/dist/services/git-connections.js +29 -0
- package/dist/types/git-connection.js +1 -0
- package/dist/utils/app-import.js +11 -0
- package/dist/utils/app-import.test.js +20 -0
- package/dist/utils/appflow-export.js +435 -0
- package/dist/utils/appflow-export.test.js +400 -0
- package/package.json +5 -5
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { parseAppflowExport } from '../utils/appflow-export.js';
|
|
2
|
+
import { UserError } from '../utils/error.js';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
7
|
+
describe('appflow-export', () => {
|
|
8
|
+
let exportDirectory;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
exportDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'appflow-export-test-'));
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
fs.rmSync(exportDirectory, { recursive: true, force: true });
|
|
14
|
+
});
|
|
15
|
+
const writeAppFiles = (appFolder, files) => {
|
|
16
|
+
for (const [fileName, content] of Object.entries(files)) {
|
|
17
|
+
const filePath = path.join(exportDirectory, 'apps', appFolder, fileName);
|
|
18
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
19
|
+
if (typeof content === 'string' || Buffer.isBuffer(content)) {
|
|
20
|
+
fs.writeFileSync(filePath, content);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
fs.writeFileSync(filePath, JSON.stringify(content));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
it('should throw a user error if the export does not contain an apps directory', async () => {
|
|
28
|
+
await expect(parseAppflowExport(exportDirectory)).rejects.toThrow(UserError);
|
|
29
|
+
});
|
|
30
|
+
it('should parse a fully configured app', async () => {
|
|
31
|
+
writeAppFiles('My App-6668c18c', {
|
|
32
|
+
'app-detail.json': { id: '6668c18c', name: 'My App', appType: 'capacitor' },
|
|
33
|
+
'repo-association.json': {
|
|
34
|
+
gitProvider: 'github',
|
|
35
|
+
gitUsername: 'robingenz',
|
|
36
|
+
cloneUrl: 'https://github.com/robingenz/appflow-export-test.git',
|
|
37
|
+
},
|
|
38
|
+
'environments.json': [
|
|
39
|
+
{ id: 33513, name: 'Development', vars: { DEBUG: 'true' }, secrets: null },
|
|
40
|
+
{ id: 33515, name: 'Production', vars: {}, secrets: { API_KEY: 'secret' } },
|
|
41
|
+
],
|
|
42
|
+
'live-update-channels.json': [
|
|
43
|
+
{ id: 'd33204c6', name: 'Production' },
|
|
44
|
+
{ id: '1e168146', name: 'Staging' },
|
|
45
|
+
],
|
|
46
|
+
'native-configs.json': [
|
|
47
|
+
{
|
|
48
|
+
id: 41087,
|
|
49
|
+
name: 'Development',
|
|
50
|
+
configs: {
|
|
51
|
+
base: { name: 'My App (Dev)', bundle_id: 'dev.robingenz.app.dev' },
|
|
52
|
+
ionic: { channel_name: 'Development', update_method: 'background' },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{ id: 41089, name: 'Barebones', configs: { base: { bundle_id: 'dev.robingenz.app.bare' } } },
|
|
56
|
+
],
|
|
57
|
+
'native-build-automations.json': [
|
|
58
|
+
{
|
|
59
|
+
name: 'Android Release',
|
|
60
|
+
gitBranch: 'main',
|
|
61
|
+
type: 'package',
|
|
62
|
+
platform: 'android',
|
|
63
|
+
buildType: 'release',
|
|
64
|
+
environmentId: 33515,
|
|
65
|
+
webhook: 'https://example.com/webhook',
|
|
66
|
+
automationEnabled: true,
|
|
67
|
+
nativeConfigId: 41087,
|
|
68
|
+
signingCertificateId: 154267,
|
|
69
|
+
destinationId: null,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'iOS Store',
|
|
73
|
+
gitBranch: 'main',
|
|
74
|
+
type: 'package',
|
|
75
|
+
platform: 'ios',
|
|
76
|
+
buildType: 'development',
|
|
77
|
+
environmentId: 99999,
|
|
78
|
+
webhook: null,
|
|
79
|
+
automationEnabled: false,
|
|
80
|
+
nativeConfigId: null,
|
|
81
|
+
signingCertificateId: null,
|
|
82
|
+
destinationId: null,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
'web-build-automations.json': [
|
|
86
|
+
{
|
|
87
|
+
name: 'Web Prod',
|
|
88
|
+
gitBranch: 'main',
|
|
89
|
+
type: 'deploy',
|
|
90
|
+
platform: 'web-deploy',
|
|
91
|
+
environmentId: 33515,
|
|
92
|
+
webhook: null,
|
|
93
|
+
automationEnabled: true,
|
|
94
|
+
channelIds: ['1e168146', 'd33204c6'],
|
|
95
|
+
webPreviewEnabled: true,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
'signing-certificates/android/Debug-154267/android-signing-certificate.json': {
|
|
99
|
+
id: 154267,
|
|
100
|
+
name: 'Debug',
|
|
101
|
+
keystoreFile: 'keystore.jks',
|
|
102
|
+
keystorePassword: 'test1234',
|
|
103
|
+
keyAlias: 'key',
|
|
104
|
+
keyPassword: 'test1234',
|
|
105
|
+
},
|
|
106
|
+
'signing-certificates/android/Debug-154267/keystore.jks': Buffer.from('keystore'),
|
|
107
|
+
'signing-certificates/ios/Dev-154266/ios-signing-certificate.json': {
|
|
108
|
+
id: 154266,
|
|
109
|
+
name: 'Dev',
|
|
110
|
+
p12File: 'ios-certificate.p12',
|
|
111
|
+
p12Password: '123456',
|
|
112
|
+
provisioningProfiles: ['Test.mobileprovision'],
|
|
113
|
+
},
|
|
114
|
+
'signing-certificates/ios/Dev-154266/ios-certificate.p12': Buffer.from('p12'),
|
|
115
|
+
'signing-certificates/ios/Dev-154266/Test.mobileprovision': Buffer.from('profile'),
|
|
116
|
+
'store-destinations/android/Prod-27890/play-store-destination.json': {
|
|
117
|
+
id: 27890,
|
|
118
|
+
name: 'Prod',
|
|
119
|
+
artifactType: 'aab',
|
|
120
|
+
packageName: 'dev.robingenz.app',
|
|
121
|
+
track: 'internal',
|
|
122
|
+
releaseStatus: 'completed',
|
|
123
|
+
},
|
|
124
|
+
'store-destinations/android/Prod-27890/json-key.json': { type: 'service_account' },
|
|
125
|
+
});
|
|
126
|
+
const { apps, skippedApps } = await parseAppflowExport(exportDirectory);
|
|
127
|
+
expect(skippedApps).toEqual([]);
|
|
128
|
+
expect(apps).toHaveLength(1);
|
|
129
|
+
const app = apps[0];
|
|
130
|
+
expect(app.sourceId).toBe('6668c18c');
|
|
131
|
+
expect(app.sourceName).toBe('My App');
|
|
132
|
+
expect(app.type).toBe('capacitor');
|
|
133
|
+
expect(app.repository).toEqual({
|
|
134
|
+
ownerSlug: 'robingenz',
|
|
135
|
+
provider: 'github',
|
|
136
|
+
repositorySlug: 'appflow-export-test',
|
|
137
|
+
projectSlug: undefined,
|
|
138
|
+
});
|
|
139
|
+
expect(app.environments).toEqual([
|
|
140
|
+
{ name: 'Development', variables: [{ key: 'DEBUG', value: 'true' }], secrets: [] },
|
|
141
|
+
{ name: 'Production', variables: [], secrets: [{ key: 'API_KEY', value: 'secret' }] },
|
|
142
|
+
]);
|
|
143
|
+
expect(app.channels).toEqual(['Production', 'Staging']);
|
|
144
|
+
expect(app.configurations).toEqual([
|
|
145
|
+
{ name: 'Development', displayName: 'My App (Dev)', packageName: 'dev.robingenz.app.dev' },
|
|
146
|
+
{ name: 'Barebones', displayName: undefined, packageName: 'dev.robingenz.app.bare' },
|
|
147
|
+
]);
|
|
148
|
+
expect(app.certificates).toHaveLength(2);
|
|
149
|
+
expect(app.certificates[0]).toMatchObject({
|
|
150
|
+
name: 'Debug',
|
|
151
|
+
platform: 'android',
|
|
152
|
+
password: 'test1234',
|
|
153
|
+
keyAlias: 'key',
|
|
154
|
+
keyPassword: 'test1234',
|
|
155
|
+
});
|
|
156
|
+
expect(app.certificates[1]).toMatchObject({ name: 'Dev', platform: 'ios', password: '123456' });
|
|
157
|
+
expect(app.certificates[1].provisioningProfilePaths).toHaveLength(1);
|
|
158
|
+
expect(app.destinations).toEqual([
|
|
159
|
+
{
|
|
160
|
+
name: 'Prod',
|
|
161
|
+
platform: 'android',
|
|
162
|
+
androidPackageName: 'dev.robingenz.app',
|
|
163
|
+
androidBuildArtifactType: 'aab',
|
|
164
|
+
androidReleaseStatus: 'completed',
|
|
165
|
+
googlePlayTrack: 'internal',
|
|
166
|
+
googleServiceAccountKeyPath: expect.stringContaining('json-key.json'),
|
|
167
|
+
},
|
|
168
|
+
]);
|
|
169
|
+
expect(app.automations).toEqual([
|
|
170
|
+
{
|
|
171
|
+
name: 'Android Release',
|
|
172
|
+
platform: 'android',
|
|
173
|
+
triggerPattern: 'main',
|
|
174
|
+
buildType: 'release',
|
|
175
|
+
enabled: true,
|
|
176
|
+
appCertificateName: 'Debug',
|
|
177
|
+
appConfigurationName: 'Development',
|
|
178
|
+
appDestinationName: undefined,
|
|
179
|
+
appEnvironmentName: 'Production',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'iOS Store',
|
|
183
|
+
platform: 'ios',
|
|
184
|
+
triggerPattern: 'main',
|
|
185
|
+
buildType: 'development',
|
|
186
|
+
enabled: false,
|
|
187
|
+
appCertificateName: undefined,
|
|
188
|
+
appConfigurationName: undefined,
|
|
189
|
+
appDestinationName: undefined,
|
|
190
|
+
appEnvironmentName: undefined,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: 'Web Prod (Staging)',
|
|
194
|
+
platform: 'web',
|
|
195
|
+
triggerPattern: 'main',
|
|
196
|
+
enabled: true,
|
|
197
|
+
appChannelName: 'Staging',
|
|
198
|
+
appEnvironmentName: 'Production',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'Web Prod (Production)',
|
|
202
|
+
platform: 'web',
|
|
203
|
+
triggerPattern: 'main',
|
|
204
|
+
enabled: true,
|
|
205
|
+
appChannelName: 'Production',
|
|
206
|
+
appEnvironmentName: 'Production',
|
|
207
|
+
},
|
|
208
|
+
]);
|
|
209
|
+
expect(app.notes).toHaveLength(4);
|
|
210
|
+
expect(app.notes).toContainEqual(expect.stringContaining('webhook'));
|
|
211
|
+
expect(app.notes).toContainEqual(expect.stringContaining('unknown environment'));
|
|
212
|
+
expect(app.notes).toContainEqual(expect.stringContaining('web previews'));
|
|
213
|
+
expect(app.notes).toContainEqual(expect.stringContaining('Live Update plugin settings'));
|
|
214
|
+
});
|
|
215
|
+
it('should keep the ambiguous app type `ionic` as source app type', async () => {
|
|
216
|
+
writeAppFiles('Legacy App-11111111', {
|
|
217
|
+
'app-detail.json': { id: '11111111', name: 'Legacy App', appType: 'ionic' },
|
|
218
|
+
});
|
|
219
|
+
const { apps } = await parseAppflowExport(exportDirectory);
|
|
220
|
+
expect(apps).toHaveLength(1);
|
|
221
|
+
expect(apps[0].sourceAppType).toBe('ionic');
|
|
222
|
+
expect(apps[0].notes).toEqual([]);
|
|
223
|
+
});
|
|
224
|
+
it('should skip apps with an unsupported app type', async () => {
|
|
225
|
+
writeAppFiles('RN App-22222222', {
|
|
226
|
+
'app-detail.json': { id: '22222222', name: 'RN App', appType: 'react_native' },
|
|
227
|
+
});
|
|
228
|
+
writeAppFiles('Other App-33333333', {
|
|
229
|
+
'app-detail.json': { id: '33333333', name: 'Other App', appType: 'unknown' },
|
|
230
|
+
});
|
|
231
|
+
const { apps, skippedApps } = await parseAppflowExport(exportDirectory);
|
|
232
|
+
expect(apps).toEqual([]);
|
|
233
|
+
expect(skippedApps).toContainEqual({
|
|
234
|
+
sourceId: '22222222',
|
|
235
|
+
sourceName: 'RN App',
|
|
236
|
+
reason: expect.stringContaining('react_native'),
|
|
237
|
+
retryLater: true,
|
|
238
|
+
});
|
|
239
|
+
expect(skippedApps).toContainEqual({
|
|
240
|
+
sourceId: '33333333',
|
|
241
|
+
sourceName: 'Other App',
|
|
242
|
+
reason: expect.stringContaining('unknown'),
|
|
243
|
+
retryLater: false,
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
it('should handle an empty repo association serialized as an array', async () => {
|
|
247
|
+
writeAppFiles('No Repo-44444444', {
|
|
248
|
+
'app-detail.json': { id: '44444444', name: 'No Repo', appType: 'capacitor' },
|
|
249
|
+
'repo-association.json': [],
|
|
250
|
+
});
|
|
251
|
+
const { apps } = await parseAppflowExport(exportDirectory);
|
|
252
|
+
expect(apps[0].repository).toBeNull();
|
|
253
|
+
});
|
|
254
|
+
it('should skip a signing certificate with a missing file', async () => {
|
|
255
|
+
writeAppFiles('Broken Cert-55555555', {
|
|
256
|
+
'app-detail.json': { id: '55555555', name: 'Broken Cert', appType: 'capacitor' },
|
|
257
|
+
'signing-certificates/android/Debug-1/android-signing-certificate.json': {
|
|
258
|
+
id: 1,
|
|
259
|
+
name: 'Debug',
|
|
260
|
+
keystoreFile: 'keystore.jks',
|
|
261
|
+
keystorePassword: 'test',
|
|
262
|
+
keyAlias: 'key',
|
|
263
|
+
keyPassword: 'test',
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
const { apps } = await parseAppflowExport(exportDirectory);
|
|
267
|
+
expect(apps[0].certificates).toEqual([]);
|
|
268
|
+
expect(apps[0].notes).toContainEqual(expect.stringContaining('keystore.jks'));
|
|
269
|
+
});
|
|
270
|
+
it.each(['v2.0.0', 'v10.0.0'])('should warn about the unsupported export format version %s', async (version) => {
|
|
271
|
+
writeAppFiles('My App-6668c18c', {
|
|
272
|
+
'app-detail.json': { id: '6668c18c', name: 'My App', appType: 'capacitor' },
|
|
273
|
+
});
|
|
274
|
+
fs.writeFileSync(path.join(exportDirectory, 'manifest.json'), JSON.stringify({ version }));
|
|
275
|
+
const { warnings } = await parseAppflowExport(exportDirectory);
|
|
276
|
+
expect(warnings).toEqual([expect.stringContaining(`\`${version}\``)]);
|
|
277
|
+
});
|
|
278
|
+
it('should not warn about a supported export format version', async () => {
|
|
279
|
+
writeAppFiles('My App-6668c18c', {
|
|
280
|
+
'app-detail.json': { id: '6668c18c', name: 'My App', appType: 'capacitor' },
|
|
281
|
+
});
|
|
282
|
+
fs.writeFileSync(path.join(exportDirectory, 'manifest.json'), JSON.stringify({ version: 'v1.0.0', exportedAt: '2026-08-24T15:07:00Z' }));
|
|
283
|
+
const { warnings } = await parseAppflowExport(exportDirectory);
|
|
284
|
+
expect(warnings).toEqual([]);
|
|
285
|
+
});
|
|
286
|
+
it('should not warn about a missing manifest', async () => {
|
|
287
|
+
writeAppFiles('My App-6668c18c', {
|
|
288
|
+
'app-detail.json': { id: '6668c18c', name: 'My App', appType: 'capacitor' },
|
|
289
|
+
});
|
|
290
|
+
const { warnings } = await parseAppflowExport(exportDirectory);
|
|
291
|
+
expect(warnings).toEqual([]);
|
|
292
|
+
});
|
|
293
|
+
it('should skip certificate files that resolve outside the export', async () => {
|
|
294
|
+
writeAppFiles('Evil-77777777', {
|
|
295
|
+
'app-detail.json': { id: '77777777', name: 'Evil', appType: 'capacitor' },
|
|
296
|
+
'signing-certificates/android/Debug-1/android-signing-certificate.json': {
|
|
297
|
+
id: 1,
|
|
298
|
+
name: 'Debug',
|
|
299
|
+
keystoreFile: '../../../outside.jks',
|
|
300
|
+
keystorePassword: 'test',
|
|
301
|
+
keyAlias: 'key',
|
|
302
|
+
keyPassword: 'test',
|
|
303
|
+
},
|
|
304
|
+
'signing-certificates/ios/Dev-2/ios-signing-certificate.json': {
|
|
305
|
+
id: 2,
|
|
306
|
+
name: 'Dev',
|
|
307
|
+
p12File: '/etc/hosts',
|
|
308
|
+
p12Password: 'test',
|
|
309
|
+
provisioningProfiles: [],
|
|
310
|
+
},
|
|
311
|
+
'signing-certificates/ios/Prod-3/ios-signing-certificate.json': {
|
|
312
|
+
id: 3,
|
|
313
|
+
name: 'Prod',
|
|
314
|
+
p12File: 'ios-certificate.p12',
|
|
315
|
+
p12Password: 'test',
|
|
316
|
+
provisioningProfiles: ['Test.mobileprovision', '../../../../etc/hosts'],
|
|
317
|
+
},
|
|
318
|
+
'signing-certificates/ios/Prod-3/ios-certificate.p12': Buffer.from('p12'),
|
|
319
|
+
'signing-certificates/ios/Prod-3/Test.mobileprovision': Buffer.from('profile'),
|
|
320
|
+
});
|
|
321
|
+
const { apps } = await parseAppflowExport(exportDirectory);
|
|
322
|
+
const app = apps[0];
|
|
323
|
+
expect(app.certificates).toHaveLength(1);
|
|
324
|
+
expect(app.certificates[0].name).toBe('Prod');
|
|
325
|
+
expect(app.certificates[0].provisioningProfilePaths).toHaveLength(1);
|
|
326
|
+
expect(app.certificates[0].provisioningProfilePaths[0]).toContain('Test.mobileprovision');
|
|
327
|
+
expect(app.notes).toContainEqual(expect.stringContaining('`../../../outside.jks` is invalid'));
|
|
328
|
+
expect(app.notes).toContainEqual(expect.stringContaining('`/etc/hosts` is invalid'));
|
|
329
|
+
expect(app.notes).toContainEqual(expect.stringContaining('`../../../../etc/hosts`'));
|
|
330
|
+
});
|
|
331
|
+
it('should rename resources whose names only differ in case', async () => {
|
|
332
|
+
writeAppFiles('Dupes-88888888', {
|
|
333
|
+
'app-detail.json': { id: '88888888', name: 'Dupes', appType: 'capacitor' },
|
|
334
|
+
'environments.json': [
|
|
335
|
+
{ id: 1, name: 'Staging', vars: {}, secrets: null },
|
|
336
|
+
{ id: 2, name: 'staging', vars: {}, secrets: null },
|
|
337
|
+
],
|
|
338
|
+
'native-build-automations.json': [
|
|
339
|
+
{
|
|
340
|
+
name: 'Android Release',
|
|
341
|
+
gitBranch: 'main',
|
|
342
|
+
platform: 'android',
|
|
343
|
+
buildType: 'release',
|
|
344
|
+
environmentId: 2,
|
|
345
|
+
webhook: null,
|
|
346
|
+
automationEnabled: true,
|
|
347
|
+
nativeConfigId: null,
|
|
348
|
+
signingCertificateId: null,
|
|
349
|
+
destinationId: 27892,
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
'store-destinations/android/Prod-27890/play-store-destination.json': {
|
|
353
|
+
id: 27890,
|
|
354
|
+
name: 'Prod',
|
|
355
|
+
artifactType: 'aab',
|
|
356
|
+
packageName: 'dev.example.app',
|
|
357
|
+
track: 'internal',
|
|
358
|
+
},
|
|
359
|
+
'store-destinations/android/prod-27892/play-store-destination.json': {
|
|
360
|
+
id: 27892,
|
|
361
|
+
name: 'prod',
|
|
362
|
+
artifactType: 'aab',
|
|
363
|
+
packageName: 'dev.example.app',
|
|
364
|
+
track: 'production',
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
const { apps } = await parseAppflowExport(exportDirectory);
|
|
368
|
+
const app = apps[0];
|
|
369
|
+
expect(app.environments.map((environment) => environment.name)).toEqual(['Staging', 'staging (2)']);
|
|
370
|
+
expect(app.destinations.map((destination) => destination.name)).toEqual(['Prod', 'prod (2)']);
|
|
371
|
+
expect(app.automations[0]).toMatchObject({ appEnvironmentName: 'staging (2)', appDestinationName: 'prod (2)' });
|
|
372
|
+
expect(app.renames).toEqual([
|
|
373
|
+
'The environment `staging` was renamed to `staging (2)` because its name is already taken.',
|
|
374
|
+
'The destination `prod` was renamed to `prod (2)` because its name is already taken.',
|
|
375
|
+
]);
|
|
376
|
+
});
|
|
377
|
+
it.each([
|
|
378
|
+
['https://user:secret-token@git.example.com/owner/repo.git', 'https://git.example.com/owner/repo.git'],
|
|
379
|
+
['https://user:secret-token@', 'https://'],
|
|
380
|
+
['user:secret-token@git.example.com:owner/repo.git', 'git.example.com:owner/repo.git'],
|
|
381
|
+
])('should not echo credentials of the unsupported repository url %s', async (cloneUrl, sanitizedUrl) => {
|
|
382
|
+
writeAppFiles('Private Repo-99999999', {
|
|
383
|
+
'app-detail.json': { id: '99999999', name: 'Private Repo', appType: 'capacitor' },
|
|
384
|
+
'repo-association.json': { gitProvider: 'gitlab_enterprise', cloneUrl },
|
|
385
|
+
});
|
|
386
|
+
const { apps } = await parseAppflowExport(exportDirectory);
|
|
387
|
+
expect(apps[0].repository).toBeNull();
|
|
388
|
+
expect(apps[0].notes).toContainEqual(expect.stringContaining(`\`${sanitizedUrl}\``));
|
|
389
|
+
expect(JSON.stringify(apps[0].notes)).not.toContain('secret-token');
|
|
390
|
+
});
|
|
391
|
+
it('should skip an app with an invalid app detail file', async () => {
|
|
392
|
+
writeAppFiles('Invalid-66666666', {
|
|
393
|
+
'app-detail.json': 'not json',
|
|
394
|
+
});
|
|
395
|
+
const { apps, skippedApps } = await parseAppflowExport(exportDirectory);
|
|
396
|
+
expect(apps).toEqual([]);
|
|
397
|
+
expect(skippedApps).toHaveLength(1);
|
|
398
|
+
expect(skippedApps[0].retryLater).toBe(false);
|
|
399
|
+
});
|
|
400
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capawesome/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.20.0",
|
|
4
4
|
"description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@clack/prompts": "0.7.0",
|
|
59
59
|
"@napi-rs/keyring": "1.3.0",
|
|
60
60
|
"@sentry/node": "10.58.0",
|
|
61
|
-
"adm-zip": "0.6.
|
|
61
|
+
"adm-zip": "0.6.1",
|
|
62
62
|
"axios": "1.18.1",
|
|
63
63
|
"axios-retry": "4.5.0",
|
|
64
64
|
"c12": "3.3.3",
|
|
@@ -81,14 +81,14 @@
|
|
|
81
81
|
"@types/mime": "3.0.4",
|
|
82
82
|
"@types/node": "24.2.1",
|
|
83
83
|
"@types/semver": "7.5.8",
|
|
84
|
-
"@vitest/coverage-v8": "4.1.
|
|
85
|
-
"@vitest/ui": "4.1.
|
|
84
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
85
|
+
"@vitest/ui": "4.1.11",
|
|
86
86
|
"nock": "14.0.10",
|
|
87
87
|
"prettier": "3.3.3",
|
|
88
88
|
"rimraf": "6.0.1",
|
|
89
89
|
"tsc-alias": "1.8.16",
|
|
90
90
|
"typescript": "5.6.3",
|
|
91
|
-
"vitest": "4.1.
|
|
91
|
+
"vitest": "4.1.11"
|
|
92
92
|
},
|
|
93
93
|
"prettier": "@ionic/prettier-config"
|
|
94
94
|
}
|