@capawesome/cli 4.6.0 → 4.8.0-dev.efa0850.1775645973
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 +35 -0
- package/dist/commands/apps/builds/create.js +160 -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/create.js +23 -4
- package/dist/commands/apps/deployments/create.js +5 -77
- package/dist/commands/apps/devices/forcechannel.js +9 -7
- package/dist/commands/apps/devices/unforcechannel.js +9 -7
- package/dist/commands/apps/link.js +34 -0
- package/dist/commands/apps/link.test.js +94 -0
- 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/apps/transfer.js +47 -0
- package/dist/commands/apps/transfer.test.js +123 -0
- package/dist/commands/apps/unlink.js +35 -0
- package/dist/commands/apps/unlink.test.js +99 -0
- package/dist/commands/manifests/generate.js +1 -1
- package/dist/index.js +13 -5
- package/dist/services/app-build-sources.js +120 -0
- package/dist/services/app-certificates.js +0 -1
- package/dist/services/app-devices.js +8 -0
- package/dist/services/apps.js +25 -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/error.js +6 -0
- package/dist/utils/file.js +12 -1
- package/dist/utils/git.js +92 -0
- package/dist/utils/git.test.js +130 -0
- package/dist/utils/job.js +77 -0
- package/dist/utils/prompt.js +1 -1
- package/dist/utils/zip.js +19 -2
- package/package.json +2 -1
package/dist/services/apps.js
CHANGED
|
@@ -42,6 +42,31 @@ class AppsServiceImpl {
|
|
|
42
42
|
});
|
|
43
43
|
return response.data;
|
|
44
44
|
}
|
|
45
|
+
async linkRepository(dto) {
|
|
46
|
+
const { appId, ...bodyData } = dto;
|
|
47
|
+
const response = await this.httpClient.put(`/v1/apps/${appId}/repository`, bodyData, {
|
|
48
|
+
headers: {
|
|
49
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
return response.data;
|
|
53
|
+
}
|
|
54
|
+
async transfer(dto) {
|
|
55
|
+
const { appId, ...bodyData } = dto;
|
|
56
|
+
const response = await this.httpClient.post(`/v1/apps/${appId}/transfer`, bodyData, {
|
|
57
|
+
headers: {
|
|
58
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
return response.data;
|
|
62
|
+
}
|
|
63
|
+
async unlinkRepository(dto) {
|
|
64
|
+
await this.httpClient.delete(`/v1/apps/${dto.appId}/repository`, {
|
|
65
|
+
headers: {
|
|
66
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
45
70
|
}
|
|
46
71
|
const appsService = new AppsServiceImpl(httpClient);
|
|
47
72
|
export default appsService;
|
|
@@ -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/error.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { AxiosError } from 'axios';
|
|
2
2
|
import { ZodError } from 'zod';
|
|
3
|
+
export class UserError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'UserError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
3
9
|
export const getMessageFromUnknownError = (error) => {
|
|
4
10
|
let message = 'An unknown error has occurred.';
|
|
5
11
|
if (error instanceof AxiosError) {
|
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,92 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { UserError } from '../utils/error.js';
|
|
3
|
+
const HOSTNAME_TO_PROVIDER = {
|
|
4
|
+
'github.com': 'github',
|
|
5
|
+
'gitlab.com': 'gitlab',
|
|
6
|
+
'bitbucket.org': 'bitbucket',
|
|
7
|
+
'dev.azure.com': 'azure',
|
|
8
|
+
'ssh.dev.azure.com': 'azure',
|
|
9
|
+
};
|
|
10
|
+
export const getGitRemoteInfo = () => {
|
|
11
|
+
const remoteUrl = getGitRemoteUrl();
|
|
12
|
+
return parseGitRemoteUrl(remoteUrl);
|
|
13
|
+
};
|
|
14
|
+
const getGitRemoteUrl = () => {
|
|
15
|
+
try {
|
|
16
|
+
return execSync('git remote get-url origin', { encoding: 'utf-8' }).trim();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new UserError('Could not read the git remote URL. Make sure you are inside a git repository with an origin remote.');
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export const parseGitRemoteUrl = (remoteUrl) => {
|
|
23
|
+
// Azure DevOps HTTPS: https://dev.azure.com/{org}/{project}/_git/{repo}
|
|
24
|
+
const azureHttpsMatch = remoteUrl.match(/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/]+?)(?:\.git)?$/);
|
|
25
|
+
if (azureHttpsMatch && azureHttpsMatch[1] && azureHttpsMatch[2] && azureHttpsMatch[3]) {
|
|
26
|
+
return {
|
|
27
|
+
ownerSlug: azureHttpsMatch[1],
|
|
28
|
+
provider: 'azure',
|
|
29
|
+
repositorySlug: azureHttpsMatch[3],
|
|
30
|
+
projectSlug: azureHttpsMatch[2],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Azure DevOps SSH: git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
|
|
34
|
+
const azureSshMatch = remoteUrl.match(/ssh\.dev\.azure\.com:v3\/([^/]+)\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
35
|
+
if (azureSshMatch && azureSshMatch[1] && azureSshMatch[2] && azureSshMatch[3]) {
|
|
36
|
+
return {
|
|
37
|
+
ownerSlug: azureSshMatch[1],
|
|
38
|
+
provider: 'azure',
|
|
39
|
+
repositorySlug: azureSshMatch[3],
|
|
40
|
+
projectSlug: azureSshMatch[2],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Visual Studio HTTPS: https://{org}.visualstudio.com/{project}/_git/{repo}
|
|
44
|
+
const vsHttpsMatch = remoteUrl.match(/([^/]+)\.visualstudio\.com\/([^/]+)\/_git\/([^/]+?)(?:\.git)?$/);
|
|
45
|
+
if (vsHttpsMatch && vsHttpsMatch[1] && vsHttpsMatch[2] && vsHttpsMatch[3]) {
|
|
46
|
+
return {
|
|
47
|
+
ownerSlug: vsHttpsMatch[1],
|
|
48
|
+
provider: 'azure',
|
|
49
|
+
repositorySlug: vsHttpsMatch[3],
|
|
50
|
+
projectSlug: vsHttpsMatch[2],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// SSH: git@{host}:{owner}[/{subgroup}]/{repo}.git
|
|
54
|
+
const sshMatch = remoteUrl.match(/git@([^:]+):([^/]+)(?:\/([^/]+))?\/([^/]+?)(?:\.git)?$/);
|
|
55
|
+
if (sshMatch && sshMatch[1] && sshMatch[2] && sshMatch[4]) {
|
|
56
|
+
const hostname = sshMatch[1];
|
|
57
|
+
const provider = HOSTNAME_TO_PROVIDER[hostname];
|
|
58
|
+
if (!provider) {
|
|
59
|
+
throw new UserError(`Unsupported git provider for hostname "${hostname}".`);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
ownerSlug: sshMatch[2],
|
|
63
|
+
provider,
|
|
64
|
+
repositorySlug: sshMatch[4],
|
|
65
|
+
projectSlug: sshMatch[3],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// HTTPS: https://[user@]{host}/{owner}[/{subgroup}]/{repo}.git
|
|
69
|
+
try {
|
|
70
|
+
const url = new URL(remoteUrl);
|
|
71
|
+
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
|
72
|
+
const hostname = url.hostname;
|
|
73
|
+
const provider = HOSTNAME_TO_PROVIDER[hostname];
|
|
74
|
+
if (!provider) {
|
|
75
|
+
throw new UserError(`Unsupported git provider for hostname "${hostname}".`);
|
|
76
|
+
}
|
|
77
|
+
const pathSegments = url.pathname.split('/').filter(Boolean);
|
|
78
|
+
const repositorySlug = pathSegments.pop()?.replace(/\.git$/, '');
|
|
79
|
+
const ownerSlug = pathSegments.shift();
|
|
80
|
+
const projectSlug = pathSegments.length > 0 ? pathSegments.join('/') : undefined;
|
|
81
|
+
if (ownerSlug && repositorySlug) {
|
|
82
|
+
return { ownerSlug, provider, repositorySlug, projectSlug };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof UserError) {
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
throw new UserError('Could not parse git remote URL.');
|
|
92
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { parseGitRemoteUrl } from './git.js';
|
|
3
|
+
describe('parseGitRemoteUrl', () => {
|
|
4
|
+
it('should parse GitHub HTTPS URL', () => {
|
|
5
|
+
const result = parseGitRemoteUrl('https://github.com/capawesome-team/cli.git');
|
|
6
|
+
expect(result).toEqual({
|
|
7
|
+
ownerSlug: 'capawesome-team',
|
|
8
|
+
provider: 'github',
|
|
9
|
+
repositorySlug: 'cli',
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
it('should parse GitHub HTTPS URL without .git suffix', () => {
|
|
13
|
+
const result = parseGitRemoteUrl('https://github.com/capawesome-team/cli');
|
|
14
|
+
expect(result).toEqual({
|
|
15
|
+
ownerSlug: 'capawesome-team',
|
|
16
|
+
provider: 'github',
|
|
17
|
+
repositorySlug: 'cli',
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
it('should parse GitHub SSH URL', () => {
|
|
21
|
+
const result = parseGitRemoteUrl('git@github.com:capawesome-team/cli.git');
|
|
22
|
+
expect(result).toEqual({
|
|
23
|
+
ownerSlug: 'capawesome-team',
|
|
24
|
+
provider: 'github',
|
|
25
|
+
repositorySlug: 'cli',
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
it('should parse GitHub SSH URL without .git suffix', () => {
|
|
29
|
+
const result = parseGitRemoteUrl('git@github.com:capawesome-team/cli');
|
|
30
|
+
expect(result).toEqual({
|
|
31
|
+
ownerSlug: 'capawesome-team',
|
|
32
|
+
provider: 'github',
|
|
33
|
+
repositorySlug: 'cli',
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
it('should parse GitLab HTTPS URL', () => {
|
|
37
|
+
const result = parseGitRemoteUrl('https://gitlab.com/my-group/my-repo.git');
|
|
38
|
+
expect(result).toEqual({
|
|
39
|
+
ownerSlug: 'my-group',
|
|
40
|
+
provider: 'gitlab',
|
|
41
|
+
repositorySlug: 'my-repo',
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
it('should parse GitLab SSH URL', () => {
|
|
45
|
+
const result = parseGitRemoteUrl('git@gitlab.com:my-group/my-repo.git');
|
|
46
|
+
expect(result).toEqual({
|
|
47
|
+
ownerSlug: 'my-group',
|
|
48
|
+
provider: 'gitlab',
|
|
49
|
+
repositorySlug: 'my-repo',
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
it('should parse GitLab HTTPS URL with subgroup', () => {
|
|
53
|
+
const result = parseGitRemoteUrl('https://gitlab.com/my-group/my-subgroup/my-repo.git');
|
|
54
|
+
expect(result).toEqual({
|
|
55
|
+
ownerSlug: 'my-group',
|
|
56
|
+
provider: 'gitlab',
|
|
57
|
+
repositorySlug: 'my-repo',
|
|
58
|
+
projectSlug: 'my-subgroup',
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
it('should parse GitLab SSH URL with subgroup', () => {
|
|
62
|
+
const result = parseGitRemoteUrl('git@gitlab.com:my-group/my-subgroup/my-repo.git');
|
|
63
|
+
expect(result).toEqual({
|
|
64
|
+
ownerSlug: 'my-group',
|
|
65
|
+
provider: 'gitlab',
|
|
66
|
+
repositorySlug: 'my-repo',
|
|
67
|
+
projectSlug: 'my-subgroup',
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
it('should parse Bitbucket HTTPS URL', () => {
|
|
71
|
+
const result = parseGitRemoteUrl('https://bitbucket.org/my-team/my-repo.git');
|
|
72
|
+
expect(result).toEqual({
|
|
73
|
+
ownerSlug: 'my-team',
|
|
74
|
+
provider: 'bitbucket',
|
|
75
|
+
repositorySlug: 'my-repo',
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
it('should parse Bitbucket SSH URL', () => {
|
|
79
|
+
const result = parseGitRemoteUrl('git@bitbucket.org:my-team/my-repo.git');
|
|
80
|
+
expect(result).toEqual({
|
|
81
|
+
ownerSlug: 'my-team',
|
|
82
|
+
provider: 'bitbucket',
|
|
83
|
+
repositorySlug: 'my-repo',
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
it('should parse Azure DevOps HTTPS URL', () => {
|
|
87
|
+
const result = parseGitRemoteUrl('https://dev.azure.com/my-org/my-project/_git/my-repo');
|
|
88
|
+
expect(result).toEqual({
|
|
89
|
+
ownerSlug: 'my-org',
|
|
90
|
+
provider: 'azure',
|
|
91
|
+
repositorySlug: 'my-repo',
|
|
92
|
+
projectSlug: 'my-project',
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
it('should parse Azure DevOps SSH URL', () => {
|
|
96
|
+
const result = parseGitRemoteUrl('git@ssh.dev.azure.com:v3/my-org/my-project/my-repo');
|
|
97
|
+
expect(result).toEqual({
|
|
98
|
+
ownerSlug: 'my-org',
|
|
99
|
+
provider: 'azure',
|
|
100
|
+
repositorySlug: 'my-repo',
|
|
101
|
+
projectSlug: 'my-project',
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
it('should parse Visual Studio HTTPS URL', () => {
|
|
105
|
+
const result = parseGitRemoteUrl('https://my-org.visualstudio.com/my-project/_git/my-repo');
|
|
106
|
+
expect(result).toEqual({
|
|
107
|
+
ownerSlug: 'my-org',
|
|
108
|
+
provider: 'azure',
|
|
109
|
+
repositorySlug: 'my-repo',
|
|
110
|
+
projectSlug: 'my-project',
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
it('should parse GitHub HTTPS URL with credentials', () => {
|
|
114
|
+
const result = parseGitRemoteUrl('https://x-access-token:ghp_secret123@github.com/capawesome-team/cli.git');
|
|
115
|
+
expect(result).toEqual({
|
|
116
|
+
ownerSlug: 'capawesome-team',
|
|
117
|
+
provider: 'github',
|
|
118
|
+
repositorySlug: 'cli',
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
it('should throw for unsupported hostname', () => {
|
|
122
|
+
expect(() => parseGitRemoteUrl('https://example.com/owner/repo.git')).toThrow('Unsupported git provider for hostname "example.com".');
|
|
123
|
+
});
|
|
124
|
+
it('should not leak credentials in error messages', () => {
|
|
125
|
+
expect(() => parseGitRemoteUrl('https://token@example.com/owner/repo.git')).toThrow('Unsupported git provider for hostname "example.com".');
|
|
126
|
+
});
|
|
127
|
+
it('should throw for unparseable URL', () => {
|
|
128
|
+
expect(() => parseGitRemoteUrl('not-a-url')).toThrow('Could not parse git remote URL.');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -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.error(`${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/prompt.js
CHANGED
|
@@ -31,7 +31,7 @@ export const promptOrganizationSelection = async (options) => {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
34
|
-
const organizationId = await prompt('Which organization do you want to use?', {
|
|
34
|
+
const organizationId = await prompt(options?.message ?? 'Which organization do you want to use?', {
|
|
35
35
|
type: 'select',
|
|
36
36
|
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
37
37
|
});
|
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.8.0-dev.efa0850.1775645973",
|
|
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",
|