elestio 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/LICENSE +21 -0
- package/README.md +262 -0
- package/bin/elestio.js +5 -0
- package/package.json +42 -0
- package/src/api.js +105 -0
- package/src/cli.js +828 -0
- package/src/commands/access.js +132 -0
- package/src/commands/actions.js +427 -0
- package/src/commands/auth.js +146 -0
- package/src/commands/backups.js +215 -0
- package/src/commands/billing.js +106 -0
- package/src/commands/cicd.js +403 -0
- package/src/commands/projects.js +145 -0
- package/src/commands/services.js +294 -0
- package/src/commands/templates.js +188 -0
- package/src/commands/volumes.js +117 -0
- package/src/config.js +84 -0
- package/src/utils.js +162 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { apiRequest } from '../api.js';
|
|
2
|
+
import { loadConfig } from '../config.js';
|
|
3
|
+
import { log, colors, formatTable, sleep, outputJson } from '../utils.js';
|
|
4
|
+
import { getServiceDetails } from './services.js';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
|
|
10
|
+
// ── List / Details ──
|
|
11
|
+
|
|
12
|
+
export async function getCicdServices(projectId = null, json = false) {
|
|
13
|
+
const config = loadConfig();
|
|
14
|
+
const pid = projectId || config.defaultProject;
|
|
15
|
+
if (!pid) throw new Error('Project ID required');
|
|
16
|
+
|
|
17
|
+
const response = await apiRequest('/api/cicd/getCICDServices', 'POST', { projectID: String(pid) });
|
|
18
|
+
let services = Array.isArray(response) ? response : (response.data?.services || []);
|
|
19
|
+
if (response.status === 'KO') throw new Error(response.message || 'Failed');
|
|
20
|
+
|
|
21
|
+
if (json) { outputJson(services); return services; }
|
|
22
|
+
if (services.length === 0) { log('info', 'No CI/CD targets found'); return []; }
|
|
23
|
+
|
|
24
|
+
const columns = [
|
|
25
|
+
{ key: 'displayName', label: 'Name' },
|
|
26
|
+
{ key: 'vmID', label: 'vmID' },
|
|
27
|
+
{ key: 'serverName', label: 'CNAME' },
|
|
28
|
+
{ key: 'vmProvider', label: 'Provider' },
|
|
29
|
+
{ key: 'vmRegion', label: 'Region' }
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const data = services.map(s => ({
|
|
33
|
+
displayName: s.displayName || s.name || 'N/A',
|
|
34
|
+
vmID: s.providerServerID || s.vmID || 'N/A',
|
|
35
|
+
serverName: s.serverName || 'N/A',
|
|
36
|
+
vmProvider: s.vmProvider || s.provider || 'N/A',
|
|
37
|
+
vmRegion: s.vmRegion || s.datacenter || 'N/A'
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
console.log(`\n${colors.bold}CI/CD Targets (${services.length})${colors.reset}\n`);
|
|
41
|
+
console.log(formatTable(data, columns));
|
|
42
|
+
console.log('');
|
|
43
|
+
return services;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function getServicePipelines(vmID, projectId = null, json = false) {
|
|
47
|
+
const config = loadConfig();
|
|
48
|
+
const pid = projectId || config.defaultProject;
|
|
49
|
+
if (!pid) throw new Error('Project ID required');
|
|
50
|
+
|
|
51
|
+
const response = await apiRequest('/api/cicd/getServicePipelines', 'POST', { projectID: String(pid), vmID: String(vmID) });
|
|
52
|
+
if (response.status !== 'OK') throw new Error(response.message || 'Failed');
|
|
53
|
+
|
|
54
|
+
const pipelines = Array.isArray(response.data) ? response.data : (response.data?.pipelines || []);
|
|
55
|
+
if (json) { outputJson(pipelines); return pipelines; }
|
|
56
|
+
if (pipelines.length === 0) { log('info', `No pipelines on ${vmID}`); return []; }
|
|
57
|
+
|
|
58
|
+
const columns = [
|
|
59
|
+
{ key: 'id', label: 'ID' },
|
|
60
|
+
{ key: 'pipelineName', label: 'Name' },
|
|
61
|
+
{ key: 'type', label: 'Mode' },
|
|
62
|
+
{ key: 'status', label: 'Status' },
|
|
63
|
+
{ key: 'buildStatus', label: 'Build' }
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
console.log(`\n${colors.bold}Pipelines on ${vmID}${colors.reset}\n`);
|
|
67
|
+
console.log(formatTable(pipelines, columns));
|
|
68
|
+
console.log('');
|
|
69
|
+
return pipelines;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function getPipelineDetails(vmID, pipelineID, projectId = null, json = false) {
|
|
73
|
+
const config = loadConfig();
|
|
74
|
+
const pid = projectId || config.defaultProject;
|
|
75
|
+
if (!pid) throw new Error('Project ID required');
|
|
76
|
+
|
|
77
|
+
const response = await apiRequest('/api/cicd/getPipelineDetails', 'POST', {
|
|
78
|
+
vmID: String(vmID), projectID: String(pid), pipelineID: parseInt(pipelineID)
|
|
79
|
+
});
|
|
80
|
+
if (response.status !== 'OK') throw new Error(response.message || 'Failed');
|
|
81
|
+
|
|
82
|
+
const pipeline = response.data;
|
|
83
|
+
if (json) { outputJson(pipeline); return pipeline; }
|
|
84
|
+
|
|
85
|
+
console.log(`\n${colors.bold}Pipeline ${pipelineID}${colors.reset}\n`);
|
|
86
|
+
console.log(` Name: ${pipeline.name || 'N/A'}`);
|
|
87
|
+
console.log(` Mode: ${pipeline.CICDMode || 'N/A'}`);
|
|
88
|
+
console.log(` Status: ${pipeline.status || 'N/A'}`);
|
|
89
|
+
console.log(` URL: ${pipeline.url || 'N/A'}`);
|
|
90
|
+
if (pipeline.gitData) {
|
|
91
|
+
console.log(` Repo: ${pipeline.gitData.repoUrl || 'N/A'}`);
|
|
92
|
+
console.log(` Branch: ${pipeline.gitData.branch || 'N/A'}`);
|
|
93
|
+
}
|
|
94
|
+
console.log('');
|
|
95
|
+
return pipeline;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Pipeline Actions ──
|
|
99
|
+
|
|
100
|
+
export async function doActionOnPipeline(vmID, pipelineID, action, additionalParams = {}, projectId = null) {
|
|
101
|
+
const config = loadConfig();
|
|
102
|
+
const pid = projectId || config.defaultProject;
|
|
103
|
+
if (!pid) throw new Error('Project ID required');
|
|
104
|
+
|
|
105
|
+
const response = await apiRequest('/api/cicd/doActionOnPipeline', 'POST', {
|
|
106
|
+
vmID: String(vmID), projectID: String(pid), pipelineID: parseInt(pipelineID), action, ...additionalParams
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (response.status !== 'OK' && !response.action) throw new Error(response.message || `Action "${action}" failed`);
|
|
110
|
+
return response;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function restartPipeline(vmID, pipelineID, projectId) {
|
|
114
|
+
log('info', `Restarting pipeline ${pipelineID}...`);
|
|
115
|
+
const result = await doActionOnPipeline(vmID, pipelineID, 'restartAppStack', {}, projectId);
|
|
116
|
+
log('success', 'Pipeline restarted');
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function stopPipeline(vmID, pipelineID, projectId) {
|
|
121
|
+
log('info', `Stopping pipeline ${pipelineID}...`);
|
|
122
|
+
const result = await doActionOnPipeline(vmID, pipelineID, 'stopAppStack', {}, projectId);
|
|
123
|
+
log('success', 'Pipeline stopped');
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function deletePipeline(vmID, pipelineID, projectId, force) {
|
|
128
|
+
if (!force) throw new Error('Requires --force flag');
|
|
129
|
+
log('info', `Deleting pipeline ${pipelineID}...`);
|
|
130
|
+
const result = await doActionOnPipeline(vmID, pipelineID, 'deletePipeline', {}, projectId);
|
|
131
|
+
log('success', 'Pipeline deleted');
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function resyncPipeline(vmID, pipelineID, projectId) {
|
|
136
|
+
log('info', `Re-syncing pipeline ${pipelineID}...`);
|
|
137
|
+
const result = await doActionOnPipeline(vmID, pipelineID, 'reSyncPipeline', {}, projectId);
|
|
138
|
+
log('success', 'Pipeline re-sync initiated');
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function getPipelineLogs(vmID, pipelineID, projectId) {
|
|
143
|
+
const result = await doActionOnPipeline(vmID, pipelineID, 'pipelineRunningLogs', {}, projectId);
|
|
144
|
+
if (result.logs) console.log('\n' + result.logs);
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function getPipelineHistory(vmID, pipelineID, projectId, json = false) {
|
|
149
|
+
const result = await doActionOnPipeline(vmID, pipelineID, 'getHistory', {}, projectId);
|
|
150
|
+
const history = result.data?.history || result.history || [];
|
|
151
|
+
if (json) { outputJson(history); return history; }
|
|
152
|
+
if (history.length === 0) { log('info', 'No build history'); return []; }
|
|
153
|
+
|
|
154
|
+
console.log(`\n${colors.bold}Build History${colors.reset}\n`);
|
|
155
|
+
history.forEach(h => console.log(` ${h.filepath || h.file}: ${h.status || 'N/A'}`));
|
|
156
|
+
console.log('');
|
|
157
|
+
return history;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function viewPipelineLog(vmID, pipelineID, filepath, projectId) {
|
|
161
|
+
const config = loadConfig();
|
|
162
|
+
const pid = projectId || config.defaultProject;
|
|
163
|
+
if (!pid) throw new Error('Project ID required');
|
|
164
|
+
|
|
165
|
+
const response = await apiRequest('/api/cicd/viewPipelineLog', 'POST', {
|
|
166
|
+
vmID: String(vmID), projectID: String(pid), pipelineID: parseInt(pipelineID), filepath
|
|
167
|
+
});
|
|
168
|
+
if (response.status !== 'OK') throw new Error(response.message || 'Failed');
|
|
169
|
+
console.log(response.data?.content || response.content || '');
|
|
170
|
+
return response;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── Pipeline Domains ──
|
|
174
|
+
|
|
175
|
+
export async function listPipelineDomains(vmID, pipelineID, projectId, json = false) {
|
|
176
|
+
const result = await doActionOnPipeline(vmID, pipelineID, 'SSLDomainsList', {}, projectId);
|
|
177
|
+
const domains = result.data?.domains || result.domains || [];
|
|
178
|
+
if (json) { outputJson(domains); return domains; }
|
|
179
|
+
if (domains.length === 0) { log('info', 'No custom domains'); return []; }
|
|
180
|
+
|
|
181
|
+
console.log(`\n${colors.bold}Pipeline Domains${colors.reset}\n`);
|
|
182
|
+
domains.forEach(d => console.log(` ${d}`));
|
|
183
|
+
console.log('');
|
|
184
|
+
return domains;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function addPipelineDomain(vmID, pipelineID, domain, projectId) {
|
|
188
|
+
log('info', `Adding domain ${domain}...`);
|
|
189
|
+
const result = await doActionOnPipeline(vmID, pipelineID, 'SSLDomainsAdd', { domain }, projectId);
|
|
190
|
+
log('success', `Domain ${domain} added`);
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function removePipelineDomain(vmID, pipelineID, domain, projectId) {
|
|
195
|
+
log('info', `Removing domain ${domain}...`);
|
|
196
|
+
const result = await doActionOnPipeline(vmID, pipelineID, 'SSLDomainsRemove', { domain }, projectId);
|
|
197
|
+
log('success', `Domain ${domain} removed`);
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── Create Pipeline ──
|
|
202
|
+
|
|
203
|
+
export async function createPipeline(configFile) {
|
|
204
|
+
if (!configFile || !fs.existsSync(configFile)) throw new Error(`Config file not found: ${configFile}`);
|
|
205
|
+
|
|
206
|
+
const pipelineConfig = JSON.parse(fs.readFileSync(configFile, 'utf-8'));
|
|
207
|
+
log('info', 'Creating pipeline...');
|
|
208
|
+
const response = await apiRequest('/api/cicd/createCiCdExistServer', 'POST', pipelineConfig);
|
|
209
|
+
|
|
210
|
+
if (response.status !== 'OK' && !response.providerServerID) throw new Error(response.message || 'Failed');
|
|
211
|
+
|
|
212
|
+
log('success', 'Pipeline created');
|
|
213
|
+
log('info', ` Service: ${response.serviceName || 'N/A'}`);
|
|
214
|
+
return response;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Auto-create Pipeline ──
|
|
218
|
+
|
|
219
|
+
async function findGitAuthID(gitType, projectId) {
|
|
220
|
+
const config = loadConfig();
|
|
221
|
+
const pid = projectId || config.defaultProject;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const cicdResp = await apiRequest('/api/cicd/getCICDServices', 'POST', { projectID: String(pid) });
|
|
225
|
+
const cicdServices = Array.isArray(cicdResp) ? cicdResp : (cicdResp.data?.services || []);
|
|
226
|
+
for (const svc of cicdServices) {
|
|
227
|
+
const vmID = svc.providerServerID || svc.vmID;
|
|
228
|
+
try {
|
|
229
|
+
const pipResp = await apiRequest('/api/cicd/getServicePipelines', 'POST', { projectID: String(pid), vmID: String(vmID) });
|
|
230
|
+
const pipelines = Array.isArray(pipResp.data) ? pipResp.data : (pipResp.data?.pipelines || []);
|
|
231
|
+
for (const p of pipelines) {
|
|
232
|
+
if (p.type === gitType || p.mode === gitType) {
|
|
233
|
+
const det = await apiRequest('/api/cicd/getPipelineDetails', 'POST', { vmID: String(vmID), projectID: String(pid), pipelineID: p.id });
|
|
234
|
+
if (det.status === 'OK' && det.data?.authID) return String(det.data.authID);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch { /* skip */ }
|
|
238
|
+
}
|
|
239
|
+
} catch { /* skip */ }
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function getCicdTargetInfo(vmID, projectId) {
|
|
244
|
+
const config = loadConfig();
|
|
245
|
+
const pid = projectId || config.defaultProject;
|
|
246
|
+
|
|
247
|
+
const response = await apiRequest('/api/cicd/getCICDServices', 'POST', { projectID: String(pid) });
|
|
248
|
+
const services = Array.isArray(response) ? response : (response.data?.services || []);
|
|
249
|
+
const target = services.find(s => String(s.providerServerID) === String(vmID) || String(s.vmID) === String(vmID));
|
|
250
|
+
if (!target) throw new Error(`CI/CD target ${vmID} not found`);
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
displayName: target.displayName || target.name,
|
|
254
|
+
id: String(target.id || target.serverID),
|
|
255
|
+
serverName: target.serverName || '',
|
|
256
|
+
vmID: String(target.providerServerID || target.vmID)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const RUNTIME_PRESETS = {
|
|
261
|
+
'static': { runtime: 'staticSPA', buildDir: '/dist', framework: 'Vite.js', buildCmd: 'npm run build', runCmd: '', installCmd: 'npm install', version: '20', containerPort: '3000' },
|
|
262
|
+
'node': { runtime: 'node', buildDir: '/', framework: 'No Framework', buildCmd: 'npm run build', runCmd: 'npm start', installCmd: 'npm install', version: '20', containerPort: '3000' },
|
|
263
|
+
'docker': { runtime: '', buildDir: '/', framework: '', buildCmd: '', runCmd: '', installCmd: '', version: '', containerPort: '80' }
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export async function autoCreatePipeline(options = {}) {
|
|
267
|
+
const config = loadConfig();
|
|
268
|
+
const pid = options.project || config.defaultProject;
|
|
269
|
+
if (!pid) throw new Error('Project ID required');
|
|
270
|
+
if (!options.target) throw new Error('CI/CD target vmID required (--target)');
|
|
271
|
+
if (!options.name) throw new Error('Pipeline name required (--name)');
|
|
272
|
+
|
|
273
|
+
const mode = options.mode || 'github';
|
|
274
|
+
let gitType, appType;
|
|
275
|
+
|
|
276
|
+
if (mode === 'docker') { gitType = null; appType = 'docker'; }
|
|
277
|
+
else if (mode.startsWith('gitlab')) { gitType = 'GITLAB'; appType = mode === 'gitlab-fullstack' ? 'node' : 'static'; }
|
|
278
|
+
else { gitType = 'GITHUB'; appType = mode === 'github-fullstack' ? 'node' : 'static'; }
|
|
279
|
+
|
|
280
|
+
log('info', 'Getting CI/CD target info...');
|
|
281
|
+
const target = await getCicdTargetInfo(options.target, pid);
|
|
282
|
+
log('success', `Target: ${target.displayName} (${target.vmID})`);
|
|
283
|
+
|
|
284
|
+
let authID = options.authId ? String(options.authId) : null;
|
|
285
|
+
let repoData = null;
|
|
286
|
+
|
|
287
|
+
if (gitType) {
|
|
288
|
+
if (!options.repo) throw new Error('Repo required (--repo owner/repo)');
|
|
289
|
+
|
|
290
|
+
if (!authID) {
|
|
291
|
+
log('info', `Looking for ${gitType} auth...`);
|
|
292
|
+
authID = await findGitAuthID(gitType, pid);
|
|
293
|
+
if (!authID) throw new Error(`No ${gitType} auth found. Connect in dashboard or provide --auth-id`);
|
|
294
|
+
}
|
|
295
|
+
log('success', `Auth ID: ${authID}`);
|
|
296
|
+
|
|
297
|
+
const orgsResp = await apiRequest('/api/cicd/getGitOrgs', 'POST', { projectID: String(pid), gitType, authID: String(authID) });
|
|
298
|
+
const orgs = orgsResp.data?.scopeUsers || [];
|
|
299
|
+
if (orgs.length === 0) throw new Error(`No ${gitType} accounts found`);
|
|
300
|
+
|
|
301
|
+
const [owner, repoName] = options.repo.split('/');
|
|
302
|
+
const repos = await apiRequest('/api/cicd/getRepoByOrg', 'POST', { projectID: String(pid), gitType, authID: String(authID), orgName: owner, gitUser: owner });
|
|
303
|
+
const repoList = repos.data || repos || [];
|
|
304
|
+
repoData = (Array.isArray(repoList) ? repoList : []).find(r => r.name?.toLowerCase() === repoName?.toLowerCase());
|
|
305
|
+
if (!repoData) throw new Error(`Repo "${repoName}" not found`);
|
|
306
|
+
log('success', `Repo: ${repoData.name}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const preset = RUNTIME_PRESETS[appType] || RUNTIME_PRESETS.static;
|
|
310
|
+
const branch = options.branch || 'main';
|
|
311
|
+
const gitHost = gitType === 'GITLAB' ? 'gitlab.com' : 'github.com';
|
|
312
|
+
|
|
313
|
+
const payload = {
|
|
314
|
+
CICDMode: gitType || 'DOCKER',
|
|
315
|
+
pipelineName: options.name,
|
|
316
|
+
projectID: String(pid),
|
|
317
|
+
authID: authID ? String(authID) : '0',
|
|
318
|
+
ports: [{ protocol: 'HTTPS', targetProtocol: 'HTTP', listeningPort: '443', targetPort: '3000', public: true, targetIP: '172.17.0.1', path: '/', isAuth: false, login: '', password: '' }],
|
|
319
|
+
variables: options.variables || '',
|
|
320
|
+
cluster: { isCluster: false, createNew: false, target },
|
|
321
|
+
imageData: gitType ? { isPipelineTemplate: false } : { imageName: options.image || 'nginx', imageTag: options.imageTag || 'alpine', registryUrl: '', isPipelineTemplate: false },
|
|
322
|
+
configData: { buildDir: options.buildDir || preset.buildDir, rootDir: options.rootDir || '/', runtime: preset.runtime, version: options.nodeVersion || preset.version, framework: options.framework || preset.framework, buildCmd: options.buildCmd || preset.buildCmd, runCmd: options.runCmd || preset.runCmd, installCmd: options.installCmd || preset.installCmd },
|
|
323
|
+
gitData: gitType ? { projectName: options.name, branch, repoUrl: `https://${gitHost}/${options.repo}`, cloneUrl: `https://${gitHost}/${options.repo}.git`, repoID: String(repoData.id), repo: options.repo.split('/')[1] } : { projectName: options.name, branch: 'main', repoUrl: '', cloneUrl: '', repoID: 0, repo: '' },
|
|
324
|
+
exposedPorts: [{ protocol: 'HTTP', hostPort: '3000', containerPort: preset.containerPort, interface: '172.17.0.1' }],
|
|
325
|
+
gitVolumeConfig: [],
|
|
326
|
+
isNeedToCreateRepo: !gitType,
|
|
327
|
+
isPublicGitRepo: repoData ? String(!repoData.private) : 'false',
|
|
328
|
+
isMovePipeline: false,
|
|
329
|
+
nonRepoWorkSpaces: [''],
|
|
330
|
+
gitUserFormData: {}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
log('info', 'Creating pipeline...');
|
|
334
|
+
const response = await apiRequest('/api/cicd/createCiCdExistServer', 'POST', payload);
|
|
335
|
+
if (response.status !== 'OK' && !response.providerServerID) throw new Error(response.message || JSON.stringify(response));
|
|
336
|
+
|
|
337
|
+
log('success', 'Pipeline created!');
|
|
338
|
+
log('info', ` Name: ${options.name} | Mode: ${mode}`);
|
|
339
|
+
if (gitType) log('info', ` Repo: ${options.repo} (${branch})`);
|
|
340
|
+
return response;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ── Pipeline Template Generator ──
|
|
344
|
+
|
|
345
|
+
function basePorts(targetPort = '3000') {
|
|
346
|
+
return [{ protocol: 'HTTPS', targetProtocol: 'HTTP', listeningPort: '443', targetPort, public: true, targetIP: '172.17.0.1', path: '/', isAuth: false, login: '', password: '' }];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function baseExposedPorts(hostPort = '3000', containerPort = '3000') {
|
|
350
|
+
return [{ protocol: 'HTTP', hostPort, containerPort, interface: '172.17.0.1' }];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function baseCluster() {
|
|
354
|
+
return { isCluster: false, createNew: false, target: { displayName: 'REPLACE', id: 'REPLACE', serverName: 'REPLACE', vmID: 'REPLACE' } };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function generatePipelineTemplate(mode = 'docker') {
|
|
358
|
+
const templates = {
|
|
359
|
+
docker: { CICDMode: 'DOCKER', pipelineName: 'REPLACE', projectID: 'REPLACE', ports: basePorts(), variables: '', cluster: baseCluster(), imageData: { imageName: 'nginx', imageTag: 'alpine', registryUrl: '', isPipelineTemplate: false }, configData: { buildDir: '/', rootDir: '/', runtime: '', version: '', framework: '', buildCmd: '', runCmd: '', installCmd: '' }, gitData: { projectName: 'REPLACE', branch: 'main', repoUrl: '', cloneUrl: '', repoID: 0, repo: '' }, exposedPorts: baseExposedPorts('3000', '80'), isNeedToCreateRepo: true, gitVolumeConfig: [], isMovePipeline: false, nonRepoWorkSpaces: [''], gitUserFormData: {} },
|
|
360
|
+
github: { CICDMode: 'GITHUB', pipelineName: 'REPLACE', projectID: 'REPLACE', ports: basePorts(), variables: '', cluster: baseCluster(), imageData: { isPipelineTemplate: false }, configData: { buildDir: '/dist', rootDir: '/', runtime: 'staticSPA', version: '20', framework: 'Vite.js', buildCmd: 'npm run build', runCmd: '', installCmd: 'npm install' }, gitData: { projectName: 'REPLACE', branch: 'main', repoUrl: 'https://github.com/OWNER/REPO', cloneUrl: 'https://github.com/OWNER/REPO.git', repoID: 'REPLACE', repo: 'OWNER/REPO' }, exposedPorts: baseExposedPorts(), isNeedToCreateRepo: false, isPublicGitRepo: 'false', gitVolumeConfig: [], isMovePipeline: false, nonRepoWorkSpaces: [''], gitUserFormData: {} }
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
templates['github-fullstack'] = { ...templates.github, configData: { buildDir: '/', rootDir: '/', runtime: 'node', version: '20', framework: 'No Framework', buildCmd: 'npm run build', runCmd: 'npm start', installCmd: 'npm install' } };
|
|
364
|
+
templates.gitlab = { ...templates.github, CICDMode: 'GITLAB', gitData: { ...templates.github.gitData, repoUrl: 'https://gitlab.com/OWNER/REPO', cloneUrl: 'https://gitlab.com/OWNER/REPO.git' } };
|
|
365
|
+
templates['gitlab-fullstack'] = { ...templates['github-fullstack'], CICDMode: 'GITLAB', gitData: { ...templates.github.gitData, repoUrl: 'https://gitlab.com/OWNER/REPO', cloneUrl: 'https://gitlab.com/OWNER/REPO.git' } };
|
|
366
|
+
|
|
367
|
+
if (!templates[mode]) {
|
|
368
|
+
const available = Object.keys(templates).join(', ');
|
|
369
|
+
throw new Error(`Unknown mode "${mode}". Available: ${available}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return JSON.stringify(templates[mode], null, 2);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ── Docker Registries ──
|
|
376
|
+
|
|
377
|
+
export async function addDockerRegistry(projectId, identityName, username, password, url) {
|
|
378
|
+
const config = loadConfig();
|
|
379
|
+
const pid = projectId || config.defaultProject;
|
|
380
|
+
|
|
381
|
+
const response = await apiRequest('/api/cicd/addDockerRegistry', 'POST', { projectID: String(pid), identityName, username, password, url });
|
|
382
|
+
if (response.status !== 'OK') throw new Error(response.message || 'Failed');
|
|
383
|
+
|
|
384
|
+
log('success', `Docker registry "${identityName}" added`);
|
|
385
|
+
return response;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export async function getDockerRegistries(projectId = null, json = false) {
|
|
389
|
+
const config = loadConfig();
|
|
390
|
+
const pid = projectId || config.defaultProject;
|
|
391
|
+
|
|
392
|
+
const response = await apiRequest('/api/cicd/getDockerRegistry', 'GET', { projectID: String(pid) });
|
|
393
|
+
if (response.status !== 'OK') throw new Error(response.message || 'Failed');
|
|
394
|
+
|
|
395
|
+
const registries = response.data?.registries || [];
|
|
396
|
+
if (json) { outputJson(registries); return registries; }
|
|
397
|
+
if (registries.length === 0) { log('info', 'No Docker registries'); return []; }
|
|
398
|
+
|
|
399
|
+
console.log(`\n${colors.bold}Docker Registries${colors.reset}\n`);
|
|
400
|
+
registries.forEach(r => console.log(` ${r.identityName}: ${r.url}`));
|
|
401
|
+
console.log('');
|
|
402
|
+
return registries;
|
|
403
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { apiRequest } from '../api.js';
|
|
2
|
+
import { formatTable, colors, log, outputJson } from '../utils.js';
|
|
3
|
+
|
|
4
|
+
export async function listProjects(json = false) {
|
|
5
|
+
const response = await apiRequest('/api/projects/getList');
|
|
6
|
+
|
|
7
|
+
if (response.status !== 'OK') {
|
|
8
|
+
throw new Error(response.message || 'Failed to list projects');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const projects = response.data?.projects || [];
|
|
12
|
+
|
|
13
|
+
if (json) {
|
|
14
|
+
outputJson(projects);
|
|
15
|
+
return projects;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (projects.length === 0) {
|
|
19
|
+
log('info', 'No projects found. Create one with: elestio projects create <name>');
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const columns = [
|
|
24
|
+
{ key: 'projectID', label: 'ID' },
|
|
25
|
+
{ key: 'project_name', label: 'Name' },
|
|
26
|
+
{ key: 'role', label: 'Role' },
|
|
27
|
+
{ key: 'networkCIDR', label: 'Network CIDR' }
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
console.log(`\n${colors.bold}Projects (${projects.length})${colors.reset}\n`);
|
|
31
|
+
console.log(formatTable(projects, columns));
|
|
32
|
+
console.log('');
|
|
33
|
+
return projects;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function createProject(name, description = '', technicalEmails = '') {
|
|
37
|
+
if (!name) throw new Error('Project name is required');
|
|
38
|
+
|
|
39
|
+
const response = await apiRequest('/api/projects/addProject', 'POST', {
|
|
40
|
+
name, description, technicalEmails
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (response.status !== 'OK') {
|
|
44
|
+
throw new Error(response.message || 'Failed to create project');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
log('success', `Project "${name}" created`);
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function editProject(projectId, name, description, technicalEmails) {
|
|
52
|
+
const body = { projectId: String(projectId) };
|
|
53
|
+
if (name) body.name = name;
|
|
54
|
+
if (description !== undefined) body.description = description;
|
|
55
|
+
if (technicalEmails !== undefined) body.technicalEmails = technicalEmails;
|
|
56
|
+
|
|
57
|
+
const response = await apiRequest('/api/projects/editProject', 'PUT', body);
|
|
58
|
+
|
|
59
|
+
if (response.status !== 'OK') {
|
|
60
|
+
throw new Error(response.message || 'Failed to edit project');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
log('success', `Project ${projectId} updated`);
|
|
64
|
+
return response;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function deleteProject(projectId, force = false) {
|
|
68
|
+
if (!force) throw new Error('Deleting a project requires --force flag');
|
|
69
|
+
|
|
70
|
+
const response = await apiRequest('/api/projects/deleteProject', 'DELETE', {
|
|
71
|
+
projectId: String(projectId)
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (response.status !== 'OK') {
|
|
75
|
+
throw new Error(response.message || 'Failed to delete project');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
log('success', `Project ${projectId} deleted`);
|
|
79
|
+
return response;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function listMembers(projectId, json = false) {
|
|
83
|
+
const response = await apiRequest('/api/projects/getMembersList', 'GET', {
|
|
84
|
+
projectId: String(projectId)
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
let members = [];
|
|
88
|
+
if (Array.isArray(response)) {
|
|
89
|
+
members = response;
|
|
90
|
+
} else if (response.status === 'OK') {
|
|
91
|
+
members = response.data?.projectMembers || response.data?.members || [];
|
|
92
|
+
} else if (response.status === 'KO' || response.message) {
|
|
93
|
+
throw new Error(response.message || 'Failed to list members');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (json) {
|
|
97
|
+
outputJson(members);
|
|
98
|
+
return members;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (members.length === 0) {
|
|
102
|
+
log('info', 'No members found');
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const columns = [
|
|
107
|
+
{ key: 'userID', label: 'User ID' },
|
|
108
|
+
{ key: 'email', label: 'Email' },
|
|
109
|
+
{ key: 'role', label: 'Role' }
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
console.log(`\n${colors.bold}Project Members${colors.reset}\n`);
|
|
113
|
+
console.log(formatTable(members, columns));
|
|
114
|
+
console.log('');
|
|
115
|
+
return members;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function addMember(projectId, email, role = 'admin') {
|
|
119
|
+
const response = await apiRequest('/api/projects/addMember', 'POST', {
|
|
120
|
+
projectId: String(projectId),
|
|
121
|
+
targetEmail: email,
|
|
122
|
+
role
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (response.status !== 'OK') {
|
|
126
|
+
throw new Error(response.message || 'Failed to add member');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
log('success', `Added ${email} as ${role} to project ${projectId}`);
|
|
130
|
+
return response;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function removeMember(projectId, memberId) {
|
|
134
|
+
const response = await apiRequest('/api/projects/deleteMember', 'DELETE', {
|
|
135
|
+
projectId: String(projectId),
|
|
136
|
+
targetId: String(memberId)
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (response.status !== 'OK' && response.status !== undefined) {
|
|
140
|
+
throw new Error(response.message || 'Failed to remove member');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
log('success', `Removed member ${memberId} from project ${projectId}`);
|
|
144
|
+
return response;
|
|
145
|
+
}
|