elestio 1.0.2 → 1.0.3

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.
@@ -1,294 +1,294 @@
1
- import { apiRequest } from '../api.js';
2
- import { loadConfig } from '../config.js';
3
- import { findTemplate } from './templates.js';
4
- import { formatTable, formatService, formatPrice, colors, log, sleep, validateServerName, outputJson } from '../utils.js';
5
-
6
- async function listProjectsRaw() {
7
- const response = await apiRequest('/api/projects/getList');
8
- if (response.status !== 'OK') return [];
9
- return response.data?.projects || [];
10
- }
11
-
12
- export async function findServiceAcrossProjects(vmID) {
13
- const projects = await listProjectsRaw();
14
- for (const project of projects) {
15
- const pid = project.projectID;
16
- try {
17
- const services = await listServicesRaw(pid);
18
- const svc = services.find(s => String(s.vmID) === String(vmID));
19
- if (svc) return { service: svc, projectId: pid, projectName: project.project_name };
20
- } catch { /* skip */ }
21
- }
22
- return null;
23
- }
24
-
25
- export async function listServicesRaw(projectId = null) {
26
- const config = loadConfig();
27
- const pid = projectId || config.defaultProject;
28
- if (!pid) throw new Error('Project ID required. Use --project or set default with: elestio config --set-default-project <id>');
29
-
30
- const response = await apiRequest('/api/servers/getServices', 'POST', {
31
- appid: 'Cloudxx',
32
- projectId: String(pid),
33
- isActiveService: 'true'
34
- });
35
-
36
- return response.servers || response.data?.services || [];
37
- }
38
-
39
- export async function listServices(projectId = null, json = false) {
40
- const config = loadConfig();
41
- const pid = projectId || config.defaultProject;
42
- if (!pid) throw new Error('Project ID required. Use --project or set default with: elestio config --set-default-project <id>');
43
-
44
- const response = await apiRequest('/api/servers/getServices', 'POST', {
45
- appid: 'Cloudxx',
46
- projectId: String(pid),
47
- isActiveService: 'true'
48
- });
49
-
50
- if (response.status === 'KO' || response.code === 'AccessDenied') {
51
- throw new Error(response.message || 'Access denied.');
52
- }
53
-
54
- const services = response.servers || response.data?.services || [];
55
-
56
- if (json) {
57
- outputJson(services);
58
- return services;
59
- }
60
-
61
- if (services.length === 0) {
62
- log('info', `No services in project ${pid}`);
63
- return [];
64
- }
65
-
66
- const columns = [
67
- { key: 'displayName', label: 'Name' },
68
- { key: 'templateName', label: 'Software' },
69
- { key: 'status', label: 'Status' },
70
- { key: 'deploymentStatus', label: 'Deploy' },
71
- { key: 'vmID', label: 'vmID' },
72
- { key: 'ipv4', label: 'IP' }
73
- ];
74
-
75
- console.log(`\n${colors.bold}Services in project ${pid} (${services.length})${colors.reset}\n`);
76
- console.log(formatTable(services, columns));
77
- console.log('');
78
- return services;
79
- }
80
-
81
- export async function getServiceDetails(vmID, projectId = null) {
82
- const config = loadConfig();
83
- const pid = projectId || config.defaultProject;
84
- if (!pid) throw new Error('Project ID required');
85
-
86
- const response = await apiRequest('/api/servers/getServerDetails', 'POST', {
87
- vmID: String(vmID),
88
- projectID: String(pid)
89
- });
90
-
91
- if (response.serviceInfos && response.serviceInfos.length > 0) return response.serviceInfos[0];
92
- if (response.status === 'OK' && response.data) return response.data;
93
- if (response.status === 'KO' || response.message) throw new Error(response.message || 'Failed to get service details');
94
- return null;
95
- }
96
-
97
- export async function getServiceByVmId(vmID, projectId = null) {
98
- const services = await listServicesRaw(projectId);
99
- return services.find(s => String(s.vmID) === String(vmID));
100
- }
101
-
102
- export async function getServerIdFromVmId(vmID, projectId = null) {
103
- const service = await getServiceByVmId(vmID, projectId);
104
- if (!service) throw new Error(`Service with vmID ${vmID} not found`);
105
- return service.id;
106
- }
107
-
108
- export async function deployService(templateNameOrId, options = {}) {
109
- const config = loadConfig();
110
- const template = await findTemplate(templateNameOrId);
111
- if (!template) throw new Error(`Template "${templateNameOrId}" not found. Use: elestio templates search <name>`);
112
-
113
- const projectId = options.project || config.defaultProject;
114
- if (!projectId) throw new Error('Project ID required. Use --project or set default');
115
-
116
- const serverName = options.name || `${template.title.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${Date.now().toString(36)}`;
117
- const serverType = options.size || config.defaults?.serverType || 'MEDIUM-2C-4G';
118
- const datacenter = options.region || config.defaults?.datacenter || 'nbg';
119
- const support = options.support || config.defaults?.support || 'level1';
120
- const adminEmail = options.email || config.email;
121
- const provider = options.provider || config.defaults?.provider || 'netcup';
122
- const version = options.version || template.dockerhub_default_tag || 'latest';
123
-
124
- const nameValidation = validateServerName(serverName);
125
- if (!nameValidation.valid) throw new Error(nameValidation.error);
126
-
127
- const isCicd = template.title?.toLowerCase().includes('ci-cd') || templateNameOrId?.toLowerCase() === 'cicd';
128
- const serviceType = isCicd ? 'CICD' : 'Service';
129
-
130
- if (options.dryRun) {
131
- const preview = {
132
- template: template.title, templateId: template.id, version,
133
- projectId, serverName, provider, datacenter, serverType, support, adminEmail, serviceType
134
- };
135
-
136
- if (options.json) {
137
- outputJson({ dryRun: true, ...preview });
138
- return preview;
139
- }
140
-
141
- console.log(`\n${colors.bold}Deployment Preview (--dry-run)${colors.reset}\n`);
142
- console.log(` Software: ${colors.cyan}${template.title}${colors.reset} (ID: ${template.id})`);
143
- console.log(` Version: ${version}`);
144
- console.log(` Project: ${projectId}`);
145
- console.log(` Name: ${serverName}`);
146
- console.log(` Provider: ${provider}`);
147
- console.log(` Region: ${datacenter}`);
148
- console.log(` Size: ${serverType}`);
149
- console.log(` Support: ${support}`);
150
- console.log(` Admin: ${adminEmail}`);
151
- console.log('');
152
- log('info', 'To deploy, run the same command without --dry-run');
153
- return preview;
154
- }
155
-
156
- log('info', `Deploying ${template.title} (ID: ${template.id})`);
157
- log('info', ` Project: ${projectId} | Name: ${serverName}`);
158
- log('info', ` Provider: ${provider} | Size: ${serverType} @ ${datacenter}`);
159
-
160
- const payload = {
161
- templateID: String(template.id), serverType, datacenter,
162
- providerName: provider, serverName, appid: 'Cloudxx',
163
- data: 'data', support, projectId: String(projectId),
164
- version, adminEmail, deploymentServiceType: 'normal', serviceType
165
- };
166
-
167
- if (isCicd) {
168
- payload.cicdPayload = { pipelineName: options.pipelineName || serverName };
169
- }
170
-
171
- const response = await apiRequest('/api/servers/createServer', 'POST', payload);
172
-
173
- if (!response.providerServerID && !response.action) {
174
- throw new Error(response.message || 'Failed to create service');
175
- }
176
-
177
- log('success', `Deployment started! Provider Server ID: ${response.providerServerID}`);
178
-
179
- if (options.wait !== false) {
180
- log('info', 'Waiting for deployment to complete...');
181
- return await waitForDeployment(response.providerServerID, projectId, options.timeout);
182
- }
183
-
184
- return response;
185
- }
186
-
187
- export async function waitForDeployment(vmID, projectId, timeoutMs = 600000) {
188
- const start = Date.now();
189
- let lastStatus = '';
190
-
191
- while (Date.now() - start < timeoutMs) {
192
- const services = await listServicesRaw(projectId);
193
- const svc = services.find(s =>
194
- String(s.vmID) === String(vmID) || String(s.providerServerID) === String(vmID)
195
- );
196
-
197
- if (!svc) { await sleep(10000); continue; }
198
-
199
- if (svc.deploymentStatus !== lastStatus) {
200
- lastStatus = svc.deploymentStatus;
201
- log('info', `Status: ${svc.deploymentStatus}`);
202
- }
203
-
204
- if (svc.deploymentStatus === 'Deployed' && svc.status === 'running') {
205
- log('success', 'Deployment complete!');
206
- console.log('\n' + formatService(svc) + '\n');
207
- return svc;
208
- }
209
-
210
- await sleep(15000);
211
- }
212
-
213
- throw new Error(`Deployment timed out after ${timeoutMs / 1000}s`);
214
- }
215
-
216
- export async function deleteService(vmID, options = {}) {
217
- if (!options.force) throw new Error('Deleting a service requires --force flag');
218
-
219
- const config = loadConfig();
220
- const projectId = options.project || config.defaultProject;
221
- if (!projectId) throw new Error('Project ID required');
222
-
223
- const response = await apiRequest('/api/servers/deleteServer', 'POST', {
224
- vmID: String(vmID),
225
- projectID: String(projectId),
226
- isDeleteServiceWithBackup: options.withBackups ? 'true' : 'false'
227
- });
228
-
229
- if (response.status !== 'OK' && !response.action) {
230
- throw new Error(response.message || 'Failed to delete service');
231
- }
232
-
233
- log('success', `Service ${vmID} deletion initiated`);
234
- return response;
235
- }
236
-
237
- export async function moveService(vmIDOrServerID, targetProjectId, sourceProjectId = null) {
238
- const config = loadConfig();
239
- const sourcePid = sourceProjectId || config.defaultProject;
240
-
241
- let serverID = vmIDOrServerID;
242
- let foundInProject = null;
243
-
244
- try {
245
- const service = await getServiceByVmId(vmIDOrServerID, sourcePid);
246
- if (service?.id) { serverID = service.id; foundInProject = sourcePid; }
247
- } catch { /* not found */ }
248
-
249
- if (!foundInProject) {
250
- log('info', `Searching across all projects...`);
251
- const found = await findServiceAcrossProjects(vmIDOrServerID);
252
- if (found) {
253
- serverID = found.service.id;
254
- log('info', `Found in project "${found.projectName}" (${found.projectId})`);
255
- } else {
256
- throw new Error(`Service ${vmIDOrServerID} not found in any project`);
257
- }
258
- }
259
-
260
- const response = await apiRequest('/api/servers/moveService', 'PUT', {
261
- serviceId: String(serverID),
262
- projectId: String(targetProjectId)
263
- });
264
-
265
- if (response.status !== 'OK') throw new Error(response.message || 'Failed to move service');
266
-
267
- log('success', `Service moved to project ${targetProjectId}`);
268
- return response;
269
- }
270
-
271
- export async function showService(vmID, projectId = null, json = false) {
272
- const config = loadConfig();
273
- const pid = projectId || config.defaultProject;
274
-
275
- const details = await getServiceDetails(vmID, pid);
276
- if (!details) throw new Error(`Service with vmID ${vmID} not found`);
277
-
278
- if (json) {
279
- outputJson(details);
280
- return details;
281
- }
282
-
283
- console.log('\n' + formatService(details));
284
- console.log(`\n${colors.bold}Extended Details${colors.reset}`);
285
- console.log(` Server ID: ${details.id || 'N/A'}`);
286
- console.log(` Firewall: ${details.isFirewallActivated ? 'enabled' : 'disabled'}`);
287
- console.log(` Alerts: ${details.isAlertsActivated ? 'enabled' : 'disabled'}`);
288
- console.log(` Remote Backup: ${details.remoteBackupsActivated ? 'enabled' : 'disabled'}`);
289
- console.log(` System Auto-Update: ${details.system_AutoUpdate_Enabled ? 'enabled' : 'disabled'}`);
290
- console.log(` App Auto-Update: ${details.app_AutoUpdate_Enabled ? 'enabled' : 'disabled'}`);
291
- console.log(` Price/Hour: $${details.pricePerHour || 'N/A'}`);
292
- console.log('');
293
- return details;
294
- }
1
+ import { apiRequest } from '../api.js';
2
+ import { loadConfig } from '../config.js';
3
+ import { findTemplate } from './templates.js';
4
+ import { formatTable, formatService, formatPrice, colors, log, sleep, validateServerName, outputJson } from '../utils.js';
5
+
6
+ async function listProjectsRaw() {
7
+ const response = await apiRequest('/api/projects/getList');
8
+ if (response.status !== 'OK') return [];
9
+ return response.data?.projects || [];
10
+ }
11
+
12
+ export async function findServiceAcrossProjects(vmID) {
13
+ const projects = await listProjectsRaw();
14
+ for (const project of projects) {
15
+ const pid = project.projectID;
16
+ try {
17
+ const services = await listServicesRaw(pid);
18
+ const svc = services.find(s => String(s.vmID) === String(vmID));
19
+ if (svc) return { service: svc, projectId: pid, projectName: project.project_name };
20
+ } catch { /* skip */ }
21
+ }
22
+ return null;
23
+ }
24
+
25
+ export async function listServicesRaw(projectId = null) {
26
+ const config = loadConfig();
27
+ const pid = projectId || config.defaultProject;
28
+ if (!pid) throw new Error('Project ID required. Use --project or set default with: elestio config --set-default-project <id>');
29
+
30
+ const response = await apiRequest('/api/servers/getServices', 'POST', {
31
+ appid: 'Cloudxx',
32
+ projectId: String(pid),
33
+ isActiveService: 'true'
34
+ });
35
+
36
+ return response.servers || response.data?.services || [];
37
+ }
38
+
39
+ export async function listServices(projectId = null, json = false) {
40
+ const config = loadConfig();
41
+ const pid = projectId || config.defaultProject;
42
+ if (!pid) throw new Error('Project ID required. Use --project or set default with: elestio config --set-default-project <id>');
43
+
44
+ const response = await apiRequest('/api/servers/getServices', 'POST', {
45
+ appid: 'Cloudxx',
46
+ projectId: String(pid),
47
+ isActiveService: 'true'
48
+ });
49
+
50
+ if (response.status === 'KO' || response.code === 'AccessDenied') {
51
+ throw new Error(response.message || 'Access denied.');
52
+ }
53
+
54
+ const services = response.servers || response.data?.services || [];
55
+
56
+ if (json) {
57
+ outputJson(services);
58
+ return services;
59
+ }
60
+
61
+ if (services.length === 0) {
62
+ log('info', `No services in project ${pid}`);
63
+ return [];
64
+ }
65
+
66
+ const columns = [
67
+ { key: 'displayName', label: 'Name' },
68
+ { key: 'templateName', label: 'Software' },
69
+ { key: 'status', label: 'Status' },
70
+ { key: 'deploymentStatus', label: 'Deploy' },
71
+ { key: 'vmID', label: 'vmID' },
72
+ { key: 'ipv4', label: 'IP' }
73
+ ];
74
+
75
+ console.log(`\n${colors.bold}Services in project ${pid} (${services.length})${colors.reset}\n`);
76
+ console.log(formatTable(services, columns));
77
+ console.log('');
78
+ return services;
79
+ }
80
+
81
+ export async function getServiceDetails(vmID, projectId = null) {
82
+ const config = loadConfig();
83
+ const pid = projectId || config.defaultProject;
84
+ if (!pid) throw new Error('Project ID required');
85
+
86
+ const response = await apiRequest('/api/servers/getServerDetails', 'POST', {
87
+ vmID: String(vmID),
88
+ projectID: String(pid)
89
+ });
90
+
91
+ if (response.serviceInfos && response.serviceInfos.length > 0) return response.serviceInfos[0];
92
+ if (response.status === 'OK' && response.data) return response.data;
93
+ if (response.status === 'KO' || response.message) throw new Error(response.message || 'Failed to get service details');
94
+ return null;
95
+ }
96
+
97
+ export async function getServiceByVmId(vmID, projectId = null) {
98
+ const services = await listServicesRaw(projectId);
99
+ return services.find(s => String(s.vmID) === String(vmID));
100
+ }
101
+
102
+ export async function getServerIdFromVmId(vmID, projectId = null) {
103
+ const service = await getServiceByVmId(vmID, projectId);
104
+ if (!service) throw new Error(`Service with vmID ${vmID} not found`);
105
+ return service.id;
106
+ }
107
+
108
+ export async function deployService(templateNameOrId, options = {}) {
109
+ const config = loadConfig();
110
+ const template = await findTemplate(templateNameOrId);
111
+ if (!template) throw new Error(`Template "${templateNameOrId}" not found. Use: elestio templates search <name>`);
112
+
113
+ const projectId = options.project || config.defaultProject;
114
+ if (!projectId) throw new Error('Project ID required. Use --project or set default');
115
+
116
+ const serverName = options.name || `${template.title.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${Date.now().toString(36)}`;
117
+ const serverType = options.size || config.defaults?.serverType || 'MEDIUM-2C-4G';
118
+ const datacenter = options.region || config.defaults?.datacenter || 'nbg';
119
+ const support = options.support || config.defaults?.support || 'level1';
120
+ const adminEmail = options.email || config.email;
121
+ const provider = options.provider || config.defaults?.provider || 'netcup';
122
+ const version = options.version || template.dockerhub_default_tag || 'latest';
123
+
124
+ const nameValidation = validateServerName(serverName);
125
+ if (!nameValidation.valid) throw new Error(nameValidation.error);
126
+
127
+ const isCicd = template.title?.toLowerCase().includes('ci-cd') || templateNameOrId?.toLowerCase() === 'cicd';
128
+ const serviceType = isCicd ? 'CICD' : 'Service';
129
+
130
+ if (options.dryRun) {
131
+ const preview = {
132
+ template: template.title, templateId: template.id, version,
133
+ projectId, serverName, provider, datacenter, serverType, support, adminEmail, serviceType
134
+ };
135
+
136
+ if (options.json) {
137
+ outputJson({ dryRun: true, ...preview });
138
+ return preview;
139
+ }
140
+
141
+ console.log(`\n${colors.bold}Deployment Preview (--dry-run)${colors.reset}\n`);
142
+ console.log(` Software: ${colors.cyan}${template.title}${colors.reset} (ID: ${template.id})`);
143
+ console.log(` Version: ${version}`);
144
+ console.log(` Project: ${projectId}`);
145
+ console.log(` Name: ${serverName}`);
146
+ console.log(` Provider: ${provider}`);
147
+ console.log(` Region: ${datacenter}`);
148
+ console.log(` Size: ${serverType}`);
149
+ console.log(` Support: ${support}`);
150
+ console.log(` Admin: ${adminEmail}`);
151
+ console.log('');
152
+ log('info', 'To deploy, run the same command without --dry-run');
153
+ return preview;
154
+ }
155
+
156
+ log('info', `Deploying ${template.title} (ID: ${template.id})`);
157
+ log('info', ` Project: ${projectId} | Name: ${serverName}`);
158
+ log('info', ` Provider: ${provider} | Size: ${serverType} @ ${datacenter}`);
159
+
160
+ const payload = {
161
+ templateID: String(template.id), serverType, datacenter,
162
+ providerName: provider, serverName, appid: 'Cloudxx',
163
+ data: 'data', support, projectId: String(projectId),
164
+ version, adminEmail, deploymentServiceType: 'normal', serviceType
165
+ };
166
+
167
+ if (isCicd) {
168
+ payload.cicdPayload = { pipelineName: options.pipelineName || serverName };
169
+ }
170
+
171
+ const response = await apiRequest('/api/servers/createServer', 'POST', payload);
172
+
173
+ if (!response.providerServerID && !response.action) {
174
+ throw new Error(response.message || 'Failed to create service');
175
+ }
176
+
177
+ log('success', `Deployment started! Provider Server ID: ${response.providerServerID}`);
178
+
179
+ if (options.wait !== false) {
180
+ log('info', 'Waiting for deployment to complete...');
181
+ return await waitForDeployment(response.providerServerID, projectId, options.timeout);
182
+ }
183
+
184
+ return response;
185
+ }
186
+
187
+ export async function waitForDeployment(vmID, projectId, timeoutMs = 600000) {
188
+ const start = Date.now();
189
+ let lastStatus = '';
190
+
191
+ while (Date.now() - start < timeoutMs) {
192
+ const services = await listServicesRaw(projectId);
193
+ const svc = services.find(s =>
194
+ String(s.vmID) === String(vmID) || String(s.providerServerID) === String(vmID)
195
+ );
196
+
197
+ if (!svc) { await sleep(10000); continue; }
198
+
199
+ if (svc.deploymentStatus !== lastStatus) {
200
+ lastStatus = svc.deploymentStatus;
201
+ log('info', `Status: ${svc.deploymentStatus}`);
202
+ }
203
+
204
+ if (svc.deploymentStatus === 'Deployed' && svc.status === 'running') {
205
+ log('success', 'Deployment complete!');
206
+ console.log('\n' + formatService(svc) + '\n');
207
+ return svc;
208
+ }
209
+
210
+ await sleep(15000);
211
+ }
212
+
213
+ throw new Error(`Deployment timed out after ${timeoutMs / 1000}s`);
214
+ }
215
+
216
+ export async function deleteService(vmID, options = {}) {
217
+ if (!options.force) throw new Error('Deleting a service requires --force flag');
218
+
219
+ const config = loadConfig();
220
+ const projectId = options.project || config.defaultProject;
221
+ if (!projectId) throw new Error('Project ID required');
222
+
223
+ const response = await apiRequest('/api/servers/deleteServer', 'POST', {
224
+ vmID: String(vmID),
225
+ projectID: String(projectId),
226
+ isDeleteServiceWithBackup: options.withBackups ? 'true' : 'false'
227
+ });
228
+
229
+ if (response.status !== 'OK' && !response.action) {
230
+ throw new Error(response.message || 'Failed to delete service');
231
+ }
232
+
233
+ log('success', `Service ${vmID} deletion initiated`);
234
+ return response;
235
+ }
236
+
237
+ export async function moveService(vmIDOrServerID, targetProjectId, sourceProjectId = null) {
238
+ const config = loadConfig();
239
+ const sourcePid = sourceProjectId || config.defaultProject;
240
+
241
+ let serverID = vmIDOrServerID;
242
+ let foundInProject = null;
243
+
244
+ try {
245
+ const service = await getServiceByVmId(vmIDOrServerID, sourcePid);
246
+ if (service?.id) { serverID = service.id; foundInProject = sourcePid; }
247
+ } catch { /* not found */ }
248
+
249
+ if (!foundInProject) {
250
+ log('info', `Searching across all projects...`);
251
+ const found = await findServiceAcrossProjects(vmIDOrServerID);
252
+ if (found) {
253
+ serverID = found.service.id;
254
+ log('info', `Found in project "${found.projectName}" (${found.projectId})`);
255
+ } else {
256
+ throw new Error(`Service ${vmIDOrServerID} not found in any project`);
257
+ }
258
+ }
259
+
260
+ const response = await apiRequest('/api/servers/moveService', 'PUT', {
261
+ serviceId: String(serverID),
262
+ projectId: String(targetProjectId)
263
+ });
264
+
265
+ if (response.status !== 'OK') throw new Error(response.message || 'Failed to move service');
266
+
267
+ log('success', `Service moved to project ${targetProjectId}`);
268
+ return response;
269
+ }
270
+
271
+ export async function showService(vmID, projectId = null, json = false) {
272
+ const config = loadConfig();
273
+ const pid = projectId || config.defaultProject;
274
+
275
+ const details = await getServiceDetails(vmID, pid);
276
+ if (!details) throw new Error(`Service with vmID ${vmID} not found`);
277
+
278
+ if (json) {
279
+ outputJson(details);
280
+ return details;
281
+ }
282
+
283
+ console.log('\n' + formatService(details));
284
+ console.log(`\n${colors.bold}Extended Details${colors.reset}`);
285
+ console.log(` Server ID: ${details.id || 'N/A'}`);
286
+ console.log(` Firewall: ${details.isFirewallActivated ? 'enabled' : 'disabled'}`);
287
+ console.log(` Alerts: ${details.isAlertsActivated ? 'enabled' : 'disabled'}`);
288
+ console.log(` Remote Backup: ${details.remoteBackupsActivated ? 'enabled' : 'disabled'}`);
289
+ console.log(` System Auto-Update: ${details.system_AutoUpdate_Enabled ? 'enabled' : 'disabled'}`);
290
+ console.log(` App Auto-Update: ${details.app_AutoUpdate_Enabled ? 'enabled' : 'disabled'}`);
291
+ console.log(` Price/Hour: $${details.pricePerHour || 'N/A'}`);
292
+ console.log('');
293
+ return details;
294
+ }