@hynaweb/stage 1.3.0 ā 2.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/bin/cli.js +202 -90
- package/package.json +24 -11
- package/Test#1/dummy.html +0 -84
- package/Test#1/index.js +0 -128
- package/Test#1/package.json +0 -16
- package/dashboard.html +0 -240
- package/dummy.html +0 -85
- package/hyna-deploy.zip +0 -0
- package/hyna-jam.zip +0 -0
- package/hyna-live.zip +0 -0
- package/hyna-stage-v1.zip +0 -0
- package/index.html +0 -128
- package/index.js +0 -189
- package/inject_logo.py +0 -32
- package/logo.png +0 -0
package/bin/cli.js
CHANGED
|
@@ -1,96 +1,208 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const SUPABASE_KEY = 'sb_publishable_sG0q8k6txKHOihBduyr67w_UKfolcvj';
|
|
10
|
-
|
|
11
|
-
const configPath = path.join(require('os').homedir(), '.hyna-stage');
|
|
12
|
-
|
|
13
|
-
async function getApiKey() {
|
|
14
|
-
// Check for --key argument first (Auto-Injection)
|
|
15
|
-
const keyArg = process.argv.find(arg => arg.startsWith('--key='));
|
|
16
|
-
if (keyArg) {
|
|
17
|
-
const key = keyArg.split('=')[1];
|
|
18
|
-
fs.writeFileSync(configPath, key.trim()); // Save it for next time
|
|
19
|
-
return key.trim();
|
|
20
|
-
}
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import readline from 'node:readline/promises';
|
|
21
9
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
10
|
+
const VERSION = '2.0.0';
|
|
11
|
+
const CONFIG_PATH = path.join(os.homedir(), '.hyna-stage');
|
|
12
|
+
const BASE_URL = process.env.HYNA_BASE_URL || 'https://mczesspakhkomlnmfstw.supabase.co/functions/v1';
|
|
13
|
+
const EP = {
|
|
14
|
+
login: process.env.HYNA_LOGIN_ENDPOINT || 'login',
|
|
15
|
+
deploy: 'deploy-stage',
|
|
16
|
+
destroy: 'destroy-stage',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function readConfig() {
|
|
20
|
+
try {
|
|
21
|
+
if (!fs.existsSync(CONFIG_PATH)) return {};
|
|
22
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf8').trim();
|
|
23
|
+
if (!raw) return {};
|
|
24
|
+
return raw.startsWith('{') ? JSON.parse(raw) : { token: raw };
|
|
25
|
+
} catch { return {}; }
|
|
26
|
+
}
|
|
27
|
+
function writeConfig(c) {
|
|
28
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(c, null, 2), { mode: 0o600 });
|
|
29
|
+
try { fs.chmodSync(CONFIG_PATH, 0o600); } catch {}
|
|
30
|
+
}
|
|
31
|
+
function requireAuth() {
|
|
32
|
+
const c = readConfig();
|
|
33
|
+
if (!c.token) fail('Not logged in. Run: hyna-stage login');
|
|
34
|
+
return c;
|
|
35
|
+
}
|
|
36
|
+
const isJson = () => process.argv.includes('--json');
|
|
37
|
+
const info = (m) => { if (!isJson()) console.log(m); };
|
|
38
|
+
const out = (d) => { if (isJson()) console.log(JSON.stringify(d, null, 2)); };
|
|
39
|
+
function fail(m, code = 1) {
|
|
40
|
+
if (isJson()) console.log(JSON.stringify({ ok: false, error: m }));
|
|
41
|
+
else console.error(chalk.red('X ') + m);
|
|
42
|
+
process.exit(code);
|
|
36
43
|
}
|
|
44
|
+
function promptHidden(question) {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
process.stdout.write(question);
|
|
47
|
+
const stdin = process.stdin;
|
|
48
|
+
stdin.setRawMode(true);
|
|
49
|
+
stdin.resume();
|
|
50
|
+
stdin.setEncoding('utf8');
|
|
51
|
+
let buf = '';
|
|
52
|
+
const onData = (ch) => {
|
|
53
|
+
if (ch === '\r' || ch === '\n' || ch === '\u0004') {
|
|
54
|
+
stdin.setRawMode(false); stdin.pause();
|
|
55
|
+
stdin.removeListener('data', onData);
|
|
56
|
+
process.stdout.write('\n'); resolve(buf);
|
|
57
|
+
} else if (ch === '\u0003') {
|
|
58
|
+
stdin.setRawMode(false); process.stdout.write('\n'); process.exit(130);
|
|
59
|
+
} else if (ch === '\u007f' || ch === '\b') {
|
|
60
|
+
buf = buf.slice(0, -1);
|
|
61
|
+
} else { buf += ch; }
|
|
62
|
+
};
|
|
63
|
+
stdin.on('data', onData);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function api(endpoint, { body, auth = true, method = 'POST' } = {}) {
|
|
67
|
+
const cfg = readConfig();
|
|
68
|
+
const headers = { 'Content-Type': 'application/json', 'x-hyna-key': process.env.HYNA_STAGE_KEY || 'hyna_xc5ub3l52qh' };
|
|
69
|
+
if (auth) headers.Authorization = `Bearer ${cfg.token}`;
|
|
70
|
+
const res = await fetch(`${BASE_URL}/${endpoint}`, {
|
|
71
|
+
method, headers, body: body ? JSON.stringify(body) : undefined,
|
|
72
|
+
});
|
|
73
|
+
const text = await res.text();
|
|
74
|
+
let data;
|
|
75
|
+
try { data = JSON.parse(text); } catch { data = { raw: text }; }
|
|
76
|
+
if (res.status === 401 || res.status === 403)
|
|
77
|
+
throw new Error('Session expired. Run: hyna-stage login');
|
|
78
|
+
if (!res.ok) throw new Error(data?.error || data?.message || res.statusText);
|
|
79
|
+
return data;
|
|
80
|
+
}
|
|
81
|
+
const program = new Command();
|
|
82
|
+
program
|
|
83
|
+
.name('hyna-stage')
|
|
84
|
+
.description('Ephemeral cloud sandboxes in seconds')
|
|
85
|
+
.version(VERSION, '-v, --version')
|
|
86
|
+
.option('--json', 'Machine-readable JSON output')
|
|
87
|
+
.addHelpCommand(false);
|
|
37
88
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
headers: {
|
|
49
|
-
'Authorization': 'Bearer ' + apiKey,
|
|
50
|
-
'apikey': SUPABASE_KEY,
|
|
51
|
-
'Content-Type': 'application/json'
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
console.log('\nā
Environment is LIVE!');
|
|
56
|
-
console.log('š URL: \x1b[36m' + res.data.url + '\x1b[0m');
|
|
57
|
-
console.log('š Sandbox ID: ' + res.data.sandbox_id);
|
|
58
|
-
console.log('ā±ļø Auto-destruct in: 1 hour\n');
|
|
59
|
-
|
|
60
|
-
} catch (err) {
|
|
61
|
-
console.error('ā Error:', err.response?.data?.error || err.message);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
else if (command === 'down') {
|
|
65
|
-
const sandboxId = process.argv.find(arg => !arg.startsWith('--') && arg !== 'down' && arg !== 'up');
|
|
66
|
-
|
|
67
|
-
if (!sandboxId) {
|
|
68
|
-
console.error('ā Please provide a sandbox ID: hyna-stage down <sandbox_id>');
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
console.log('šļø Destroying sandbox ' + sandboxId + '...');
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
await axios.post(DESTROY_URL, { sandbox_id: sandboxId }, {
|
|
76
|
-
headers: {
|
|
77
|
-
'Authorization': 'Bearer ' + apiKey,
|
|
78
|
-
'apikey': SUPABASE_KEY,
|
|
79
|
-
'Content-Type': 'application/json'
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
console.log('\nā
Environment destroyed successfully.\n');
|
|
84
|
-
|
|
85
|
-
} catch (err) {
|
|
86
|
-
console.error('ā Error:', err.response?.data?.error || err.message);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
console.log('Usage:');
|
|
91
|
-
console.log(' hyna-stage up Spin up a new environment');
|
|
92
|
-
console.log(' hyna-stage down <id> Destroy a specific environment');
|
|
89
|
+
program.command('login')
|
|
90
|
+
.description('Sign in with username and password')
|
|
91
|
+
.argument('[username]', 'Username (prompted if omitted)')
|
|
92
|
+
.option('-p, --password <password>', 'Password (prompted hidden if omitted)')
|
|
93
|
+
.action(async (username, opts) => {
|
|
94
|
+
let user = username;
|
|
95
|
+
if (!user) {
|
|
96
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
97
|
+
user = (await rl.question('Username: ')).trim();
|
|
98
|
+
rl.close();
|
|
93
99
|
}
|
|
94
|
-
|
|
100
|
+
let pass = opts.password;
|
|
101
|
+
if (!pass) pass = await promptHidden('Password: ');
|
|
102
|
+
if (!user || !pass) fail('Username and password are required.');
|
|
103
|
+
const spinner = isJson() ? null : ora('Signing in...').start();
|
|
104
|
+
try {
|
|
105
|
+
const d = await api(EP.login, { auth: false, body: { username: user, password: pass } });
|
|
106
|
+
const token = d.token || d.access_token || d.session?.access_token;
|
|
107
|
+
if (!token) throw new Error('Server did not return a token.');
|
|
108
|
+
writeConfig({ username: user, token, baseUrl: BASE_URL, savedAt: new Date().toISOString() });
|
|
109
|
+
spinner?.succeed(`Signed in as ${chalk.cyan(user)}`);
|
|
110
|
+
out({ ok: true, username: user });
|
|
111
|
+
} catch (e) { spinner?.fail(e.message); fail(e.message); }
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
program.command('logout')
|
|
115
|
+
.description('Sign out and remove saved token')
|
|
116
|
+
.action(() => {
|
|
117
|
+
const c = readConfig();
|
|
118
|
+
if (!c.token) { info(chalk.yellow('Already logged out.')); return; }
|
|
119
|
+
writeConfig({ ...c, token: undefined, username: undefined });
|
|
120
|
+
out({ ok: true, loggedOut: true });
|
|
121
|
+
info(chalk.green('OK ') + 'Logged out.');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
program.command('whoami')
|
|
125
|
+
.description('Show current session')
|
|
126
|
+
.action(() => {
|
|
127
|
+
const c = readConfig();
|
|
128
|
+
if (!c.token) { out({ ok: true, loggedIn: false }); info(chalk.yellow('Not logged in.')); return; }
|
|
129
|
+
out({ ok: true, loggedIn: true, username: c.username, baseUrl: c.baseUrl || BASE_URL });
|
|
130
|
+
info(`Signed in as ${chalk.cyan(c.username || 'unknown')} -> ${chalk.dim(c.baseUrl || BASE_URL)}`);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
program.command('up')
|
|
134
|
+
.alias('deploy')
|
|
135
|
+
.description('Deploy a sandbox from a file')
|
|
136
|
+
.argument('<file>', 'File to deploy')
|
|
137
|
+
.option('-n, --name <name>', 'Sandbox name')
|
|
138
|
+
.option('-t, --ttl <seconds>', 'Time-to-live in seconds', '600')
|
|
139
|
+
.action(async (file, opts) => {
|
|
140
|
+
requireAuth();
|
|
141
|
+
if (!fs.existsSync(file)) fail(`File not found: ${file}`);
|
|
142
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
143
|
+
const payload = { file: path.basename(file), name: opts.name, ttl: Number(opts.ttl), version: VERSION, content };
|
|
144
|
+
const spinner = isJson() ? null : ora('Deploying sandbox...').start();
|
|
145
|
+
try {
|
|
146
|
+
const d = await api(EP.deploy, { body: payload });
|
|
147
|
+
spinner?.succeed('Sandbox is live');
|
|
148
|
+
out({ ok: true, ...d });
|
|
149
|
+
if (!isJson()) {
|
|
150
|
+
console.log(`\n ID ${d.id}`);
|
|
151
|
+
console.log(` URL ${chalk.cyan(d.url)}`);
|
|
152
|
+
console.log(` TTL ${d.ttl || opts.ttl}s\n`);
|
|
153
|
+
console.log(` Destroy: hyna-stage down ${d.id}\n`);
|
|
154
|
+
}
|
|
155
|
+
} catch (e) { spinner?.fail(e.message); fail(e.message); }
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
program.command('down')
|
|
159
|
+
.alias('destroy')
|
|
160
|
+
.alias('kill')
|
|
161
|
+
.description('Destroy a running sandbox')
|
|
162
|
+
.argument('<id>', 'Sandbox ID')
|
|
163
|
+
.action(async (id) => {
|
|
164
|
+
requireAuth();
|
|
165
|
+
const spinner = isJson() ? null : ora('Destroying sandbox...').start();
|
|
166
|
+
try {
|
|
167
|
+
const d = await api(EP.destroy, { body: { sandbox_id: id } });
|
|
168
|
+
spinner?.succeed(`Sandbox ${id} destroyed`);
|
|
169
|
+
out({ ok: true, id, ...d });
|
|
170
|
+
} catch (e) { spinner?.fail(e.message); fail(e.message); }
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
program.command('help')
|
|
176
|
+
.description('Show detailed help with examples')
|
|
177
|
+
.argument('[command]')
|
|
178
|
+
.action((cmd) => {
|
|
179
|
+
if (cmd) {
|
|
180
|
+
const c = program.commands.find((x) => x.name() === cmd || x.aliases().includes(cmd));
|
|
181
|
+
if (!c) fail(`Unknown command: ${cmd}`);
|
|
182
|
+
return c.outputHelp();
|
|
183
|
+
}
|
|
184
|
+
console.log(`
|
|
185
|
+
HYNA-Stage CLI v${VERSION}
|
|
186
|
+
Ephemeral cloud sandboxes in seconds.
|
|
187
|
+
|
|
188
|
+
USAGE
|
|
189
|
+
hyna-stage <command> [options]
|
|
190
|
+
|
|
191
|
+
AUTH
|
|
192
|
+
login [username] Sign in (prompts for password)
|
|
193
|
+
logout Remove saved token
|
|
194
|
+
whoami Show current session
|
|
195
|
+
|
|
196
|
+
SANDBOXES
|
|
197
|
+
up <file> Deploy a sandbox (-n name, -t ttl)
|
|
198
|
+
down <id> Destroy a sandbox
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
GLOBAL
|
|
202
|
+
--json Machine-readable output
|
|
203
|
+
-v, --version Show version
|
|
204
|
+
`);
|
|
205
|
+
});
|
|
95
206
|
|
|
96
|
-
|
|
207
|
+
program.on('command:*', (args) => fail(`Unknown command: ${args[0]}. Run: hyna-stage help`));
|
|
208
|
+
program.parseAsync(process.argv).catch((e) => fail(e.message));
|
package/package.json
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hynaweb/stage",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Ephemeral cloud
|
|
5
|
-
"
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Ephemeral cloud sandboxes in seconds. Built for the KODA Hacker Stage.",
|
|
5
|
+
"author": "Harun Solo Dev",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"bin": {
|
|
7
|
-
"hyna-stage": "
|
|
8
|
+
"hyna-stage": "bin/cli.js"
|
|
8
9
|
},
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/cli.js",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"sandbox",
|
|
21
|
+
"e2b",
|
|
22
|
+
"cloud",
|
|
23
|
+
"hyna",
|
|
24
|
+
"koda"
|
|
25
|
+
],
|
|
12
26
|
"dependencies": {
|
|
13
27
|
"chalk": "^5.3.0",
|
|
14
|
-
"commander": "^
|
|
15
|
-
"
|
|
16
|
-
"ora": "^7.0.1"
|
|
28
|
+
"commander": "^12.1.0",
|
|
29
|
+
"ora": "^8.0.1"
|
|
17
30
|
}
|
|
18
|
-
}
|
|
31
|
+
}
|
package/Test#1/dummy.html
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>HYNA-Stage Test</title>
|
|
7
|
-
<style>
|
|
8
|
-
body {
|
|
9
|
-
background-color: #030303;
|
|
10
|
-
color: #ffffff;
|
|
11
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
12
|
-
display: flex;
|
|
13
|
-
flex-direction: column;
|
|
14
|
-
align-items: center;
|
|
15
|
-
justify-content: center;
|
|
16
|
-
height: 100vh;
|
|
17
|
-
margin: 0;
|
|
18
|
-
overflow: hidden;
|
|
19
|
-
}
|
|
20
|
-
.glow {
|
|
21
|
-
position: absolute;
|
|
22
|
-
width: 300px;
|
|
23
|
-
height: 300px;
|
|
24
|
-
background: #3B82F6;
|
|
25
|
-
border-radius: 50%;
|
|
26
|
-
filter: blur(80px);
|
|
27
|
-
opacity: 0.3;
|
|
28
|
-
z-index: -1;
|
|
29
|
-
}
|
|
30
|
-
.card {
|
|
31
|
-
background: rgba(10, 10, 10, 0.8);
|
|
32
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
33
|
-
padding: 40px;
|
|
34
|
-
border-radius: 20px;
|
|
35
|
-
text-align: center;
|
|
36
|
-
backdrop-filter: blur(10px);
|
|
37
|
-
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
|
38
|
-
}
|
|
39
|
-
h1 {
|
|
40
|
-
font-size: 2rem;
|
|
41
|
-
margin-bottom: 10px;
|
|
42
|
-
background: linear-gradient(to right, #60A5FA, #3B82F6);
|
|
43
|
-
-webkit-background-clip: text;
|
|
44
|
-
-webkit-text-fill-color: transparent;
|
|
45
|
-
}
|
|
46
|
-
p {
|
|
47
|
-
color: #9CA3AF;
|
|
48
|
-
margin-bottom: 20px;
|
|
49
|
-
}
|
|
50
|
-
.status {
|
|
51
|
-
display: inline-block;
|
|
52
|
-
padding: 6px 12px;
|
|
53
|
-
background: rgba(34, 197, 94, 0.1);
|
|
54
|
-
color: #22c55e;
|
|
55
|
-
border: 1px solid rgba(34, 197, 94, 0.2);
|
|
56
|
-
border-radius: 20px;
|
|
57
|
-
font-size: 0.85rem;
|
|
58
|
-
font-weight: bold;
|
|
59
|
-
}
|
|
60
|
-
.time {
|
|
61
|
-
margin-top: 20px;
|
|
62
|
-
font-family: monospace;
|
|
63
|
-
color: #6B7280;
|
|
64
|
-
font-size: 0.9rem;
|
|
65
|
-
}
|
|
66
|
-
</style>
|
|
67
|
-
</head>
|
|
68
|
-
<body>
|
|
69
|
-
<div class="glow"></div>
|
|
70
|
-
<div class="card">
|
|
71
|
-
<h1>š HYNA-Stage is LIVE</h1>
|
|
72
|
-
<p>This environment was provisioned in under 5 seconds.</p>
|
|
73
|
-
<div class="status">ā RUNNING</div>
|
|
74
|
-
<div class="time" id="clock">Loading time...</div>
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
<script>
|
|
78
|
-
setInterval(() => {
|
|
79
|
-
const now = new Date();
|
|
80
|
-
document.getElementById('clock').innerText = 'Server Time: ' + now.toLocaleTimeString();
|
|
81
|
-
}, 1000);
|
|
82
|
-
</script>
|
|
83
|
-
</body>
|
|
84
|
-
</html>
|
package/Test#1/index.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { program } from 'commander';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import ora from 'ora';
|
|
8
|
-
import inquirer from 'inquirer';
|
|
9
|
-
|
|
10
|
-
const DEPLOY_URL = "https://mczesspakhkomlnmfstw.supabase.co/functions/v1/deploy-stage";
|
|
11
|
-
const DESTROY_URL = "https://mczesspakhkomlnmfstw.supabase.co/functions/v1/destroy-stage";
|
|
12
|
-
|
|
13
|
-
program
|
|
14
|
-
.name('hyna-stage')
|
|
15
|
-
.description('Ephemeral cloud environments in 5 seconds.')
|
|
16
|
-
.version('1.0.7');
|
|
17
|
-
|
|
18
|
-
// āā UP COMMAND āā
|
|
19
|
-
program
|
|
20
|
-
.command('up')
|
|
21
|
-
.description('Deploy a file to a temporary HYNA environment')
|
|
22
|
-
.option('-f, --file <file>', 'File to deploy', 'index.html')
|
|
23
|
-
.option('-k, --key <key>', 'Your HYNA API key')
|
|
24
|
-
.action(async (options) => {
|
|
25
|
-
let apiKey = options.key;
|
|
26
|
-
if (!apiKey) {
|
|
27
|
-
const answers = await inquirer.prompt([
|
|
28
|
-
{
|
|
29
|
-
type: 'input',
|
|
30
|
-
name: 'key',
|
|
31
|
-
message: 'Enter your HYNA API Key:',
|
|
32
|
-
validate: (input) => input.length > 0 ? true : 'API Key is required'
|
|
33
|
-
}
|
|
34
|
-
]);
|
|
35
|
-
apiKey = answers.key;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const filePath = path.resolve(process.cwd(), options.file);
|
|
39
|
-
if (!fs.existsSync(filePath)) {
|
|
40
|
-
console.error(chalk.red(`ā Error: File not found -> ${options.file}`));
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const stats = fs.statSync(filePath);
|
|
45
|
-
const spinner = ora('Spinning up cloud environment...').start();
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
let fileContent = fs.readFileSync(filePath);
|
|
49
|
-
const isLarge = stats.size > 50000;
|
|
50
|
-
|
|
51
|
-
if (isLarge) {
|
|
52
|
-
spinner.text = 'Encoding large file for transport...';
|
|
53
|
-
fileContent = fileContent.toString('base64');
|
|
54
|
-
} else {
|
|
55
|
-
fileContent = fileContent.toString('utf-8');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
spinner.text = 'Deploying to E2B MicroVM...';
|
|
59
|
-
|
|
60
|
-
const response = await fetch(DEPLOY_URL, {
|
|
61
|
-
method: 'POST',
|
|
62
|
-
headers: {
|
|
63
|
-
'Content-Type': 'application/json',
|
|
64
|
-
'Authorization': `Bearer ${apiKey}`
|
|
65
|
-
},
|
|
66
|
-
body: JSON.stringify({ apiKey: apiKey, code: fileContent, isBase64: isLarge })
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const data = await response.json();
|
|
70
|
-
if (!response.ok) throw new Error(data.error || `Server responded with ${response.status}`);
|
|
71
|
-
|
|
72
|
-
spinner.succeed(chalk.green('Environment is LIVE!'));
|
|
73
|
-
|
|
74
|
-
console.log('');
|
|
75
|
-
console.log(chalk.bgBlue.white.bold(` š URL: ${data.url} `));
|
|
76
|
-
console.log(chalk.gray(` Sandbox ID: ${data.sandbox_id}`));
|
|
77
|
-
console.log(chalk.gray(` (Auto-destroys in 1 hour)\n`));
|
|
78
|
-
|
|
79
|
-
} catch (err) {
|
|
80
|
-
spinner.fail(chalk.red('Deployment failed'));
|
|
81
|
-
console.error(chalk.red(`\nā ${err.message}\n`));
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// āā DOWN COMMAND āā
|
|
86
|
-
program
|
|
87
|
-
.command('down')
|
|
88
|
-
.description('Destroy an active environment early')
|
|
89
|
-
.argument('<sandbox_id>', 'The Sandbox ID to destroy')
|
|
90
|
-
.option('-k, --key <key>', 'Your HYNA API key')
|
|
91
|
-
.action(async (sandboxId, options) => {
|
|
92
|
-
let apiKey = options.key;
|
|
93
|
-
if (!apiKey) {
|
|
94
|
-
const answers = await inquirer.prompt([
|
|
95
|
-
{
|
|
96
|
-
type: 'input',
|
|
97
|
-
name: 'key',
|
|
98
|
-
message: 'Enter your HYNA API Key:',
|
|
99
|
-
validate: (input) => input.length > 0 ? true : 'API Key is required'
|
|
100
|
-
}
|
|
101
|
-
]);
|
|
102
|
-
apiKey = answers.key;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const spinner = ora(`Destroying sandbox ${sandboxId}...`).start();
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
const response = await fetch(DESTROY_URL, {
|
|
109
|
-
method: 'POST',
|
|
110
|
-
headers: {
|
|
111
|
-
'Content-Type': 'application/json',
|
|
112
|
-
'Authorization': `Bearer ${apiKey}`
|
|
113
|
-
},
|
|
114
|
-
body: JSON.stringify({ sandbox_id: sandboxId })
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const data = await response.json();
|
|
118
|
-
if (!response.ok) throw new Error(data.error || 'Failed to destroy sandbox');
|
|
119
|
-
|
|
120
|
-
spinner.succeed(chalk.green('Environment destroyed successfully!'));
|
|
121
|
-
console.log(chalk.gray(` The E2B VM has been terminated.\n`));
|
|
122
|
-
} catch (err) {
|
|
123
|
-
spinner.fail(chalk.red('Destroy failed'));
|
|
124
|
-
console.error(chalk.red(`\nā ${err.message}\n`));
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
program.parse();
|
package/Test#1/package.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@hynaweb/stage",
|
|
3
|
-
"version": "1.0.7",
|
|
4
|
-
"description": "Ephemeral cloud environments in 5 seconds.",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"hyna-stage": "./index.js"
|
|
8
|
-
},
|
|
9
|
-
"type": "module",
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"chalk": "^5.3.0",
|
|
12
|
-
"commander": "^11.0.0",
|
|
13
|
-
"inquirer": "^9.2.12",
|
|
14
|
-
"ora": "^7.0.1"
|
|
15
|
-
}
|
|
16
|
-
}
|