@easybits.cloud/cli 0.1.0 → 0.2.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/dist/index.js +184 -1
- package/package.json +11 -6
package/dist/index.js
CHANGED
|
@@ -8,7 +8,11 @@ import {
|
|
|
8
8
|
resolveBaseUrl,
|
|
9
9
|
createClientFromEnv
|
|
10
10
|
} from "@easybits.cloud/sdk";
|
|
11
|
-
import { readFileSync, existsSync, statSync } from "fs";
|
|
11
|
+
import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync } from "fs";
|
|
12
|
+
function flag(name) {
|
|
13
|
+
const i = process.argv.indexOf(name);
|
|
14
|
+
return i > -1 ? process.argv[i + 1] : void 0;
|
|
15
|
+
}
|
|
12
16
|
async function getClient() {
|
|
13
17
|
try {
|
|
14
18
|
return await createClientFromEnv();
|
|
@@ -151,6 +155,180 @@ function printMcpStdioConfig() {
|
|
|
151
155
|
};
|
|
152
156
|
console.log(JSON.stringify(config, null, 2));
|
|
153
157
|
}
|
|
158
|
+
async function init() {
|
|
159
|
+
const appDir = flag("--app-dir") ?? "/srv/app";
|
|
160
|
+
const port = flag("--port") ?? "3000";
|
|
161
|
+
if (!existsSync("package.json")) {
|
|
162
|
+
console.error(
|
|
163
|
+
"Aqu\xED no hay package.json. Corre esto en la ra\xEDz del repo de tu app."
|
|
164
|
+
);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
const workflow = `# Despliegue a EasyBits en cada push a main.
|
|
168
|
+
#
|
|
169
|
+
# El build ocurre AQU\xCD, no dentro de la m\xE1quina: as\xED la caja s\xF3lo descarga y
|
|
170
|
+
# arranca, y el sitio cabe en un tier peque\xF1o. Si el build falla, no llega a
|
|
171
|
+
# producci\xF3n y el sitio sigue en pie.
|
|
172
|
+
#
|
|
173
|
+
# Secretos que necesita el repo (Settings \u2192 Secrets and variables \u2192 Actions):
|
|
174
|
+
# EASYBITS_API_KEY tu key de easybits.cloud/dash/developer
|
|
175
|
+
# EASYBITS_SANDBOX_ID el id que devolvi\xF3 machines/launch al crear la m\xE1quina
|
|
176
|
+
#
|
|
177
|
+
# Las variables de la app (DATABASE_URL, etc.) NO van aqu\xED: se cargan una vez
|
|
178
|
+
# con PUT /machines/:id/secrets y viven cifradas en tu b\xF3veda.
|
|
179
|
+
name: Deploy a EasyBits
|
|
180
|
+
|
|
181
|
+
on:
|
|
182
|
+
push:
|
|
183
|
+
branches: [main]
|
|
184
|
+
workflow_dispatch:
|
|
185
|
+
|
|
186
|
+
concurrency: deploy-produccion
|
|
187
|
+
|
|
188
|
+
jobs:
|
|
189
|
+
deploy:
|
|
190
|
+
runs-on: ubuntu-latest
|
|
191
|
+
steps:
|
|
192
|
+
- uses: actions/checkout@v4
|
|
193
|
+
- uses: actions/setup-node@v4
|
|
194
|
+
with:
|
|
195
|
+
node-version: 24
|
|
196
|
+
cache: npm
|
|
197
|
+
- run: npm ci
|
|
198
|
+
- run: npm run build
|
|
199
|
+
|
|
200
|
+
- name: Empaquetar y desplegar
|
|
201
|
+
env:
|
|
202
|
+
EASYBITS_API_KEY: \${{ secrets.EASYBITS_API_KEY }}
|
|
203
|
+
EASYBITS_SANDBOX_ID: \${{ secrets.EASYBITS_SANDBOX_ID }}
|
|
204
|
+
EASYBITS_APP_DIR: ${appDir}
|
|
205
|
+
EASYBITS_PORT: "${port}"
|
|
206
|
+
run: node .github/scripts/easybits-deploy.mjs
|
|
207
|
+
`;
|
|
208
|
+
const script = [
|
|
209
|
+
"// Empaqueta lo que hace falta para servir y lo manda a la m\xE1quina.",
|
|
210
|
+
"// Lo genera 'easybits init'; aj\xFAstalo si tu app necesita otros archivos.",
|
|
211
|
+
"",
|
|
212
|
+
'import { createReadStream, statSync } from "node:fs";',
|
|
213
|
+
'import { execFileSync } from "node:child_process";',
|
|
214
|
+
"",
|
|
215
|
+
'const API = process.env.EASYBITS_API || "https://www.easybits.cloud/api/v2";',
|
|
216
|
+
"const KEY = process.env.EASYBITS_API_KEY;",
|
|
217
|
+
"const MACHINE = process.env.EASYBITS_SANDBOX_ID;",
|
|
218
|
+
'const APP_DIR = process.env.EASYBITS_APP_DIR || "' + appDir + '";',
|
|
219
|
+
"const PORT = Number(process.env.EASYBITS_PORT || " + port + ");",
|
|
220
|
+
'const TARBALL = "/tmp/easybits-build.tgz";',
|
|
221
|
+
'const SHA = (process.env.GITHUB_SHA || "manual").slice(0, 7);',
|
|
222
|
+
"",
|
|
223
|
+
"if (!KEY || !MACHINE) {",
|
|
224
|
+
' console.error("Faltan EASYBITS_API_KEY o EASYBITS_SANDBOX_ID en los secretos del repo.");',
|
|
225
|
+
" process.exit(1);",
|
|
226
|
+
"}",
|
|
227
|
+
"",
|
|
228
|
+
"async function api(path, body) {",
|
|
229
|
+
" const res = await fetch(API + path, {",
|
|
230
|
+
' method: "POST",',
|
|
231
|
+
' headers: { Authorization: "Bearer " + KEY, "Content-Type": "application/json" },',
|
|
232
|
+
" body: JSON.stringify(body),",
|
|
233
|
+
" });",
|
|
234
|
+
" const text = await res.text();",
|
|
235
|
+
" let data;",
|
|
236
|
+
" try { data = JSON.parse(text); } catch { data = {}; }",
|
|
237
|
+
" if (!res.ok) {",
|
|
238
|
+
' console.error("x " + path + " respondio " + res.status);',
|
|
239
|
+
" console.error(data.message || data.error || text);",
|
|
240
|
+
" process.exit(1);",
|
|
241
|
+
" }",
|
|
242
|
+
" return data;",
|
|
243
|
+
"}",
|
|
244
|
+
"",
|
|
245
|
+
"// Servir no necesita vite ni los compiladores: podarlos es la diferencia",
|
|
246
|
+
"// entre mandar 170 MB por deploy y mandar lo que la maquina ejecuta.",
|
|
247
|
+
'console.log("-> podando dependencias de desarrollo");',
|
|
248
|
+
'execFileSync("npm", ["prune", "--omit=dev"], { stdio: "inherit" });',
|
|
249
|
+
"",
|
|
250
|
+
'console.log("-> empaquetando");',
|
|
251
|
+
'execFileSync("tar", [',
|
|
252
|
+
' "czf", TARBALL, "--exclude=.git", "--exclude=node_modules/.cache",',
|
|
253
|
+
' "build", "node_modules", "package.json", "package-lock.json",',
|
|
254
|
+
"]);",
|
|
255
|
+
"const size = statSync(TARBALL).size;",
|
|
256
|
+
'console.log(" " + (size / 1048576).toFixed(1) + " MB");',
|
|
257
|
+
"",
|
|
258
|
+
"// Publico y de vida corta: la caja lo baja con curl, sin credenciales. No",
|
|
259
|
+
"// lleva secretos: esos los inyecta EasyBits desde la boveda, ya dentro.",
|
|
260
|
+
'console.log("-> subiendo");',
|
|
261
|
+
'const up = await api("/files", {',
|
|
262
|
+
' fileName: "build-" + SHA + ".tgz",',
|
|
263
|
+
' contentType: "application/gzip",',
|
|
264
|
+
" size,",
|
|
265
|
+
' access: "public",',
|
|
266
|
+
"});",
|
|
267
|
+
"const put = await fetch(up.putUrl, {",
|
|
268
|
+
' method: "PUT",',
|
|
269
|
+
' headers: { "Content-Type": "application/gzip", "Content-Length": String(size) },',
|
|
270
|
+
" body: createReadStream(TARBALL),",
|
|
271
|
+
' duplex: "half",',
|
|
272
|
+
"});",
|
|
273
|
+
"if (!put.ok) {",
|
|
274
|
+
' console.error("x la subida devolvio " + put.status);',
|
|
275
|
+
" process.exit(1);",
|
|
276
|
+
"}",
|
|
277
|
+
"",
|
|
278
|
+
"// prebuilt: la caja no reconstruye nada, solo descomprime y arranca.",
|
|
279
|
+
'console.log("-> desplegando");',
|
|
280
|
+
'const out = await api("/machines/launch", {',
|
|
281
|
+
" sandboxId: MACHINE,",
|
|
282
|
+
" archiveUrl: up.file.url,",
|
|
283
|
+
" prebuilt: true,",
|
|
284
|
+
" appDir: APP_DIR,",
|
|
285
|
+
" port: PORT,",
|
|
286
|
+
' message: "deploy " + SHA,',
|
|
287
|
+
"});",
|
|
288
|
+
"",
|
|
289
|
+
"if (out.exitCode !== 0) {",
|
|
290
|
+
' console.error("x termino con codigo " + out.exitCode);',
|
|
291
|
+
' console.error(out.buildOutput || out.startOutput || "");',
|
|
292
|
+
" process.exit(1);",
|
|
293
|
+
"}",
|
|
294
|
+
'console.log("OK desplegado - version " + out.version);',
|
|
295
|
+
'console.log(" " + out.url);',
|
|
296
|
+
""
|
|
297
|
+
].join("\n");
|
|
298
|
+
mkdirSync(".github/workflows", { recursive: true });
|
|
299
|
+
mkdirSync(".github/scripts", { recursive: true });
|
|
300
|
+
writeFileSync(".github/workflows/easybits-deploy.yml", workflow);
|
|
301
|
+
writeFileSync(".github/scripts/easybits-deploy.mjs", script);
|
|
302
|
+
console.log(`Listo. Escrib\xED:
|
|
303
|
+
.github/workflows/easybits-deploy.yml
|
|
304
|
+
.github/scripts/easybits-deploy.mjs
|
|
305
|
+
|
|
306
|
+
Falta, una sola vez:
|
|
307
|
+
|
|
308
|
+
1. Crea la m\xE1quina (si a\xFAn no la tienes):
|
|
309
|
+
|
|
310
|
+
curl -X POST https://www.easybits.cloud/api/v2/machines/launch \\
|
|
311
|
+
-H "Authorization: Bearer $EASYBITS_API_KEY" \\
|
|
312
|
+
-H "Content-Type: application/json" \\
|
|
313
|
+
-d '{"repo":"https://github.com/TU/REPO.git","branch":"main",
|
|
314
|
+
"tier":"micro","template":"node","appDir":"${appDir}","port":${port}}'
|
|
315
|
+
|
|
316
|
+
Un repo privado necesita un token en la URL:
|
|
317
|
+
https://x-access-token:TOKEN@github.com/TU/REPO.git
|
|
318
|
+
|
|
319
|
+
2. Guarda en el repo (Settings \u2192 Secrets and variables \u2192 Actions):
|
|
320
|
+
EASYBITS_API_KEY tu key
|
|
321
|
+
EASYBITS_SANDBOX_ID el id que devolvi\xF3 el paso 1
|
|
322
|
+
|
|
323
|
+
3. Si tu app usa variables secretas:
|
|
324
|
+
|
|
325
|
+
curl -X PUT https://www.easybits.cloud/api/v2/machines/SANDBOX_ID/secrets \\
|
|
326
|
+
-H "Authorization: Bearer $EASYBITS_API_KEY" \\
|
|
327
|
+
-H "Content-Type: application/json" \\
|
|
328
|
+
-d '{"DATABASE_URL":"..."}'
|
|
329
|
+
|
|
330
|
+
Desde ah\xED, cada push a main despliega.`);
|
|
331
|
+
}
|
|
154
332
|
var [cmd, sub] = [process.argv[2], process.argv[3]];
|
|
155
333
|
switch (cmd) {
|
|
156
334
|
case "login":
|
|
@@ -165,6 +343,9 @@ switch (cmd) {
|
|
|
165
343
|
case "providers":
|
|
166
344
|
providersList();
|
|
167
345
|
break;
|
|
346
|
+
case "init":
|
|
347
|
+
init();
|
|
348
|
+
break;
|
|
168
349
|
case "config":
|
|
169
350
|
printMcpConfig();
|
|
170
351
|
break;
|
|
@@ -177,6 +358,8 @@ switch (cmd) {
|
|
|
177
358
|
|
|
178
359
|
Commands:
|
|
179
360
|
login <key> Save API key
|
|
361
|
+
init Write the GitHub Actions deploy workflow
|
|
362
|
+
(--app-dir /srv/app, --port 3000)
|
|
180
363
|
files list List your files
|
|
181
364
|
files upload <f> Upload a file
|
|
182
365
|
files delete <id> Delete a file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@easybits.cloud/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Official CLI for Easybits
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Official CLI for Easybits \u2014 manage files, storage, and MCP from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"easybits": "./dist/index.js"
|
|
@@ -24,13 +24,18 @@
|
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
27
|
-
"dev": "tsup src/index.ts --format esm --watch"
|
|
27
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
28
32
|
},
|
|
29
33
|
"dependencies": {
|
|
30
|
-
"@easybits.cloud/sdk": "^0.
|
|
34
|
+
"@easybits.cloud/sdk": "^0.28.0"
|
|
31
35
|
},
|
|
32
36
|
"devDependencies": {
|
|
33
37
|
"tsup": "^8.0.0",
|
|
34
|
-
"typescript": "^5.7.0"
|
|
38
|
+
"typescript": "^5.7.0",
|
|
39
|
+
"@types/node": "^22.0.0"
|
|
35
40
|
}
|
|
36
|
-
}
|
|
41
|
+
}
|