@solidactions/cli 1.0.0 → 1.2.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/dist/commands/deploy.js +1 -1
- package/dist/commands/run-list.js +114 -6
- package/dist/commands/run-view.js +19 -7
- package/dist/index.js +1 -0
- package/package.json +1 -1
package/dist/commands/deploy.js
CHANGED
|
@@ -312,7 +312,7 @@ async function deploy(projectName, sourcePath, options = {}) {
|
|
|
312
312
|
});
|
|
313
313
|
// Dockerfile always at archive root, referencing tenantcode/
|
|
314
314
|
const universalDockerfile = [
|
|
315
|
-
'FROM node:
|
|
315
|
+
'FROM node:24-alpine',
|
|
316
316
|
'WORKDIR /app',
|
|
317
317
|
'COPY tenantcode/package.json tenantcode/package-lock.json* ./',
|
|
318
318
|
'RUN npm install',
|
|
@@ -26,6 +26,8 @@ async function runs(projectName, options = {}) {
|
|
|
26
26
|
params.workflow = options.workflow;
|
|
27
27
|
if (options.detailed)
|
|
28
28
|
params.detailed = '1';
|
|
29
|
+
if (options.hasErrors)
|
|
30
|
+
params.has_errors = '1';
|
|
29
31
|
const response = await axios_1.default.get(`${config.host}/api/v1/runs`, {
|
|
30
32
|
headers: (0, api_1.getApiHeaders)(config),
|
|
31
33
|
params,
|
|
@@ -97,8 +99,10 @@ function displayDetailedList(runsList, projectName) {
|
|
|
97
99
|
const status = run.execution_status || run.status || '?';
|
|
98
100
|
const statusColor = getStatusColor(status);
|
|
99
101
|
const exitStr = run.exit_code !== null && run.exit_code !== undefined ? ` (exit ${run.exit_code})` : '';
|
|
102
|
+
const isSilentFailure = hasRunErrors(run) && isSuccessStatus(status);
|
|
100
103
|
console.log('');
|
|
101
|
-
|
|
104
|
+
const silentTag = isSilentFailure ? chalk_1.default.yellow(' [DEGRADED]') : '';
|
|
105
|
+
console.log(chalk_1.default.bold(` Run #${run.id}`) + chalk_1.default.gray(` — ${run.workflow_name || '?'} (${run.project_name || '?'})`) + silentTag);
|
|
102
106
|
console.log(` Status: ${statusColor(status)}${chalk_1.default.gray(exitStr)}`);
|
|
103
107
|
console.log(` Trigger: ${chalk_1.default.gray(run.triggered_by || '-')}`);
|
|
104
108
|
// Timeline
|
|
@@ -108,18 +112,38 @@ function displayDetailedList(runsList, projectName) {
|
|
|
108
112
|
console.log(` Started: ${chalk_1.default.gray(formatTs(run.timeline.started))}`);
|
|
109
113
|
console.log(` Completed: ${chalk_1.default.gray(formatTs(run.timeline.completed))}`);
|
|
110
114
|
}
|
|
111
|
-
// Steps
|
|
115
|
+
// Steps detail
|
|
112
116
|
if (run.steps && run.steps.length > 0) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
displayStepsDetail(run.steps);
|
|
118
|
+
}
|
|
119
|
+
// Workflow output
|
|
120
|
+
if (run.output) {
|
|
121
|
+
const outputStr = JSON.stringify(unwrapOutput(run.output), null, 2);
|
|
122
|
+
const lines = outputStr.split('\n');
|
|
123
|
+
if (lines.length === 1) {
|
|
124
|
+
console.log(` Output: ${chalk_1.default.gray(truncate(outputStr, 60))}`);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
console.log(` Output:`);
|
|
128
|
+
for (const line of lines.slice(0, 5)) {
|
|
129
|
+
console.log(chalk_1.default.gray(` ${line}`));
|
|
130
|
+
}
|
|
131
|
+
if (lines.length > 5) {
|
|
132
|
+
console.log(chalk_1.default.gray(` ... (${lines.length - 5} more lines)`));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Workflow error
|
|
137
|
+
if (run.error) {
|
|
138
|
+
const errMsg = typeof run.error === 'string' ? run.error : JSON.stringify(run.error);
|
|
139
|
+
console.log(` ${chalk_1.default.bold.red('Error:')} ${chalk_1.default.red(truncate(errMsg, 60))}`);
|
|
116
140
|
}
|
|
117
141
|
// Logs snippet (first errors or last 3 lines)
|
|
118
142
|
if (run.logs && typeof run.logs === 'string' && run.logs.trim()) {
|
|
119
143
|
const lines = run.logs.trim().split('\n');
|
|
120
144
|
const errorLines = lines.filter((l) => /error|fail|exception/i.test(l));
|
|
121
145
|
if (errorLines.length > 0) {
|
|
122
|
-
console.log(`
|
|
146
|
+
console.log(` Logs:`);
|
|
123
147
|
for (const line of errorLines.slice(0, 3)) {
|
|
124
148
|
console.log(chalk_1.default.red(` ${truncate(line, 80)}`));
|
|
125
149
|
}
|
|
@@ -129,7 +153,91 @@ function displayDetailedList(runsList, projectName) {
|
|
|
129
153
|
console.log('');
|
|
130
154
|
console.log(chalk_1.default.gray(`Showing ${runsList.length} run(s)`));
|
|
131
155
|
}
|
|
156
|
+
function displayStepsDetail(steps) {
|
|
157
|
+
const completed = steps.filter((s) => s.completed_at || s.completed_at_epoch_ms).length;
|
|
158
|
+
const totalDuration = steps.reduce((sum, s) => sum + (s.duration_ms || 0), 0);
|
|
159
|
+
const errorCount = steps.filter((s) => s.error || s.status === 'error' || s.status === 'failed').length;
|
|
160
|
+
const retryCount = steps.filter((s) => s.retried || s.status === 'retried').length;
|
|
161
|
+
let summary = `${completed}/${steps.length} completed (${formatDuration(totalDuration)} total)`;
|
|
162
|
+
if (errorCount > 0)
|
|
163
|
+
summary += chalk_1.default.red(` · ${errorCount} errored`);
|
|
164
|
+
if (retryCount > 0)
|
|
165
|
+
summary += chalk_1.default.yellow(` · ${retryCount} retried`);
|
|
166
|
+
console.log(` Steps: ${chalk_1.default.gray(summary)}`);
|
|
167
|
+
// Per-step detail table
|
|
168
|
+
console.log(chalk_1.default.gray(` ${'NAME'.padEnd(24)}${'STATUS'.padEnd(12)}${'DURATION'.padEnd(10)}OUTPUT`));
|
|
169
|
+
console.log(chalk_1.default.gray(` ${'─'.repeat(70)}`));
|
|
170
|
+
for (const step of steps) {
|
|
171
|
+
const name = truncate(step.name || '?', 23).padEnd(24);
|
|
172
|
+
const stepStatus = getStepStatus(step);
|
|
173
|
+
const stepStatusColor = getStepStatusColor(stepStatus);
|
|
174
|
+
const duration = formatDuration(step.duration_ms);
|
|
175
|
+
const output = step.output ? truncate(JSON.stringify(unwrapOutput(step.output)), 40) : '-';
|
|
176
|
+
console.log(` ${name}${stepStatusColor(stepStatus.padEnd(12))}${chalk_1.default.gray(duration.padEnd(10))}${chalk_1.default.gray(output)}`);
|
|
177
|
+
if (step.error) {
|
|
178
|
+
const errMsg = typeof step.error === 'string' ? step.error : JSON.stringify(step.error);
|
|
179
|
+
console.log(chalk_1.default.red(` error: ${truncate(errMsg, 70)}`));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
132
183
|
// ─── Utility ───────────────────────────────────────────────────────────────
|
|
184
|
+
function unwrapOutput(output) {
|
|
185
|
+
if (output && output.__solidactions_serializer === 'superjson' && output.json) {
|
|
186
|
+
return output.json;
|
|
187
|
+
}
|
|
188
|
+
return output;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Determine if a run has any errors (step errors, retries, or non-empty error arrays in output).
|
|
192
|
+
*/
|
|
193
|
+
function hasRunErrors(run) {
|
|
194
|
+
// Check step-level errors
|
|
195
|
+
if (run.steps && run.steps.some((s) => s.error || s.status === 'error' || s.status === 'failed' || s.retried || s.status === 'retried')) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
// Check workflow-level error
|
|
199
|
+
if (run.error)
|
|
200
|
+
return true;
|
|
201
|
+
// Check output.errors array
|
|
202
|
+
const output = unwrapOutput(run.output);
|
|
203
|
+
if (output && Array.isArray(output.errors) && output.errors.length > 0)
|
|
204
|
+
return true;
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
function isSuccessStatus(status) {
|
|
208
|
+
const s = status?.toLowerCase();
|
|
209
|
+
return s === 'completed' || s === 'success';
|
|
210
|
+
}
|
|
211
|
+
function getStepStatus(step) {
|
|
212
|
+
if (step.status)
|
|
213
|
+
return step.status;
|
|
214
|
+
if (step.error)
|
|
215
|
+
return 'error';
|
|
216
|
+
if (step.completed_at || step.completed_at_epoch_ms)
|
|
217
|
+
return 'completed';
|
|
218
|
+
if (step.started_at || step.started_at_epoch_ms)
|
|
219
|
+
return 'running';
|
|
220
|
+
return 'pending';
|
|
221
|
+
}
|
|
222
|
+
function getStepStatusColor(status) {
|
|
223
|
+
switch (status?.toLowerCase()) {
|
|
224
|
+
case 'completed':
|
|
225
|
+
case 'success':
|
|
226
|
+
return chalk_1.default.green;
|
|
227
|
+
case 'running':
|
|
228
|
+
return chalk_1.default.blue;
|
|
229
|
+
case 'pending':
|
|
230
|
+
case 'queued':
|
|
231
|
+
return chalk_1.default.yellow;
|
|
232
|
+
case 'failed':
|
|
233
|
+
case 'error':
|
|
234
|
+
return chalk_1.default.red;
|
|
235
|
+
case 'retried':
|
|
236
|
+
return chalk_1.default.yellow;
|
|
237
|
+
default:
|
|
238
|
+
return chalk_1.default.gray;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
133
241
|
/**
|
|
134
242
|
* Parse --since value into epoch ms.
|
|
135
243
|
* Accepts: "1h", "30m", "2d", "1w", or ISO date string.
|
|
@@ -16,16 +16,18 @@ async function runView(runId, options) {
|
|
|
16
16
|
});
|
|
17
17
|
const runData = runResponse.data;
|
|
18
18
|
// Build timeline
|
|
19
|
+
// Duration = container start → container stop (not triggered → stop)
|
|
19
20
|
const triggeredAt = runData.triggered_at || runData.created_at;
|
|
20
21
|
const sessionStartedMs = runData.session_started_at_epoch_ms;
|
|
21
22
|
const sessionCompletedMs = runData.session_completed_at_epoch_ms;
|
|
22
|
-
const
|
|
23
|
-
|
|
23
|
+
const durationMs = (sessionStartedMs && sessionCompletedMs)
|
|
24
|
+
? sessionCompletedMs - sessionStartedMs
|
|
25
|
+
: null;
|
|
24
26
|
const timeline = {
|
|
25
27
|
triggered: triggeredAt || null,
|
|
26
28
|
started: sessionStartedMs ? new Date(sessionStartedMs).toISOString() : null,
|
|
27
29
|
completed: sessionCompletedMs ? new Date(sessionCompletedMs).toISOString() : null,
|
|
28
|
-
|
|
30
|
+
durationMs,
|
|
29
31
|
};
|
|
30
32
|
// --logs: fetch and display raw logs
|
|
31
33
|
if (options.logs) {
|
|
@@ -127,7 +129,7 @@ function displayFullView(runData, timeline, steps, logsData) {
|
|
|
127
129
|
console.log(` Status: ${statusColor(status)}${chalk_1.default.gray(exitStr)}`);
|
|
128
130
|
console.log(` Trigger: ${chalk_1.default.gray(runData.triggered_by || '-')}`);
|
|
129
131
|
// Timeline
|
|
130
|
-
displayTimeline(runData, timeline);
|
|
132
|
+
displayTimeline(runData, timeline, steps);
|
|
131
133
|
// Steps
|
|
132
134
|
if (steps.length > 0) {
|
|
133
135
|
console.log('');
|
|
@@ -170,17 +172,27 @@ function displayFullView(runData, timeline, steps, logsData) {
|
|
|
170
172
|
}
|
|
171
173
|
console.log('');
|
|
172
174
|
}
|
|
173
|
-
function displayTimeline(runData, timeline) {
|
|
175
|
+
function displayTimeline(runData, timeline, steps = []) {
|
|
174
176
|
const formatTs = (iso) => {
|
|
175
177
|
if (!iso)
|
|
176
178
|
return '-';
|
|
177
179
|
const d = new Date(iso);
|
|
178
180
|
return d.toLocaleString();
|
|
179
181
|
};
|
|
180
|
-
|
|
182
|
+
// Compute startup: triggered → first step start
|
|
183
|
+
let startupMs = null;
|
|
184
|
+
if (timeline.triggered && steps.length > 0 && steps[0].startedAt) {
|
|
185
|
+
const triggeredMs = new Date(timeline.triggered).getTime();
|
|
186
|
+
const firstStepMs = new Date(steps[0].startedAt).getTime();
|
|
187
|
+
startupMs = firstStepMs - triggeredMs;
|
|
188
|
+
}
|
|
189
|
+
console.log(` Created: ${chalk_1.default.gray(formatTs(timeline.triggered))}`);
|
|
181
190
|
console.log(` Started: ${chalk_1.default.gray(formatTs(timeline.started))}`);
|
|
182
191
|
console.log(` Completed: ${chalk_1.default.gray(formatTs(timeline.completed))}`);
|
|
183
|
-
|
|
192
|
+
if (startupMs !== null && startupMs > 0) {
|
|
193
|
+
console.log(` Startup: ${chalk_1.default.gray(formatDuration(startupMs))}`);
|
|
194
|
+
}
|
|
195
|
+
console.log(` Duration: ${chalk_1.default.gray(timeline.durationMs ? formatDuration(timeline.durationMs) : '-')}`);
|
|
184
196
|
}
|
|
185
197
|
function displayStepsTable(steps, indent = ' ') {
|
|
186
198
|
if (steps.length === 0) {
|
package/dist/index.js
CHANGED
|
@@ -126,6 +126,7 @@ runCmd
|
|
|
126
126
|
.option('--since <duration>', 'Filter to runs since (e.g., 1h, 30m, 2d, 1w)')
|
|
127
127
|
.option('--workflow <name>', 'Filter by workflow name')
|
|
128
128
|
.option('--detailed', 'Include timeline, steps, and logs per run (default limit: 5)')
|
|
129
|
+
.option('--has-errors', 'Show only runs with errors (step errors, retries, or degraded results)')
|
|
129
130
|
.option('--json', 'Output as JSON')
|
|
130
131
|
.action((projectName, options) => {
|
|
131
132
|
(0, run_list_1.runs)(projectName, options);
|