@capawesome/cli 3.4.2 → 3.5.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,15 @@
|
|
|
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.5.0](https://github.com/capawesome-team/cli/compare/v3.4.2...v3.5.0) (2025-11-24)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* add `apps:builds:download` command ([#98](https://github.com/capawesome-team/cli/issues/98)) ([5f9ba10](https://github.com/capawesome-team/cli/commit/5f9ba1068afc835aacb2e6a356abbc90d7ebd70c))
|
|
11
|
+
* add `apps:builds:logs` command ([#96](https://github.com/capawesome-team/cli/issues/96)) ([30ebd7e](https://github.com/capawesome-team/cli/commit/30ebd7e0159c53772f8ab80a9757e8ed957036ba))
|
|
12
|
+
* add `apps:deployments:logs` command ([#97](https://github.com/capawesome-team/cli/issues/97)) ([9c91fd1](https://github.com/capawesome-team/cli/commit/9c91fd1ca6d3e6a31465e0671ced534f1f5fa138))
|
|
13
|
+
|
|
5
14
|
## [3.4.2](https://github.com/capawesome-team/cli/compare/v3.4.1...v3.4.2) (2025-11-22)
|
|
6
15
|
|
|
7
16
|
## [3.4.1](https://github.com/capawesome-team/cli/compare/v3.4.0...v3.4.1) (2025-11-20)
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import appBuildsService from '../../../services/app-builds.js';
|
|
2
|
+
import appsService from '../../../services/apps.js';
|
|
3
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
4
|
+
import organizationsService from '../../../services/organizations.js';
|
|
5
|
+
import { prompt } from '../../../utils/prompt.js';
|
|
6
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
7
|
+
import consola from 'consola';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { hasTTY } from 'std-env';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
export default defineCommand({
|
|
13
|
+
description: 'Download an app build.',
|
|
14
|
+
options: defineOptions(z.object({
|
|
15
|
+
appId: z
|
|
16
|
+
.uuid({
|
|
17
|
+
message: 'App ID must be a UUID.',
|
|
18
|
+
})
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('App ID the build belongs to.'),
|
|
21
|
+
buildId: z
|
|
22
|
+
.uuid({
|
|
23
|
+
message: 'Build ID must be a UUID.',
|
|
24
|
+
})
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Build ID to download.'),
|
|
27
|
+
apk: z
|
|
28
|
+
.union([z.boolean(), z.string()])
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Download the APK artifact. Optionally provide a file path.'),
|
|
31
|
+
aab: z
|
|
32
|
+
.union([z.boolean(), z.string()])
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('Download the AAB artifact. Optionally provide a file path.'),
|
|
35
|
+
ipa: z
|
|
36
|
+
.union([z.boolean(), z.string()])
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Download the IPA artifact. Optionally provide a file path.'),
|
|
39
|
+
})),
|
|
40
|
+
action: async (options) => {
|
|
41
|
+
let { appId, buildId } = options;
|
|
42
|
+
// Check if the user is logged in
|
|
43
|
+
if (!authorizationService.hasAuthorizationToken()) {
|
|
44
|
+
consola.error('You must be logged in to run this command.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
// Prompt for app ID if not provided
|
|
48
|
+
if (!appId) {
|
|
49
|
+
if (!hasTTY) {
|
|
50
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
const organizations = await organizationsService.findAll();
|
|
54
|
+
if (organizations.length === 0) {
|
|
55
|
+
consola.error('You must create an organization before downloading a build.');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
59
|
+
const organizationId = await prompt('Select the organization of the app for which you want to download a build:', {
|
|
60
|
+
type: 'select',
|
|
61
|
+
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
62
|
+
});
|
|
63
|
+
if (!organizationId) {
|
|
64
|
+
consola.error('You must select the organization of an app for which you want to download a build.');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const apps = await appsService.findAll({
|
|
68
|
+
organizationId,
|
|
69
|
+
});
|
|
70
|
+
if (apps.length === 0) {
|
|
71
|
+
consola.error('You must create an app before downloading a build.');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
75
|
+
appId = await prompt('Select the app for which you want to download a build:', {
|
|
76
|
+
type: 'select',
|
|
77
|
+
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
78
|
+
});
|
|
79
|
+
if (!appId) {
|
|
80
|
+
consola.error('You must select an app to download a build for.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Prompt for build ID if not provided
|
|
85
|
+
if (!buildId) {
|
|
86
|
+
if (!hasTTY) {
|
|
87
|
+
consola.error('You must provide a build ID when running in non-interactive environment.');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
const builds = await appBuildsService.findAll({ appId });
|
|
91
|
+
if (builds.length === 0) {
|
|
92
|
+
consola.error('No builds found for this app.');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
96
|
+
buildId = await prompt('Select the build you want to download:', {
|
|
97
|
+
type: 'select',
|
|
98
|
+
options: builds.map((build) => ({
|
|
99
|
+
label: `Build #${build.numberAsString || build.id} (${build.platform} - ${build.type})`,
|
|
100
|
+
value: build.id,
|
|
101
|
+
})),
|
|
102
|
+
});
|
|
103
|
+
if (!buildId) {
|
|
104
|
+
consola.error('You must select a build to download.');
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Fetch the build details to get the job ID
|
|
109
|
+
const build = await appBuildsService.findOne({ appId, appBuildId: buildId, relations: 'appBuildArtifacts,job' });
|
|
110
|
+
if (build.job?.status !== 'succeeded') {
|
|
111
|
+
consola.error('The build has not succeeded yet. Cannot download artifacts for incomplete builds.');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
// Validate platform-specific artifact flags
|
|
115
|
+
if (build.platform === 'android' && options.ipa) {
|
|
116
|
+
consola.error('Cannot download IPA artifact for an Android build.');
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
if (build.platform === 'ios' && (options.apk || options.aab)) {
|
|
120
|
+
consola.error('Cannot download APK or AAB artifacts for an iOS build.');
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
// Determine which artifacts to download
|
|
124
|
+
let downloadApk = options.apk;
|
|
125
|
+
let downloadAab = options.aab;
|
|
126
|
+
let downloadIpa = options.ipa;
|
|
127
|
+
// Prompt for artifact types if none were provided
|
|
128
|
+
if (!downloadApk && !downloadAab && !downloadIpa) {
|
|
129
|
+
if (!hasTTY) {
|
|
130
|
+
consola.error('You must specify at least one artifact type (--apk, --aab, or --ipa) when running in non-interactive environment.');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
// Get available artifact types from the build
|
|
134
|
+
const availableArtifacts = build.appBuildArtifacts?.filter((artifact) => artifact.status === 'ready').map((artifact) => artifact.type) ||
|
|
135
|
+
[];
|
|
136
|
+
if (availableArtifacts.length === 0) {
|
|
137
|
+
consola.error('No artifacts available for download.');
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
// Create options based on available artifacts and platform
|
|
141
|
+
const artifactOptions = [];
|
|
142
|
+
if (build.platform === 'android') {
|
|
143
|
+
if (availableArtifacts.includes('apk')) {
|
|
144
|
+
artifactOptions.push({ label: 'APK', value: 'apk' });
|
|
145
|
+
}
|
|
146
|
+
if (availableArtifacts.includes('aab')) {
|
|
147
|
+
artifactOptions.push({ label: 'AAB', value: 'aab' });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (build.platform === 'ios') {
|
|
151
|
+
if (availableArtifacts.includes('ipa')) {
|
|
152
|
+
artifactOptions.push({ label: 'IPA', value: 'ipa' });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
156
|
+
const selectedArtifacts = await prompt('Which artifact type(s) do you want to download:', {
|
|
157
|
+
type: 'multiselect',
|
|
158
|
+
options: artifactOptions,
|
|
159
|
+
});
|
|
160
|
+
if (!selectedArtifacts || selectedArtifacts.length === 0) {
|
|
161
|
+
consola.error('You must select at least one artifact type to download.');
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
// Set flags based on selection
|
|
165
|
+
downloadApk = selectedArtifacts.includes('apk');
|
|
166
|
+
downloadAab = selectedArtifacts.includes('aab');
|
|
167
|
+
downloadIpa = selectedArtifacts.includes('ipa');
|
|
168
|
+
}
|
|
169
|
+
// Download artifacts if flags are set
|
|
170
|
+
if (downloadApk) {
|
|
171
|
+
await handleArtifactDownload({
|
|
172
|
+
appId,
|
|
173
|
+
buildId: buildId,
|
|
174
|
+
buildArtifacts: build.appBuildArtifacts,
|
|
175
|
+
artifactType: 'apk',
|
|
176
|
+
filePath: typeof options.apk === 'string' ? options.apk : undefined,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (downloadAab) {
|
|
180
|
+
await handleArtifactDownload({
|
|
181
|
+
appId,
|
|
182
|
+
buildId: buildId,
|
|
183
|
+
buildArtifacts: build.appBuildArtifacts,
|
|
184
|
+
artifactType: 'aab',
|
|
185
|
+
filePath: typeof options.aab === 'string' ? options.aab : undefined,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (downloadIpa) {
|
|
189
|
+
await handleArtifactDownload({
|
|
190
|
+
appId,
|
|
191
|
+
buildId: buildId,
|
|
192
|
+
buildArtifacts: build.appBuildArtifacts,
|
|
193
|
+
artifactType: 'ipa',
|
|
194
|
+
filePath: typeof options.ipa === 'string' ? options.ipa : undefined,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
/**
|
|
200
|
+
* Download a build artifact (APK, AAB, or IPA).
|
|
201
|
+
*/
|
|
202
|
+
const handleArtifactDownload = async (options) => {
|
|
203
|
+
const { appId, buildId, buildArtifacts, artifactType, filePath } = options;
|
|
204
|
+
try {
|
|
205
|
+
const artifactTypeUpper = artifactType.toUpperCase();
|
|
206
|
+
consola.start(`Downloading ${artifactTypeUpper}...`);
|
|
207
|
+
// Find the artifact
|
|
208
|
+
const artifact = buildArtifacts?.find((artifact) => artifact.type === artifactType);
|
|
209
|
+
if (!artifact) {
|
|
210
|
+
consola.warn(`No ${artifactTypeUpper} artifact found for this build.`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (artifact.status !== 'ready') {
|
|
214
|
+
consola.warn(`${artifactTypeUpper} artifact is not ready (status: ${artifact.status}).`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Download the artifact
|
|
218
|
+
const artifactData = await appBuildsService.downloadArtifact({
|
|
219
|
+
appId,
|
|
220
|
+
appBuildId: buildId,
|
|
221
|
+
artifactId: artifact.id,
|
|
222
|
+
});
|
|
223
|
+
// Determine the file path
|
|
224
|
+
let outputPath;
|
|
225
|
+
if (filePath) {
|
|
226
|
+
// Use provided path (can be relative or absolute)
|
|
227
|
+
outputPath = path.resolve(filePath);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
// Default to current working directory with build ID as filename
|
|
231
|
+
outputPath = path.resolve(`${buildId}.${artifactType}`);
|
|
232
|
+
}
|
|
233
|
+
// Save the file
|
|
234
|
+
await fs.writeFile(outputPath, Buffer.from(artifactData));
|
|
235
|
+
consola.success(`${artifactTypeUpper} downloaded successfully: ${outputPath}`);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
consola.error(`Failed to download ${artifactType.toUpperCase()}:`, error);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import appBuildsService from '../../../services/app-builds.js';
|
|
2
|
+
import appsService from '../../../services/apps.js';
|
|
3
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
4
|
+
import organizationsService from '../../../services/organizations.js';
|
|
5
|
+
import { unescapeAnsi } from '../../../utils/ansi.js';
|
|
6
|
+
import { prompt } from '../../../utils/prompt.js';
|
|
7
|
+
import { wait } from '../../../utils/wait.js';
|
|
8
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
9
|
+
import consola from 'consola';
|
|
10
|
+
import { hasTTY } from 'std-env';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
export default defineCommand({
|
|
13
|
+
description: 'Show logs of an app build.',
|
|
14
|
+
options: defineOptions(z.object({
|
|
15
|
+
appId: z
|
|
16
|
+
.uuid({
|
|
17
|
+
message: 'App ID must be a UUID.',
|
|
18
|
+
})
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('App ID to display the build logs for.'),
|
|
21
|
+
buildId: z
|
|
22
|
+
.uuid({
|
|
23
|
+
message: 'Build ID must be a UUID.',
|
|
24
|
+
})
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Build ID to display the build logs for.'),
|
|
27
|
+
})),
|
|
28
|
+
action: async (options) => {
|
|
29
|
+
let { appId, buildId } = options;
|
|
30
|
+
// Check if the user is logged in
|
|
31
|
+
if (!authorizationService.hasAuthorizationToken()) {
|
|
32
|
+
consola.error('You must be logged in to run this command.');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
// Prompt for app ID if not provided
|
|
36
|
+
if (!appId) {
|
|
37
|
+
if (!hasTTY) {
|
|
38
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const organizations = await organizationsService.findAll();
|
|
42
|
+
if (organizations.length === 0) {
|
|
43
|
+
consola.error('You must create an organization before viewing build logs.');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
47
|
+
const organizationId = await prompt('Select the organization of the app for which you want to view the build logs.', {
|
|
48
|
+
type: 'select',
|
|
49
|
+
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
50
|
+
});
|
|
51
|
+
if (!organizationId) {
|
|
52
|
+
consola.error('You must select the organization of an app for which you want to view the build logs.');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const apps = await appsService.findAll({
|
|
56
|
+
organizationId,
|
|
57
|
+
});
|
|
58
|
+
if (apps.length === 0) {
|
|
59
|
+
consola.error('You must create an app before viewing build logs.');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
63
|
+
appId = await prompt('Which app do you want to view the build logs for:', {
|
|
64
|
+
type: 'select',
|
|
65
|
+
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
66
|
+
});
|
|
67
|
+
if (!appId) {
|
|
68
|
+
consola.error('You must select an app to view the build logs.');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Prompt for platform if not provided
|
|
73
|
+
if (!buildId) {
|
|
74
|
+
if (!hasTTY) {
|
|
75
|
+
consola.error('You must provide a platform when running in non-interactive environment.');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const appBuilds = await appBuildsService.findAll({ appId });
|
|
79
|
+
if (appBuilds.length === 0) {
|
|
80
|
+
consola.error('You must create a build before viewing the logs.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
buildId = await prompt('Select the build of the app for which you want to view the logs.', {
|
|
84
|
+
type: 'select',
|
|
85
|
+
options: appBuilds.map((build) => ({
|
|
86
|
+
label: `Build #${build.numberAsString} (${build.platform} - ${build.type})`,
|
|
87
|
+
value: build.id,
|
|
88
|
+
})),
|
|
89
|
+
});
|
|
90
|
+
if (!buildId) {
|
|
91
|
+
consola.error('You must select the build of an app for which you want to view the logs.');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let appBuildDto = await appBuildsService.findOne({ appId, appBuildId: buildId, relations: 'job,job.jobLogs' });
|
|
96
|
+
let isFinished = !!appBuildDto.job?.finishedAt;
|
|
97
|
+
let lastLogNumber = 0;
|
|
98
|
+
if (isFinished) {
|
|
99
|
+
const logs = appBuildDto.job?.jobLogs || [];
|
|
100
|
+
for (const logEntry of logs) {
|
|
101
|
+
console.log(unescapeAnsi(logEntry.payload));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
while (!isFinished) {
|
|
106
|
+
appBuildDto = await appBuildsService.findOne({ appId, appBuildId: buildId, relations: 'job,job.jobLogs' });
|
|
107
|
+
isFinished = !!appBuildDto.job?.finishedAt;
|
|
108
|
+
const logs = appBuildDto.job?.jobLogs || [];
|
|
109
|
+
const newLogs = logs.filter((log) => log.number > lastLogNumber);
|
|
110
|
+
for (const logEntry of newLogs) {
|
|
111
|
+
console.log(unescapeAnsi(logEntry.payload));
|
|
112
|
+
lastLogNumber = logEntry.number;
|
|
113
|
+
}
|
|
114
|
+
if (!isFinished) {
|
|
115
|
+
await wait(3000);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import appsService from '../../../services/apps.js';
|
|
2
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
3
|
+
import organizationsService from '../../../services/organizations.js';
|
|
4
|
+
import { unescapeAnsi } from '../../../utils/ansi.js';
|
|
5
|
+
import { prompt } from '../../../utils/prompt.js';
|
|
6
|
+
import { wait } from '../../../utils/wait.js';
|
|
7
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
8
|
+
import consola from 'consola';
|
|
9
|
+
import { hasTTY } from 'std-env';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import appDeploymentsService from '../../../services/app-deployments.js';
|
|
12
|
+
export default defineCommand({
|
|
13
|
+
description: 'View the deployment logs of an app.',
|
|
14
|
+
options: defineOptions(z.object({
|
|
15
|
+
appId: z
|
|
16
|
+
.uuid({
|
|
17
|
+
message: 'App ID must be a UUID.',
|
|
18
|
+
})
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('App ID to view the deployment logs for.'),
|
|
21
|
+
deploymentId: z
|
|
22
|
+
.uuid({
|
|
23
|
+
message: 'Deployment ID must be a UUID.',
|
|
24
|
+
})
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Deployment ID to view logs.'),
|
|
27
|
+
})),
|
|
28
|
+
action: async (options) => {
|
|
29
|
+
let { appId, deploymentId } = options;
|
|
30
|
+
// Check if the user is logged in
|
|
31
|
+
if (!authorizationService.hasAuthorizationToken()) {
|
|
32
|
+
consola.error('You must be logged in to run this command.');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
// Prompt for app ID if not provided
|
|
36
|
+
if (!appId) {
|
|
37
|
+
if (!hasTTY) {
|
|
38
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const organizations = await organizationsService.findAll();
|
|
42
|
+
if (organizations.length === 0) {
|
|
43
|
+
consola.error('You must create an organization before viewing deployment logs.');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
47
|
+
const organizationId = await prompt('Select the organization that contains the app whose deployment logs you want to view?', {
|
|
48
|
+
type: 'select',
|
|
49
|
+
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
50
|
+
});
|
|
51
|
+
if (!organizationId) {
|
|
52
|
+
consola.error('You must select the organization containing the app whose deployment logs you want to view.');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const apps = await appsService.findAll({
|
|
56
|
+
organizationId,
|
|
57
|
+
});
|
|
58
|
+
if (apps.length === 0) {
|
|
59
|
+
consola.error('You must create an app before viewing deployment logs.');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
63
|
+
appId = await prompt('Which app do you want to view deployment logs for?', {
|
|
64
|
+
type: 'select',
|
|
65
|
+
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
66
|
+
});
|
|
67
|
+
if (!appId) {
|
|
68
|
+
consola.error('You must select an app to view its deployment logs.');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Prompt for deployment ID if not provided
|
|
73
|
+
if (!deploymentId) {
|
|
74
|
+
if (!hasTTY) {
|
|
75
|
+
consola.error('You must provide a deployment ID when running in non-interactive environment.');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const appDeployments = await appDeploymentsService.findAll({ appId });
|
|
79
|
+
if (appDeployments.length === 0) {
|
|
80
|
+
consola.error('There are no deployments for this app.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
84
|
+
deploymentId = await prompt('Which deployment do you want to view the logs for?', {
|
|
85
|
+
type: 'select',
|
|
86
|
+
options: appDeployments.map((deployment) => ({
|
|
87
|
+
label: `Deployment ${deployment.id}`,
|
|
88
|
+
value: deployment.id,
|
|
89
|
+
})),
|
|
90
|
+
});
|
|
91
|
+
if (!deploymentId) {
|
|
92
|
+
consola.error('You must select a deployment to view the logs.');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
let appDeploymentDto = await appDeploymentsService.findOne({
|
|
97
|
+
appId,
|
|
98
|
+
appDeploymentId: deploymentId,
|
|
99
|
+
relations: 'job,job.jobLogs',
|
|
100
|
+
});
|
|
101
|
+
let isFinished = !!appDeploymentDto.job?.finishedAt;
|
|
102
|
+
let lastLogNumber = 0;
|
|
103
|
+
if (isFinished) {
|
|
104
|
+
const logs = appDeploymentDto.job?.jobLogs || [];
|
|
105
|
+
for (const logEntry of logs) {
|
|
106
|
+
console.log(unescapeAnsi(logEntry.payload));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
while (!isFinished) {
|
|
111
|
+
appDeploymentDto = await appDeploymentsService.findOne({
|
|
112
|
+
appId,
|
|
113
|
+
appDeploymentId: deploymentId,
|
|
114
|
+
relations: 'job,job.jobLogs',
|
|
115
|
+
});
|
|
116
|
+
isFinished = !!appDeploymentDto.job?.finishedAt;
|
|
117
|
+
const logs = appDeploymentDto.job?.jobLogs || [];
|
|
118
|
+
const newLogs = logs.filter((log) => log.number > lastLogNumber);
|
|
119
|
+
for (const logEntry of newLogs) {
|
|
120
|
+
console.log(unescapeAnsi(logEntry.payload));
|
|
121
|
+
lastLogNumber = logEntry.number;
|
|
122
|
+
}
|
|
123
|
+
if (!isFinished) {
|
|
124
|
+
await wait(3000);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,8 @@ const config = defineConfig({
|
|
|
25
25
|
'apps:delete': await import('./commands/apps/delete.js').then((mod) => mod.default),
|
|
26
26
|
'apps:builds:cancel': await import('./commands/apps/builds/cancel.js').then((mod) => mod.default),
|
|
27
27
|
'apps:builds:create': await import('./commands/apps/builds/create.js').then((mod) => mod.default),
|
|
28
|
+
'apps:builds:logs': await import('./commands/apps/builds/logs.js').then((mod) => mod.default),
|
|
29
|
+
'apps:builds:download': await import('./commands/apps/builds/download.js').then((mod) => mod.default),
|
|
28
30
|
'apps:bundles:create': await import('./commands/apps/bundles/create.js').then((mod) => mod.default),
|
|
29
31
|
'apps:bundles:delete': await import('./commands/apps/bundles/delete.js').then((mod) => mod.default),
|
|
30
32
|
'apps:bundles:update': await import('./commands/apps/bundles/update.js').then((mod) => mod.default),
|
|
@@ -35,6 +37,7 @@ const config = defineConfig({
|
|
|
35
37
|
'apps:channels:update': await import('./commands/apps/channels/update.js').then((mod) => mod.default),
|
|
36
38
|
'apps:deployments:create': await import('./commands/apps/deployments/create.js').then((mod) => mod.default),
|
|
37
39
|
'apps:deployments:cancel': await import('./commands/apps/deployments/cancel.js').then((mod) => mod.default),
|
|
40
|
+
'apps:deployments:logs': await import('./commands/apps/deployments/logs.js').then((mod) => mod.default),
|
|
38
41
|
'apps:devices:delete': await import('./commands/apps/devices/delete.js').then((mod) => mod.default),
|
|
39
42
|
'manifests:generate': await import('./commands/manifests/generate.js').then((mod) => mod.default),
|
|
40
43
|
'organizations:create': await import('./commands/organizations/create.js').then((mod) => mod.default),
|