@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.
Files changed (2) hide show
  1. package/dist/index.js +184 -1
  2. 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.1.0",
4
- "description": "Official CLI for Easybits — manage files, storage, and MCP from your terminal",
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.1.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
+ }