@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.
@@ -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.name || webhook.slug || '?';
40
- const url = webhook.url || '?';
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.secret || '-';
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 workspaces\` to list available workspaces.`));
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;