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.
package/src/config.js CHANGED
@@ -1,84 +1,84 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import os from 'os';
4
-
5
- const ELESTIO_DIR = path.join(os.homedir(), '.elestio');
6
- const CREDENTIALS_PATH = path.join(ELESTIO_DIR, 'credentials');
7
- const CONFIG_PATH = path.join(ELESTIO_DIR, 'config.json');
8
-
9
- const DEFAULT_CONFIG = {
10
- jwt: null,
11
- jwtExpiry: null,
12
- defaultProject: null,
13
- defaults: {
14
- provider: 'netcup',
15
- datacenter: 'nbg',
16
- serverType: 'MEDIUM-2C-4G',
17
- support: 'level1'
18
- }
19
- };
20
-
21
- function ensureDir() {
22
- if (!fs.existsSync(ELESTIO_DIR)) {
23
- fs.mkdirSync(ELESTIO_DIR, { mode: 0o700, recursive: true });
24
- }
25
- }
26
-
27
- // ── Credentials (email + apiToken) ──
28
-
29
- export function getCredentials() {
30
- try {
31
- if (fs.existsSync(CREDENTIALS_PATH)) {
32
- const data = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, 'utf-8'));
33
- return {
34
- email: data.email || null,
35
- apiToken: data.apiToken || null
36
- };
37
- }
38
- } catch {
39
- // ignore
40
- }
41
- return { email: null, apiToken: null };
42
- }
43
-
44
- export function saveCredentials(email, apiToken) {
45
- ensureDir();
46
- fs.writeFileSync(
47
- CREDENTIALS_PATH,
48
- JSON.stringify({ email, apiToken }, null, 2),
49
- { mode: 0o600 }
50
- );
51
- }
52
-
53
- // ── Config (jwt, defaults, defaultProject) ──
54
-
55
- export function loadConfig() {
56
- let config = { ...DEFAULT_CONFIG };
57
-
58
- try {
59
- if (fs.existsSync(CONFIG_PATH)) {
60
- const data = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
61
- config = { ...DEFAULT_CONFIG, ...data };
62
- }
63
- } catch {
64
- // ignore
65
- }
66
-
67
- // Merge credentials into config for convenience
68
- const creds = getCredentials();
69
- config.email = creds.email;
70
- config.apiToken = creds.apiToken;
71
-
72
- return config;
73
- }
74
-
75
- export function saveConfig(config) {
76
- ensureDir();
77
- // Never persist credentials in config.json
78
- const { email, apiToken, ...safeConfig } = config;
79
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(safeConfig, null, 2));
80
- }
81
-
82
- // ── Exports ──
83
-
84
- export { ELESTIO_DIR, CREDENTIALS_PATH, CONFIG_PATH };
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ const ELESTIO_DIR = path.join(os.homedir(), '.elestio');
6
+ const CREDENTIALS_PATH = path.join(ELESTIO_DIR, 'credentials');
7
+ const CONFIG_PATH = path.join(ELESTIO_DIR, 'config.json');
8
+
9
+ const DEFAULT_CONFIG = {
10
+ jwt: null,
11
+ jwtExpiry: null,
12
+ defaultProject: null,
13
+ defaults: {
14
+ provider: 'netcup',
15
+ datacenter: 'nbg',
16
+ serverType: 'MEDIUM-2C-4G',
17
+ support: 'level1'
18
+ }
19
+ };
20
+
21
+ function ensureDir() {
22
+ if (!fs.existsSync(ELESTIO_DIR)) {
23
+ fs.mkdirSync(ELESTIO_DIR, { mode: 0o700, recursive: true });
24
+ }
25
+ }
26
+
27
+ // ── Credentials (email + apiToken) ──
28
+
29
+ export function getCredentials() {
30
+ try {
31
+ if (fs.existsSync(CREDENTIALS_PATH)) {
32
+ const data = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, 'utf-8'));
33
+ return {
34
+ email: data.email || null,
35
+ apiToken: data.apiToken || null
36
+ };
37
+ }
38
+ } catch {
39
+ // ignore
40
+ }
41
+ return { email: null, apiToken: null };
42
+ }
43
+
44
+ export function saveCredentials(email, apiToken) {
45
+ ensureDir();
46
+ fs.writeFileSync(
47
+ CREDENTIALS_PATH,
48
+ JSON.stringify({ email, apiToken }, null, 2),
49
+ { mode: 0o600 }
50
+ );
51
+ }
52
+
53
+ // ── Config (jwt, defaults, defaultProject) ──
54
+
55
+ export function loadConfig() {
56
+ let config = { ...DEFAULT_CONFIG };
57
+
58
+ try {
59
+ if (fs.existsSync(CONFIG_PATH)) {
60
+ const data = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
61
+ config = { ...DEFAULT_CONFIG, ...data };
62
+ }
63
+ } catch {
64
+ // ignore
65
+ }
66
+
67
+ // Merge credentials into config for convenience
68
+ const creds = getCredentials();
69
+ config.email = creds.email;
70
+ config.apiToken = creds.apiToken;
71
+
72
+ return config;
73
+ }
74
+
75
+ export function saveConfig(config) {
76
+ ensureDir();
77
+ // Never persist credentials in config.json
78
+ const { email, apiToken, ...safeConfig } = config;
79
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(safeConfig, null, 2));
80
+ }
81
+
82
+ // ── Exports ──
83
+
84
+ export { ELESTIO_DIR, CREDENTIALS_PATH, CONFIG_PATH };
package/src/utils.js CHANGED
@@ -1,162 +1,162 @@
1
- // ── ANSI Colors ──
2
-
3
- export const colors = {
4
- reset: '\x1b[0m',
5
- bold: '\x1b[1m',
6
- dim: '\x1b[2m',
7
- red: '\x1b[31m',
8
- green: '\x1b[32m',
9
- yellow: '\x1b[33m',
10
- blue: '\x1b[34m',
11
- magenta: '\x1b[35m',
12
- cyan: '\x1b[36m',
13
- white: '\x1b[37m',
14
- gray: '\x1b[90m'
15
- };
16
-
17
- // ── Logging ──
18
-
19
- export function log(type, message) {
20
- const prefix = {
21
- info: `${colors.blue}[INFO]${colors.reset}`,
22
- success: `${colors.green}[SUCCESS]${colors.reset}`,
23
- error: `${colors.red}[ERROR]${colors.reset}`,
24
- warn: `${colors.yellow}[WARN]${colors.reset}`,
25
- debug: `${colors.gray}[DEBUG]${colors.reset}`
26
- };
27
- console.log(`${prefix[type] || ''} ${message}`);
28
- }
29
-
30
- // ── Argument parsing ──
31
-
32
- export function parseArgs(argv) {
33
- const args = { _: [] };
34
- let i = 0;
35
-
36
- while (i < argv.length) {
37
- const arg = argv[i];
38
-
39
- if (arg.startsWith('--')) {
40
- const key = arg.slice(2);
41
- const next = argv[i + 1];
42
-
43
- if (next && !next.startsWith('-')) {
44
- args[key] = next;
45
- i += 2;
46
- } else {
47
- args[key] = true;
48
- i++;
49
- }
50
- } else if (arg.startsWith('-') && arg.length === 2) {
51
- const key = arg.slice(1);
52
- const next = argv[i + 1];
53
-
54
- if (next && !next.startsWith('-')) {
55
- args[key] = next;
56
- i += 2;
57
- } else {
58
- args[key] = true;
59
- i++;
60
- }
61
- } else {
62
- args._.push(arg);
63
- i++;
64
- }
65
- }
66
-
67
- return args;
68
- }
69
-
70
- // ── Table formatting ──
71
-
72
- export function formatTable(data, columns) {
73
- if (!data || data.length === 0) return 'No data';
74
-
75
- const widths = {};
76
- columns.forEach(col => { widths[col.key] = col.label.length; });
77
-
78
- data.forEach(row => {
79
- columns.forEach(col => {
80
- const val = String(row[col.key] ?? '');
81
- widths[col.key] = Math.max(widths[col.key], val.length);
82
- });
83
- });
84
-
85
- const header = columns.map(col => col.label.padEnd(widths[col.key])).join(' ');
86
- const separator = columns.map(col => '-'.repeat(widths[col.key])).join(' ');
87
- const rows = data.map(row =>
88
- columns.map(col => String(row[col.key] ?? '').padEnd(widths[col.key])).join(' ')
89
- );
90
-
91
- return [header, separator, ...rows].join('\n');
92
- }
93
-
94
- // ── Helpers ──
95
-
96
- export function formatBytes(bytes) {
97
- if (bytes === 0) return '0 B';
98
- const k = 1024;
99
- const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
100
- const i = Math.floor(Math.log(bytes) / Math.log(k));
101
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
102
- }
103
-
104
- export function formatPrice(pricePerHour) {
105
- const hourly = parseFloat(pricePerHour);
106
- const monthly = (hourly * 24 * 30).toFixed(2);
107
- return `$${monthly}/mo`;
108
- }
109
-
110
- export function validateServerName(name) {
111
- if (!name) return { valid: false, error: 'Server name is required' };
112
- if (name.length < 3) return { valid: false, error: 'Server name must be at least 3 characters' };
113
- if (name.length > 50) return { valid: false, error: 'Server name must be at most 50 characters' };
114
- if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(name) && name.length > 1) {
115
- return { valid: false, error: 'Server name must be lowercase alphanumeric with hyphens, cannot start/end with hyphen' };
116
- }
117
- return { valid: true };
118
- }
119
-
120
- export function sleep(ms) {
121
- return new Promise(resolve => setTimeout(resolve, ms));
122
- }
123
-
124
- export function formatService(svc) {
125
- const status = svc.status === 'running'
126
- ? `${colors.green}running${colors.reset}`
127
- : `${colors.yellow}${svc.status}${colors.reset}`;
128
-
129
- const deployment = svc.deploymentStatus === 'Deployed'
130
- ? `${colors.green}Deployed${colors.reset}`
131
- : `${colors.yellow}${svc.deploymentStatus}${colors.reset}`;
132
-
133
- return `
134
- ${colors.bold}${svc.displayName}${colors.reset} (${svc.templateName || 'CI/CD'})
135
- Status: ${status} | Deployment: ${deployment}
136
- IP: ${svc.ipv4 || 'pending'}
137
- URL: https://${svc.cname || 'pending'}/
138
- vmID: ${svc.vmID} | serverID: ${svc.id}
139
- Size: ${svc.serverType} (${svc.cores} cores, ${svc.ramGB}GB RAM)
140
- Provider: ${svc.provider} / ${svc.datacenter || 'N/A'}
141
- Cost: ${formatPrice(svc.pricePerHour)}`.trim();
142
- }
143
-
144
- export function showHelp(command, subcommands) {
145
- console.log(`\n${colors.bold}Usage:${colors.reset} elestio ${command} <action> [options]\n`);
146
- console.log(`${colors.bold}Actions:${colors.reset}`);
147
- Object.entries(subcommands).forEach(([name, desc]) => {
148
- console.log(` ${colors.cyan}${name.padEnd(25)}${colors.reset} ${desc}`);
149
- });
150
- console.log('');
151
- }
152
-
153
- export function truncate(str, maxLen = 30) {
154
- if (!str) return '';
155
- return str.length > maxLen ? str.slice(0, maxLen - 3) + '...' : str;
156
- }
157
-
158
- // ── JSON output helper ──
159
-
160
- export function outputJson(data) {
161
- console.log(JSON.stringify(data, null, 2));
162
- }
1
+ // ── ANSI Colors ──
2
+
3
+ export const colors = {
4
+ reset: '\x1b[0m',
5
+ bold: '\x1b[1m',
6
+ dim: '\x1b[2m',
7
+ red: '\x1b[31m',
8
+ green: '\x1b[32m',
9
+ yellow: '\x1b[33m',
10
+ blue: '\x1b[34m',
11
+ magenta: '\x1b[35m',
12
+ cyan: '\x1b[36m',
13
+ white: '\x1b[37m',
14
+ gray: '\x1b[90m'
15
+ };
16
+
17
+ // ── Logging ──
18
+
19
+ export function log(type, message) {
20
+ const prefix = {
21
+ info: `${colors.blue}[INFO]${colors.reset}`,
22
+ success: `${colors.green}[SUCCESS]${colors.reset}`,
23
+ error: `${colors.red}[ERROR]${colors.reset}`,
24
+ warn: `${colors.yellow}[WARN]${colors.reset}`,
25
+ debug: `${colors.gray}[DEBUG]${colors.reset}`
26
+ };
27
+ console.log(`${prefix[type] || ''} ${message}`);
28
+ }
29
+
30
+ // ── Argument parsing ──
31
+
32
+ export function parseArgs(argv) {
33
+ const args = { _: [] };
34
+ let i = 0;
35
+
36
+ while (i < argv.length) {
37
+ const arg = argv[i];
38
+
39
+ if (arg.startsWith('--')) {
40
+ const key = arg.slice(2);
41
+ const next = argv[i + 1];
42
+
43
+ if (next && !next.startsWith('-')) {
44
+ args[key] = next;
45
+ i += 2;
46
+ } else {
47
+ args[key] = true;
48
+ i++;
49
+ }
50
+ } else if (arg.startsWith('-') && arg.length === 2) {
51
+ const key = arg.slice(1);
52
+ const next = argv[i + 1];
53
+
54
+ if (next && !next.startsWith('-')) {
55
+ args[key] = next;
56
+ i += 2;
57
+ } else {
58
+ args[key] = true;
59
+ i++;
60
+ }
61
+ } else {
62
+ args._.push(arg);
63
+ i++;
64
+ }
65
+ }
66
+
67
+ return args;
68
+ }
69
+
70
+ // ── Table formatting ──
71
+
72
+ export function formatTable(data, columns) {
73
+ if (!data || data.length === 0) return 'No data';
74
+
75
+ const widths = {};
76
+ columns.forEach(col => { widths[col.key] = col.label.length; });
77
+
78
+ data.forEach(row => {
79
+ columns.forEach(col => {
80
+ const val = String(row[col.key] ?? '');
81
+ widths[col.key] = Math.max(widths[col.key], val.length);
82
+ });
83
+ });
84
+
85
+ const header = columns.map(col => col.label.padEnd(widths[col.key])).join(' ');
86
+ const separator = columns.map(col => '-'.repeat(widths[col.key])).join(' ');
87
+ const rows = data.map(row =>
88
+ columns.map(col => String(row[col.key] ?? '').padEnd(widths[col.key])).join(' ')
89
+ );
90
+
91
+ return [header, separator, ...rows].join('\n');
92
+ }
93
+
94
+ // ── Helpers ──
95
+
96
+ export function formatBytes(bytes) {
97
+ if (bytes === 0) return '0 B';
98
+ const k = 1024;
99
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
100
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
101
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
102
+ }
103
+
104
+ export function formatPrice(pricePerHour) {
105
+ const hourly = parseFloat(pricePerHour);
106
+ const monthly = (hourly * 24 * 30).toFixed(2);
107
+ return `$${monthly}/mo`;
108
+ }
109
+
110
+ export function validateServerName(name) {
111
+ if (!name) return { valid: false, error: 'Server name is required' };
112
+ if (name.length < 3) return { valid: false, error: 'Server name must be at least 3 characters' };
113
+ if (name.length > 50) return { valid: false, error: 'Server name must be at most 50 characters' };
114
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(name) && name.length > 1) {
115
+ return { valid: false, error: 'Server name must be lowercase alphanumeric with hyphens, cannot start/end with hyphen' };
116
+ }
117
+ return { valid: true };
118
+ }
119
+
120
+ export function sleep(ms) {
121
+ return new Promise(resolve => setTimeout(resolve, ms));
122
+ }
123
+
124
+ export function formatService(svc) {
125
+ const status = svc.status === 'running'
126
+ ? `${colors.green}running${colors.reset}`
127
+ : `${colors.yellow}${svc.status}${colors.reset}`;
128
+
129
+ const deployment = svc.deploymentStatus === 'Deployed'
130
+ ? `${colors.green}Deployed${colors.reset}`
131
+ : `${colors.yellow}${svc.deploymentStatus}${colors.reset}`;
132
+
133
+ return `
134
+ ${colors.bold}${svc.displayName}${colors.reset} (${svc.templateName || 'CI/CD'})
135
+ Status: ${status} | Deployment: ${deployment}
136
+ IP: ${svc.ipv4 || 'pending'}
137
+ URL: https://${svc.cname || 'pending'}/
138
+ vmID: ${svc.vmID} | serverID: ${svc.id}
139
+ Size: ${svc.serverType} (${svc.cores} cores, ${svc.ramGB}GB RAM)
140
+ Provider: ${svc.provider} / ${svc.datacenter || 'N/A'}
141
+ Cost: ${formatPrice(svc.pricePerHour)}`.trim();
142
+ }
143
+
144
+ export function showHelp(command, subcommands) {
145
+ console.log(`\n${colors.bold}Usage:${colors.reset} elestio ${command} <action> [options]\n`);
146
+ console.log(`${colors.bold}Actions:${colors.reset}`);
147
+ Object.entries(subcommands).forEach(([name, desc]) => {
148
+ console.log(` ${colors.cyan}${name.padEnd(25)}${colors.reset} ${desc}`);
149
+ });
150
+ console.log('');
151
+ }
152
+
153
+ export function truncate(str, maxLen = 30) {
154
+ if (!str) return '';
155
+ return str.length > maxLen ? str.slice(0, maxLen - 3) + '...' : str;
156
+ }
157
+
158
+ // ── JSON output helper ──
159
+
160
+ export function outputJson(data) {
161
+ console.log(JSON.stringify(data, null, 2));
162
+ }