@solidactions/cli 0.7.0 → 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 +26 -27
- package/dist/commands/env-delete.js +6 -22
- package/dist/commands/env-list.js +4 -14
- package/dist/commands/env-map.js +29 -17
- package/dist/commands/env-pull.js +6 -16
- package/dist/commands/env-push.js +5 -16
- package/dist/commands/env-set.js +41 -27
- package/dist/commands/init.js +21 -5
- package/dist/commands/project-list.js +52 -0
- package/dist/commands/{logs-build.js → project-logs.js} +3 -10
- package/dist/commands/pull.js +32 -22
- package/dist/commands/run-list.js +186 -0
- package/dist/commands/{run.js → run-start.js} +4 -15
- package/dist/commands/run-view.js +255 -0
- package/dist/commands/schedule-delete.js +4 -14
- package/dist/commands/schedule-list.js +3 -10
- package/dist/commands/schedule-set.js +48 -12
- package/dist/commands/{webhooks.js → webhook-list.js} +7 -14
- package/dist/commands/workspaces.js +89 -0
- package/dist/index.js +130 -98
- package/dist/utils/api.js +129 -0
- package/package.json +4 -4
- package/dist/commands/logs.js +0 -119
- package/dist/commands/runs.js +0 -97
- package/dist/commands/steps.js +0 -215
package/dist/commands/env-set.js
CHANGED
|
@@ -6,17 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.envSet = envSet;
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
-
const
|
|
9
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
10
|
+
const api_1 = require("../utils/api");
|
|
10
11
|
async function envSet(keyOrProject, valueOrKey, valueIfProject, options = {}) {
|
|
11
|
-
const config = (0,
|
|
12
|
-
if (!config?.apiKey) {
|
|
13
|
-
console.error(chalk_1.default.red('Not initialized. Run "solidactions init <api-key>" first.'));
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
12
|
+
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
16
13
|
// Detect mode based on arguments
|
|
17
14
|
const isProjectMode = valueIfProject !== undefined;
|
|
18
15
|
if (isProjectMode) {
|
|
19
|
-
// Project mode: solidactions env
|
|
16
|
+
// Project mode: solidactions env set <project> <key> <value>
|
|
20
17
|
const projectName = keyOrProject;
|
|
21
18
|
const key = valueOrKey;
|
|
22
19
|
const value = valueIfProject;
|
|
@@ -28,6 +25,25 @@ async function envSet(keyOrProject, valueOrKey, valueIfProject, options = {}) {
|
|
|
28
25
|
// Auto-detect secrets
|
|
29
26
|
const isSecret = options.secret || /secret|key|token|password|credential/i.test(key);
|
|
30
27
|
try {
|
|
28
|
+
// Check if variable already has a value
|
|
29
|
+
if (!options.yes) {
|
|
30
|
+
const mappingsResponse = await axios_1.default.get(`${config.host}/api/v1/projects/${projectSlug}/variable-mappings?reveal=true`, { headers: (0, api_1.getApiHeaders)(config) });
|
|
31
|
+
const mappings = mappingsResponse.data || [];
|
|
32
|
+
const existing = mappings.find((m) => m.env_name === key);
|
|
33
|
+
if (existing && existing.has_value) {
|
|
34
|
+
console.log(chalk_1.default.yellow(`Variable "${key}" already has a value in "${projectName}" (${environment}).`));
|
|
35
|
+
const confirm = await (0, prompts_1.default)({
|
|
36
|
+
type: 'confirm',
|
|
37
|
+
name: 'proceed',
|
|
38
|
+
message: 'Overwrite?',
|
|
39
|
+
initial: false,
|
|
40
|
+
});
|
|
41
|
+
if (!confirm.proceed) {
|
|
42
|
+
console.log(chalk_1.default.gray('Cancelled.'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
31
47
|
// Use bulk endpoint for upsert
|
|
32
48
|
const response = await axios_1.default.post(`${config.host}/api/v1/projects/${projectSlug}/variable-mappings/bulk`, {
|
|
33
49
|
variables: [{
|
|
@@ -36,11 +52,7 @@ async function envSet(keyOrProject, valueOrKey, valueIfProject, options = {}) {
|
|
|
36
52
|
is_secret: isSecret,
|
|
37
53
|
}]
|
|
38
54
|
}, {
|
|
39
|
-
headers:
|
|
40
|
-
'Authorization': `Bearer ${config.apiKey}`,
|
|
41
|
-
'Accept': 'application/json',
|
|
42
|
-
'Content-Type': 'application/json',
|
|
43
|
-
},
|
|
55
|
+
headers: (0, api_1.getApiHeaders)(config, 'application/json'),
|
|
44
56
|
});
|
|
45
57
|
const { created, updated } = response.data;
|
|
46
58
|
const action = created > 0 ? 'created' : 'updated';
|
|
@@ -68,7 +80,7 @@ async function envSet(keyOrProject, valueOrKey, valueIfProject, options = {}) {
|
|
|
68
80
|
}
|
|
69
81
|
}
|
|
70
82
|
else {
|
|
71
|
-
// Global mode: solidactions env
|
|
83
|
+
// Global mode: solidactions env set <key> <value>
|
|
72
84
|
const key = keyOrProject;
|
|
73
85
|
const value = valueOrKey;
|
|
74
86
|
const isSecret = options.secret || false;
|
|
@@ -100,31 +112,33 @@ async function envSet(keyOrProject, valueOrKey, valueIfProject, options = {}) {
|
|
|
100
112
|
try {
|
|
101
113
|
// Check if variable already exists
|
|
102
114
|
const getResponse = await axios_1.default.get(`${config.host}/api/v1/variables`, {
|
|
103
|
-
headers:
|
|
104
|
-
'Authorization': `Bearer ${config.apiKey}`,
|
|
105
|
-
'Accept': 'application/json',
|
|
106
|
-
},
|
|
115
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
107
116
|
});
|
|
108
117
|
const variables = getResponse.data?.data || [];
|
|
109
118
|
const existing = variables.find((v) => v.key === key);
|
|
110
119
|
let action;
|
|
111
120
|
if (existing) {
|
|
121
|
+
if (!options.yes) {
|
|
122
|
+
console.log(chalk_1.default.yellow(`Global variable "${key}" already exists.`));
|
|
123
|
+
const confirm = await (0, prompts_1.default)({
|
|
124
|
+
type: 'confirm',
|
|
125
|
+
name: 'proceed',
|
|
126
|
+
message: 'Overwrite?',
|
|
127
|
+
initial: false,
|
|
128
|
+
});
|
|
129
|
+
if (!confirm.proceed) {
|
|
130
|
+
console.log(chalk_1.default.gray('Cancelled.'));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
112
134
|
await axios_1.default.put(`${config.host}/api/v1/variables/${existing.id}`, body, {
|
|
113
|
-
headers:
|
|
114
|
-
'Authorization': `Bearer ${config.apiKey}`,
|
|
115
|
-
'Accept': 'application/json',
|
|
116
|
-
'Content-Type': 'application/json',
|
|
117
|
-
},
|
|
135
|
+
headers: (0, api_1.getApiHeaders)(config, 'application/json'),
|
|
118
136
|
});
|
|
119
137
|
action = 'updated';
|
|
120
138
|
}
|
|
121
139
|
else {
|
|
122
140
|
await axios_1.default.post(`${config.host}/api/v1/variables`, body, {
|
|
123
|
-
headers:
|
|
124
|
-
'Authorization': `Bearer ${config.apiKey}`,
|
|
125
|
-
'Accept': 'application/json',
|
|
126
|
-
'Content-Type': 'application/json',
|
|
127
|
-
},
|
|
141
|
+
headers: (0, api_1.getApiHeaders)(config, 'application/json'),
|
|
128
142
|
});
|
|
129
143
|
action = 'created';
|
|
130
144
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -13,6 +13,8 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const os_1 = __importDefault(require("os"));
|
|
15
15
|
const chalk_1 = __importDefault(require("chalk"));
|
|
16
|
+
const api_1 = require("../utils/api");
|
|
17
|
+
const workspaces_1 = require("./workspaces");
|
|
16
18
|
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.solidactions');
|
|
17
19
|
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
|
|
18
20
|
function getConfig() {
|
|
@@ -63,17 +65,31 @@ async function init(apiKey, options) {
|
|
|
63
65
|
console.log(chalk_1.default.blue(`Initializing SolidActions CLI...`));
|
|
64
66
|
console.log(chalk_1.default.gray(`Host: ${host}`));
|
|
65
67
|
// Save the configuration
|
|
66
|
-
|
|
68
|
+
const config = {
|
|
67
69
|
host,
|
|
68
70
|
apiKey: apiKey.trim(),
|
|
69
|
-
}
|
|
71
|
+
};
|
|
72
|
+
saveConfig(config);
|
|
70
73
|
console.log(chalk_1.default.green('CLI initialized successfully!'));
|
|
71
74
|
console.log(chalk_1.default.gray(`Configuration saved to ${CONFIG_FILE}`));
|
|
72
75
|
console.log('');
|
|
76
|
+
// Set workspace — explicit flag or interactive prompt
|
|
77
|
+
try {
|
|
78
|
+
if (options.workspace) {
|
|
79
|
+
await (0, workspaces_1.workspaceSet)(options.workspace);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
await (0, api_1.ensureWorkspaceSelected)(config);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
console.log(chalk_1.default.yellow('Could not set workspace. Run `solidactions workspace set` later.'));
|
|
87
|
+
}
|
|
88
|
+
console.log('');
|
|
73
89
|
console.log(chalk_1.default.blue('Quick start:'));
|
|
74
|
-
console.log(chalk_1.default.gray(' solidactions deploy <
|
|
75
|
-
console.log(chalk_1.default.gray(' solidactions run <
|
|
76
|
-
console.log(chalk_1.default.gray(' solidactions
|
|
90
|
+
console.log(chalk_1.default.gray(' solidactions project deploy <name> Deploy current directory'));
|
|
91
|
+
console.log(chalk_1.default.gray(' solidactions run start <proj> <wf> Run a workflow'));
|
|
92
|
+
console.log(chalk_1.default.gray(' solidactions run list List recent runs'));
|
|
77
93
|
}
|
|
78
94
|
async function logout() {
|
|
79
95
|
if (fs_1.default.existsSync(CONFIG_FILE)) {
|
|
@@ -0,0 +1,52 @@
|
|
|
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.projectList = projectList;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const api_1 = require("../utils/api");
|
|
10
|
+
async function projectList() {
|
|
11
|
+
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
12
|
+
console.log(chalk_1.default.blue('Projects:\n'));
|
|
13
|
+
try {
|
|
14
|
+
const response = await axios_1.default.get(`${config.host}/api/v1/projects`, {
|
|
15
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
16
|
+
});
|
|
17
|
+
const projects = response.data.data || [];
|
|
18
|
+
if (projects.length === 0) {
|
|
19
|
+
console.log(chalk_1.default.gray('No projects found.'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.log(chalk_1.default.gray('NAME'.padEnd(25) + 'STATUS'.padEnd(15) + 'SNAPSHOT'.padEnd(30) + 'ENVIRONMENTS'));
|
|
23
|
+
console.log(chalk_1.default.gray('-'.repeat(100)));
|
|
24
|
+
for (const project of projects) {
|
|
25
|
+
const name = (project.name || '?').padEnd(25);
|
|
26
|
+
const status = project.status || '?';
|
|
27
|
+
const statusColor = status === 'ready' ? chalk_1.default.green : status === 'building' ? chalk_1.default.yellow : chalk_1.default.gray;
|
|
28
|
+
const snapshot = (project.snapshot_name || '-').padEnd(30);
|
|
29
|
+
const envs = (project.environments || [project.environment || 'production']).join(', ');
|
|
30
|
+
console.log(name +
|
|
31
|
+
statusColor(status.padEnd(15)) +
|
|
32
|
+
chalk_1.default.gray(snapshot) +
|
|
33
|
+
chalk_1.default.gray(envs));
|
|
34
|
+
}
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(chalk_1.default.gray(`${projects.length} project(s)`));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.response) {
|
|
40
|
+
if (error.response.status === 401) {
|
|
41
|
+
console.error(chalk_1.default.red('Authentication failed. Run "solidactions init <api-key>" to re-configure.'));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.error(chalk_1.default.red(`Failed: ${error.response.status}`), error.response.data);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.error(chalk_1.default.red('Connection failed:'), error.message);
|
|
49
|
+
}
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -6,20 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.logsBuild = logsBuild;
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
-
const
|
|
9
|
+
const api_1 = require("../utils/api");
|
|
10
10
|
async function logsBuild(projectName) {
|
|
11
|
-
const config = (0,
|
|
12
|
-
if (!config?.apiKey) {
|
|
13
|
-
console.error(chalk_1.default.red('Not initialized. Run "solidactions init <api-key>" first.'));
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
11
|
+
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
16
12
|
console.log(chalk_1.default.blue(`Fetching build logs for project "${projectName}"...`));
|
|
17
13
|
try {
|
|
18
14
|
const response = await axios_1.default.get(`${config.host}/api/v1/projects/${projectName}/build-log`, {
|
|
19
|
-
headers:
|
|
20
|
-
'Authorization': `Bearer ${config.apiKey}`,
|
|
21
|
-
'Accept': 'application/json',
|
|
22
|
-
},
|
|
15
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
23
16
|
});
|
|
24
17
|
const buildLog = response.data.build_log || response.data;
|
|
25
18
|
if (!buildLog || buildLog.length === 0) {
|
package/dist/commands/pull.js
CHANGED
|
@@ -6,37 +6,47 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.pull = pull;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const zlib_1 = require("zlib");
|
|
10
|
+
const promises_1 = require("stream/promises");
|
|
11
|
+
const stream_1 = require("stream");
|
|
12
|
+
const tar_1 = require("tar");
|
|
9
13
|
const axios_1 = __importDefault(require("axios"));
|
|
10
14
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
async function pull(projectName, destPath) {
|
|
14
|
-
const config = (0,
|
|
15
|
-
if (!config?.apiKey) {
|
|
16
|
-
console.error(chalk_1.default.red('Not initialized. Run `solidactions init <api-key>` first.'));
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
15
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
16
|
+
const api_1 = require("../utils/api");
|
|
17
|
+
async function pull(projectName, destPath, options = {}) {
|
|
18
|
+
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
19
19
|
const destination = destPath ? path_1.default.resolve(destPath) : process.cwd();
|
|
20
|
+
// Warn if destination has existing files
|
|
21
|
+
if (fs_1.default.existsSync(destination)) {
|
|
22
|
+
const entries = fs_1.default.readdirSync(destination);
|
|
23
|
+
if (entries.length > 0 && !options.yes) {
|
|
24
|
+
console.log(chalk_1.default.yellow(`Destination "${destination}" is not empty (${entries.length} items).`));
|
|
25
|
+
console.log(chalk_1.default.yellow('Pulling will overwrite existing files.'));
|
|
26
|
+
const response = await (0, prompts_1.default)({
|
|
27
|
+
type: 'confirm',
|
|
28
|
+
name: 'proceed',
|
|
29
|
+
message: 'Continue?',
|
|
30
|
+
initial: false,
|
|
31
|
+
});
|
|
32
|
+
if (!response.proceed) {
|
|
33
|
+
console.log(chalk_1.default.gray('Cancelled.'));
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
20
38
|
console.log(chalk_1.default.blue(`Pulling project "${projectName}"...`));
|
|
21
39
|
try {
|
|
22
40
|
const response = await axios_1.default.get(`${config.host}/api/v1/projects/${projectName}/source`, {
|
|
23
|
-
headers: {
|
|
24
|
-
'Authorization': `Bearer ${config.apiKey}`,
|
|
25
|
-
'Accept': 'application/octet-stream',
|
|
26
|
-
},
|
|
41
|
+
headers: { ...(0, api_1.getApiHeaders)(config), 'Accept': 'application/octet-stream' },
|
|
27
42
|
responseType: 'arraybuffer',
|
|
28
43
|
});
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
// Write the zip to a temp file
|
|
32
|
-
fs_1.default.writeFileSync(tempZipPath, zipBuffer);
|
|
33
|
-
console.log(chalk_1.default.gray(`Downloaded ${zipBuffer.length} bytes`));
|
|
44
|
+
const buffer = Buffer.from(response.data);
|
|
45
|
+
console.log(chalk_1.default.gray(`Downloaded ${buffer.length} bytes`));
|
|
34
46
|
console.log(chalk_1.default.yellow(`Extracting to ${destination}...`));
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
// Clean up temp file
|
|
39
|
-
fs_1.default.unlinkSync(tempZipPath);
|
|
47
|
+
fs_1.default.mkdirSync(destination, { recursive: true });
|
|
48
|
+
const readable = stream_1.Readable.from(buffer);
|
|
49
|
+
await (0, promises_1.pipeline)(readable, (0, zlib_1.createGunzip)(), (0, tar_1.extract)({ cwd: destination, strip: 0 }));
|
|
40
50
|
console.log(chalk_1.default.green(`Project "${projectName}" pulled successfully!`));
|
|
41
51
|
}
|
|
42
52
|
catch (error) {
|
|
@@ -0,0 +1,186 @@
|
|
|
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.runs = runs;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const api_1 = require("../utils/api");
|
|
10
|
+
async function runs(projectName, options = {}) {
|
|
11
|
+
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
12
|
+
// Default limit is lower for --detailed (more data per run)
|
|
13
|
+
const defaultLimit = options.detailed ? 5 : 20;
|
|
14
|
+
const limit = options.limit || defaultLimit;
|
|
15
|
+
try {
|
|
16
|
+
const params = { limit };
|
|
17
|
+
if (projectName)
|
|
18
|
+
params.project = projectName;
|
|
19
|
+
if (options.offset)
|
|
20
|
+
params.offset = options.offset;
|
|
21
|
+
if (options.status)
|
|
22
|
+
params.status = options.status;
|
|
23
|
+
if (options.since)
|
|
24
|
+
params.since = parseSince(options.since);
|
|
25
|
+
if (options.workflow)
|
|
26
|
+
params.workflow = options.workflow;
|
|
27
|
+
if (options.detailed)
|
|
28
|
+
params.detailed = '1';
|
|
29
|
+
const response = await axios_1.default.get(`${config.host}/api/v1/runs`, {
|
|
30
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
31
|
+
params,
|
|
32
|
+
});
|
|
33
|
+
const runsList = response.data.data || response.data;
|
|
34
|
+
if (!runsList || runsList.length === 0) {
|
|
35
|
+
if (options.json) {
|
|
36
|
+
console.log('[]');
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.log(chalk_1.default.gray('No runs found.'));
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (options.json) {
|
|
44
|
+
console.log(JSON.stringify(runsList, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Human-readable output
|
|
48
|
+
if (options.detailed) {
|
|
49
|
+
displayDetailedList(runsList, projectName);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
displaySummaryTable(runsList, projectName);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (error.response) {
|
|
57
|
+
if (error.response.status === 401) {
|
|
58
|
+
console.error(chalk_1.default.red('Authentication failed. Run "solidactions init <api-key>" to re-configure.'));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.error(chalk_1.default.red(`Failed: ${error.response.status}`), error.response.data);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.error(chalk_1.default.red('Connection failed:'), error.message);
|
|
66
|
+
}
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// ─── Display modes ─────────────────────────────────────────────────────────
|
|
71
|
+
function displaySummaryTable(runsList, projectName) {
|
|
72
|
+
const header = projectName ? `Recent runs for "${projectName}":` : 'Recent runs:';
|
|
73
|
+
console.log(chalk_1.default.blue(header));
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(chalk_1.default.gray('ID'.padEnd(8) + 'WORKFLOW'.padEnd(25) + 'STATUS'.padEnd(12) + 'TRIGGERED'.padEnd(22) + 'TRIGGERED BY'));
|
|
76
|
+
console.log(chalk_1.default.gray('-'.repeat(90)));
|
|
77
|
+
for (const run of runsList) {
|
|
78
|
+
const id = String(run.id || '?').padEnd(8);
|
|
79
|
+
const workflow = truncate(run.workflow_name || '?', 24).padEnd(25);
|
|
80
|
+
const status = run.execution_status || run.status || '?';
|
|
81
|
+
const statusColor = getStatusColor(status);
|
|
82
|
+
const triggeredAt = run.triggered_at ? new Date(run.triggered_at).toLocaleString() : '-';
|
|
83
|
+
const triggeredBy = run.triggered_by || '-';
|
|
84
|
+
console.log(chalk_1.default.gray(id) +
|
|
85
|
+
workflow +
|
|
86
|
+
statusColor(status.padEnd(12)) +
|
|
87
|
+
chalk_1.default.gray(triggeredAt.padEnd(22)) +
|
|
88
|
+
chalk_1.default.gray(triggeredBy));
|
|
89
|
+
}
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log(chalk_1.default.gray(`Showing ${runsList.length} run(s)`));
|
|
92
|
+
}
|
|
93
|
+
function displayDetailedList(runsList, projectName) {
|
|
94
|
+
const header = projectName ? `Runs for "${projectName}" (detailed):` : 'Runs (detailed):';
|
|
95
|
+
console.log(chalk_1.default.blue(header));
|
|
96
|
+
for (const run of runsList) {
|
|
97
|
+
const status = run.execution_status || run.status || '?';
|
|
98
|
+
const statusColor = getStatusColor(status);
|
|
99
|
+
const exitStr = run.exit_code !== null && run.exit_code !== undefined ? ` (exit ${run.exit_code})` : '';
|
|
100
|
+
console.log('');
|
|
101
|
+
console.log(chalk_1.default.bold(` Run #${run.id}`) + chalk_1.default.gray(` — ${run.workflow_name || '?'} (${run.project_name || '?'})`));
|
|
102
|
+
console.log(` Status: ${statusColor(status)}${chalk_1.default.gray(exitStr)}`);
|
|
103
|
+
console.log(` Trigger: ${chalk_1.default.gray(run.triggered_by || '-')}`);
|
|
104
|
+
// Timeline
|
|
105
|
+
if (run.timeline) {
|
|
106
|
+
const formatTs = (iso) => iso ? new Date(iso).toLocaleString() : '-';
|
|
107
|
+
console.log(` Triggered: ${chalk_1.default.gray(formatTs(run.timeline.triggered))}`);
|
|
108
|
+
console.log(` Started: ${chalk_1.default.gray(formatTs(run.timeline.started))}`);
|
|
109
|
+
console.log(` Completed: ${chalk_1.default.gray(formatTs(run.timeline.completed))}`);
|
|
110
|
+
}
|
|
111
|
+
// Steps summary
|
|
112
|
+
if (run.steps && run.steps.length > 0) {
|
|
113
|
+
const completed = run.steps.filter((s) => s.completed_at || s.completed_at_epoch_ms).length;
|
|
114
|
+
const totalDuration = run.steps.reduce((sum, s) => sum + (s.duration_ms || 0), 0);
|
|
115
|
+
console.log(` Steps: ${chalk_1.default.gray(`${completed}/${run.steps.length} completed (${formatDuration(totalDuration)} total)`)}`);
|
|
116
|
+
}
|
|
117
|
+
// Logs snippet (first errors or last 3 lines)
|
|
118
|
+
if (run.logs && typeof run.logs === 'string' && run.logs.trim()) {
|
|
119
|
+
const lines = run.logs.trim().split('\n');
|
|
120
|
+
const errorLines = lines.filter((l) => /error|fail|exception/i.test(l));
|
|
121
|
+
if (errorLines.length > 0) {
|
|
122
|
+
console.log(` Errors:`);
|
|
123
|
+
for (const line of errorLines.slice(0, 3)) {
|
|
124
|
+
console.log(chalk_1.default.red(` ${truncate(line, 80)}`));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
console.log('');
|
|
130
|
+
console.log(chalk_1.default.gray(`Showing ${runsList.length} run(s)`));
|
|
131
|
+
}
|
|
132
|
+
// ─── Utility ───────────────────────────────────────────────────────────────
|
|
133
|
+
/**
|
|
134
|
+
* Parse --since value into epoch ms.
|
|
135
|
+
* Accepts: "1h", "30m", "2d", "1w", or ISO date string.
|
|
136
|
+
*/
|
|
137
|
+
function parseSince(since) {
|
|
138
|
+
const match = since.match(/^(\d+)(m|h|d|w)$/);
|
|
139
|
+
if (match) {
|
|
140
|
+
const value = parseInt(match[1]);
|
|
141
|
+
const unit = match[2];
|
|
142
|
+
const multipliers = {
|
|
143
|
+
m: 60 * 1000,
|
|
144
|
+
h: 60 * 60 * 1000,
|
|
145
|
+
d: 24 * 60 * 60 * 1000,
|
|
146
|
+
w: 7 * 24 * 60 * 60 * 1000,
|
|
147
|
+
};
|
|
148
|
+
const ms = Date.now() - (value * multipliers[unit]);
|
|
149
|
+
return String(ms);
|
|
150
|
+
}
|
|
151
|
+
// Assume ISO date string — let the backend parse it
|
|
152
|
+
return since;
|
|
153
|
+
}
|
|
154
|
+
function formatDuration(ms) {
|
|
155
|
+
if (ms === null || ms === undefined)
|
|
156
|
+
return '-';
|
|
157
|
+
if (ms < 1000)
|
|
158
|
+
return `${ms}ms`;
|
|
159
|
+
if (ms < 60000)
|
|
160
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
161
|
+
if (ms < 3600000)
|
|
162
|
+
return `${Math.round(ms / 60000)}m`;
|
|
163
|
+
return `${Math.round(ms / 3600000)}h`;
|
|
164
|
+
}
|
|
165
|
+
function truncate(str, max) {
|
|
166
|
+
if (str.length <= max)
|
|
167
|
+
return str;
|
|
168
|
+
return str.substring(0, max - 3) + '...';
|
|
169
|
+
}
|
|
170
|
+
function getStatusColor(status) {
|
|
171
|
+
switch (status?.toLowerCase()) {
|
|
172
|
+
case 'completed':
|
|
173
|
+
case 'success':
|
|
174
|
+
return chalk_1.default.green;
|
|
175
|
+
case 'running':
|
|
176
|
+
return chalk_1.default.blue;
|
|
177
|
+
case 'pending':
|
|
178
|
+
case 'queued':
|
|
179
|
+
return chalk_1.default.yellow;
|
|
180
|
+
case 'failed':
|
|
181
|
+
case 'error':
|
|
182
|
+
return chalk_1.default.red;
|
|
183
|
+
default:
|
|
184
|
+
return chalk_1.default.gray;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -6,13 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.run = run;
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
-
const
|
|
9
|
+
const api_1 = require("../utils/api");
|
|
10
10
|
async function run(projectName, workflowName, options) {
|
|
11
|
-
const config = (0,
|
|
12
|
-
if (!config?.apiKey) {
|
|
13
|
-
console.error(chalk_1.default.red('Not initialized. Run "solidactions init <api-key>" first.'));
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
11
|
+
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
16
12
|
const environment = options.env || 'dev';
|
|
17
13
|
const projectSlug = environment === 'production'
|
|
18
14
|
? projectName
|
|
@@ -30,11 +26,7 @@ async function run(projectName, workflowName, options) {
|
|
|
30
26
|
}
|
|
31
27
|
try {
|
|
32
28
|
const response = await axios_1.default.post(`${config.host}/api/v1/projects/${projectSlug}/workflows/${workflowName}/trigger`, { input: inputData }, {
|
|
33
|
-
headers:
|
|
34
|
-
'Authorization': `Bearer ${config.apiKey}`,
|
|
35
|
-
'Accept': 'application/json',
|
|
36
|
-
'Content-Type': 'application/json',
|
|
37
|
-
},
|
|
29
|
+
headers: (0, api_1.getApiHeaders)(config, 'application/json'),
|
|
38
30
|
});
|
|
39
31
|
const runData = response.data.run || response.data;
|
|
40
32
|
console.log(chalk_1.default.green(`Workflow triggered! Run ID: ${runData.id}`));
|
|
@@ -46,10 +38,7 @@ async function run(projectName, workflowName, options) {
|
|
|
46
38
|
try {
|
|
47
39
|
attempts++;
|
|
48
40
|
const statusResponse = await axios_1.default.get(`${config.host}/api/v1/runs/${runData.id}`, {
|
|
49
|
-
headers:
|
|
50
|
-
'Authorization': `Bearer ${config.apiKey}`,
|
|
51
|
-
'Accept': 'application/json',
|
|
52
|
-
},
|
|
41
|
+
headers: (0, api_1.getApiHeaders)(config),
|
|
53
42
|
});
|
|
54
43
|
const status = statusResponse.data.status;
|
|
55
44
|
if (status === 'completed') {
|