Sign In

insta

Package Overview
Dependencies
Maintainers
3
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

insta - npm Package Compare versions

Comparing version
0.0.30
to
0.0.31
+47
-7
dist/commands/compute.js

@@ -1,2 +0,2 @@

import { ApiClient, requireProject } from '../api.js';
import { ApiClient, ApiError, requireProject } from '../api.js';
import { info, printJson, handleApproval } from '../util.js';

@@ -119,3 +119,3 @@ import { resolveComputeServiceId, q, parseVolumeGib } from './services.js';

}
// ---- volume (the persistent /data disk; attach any time, grow-only, never detach) ----
// ---- volume (the persistent /data disk; attach any time, grow-only, deletable; never detach) ----
// Render the volume read. Pure, exported for tests (mirrors serviceListLine). Every plan may view;

@@ -130,3 +130,3 @@ // only growth is paid — that gate is the backend's to enforce, so nothing here pre-blocks.

`compute ${name}: volume ${volume.sizeGib}Gi at ${volume.mountPath} (plan max ${cap.volumeGib}Gi)`,
' billing is actual data stored — the size is a cap, not a price; grow with --size (grow-only)',
' billing is actual data stored — the size is a cap, not a price; grow with --size (grow-only), delete with --delete (destroys the data)',
];

@@ -143,7 +143,32 @@ }

}
// Show, attach, or grow a compute service's /data volume. No --size: a safe read (size + mount
// path + the plan cap). --size: PUT .../volume — attaches when no volume exists, grows otherwise.
// The paid/cap/machine-count gates all belong to the backend, whose 403/400 messages carry the
// upgrade hints and must reach the user verbatim (the guard prints ApiError messages as-is).
// Render the DELETE result. Pure, exported for tests. Deleting is the only way off the volume
// path (there is no detach), so the line says what came back with it: the two constraints the
// volume imposed.
export function volumeDeleteLine(name) {
return `compute ${name}: volume deleted — the disk and its data are gone; suspend fast-wake and scale-out are back`;
}
// Map a DELETE .../volume failure. Pure, exported for tests (r2d2 review rounds 1+2: this is the
// close-call branch worth pinning). An older backend has no DELETE route, and what its 404 looks
// like depends on who answered: the real platform (Fastify, no custom notFound handler) sends its
// default body {"message":"Route DELETE:/… not found","error":"Not Found"} → ApiError message
// "Not Found"; a proxy or bodyless 404 leaves ApiError's own "HTTP 404" fallback. BOTH are the
// generic route-miss shape and mean version skew, not a bug — parroting them would send the user
// hunting the wrong thing. A backend that HAS the route names the real problem in a DOMAIN
// message ("this service has no volume", …), which must flow verbatim, 404 or not.
const GENERIC_404 = /^(HTTP 404|Not Found)$/i;
export function volumeDeleteError(e) {
if (e instanceof ApiError && e.status === 404 && GENERIC_404.test(e.message.trim())) {
return new Error('this backend does not support volume delete yet — update the platform, or delete the service to remove its volume');
}
return e;
}
// Show, attach, grow, or delete a compute service's /data volume. No flag: a safe read (size +
// mount path + the plan cap). --size: PUT .../volume — attaches when no volume exists, grows
// otherwise. --delete: DELETE .../volume — destroys the disk and its data immediately (no detach,
// no undo; billing stops now). The paid/cap/machine-count gates all belong to the backend, whose
// 403/400 messages carry the upgrade hints and must reach the user verbatim (the guard prints
// ApiError messages as-is).
export async function computeVolume(serviceName, opts) {
if (opts.delete && opts.size)
throw new Error('--delete cannot be combined with --size (one changes the volume, the other destroys it)');
const api = await ApiClient.load();

@@ -154,2 +179,17 @@ const p = await requireProject();

const id = resolveComputeServiceId(services, serviceName);
if (opts.delete) {
let res;
try {
res = await api.rawRequest('DELETE', `/projects/${p.projectId}/services/${id}/volume`);
}
catch (e) {
throw volumeDeleteError(e);
}
if (handleApproval(res))
return;
if (opts.json)
return printJson(res.body);
info(volumeDeleteLine(res.body.service?.name ?? serviceName ?? id));
return;
}
if (!opts.size) {

@@ -156,0 +196,0 @@ const r = await api.request('GET', `/projects/${p.projectId}/services/${id}/volume`);

@@ -75,2 +75,20 @@ // Build a source directory into an image and push it to Fly's registry, using a short-lived,

}
if (process.platform === 'linux') {
// A fresh Linux machine (CI containers included — insta-e2e run 31284364163) has no flyctl
// and no brew; without this branch `insta deploy <dir>` dead-ends on a hand-install of a
// third-party CLI. Official installer, pinned into ~/.fly; the current process extends its
// own PATH because the installer's shell-profile edit can't reach an already-running process.
info('flyctl not found — installing to ~/.fly (one-time)…');
const flyHome = `${process.env.HOME ?? '~'}/.fly`;
const installed = await ok('sh', ['-c', `curl -fsSL https://fly.io/install.sh | FLYCTL_INSTALL="${flyHome}" sh`], true);
if (installed) {
process.env.PATH = `${process.env.PATH}:${flyHome}/bin`;
if (await ok('flyctl', ['version'])) {
info('flyctl installed ✓');
return;
}
}
info('flyctl install failed — install manually: https://fly.io/docs/flyctl/install/');
return;
}
info('flyctl (fly CLI) not found — install it to deploy from source: https://fly.io/docs/flyctl/install/');

@@ -77,0 +95,0 @@ }

+2
-1

@@ -174,4 +174,5 @@ #!/usr/bin/env node

.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((mode, service, o) => computeCmd.computeAlwaysOn(mode, service, o)));
compute.command('volume [service]').description("Show, attach, or grow a compute service's persistent /data volume. No --size: print size, mount path, and the plan cap (any plan). --size on a volumeless service ATTACHES one (any plan at the default 1Gi; larger is paid and plan-capped; the disk mounts at /data on the next deploy); on a volume-bearing one it grows (paid plans; grow-only — a provisioned disk cannot shrink). Billing is actual data stored — the size is a cap, not a price")
compute.command('volume [service]').description("Show, attach, grow, or delete a compute service's persistent /data volume. No flag: print size, mount path, and the plan cap (any plan). --size on a volumeless service ATTACHES one (any plan at the default 1Gi; larger is paid and plan-capped; the disk mounts at /data on the next deploy); on a volume-bearing one it grows (paid plans; grow-only — a provisioned disk cannot shrink). --delete DESTROYS the disk and ALL its data immediately (no detach, no undo; billing stops now, and suspend fast-wake + scale-out return). Billing is actual data stored — the size is a cap, not a price")
.option('--size <gi>', 'new size in whole Gi, e.g. 10 (must be ≥ the current size)')
.option('--delete', 'destroy the volume and ALL its data (irreversible; download anything you need first)')
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeVolume(service, o)));

@@ -178,0 +179,0 @@ // ---- db (postgres service controls) ----

{
"name": "insta",
"version": "0.0.30",
"version": "0.0.31",
"type": "module",

@@ -5,0 +5,0 @@ "description": "InstaCloud CLI — a thin client of the platform control-plane API.",