@solidactions/cli 0.7.3 → 1.0.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/README.md +68 -25
- package/dist/commands/ai-examples.js +1 -1
- package/dist/commands/deploy.js +10 -0
- package/dist/commands/env-map.js +25 -2
- package/dist/commands/env-pull.js +2 -2
- package/dist/commands/env-push.js +1 -1
- package/dist/commands/env-set.js +35 -2
- package/dist/commands/init.js +4 -4
- package/dist/commands/project-list.js +52 -0
- package/dist/commands/pull.js +29 -12
- package/dist/commands/run-list.js +186 -0
- package/dist/commands/run-view.js +255 -0
- package/dist/commands/schedule-set.js +45 -1
- package/dist/commands/{webhooks.js → webhook-list.js} +4 -4
- package/dist/commands/workspaces.js +1 -1
- package/dist/index.js +117 -103
- package/package.json +4 -4
- package/dist/commands/logs.js +0 -103
- package/dist/commands/runs.js +0 -90
- package/dist/commands/steps.js +0 -202
- /package/dist/commands/{logs-build.js → project-logs.js} +0 -0
- /package/dist/commands/{run.js → run-start.js} +0 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runView = runView;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const api_1 = require("../utils/api");
|
|
10
|
+
async function runView(runId, options) {
|
|
11
|
+
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
12
|
+
try {
|
|
13
|
+
// Fetch run data (includes session timing)
|
|
14
|
+
const runResponse = await axios_1.default.get(`${config.host}/api/v1/runs/${runId}`, {
|
|
15
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
16
|
+
});
|
|
17
|
+
const runData = runResponse.data;
|
|
18
|
+
// Build timeline
|
|
19
|
+
const triggeredAt = runData.triggered_at || runData.created_at;
|
|
20
|
+
const sessionStartedMs = runData.session_started_at_epoch_ms;
|
|
21
|
+
const sessionCompletedMs = runData.session_completed_at_epoch_ms;
|
|
22
|
+
const triggeredMs = triggeredAt ? new Date(triggeredAt).getTime() : null;
|
|
23
|
+
const totalMs = (triggeredMs && sessionCompletedMs) ? sessionCompletedMs - triggeredMs : null;
|
|
24
|
+
const timeline = {
|
|
25
|
+
triggered: triggeredAt || null,
|
|
26
|
+
started: sessionStartedMs ? new Date(sessionStartedMs).toISOString() : null,
|
|
27
|
+
completed: sessionCompletedMs ? new Date(sessionCompletedMs).toISOString() : null,
|
|
28
|
+
totalMs,
|
|
29
|
+
};
|
|
30
|
+
// --logs: fetch and display raw logs
|
|
31
|
+
if (options.logs) {
|
|
32
|
+
const logsResponse = await axios_1.default.get(`${config.host}/api/v1/runs/${runId}/logs`, {
|
|
33
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
34
|
+
});
|
|
35
|
+
const logData = logsResponse.data.logs || '';
|
|
36
|
+
// --logs always outputs raw text, even with --json (wrapping logs in JSON is pointless)
|
|
37
|
+
if (typeof logData === 'string') {
|
|
38
|
+
console.log(logData);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.log(JSON.stringify(logData, null, 2));
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Fetch steps data
|
|
46
|
+
const stepsResponse = await axios_1.default.get(`${config.host}/api/v1/runs/${runId}/steps`, {
|
|
47
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
48
|
+
});
|
|
49
|
+
const stepsData = stepsResponse.data;
|
|
50
|
+
const flatSteps = flattenSteps(stepsData.workers || []);
|
|
51
|
+
// --timeline
|
|
52
|
+
if (options.timeline) {
|
|
53
|
+
if (options.json) {
|
|
54
|
+
console.log(JSON.stringify(timeline, null, 2));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
displayTimeline(runData, timeline);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// --steps
|
|
62
|
+
if (options.steps) {
|
|
63
|
+
if (options.json) {
|
|
64
|
+
console.log(JSON.stringify(flatSteps, null, 2));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
displayStepsTable(flatSteps);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Full view
|
|
72
|
+
// Fetch logs for the full view
|
|
73
|
+
const logsResponse = await axios_1.default.get(`${config.host}/api/v1/runs/${runId}/logs`, {
|
|
74
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
75
|
+
});
|
|
76
|
+
if (options.json) {
|
|
77
|
+
// Full JSON output
|
|
78
|
+
const output = {
|
|
79
|
+
id: runData.id,
|
|
80
|
+
workflow: runData.workflow_name,
|
|
81
|
+
project: runData.project_name,
|
|
82
|
+
status: runData.execution_status || runData.status,
|
|
83
|
+
exitCode: runData.exit_code ?? null,
|
|
84
|
+
timeline,
|
|
85
|
+
steps: flatSteps,
|
|
86
|
+
logs: logsResponse.data.logs || '',
|
|
87
|
+
};
|
|
88
|
+
if (runData.output)
|
|
89
|
+
output.output = runData.output;
|
|
90
|
+
if (runData.error)
|
|
91
|
+
output.error = runData.error;
|
|
92
|
+
console.log(JSON.stringify(output, null, 2));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Human-readable output
|
|
96
|
+
displayFullView(runData, timeline, flatSteps, logsResponse.data);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (error.response) {
|
|
101
|
+
if (error.response.status === 401) {
|
|
102
|
+
console.error(chalk_1.default.red('Authentication failed. Run "solidactions init <api-key>" to re-configure.'));
|
|
103
|
+
}
|
|
104
|
+
else if (error.response.status === 404) {
|
|
105
|
+
console.error(chalk_1.default.red('Run not found.'));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.error(chalk_1.default.red(`Failed: ${error.response.status}`), error.response.data);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
console.error(chalk_1.default.red('Connection failed:'), error.message);
|
|
113
|
+
}
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// ─── Display helpers ───────────────────────────────────────────────────────
|
|
118
|
+
function displayFullView(runData, timeline, steps, logsData) {
|
|
119
|
+
const status = runData.execution_status || runData.status;
|
|
120
|
+
const exitCode = runData.exit_code;
|
|
121
|
+
const statusColor = getStatusColor(status);
|
|
122
|
+
console.log('');
|
|
123
|
+
console.log(chalk_1.default.bold(`Run #${runData.id}`) + chalk_1.default.gray(` — ${runData.workflow_name} (${runData.project_name})`));
|
|
124
|
+
console.log('');
|
|
125
|
+
// Status line
|
|
126
|
+
const exitStr = exitCode !== null && exitCode !== undefined ? ` (exit ${exitCode})` : '';
|
|
127
|
+
console.log(` Status: ${statusColor(status)}${chalk_1.default.gray(exitStr)}`);
|
|
128
|
+
console.log(` Trigger: ${chalk_1.default.gray(runData.triggered_by || '-')}`);
|
|
129
|
+
// Timeline
|
|
130
|
+
displayTimeline(runData, timeline);
|
|
131
|
+
// Steps
|
|
132
|
+
if (steps.length > 0) {
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(chalk_1.default.bold(' Steps:'));
|
|
135
|
+
displayStepsTable(steps, ' ');
|
|
136
|
+
}
|
|
137
|
+
// Output
|
|
138
|
+
if (runData.output) {
|
|
139
|
+
console.log('');
|
|
140
|
+
console.log(chalk_1.default.bold(' Output:'));
|
|
141
|
+
const outputStr = JSON.stringify(unwrapOutput(runData.output), null, 2);
|
|
142
|
+
for (const line of outputStr.split('\n')) {
|
|
143
|
+
console.log(chalk_1.default.gray(` ${line}`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Error
|
|
147
|
+
if (runData.error) {
|
|
148
|
+
console.log('');
|
|
149
|
+
console.log(chalk_1.default.bold.red(' Error:'));
|
|
150
|
+
console.log(chalk_1.default.red(` ${runData.error}`));
|
|
151
|
+
}
|
|
152
|
+
// Logs snippet
|
|
153
|
+
const logs = logsData.logs || '';
|
|
154
|
+
if (logs && typeof logs === 'string' && logs.trim()) {
|
|
155
|
+
const logLines = logs.trim().split('\n');
|
|
156
|
+
const showLines = logLines.length > 10 ? logLines.slice(-10) : logLines;
|
|
157
|
+
console.log('');
|
|
158
|
+
console.log(chalk_1.default.bold(' Logs:') + chalk_1.default.gray(logLines.length > 10 ? ` (last 10 of ${logLines.length} lines, use --logs for full)` : ''));
|
|
159
|
+
for (const line of showLines) {
|
|
160
|
+
console.log(chalk_1.default.gray(` ${line}`));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Errors from workers
|
|
164
|
+
const errors = logsData.errors || [];
|
|
165
|
+
if (errors.length > 0) {
|
|
166
|
+
console.log('');
|
|
167
|
+
for (const err of errors) {
|
|
168
|
+
console.log(chalk_1.default.red(` Worker ${err.worker}: ${err.error}`));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
console.log('');
|
|
172
|
+
}
|
|
173
|
+
function displayTimeline(runData, timeline) {
|
|
174
|
+
const formatTs = (iso) => {
|
|
175
|
+
if (!iso)
|
|
176
|
+
return '-';
|
|
177
|
+
const d = new Date(iso);
|
|
178
|
+
return d.toLocaleString();
|
|
179
|
+
};
|
|
180
|
+
console.log(` Triggered: ${chalk_1.default.gray(formatTs(timeline.triggered))}`);
|
|
181
|
+
console.log(` Started: ${chalk_1.default.gray(formatTs(timeline.started))}`);
|
|
182
|
+
console.log(` Completed: ${chalk_1.default.gray(formatTs(timeline.completed))}`);
|
|
183
|
+
console.log(` Duration: ${chalk_1.default.gray(timeline.totalMs ? formatDuration(timeline.totalMs) : '-')}`);
|
|
184
|
+
}
|
|
185
|
+
function displayStepsTable(steps, indent = ' ') {
|
|
186
|
+
if (steps.length === 0) {
|
|
187
|
+
console.log(chalk_1.default.gray(`${indent}(no steps)`));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
console.log(chalk_1.default.gray(`${indent}${'STEP'.padEnd(24)}${'STATUS'.padEnd(12)}${'DURATION'.padEnd(12)}OUTPUT`));
|
|
191
|
+
console.log(chalk_1.default.gray(`${indent}${'─'.repeat(72)}`));
|
|
192
|
+
for (const step of steps) {
|
|
193
|
+
const name = (step.name || '?').padEnd(24);
|
|
194
|
+
const status = step.completedAt ? 'completed' : step.startedAt ? 'running' : 'pending';
|
|
195
|
+
const statusColor = getStatusColor(status);
|
|
196
|
+
const duration = formatDuration(step.durationMs);
|
|
197
|
+
const output = step.output ? truncate(JSON.stringify(unwrapOutput(step.output)), 40) : '-';
|
|
198
|
+
console.log(`${indent}${name}${statusColor(status.padEnd(12))}${chalk_1.default.gray(duration.padEnd(12))}${chalk_1.default.gray(output)}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// ─── Utility ───────────────────────────────────────────────────────────────
|
|
202
|
+
function flattenSteps(workers) {
|
|
203
|
+
const steps = [];
|
|
204
|
+
for (const worker of workers) {
|
|
205
|
+
for (const step of worker.steps || []) {
|
|
206
|
+
steps.push({
|
|
207
|
+
name: step.name,
|
|
208
|
+
startedAt: step.started_at || null,
|
|
209
|
+
completedAt: step.completed_at || null,
|
|
210
|
+
durationMs: step.duration_ms ?? null,
|
|
211
|
+
output: step.output ?? null,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return steps;
|
|
216
|
+
}
|
|
217
|
+
function unwrapOutput(output) {
|
|
218
|
+
if (output && output.__solidactions_serializer === 'superjson' && output.json) {
|
|
219
|
+
return output.json;
|
|
220
|
+
}
|
|
221
|
+
return output;
|
|
222
|
+
}
|
|
223
|
+
function formatDuration(ms) {
|
|
224
|
+
if (ms === null || ms === undefined)
|
|
225
|
+
return '-';
|
|
226
|
+
if (ms < 1000)
|
|
227
|
+
return `${ms}ms`;
|
|
228
|
+
if (ms < 60000)
|
|
229
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
230
|
+
if (ms < 3600000)
|
|
231
|
+
return `${Math.round(ms / 60000)}m`;
|
|
232
|
+
return `${Math.round(ms / 3600000)}h`;
|
|
233
|
+
}
|
|
234
|
+
function truncate(str, max) {
|
|
235
|
+
if (str.length <= max)
|
|
236
|
+
return str;
|
|
237
|
+
return str.substring(0, max - 3) + '...';
|
|
238
|
+
}
|
|
239
|
+
function getStatusColor(status) {
|
|
240
|
+
switch (status?.toLowerCase()) {
|
|
241
|
+
case 'completed':
|
|
242
|
+
case 'success':
|
|
243
|
+
return chalk_1.default.green;
|
|
244
|
+
case 'running':
|
|
245
|
+
return chalk_1.default.blue;
|
|
246
|
+
case 'pending':
|
|
247
|
+
case 'queued':
|
|
248
|
+
return chalk_1.default.yellow;
|
|
249
|
+
case 'failed':
|
|
250
|
+
case 'error':
|
|
251
|
+
return chalk_1.default.red;
|
|
252
|
+
default:
|
|
253
|
+
return chalk_1.default.gray;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -6,10 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.scheduleSet = scheduleSet;
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
9
10
|
const api_1 = require("../utils/api");
|
|
10
11
|
async function scheduleSet(projectName, cron, options) {
|
|
11
12
|
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
12
|
-
console.log(chalk_1.default.blue(`Setting schedule for project "${projectName}"...`));
|
|
13
13
|
// Parse input JSON if provided
|
|
14
14
|
let inputData;
|
|
15
15
|
if (options.input) {
|
|
@@ -21,6 +21,50 @@ async function scheduleSet(projectName, cron, options) {
|
|
|
21
21
|
process.exit(1);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
// Check for existing schedule on the same workflow
|
|
25
|
+
if (!options.yes) {
|
|
26
|
+
try {
|
|
27
|
+
const listResponse = await axios_1.default.get(`${config.host}/api/v1/projects/${projectName}/schedules`, {
|
|
28
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
29
|
+
});
|
|
30
|
+
const schedules = listResponse.data.data || listResponse.data || [];
|
|
31
|
+
const existing = schedules.find((s) => {
|
|
32
|
+
if (options.workflow) {
|
|
33
|
+
return s.workflow_name === options.workflow || s.workflow_slug === options.workflow;
|
|
34
|
+
}
|
|
35
|
+
return true; // No workflow specified — any existing schedule is a match
|
|
36
|
+
});
|
|
37
|
+
if (existing) {
|
|
38
|
+
const workflowName = existing.workflow_name || existing.workflow_slug || 'unknown';
|
|
39
|
+
console.log(chalk_1.default.yellow(`"${workflowName}" already has a schedule: ${existing.cron_expression}`));
|
|
40
|
+
const confirm = await (0, prompts_1.default)({
|
|
41
|
+
type: 'select',
|
|
42
|
+
name: 'action',
|
|
43
|
+
message: 'What would you like to do?',
|
|
44
|
+
choices: [
|
|
45
|
+
{ title: 'Replace existing schedule', value: 'replace' },
|
|
46
|
+
{ title: 'Add another schedule', value: 'add' },
|
|
47
|
+
{ title: 'Cancel', value: 'cancel' },
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
if (confirm.action === 'cancel' || confirm.action === undefined) {
|
|
51
|
+
console.log(chalk_1.default.gray('Cancelled.'));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (confirm.action === 'replace') {
|
|
55
|
+
// Delete the existing schedule first
|
|
56
|
+
await axios_1.default.delete(`${config.host}/api/v1/projects/${projectName}/schedules/${existing.id}`, {
|
|
57
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
58
|
+
});
|
|
59
|
+
console.log(chalk_1.default.gray(`Removed old schedule (${existing.cron_expression}).`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// If we can't check, proceed anyway
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk_1.default.blue(`Setting schedule for project "${projectName}"...`));
|
|
24
68
|
try {
|
|
25
69
|
const payload = {
|
|
26
70
|
cron,
|
|
@@ -21,7 +21,7 @@ async function webhookList(projectName, options = {}) {
|
|
|
21
21
|
headers: (0, api_1.getApiHeaders)(config),
|
|
22
22
|
params,
|
|
23
23
|
});
|
|
24
|
-
const webhooks = response.data || [];
|
|
24
|
+
const webhooks = response.data.data || [];
|
|
25
25
|
if (webhooks.length === 0) {
|
|
26
26
|
console.log(chalk_1.default.yellow('No webhooks found for project "' + projectName + '".'));
|
|
27
27
|
return;
|
|
@@ -36,11 +36,11 @@ async function webhookList(projectName, options = {}) {
|
|
|
36
36
|
console.log(chalk_1.default.gray('-'.repeat(90)));
|
|
37
37
|
}
|
|
38
38
|
for (const webhook of webhooks) {
|
|
39
|
-
const name = webhook.
|
|
40
|
-
const url = webhook.
|
|
39
|
+
const name = webhook.workflow_name || webhook.workflow_slug || '?';
|
|
40
|
+
const url = webhook.webhook_path_url || webhook.webhook_url || '?';
|
|
41
41
|
let line = name.padEnd(30) + chalk_1.default.cyan(url.padEnd(60));
|
|
42
42
|
if (options.showSecrets) {
|
|
43
|
-
const secret = webhook.
|
|
43
|
+
const secret = webhook.webhook_secret || '-';
|
|
44
44
|
line += chalk_1.default.gray(secret);
|
|
45
45
|
}
|
|
46
46
|
console.log(line);
|
|
@@ -75,7 +75,7 @@ async function workspaceSet(workspaceId) {
|
|
|
75
75
|
}
|
|
76
76
|
const workspace = allWorkspaces.find((w) => w.id === workspaceId || w.slug === workspaceId || w.name === workspaceId);
|
|
77
77
|
if (!workspace) {
|
|
78
|
-
console.error(chalk_1.default.red(`Workspace "${workspaceId}" not found. Run \`solidactions
|
|
78
|
+
console.error(chalk_1.default.red(`Workspace "${workspaceId}" not found. Run \`solidactions workspace list\` to list available workspaces.`));
|
|
79
79
|
process.exit(1);
|
|
80
80
|
}
|
|
81
81
|
config.workspaceId = workspace.id;
|