@capawesome/cli 4.5.0 → 4.6.0-dev.12df3ca.1775037612
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 +7 -0
- package/dist/commands/apps/builds/create.js +145 -128
- package/dist/commands/apps/bundles/create.js +4 -2
- package/dist/commands/apps/bundles/delete.js +2 -3
- package/dist/commands/apps/bundles/update.js +2 -3
- package/dist/commands/apps/certificates/create.js +3 -19
- package/dist/commands/apps/certificates/delete.js +28 -5
- package/dist/commands/apps/certificates/get.js +28 -5
- package/dist/commands/apps/certificates/update.js +3 -1
- package/dist/commands/apps/deployments/create.js +5 -77
- package/dist/commands/apps/devices/forcechannel.js +9 -7
- package/dist/commands/apps/devices/probe.js +70 -0
- package/dist/commands/apps/devices/unforcechannel.js +9 -7
- package/dist/commands/apps/liveupdates/bundle.js +12 -2
- package/dist/commands/apps/liveupdates/create.js +293 -0
- package/dist/commands/apps/liveupdates/create.test.js +300 -0
- package/dist/commands/apps/liveupdates/generate-manifest.js +17 -1
- package/dist/commands/apps/liveupdates/generate-manifest.test.js +21 -1
- package/dist/commands/apps/liveupdates/register.js +10 -15
- package/dist/commands/apps/liveupdates/upload.js +25 -16
- package/dist/commands/manifests/generate.js +1 -1
- package/dist/index.js +2 -0
- package/dist/services/app-build-sources.js +120 -0
- package/dist/services/app-certificates.js +0 -1
- package/dist/services/app-devices.js +44 -0
- package/dist/services/authorization-service.js +5 -1
- package/dist/services/jobs.js +13 -0
- package/dist/types/app-build-source.js +1 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/custom-properties.js +22 -0
- package/dist/utils/file.js +12 -1
- package/dist/utils/job.js +77 -0
- package/dist/utils/zip.js +19 -2
- package/package.json +2 -1
|
@@ -11,7 +11,6 @@ class AppCertificatesServiceImpl {
|
|
|
11
11
|
formData.append('file', dto.buffer, { filename: dto.fileName });
|
|
12
12
|
formData.append('name', dto.name);
|
|
13
13
|
formData.append('platform', dto.platform);
|
|
14
|
-
formData.append('type', dto.type);
|
|
15
14
|
if (dto.password) {
|
|
16
15
|
formData.append('password', dto.password);
|
|
17
16
|
}
|
|
@@ -12,6 +12,42 @@ class AppDevicesServiceImpl {
|
|
|
12
12
|
},
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
+
async findOneById(data) {
|
|
16
|
+
const response = await this.httpClient.get(`/v1/apps/${data.appId}/devices/${data.deviceId}`, {
|
|
17
|
+
headers: {
|
|
18
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
19
|
+
},
|
|
20
|
+
params: {
|
|
21
|
+
relations: 'appChannel',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
return response.data;
|
|
25
|
+
}
|
|
26
|
+
async probe(data) {
|
|
27
|
+
const params = {
|
|
28
|
+
appVersionCode: data.appVersionCode,
|
|
29
|
+
appVersionName: data.appVersionName,
|
|
30
|
+
osVersion: data.osVersion,
|
|
31
|
+
platform: data.platform.toString(),
|
|
32
|
+
pluginVersion: data.pluginVersion,
|
|
33
|
+
};
|
|
34
|
+
if (data.channelName) {
|
|
35
|
+
params.channelName = data.channelName;
|
|
36
|
+
}
|
|
37
|
+
if (data.customId) {
|
|
38
|
+
params.customId = data.customId;
|
|
39
|
+
}
|
|
40
|
+
if (data.deviceId) {
|
|
41
|
+
params.deviceId = data.deviceId;
|
|
42
|
+
}
|
|
43
|
+
const response = await this.httpClient.get(`/v1/apps/${data.appId}/bundles/latest`, {
|
|
44
|
+
headers: {
|
|
45
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
46
|
+
},
|
|
47
|
+
params,
|
|
48
|
+
});
|
|
49
|
+
return response.data;
|
|
50
|
+
}
|
|
15
51
|
async update(data) {
|
|
16
52
|
await this.httpClient.patch(`/v1/apps/${data.appId}/devices/${data.deviceId}`, { forcedAppChannelId: data.forcedAppChannelId }, {
|
|
17
53
|
headers: {
|
|
@@ -19,6 +55,14 @@ class AppDevicesServiceImpl {
|
|
|
19
55
|
},
|
|
20
56
|
});
|
|
21
57
|
}
|
|
58
|
+
async updateMany(data) {
|
|
59
|
+
const ids = data.deviceIds.join(',');
|
|
60
|
+
await this.httpClient.patch(`/v1/apps/${data.appId}/devices?ids=${ids}`, { forcedAppChannelId: data.forcedAppChannelId }, {
|
|
61
|
+
headers: {
|
|
62
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
22
66
|
}
|
|
23
67
|
const appDevicesService = new AppDevicesServiceImpl(httpClient);
|
|
24
68
|
export default appDevicesService;
|
|
@@ -5,7 +5,11 @@ class AuthorizationServiceImpl {
|
|
|
5
5
|
this.userConfig = userConfig;
|
|
6
6
|
}
|
|
7
7
|
getCurrentAuthorizationToken() {
|
|
8
|
-
|
|
8
|
+
const token = this.userConfig.read().token || process.env.CAPAWESOME_CLOUD_TOKEN || process.env.CAPAWESOME_TOKEN || null;
|
|
9
|
+
// Trim to remove newline characters that may be included when pasting a token,
|
|
10
|
+
// which would cause an invalid character error in the Authorization header.
|
|
11
|
+
const trimmedToken = token?.trim();
|
|
12
|
+
return trimmedToken || null;
|
|
9
13
|
}
|
|
10
14
|
hasAuthorizationToken() {
|
|
11
15
|
return !!this.getCurrentAuthorizationToken();
|
package/dist/services/jobs.js
CHANGED
|
@@ -5,6 +5,19 @@ class JobsServiceImpl {
|
|
|
5
5
|
constructor(httpClient) {
|
|
6
6
|
this.httpClient = httpClient;
|
|
7
7
|
}
|
|
8
|
+
async findOne(dto) {
|
|
9
|
+
const params = {};
|
|
10
|
+
if (dto.relations) {
|
|
11
|
+
params.relations = dto.relations;
|
|
12
|
+
}
|
|
13
|
+
const response = await this.httpClient.get(`/v1/jobs/${dto.jobId}`, {
|
|
14
|
+
headers: {
|
|
15
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
16
|
+
},
|
|
17
|
+
params,
|
|
18
|
+
});
|
|
19
|
+
return response.data;
|
|
20
|
+
}
|
|
8
21
|
async update(options) {
|
|
9
22
|
const { jobId, dto } = options;
|
|
10
23
|
const response = await this.httpClient.patch(`/v1/jobs/${jobId}`, dto, {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/index.js
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const parseCustomProperties = (customProperty) => {
|
|
2
|
+
if (!customProperty) {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
const customProperties = {};
|
|
6
|
+
for (const property of customProperty) {
|
|
7
|
+
if (!property) {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
const separatorIndex = property.indexOf('=');
|
|
11
|
+
if (separatorIndex === -1) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const key = property.slice(0, separatorIndex).trim();
|
|
15
|
+
const value = property.slice(separatorIndex + 1).trim();
|
|
16
|
+
if (!key || !value) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
customProperties[key] = value;
|
|
20
|
+
}
|
|
21
|
+
return Object.keys(customProperties).length > 0 ? customProperties : undefined;
|
|
22
|
+
};
|
package/dist/utils/file.js
CHANGED
|
@@ -7,7 +7,10 @@ export const getFilesInDirectoryAndSubdirectories = async (path) => {
|
|
|
7
7
|
const dirEntries = await fs.promises.readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
8
8
|
for (const dirEntry of dirEntries) {
|
|
9
9
|
const fullPath = pathModule.join(directory, dirEntry.name);
|
|
10
|
-
if (dirEntry.
|
|
10
|
+
if (dirEntry.isSymbolicLink()) {
|
|
11
|
+
// Skip symlinks
|
|
12
|
+
}
|
|
13
|
+
else if (dirEntry.isDirectory()) {
|
|
11
14
|
await walk(fullPath);
|
|
12
15
|
}
|
|
13
16
|
else {
|
|
@@ -35,6 +38,14 @@ export const getFilesInDirectoryAndSubdirectories = async (path) => {
|
|
|
35
38
|
await walk(path);
|
|
36
39
|
return files;
|
|
37
40
|
};
|
|
41
|
+
export const directoryContainsSymlinks = async (path) => {
|
|
42
|
+
const dirEntries = await fs.promises.readdir(path, { withFileTypes: true, recursive: true }).catch(() => []);
|
|
43
|
+
return dirEntries.some((dirEntry) => dirEntry.isSymbolicLink());
|
|
44
|
+
};
|
|
45
|
+
export const directoryContainsSourceMaps = async (path) => {
|
|
46
|
+
const files = await getFilesInDirectoryAndSubdirectories(path);
|
|
47
|
+
return files.some((file) => file.name.endsWith('.js.map') || file.name.endsWith('.css.map'));
|
|
48
|
+
};
|
|
38
49
|
export const fileExistsAtPath = async (path) => {
|
|
39
50
|
return new Promise((resolve) => {
|
|
40
51
|
fs.access(path, fs.constants.F_OK, (err) => {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import jobsService from '../services/jobs.js';
|
|
2
|
+
import { unescapeAnsi } from '../utils/ansi.js';
|
|
3
|
+
import { wait } from '../utils/wait.js';
|
|
4
|
+
import consola from 'consola';
|
|
5
|
+
const getLabel = (job) => {
|
|
6
|
+
if (job.appBuildId) {
|
|
7
|
+
return 'build';
|
|
8
|
+
}
|
|
9
|
+
if (job.appDeploymentId) {
|
|
10
|
+
return 'deployment';
|
|
11
|
+
}
|
|
12
|
+
return 'job';
|
|
13
|
+
};
|
|
14
|
+
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
15
|
+
export const waitForJobCompletion = async (options) => {
|
|
16
|
+
const { jobId } = options;
|
|
17
|
+
let lastPrintedLogNumber = 0;
|
|
18
|
+
let isWaitingForStart = true;
|
|
19
|
+
while (true) {
|
|
20
|
+
try {
|
|
21
|
+
const job = await jobsService.findOne({ jobId, relations: 'jobLogs' });
|
|
22
|
+
const label = getLabel(job);
|
|
23
|
+
const jobStatus = job.status;
|
|
24
|
+
if (jobStatus === 'queued' || jobStatus === 'pending') {
|
|
25
|
+
if (isWaitingForStart) {
|
|
26
|
+
consola.start(`Waiting for ${label} to start (status: ${jobStatus})...`);
|
|
27
|
+
}
|
|
28
|
+
await wait(3000);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (isWaitingForStart && jobStatus === 'in_progress') {
|
|
32
|
+
isWaitingForStart = false;
|
|
33
|
+
consola.success(`${capitalize(label)} started...`);
|
|
34
|
+
}
|
|
35
|
+
if (job.jobLogs && job.jobLogs.length > 0) {
|
|
36
|
+
const newLogs = job.jobLogs
|
|
37
|
+
.filter((log) => log.number > lastPrintedLogNumber)
|
|
38
|
+
.sort((a, b) => a.number - b.number);
|
|
39
|
+
for (const log of newLogs) {
|
|
40
|
+
console.log(unescapeAnsi(log.payload));
|
|
41
|
+
lastPrintedLogNumber = log.number;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (jobStatus === 'succeeded' ||
|
|
45
|
+
jobStatus === 'failed' ||
|
|
46
|
+
jobStatus === 'canceled' ||
|
|
47
|
+
jobStatus === 'rejected' ||
|
|
48
|
+
jobStatus === 'timed_out') {
|
|
49
|
+
console.log();
|
|
50
|
+
if (jobStatus === 'succeeded') {
|
|
51
|
+
return job;
|
|
52
|
+
}
|
|
53
|
+
else if (jobStatus === 'failed') {
|
|
54
|
+
consola.error(`${capitalize(label)} failed.`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
else if (jobStatus === 'canceled') {
|
|
58
|
+
consola.warn(`${capitalize(label)} was canceled.`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
else if (jobStatus === 'rejected') {
|
|
62
|
+
consola.error(`${capitalize(label)} was rejected.`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
else if (jobStatus === 'timed_out') {
|
|
66
|
+
consola.error(`${capitalize(label)} timed out.`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
await wait(3000);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
consola.error('Error polling job status:', error);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
package/dist/utils/zip.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import AdmZip from 'adm-zip';
|
|
2
|
+
import { globby } from 'globby';
|
|
3
|
+
import path from 'path';
|
|
2
4
|
class ZipImpl {
|
|
3
5
|
async zipFolder(sourceFolder) {
|
|
4
6
|
const zip = new AdmZip();
|
|
5
7
|
zip.addLocalFolder(sourceFolder);
|
|
6
8
|
return zip.toBuffer();
|
|
7
9
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
async zipFolderWithGitignore(sourceFolder) {
|
|
11
|
+
const files = await globby(['**/*'], {
|
|
12
|
+
cwd: sourceFolder,
|
|
13
|
+
gitignore: true,
|
|
14
|
+
ignore: ['.git/**'],
|
|
15
|
+
dot: true,
|
|
16
|
+
});
|
|
17
|
+
const zip = new AdmZip();
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
const filePath = path.join(sourceFolder, file);
|
|
20
|
+
const dirName = path.dirname(file);
|
|
21
|
+
zip.addLocalFile(filePath, dirName === '.' ? '' : dirName);
|
|
22
|
+
}
|
|
23
|
+
return zip.toBuffer();
|
|
24
|
+
}
|
|
25
|
+
isZipped(filePath) {
|
|
26
|
+
return filePath.endsWith('.zip');
|
|
10
27
|
}
|
|
11
28
|
}
|
|
12
29
|
const zip = new ZipImpl();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capawesome/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0-dev.12df3ca.1775037612",
|
|
4
4
|
"description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"c12": "3.3.3",
|
|
62
62
|
"consola": "3.3.0",
|
|
63
63
|
"form-data": "4.0.4",
|
|
64
|
+
"globby": "16.1.1",
|
|
64
65
|
"http-proxy-agent": "7.0.2",
|
|
65
66
|
"https-proxy-agent": "7.0.6",
|
|
66
67
|
"mime": "4.0.7",
|