@insforge/cli 0.1.40 → 0.1.43
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/README.md +185 -170
- package/dist/index.js +526 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
5
|
+
import { join as join8, dirname } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import * as clack14 from "@clack/prompts";
|
|
@@ -797,6 +797,47 @@ async function reportCliUsage(toolName, success, maxRetries = 1) {
|
|
|
797
797
|
}
|
|
798
798
|
}
|
|
799
799
|
|
|
800
|
+
// src/lib/analytics.ts
|
|
801
|
+
import { PostHog } from "posthog-node";
|
|
802
|
+
var POSTHOG_API_KEY = "phc_ueV1ii62wdBTkH7E70ugyeqHIHu8dFDdjs0qq3TZhJz";
|
|
803
|
+
var POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
|
|
804
|
+
var client = null;
|
|
805
|
+
function getClient() {
|
|
806
|
+
if (!POSTHOG_API_KEY) return null;
|
|
807
|
+
if (!client) {
|
|
808
|
+
client = new PostHog(POSTHOG_API_KEY, { host: POSTHOG_HOST });
|
|
809
|
+
}
|
|
810
|
+
return client;
|
|
811
|
+
}
|
|
812
|
+
function captureEvent(distinctId, event, properties) {
|
|
813
|
+
try {
|
|
814
|
+
getClient()?.capture({ distinctId, event, properties });
|
|
815
|
+
} catch {
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
function trackCommand(command, distinctId, properties) {
|
|
819
|
+
captureEvent(distinctId, "cli_command_invoked", {
|
|
820
|
+
command,
|
|
821
|
+
...properties
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
function trackDiagnose(subcommand, config) {
|
|
825
|
+
captureEvent(config.project_id, "cli_diagnose_invoked", {
|
|
826
|
+
subcommand,
|
|
827
|
+
project_id: config.project_id,
|
|
828
|
+
project_name: config.project_name,
|
|
829
|
+
org_id: config.org_id,
|
|
830
|
+
region: config.region,
|
|
831
|
+
oss_mode: config.project_id === "oss-project"
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
async function shutdownAnalytics() {
|
|
835
|
+
try {
|
|
836
|
+
if (client) await client.shutdown();
|
|
837
|
+
} catch {
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
800
841
|
// src/commands/projects/link.ts
|
|
801
842
|
function buildOssHost(appkey, region) {
|
|
802
843
|
return `https://${appkey}.${region}.insforge.app`;
|
|
@@ -831,6 +872,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
831
872
|
} else {
|
|
832
873
|
outputSuccess(`Linked to direct project at ${projectConfig2.oss_host}`);
|
|
833
874
|
}
|
|
875
|
+
trackCommand("link", "oss-org", { direct: true });
|
|
834
876
|
await installSkills(json);
|
|
835
877
|
await reportCliUsage("cli.link_direct", true, 6);
|
|
836
878
|
try {
|
|
@@ -916,6 +958,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
916
958
|
oss_host: buildOssHost(project.appkey, project.region)
|
|
917
959
|
};
|
|
918
960
|
saveProjectConfig(projectConfig);
|
|
961
|
+
trackCommand("link", project.organization_id);
|
|
919
962
|
if (json) {
|
|
920
963
|
outputJson({ success: true, project: { id: project.id, name: project.name, region: project.region } });
|
|
921
964
|
} else {
|
|
@@ -930,6 +973,8 @@ function registerProjectLinkCommand(program2) {
|
|
|
930
973
|
} catch (err) {
|
|
931
974
|
await reportCliUsage("cli.link", false);
|
|
932
975
|
handleError(err, json);
|
|
976
|
+
} finally {
|
|
977
|
+
await shutdownAnalytics();
|
|
933
978
|
}
|
|
934
979
|
});
|
|
935
980
|
}
|
|
@@ -967,7 +1012,15 @@ async function ossFetch(path4, options = {}) {
|
|
|
967
1012
|
const res = await fetch(`${config.oss_host}${path4}`, { ...options, headers });
|
|
968
1013
|
if (!res.ok) {
|
|
969
1014
|
const err = await res.json().catch(() => ({}));
|
|
970
|
-
|
|
1015
|
+
let message = err.message ?? err.error ?? `OSS request failed: ${res.status}`;
|
|
1016
|
+
if (err.nextActions) {
|
|
1017
|
+
message += `
|
|
1018
|
+
${err.nextActions}`;
|
|
1019
|
+
}
|
|
1020
|
+
if (res.status === 404 && path4.startsWith("/api/compute")) {
|
|
1021
|
+
message = "Compute services are not available on this backend.\nSelf-hosted: upgrade your InsForge instance. Cloud: contact your InsForge admin to enable compute.";
|
|
1022
|
+
}
|
|
1023
|
+
throw new CLIError(message);
|
|
971
1024
|
}
|
|
972
1025
|
return res;
|
|
973
1026
|
}
|
|
@@ -1896,31 +1949,6 @@ async function readEnvFile(cwd) {
|
|
|
1896
1949
|
return [];
|
|
1897
1950
|
}
|
|
1898
1951
|
|
|
1899
|
-
// src/lib/analytics.ts
|
|
1900
|
-
import { PostHog } from "posthog-node";
|
|
1901
|
-
var POSTHOG_API_KEY = "phc_ueV1ii62wdBTkH7E70ugyeqHIHu8dFDdjs0qq3TZhJz";
|
|
1902
|
-
var POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
|
|
1903
|
-
var client = null;
|
|
1904
|
-
function getClient() {
|
|
1905
|
-
if (!POSTHOG_API_KEY) return null;
|
|
1906
|
-
if (!client) {
|
|
1907
|
-
client = new PostHog(POSTHOG_API_KEY, { host: POSTHOG_HOST });
|
|
1908
|
-
}
|
|
1909
|
-
return client;
|
|
1910
|
-
}
|
|
1911
|
-
function captureEvent(distinctId, event, properties) {
|
|
1912
|
-
try {
|
|
1913
|
-
getClient()?.capture({ distinctId, event, properties });
|
|
1914
|
-
} catch {
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
async function shutdownAnalytics() {
|
|
1918
|
-
try {
|
|
1919
|
-
if (client) await client.shutdown();
|
|
1920
|
-
} catch {
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
1952
|
// src/commands/deployments/deploy.ts
|
|
1925
1953
|
import * as path2 from "path";
|
|
1926
1954
|
import * as fs2 from "fs/promises";
|
|
@@ -2353,6 +2381,7 @@ function registerCreateCommand(program2) {
|
|
|
2353
2381
|
}
|
|
2354
2382
|
}
|
|
2355
2383
|
await installSkills(json);
|
|
2384
|
+
trackCommand("create", orgId);
|
|
2356
2385
|
await reportCliUsage("cli.create", true, 6);
|
|
2357
2386
|
if (hasTemplate) {
|
|
2358
2387
|
const installSpinner = !json ? clack10.spinner() : null;
|
|
@@ -3261,6 +3290,445 @@ function registerSchedulesLogsCommand(schedulesCmd2) {
|
|
|
3261
3290
|
});
|
|
3262
3291
|
}
|
|
3263
3292
|
|
|
3293
|
+
// src/commands/compute/list.ts
|
|
3294
|
+
function registerComputeListCommand(computeCmd2) {
|
|
3295
|
+
computeCmd2.command("list").description("List all compute services").action(async (_opts, cmd) => {
|
|
3296
|
+
const { json } = getRootOpts(cmd);
|
|
3297
|
+
try {
|
|
3298
|
+
await requireAuth();
|
|
3299
|
+
const res = await ossFetch("/api/compute/services");
|
|
3300
|
+
const raw = await res.json();
|
|
3301
|
+
const services = Array.isArray(raw) ? raw : [];
|
|
3302
|
+
if (json) {
|
|
3303
|
+
outputJson(services);
|
|
3304
|
+
} else {
|
|
3305
|
+
if (services.length === 0) {
|
|
3306
|
+
console.log("No compute services found.");
|
|
3307
|
+
await reportCliUsage("cli.compute.list", true);
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
outputTable(
|
|
3311
|
+
["Name", "Status", "Image", "CPU", "Memory", "Endpoint"],
|
|
3312
|
+
services.map((s) => [
|
|
3313
|
+
String(s.name ?? "-"),
|
|
3314
|
+
String(s.status ?? "-"),
|
|
3315
|
+
String(s.imageUrl ?? "-"),
|
|
3316
|
+
String(s.cpu ?? "-"),
|
|
3317
|
+
s.memory ? `${s.memory}MB` : "-",
|
|
3318
|
+
String(s.endpointUrl ?? "-")
|
|
3319
|
+
])
|
|
3320
|
+
);
|
|
3321
|
+
}
|
|
3322
|
+
await reportCliUsage("cli.compute.list", true);
|
|
3323
|
+
} catch (err) {
|
|
3324
|
+
await reportCliUsage("cli.compute.list", false);
|
|
3325
|
+
handleError(err, json);
|
|
3326
|
+
}
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
// src/commands/compute/get.ts
|
|
3331
|
+
function registerComputeGetCommand(computeCmd2) {
|
|
3332
|
+
computeCmd2.command("get <id>").description("Get details of a compute service").action(async (id, _opts, cmd) => {
|
|
3333
|
+
const { json } = getRootOpts(cmd);
|
|
3334
|
+
try {
|
|
3335
|
+
await requireAuth();
|
|
3336
|
+
const res = await ossFetch(`/api/compute/services/${encodeURIComponent(id)}`);
|
|
3337
|
+
const service = await res.json();
|
|
3338
|
+
if (json) {
|
|
3339
|
+
outputJson(service);
|
|
3340
|
+
} else {
|
|
3341
|
+
outputInfo(`Name: ${service.name}`);
|
|
3342
|
+
outputInfo(`ID: ${service.id}`);
|
|
3343
|
+
outputInfo(`Status: ${service.status}`);
|
|
3344
|
+
outputInfo(`Image: ${service.imageUrl}`);
|
|
3345
|
+
outputInfo(`CPU: ${service.cpu}`);
|
|
3346
|
+
outputInfo(`Memory: ${service.memory}MB`);
|
|
3347
|
+
outputInfo(`Region: ${service.region}`);
|
|
3348
|
+
outputInfo(`Endpoint: ${service.endpointUrl ?? "n/a"}`);
|
|
3349
|
+
outputInfo(`Created: ${service.createdAt}`);
|
|
3350
|
+
}
|
|
3351
|
+
await reportCliUsage("cli.compute.get", true);
|
|
3352
|
+
} catch (err) {
|
|
3353
|
+
await reportCliUsage("cli.compute.get", false);
|
|
3354
|
+
handleError(err, json);
|
|
3355
|
+
}
|
|
3356
|
+
});
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
// src/commands/compute/create.ts
|
|
3360
|
+
function registerComputeCreateCommand(computeCmd2) {
|
|
3361
|
+
computeCmd2.command("create").description("Create and deploy a compute service").requiredOption("--name <name>", "Service name (DNS-safe, e.g. my-api)").requiredOption("--image <image>", "Docker image URL (e.g. nginx:alpine)").option("--port <port>", "Container port", "8080").option("--cpu <tier>", "CPU tier (shared-1x, shared-2x, performance-1x, performance-2x, performance-4x)", "shared-1x").option("--memory <mb>", "Memory in MB (256, 512, 1024, 2048, 4096, 8192)", "512").option("--region <region>", "Fly.io region", "iad").option("--env <json>", "Environment variables as JSON object").action(async (opts, cmd) => {
|
|
3362
|
+
const { json } = getRootOpts(cmd);
|
|
3363
|
+
try {
|
|
3364
|
+
await requireAuth();
|
|
3365
|
+
const body = {
|
|
3366
|
+
name: opts.name,
|
|
3367
|
+
imageUrl: opts.image,
|
|
3368
|
+
port: Number(opts.port),
|
|
3369
|
+
cpu: opts.cpu,
|
|
3370
|
+
memory: Number(opts.memory),
|
|
3371
|
+
region: opts.region
|
|
3372
|
+
};
|
|
3373
|
+
if (opts.env) {
|
|
3374
|
+
try {
|
|
3375
|
+
body.envVars = JSON.parse(opts.env);
|
|
3376
|
+
} catch {
|
|
3377
|
+
throw new CLIError("Invalid JSON for --env");
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
const res = await ossFetch("/api/compute/services", {
|
|
3381
|
+
method: "POST",
|
|
3382
|
+
body: JSON.stringify(body)
|
|
3383
|
+
});
|
|
3384
|
+
const service = await res.json();
|
|
3385
|
+
if (json) {
|
|
3386
|
+
outputJson(service);
|
|
3387
|
+
} else {
|
|
3388
|
+
outputSuccess(`Service "${service.name}" created [${service.status}]`);
|
|
3389
|
+
if (service.endpointUrl) {
|
|
3390
|
+
console.log(` Endpoint: ${service.endpointUrl}`);
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
await reportCliUsage("cli.compute.create", true);
|
|
3394
|
+
} catch (err) {
|
|
3395
|
+
await reportCliUsage("cli.compute.create", false);
|
|
3396
|
+
handleError(err, json);
|
|
3397
|
+
}
|
|
3398
|
+
});
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
// src/commands/compute/update.ts
|
|
3402
|
+
function registerComputeUpdateCommand(computeCmd2) {
|
|
3403
|
+
computeCmd2.command("update <id>").description("Update a compute service").option("--image <image>", "Docker image URL").option("--port <port>", "Container port").option("--cpu <tier>", "CPU tier").option("--memory <mb>", "Memory in MB").option("--region <region>", "Fly.io region").option("--env <json>", "Environment variables as JSON object").action(async (id, opts, cmd) => {
|
|
3404
|
+
const { json } = getRootOpts(cmd);
|
|
3405
|
+
try {
|
|
3406
|
+
await requireAuth();
|
|
3407
|
+
const body = {};
|
|
3408
|
+
if (opts.image) body.imageUrl = opts.image;
|
|
3409
|
+
if (opts.port) {
|
|
3410
|
+
if (!Number.isFinite(Number(opts.port))) {
|
|
3411
|
+
throw new CLIError("Invalid value for --port: must be a number");
|
|
3412
|
+
}
|
|
3413
|
+
body.port = Number(opts.port);
|
|
3414
|
+
}
|
|
3415
|
+
if (opts.cpu) body.cpu = opts.cpu;
|
|
3416
|
+
if (opts.memory) {
|
|
3417
|
+
if (!Number.isFinite(Number(opts.memory))) {
|
|
3418
|
+
throw new CLIError("Invalid value for --memory: must be a number");
|
|
3419
|
+
}
|
|
3420
|
+
body.memory = Number(opts.memory);
|
|
3421
|
+
}
|
|
3422
|
+
if (opts.region) body.region = opts.region;
|
|
3423
|
+
if (opts.env) {
|
|
3424
|
+
try {
|
|
3425
|
+
body.envVars = JSON.parse(opts.env);
|
|
3426
|
+
} catch {
|
|
3427
|
+
throw new CLIError("Invalid JSON for --env");
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
if (Object.keys(body).length === 0) {
|
|
3431
|
+
throw new CLIError("No update fields provided. Use --image, --port, --cpu, --memory, --region, or --env.");
|
|
3432
|
+
}
|
|
3433
|
+
const res = await ossFetch(`/api/compute/services/${encodeURIComponent(id)}`, {
|
|
3434
|
+
method: "PATCH",
|
|
3435
|
+
body: JSON.stringify(body)
|
|
3436
|
+
});
|
|
3437
|
+
const service = await res.json();
|
|
3438
|
+
if (json) {
|
|
3439
|
+
outputJson(service);
|
|
3440
|
+
} else {
|
|
3441
|
+
outputSuccess(`Service "${service.name}" updated [${service.status}]`);
|
|
3442
|
+
}
|
|
3443
|
+
await reportCliUsage("cli.compute.update", true);
|
|
3444
|
+
} catch (err) {
|
|
3445
|
+
await reportCliUsage("cli.compute.update", false);
|
|
3446
|
+
handleError(err, json);
|
|
3447
|
+
}
|
|
3448
|
+
});
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
// src/commands/compute/delete.ts
|
|
3452
|
+
function registerComputeDeleteCommand(computeCmd2) {
|
|
3453
|
+
computeCmd2.command("delete <id>").description("Delete a compute service and its Fly.io resources").action(async (id, _opts, cmd) => {
|
|
3454
|
+
const { json } = getRootOpts(cmd);
|
|
3455
|
+
try {
|
|
3456
|
+
await requireAuth();
|
|
3457
|
+
const res = await ossFetch(`/api/compute/services/${encodeURIComponent(id)}`, {
|
|
3458
|
+
method: "DELETE"
|
|
3459
|
+
});
|
|
3460
|
+
const data = await res.json();
|
|
3461
|
+
if (json) {
|
|
3462
|
+
outputJson(data);
|
|
3463
|
+
} else {
|
|
3464
|
+
outputSuccess("Service deleted.");
|
|
3465
|
+
}
|
|
3466
|
+
await reportCliUsage("cli.compute.delete", true);
|
|
3467
|
+
} catch (err) {
|
|
3468
|
+
await reportCliUsage("cli.compute.delete", false);
|
|
3469
|
+
handleError(err, json);
|
|
3470
|
+
}
|
|
3471
|
+
});
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
// src/commands/compute/start.ts
|
|
3475
|
+
function registerComputeStartCommand(computeCmd2) {
|
|
3476
|
+
computeCmd2.command("start <id>").description("Start a stopped compute service").action(async (id, _opts, cmd) => {
|
|
3477
|
+
const { json } = getRootOpts(cmd);
|
|
3478
|
+
try {
|
|
3479
|
+
await requireAuth();
|
|
3480
|
+
const res = await ossFetch(`/api/compute/services/${encodeURIComponent(id)}/start`, {
|
|
3481
|
+
method: "POST"
|
|
3482
|
+
});
|
|
3483
|
+
const service = await res.json();
|
|
3484
|
+
if (json) {
|
|
3485
|
+
outputJson(service);
|
|
3486
|
+
} else {
|
|
3487
|
+
outputSuccess(`Service "${service.name}" started [${service.status}]`);
|
|
3488
|
+
if (service.endpointUrl) {
|
|
3489
|
+
console.log(` Endpoint: ${service.endpointUrl}`);
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
await reportCliUsage("cli.compute.start", true);
|
|
3493
|
+
} catch (err) {
|
|
3494
|
+
await reportCliUsage("cli.compute.start", false);
|
|
3495
|
+
handleError(err, json);
|
|
3496
|
+
}
|
|
3497
|
+
});
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
// src/commands/compute/stop.ts
|
|
3501
|
+
function registerComputeStopCommand(computeCmd2) {
|
|
3502
|
+
computeCmd2.command("stop <id>").description("Stop a running compute service").action(async (id, _opts, cmd) => {
|
|
3503
|
+
const { json } = getRootOpts(cmd);
|
|
3504
|
+
try {
|
|
3505
|
+
await requireAuth();
|
|
3506
|
+
const res = await ossFetch(`/api/compute/services/${encodeURIComponent(id)}/stop`, {
|
|
3507
|
+
method: "POST"
|
|
3508
|
+
});
|
|
3509
|
+
const service = await res.json();
|
|
3510
|
+
if (json) {
|
|
3511
|
+
outputJson(service);
|
|
3512
|
+
} else {
|
|
3513
|
+
outputSuccess(`Service "${service.name}" stopped.`);
|
|
3514
|
+
}
|
|
3515
|
+
await reportCliUsage("cli.compute.stop", true);
|
|
3516
|
+
} catch (err) {
|
|
3517
|
+
await reportCliUsage("cli.compute.stop", false);
|
|
3518
|
+
handleError(err, json);
|
|
3519
|
+
}
|
|
3520
|
+
});
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
// src/commands/compute/logs.ts
|
|
3524
|
+
function registerComputeLogsCommand(computeCmd2) {
|
|
3525
|
+
computeCmd2.command("logs <id>").description("Get compute service logs (machine events)").option("--limit <n>", "Max number of log entries", "50").action(async (id, opts, cmd) => {
|
|
3526
|
+
const { json } = getRootOpts(cmd);
|
|
3527
|
+
try {
|
|
3528
|
+
await requireAuth();
|
|
3529
|
+
const limit = Math.max(1, Math.min(Number(opts.limit) || 50, 1e3));
|
|
3530
|
+
const res = await ossFetch(
|
|
3531
|
+
`/api/compute/services/${encodeURIComponent(id)}/logs?limit=${limit}`
|
|
3532
|
+
);
|
|
3533
|
+
const logs = await res.json();
|
|
3534
|
+
if (json) {
|
|
3535
|
+
outputJson(logs);
|
|
3536
|
+
} else {
|
|
3537
|
+
if (!Array.isArray(logs) || logs.length === 0) {
|
|
3538
|
+
console.log("No logs found.");
|
|
3539
|
+
await reportCliUsage("cli.compute.logs", true);
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
for (const entry of logs) {
|
|
3543
|
+
const ts = new Date(entry.timestamp).toISOString();
|
|
3544
|
+
console.log(`${ts} ${entry.message}`);
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
await reportCliUsage("cli.compute.logs", true);
|
|
3548
|
+
} catch (err) {
|
|
3549
|
+
await reportCliUsage("cli.compute.logs", false);
|
|
3550
|
+
handleError(err, json);
|
|
3551
|
+
}
|
|
3552
|
+
});
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
// src/commands/compute/deploy.ts
|
|
3556
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, renameSync } from "fs";
|
|
3557
|
+
import { join as join7 } from "path";
|
|
3558
|
+
import { execSync, spawn } from "child_process";
|
|
3559
|
+
function parseFlyToml(dir) {
|
|
3560
|
+
const tomlPath = join7(dir, "fly.toml");
|
|
3561
|
+
if (!existsSync5(tomlPath)) return {};
|
|
3562
|
+
const content = readFileSync6(tomlPath, "utf-8");
|
|
3563
|
+
const config = {};
|
|
3564
|
+
const portMatch = content.match(/internal_port\s*=\s*(\d+)/);
|
|
3565
|
+
if (portMatch) config.internalPort = Number(portMatch[1]);
|
|
3566
|
+
const regionMatch = content.match(/primary_region\s*=\s*'([^']+)'/);
|
|
3567
|
+
if (regionMatch) config.region = regionMatch[1];
|
|
3568
|
+
const memoryMatch = content.match(/memory\s*=\s*'(\d+)/);
|
|
3569
|
+
if (memoryMatch) config.memory = memoryMatch[1];
|
|
3570
|
+
const cpuKindMatch = content.match(/cpu_kind\s*=\s*'([^']+)'/);
|
|
3571
|
+
if (cpuKindMatch) config.cpuKind = cpuKindMatch[1];
|
|
3572
|
+
const cpusMatch = content.match(/cpus\s*=\s*(\d+)/);
|
|
3573
|
+
if (cpusMatch) config.cpus = Number(cpusMatch[1]);
|
|
3574
|
+
return config;
|
|
3575
|
+
}
|
|
3576
|
+
function memoryToCpuTier(cpuKind, cpus) {
|
|
3577
|
+
if (!cpuKind) return void 0;
|
|
3578
|
+
const kind = cpuKind === "performance" ? "performance" : "shared";
|
|
3579
|
+
const count = cpus ?? 1;
|
|
3580
|
+
return `${kind}-${count}x`;
|
|
3581
|
+
}
|
|
3582
|
+
function generateFlyToml(appName, opts) {
|
|
3583
|
+
const cpuParts = opts.cpu.split("-");
|
|
3584
|
+
const cpuKind = cpuParts[0] ?? "shared";
|
|
3585
|
+
const cpuCount = parseInt(cpuParts[1] ?? "1", 10);
|
|
3586
|
+
return `# Auto-generated by InsForge CLI
|
|
3587
|
+
app = '${appName}'
|
|
3588
|
+
primary_region = '${opts.region}'
|
|
3589
|
+
|
|
3590
|
+
[build]
|
|
3591
|
+
dockerfile = 'Dockerfile'
|
|
3592
|
+
|
|
3593
|
+
[http_service]
|
|
3594
|
+
internal_port = ${opts.port}
|
|
3595
|
+
force_https = true
|
|
3596
|
+
auto_stop_machines = 'stop'
|
|
3597
|
+
auto_start_machines = true
|
|
3598
|
+
min_machines_running = 0
|
|
3599
|
+
|
|
3600
|
+
[[vm]]
|
|
3601
|
+
memory = '${opts.memory}mb'
|
|
3602
|
+
cpu_kind = '${cpuKind}'
|
|
3603
|
+
cpus = ${cpuCount}
|
|
3604
|
+
`;
|
|
3605
|
+
}
|
|
3606
|
+
function checkFlyctl() {
|
|
3607
|
+
try {
|
|
3608
|
+
execSync("flyctl version", { stdio: "pipe" });
|
|
3609
|
+
} catch {
|
|
3610
|
+
throw new CLIError(
|
|
3611
|
+
"flyctl is not installed.\nInstall it with: curl -L https://fly.io/install.sh | sh\nOr: brew install flyctl"
|
|
3612
|
+
);
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
function getFlyToken() {
|
|
3616
|
+
const token = process.env.FLY_API_TOKEN;
|
|
3617
|
+
if (!token) {
|
|
3618
|
+
throw new CLIError(
|
|
3619
|
+
"FLY_API_TOKEN environment variable is required for compute deploy.\nSet it with: export FLY_API_TOKEN=<your-fly-token>"
|
|
3620
|
+
);
|
|
3621
|
+
}
|
|
3622
|
+
return token;
|
|
3623
|
+
}
|
|
3624
|
+
function registerComputeDeployCommand(computeCmd2) {
|
|
3625
|
+
computeCmd2.command("deploy [directory]").description("Build and deploy a Dockerfile as a compute service").requiredOption("--name <name>", "Service name").option("--port <port>", "Container port").option("--cpu <tier>", "CPU tier (shared-1x, shared-2x, performance-1x, etc.)").option("--memory <mb>", "Memory in MB (256, 512, 1024, 2048, 4096, 8192)").option("--region <region>", "Fly.io region").option("--env <json>", "Environment variables as JSON object").action(async (directory, opts, cmd) => {
|
|
3626
|
+
const { json } = getRootOpts(cmd);
|
|
3627
|
+
try {
|
|
3628
|
+
await requireAuth();
|
|
3629
|
+
checkFlyctl();
|
|
3630
|
+
const flyToken = getFlyToken();
|
|
3631
|
+
const dir = directory ?? process.cwd();
|
|
3632
|
+
const dockerfilePath = join7(dir, "Dockerfile");
|
|
3633
|
+
if (!existsSync5(dockerfilePath)) {
|
|
3634
|
+
throw new CLIError(`No Dockerfile found in ${dir}`);
|
|
3635
|
+
}
|
|
3636
|
+
const flyTomlDefaults = parseFlyToml(dir);
|
|
3637
|
+
const port = Number(opts.port) || flyTomlDefaults.internalPort || 8080;
|
|
3638
|
+
const cpu = opts.cpu || memoryToCpuTier(flyTomlDefaults.cpuKind, flyTomlDefaults.cpus) || "shared-1x";
|
|
3639
|
+
const memory = Number(opts.memory) || Number(flyTomlDefaults.memory) || 512;
|
|
3640
|
+
const region = opts.region || flyTomlDefaults.region || "iad";
|
|
3641
|
+
let envVars;
|
|
3642
|
+
if (opts.env) {
|
|
3643
|
+
try {
|
|
3644
|
+
envVars = JSON.parse(opts.env);
|
|
3645
|
+
} catch {
|
|
3646
|
+
throw new CLIError("Invalid JSON for --env");
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
if (!json) outputInfo(`Checking for existing service "${opts.name}"...`);
|
|
3650
|
+
const listRes = await ossFetch("/api/compute/services");
|
|
3651
|
+
const services = await listRes.json();
|
|
3652
|
+
const existing = services.find((s) => s.name === opts.name);
|
|
3653
|
+
let serviceId;
|
|
3654
|
+
let flyAppId;
|
|
3655
|
+
if (existing) {
|
|
3656
|
+
serviceId = existing.id;
|
|
3657
|
+
flyAppId = existing.flyAppId;
|
|
3658
|
+
if (!json) outputInfo(`Found existing service, redeploying...`);
|
|
3659
|
+
} else {
|
|
3660
|
+
if (!json) outputInfo(`Creating service "${opts.name}"...`);
|
|
3661
|
+
const body = {
|
|
3662
|
+
name: opts.name,
|
|
3663
|
+
imageUrl: "dockerfile",
|
|
3664
|
+
port,
|
|
3665
|
+
cpu,
|
|
3666
|
+
memory,
|
|
3667
|
+
region
|
|
3668
|
+
};
|
|
3669
|
+
if (envVars) body.envVars = envVars;
|
|
3670
|
+
const deployRes = await ossFetch("/api/compute/services/deploy", {
|
|
3671
|
+
method: "POST",
|
|
3672
|
+
body: JSON.stringify(body)
|
|
3673
|
+
});
|
|
3674
|
+
const service = await deployRes.json();
|
|
3675
|
+
serviceId = service.id;
|
|
3676
|
+
flyAppId = service.flyAppId;
|
|
3677
|
+
}
|
|
3678
|
+
const existingTomlPath = join7(dir, "fly.toml");
|
|
3679
|
+
const backupTomlPath = join7(dir, "fly.toml.insforge-backup");
|
|
3680
|
+
let hadExistingToml = false;
|
|
3681
|
+
if (existsSync5(existingTomlPath)) {
|
|
3682
|
+
hadExistingToml = true;
|
|
3683
|
+
renameSync(existingTomlPath, backupTomlPath);
|
|
3684
|
+
}
|
|
3685
|
+
const tomlContent = generateFlyToml(flyAppId, { port, memory, cpu, region });
|
|
3686
|
+
writeFileSync4(existingTomlPath, tomlContent);
|
|
3687
|
+
try {
|
|
3688
|
+
if (!json) outputInfo("Building and deploying (this may take a few minutes)...");
|
|
3689
|
+
await new Promise((resolve2, reject) => {
|
|
3690
|
+
const child = spawn(
|
|
3691
|
+
"flyctl",
|
|
3692
|
+
["deploy", "--remote-only", "--app", flyAppId, "--access-token", flyToken, "--yes"],
|
|
3693
|
+
{ cwd: dir, stdio: json ? "pipe" : "inherit" }
|
|
3694
|
+
);
|
|
3695
|
+
child.on("close", (code) => {
|
|
3696
|
+
if (code === 0) resolve2();
|
|
3697
|
+
else reject(new CLIError(`flyctl deploy failed with exit code ${code}`));
|
|
3698
|
+
});
|
|
3699
|
+
child.on("error", (err) => reject(new CLIError(`flyctl deploy error: ${err.message}`)));
|
|
3700
|
+
});
|
|
3701
|
+
if (!json) outputInfo("Syncing deployment info...");
|
|
3702
|
+
const syncRes = await ossFetch(
|
|
3703
|
+
`/api/compute/services/${encodeURIComponent(serviceId)}/sync`,
|
|
3704
|
+
{ method: "PATCH" }
|
|
3705
|
+
);
|
|
3706
|
+
const synced = await syncRes.json();
|
|
3707
|
+
if (json) {
|
|
3708
|
+
outputJson(synced);
|
|
3709
|
+
} else {
|
|
3710
|
+
outputSuccess(`Service "${synced.name}" deployed [${synced.status}]`);
|
|
3711
|
+
if (synced.endpointUrl) {
|
|
3712
|
+
console.log(` Endpoint: ${synced.endpointUrl}`);
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
await reportCliUsage("cli.compute.deploy", true);
|
|
3716
|
+
} finally {
|
|
3717
|
+
try {
|
|
3718
|
+
unlinkSync2(existingTomlPath);
|
|
3719
|
+
if (hadExistingToml) {
|
|
3720
|
+
renameSync(backupTomlPath, existingTomlPath);
|
|
3721
|
+
}
|
|
3722
|
+
} catch {
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
} catch (err) {
|
|
3726
|
+
await reportCliUsage("cli.compute.deploy", false);
|
|
3727
|
+
handleError(err, json);
|
|
3728
|
+
}
|
|
3729
|
+
});
|
|
3730
|
+
}
|
|
3731
|
+
|
|
3264
3732
|
// src/commands/logs.ts
|
|
3265
3733
|
var VALID_SOURCES = ["insforge.logs", "postgREST.logs", "postgres.logs", "function.logs", "function-deploy.logs"];
|
|
3266
3734
|
var SOURCE_LOOKUP = new Map(VALID_SOURCES.map((s) => [s.toLowerCase(), s]));
|
|
@@ -3465,6 +3933,7 @@ function registerDiagnoseMetricsCommand(diagnoseCmd2) {
|
|
|
3465
3933
|
"Metrics requires InsForge Platform login. Not available when linked via --api-key."
|
|
3466
3934
|
);
|
|
3467
3935
|
}
|
|
3936
|
+
trackDiagnose("metrics", config);
|
|
3468
3937
|
const params = new URLSearchParams({ range: opts.range });
|
|
3469
3938
|
if (opts.metrics) params.set("metrics", opts.metrics);
|
|
3470
3939
|
const res = await platformFetch(
|
|
@@ -3505,6 +3974,8 @@ function registerDiagnoseMetricsCommand(diagnoseCmd2) {
|
|
|
3505
3974
|
} catch (err) {
|
|
3506
3975
|
await reportCliUsage("cli.diagnose.metrics", false);
|
|
3507
3976
|
handleError(err, json);
|
|
3977
|
+
} finally {
|
|
3978
|
+
await shutdownAnalytics();
|
|
3508
3979
|
}
|
|
3509
3980
|
});
|
|
3510
3981
|
}
|
|
@@ -3526,6 +3997,7 @@ function registerDiagnoseAdvisorCommand(diagnoseCmd2) {
|
|
|
3526
3997
|
"Advisor requires InsForge Platform login. Not available when linked via --api-key."
|
|
3527
3998
|
);
|
|
3528
3999
|
}
|
|
4000
|
+
trackDiagnose("advisor", config);
|
|
3529
4001
|
const projectId = config.project_id;
|
|
3530
4002
|
const scanRes = await platformFetch(
|
|
3531
4003
|
`/projects/v1/${projectId}/advisor/latest`,
|
|
@@ -3569,6 +4041,8 @@ function registerDiagnoseAdvisorCommand(diagnoseCmd2) {
|
|
|
3569
4041
|
} catch (err) {
|
|
3570
4042
|
await reportCliUsage("cli.diagnose.advisor", false);
|
|
3571
4043
|
handleError(err, json);
|
|
4044
|
+
} finally {
|
|
4045
|
+
await shutdownAnalytics();
|
|
3572
4046
|
}
|
|
3573
4047
|
});
|
|
3574
4048
|
}
|
|
@@ -3719,7 +4193,9 @@ function registerDiagnoseDbCommand(diagnoseCmd2) {
|
|
|
3719
4193
|
const { json } = getRootOpts(cmd);
|
|
3720
4194
|
try {
|
|
3721
4195
|
await requireAuth();
|
|
3722
|
-
|
|
4196
|
+
const config = getProjectConfig();
|
|
4197
|
+
if (!config) throw new ProjectNotLinkedError();
|
|
4198
|
+
trackDiagnose("db", config);
|
|
3723
4199
|
const checkNames = opts.check === "all" ? ALL_CHECKS : opts.check.split(",").map((s) => s.trim());
|
|
3724
4200
|
const results = {};
|
|
3725
4201
|
for (const name of checkNames) {
|
|
@@ -3754,6 +4230,8 @@ function registerDiagnoseDbCommand(diagnoseCmd2) {
|
|
|
3754
4230
|
} catch (err) {
|
|
3755
4231
|
await reportCliUsage("cli.diagnose.db", false);
|
|
3756
4232
|
handleError(err, json);
|
|
4233
|
+
} finally {
|
|
4234
|
+
await shutdownAnalytics();
|
|
3757
4235
|
}
|
|
3758
4236
|
});
|
|
3759
4237
|
}
|
|
@@ -3807,7 +4285,9 @@ function registerDiagnoseLogsCommand(diagnoseCmd2) {
|
|
|
3807
4285
|
const { json } = getRootOpts(cmd);
|
|
3808
4286
|
try {
|
|
3809
4287
|
await requireAuth();
|
|
3810
|
-
|
|
4288
|
+
const config = getProjectConfig();
|
|
4289
|
+
if (!config) throw new ProjectNotLinkedError();
|
|
4290
|
+
trackDiagnose("logs", config);
|
|
3811
4291
|
const limit = parseInt(opts.limit, 10) || 100;
|
|
3812
4292
|
const sources = opts.source ? [opts.source] : [...LOG_SOURCES];
|
|
3813
4293
|
const summaries = [];
|
|
@@ -3840,6 +4320,8 @@ function registerDiagnoseLogsCommand(diagnoseCmd2) {
|
|
|
3840
4320
|
} catch (err) {
|
|
3841
4321
|
await reportCliUsage("cli.diagnose.logs", false);
|
|
3842
4322
|
handleError(err, json);
|
|
4323
|
+
} finally {
|
|
4324
|
+
await shutdownAnalytics();
|
|
3843
4325
|
}
|
|
3844
4326
|
});
|
|
3845
4327
|
}
|
|
@@ -3858,6 +4340,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
3858
4340
|
const projectId = config.project_id;
|
|
3859
4341
|
const projectName = config.project_name;
|
|
3860
4342
|
const ossMode = config.project_id === "oss-project";
|
|
4343
|
+
trackDiagnose("report", config);
|
|
3861
4344
|
const metricsPromise = ossMode ? Promise.reject(new Error("Platform login required (linked via --api-key)")) : fetchMetricsSummary(projectId, apiUrl);
|
|
3862
4345
|
const advisorPromise = ossMode ? Promise.reject(new Error("Platform login required (linked via --api-key)")) : fetchAdvisorSummary(projectId, apiUrl);
|
|
3863
4346
|
const [metricsResult, advisorResult, dbResult, logsResult] = await Promise.allSettled([
|
|
@@ -3977,6 +4460,8 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
3977
4460
|
} catch (err) {
|
|
3978
4461
|
await reportCliUsage("cli.diagnose", false);
|
|
3979
4462
|
handleError(err, json);
|
|
4463
|
+
} finally {
|
|
4464
|
+
await shutdownAnalytics();
|
|
3980
4465
|
}
|
|
3981
4466
|
});
|
|
3982
4467
|
registerDiagnoseMetricsCommand(diagnoseCmd2);
|
|
@@ -3992,7 +4477,7 @@ function formatBytesCompact(bytes) {
|
|
|
3992
4477
|
|
|
3993
4478
|
// src/index.ts
|
|
3994
4479
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3995
|
-
var pkg = JSON.parse(
|
|
4480
|
+
var pkg = JSON.parse(readFileSync7(join8(__dirname, "../package.json"), "utf-8"));
|
|
3996
4481
|
var INSFORGE_LOGO = `
|
|
3997
4482
|
\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
3998
4483
|
\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
|
|
@@ -4060,6 +4545,16 @@ registerLogsCommand(program);
|
|
|
4060
4545
|
registerMetadataCommand(program);
|
|
4061
4546
|
var diagnoseCmd = program.command("diagnose");
|
|
4062
4547
|
registerDiagnoseCommands(diagnoseCmd);
|
|
4548
|
+
var computeCmd = program.command("compute").description("Manage compute services (Docker containers on Fly.io)");
|
|
4549
|
+
registerComputeListCommand(computeCmd);
|
|
4550
|
+
registerComputeGetCommand(computeCmd);
|
|
4551
|
+
registerComputeCreateCommand(computeCmd);
|
|
4552
|
+
registerComputeDeployCommand(computeCmd);
|
|
4553
|
+
registerComputeUpdateCommand(computeCmd);
|
|
4554
|
+
registerComputeDeleteCommand(computeCmd);
|
|
4555
|
+
registerComputeStartCommand(computeCmd);
|
|
4556
|
+
registerComputeStopCommand(computeCmd);
|
|
4557
|
+
registerComputeLogsCommand(computeCmd);
|
|
4063
4558
|
var schedulesCmd = program.command("schedules").description("Manage scheduled tasks (cron jobs)");
|
|
4064
4559
|
registerSchedulesListCommand(schedulesCmd);
|
|
4065
4560
|
registerSchedulesGetCommand(schedulesCmd);
|